Annotation of wikisrc/Converting_drivers_to_the_new_wifi_stack.mdwn, revision 1.6

1.1       wiki        1: # Intro
                      2: 
                      3:  This guide is meant to help you convert an old style NetBSD driver
                      4: for a wifi device to the newer wifi stack based on FreeBSD's current
                      5: version.
                      6: 
                      7:  This is work in progress, as we are trying to create helper functions
                      8: and libraries where usefull, and also the target API is a slightly
                      9: moving target.
                     10: 
                     11: # Overview
                     12: 
                     13:  The main difference between old style and new style drivers is the
                     14: VAP concept. VAP means Virtual Access Point, and it is used to split
                     15: the configuration properties of a wlan (as visible on air) from the
                     16: radio hardware. In many cases multiple wlans can be created using a
                     17: single radio hardware, which is naturally modelled as multiple VAPs
                     18: being created (by pure software) using the same physical radio device.
                     19: 
                     20:  At the user level the radio hardware is hidden quite well - it only
1.6     ! wiki       21: shows up in `dmesg` or `sysctl net.wlan.devices`.
1.1       wiki       22: There is no network interface for the raw hardware, so ifconfig will
                     23: not show them.
                     24: 
                     25:  To create a VAP for a radio device (and that way a network interface)
                     26: use a command like:
                     27: 
                     28:     ifconfig wlan0 create wlandev run0
                     29: 
                     30:  This split between common radio hardware and per-VAP network properties
                     31: reflects on the driver and causes most of the changes needed.
                     32: 
