Annotation of wikisrc/Converting_drivers_to_the_new_wifi_stack.mdwn, revision 1.1

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

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