File:  [NetBSD Developer Wiki] / wikisrc / Converting_drivers_to_the_new_wifi_stack.mdwn
Revision 1.24: download - view: text, annotated - select for diffs
Mon Jul 26 15:47:05 2021 UTC (2 months, 4 weeks ago) by wiki
Branches: MAIN
CVS tags: HEAD
web commit by martin: Cleanup test markup, rework a few more methods and mark as non-usb relevant

# Intro

 This guide is meant to help you convert an old style NetBSD driver
for a wifi device to the newer wifi stack based on FreeBSD's current

 This is work in progress, as we are trying to create helper functions
and libraries where usefull, and also the target API is a slightly
moving target.

# Overview

 The main difference between old style and new style drivers is the
VAP concept. VAP means Virtual Access Point, and it is used to split
the configuration properties of a wlan (as visible on air) from the
radio hardware. In many cases multiple wlans can be created using a
single radio hardware, which is naturally modelled as multiple VAPs
being created (by pure software) using the same physical radio device.

 At the user level the radio hardware is hidden quite well - it only
shows up in `dmesg` or `sysctl net.wlan.devices`.
There is no network interface for the raw hardware, so ifconfig will
not show them.

 To create a VAP for a radio device (and that way a network interface)
use a command like:

    ifconfig wlan0 create wlandev run0

 This split between common radio hardware and per-VAP network properties