1.6     ! wiki       33:  After creation of the `wlan0` VAP, it will also show up under
1.1       wiki       34: 
1.4       wiki       35:     sysctl net.wlan.wlan0
1.1       wiki       36: 
1.4       wiki       37:  and in ifconfig output.
1.1       wiki       38: 
1.3       wiki       39:   More complete instructions for testing are here: [[Testing new wifi]].
                     40:   There also is an older paper describing the initial design in FreeBSD:
                     41:   [FreeBSD wireless BSDCan 2005](https://www.bsdcan.org/2005/papers/FreeBSDWirelessNetwokringSupport.pdf)
                     42: 
1.1       wiki       43: # Step by Step at the driver level
                     44: 
1.6     ! wiki       45:  * The data is split into the generic `struct ieee80211com`
        !            46:    describing the radio hardware (often used as `ic` pointer),
1.1       wiki       47:    which is part of the drivers softc. The individual VAPs are
                     48:    in a TAILQ in the ic structure, and often are a driver defined
1.6     ! wiki       49:    structure 'derived' from `struct ieee80211vap` (with some
1.1       wiki       50:    additional driver specific data). The VAP structure has the
1.6     ! wiki       51:    network interface `struct ifnet` as `iv_ifp`.
        !            52:    The `if_softc` pointer in the ifnet structure points to the VAP, the VAP structure
        !            53:    has a pointer to the ic `iv_ic`, the ic structure has a `ic_softc` pointer to the softc.
1.1       wiki       54: 
                     55:  * MAC addresses are now stored in ic_macaddr instead of ic_myaddr
                     56:    (in struct ieee80211com). It is typically read from the device
                     57:    and cloned into each VAP. Typical code:
                     58: 
                     59:     IEEE80211_ADDR_COPY(ic->ic_macaddr, rom->macaddr);
                     60: 
1.6     ! wiki       61:  * In the attach function, no `struct ifnet ifp` will be needed,
1.1       wiki       62:    so get rid of code like:
                     63: 
                     64:      struct ifnet *ifp = GET_IFP(sc);
                     65: 
                     66:    and all initialization of ifp members. Move them to the vap_create
                     67:    function (see below) if needed instead, but typically none
                     68:    should be needed (as the generic code initializes the VAP's
                     69:    struct ifnet).
                     70: 
                     71:    Set a direct backlink to the softc, like:
                     72: 
                     73:     ic->ic_softc = sc;
                     74: 
                     75:    Make sure the ic_caps get initialized correctly to the capabilities
                     76:    of the radio, especially you want to add IEEE80211_C_STA for
                     77:    standard AP (station) mode.
                     78: 
                     79:    There is no statemachine in the common radio part, it all moves into
                     80:    per-VAP state, so also remove initialization for it, like:
                     81: 
                     82:     ic->ic_state = IEEE80211_S_INIT;
                     83: 
                     84:    You may want a replacement for IFF_OACATIVE logic for the main device,
                     85:    as there is no interface so no flags to (ab)use for that.
                     86: 
                     87:    Note that the way channels are set up has changed.
                     88:    If your driver had a driver_init_channels() function, you can
1.6     ! wiki       89:    use it mostly verbatim. Rename it to `driver_get_radiocaps` and
1.1       wiki       90:    make it look like:
                     91: 
1.2       wiki       92:     ```
1.1       wiki       93:     static void
                     94:     urtwn_get_radiocaps(struct ieee80211com *ic,
                     95:         int maxchans, int *nchans, struct ieee80211_channel chans[])
                     96:     {
                     97:         uint8_t bands[IEEE80211_MODE_BYTES];
                     98:         
                     99:         memset(bands, 0, sizeof(bands));
                    100:         setbit(bands, IEEE80211_MODE_11B);
                    101:         setbit(bands, IEEE80211_MODE_11G);
                    102:         setbit(bands, IEEE80211_MODE_11NG);
                    103:         ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
                    104:     }
1.2       wiki      105:     ```
1.1       wiki      106: 
1.6     ! wiki      107:    and also assign this function to `ic->ic_getradiocaps` in
1.1       wiki      108:    the attach function.
                    109: 
1.6     ! wiki      110:    An initial setup must happen before calling `ieee80211_ifattach(ic)`.
        !           111:    You can just call the `driver_get_radiocaps()` function during
1.1       wiki      112:    attach like:
                    113: 
1.2       wiki      114:     ```
                    115:     urtwn_get_radiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,   
                    116:          ic->ic_channels);
                    117:     ieee80211_ifattach(ic);
                    118:     [..]
                    119:     ic->ic_getradiocaps = urtwn_get_radiocaps;
                    120:     ```
1.1       wiki      121: 
                    122:  * If your driver used to override state machine functions (typical
1.6     ! wiki      123:    `newstate` to controll LEDs when associating or similar),
1.1       wiki      124:    remove all traces for this from your softc and override a
                    125:    custom VAP type instead (i.e. derive a struct from
                    126:    struct ieee80211vap and use your own struct in your vap_create
                    127:    and vap_delete method).
                    128: 
                    129:    Remove the (likely to exist) old sc_newstate function from
                    130:    your drivers softc (previously used to store the frameworks
                    131:    newstate function, which now is per VAP and moved into your
                    132:    derived per-VAP structure.
                    133: 
1.6     ! wiki      134:  * Many functions will get a `struct ieee80211vap *vap` passed instead
1.1       wiki      135:    of a softc. A typical change to adapt for that looks like:
                    136: 
1.2       wiki      137:     ```
1.1       wiki      138:     -       struct ieee80211com *ic = &sc->sc_ic;
                    139:     -       struct ieee80211_node *ni = ic->ic_bss;
                    140:     +       struct ieee80211com *ic = vap->iv_ic;
                    141:     +       struct rtwn_softc *sc = ic->ic_softc;
                    142:     +       struct ieee80211_node *ni = vap->iv_bss;
1.2       wiki      143:     ```
1.1       wiki      144: 
                    145:  * The hardware driver will need a global new outgoing packet queue
                    146:    (as the per-interface queues are attached to VAPs, not the single
                    147:    common transmit hardware). Add the new ifque like to your softc
                    148:    like this:
                    149: 
                    150:     struct ifqueue sc_sendq;
                    151: 
                    152:    While there, check that there is no "struct ethercom sc_ec" in your
                    153:    softc - it is not needed.
                    154: 
                    155:    This queue needs initialization in the attach function, like:
                    156: 
1.2       wiki      157:     ```
1.1       wiki      158:     sc->sc_sendq.ifq_maxlen = ifqmaxlen;
                    159:     IFQ_LOCK_INIT(&sc->sc_sendq);
1.2       wiki      160:     ```
1.1       wiki      161: 
                    162:    This device private send queue (writer: any of the current VAPs
                    163:    of this interface, consumer: the devices transmit interrupt handler)
                    164:    abuses the mbuf rcvif pointer to store the node pointer
1.6     ! wiki      165:    `struct ieee80211_node *` of the target node for this packet.
1.1       wiki      166:    When dequeing a packet before handing it over to the output function,
                    167:    the node is extracted and the rcvif pointer set to NULL.
                    168:    Yes - this is a hack.
                    169: 
                    170:    Also the attach function needs to set the name of the interface
                    171:    in the common radio device structure:
                    172: 
                    173:     ic->ic_name = device_xname(self);
                    174: 
                    175:    Many drivers query the hardware/firmware for the number of available
1.6     ! wiki      176:    input / output queues and store these in e.g. `sc->ntxchains`
        !           177:    and `sc->nrxchains` or similar variables. This information
1.1       wiki      178:    needs to be propagated to the common radio structure:
                    179: 
1.2       wiki      180:     ```
1.1       wiki      181:     ic->ic_txstream = sc->ntxchains;
                    182:     ic->ic_rxstream = sc->nrxchains;
1.2       wiki      183:     ```
1.1       wiki      184: 
                    185:    Same for interface flags:
                    186: 
                    187:     ic->ic_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
                    188: 
1.6     ! wiki      189:    After calling `ieee80211_ifattach(ic);` some methods in ic
1.1       wiki      190:    need to be overwritten. The functions needed are explained
                    191:    below, here is a typical setup:
                    192: 
1.2       wiki      193:     ```
1.1       wiki      194:     /* override default methods */
                    195:     ic->ic_newassoc = urtwn_newassoc;
                    196:     ic->ic_wme.wme_update = urtwn_wme_update;
                    197:     ic->ic_vap_create = urtwn_vap_create;
                    198:     ic->ic_vap_delete = urtwn_vap_delete;
                    199:     ic->ic_parent = urtwn_parent;
                    200:     ic->ic_scan_start = urtwn_scan_start;
                    201:     ic->ic_scan_end = urtwn_scan_end;
                    202:     ic->ic_set_channel = urtwn_set_channel;
                    203:     ic->ic_transmit = urtwn_transmit;
                    204:     ic->ic_raw_xmit = urtwn_raw_xmit;
1.2       wiki      205:     ```
1.1       wiki      206: 
                    207: 
                    208:  * detach does not deal with any interfaces any more, remove all traces
1.6     ! wiki      209:    of `struct ifnet *ifp`.
1.1       wiki      210: 
                    211:  * Add a new function for VAP creation and move all interface specific
                    212:    setup there. It looks like:
                    213: 
1.2       wiki      214:     ```
1.1       wiki      215:     static struct ieee80211vap *
                    216:     urtwn_vap_create(struct ieee80211com *ic,  const char name[IFNAMSIZ],
                    217:         int  unit, enum ieee80211_opmode opmode, int flags,
                    218:         const uint8_t bssid[IEEE80211_ADDR_LEN],
                    219:         const uint8_t macaddr[IEEE80211_ADDR_LEN])
                    220:     {
                    221:            struct urtwn_softc *sc = ic->ic_softc;
                    222:            struct ifnet *ifp;
                    223:            struct ieee80211vap *vap;
                    224:     ...
                    225:            return vap;
                    226:     }
1.2       wiki      227:     ```
1.1       wiki      228: 
1.6     ! wiki      229:    It allocates a new VAP with `kmem_zalloc` initializes it
        !           230:    by calling `ieee80211_vap_setup` for it and then ovverriding whatever
        !           231:    the driver needs, passes it to `ieee80211_vap_attach` and
1.1       wiki      232:    attaches bpf to the interface.
                    233: 
                    234:    If your interrupt handler is called in hard interrupt context
                    235:    (that probably means: for all devices that are not on USB)
                    236:    you need to initialize the per CPU interface queue:
                    237: 
1.2       wiki      238:     ```
1.1       wiki      239:     /* Use common softint-based if_input */
                    240:     ifp->if_percpuq = if_percpuq_create(ifp);
1.2       wiki      241:     ```
1.1       wiki      242: 
                    243:    If your interrupt handler always is called in softint context,
                    244:    do not do this. If if_percpuq is NULL the wifi framework will
                    245:    directly handle incoming packets, otherwise it will take a
                    246:    round trip via a per cpu softint handler.
                    247: 
                    248:  * Add a new function for VAP destruction and move interface specific
                    249:    parts that you deleted in the detach() function here. A very
                    250:    minimalistic example looks like:
                    251: 
1.2       wiki      252:     ```
1.1       wiki      253:     static void
                    254:     urtwn_vap_delete(struct ieee80211vap *vap)
                    255:     {
                    256:         struct ifnet *ifp = vap->iv_ifp;
1.5       wiki      257:         struct urtwn_softc *sc __unused = vap->iv_ic->ic_softc;
1.1       wiki      258: 
                    259:         bpf_detach(ifp);
                    260:         ieee80211_vap_detach(vap);
                    261:         kmem_free(vap, sizeof(struct ieee80211vap));
                    262:     }
1.2       wiki      263:     ```
1.1       wiki      264: 
                    265:  * Rate Adaption happens per VAP, so the ra_init call changes like this:
                    266: 
1.2       wiki      267:     ```
1.1       wiki      268:     -static int urtwn_ra_init(struct urtwn_softc *);
                    269:     +static int urtwn_ra_init(struct ieee80211vap );
1.2       wiki      270:     ```
1.1       wiki      271: 
                    272:    See above for recipes how to acces the needed structs in there.
                    273: 
                    274:  * State is per VAP, so newstate changes the first parameter:
                    275: 
1.2       wiki      276:     ```
1.1       wiki      277:     -static int urtwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
                    278:     +static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int);
1.2       wiki      279:     ```
1.1       wiki      280: 
                    281:    If there was a newstate_cb function previously, it is probably not needed
                    282:    any more.
                    283: 
                    284:  * Reset is per interface, so per VAP:
                    285: 
1.2       wiki      286:     ```
1.1       wiki      287:     -static int urtwn_reset(struct ifnet *);
                    288:     +static int urtwn_reset(struct ieee80211vap *, u_long cmd);
1.2       wiki      289:     ```
1.1       wiki      290: 
                    291:    A driver is not required to provide this method, if not filled in a
                    292:    default method will be used that always causes a full reset of the
                    293:    device.
                    294: 
                    295:  * The set channel function is generic now, so gets passed a
1.6     ! wiki      296:    `struct ieee80211com *ic` instead of a softc, and sets
1.1       wiki      297:    the radio hardware to the current channel in that structure,
1.6     ! wiki      298:    `ic->ic_curchan`.
1.1       wiki      299: 
1.6     ! wiki      300:  * The start function does not need a `struct ifnet *ifp` any more,
        !           301:    but can use a softc or `struct ieee80211com *ic` instead. It is
1.1       wiki      302:    supposed to send management frames (from ic->ic_mgtq) first, then
                    303:    everything in the device sc_sendq (see above).
                    304: 
                    305:  * Typicall generic "media change" and "status" functions will
1.6     ! wiki      306:    be used: `ieee80211_media_change` and `ieee80211_media_status`.
1.1       wiki      307:    Old drivers often implement their own function - remove that (unless
                    308:    there is something actually chip specific in them).
                    309: 
                    310:    If there is, override media_change and/or media_status, and make
                    311:    sure to call the generic variant for all the real work. Also
1.6     ! wiki      312:    make sure to pass your override function to `ieee80211_vap_attach`.
1.1       wiki      313:    run(4) is an example of this.
                    314: 
                    315:    The overriden media_{change,status} functions are the only one
1.6     ! wiki      316:    in a wlan driver that should get a `struct ifnet *` argument
1.1       wiki      317:    passed. Checking for this is a good measurement to know you are
                    318:    done with conversion (besides the kernel build failing, of course).
                    319: 
                    320:  * Your driver needs a new transmit function, used to enqueue a
                    321:    mbuf to the hardware send queue (and start transmission if needed).
                    322:    A very simple example:
                    323: 
1.2       wiki      324:     ```
1.1       wiki      325:     static int
                    326:     urtwn_transmit(struct ieee80211com *ic, struct mbuf *m)
                    327:     {  
                    328:         struct urtwn_softc *sc = ic->ic_softc;
                    329:         int s;
                    330:         
                    331:         s = splnet();
                    332:         IF_ENQUEUE(&sc->sc_sendq, m);
                    333:         splx(s);
                    334: 
                    335:         if (!sc->sc_oactive)
                    336:                 urtwn_start(sc);
                    337: 
                    338:         return 0;
                    339:     }
1.2       wiki      340:     ```
1.1       wiki      341: 
                    342:    A pointer to this function is assigned to the ic->ic_transmit member
                    343:    in the attach function.
                    344: 
                    345:  * Your driver needs a raw_xmit function which is close to what
                    346:    the old driver called *_tx(), it looks like:
                    347: 
1.2       wiki      348:     ```
1.1       wiki      349:     static int
                    350:     urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
                    351:         const struct ieee80211_bpf_params *bpfp)
1.2       wiki      352:     ```
1.1       wiki      353: 
                    354:    and the "missing" arguments are derived like:
                    355: 
1.2       wiki      356:     ```
1.1       wiki      357:     struct ieee80211vap *vap = ni->ni_vap;
                    358:     struct ieee80211com *ic = ni->ni_ic;
                    359:     struct urtwn_softc *sc = ic->ic_softc;
1.2       wiki      360:     ```
1.1       wiki      361: 
1.6     ! wiki      362:    The check for encrypting packets often was testing the `ieee80211_frame'
        !           363:    wh->i_fc[1] & IEEE80211_FC1_WE` before encapsulating the `mbuf`
        !           364:    via `ieee80211_crypto_encap()`. The check now is for
        !           365:    `wh->i_fc[1] & IEEE80211_FC1_PROTECTED`, since WEP is a thing of the
        !           366:    past and there are other protocols. Also the `ieee80211_crypto_encap`
1.1       wiki      367:    function lost its first argument (the radio common ic pointer).
                    368: 
                    369:      XXXXX
                    370: 
1.6     ! wiki      371:    This function frees the `mbuf` and calls `ieee80211_free_node(ni)`.
1.1       wiki      372: 
                    373:  * If the driver_activate() function only passes deactivation requests
                    374:    on to if_deactivate() you can replace it by a shim that gets
                    375:    your softc and calls ieee80211_activate(), like:
                    376: 
1.2       wiki      377:     ```
1.1       wiki      378:     static int
                    379:     urtwn_activate(device_t self, enum devact act)
                    380:     {
                    381:         struct urtwn_softc *sc = device_private(self);
                    382:         
                    383:         return ieee80211_activate(&sc->sc_ic, act);
                    384:     }       
1.2       wiki      385:     ```
1.1       wiki      386: 
                    387:  * When the hardware received a full frame, move it into a mbuf
                    388:    and call (with proper frame bound checks)
                    389: 
1.2       wiki      390:     ```
1.1       wiki      391:     struct ieee80211_node *ni;
                    392: 
                    393:     // XXX unclear - same on NetBSD?
                    394:     if (ieee80211_radiotap_active(ic))
                    395:         mactime = rx->mactime;
                    396:     // or:
                    397:     if (ieee80211_radiotap_active(ic)) 
                    398:         struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap;
                    399: 
                    400:         tap->wr_tsf = mactime;
                    401:         tap->wr_flags = 0;
                    402:         tap->wr_dbm_antsignal = (int8_t)rssi;
                    403:     }
                    404: 
                    405:     ni = ieee80211_find_rxnode(ic,
                    406:         mtod(m, struct ieee80211_frame_min *));
                    407:     if (ni != NULL) {
                    408:         ieee80211_input(ni, m, rssi, nf);
                    409:        ieee80211_free_node(ni);
                    410:     } else {
                    411:         ieee80211_input_all(ic, m, rssi, nf);
                    412:     }
1.2       wiki      413:     ```
1.1       wiki      414: 
                    415:    If a node is found, the data is passed on to that VAP, otherwise
                    416:    it is a general management packet.
                    417: 
                    418:    Old code often has tests for too short or too long packets and
                    419:    increments error counters in the struct ifnet of the interface
                    420:    that used to belong to the wlan driver. Since there is no such
                    421:    single interface anymore, errors are accounted later (at the VAP
                    422:    level) if a VAP/node is identified for the packet. Otherwise
                    423:    just increment the global ic->ic_ierrors counter for errors with
                    424:    incoming packets that can not be associated with a concrete
                    425:    network (or similar ic->ic_oerrors for output errors).
                    426:  
                    427:  * Watchdog functions
                    428:    Since there is no 1:1 ifnet and its global watchdog mechanism, all
                    429:    driver watchdog functions are converted to a callout.
                    430:    A typical implementation looks like:
                    431: 
1.2       wiki      432:     ```
1.1       wiki      433:     static void     
                    434:     urtwn_watchdog(void *arg)
                    435:     {
                    436:         struct urtwn_softc *sc = arg;
                    437:        struct ieee80211com *ic = &sc->sc_ic;
                    438: 
                    439:         if (sc->tx_timer > 0) {
                    440:                 if (--sc->tx_timer == 0) {
                    441:                         aprint_error_dev(sc->sc_dev, "device timeout\n");
                    442:                         ieee80211_stat_add(&ic->ic_oerrors, 1);
                    443:                         ieee80211_restart_all(ic);
                    444:                         return;
                    445:                 }
                    446:                 callout_schedule(&sc->sc_watchdog_to, hz);
                    447:         }
                    448:     }
1.2       wiki      449:     ```
1.1       wiki      450: 
1.6     ! wiki      451:    There is no `ieee80211_watchdog()` function any more.
1.1       wiki      452: 
                    453: 
                    454:  * ioctl functions
                    455:    Typicaly there are no ioctl functions in a wlan driver. The ioctls
                    456:    are handled in generic code and the ic_parent function is called to
                    457:    update the device state accordingly.  XXXX - is this true? implemented?
                    458:    Or one of ic_scan_start/ic_scan_end/ic_set_channel
                    459: 
                    460:  * device _parent function
                    461:    You need to add a new function to update the device state when one of
                    462:    the active VAPs requires something special, like switch to promiscous
                    463:    mode. The function looks like:
                    464: 
1.2       wiki      465:     ```
1.1       wiki      466:     static void
                    467:     rtwn_parent(struct ieee80211com *ic)
                    468:     {
                    469:         struct rtwn_softc *sc = ic->ic_softc;
1.2       wiki      470:         bool startall = false;
1.1       wiki      471: 
                    472:         mutex_enter(&sc->sc_tx_mtx);
                    473:         if (ic->ic_nrunning > 0 && !sc->sc_running)
1.2       wiki      474:             startall = true;
                    475:         else if (ic->ic_nrunning == 0 && sc->sc_running)
                    476:             rtwn_stop(sc);
1.1       wiki      477:         mutex_exit(&sc->sc_tx_mtx);
                    478: 
                    479:         if (startall)
1.2       wiki      480:             ieee80211_start_all(ic);
1.1       wiki      481:     }
1.2       wiki      482:     ```

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