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, 3 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

    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: # Help available!
   44: 
   45: If your device is a usb(4) device, you can use a new helper facility "usbwifi" similar
   46: to usbnet(9), which already handles a lot of new wifi stack specific details.
   47: In the following text the items not applicable for drivers using usbwifi are marked
   48: like this: [!]*usbwifi*{X} or `// !usbwifi` if appearing in code sections.
   49: 
   50: # Step by Step at the driver level
   51: 
   52:  * The data is split into the generic `struct ieee80211com`
   53:    describing the radio hardware (often used as `ic` pointer),
   54:    which is part of the drivers softc. The individual VAPs are
   55:    in a TAILQ in the ic structure, and often are a driver defined
   56:    structure 'derived' from `struct ieee80211vap` (with some
   57:    additional driver specific data). The VAP structure has the
   58:    network interface `struct ifnet` as `iv_ifp`.
   59:    The `if_softc` pointer in the ifnet structure points to the VAP, the VAP structure
   60:    has a pointer to the ic `iv_ic`, the ic structure has a `ic_softc` pointer to the softc.
   61: 
   62:  * MAC addresses are now stored in ic_macaddr instead of ic_myaddr
   63:    (in struct ieee80211com). It is typically read from the device
   64:    and cloned into each VAP. Typical code:
   65: 
   66:         IEEE80211_ADDR_COPY(ic->ic_macaddr, rom->macaddr);
   67: 
   68:  * In the attach function, no `struct ifnet ifp` will be needed,
   69:    so get rid of code like:
   70: 
   71:          struct ifnet *ifp = GET_IFP(sc);
   72: 
   73:    and all initialization of ifp members. Move them to the vap_create
   74:    function (see below) if needed instead, but typically none
   75:    should be needed (as the generic code initializes the VAP's
   76:    struct ifnet).
   77: 
   78:    Set a direct backlink to the softc, like:
   79: 
   80:         ic->ic_softc = sc;
   81: 
   82:    Make sure the ic_caps get initialized correctly to the capabilities
   83:    of the radio, especially you want to add IEEE80211_C_STA for
   84:    standard AP (station) mode.
   85: 
   86:    There is no state machine in the common radio part, it all moves into
   87:    per-VAP state, so also remove initialization for it, like:
   88: 
   89:         ic->ic_state = IEEE80211_S_INIT;
   90: 
   91:    You may want a replacement for `IFF_OACATIVE` logic for the main device,
   92:    as there is no interface so no flags to (ab)use for that.
   93: 
   94:  * The way channels are set up has changed.
   95:    If your driver had a `driver_init_channels()` function, you can
   96:    use it mostly verbatim (but see caveats below for `maxchans` and `nchans`).
   97:    Rename it to `driver_get_radiocaps` and make it look like:
   98: 
   99: 
  100:         static void
  101:         urtwn_get_radiocaps(struct ieee80211com *ic,
  102:             int maxchans, int *nchans, struct ieee80211_channel chans[])
  103:         {
  104:             uint8_t bands[IEEE80211_MODE_BYTES];
  105:             
  106:             memset(bands, 0, sizeof(bands));
  107:             setbit(bands, IEEE80211_MODE_11B);
  108:             setbit(bands, IEEE80211_MODE_11G);
  109:             setbit(bands, IEEE80211_MODE_11NG);
  110:             // XXX see description below if your chip can use other ranges (like 5ghz)
  111:             ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
  112:         }
  113: 
  114: 
  115:    and also assign this function to `ic->ic_getradiocaps` in
  116:    the attach function.
  117: 
  118:    An initial setup must happen before calling `ieee80211_ifattach(ic)`.
  119:    You can just call the `driver_get_radiocaps()` function during
  120:    attach like:
  121: 
  122:         urtwn_get_radiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,   
  123:              ic->ic_channels);
  124:         ieee80211_ifattach(ic);
  125:         [..]
  126:         ic->ic_getradiocaps = urtwn_get_radiocaps;
  127: 
  128:    If the hardware can use different frequency bands you may have to add them
  129:    band by band instead of using `ieee80211_add_channels_default_2ghz`, also if
  130:    the firmware knows about regulatory domains it may be required to select
  131:    subsets of channels.
  132: 
  133:    An example of a multi-frequency driver doing this is iwm(4).
  134: 
  135:    It uses a helper function to add single channels individually
  136:    via `ieee80211_add_channel`.
  137:    To add the channels for a band, it calls
  138:    `iwm_add_channel_band(ic, chans, maxchans, nchans, start, num, bands)`
  139:    where `chans`, `maxchans` and `nchans` are the arguments passed to
  140:    this callback, `chans` collecting the resulting channels, `nchans` pointing
  141:    to the number of channels already collected, `bands` is the bitset of
  142:    bands to add (multiple `IEEE80211_MODE_*` bits). The offset `start`
  143:    is the index of the first channel to add to the output (first call will
  144:    always be with 0 as `start`), and `num` the number of channels
  145:    you expect `iwm_add_channel_band` to add. The `iwm_add_channel_band`
  146:    helper function iterates over the channels, gets flags for the channels
  147:    from firmware and calls `ieee80211_add_channel`.
  148: 
  149:    Overall the most important part in conversion of old drivers is to make sure that
  150:    the out parameter `*nchans` is properly set to the number of channel data
  151:    actually filled, and to respect the `maxchans` limit. This callback
  152:    is called from two places, once from the drivers attach function,
  153:    where `maxchan` is big enough (always `IEEE80211_CHAN_MAX`), and from
  154:    ioctl `IEEE80211_IOC_DEVCAPS` where `maxchan` depends on the userland arguments
  155:    and e.g. wpa_supplicant only asks for minmal data, as it is not interested
  156:    in the actual channels available (`maxchan` is 1). The ioctl handling code
  157:    will KASSERT proper behaviour of the callback.
  158: 
  159:  * If your driver used to override state machine functions (typical
  160:    `newstate` to controll LEDs when associating or similar),
  161:    remove all traces for this from your softc and override a
  162:    custom VAP type instead (i.e. derive a struct from
  163:    `struct ieee80211vap` and use your own struct in your `vap_create`
  164:    and `vap_delete` methods).
  165: 
  166:    Remove the (likely to exist) old `sc_newstate` function from
  167:    your drivers softc (previously used to store the frameworks
  168:    newstate function, which now is per-VAP and moved into your
  169:    derived per-VAP structure.
  170: 
  171:  * Many functions will get a `struct ieee80211vap *vap` passed instead
  172:    of a softc. A typical change to adapt for that looks like:
  173: 
  174:         -       struct ieee80211com *ic = &sc->sc_ic;
  175:         -       struct ieee80211_node *ni = ic->ic_bss;
  176:         +       struct ieee80211com *ic = vap->iv_ic;
  177:         +       struct rtwn_softc *sc = ic->ic_softc;
  178:         +       struct ieee80211_node *ni = vap->iv_bss;
  179: 
  180:  * The hardware driver will need a global new outgoing packet queue
  181:    (as the per-interface queues are attached to VAPs, not the single
  182:    common transmit hardware). Add the new ifqueue like to your softc
  183:    like this:
  184: 
  185:         struct ifqueue sc_sendq;
  186: 
  187:    If your hardware supports multiple transmit priorities you may
  188:    consider doing more than one ifqueue. Usbwifi does this for devices
  189:    with multiple transmit pipes (creating a ifqueue per hardware priority
  190:    and in this case USB send endpoint).
  191: 
  192:    While there, check that there is no `struct ethercom sc_ec` in your
  193:    softc - it is not needed.
  194: 
  195:    This queue needs initialization in the attach function, like:
  196: 
  197:         sc->sc_sendq.ifq_maxlen = ifqmaxlen;
  198:         IFQ_LOCK_INIT(&sc->sc_sendq);
  199: 
  200: 
  201:    This device private send queue (writer: any of the current VAPs
  202:    of this interface, consumer: the devices transmit interrupt handler)
  203:    abuses the `mbuf rcvif` pointer to store the node pointer
  204:    `struct ieee80211_node *` of the target node for this packet.
  205:    When dequeuing a packet before handing it over to the output function,
  206:    the node is extracted and the `rcvif` pointer set to `NULL`.
  207:    Yes - this is a hack.
  208: 
  209:    Also the attach function needs to set the name of the interface
  210:    in the common radio device structure:
  211: 
  212:         ic->ic_name = device_xname(self);
  213: 
  214:    Many drivers query the hardware/firmware for the number of available
  215:    input/output queues and store these in e.g. `sc->ntxchains`
  216:    and `sc->nrxchains` or similar variables. This information
  217:    needs to be propagated to the common radio structure:
  218: 
  219:         ic->ic_txstream = sc->ntxchains;
  220:         ic->ic_rxstream = sc->nrxchains;
  221: 
  222:    Same for interface flags:
  223: 
  224:         ic->ic_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
  225: 
  226:    After calling `ieee80211_ifattach(ic);` some methods in `ic`
  227:    need to be overwritten. The functions needed are explained
  228:    below, here is a typical setup:
  229: 
  230:         /* override default methods */
  231:         ic->ic_newassoc = urtwn_newassoc;
  232:         ic->ic_wme.wme_update = urtwn_wme_update;
  233:         ic->ic_vap_create = urtwn_vap_create;
  234:         ic->ic_vap_delete = urtwn_vap_delete;
  235:         ic->ic_parent = urtwn_parent;
  236:         ic->ic_scan_start = urtwn_scan_start;
  237:         ic->ic_scan_end = urtwn_scan_end;
  238:         ic->ic_set_channel = urtwn_set_channel;
  239:         ic->ic_transmit = urtwn_transmit;    // !usbwifi
  240:         ic->ic_raw_xmit = urtwn_raw_xmit;    // !usbwifi
  241: 
  242:  * detach does not deal with any interfaces any more, remove all traces
  243:    of `struct ifnet *ifp`.
  244: 
  245:  * Add a new function for VAP creation and move all interface specific
  246:    setup there. It looks like:
  247: 
  248:         static struct ieee80211vap *
  249:         urtwn_vap_create(struct ieee80211com *ic,  const char name[IFNAMSIZ],
  250:             int  unit, enum ieee80211_opmode opmode, int flags,
  251:             const uint8_t bssid[IEEE80211_ADDR_LEN],
  252:             const uint8_t macaddr[IEEE80211_ADDR_LEN])
  253:         {
  254:                struct urtwn_softc *sc = ic->ic_softc;
  255:                struct ifnet *ifp;
  256:                struct ieee80211vap *vap;
  257:         ...
  258:                return vap;
  259:         }
  260: 
  261:    It allocates a new VAP with `kmem_zalloc` initializes it
  262:    by calling `ieee80211_vap_setup` for it and then overriding whatever
  263:    the driver needs, passes it to `ieee80211_vap_attach` and
  264:    attaches BPF to the interface.
  265: 
  266:    If your interrupt handler is called in hard interrupt context
  267:    (that probably means: for all devices that are not on USB)
  268:    you need to initialize the per CPU interface queue:
  269: 
  270:         /* Use common softint-based if_input */
  271:         ifp->if_percpuq = if_percpuq_create(ifp);
  272: 
  273:    If your interrupt handler always is called in softint context,
  274:    do not do this. If `if_percpuq` is `NULL` the wifi framework will
  275:    directly handle incoming packets, otherwise it will take a
  276:    round trip via a per cpu softint handler.
  277: 
  278:  * Add a new function for VAP destruction and move interface specific
  279:    parts that you deleted in the detach function here. A very
  280:    minimalistic example looks like:
  281: 
  282:         static void
  283:         urtwn_vap_delete(struct ieee80211vap *vap)
  284:         {
  285:             struct ifnet *ifp = vap->iv_ifp;
  286:             struct urtwn_softc *sc __unused = vap->iv_ic->ic_softc;
  287: 
  288:             bpf_detach(ifp);
  289:             ieee80211_vap_detach(vap);
  290:             kmem_free(vap, sizeof(struct ieee80211vap));
  291:         }
  292: 
  293:  * Rate Adaption happens per VAP, so the ra_init call changes like this:
  294: 
  295:         -static int urtwn_ra_init(struct urtwn_softc *);
  296:         +static int urtwn_ra_init(struct ieee80211vap );
  297: 
  298:    See above for recipes how to access the needed structs in there.
  299: 
  300:  * State is per VAP, so newstate changes the first parameter:
  301: 
  302:         -static int urtwn_newstate(struct ieee80211com *, enum ieee80211_state, int);
  303:         +static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int);
  304: 
  305:    If there was a newstate_cb function previously, it is probably not needed
  306:    any more.
  307: 
  308:  * Reset is per interface, so per VAP:
  309: 
  310:         -static int urtwn_reset(struct ifnet *);
  311:         +static int urtwn_reset(struct ieee80211vap *, u_long cmd);
  312: 
  313:    A driver is not required to provide this method, if not filled in a
  314:    default method will be used that always causes a full reset of the
  315:    device.
  316: 
  317:  * The set channel function is generic now, so gets passed a
  318:    `struct ieee80211com *ic` instead of a softc, and sets
  319:    the radio hardware to the current channel in that structure,
  320:    `ic->ic_curchan`.
  321: 
  322:  * The start function does not need a `struct ifnet *ifp` any more,
  323:    but can use a softc or `struct ieee80211com *ic` instead.
  324: 
  325:  * Typically, generic media change and status functions will
  326:    be used: `ieee80211_media_change` and `ieee80211_media_status`.
  327:    Old drivers often implement their own function - remove that (unless
  328:    there is something actually chip specific in them).
  329: 
  330:    If there is, override media_change and/or media_status, and make
  331:    sure to call the generic variant for all the real work.
  332:    Also make sure to pass your override function to `ieee80211_vap_attach.`
  333:    run(4) is an example of this.
  334: 
  335:    The overridden media_\{change,status\} functions are the only one
  336:    in a wlan driver that should get a `struct ifnet *` argument
  337:    passed.
  338:    Checking for this is a good measurement to know you are done with
  339:    conversion (besides the kernel build failing, of course).
  340: 
  341:  * [!]*usbwifi*{X} Your driver needs a new transmit function, used to enqueue a
  342:    mbuf to the hardware send queue (and start transmission if needed).
  343:    A very simple example:
  344: 
  345:         static int
  346:         urtwn_transmit(struct ieee80211com *ic, struct mbuf *m)
  347:         {
  348:             struct urtwn_softc *sc = ic->ic_softc;
  349:             int s;
  350:             
  351:             s = splnet();
  352:             IF_ENQUEUE(&sc->sc_sendq, m);
  353:             splx(s);
  354: 
  355:             if (!sc->sc_oactive)
  356:                     urtwn_start(sc);
  357: 
  358:             return 0;
  359:         }
  360: 
  361: 
  362:    A pointer to this function is assigned to the `ic->ic_transmit` member
  363:    in the attach function.
  364: 
  365:  * [!]*usbwifi*{X} Your driver needs a raw_xmit function which is close to what
  366:    the old driver called *_tx(), it looks like:
  367: 
  368: 
  369:         static int
  370:         urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
  371:             const struct ieee80211_bpf_params *bpfp)
  372: 
  373:    and the "missing" arguments are derived like:
  374: 
  375:         struct ieee80211vap *vap = ni->ni_vap;
  376:         struct ieee80211com *ic = ni->ni_ic;
  377:         struct urtwn_softc *sc = ic->ic_softc;
  378: 
  379:    The check for encrypting packets often was testing the `ieee80211_frame`
  380:    `wh->i_fc[1] & IEEE80211_FC1_WE` before encapsulating the mbuf
  381:    via `ieee80211_crypto_encap()`. The check now is for
  382:    `wh->i_fc[1] & IEEE80211_FC1_PROTECTED`, since WEP is a thing of the
  383:    past and there are other protocols. Also the `ieee80211_crypto_encap`
  384:    function lost its first argument (the radio common ic pointer).
  385: 
  386:    The transmit function queues the mbuf for transmit and
  387:    makes sure when transmission is done by hardware to call
  388:    `ieee80211_tx_complete` with both `mbuf` and `ni`. This function
  389:    also needs to be called in all error cases, with an error value as
  390:    status (last argument). The actual value does not matter, the stack
  391:    just checks for 0 (success) and non zero (failure). This call
  392:    will free the mbuf and the node, and in some cases also cause
  393:    further state changes (like waking up VAPs, scheduling a new scan
  394:    later, or even causing immediate state machine transitions and retrying
  395:    in error cases).
  396: 
  397:  * [!]*usbwifi*{X} If the `driver_activate()` function only passes deactivation requests
  398:    on to if_deactivate() you can replace it by a shim that gets
  399:    your softc and calls `ieee80211_activate()`, like:
  400: 
  401:         static int
  402:         urtwn_activate(device_t self, enum devact act)
  403:         {
  404:             struct urtwn_softc *sc = device_private(self);
  405:             
  406:             return ieee80211_activate(&sc->sc_ic, act);
  407:         }
  408: 
  409:  * When the hardware received a full frame, move it into a mbuf
  410:    and call (with proper frame bound checks)
  411: 
  412:         struct ieee80211_node *ni;
  413: 
  414:         // XXX unclear - same on NetBSD?
  415:         if (ieee80211_radiotap_active(ic))
  416:             mactime = rx->mactime;
  417:         // or:
  418:         if (ieee80211_radiotap_active(ic)) 
  419:             struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap;
  420: 
  421:             tap->wr_tsf = mactime;
  422:             tap->wr_flags = 0;
  423:             tap->wr_dbm_antsignal = (int8_t)rssi;
  424:         }
  425: 
  426:         ni = ieee80211_find_rxnode(ic,
  427:             mtod(m, struct ieee80211_frame_min *));
  428:         if (ni != NULL) {
  429:             ieee80211_input(ni, m, rssi, nf);
  430:             ieee80211_free_node(ni);
  431:         } else {
  432:             ieee80211_input_all(ic, m, rssi, nf);
  433:         }
  434: 
  435:    If a node is found, the data is passed on to that VAP, otherwise
  436:    it is a general management packet.
  437: 
  438:    Old code often has tests for too short or too long packets and
  439:    increments error counters in the struct ifnet of the interface
  440:    that used to belong to the wlan driver. Since there is no such
  441:    single interface anymore, errors are accounted later (at the VAP
  442:    level) if a VAP/node is identified for the packet. Otherwise
  443:    just increment the global `ic->ic_ierrors` counter for errors with
  444:    incoming packets that can not be associated with a concrete
  445:    network (or similar `ic->ic_oerrors` for output errors).
  446: 
  447:  * Various BPF changes:
  448: 
  449:    Many drivers avoid complex setup options for BPF listeners if there are none.
  450:    With virtual APs this became a bit more complex, but the framework tracks it
  451:    for the driver. To test if any VAP on the radio hardware is in MONITOR mode and has
  452:    an active  BPF peer, simply test the `ic->ic_montaps` counter - it will be zero if
  453:    there are none.
  454: 
  455:    For most drivers it should not be necessary to do anything special for BPF, the
  456:    taps are in the framework.
  457: 
  458:    Some drivers provide special low level radiotap data, often via a driver specific
  459:    `sc->sc_drvbpf`. This is replaced by a generic radiotap bpf listener created by
  460:    the framework code and stored as `vap-> iv_rawbpf`. A driver signals support for
  461:    radiotap by making `ic->ic_th` point to a radiotap transmit header (usually in
  462:    the softc, initialized before attach and updated on transmit) and similar `ic->ic_rh` 
  463:    point to a receive tap header (updated on every received packet). Make sure to initialize
  464:    the tap headers `it_len` fields to the proper size (in little endian byte order)
  465:    and `it_present` flags (also in little endian byte order) to the relevant
  466:    `IEEE80211_RADIOTAP_*` bits.
  467: 
  468:    To avoid updating the receive/transmit tap headers unessecarily the driver can check
  469:    the `ic->ic_flags_ext` for the `IEEE80211_FEXT_BPF` bit being set - if not, there is
  470:    no VAP with bpf listener on radiotap headers.
  471: 
  472:    The driver needs to update `ic_rh` (usually only the storage for a single header
  473:    it points to) on every received packet. This is used as a side channel for the
  474:    radiotap data when passing packet up the stack via `ieee80211_input_all` or
  475:    `ieee80211_input`. Or in the NetBSD case for USB drivers when calling `usbwifi_enqueue`.
  476:    The stack automatically dispatches this to the radiotap bpf tap associated with any
  477:    relevant VAP and also makes sure it is seen by VAPs in MONITOR mode.
  478: 
  479:  * Watchdog functions move to callout:
  480: 
  481:    Since there is no 1:1 ifnet and its global watchdog mechanism, all
  482:    driver watchdog functions are converted to a callout.
  483:    A typical implementation looks like:
  484: 
  485:         static void
  486:         urtwn_watchdog(void *arg)
  487:         {
  488:             struct urtwn_softc *sc = arg;
  489:             struct ieee80211com *ic = &sc->sc_ic;
  490: 
  491:             if (sc->tx_timer > 0) {
  492:                     if (--sc->tx_timer == 0) {
  493:                             aprint_error_dev(sc->sc_dev, "device timeout\n");
  494:                             ieee80211_stat_add(&ic->ic_oerrors, 1);
  495:                             ieee80211_restart_all(ic);
  496:                             return;
  497:                     }
  498:                     callout_schedule(&sc->sc_watchdog_to, hz);
  499:             }
  500:         }
  501: 
  502:    There is no `ieee80211_watchdog()` function any more.
  503: 
  504: 
  505:  * ioctl functions
  506:    Typicaly there are no ioctl functions in a wlan driver. The ioctls
  507:    are handled in generic code and the ic_parent function is called to
  508:    update the device state accordingly.  XXXX - is this true? implemented?
  509:    Or one of ic_scan_start/ic_scan_end/ic_set_channel
  510: 
  511:  * device _parent function
  512:    You need to add a new function to update the device state when one of
  513:    the active VAPs requires something special, like switch to promiscous
  514:    mode. The function looks like:
  515: 
  516:         static void
  517:         rtwn_parent(struct ieee80211com *ic)
  518:         {
  519:             struct rtwn_softc *sc = ic->ic_softc;
  520:             bool startall = false;
  521: 
  522:             mutex_enter(&sc->sc_tx_mtx);
  523:             if (ic->ic_nrunning > 0 && !sc->sc_running)
  524:                 startall = true;
  525:             else if (ic->ic_nrunning == 0 && sc->sc_running)
  526:                 rtwn_stop(sc);
  527:             mutex_exit(&sc->sc_tx_mtx);
  528: 
  529:             if (startall)
  530:                 ieee80211_start_all(ic);
  531:         }

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