Table of Contents

Writing wireless card device drivers

Basic stuff

gPXE implements wireless support using a wrapper around a net_device, following much the same concept as the Infiniband subsystem. Since 802.11 uses Ethernet-style addressing, it is not necessary to devise a separate means for handling “IP over 802.11”; there only needs to be an association between a generic net_device for a particular wireless card and the net80211_device representing its 802.11-specific attributes. For simplicity of design I chose to have the net_device wrap the net80211_device, instead of vice versa; that way we don't have to worry about queueing packets or tracking TX completions ourselves.

As much as possible, I have tried to keep the wireless API analogous to that for wired NICs except where additional functionality in the wireless realm forces differences. Because a wireless device is encapsulated in a net_device, though, almost all gPXE code need know nothing about wireless. Calling wireless-specific functions is necessary only for wireless drivers and perhaps in the future some wireless-specific code in other areas. Such calls should generally be avoided, because they introduce a link-time dependency on the rather large 802.11 stack; when the file introducing them can be compiled in or out due to a configuration option, the REQUIRE_OBJECT() calls should be placed in config_net80211.c instead of config.c to avoid ever pulling in the objects without a wireless stack present to make them useful.

When a wireless network card's driver probe() function is called, it acts much the same as a wired NIC, setting up device fundamentals and registering itself with the network stack. An 802.11 device is allocated using net80211_alloc() and registered using net80211_register(); the driver must additionally pass some hardware-specific information about the wireless capabilities of the device it is registering. A wrapping net_device is created and registered behind the scenes in this call, and it can later be accessed by the netdev field in the net80211_device structure in use.

In all of the following, netdev denotes a pointer to a net_device structure and dev denotes a pointer to a net80211_device structure.

Use in a wireless driver

In general, in an 802.11 driver, net80211_device simply takes the place of net_device. Some of the fields in net_device must continue to be accessed through the dev→netdev wrapper structure pointer, while others have analogues in the net80211_device structure directly. Specifically:

There are also many more fields in net80211_device than in net_device, owing to the significant added complexity of wireless. Those that a driver writer needs to know about are:

To manage these additional wireless-specific issues, a new API function is added to net80211_device_operations: config(), passed a pointer to the device and a bitmask of what (from the set of channel, TX rate, association, and PHY parameters) has changed. In all cases the changes are not passed directly; the changed parameters are updated in the net80211_device structure before the call, and the driver is expected to update the card's understanding to match them. It is possible for a bit to be set in the changed argument without the underlying parameter having actually changed, but this is expected to be rare.

The operations structure is otherwise perfectly analogous to net_device_operations, with the obvious substitution of net80211_device structures for net_device structures in arguments.

The creation and registration process for an 802.11 device is fairly similar to that for a regular network device: net80211_alloc() passing the size of a driver-private data area to allocate, followed by field-filling and net80211_register(). Differences lie in the fact that net80211_register() expects both the device operations structure and an 802.11-specific hardware information structure. There is no analogue to netdev_init(); initialisation is performed by either the allocation or the registration function as appropriate. Before freeing an 802.11 device, it must be unregistered using net80211_unregister(). 802.11 devices are not reference-counted, so they should be freed using net80211_free() rather than a combination of nullify and put.

As in the wired case, drivers must supply notification to the network layer of received and transmitted packets:

  void net80211_rx ( struct net80211_device *dev, struct io_buffer *iob,
                     int signal, u16 rate );

Called with a received packet in iob, which the network layer takes ownership of. In signal, pass a hardware-level signal strength indication; it will be interpreted according to the information specified in the hardware info structure passed to net80211_register. In rate, pass the bitrate used to receive the packet, as usual in units of 100 kbps. This information is used to adaptively set the best TX rate.

  void net80211_rx_err ( struct net80211_device *dev,
                         struct io_buffer *iob, int rc );

Called when it is known that reception of a packet failed (e.g. due to a reported CRC error). If iob is non-NULL, it is freed, and an error of type rc is recorded in the net_device rx statistics.

  void net80211_tx_complete ( struct net80211_device *dev,
                              struct io_buffer *iob, int retries, int rc );

Called when a transmission completes, whether successfully or no. (There is no separate net80211_tx_complete_err() function for error conditions.) rc must be set to 0 if the transmission was ultimately successful, or an error code if it failed. If the transmission had to be retried before it succeeded, this should be reflected in a nonzero retries value, measuring the number of failed transmissions before the successful one.