Subject: 802.11 transmit power API (long)
To: None <>
From: David Young <>
List: tech-net
Date: 02/18/2003 16:49:07
802.11 Transmit Power Control API for NetBSD


  This document describes a universal API for adjusting the transmit power
  of 802.11 NICs.  Transmit power control (TPC) helps operators re-use
  channels and meet power output regulations. The TPC API adds ioctls
  SIOCG80211TPC and SIOCS80211TPC for unitless power adjustments and for
  relative centibel and milliwatt power adjustments. It is possible to use
  the API to query the maximum power level and to calibrate power levels.

  This document contains sections Motivation, API Overview, API Reference,
  Implementation Details, Future Work, and Examples.


  Fine-grained transmit power control (TPC) improves the bandwidth,
  robustness, and versatility of 802.11 gear.  TPC facilitates frequency
  re-use: it permits many stations to "whisper" to each other in the same
  space where only a few could "shout," which translates to an increase
  in network bandwidth. It allows for adaptation to fades caused by
  precipitation, freezing or melting ice and snow.  It permits users
  to select from a wide range of high-gain antennas while still meeting
  Effective Isotropic Radiated Power (EIRP) limits set by regulators such
  as the Federal Communications Commission.  TPC improves 802.11 networks.

  Widely-available 802.11 radios provide TPC to their hosts by different
  mechanisms. For example, the Cisco/Aironet cards provide a choice
  of a few milliwatt presets.  Compare with the Prism chipset, which
  provides unitless, continuous power adjustments, provided the host
  takes over a power-control feedback loop from the firmware. New 802.11
  chips coming from Broadcom, Atmel, and ADMtek may support TPC by still
  different mechanisms. The application programmer faces a huge task if
  he will support the idiosyncrasies of every radio. It is important,
  from his perspective, for the kernel to smooth over differences in
  TPC mechanisms.

  A desirable TPC API brings the benefits of transmit power control
  to applications, while it insulates applications from the low-level,
  driver-specific intricacies.

API Overview

  Commands in the NetBSD TPC API let an application query the transmit
  power setting, set the transmit power setting, calibrate transmit
  power settings, and perform simple arithmetic on power settings.

  The TPC API is chiefly concerned with manipulating a unitless power
  setting between the highest and the lowest available settings. However,
  a driver and an application may exchange "hints" in centibels (a
  centibel is a tenth of a decibel) or in milliwatts whenever there is
  driver support for such hints. Hints are only adjustments to unitless
  power settings; they cannot be used for the driver and application to
  tell each other ANY absolute power level EXCEPT for the maximum power
  level. The rounding mode for hints is under application control.

  Power settings are chosen from a compact set of u_int32_ts.  The API
  provides a few guarantees about the unitless settings: 1) the transmit
  power is a monotonically non-decreasing function of the setting, 2)
  IEEE80211_TPCSET_MIN is the minimum power setting, and 3) the maximum
  power setting is at most IEEE80211_TPCSET_MAX.

  It is an application's responsibility to map u_int32_ts to units of
  transmit power using, e.g., feedback from receivers, or hints from the
  driver. Drivers will give more or less unambiguous hints depending on
  the underlying hardware's capabilities.

API Reference

  Commands are sent to the radio through a raw socket using ioctls
  SIOCS80211TPC and SIOCS80211TPC,

