Subject: 802.11 transmit power API (long)
To: None <tech-net@netbsd.org>
From: David Young <dyoung@pobox.com>
List: tech-net
Date: 02/18/2003 16:49:07
802.11 Transmit Power Control API for NetBSD
Abstract
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.
Motivation
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 i_hint.mw */
#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 */
#define IEEE80211_TPC_CALIBRATE IEEE80211_TPC_CONVERT
/* 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.
READ DRIVER POWER SETTING
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
IEEE80211_TPC_ROUNDUP or IEEE80211_TPC_ROUNDDOWN is set, SIOCS80211TPC
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.
CONVERT APPLICATION POWER SETTING
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
exclusive.)
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
exclusive.)
(IEEE80211_TPC_HINT_CB and IEEE80211_TPC_HINT_MW are mutually
exclusive.)
RETURN VALUE
SIOCG80211TPC sets errno to EINVAL if the application sets conflicting
flags.
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);
WRITE DRIVER POWER SETTING
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. If either IEEE80211_TPC_ROUNDUP or
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.
CALIBRATE
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.
RETURN VALUE
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
EPERM.
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.
Examples
/* 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");
exit(0);
}
err(EXIT_FAILURE, "ioctl(, SIOCG80211TPC, )");
}
if ((tpc.i_flags & IEEE80211_TPC_HINT_CB) == 0) {
printf("Maximum dBm is unknown.\n");
exit(0);
}
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");
exit(0);
}
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
dyoung@ojctech.com Engineering from the Right Brain
Urbana, IL * (217) 278-3933