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 (6 weeks, 6 days ago) by wiki
Branches: MAIN
CVS tags: HEAD
web commit by martin: Try to improve markup of inline code citations

    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
   21: shows up in `dmesg` or `sysctl net.wlan.devices`.
   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: 
   33:  After creation of the `wlan0` VAP, it will also show up under
   34: 
   35:     sysctl net.wlan.wlan0
   36: 
   37:  and in ifconfig output.
   38: 
   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: 
   43: # Step by Step at the driver level
   44: 
   45:  * The data is split into the generic `struct ieee80211com`
   46:    describing the radio hardware (often used as `ic` pointer),
   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
   49:    structure 'derived' from `struct ieee80211vap` (with some
   50:    additional driver specific data). The VAP structure has the
   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.
   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: 
   61:  * In the attach function, no `struct ifnet ifp` will be needed,
   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
   89:    use it mostly verbatim. Rename it to `driver_get_radiocaps` and
   90:    make it look like:
   91: 
   92:     ```
   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:     }
  105:     ```
  106: 
  107:    and also assign this function to `ic->ic_getradiocaps` in
  108:    the attach function.
  109: 
  110:    An initial setup must happen before calling `ieee80211_ifattach(ic)`.
  111:    You can just call the `driver_get_radiocaps()` function during
  112:    attach like:
  113: 
  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:     ```
  121: 
  122:  * If your driver used to override state machine functions (typical
  123:    `newstate` to controll LEDs when associating or similar),
  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: 
  134:  * Many functions will get a `struct ieee80211vap *vap` passed instead
  135:    of a softc. A typical change to adapt for that looks like:
  136: 
  137:     ```
  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;
  143:     ```
  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: 
  157:     ```
  158:     sc->sc_sendq.ifq_maxlen = ifqmaxlen;
  159:     IFQ_LOCK_INIT(&sc->sc_sendq);
  160:     ```
  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
  165:    `struct ieee80211_node *` of the target node for this packet.
  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
  176:    input / output queues and store these in e.g. `sc->ntxchains`
  177:    and `sc->nrxchains` or similar variables. This information
  178:    needs to be propagated to the common radio structure:
  179: 
  180:     ```
  181:     ic->ic_txstream = sc->ntxchains;
  182:     ic->ic_rxstream = sc->nrxchains;
  183:     ```
  184: 
  185:    Same for interface flags:
  186: 
  187:     ic->ic_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
  188: 
  189:    After calling `ieee80211_ifattach(ic);` some methods in ic
  190:    need to be overwritten. The functions needed are explained
  191:    below, here is a typical setup:
  192: 
  193:     ```
  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;
  205:     ```
  206: 
  207: 
  208:  * detach does not deal with any interfaces any more, remove all traces
  209:    of `struct ifnet *ifp`.
  210: 
  211:  * Add a new function for VAP creation and move all interface specific
  212:    setup there. It looks like:
  213: 
  214:     ```
  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:     }
  227:     ```
  228: 
  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
  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: 
  238:     ```
  239:     /* Use common softint-based if_input */
  240:     ifp->if_percpuq = if_percpuq_create(ifp);
  241:     ```
  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: 
  252:     ```
  253:     static void
  254:     urtwn_vap_delete(struct ieee80211vap *vap)
  255:     {
  256:         struct ifnet *ifp = vap->iv_ifp;
  257:         struct urtwn_softc *sc __unused = vap->iv_ic->ic_softc;
  258: 
  259:         bpf_detach(ifp);
  260:         ieee80211_vap_detach(vap);
  261:         kmem_free(vap, sizeof(struct ieee80211vap));
  262:     }
  263:     ```
  264: 
  265:  * Rate Adaption happens per VAP, so the ra_init call changes like this:
  266: 
  267:     ```
  268:     -static int urtwn_ra_init(struct urtwn_softc *);
  269:     +static int urtwn_ra_init(struct ieee80211vap );
  270:     ```
  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: 
  276:     ```
  277:     -static int urtwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
  278:     +static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int);
  279:     ```
  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: 
  286:     ```
  287:     -static int urtwn_reset(struct ifnet *);
  288:     +static int urtwn_reset(struct ieee80211vap *, u_long cmd);
  289:     ```
  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
  296:    `struct ieee80211com *ic` instead of a softc, and sets
  297:    the radio hardware to the current channel in that structure,
  298:    `ic->ic_curchan`.
  299: 
  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
  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
  306:    be used: `ieee80211_media_change` and `ieee80211_media_status`.
  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
  312:    make sure to pass your override function to `ieee80211_vap_attach`.
  313:    run(4) is an example of this.
  314: 
  315:    The overriden media_{change,status} functions are the only one
  316:    in a wlan driver that should get a `struct ifnet *` argument
  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: 
  324:     ```
  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:     }
  340:     ```
  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: 
  348:     ```
  349:     static int
  350:     urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
  351:         const struct ieee80211_bpf_params *bpfp)
  352:     ```
  353: 
  354:    and the "missing" arguments are derived like:
  355: 
  356:     ```
  357:     struct ieee80211vap *vap = ni->ni_vap;
  358:     struct ieee80211com *ic = ni->ni_ic;
  359:     struct urtwn_softc *sc = ic->ic_softc;
  360:     ```
  361: 
  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`
  367:    function lost its first argument (the radio common ic pointer).
  368: 
  369:      XXXXX
  370: 
  371:    This function frees the `mbuf` and calls `ieee80211_free_node(ni)`.
  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: 
  377:     ```
  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:     }       
  385:     ```
  386: 
  387:  * When the hardware received a full frame, move it into a mbuf
  388:    and call (with proper frame bound checks)
  389: 
  390:     ```
  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:     }
  413:     ```
  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: 
  432:     ```
  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:     }
  449:     ```
  450: 
  451:    There is no `ieee80211_watchdog()` function any more.
  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: 
  465:     ```
  466:     static void
  467:     rtwn_parent(struct ieee80211com *ic)
  468:     {
  469:         struct rtwn_softc *sc = ic->ic_softc;
  470:         bool startall = false;
  471: 
  472:         mutex_enter(&sc->sc_tx_mtx);
  473:         if (ic->ic_nrunning > 0 && !sc->sc_running)
  474:             startall = true;
  475:         else if (ic->ic_nrunning == 0 && sc->sc_running)
  476:             rtwn_stop(sc);
  477:         mutex_exit(&sc->sc_tx_mtx);
  478: 
  479:         if (startall)
  480:             ieee80211_start_all(ic);
  481:     }
  482:     ```

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