File:  [NetBSD Developer Wiki] / wikisrc / Converting_drivers_to_the_new_wifi_stack.mdwn
Revision 1.6: download - view: text, annotated - select for diffs
Sat Oct 17 17:47:24 2020 UTC (5 weeks, 6 days ago) by wiki
Branches: MAIN
CVS tags: HEAD
web commit by martin: Try to improve markup of inline code citations

# 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
version.

 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](https://www.bsdcan.org/2005/papers/FreeBSDWirelessNetwokringSupport.pdf)

# 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 statemachine 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.

   Note that the way channels are set up has changed.
   If your driver had a driver_init_channels() function, you can
   use it mostly verbatim. 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);
        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_channels);
    ieee80211_ifattach(ic);
    [..]
    ic->ic_getradiocaps = urtwn_get_radiocaps;
    ```

 * 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 method).

   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 ifque like to your softc
   like this:

    struct ifqueue sc_sendq;

   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;
    IFQ_LOCK_INIT(&sc->sc_sendq);
    ```

   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 dequeing 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:

    ic->ic_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;

   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;
    ic->ic_raw_xmit = urtwn_raw_xmit;
    ```


 * 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 ovverriding 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;

        bpf_detach(ifp);
        ieee80211_vap_detach(vap);
        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 acces 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
   device.

 * 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,
   `ic->ic_curchan`.

 * The start function does not need a `struct ifnet *ifp` any more,
   but can use a softc or `struct ieee80211com *ic` instead. It is
   supposed to send management frames (from ic->ic_mgtq) first, then
   everything in the device sc_sendq (see above).

 * Typicall 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 overriden media_{change,status} functions are the only one
   in a wlan driver that should get a `struct ifnet *` argument
   passed. Checking for this is a good measurement to know you are
   done with conversion (besides the kernel build failing, of course).

 * 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);
        splx(s);

        if (!sc->sc_oactive)
                urtwn_start(sc);

        return 0;
    }
    ```

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

 * 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).

     XXXXX

   This function frees the `mbuf` and calls `ieee80211_free_node(ni)`.

 * 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);
	ieee80211_free_node(ni);
    } 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).
 
 * Watchdog functions
   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);
                        ieee80211_restart_all(ic);
                        return;
                }
                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;

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

        if (startall)
            ieee80211_start_all(ic);
    }
    ```

CVSweb for NetBSD wikisrc <wikimaster@NetBSD.org> software: FreeBSD-CVSweb