Annotation of wikisrc/Converting_drivers_to_the_new_wifi_stack.mdwn, revision 1.12

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

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