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:

  • For a wired NIC, the proper MAC address to use is always netdev→ll_addr. For a wireless NIC, there are two available: dev→netdev→ll_addr is the MAC address most recently configured by the user, and dev→hw→hwaddr is the MAC address burned into the card. Usually (e.g. for setting the RX filter) the former should be used.
  • Any access to netdev→name and netdev→ll_protocol should continue to use the wrapping net_device.
  • The netdev→state field retains its validity, but for wireless it does not provide much status detail. The netdev→link_rc field is useful for presenting a user-visible error, and it keeps its value until a different error or a success. For programmatic status information, dev→state contains the most recent 802.11 status code and several bits indicating how network association is progressing.
  • The netdev→priv now points to the wrapped net80211_device structure. Driver-private data must be accessed using dev→priv (the priv field in the net80211_device itself).

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:

  • dev→hw contains a pointer to the net80211_hw_info structure passed to net80211_register().
  • dev→channels is a list of 802.11 channels that might be used, each identified by a structure indicating center frequency, standard channel number, transmission power, etc. dev→nr_channels contains a count of channels, and dev→channel is the index of the channel currently in use.
  • In the same vein, dev→rates is an array of transmission rates counted by dev→nr_rates and indexed by dev→rate. For simplicity, rates are not represented using a structure, but instead simply as an integral multiple of 100,000 bits per second.
  • dev→rtscts_rate is the index of the rate that should be used for RTS and CTS (request-to-send and clear-to-send) frames, if their use is necessary.
  • dev→bssid is the MAC address of the Access Point with which the card is associated; generally this is used in setting the RX filter.
  • dev→phy_flags is a bitmask of physical-layer flags (whether or not to use short preamble, short slot time, or CTS protection) that the driver must communicate appropriately to the card.

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.

QR Code
QR Code wirelessboot:drivers (generated for current page)