#define SIOCS80211TPC           _IOWR('i', 242, struct ieee80211_tpc)
#define SIOCG80211TPC           _IOWR('i', 243, struct ieee80211_tpc)

  which both take a pointer to a struct ieee80211_tpc.  The kernel sends
  back results using the same structure.  A ieee80211_tpc structure
  is defined

	struct ieee80211_tpc {
			char                    i_name[IFNAMSIZ];
			u_int32_t               i_flags;
			u_int32_t               i_set;
			union {
					int32_t cb;    /* 1/10 dB from maximum */
					int32_t mw;     /* mW offset from maximum */
			} i_hint;

/* i_flags */
#define IEEE80211_TPC_HINT_CB	 0x1	/* use i_hint.cb */
#define IEEE80211_TPC_HINT_MW	 0x2	/* use */ 
#define IEEE80211_TPC_CONVERT	 0x4	/* SIOCG80211TPC: convert arg */
#define IEEE80211_TPC_ROUNDUP	 0x8	/* SIOC[GS]80211TPC: round up */
#define IEEE80211_TPC_ROUNDDOWN	0x10	/* SIOC[GS]80211TPC: round down */
					/* SIOCS80211TPC: calibrate */

/* i_set */
#define IEEE80211_TPCSET_MIN    ((u_int32_t)0)
#define IEEE80211_TPCSET_MAX    (~TPC_SET_MIN)

int s, rc;
struct ieee80211_tpc tpc;
rc = ioctl(s, SIOCG80211TPC, &tpc);

  SIOCG80211TPC operates in two modes. The mode is selected by the
  flag IEEE80211_TPC_CONVERT. In the first mode (no flag) it reads the
  TPC setting from the driver, and in the second mode (CONVERT flag)
  it converts a setting given by the application.


  When IEEE80211_TPC_CONVERT is clear, SIOCG80211TPC asks the driver for
  the current TPC setting. If the flags do not indicate any adjustment
  with IEEE80211_TPC_HINT_CB or IEEE80211_TPC_HINT_MW, then SIOCG80211TPC
  writes i_set with the current setting, and returns.

  If the flags DO indicate an adjustment, SIOCG80211TPC adds i_hint to
  the current driver setting and rounds the result before writing it
  to i_set.  Finally, SIOCG80211TPC computes the nearest offset from
  maximum power to i_set and writes it to i_hint.

  SIOCG80211TPC rounds i_set + i_hint to a driver setting according to
  the rounding mode given in the flags.  The rounding mode is affected by
  the flags IEEE80211_TPC_ROUNDUP and IEEE80211_TPC_ROUNDDOWN. If either
  rounds the adjusted setting accordingly.  If both are set, SIOCS80211TPC
  rounds to the nearest setting. If neither is set, and i_set + i_hint
  does not fall precisely on a driver setting, SIOCS80211TPC exits
  with EINVAL.  SIOCS80211TPC tells which way it rounded, if it rounded,
  using either IEEE80211_TPC_ROUNDUP or IEEE80211_TPC_ROUNDDOWN.

  If the sum of the current setting and i_hint exceeds the driver's
  maximum setting, and IEEE80211_TPC_ROUNDDOWN is not set, then
  SIOCG80211TPC will exit with EINVAL. Likewise, SIOCG80211TPC will exit
  with EINVAL if the sum is below IEEE80211_TPCSET_MIN after adjustment,
  but IEEE80211_TPC_ROUNDUP is not set.


  When IEEE80211_TPC_CONVERT is set, SIOCG80211TPC behaves identically
  as it does when IEEE80211_TPC_CONVERT is clear, EXCEPT that

    * it treats i_set as the current TPC setting, and
    * if the caller passes an i_set equal to IEEE80211_TPCSET_MAX and
      a hint equal to 0, SIOCG80211TPC will try to write the ABSOLUTE
      magnitude of the maximum output power to i_hint. If the absolute
      maximum power is not known, SIOCG80211TPC will set errno to ENODEV
      before returning. The author feels that this special case is
      justified because it makes the conversion from IEEE80211_TPCSET_MAX
      informative; the cB/mW offset from IEEE80211_TPCSET_MAX is 0 by
      definition, so an application never needs to convert it.

  FLAGS (on entry)

    IEEE80211_TPC_CONVERT    Use i_set instead of the driver's setting.
    IEEE80211_TPC_HINT_CB    Add a cB offset to the setting and return
                             a cB hint.
    IEEE80211_TPC_HINT_MW    Add a milliwatt offset to the setting and return
                             a milliwatt hint.
    IEEE80211_TPC_ROUNDUP    After adjusting by i_hint, round up to a
                             driver power setting.
    IEEE80211_TPC_ROUNDDOWN  After adjusting by i_hint, round down to a
                             driver power setting.

    (IEEE80211_TPC_HINT_CB and IEEE80211_TPC_HINT_MW are mutually

  FLAGS (on exit)

    IEEE80211_TPC_HINT_CB    Returning a cB hint (cBm for IEEE80211_TPCSET_MAX).
    IEEE80211_TPC_HINT_MW    Returning a milliwatt hint.
    IEEE80211_TPC_ROUNDUP    After adjusting by i_hint, rounded up.
    IEEE80211_TPC_ROUNDDOWN  After adjusting by i_hint, rounded down.

    (IEEE80211_TPC_ROUNDUP and IEEE80211_TPC_ROUNDDOWN are mutually

    (IEEE80211_TPC_HINT_CB and IEEE80211_TPC_HINT_MW are mutually


    SIOCG80211TPC sets errno to EINVAL if the application sets conflicting

    SIOCG80211TPC sets errno to ENODEV if the application sets a hint
    flag which the driver does not support.

    SIOCG80211TPC sets a hint flag before returning if and only if it
    produced valid hints. Applications should always check the hint flags
    before interpreting the hint fields, because the flags may change
    from their values on entry.  Drivers support hints optionally.

int s, rc;
struct ieee80211_tpc tpc;
rc = ioctl(s, SIOCS80211TPC, &tpc);


  After adjusting i_set by any hint in i_hint, and rounding it according
  to the flags, SIOCS80211TPC writes a new TPC setting to the hardware.

  Rounding is affected by the flags IEEE80211_TPC_ROUNDUP and
  IEEE80211_TPC_ROUNDDOWN is set, SIOCS80211TPC rounds accordingly.
  If both are set, SIOCS80211TPC rounds to the nearest setting. If
  neither is set, SIOCS80211TPC exits with EINVAL if an adjustment does
  not result precisely in a driver setting.

  If a setting exceeds the driver's maximum setting, even after any
  adjustments, and IEEE80211_TPC_ROUNDDOWN is not set, then SIOCS80211TPC
  will exit with EINVAL. Likewise, SIOCS80211TPC will exit with EINVAL
  if a setting is below IEEE80211_TPCSET_MIN after adjustment, and
  IEEE80211_TPC_ROUNDUP is not set.


  An application may calibrate the driver's unitless power settings
  using the flag IEEE80211_TPC_CALIBRATE.  An application associates a
  power setting in i_set with an offset from maximum power in i_hint by
  setting flag IEEE80211_TPC_CALIBRATE.  An application calibrates the
  absolute maximum power by writing its setting (or IEEE80211_TPCSET_MAX)
  to i_set and writing the absolute power to i_hint.

  When IEEE80211_TPC_CALIBRATE is set, ioctl will not change the current
  power setting.

  An application deletes the association with power setting i_set by
  using IEEE80211_TPC_CALIBRATE without a hint.


  When SIOCS80211TPC is called without IEEE80211_TPC_CALIBRATE, it
  writes the driver power setting it adopts into i_set. It writes a hint,
  as appropriate, into i_hint, and sets the flags accordingly.

  A driver may protect its calibrations from deletion by returning ENODEV.

  When an application tries to overwrite an existing calibration, ioctl
  returns EEXIST. Calibrations must be monotonically non-decreasing:
  if a driver tries to make a setting/level association that is out of
  order with existing associations, ioctl returns EINVAL.

  If a driver does not permit its own calibrations to be deleted, it
  returns ENODEV.

  If a non-root user attempts to calibrate power settings, ioctl returns

Implementation Details

  The maximum power level setting may change from channel to channel.
  That is, if i_set = 25 is acceptable for channel 1, it may not be
  acceptable for channel 3, where i_set = 20 is the maximum.

  When the driver changes channel, it should select the minimum of the
  power level last set by an ioctl and the maximum power allowed on the
  new channel.

  A driver should strive for the graph of power versus unitless power
  setting to be everywhere increasing, without any "plateaus".

Future Work

  If there are good arguments for it, the TPC API should let apps set
  higher power levels than the factory calibrations. It is important
  to consider the impact of "turning it up to eleven" on other users
  of the spectrum (think about out-of-band emissions). Also consider an
  operator's legal liability, and the affects (if any) on a radio from
  operating its outside tolerances.

  The API should provide for applications to take over the automatic
  level control (ALC) loop from drivers.

  To support TPC decisions, receivers should tell received signal
  strength indications to transmitters. Perhaps a protocol for this is
  coming out of the IEEE?

  It is useful to label direct routes with TPC information, so that IP
  messages can be sent on their next hop with the least necessary power.


	/* Here is the way you find the absolute minimum and maximum
	 * power.
	 * First, find out the absolute maximum output power in cBm,
	 * if it is available.
	int32_t maxpow, minpow;
	struct ieee80211_tpc tpc;

	tpc.i_flags = IEEE80211_TPC_CONVERT | IEEE80211_TPC_HINT_CB;
	tpc.i_set = IEEE80211_TPCSET_MAX;
	tpc.i_hint.db = 0;
	rc = ioctl(s, SIOCG80211TPC, &tpc);

	if (rc != 0) {
		if (errno == ENODEV) {
			printf("Maximum dBm is unknown.\n");
		err(EXIT_FAILURE, "ioctl(, SIOCG80211TPC, )");

	if ((tpc.i_flags & IEEE80211_TPC_HINT_CB) == 0) {
		printf("Maximum dBm is unknown.\n");

	maxpow = tpc.i_hint;
	printf("%d.%01d dBm maximum power\n", maxpow / 10, maxpow % 10);

	/* Find out the minimum output power as a cB difference from
	 * the maximum.  Add it to the absolute maximum to get the absolute
	 * minimum.

	tpc.i_flags = IEEE80211_TPC_CONVERT | IEEE80211_TPC_HINT_CB;
	tpc.i_set = IEEE80211_TPCSET_MIN;
	tpc.i_hint.db = 0;
	rc = ioctl(s, SIOCG80211TPC, &tpc);
	if (rc != 0)
		err(EXIT_FAILURE, "ioctl(, SIOCG80211TPC, )");

	if ((tpc.i_flags & IEEE80211_TPC_HINT_CB) == 0) {
		printf("Minimum dBm is unknown.\n");

	minpow = maxpow + tpc.i_hint;
	printf("%d.%01d dBm minimum power\n", minpow / 10, minpow % 10);

	/* Set power to half maximum power. */
	tpc.i_flags = IEEE80211_TPC_HINT_CB
	tpc.i_set = IEEE80211_TPCSET_MAX;
	tpc.i_hint.db = -30;

	rc = ioctl(s, SIOCS80211TPC, &tpc);

	if (rc != 0)
		err(EXIT_FAILURE, "ioctl(, SIOCG80211TPC, )");

	/* Let's see how close we got. */
	if ((tpc.i_flags & IEEE80211_TPC_HINT_CB) != 0) {
		printf("%d.%.01d dBm from 1/2 maximum power\n",
		    (30 + minpow) / 10, minpow % 10);

David Young             OJC Technologies      Engineering from the Right Brain
                        Urbana, IL * (217) 278-3933