reflects on the driver and causes most of the changes needed.

 After creation of the `wlan0` VAP, it will also show up under

    sysctl net.wlan.wlan0

 and in ifconfig output.

  More complete instructions for testing are here: [[Testing new wifi]].
  There also is an older paper describing the initial design in FreeBSD:
  [FreeBSD wireless BSDCan 2005](

# Help available!

If your device is a usb(4) device, you can use a new helper facility "usbwifi" similar
to usbnet(9), which already handles a lot of new wifi stack specific details.
In the following text the items not applicable for drivers using usbwifi are marked
like this: [!]*usbwifi*{X} or `// !usbwifi` if appearing in code sections.

# Step by Step at the driver level

 * The data is split into the generic `struct ieee80211com`
   describing the radio hardware (often used as `ic` pointer),
   which is part of the drivers softc. The individual VAPs are
   in a TAILQ in the ic structure, and often are a driver defined
   structure 'derived' from `struct ieee80211vap` (with some
   additional driver specific data). The VAP structure has the
   network interface `struct ifnet` as `iv_ifp`.
   The `if_softc` pointer in the ifnet structure points to the VAP, the VAP structure
   has a pointer to the ic `iv_ic`, the ic structure has a `ic_softc` pointer to the softc.

 * MAC addresses are now stored in ic_macaddr instead of ic_myaddr
   (in struct ieee80211com). It is typically read from the device
   and cloned into each VAP. Typical code:

        IEEE80211_ADDR_COPY(ic->ic_macaddr, rom->macaddr);

 * In the attach function, no `struct ifnet ifp` will be needed,
   so get rid of code like:

         struct ifnet *ifp = GET_IFP(sc);

   and all initialization of ifp members. Move them to the vap_create
   function (see below) if needed instead, but typically none
   should be needed (as the generic code initializes the VAP's
   struct ifnet).

   Set a direct backlink to the softc, like:

        ic->ic_softc = sc;

   Make sure the ic_caps get initialized correctly to the capabilities
   of the radio, especially you want to add IEEE80211_C_STA for
   standard AP (station) mode.

   There is no state machine in the common radio part, it all moves into
   per-VAP state, so also remove initialization for it, like:

        ic->ic_state = IEEE80211_S_INIT;

   You may want a replacement for `IFF_OACATIVE` logic for the main device,
   as there is no interface so no flags to (ab)use for that.

 * The way channels are set up has changed.
   If your driver had a `driver_init_channels()` function, you can
   use it mostly verbatim (but see caveats below for `maxchans` and `nchans`).
   Rename it to `driver_get_radiocaps` and make it look like:

        static void
        urtwn_get_radiocaps(struct ieee80211com *ic,
            int maxchans, int *nchans, struct ieee80211_channel chans[])
            uint8_t bands[IEEE80211_MODE_BYTES];
            memset(bands, 0, sizeof(bands));
            setbit(bands, IEEE80211_MODE_11B);
            setbit(bands, IEEE80211_MODE_11G);
            setbit(bands, IEEE80211_MODE_11NG);
            // XXX see description below if your chip can use other ranges (like 5ghz)
            ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);

   and also assign this function to `ic->ic_getradiocaps` in
   the attach function.

   An initial setup must happen before calling `ieee80211_ifattach(ic)`.
   You can just call the `driver_get_radiocaps()` function during
   attach like:

        urtwn_get_radiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,   
        ic->ic_getradiocaps = urtwn_get_radiocaps;

   If the hardware can use different frequency bands you may have to add them
   band by band instead of using `ieee80211_add_channels_default_2ghz`, also if
   the firmware knows about regulatory domains it may be required to select
   subsets of channels.

   An example of a multi-frequency driver doing this is iwm(4).

   It uses a helper function to add single channels individually
   via `ieee80211_add_channel`.
   To add the channels for a band, it calls
   `iwm_add_channel_band(ic, chans, maxchans, nchans, start, num, bands)`
   where `chans`, `maxchans` and `nchans` are the arguments passed to
   this callback, `chans` collecting the resulting channels, `nchans` pointing
   to the number of channels already collected, `bands` is the bitset of
   bands to add (multiple `IEEE80211_MODE_*` bits). The offset `start`
   is the index of the first channel to add to the output (first call will
   always be with 0 as `start`), and `num` the number of channels
   you expect `iwm_add_channel_band` to add. The `iwm_add_channel_band`
   helper function iterates over the channels, gets flags for the channels
   from firmware and calls `ieee80211_add_channel`.

   Overall the most important part in conversion of old drivers is to make sure that
   the out parameter `*nchans` is properly set to the number of channel data
   actually filled, and to respect the `maxchans` limit. This callback
   is called from two places, once from the drivers attach function,
   where `maxchan` is big enough (always `IEEE80211_CHAN_MAX`), and from
   ioctl `IEEE80211_IOC_DEVCAPS` where `maxchan` depends on the userland arguments
   and e.g. wpa_supplicant only asks for minmal data, as it is not interested
   in the actual channels available (`maxchan` is 1). The ioctl handling code
   will KASSERT proper behaviour of the callback.

 * If your driver used to override state machine functions (typical
   `newstate` to controll LEDs when associating or similar),
   remove all traces for this from your softc and override a
   custom VAP type instead (i.e. derive a struct from
   `struct ieee80211vap` and use your own struct in your `vap_create`
   and `vap_delete` methods).

   Remove the (likely to exist) old `sc_newstate` function from
   your drivers softc (previously used to store the frameworks
   newstate function, which now is per-VAP and moved into your
   derived per-VAP structure.

 * Many functions will get a `struct ieee80211vap *vap` passed instead
   of a softc. A typical change to adapt for that looks like:

        -       struct ieee80211com *ic = &sc->sc_ic;
        -       struct ieee80211_node *ni = ic->ic_bss;
        +       struct ieee80211com *ic = vap->iv_ic;
        +       struct rtwn_softc *sc = ic->ic_softc;
        +       struct ieee80211_node *ni = vap->iv_bss;

 * The hardware driver will need a global new outgoing packet queue
   (as the per-interface queues are attached to VAPs, not the single
   common transmit hardware). Add the new ifqueue like to your softc
   like this:

        struct ifqueue sc_sendq;

   If your hardware supports multiple transmit priorities you may
   consider doing more than one ifqueue. Usbwifi does this for devices
   with multiple transmit pipes (creating a ifqueue per hardware priority
   and in this case USB send endpoint).

   While there, check that there is no `struct ethercom sc_ec` in your
   softc - it is not needed.

   This queue needs initialization in the attach function, like:

        sc->sc_sendq.ifq_maxlen = ifqmaxlen;

   This device private send queue (writer: any of the current VAPs
   of this interface, consumer: the devices transmit interrupt handler)
   abuses the `mbuf rcvif` pointer to store the node pointer
   `struct ieee80211_node *` of the target node for this packet.
   When dequeuing a packet before handing it over to the output function,
   the node is extracted and the `rcvif` pointer set to `NULL`.
   Yes - this is a hack.

   Also the attach function needs to set the name of the interface
   in the common radio device structure:

        ic->ic_name = device_xname(self);

   Many drivers query the hardware/firmware for the number of available
   input/output queues and store these in e.g. `sc->ntxchains`
   and `sc->nrxchains` or similar variables. This information
   needs to be propagated to the common radio structure:

        ic->ic_txstream = sc->ntxchains;
        ic->ic_rxstream = sc->nrxchains;

   Same for interface flags:


   After calling `ieee80211_ifattach(ic);` some methods in `ic`
   need to be overwritten. The functions needed are explained
   below, here is a typical setup:

        /* override default methods */
        ic->ic_newassoc = urtwn_newassoc;
        ic->ic_wme.wme_update = urtwn_wme_update;
        ic->ic_vap_create = urtwn_vap_create;
        ic->ic_vap_delete = urtwn_vap_delete;
        ic->ic_parent = urtwn_parent;
        ic->ic_scan_start = urtwn_scan_start;
        ic->ic_scan_end = urtwn_scan_end;
        ic->ic_set_channel = urtwn_set_channel;
        ic->ic_transmit = urtwn_transmit;    // !usbwifi
        ic->ic_raw_xmit = urtwn_raw_xmit;    // !usbwifi

 * detach does not deal with any interfaces any more, remove all traces
   of `struct ifnet *ifp`.

 * Add a new function for VAP creation and move all interface specific
   setup there. It looks like:

        static struct ieee80211vap *
        urtwn_vap_create(struct ieee80211com *ic,  const char name[IFNAMSIZ],
            int  unit, enum ieee80211_opmode opmode, int flags,
            const uint8_t bssid[IEEE80211_ADDR_LEN],
            const uint8_t macaddr[IEEE80211_ADDR_LEN])
               struct urtwn_softc *sc = ic->ic_softc;
               struct ifnet *ifp;
               struct ieee80211vap *vap;
               return vap;

   It allocates a new VAP with `kmem_zalloc` initializes it
   by calling `ieee80211_vap_setup` for it and then overriding whatever
   the driver needs, passes it to `ieee80211_vap_attach` and
   attaches BPF to the interface.

   If your interrupt handler is called in hard interrupt context
   (that probably means: for all devices that are not on USB)
   you need to initialize the per CPU interface queue:

        /* Use common softint-based if_input */
        ifp->if_percpuq = if_percpuq_create(ifp);

   If your interrupt handler always is called in softint context,
   do not do this. If `if_percpuq` is `NULL` the wifi framework will
   directly handle incoming packets, otherwise it will take a
   round trip via a per cpu softint handler.

 * Add a new function for VAP destruction and move interface specific
   parts that you deleted in the detach function here. A very
   minimalistic example looks like:

        static void
        urtwn_vap_delete(struct ieee80211vap *vap)
            struct ifnet *ifp = vap->iv_ifp;
            struct urtwn_softc *sc __unused = vap->iv_ic->ic_softc;

            kmem_free(vap, sizeof(struct ieee80211vap));

 * Rate Adaption happens per VAP, so the ra_init call changes like this:

        -static int urtwn_ra_init(struct urtwn_softc *);
        +static int urtwn_ra_init(struct ieee80211vap );

   See above for recipes how to access the needed structs in there.

 * State is per VAP, so newstate changes the first parameter:

        -static int urtwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
        +static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int);

   If there was a newstate_cb function previously, it is probably not needed
   any more.

 * Reset is per interface, so per VAP:

        -static int urtwn_reset(struct ifnet *);
        +static int urtwn_reset(struct ieee80211vap *, u_long cmd);

   A driver is not required to provide this method, if not filled in a
   default method will be used that always causes a full reset of the

 * The set channel function is generic now, so gets passed a
   `struct ieee80211com *ic` instead of a softc, and sets
   the radio hardware to the current channel in that structure,

 * The start function does not need a `struct ifnet *ifp` any more,
   but can use a softc or `struct ieee80211com *ic` instead.

 * Typically, generic media change and status functions will
   be used: `ieee80211_media_change` and `ieee80211_media_status`.
   Old drivers often implement their own function - remove that (unless
   there is something actually chip specific in them).

   If there is, override media_change and/or media_status, and make
   sure to call the generic variant for all the real work.
   Also make sure to pass your override function to `ieee80211_vap_attach.`
   run(4) is an example of this.

   The overridden media_\{change,status\} functions are the only one
   in a wlan driver that should get a `struct ifnet *` argument
   Checking for this is a good measurement to know you are done with
   conversion (besides the kernel build failing, of course).

 * [!]*usbwifi*{X} Your driver needs a new transmit function, used to enqueue a
   mbuf to the hardware send queue (and start transmission if needed).
   A very simple example:

        static int
        urtwn_transmit(struct ieee80211com *ic, struct mbuf *m)
            struct urtwn_softc *sc = ic->ic_softc;
            int s;
            s = splnet();
            IF_ENQUEUE(&sc->sc_sendq, m);

            if (!sc->sc_oactive)

            return 0;

   A pointer to this function is assigned to the `ic->ic_transmit` member
   in the attach function.

 * [!]*usbwifi*{X} Your driver needs a raw_xmit function which is close to what
   the old driver called *_tx(), it looks like:

        static int
        urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
            const struct ieee80211_bpf_params *bpfp)

   and the "missing" arguments are derived like:

        struct ieee80211vap *vap = ni->ni_vap;
        struct ieee80211com *ic = ni->ni_ic;
        struct urtwn_softc *sc = ic->ic_softc;

   The check for encrypting packets often was testing the `ieee80211_frame`
   `wh->i_fc[1] & IEEE80211_FC1_WE` before encapsulating the mbuf
   via `ieee80211_crypto_encap()`. The check now is for
   `wh->i_fc[1] & IEEE80211_FC1_PROTECTED`, since WEP is a thing of the
   past and there are other protocols. Also the `ieee80211_crypto_encap`
   function lost its first argument (the radio common ic pointer).

   The transmit function queues the mbuf for transmit and
   makes sure when transmission is done by hardware to call
   `ieee80211_tx_complete` with both `mbuf` and `ni`. This function
   also needs to be called in all error cases, with an error value as
   status (last argument). The actual value does not matter, the stack
   just checks for 0 (success) and non zero (failure). This call
   will free the mbuf and the node, and in some cases also cause
   further state changes (like waking up VAPs, scheduling a new scan
   later, or even causing immediate state machine transitions and retrying
   in error cases).

 * [!]*usbwifi*{X} If the `driver_activate()` function only passes deactivation requests
   on to if_deactivate() you can replace it by a shim that gets
   your softc and calls `ieee80211_activate()`, like:

        static int
        urtwn_activate(device_t self, enum devact act)
            struct urtwn_softc *sc = device_private(self);
            return ieee80211_activate(&sc->sc_ic, act);

 * When the hardware received a full frame, move it into a mbuf
   and call (with proper frame bound checks)

        struct ieee80211_node *ni;

        // XXX unclear - same on NetBSD?
        if (ieee80211_radiotap_active(ic))
            mactime = rx->mactime;
        // or:
        if (ieee80211_radiotap_active(ic)) 
            struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap;

            tap->wr_tsf = mactime;
            tap->wr_flags = 0;
            tap->wr_dbm_antsignal = (int8_t)rssi;

        ni = ieee80211_find_rxnode(ic,
            mtod(m, struct ieee80211_frame_min *));
        if (ni != NULL) {
            ieee80211_input(ni, m, rssi, nf);
        } else {
            ieee80211_input_all(ic, m, rssi, nf);

   If a node is found, the data is passed on to that VAP, otherwise
   it is a general management packet.

   Old code often has tests for too short or too long packets and
   increments error counters in the struct ifnet of the interface
   that used to belong to the wlan driver. Since there is no such
   single interface anymore, errors are accounted later (at the VAP
   level) if a VAP/node is identified for the packet. Otherwise
   just increment the global `ic->ic_ierrors` counter for errors with
   incoming packets that can not be associated with a concrete
   network (or similar `ic->ic_oerrors` for output errors).

 * Various BPF changes:

   Many drivers avoid complex setup options for BPF listeners if there are none.
   With virtual APs this became a bit more complex, but the framework tracks it
   for the driver. To test if any VAP on the radio hardware is in MONITOR mode and has
   an active  BPF peer, simply test the `ic->ic_montaps` counter - it will be zero if
   there are none.

   For most drivers it should not be necessary to do anything special for BPF, the
   taps are in the framework.

   Some drivers provide special low level radiotap data, often via a driver specific
   `sc->sc_drvbpf`. This is replaced by a generic radiotap bpf listener created by
   the framework code and stored as `vap-> iv_rawbpf`. A driver signals support for
   radiotap by making `ic->ic_th` point to a radiotap transmit header (usually in
   the softc, initialized before attach and updated on transmit) and similar `ic->ic_rh` 
   point to a receive tap header (updated on every received packet). Make sure to initialize
   the tap headers `it_len` fields to the proper size (in little endian byte order)
   and `it_present` flags (also in little endian byte order) to the relevant
   `IEEE80211_RADIOTAP_*` bits.

   To avoid updating the receive/transmit tap headers unessecarily the driver can check
   the `ic->ic_flags_ext` for the `IEEE80211_FEXT_BPF` bit being set - if not, there is
   no VAP with bpf listener on radiotap headers.

   The driver needs to update `ic_rh` (usually only the storage for a single header
   it points to) on every received packet. This is used as a side channel for the
   radiotap data when passing packet up the stack via `ieee80211_input_all` or
   `ieee80211_input`. Or in the NetBSD case for USB drivers when calling `usbwifi_enqueue`.
   The stack automatically dispatches this to the radiotap bpf tap associated with any
   relevant VAP and also makes sure it is seen by VAPs in MONITOR mode.

 * Watchdog functions move to callout:

   Since there is no 1:1 ifnet and its global watchdog mechanism, all
   driver watchdog functions are converted to a callout.
   A typical implementation looks like:

        static void
        urtwn_watchdog(void *arg)
            struct urtwn_softc *sc = arg;
            struct ieee80211com *ic = &sc->sc_ic;

            if (sc->tx_timer > 0) {
                    if (--sc->tx_timer == 0) {
                            aprint_error_dev(sc->sc_dev, "device timeout\n");
                            ieee80211_stat_add(&ic->ic_oerrors, 1);
                    callout_schedule(&sc->sc_watchdog_to, hz);

   There is no `ieee80211_watchdog()` function any more.

 * ioctl functions
   Typicaly there are no ioctl functions in a wlan driver. The ioctls
   are handled in generic code and the ic_parent function is called to
   update the device state accordingly.  XXXX - is this true? implemented?
   Or one of ic_scan_start/ic_scan_end/ic_set_channel

 * device _parent function
   You need to add a new function to update the device state when one of
   the active VAPs requires something special, like switch to promiscous
   mode. The function looks like:

        static void
        rtwn_parent(struct ieee80211com *ic)
            struct rtwn_softc *sc = ic->ic_softc;
            bool startall = false;

            if (ic->ic_nrunning > 0 && !sc->sc_running)
                startall = true;
            else if (ic->ic_nrunning == 0 && sc->sc_running)

            if (startall)

CVSweb for NetBSD wikisrc <> software: FreeBSD-CVSweb