# Intro This guide is meant to help you convert an old style NetBSD driver for a wifi device to the newer wifi stack based on FreeBSD's current version. This is work in progress, as we are trying to create helper functions and libraries where usefull, and also the target API is a slightly moving target. # Overview The main difference between old style and new style drivers is the VAP concept. VAP means Virtual Access Point, and it is used to split the configuration properties of a wlan (as visible on air) from the radio hardware. In many cases multiple wlans can be created using a single radio hardware, which is naturally modelled as multiple VAPs being created (by pure software) using the same physical radio device. At the user level the radio hardware is hidden quite well - it only shows up in `dmesg` or `sysctl net.wlan.devices`. There is no network interface for the raw hardware, so ifconfig will not show them. To create a VAP for a radio device (and that way a network interface) use a command like: ifconfig wlan0 create wlandev run0 This split between common radio hardware and per-VAP network properties reflects on the driver and causes most of the changes needed. After creation of the `wlan0` VAP, it will also show up under sysctl net.wlan.wlan0 and in ifconfig output. More complete instructions for testing are here: [[Testing new wifi]]. There also is an older paper describing the initial design in FreeBSD: [FreeBSD wireless BSDCan 2005](https://www.bsdcan.org/2005/papers/FreeBSDWirelessNetwokringSupport.pdf) # Help available! If your device is a usb(4) device, you can use a new helper facility "usbwifi" similar to usbnet(9), which already handles a lot of new wifi stack specific details. In the following text the items not applicable for drivers using usbwifi are marked like this: [!]*usbwifi*{X} or `// !usbwifi` if appearing in code sections. # Step by Step at the driver level * The data is split into the generic `struct ieee80211com` describing the radio hardware (often used as `ic` pointer), which is part of the drivers softc. The individual VAPs are in a TAILQ in the ic structure, and often are a driver defined structure 'derived' from `struct ieee80211vap` (with some additional driver specific data). The VAP structure has the network interface `struct ifnet` as `iv_ifp`. The `if_softc` pointer in the ifnet structure points to the VAP, the VAP structure has a pointer to the ic `iv_ic`, the ic structure has a `ic_softc` pointer to the softc. * MAC addresses are now stored in ic_macaddr instead of ic_myaddr (in struct ieee80211com). It is typically read from the device and cloned into each VAP. Typical code: IEEE80211_ADDR_COPY(ic->ic_macaddr, rom->macaddr); * In the attach function, no `struct ifnet ifp` will be needed, so get rid of code like: struct ifnet *ifp = GET_IFP(sc); and all initialization of ifp members. Move them to the vap_create function (see below) if needed instead, but typically none should be needed (as the generic code initializes the VAP's struct ifnet). Set a direct backlink to the softc, like: ic->ic_softc = sc; Make sure the ic_caps get initialized correctly to the capabilities of the radio, especially you want to add IEEE80211_C_STA for standard AP (station) mode. There is no state machine in the common radio part, it all moves into per-VAP state, so also remove initialization for it, like: ic->ic_state = IEEE80211_S_INIT; You may want a replacement for `IFF_OACATIVE` logic for the main device, as there is no interface so no flags to (ab)use for that. * The way channels are set up has changed. If your driver had a `driver_init_channels()` function, you can use it mostly verbatim (but see caveats below for `maxchans` and `nchans`). Rename it to `driver_get_radiocaps` and make it look like: static void urtwn_get_radiocaps(struct ieee80211com *ic, int maxchans, int *nchans, struct ieee80211_channel chans[]) { uint8_t bands[IEEE80211_MODE_BYTES]; memset(bands, 0, sizeof(bands)); setbit(bands, IEEE80211_MODE_11B); setbit(bands, IEEE80211_MODE_11G); setbit(bands, IEEE80211_MODE_11NG); // XXX see description below if your chip can use other ranges (like 5ghz) ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0); } and also assign this function to `ic->ic_getradiocaps` in the attach function. An initial setup must happen before calling `ieee80211_ifattach(ic)`. You can just call the `driver_get_radiocaps()` function during attach like: urtwn_get_radiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); ieee80211_ifattach(ic); [..] ic->ic_getradiocaps = urtwn_get_radiocaps; If the hardware can use different frequency bands you may have to add them band by band instead of using `ieee80211_add_channels_default_2ghz`, also if the firmware knows about regulatory domains it may be required to select subsets of channels. An example of a multi-frequency driver doing this is iwm(4). It uses a helper function to add single channels individually via `ieee80211_add_channel`. To add the channels for a band, it calls `iwm_add_channel_band(ic, chans, maxchans, nchans, start, num, bands)` where `chans`, `maxchans` and `nchans` are the arguments passed to this callback, `chans` collecting the resulting channels, `nchans` pointing to the number of channels already collected, `bands` is the bitset of bands to add (multiple `IEEE80211_MODE_*` bits). The offset `start` is the index of the first channel to add to the output (first call will always be with 0 as `start`), and `num` the number of channels you expect `iwm_add_channel_band` to add. The `iwm_add_channel_band` helper function iterates over the channels, gets flags for the channels from firmware and calls `ieee80211_add_channel`. Overall the most important part in conversion of old drivers is to make sure that the out parameter `*nchans` is properly set to the number of channel data actually filled, and to respect the `maxchans` limit. This callback is called from two places, once from the drivers attach function, where `maxchan` is big enough (always `IEEE80211_CHAN_MAX`), and from ioctl `IEEE80211_IOC_DEVCAPS` where `maxchan` depends on the userland arguments and e.g. wpa_supplicant only asks for minmal data, as it is not interested in the actual channels available (`maxchan` is 1). The ioctl handling code will KASSERT proper behaviour of the callback. * If your driver used to override state machine functions (typical `newstate` to controll LEDs when associating or similar), remove all traces for this from your softc and override a custom VAP type instead (i.e. derive a struct from `struct ieee80211vap` and use your own struct in your `vap_create` and `vap_delete` methods). Remove the (likely to exist) old `sc_newstate` function from your drivers softc (previously used to store the frameworks newstate function, which now is per-VAP and moved into your derived per-VAP structure. * Many functions will get a `struct ieee80211vap *vap` passed instead of a softc. A typical change to adapt for that looks like: - struct ieee80211com *ic = &sc->sc_ic; - struct ieee80211_node *ni = ic->ic_bss; + struct ieee80211com *ic = vap->iv_ic; + struct rtwn_softc *sc = ic->ic_softc; + struct ieee80211_node *ni = vap->iv_bss; * The hardware driver will need a global new outgoing packet queue (as the per-interface queues are attached to VAPs, not the single common transmit hardware). Add the new ifqueue like to your softc like this: struct ifqueue sc_sendq; If your hardware supports multiple transmit priorities you may consider doing more than one ifqueue. Usbwifi does this for devices with multiple transmit pipes (creating a ifqueue per hardware priority and in this case USB send endpoint). While there, check that there is no `struct ethercom sc_ec` in your softc - it is not needed. This queue needs initialization in the attach function, like: sc->sc_sendq.ifq_maxlen = ifqmaxlen; IFQ_LOCK_INIT(&sc->sc_sendq); This device private send queue (writer: any of the current VAPs of this interface, consumer: the devices transmit interrupt handler) abuses the `mbuf rcvif` pointer to store the node pointer `struct ieee80211_node *` of the target node for this packet. When dequeuing a packet before handing it over to the output function, the node is extracted and the `rcvif` pointer set to `NULL`. Yes - this is a hack. Also the attach function needs to set the name of the interface in the common radio device structure: ic->ic_name = device_xname(self); Many drivers query the hardware/firmware for the number of available input/output queues and store these in e.g. `sc->ntxchains` and `sc->nrxchains` or similar variables. This information needs to be propagated to the common radio structure: ic->ic_txstream = sc->ntxchains; ic->ic_rxstream = sc->nrxchains; Same for interface flags: ic->ic_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; After calling `ieee80211_ifattach(ic);` some methods in `ic` need to be overwritten. The functions needed are explained below, here is a typical setup: /* override default methods */ ic->ic_newassoc = urtwn_newassoc; ic->ic_wme.wme_update = urtwn_wme_update; ic->ic_vap_create = urtwn_vap_create; ic->ic_vap_delete = urtwn_vap_delete; ic->ic_parent = urtwn_parent; ic->ic_scan_start = urtwn_scan_start; ic->ic_scan_end = urtwn_scan_end; ic->ic_set_channel = urtwn_set_channel; ic->ic_transmit = urtwn_transmit; // !usbwifi ic->ic_raw_xmit = urtwn_raw_xmit; // !usbwifi * detach does not deal with any interfaces any more, remove all traces of `struct ifnet *ifp`. * Add a new function for VAP creation and move all interface specific setup there. It looks like: static struct ieee80211vap * urtwn_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit, enum ieee80211_opmode opmode, int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t macaddr[IEEE80211_ADDR_LEN]) { struct urtwn_softc *sc = ic->ic_softc; struct ifnet *ifp; struct ieee80211vap *vap; ... return vap; } It allocates a new VAP with `kmem_zalloc` initializes it by calling `ieee80211_vap_setup` for it and then overriding whatever the driver needs, passes it to `ieee80211_vap_attach` and attaches BPF to the interface. If your interrupt handler is called in hard interrupt context (that probably means: for all devices that are not on USB) you need to initialize the per CPU interface queue: /* Use common softint-based if_input */ ifp->if_percpuq = if_percpuq_create(ifp); If your interrupt handler always is called in softint context, do not do this. If `if_percpuq` is `NULL` the wifi framework will directly handle incoming packets, otherwise it will take a round trip via a per cpu softint handler. * Add a new function for VAP destruction and move interface specific parts that you deleted in the detach function here. A very minimalistic example looks like: static void urtwn_vap_delete(struct ieee80211vap *vap) { struct ifnet *ifp = vap->iv_ifp; struct urtwn_softc *sc __unused = vap->iv_ic->ic_softc; bpf_detach(ifp); ieee80211_vap_detach(vap); kmem_free(vap, sizeof(struct ieee80211vap)); } * Rate Adaption happens per VAP, so the ra_init call changes like this: -static int urtwn_ra_init(struct urtwn_softc *); +static int urtwn_ra_init(struct ieee80211vap ); See above for recipes how to access the needed structs in there. * State is per VAP, so newstate changes the first parameter: -static int urtwn_newstate(struct ieee80211com *, enum ieee80211_state, int); +static int urtwn_newstate(struct ieee80211vap *, enum ieee80211_state, int); If there was a newstate_cb function previously, it is probably not needed any more. * Reset is per interface, so per VAP: -static int urtwn_reset(struct ifnet *); +static int urtwn_reset(struct ieee80211vap *, u_long cmd); A driver is not required to provide this method, if not filled in a default method will be used that always causes a full reset of the device. * Selecting TX rate for different frame types may need rework. When creating TX descriptors the driver checks the type of packet in the mbuf at hand, which has a `struct ieee80211_node *` associated, which has a member with transmit params: Example for management frames: const struct ieee80211_txparam *tp = ni->ni_txparms; rate = tp->mgmtrate; Similar values are available for multicast (`mcastrate`) and standard unicast frames (`ucastrate`). Rates are either legacy/basic in 0.5 Mb/s or MCS/HT (with the `IEEE80211_RATE_MCS` bit set). No valid rate is encoded as `IEEE80211_FIXED_RATE_NONE`. The macros `IEEE80211_IS_HT_RATE`, `IEEE80211_HT_RC_2_MCS` and `IEEE80211_HT_RC_2_STREAMS` can be useful to categorize them or extract physical details. Many drivers select rates based on the current channel - this is not needed here, the `ni_txparms` pointer is updated whenever the channel switches. * The set channel function is generic now, so gets passed a `struct ieee80211com *ic` instead of a softc, and sets the radio hardware to the current channel in that structure, `ic->ic_curchan`. * The start function does not need a `struct ifnet *ifp` any more, but can use a softc or `struct ieee80211com *ic` instead. * Typically, generic media change and status functions will be used: `ieee80211_media_change` and `ieee80211_media_status`. Old drivers often implement their own function - remove that (unless there is something actually chip specific in them). If there is, override media_change and/or media_status, and make sure to call the generic variant for all the real work. Also make sure to pass your override function to `ieee80211_vap_attach.` run(4) is an example of this. The overridden media_\{change,status\} functions are the only one in a wlan driver that should get a `struct ifnet *` argument passed. Checking for this is a good measurement to know you are done with conversion (besides the kernel build failing, of course). * [!]*usbwifi*{X} Your driver needs a new transmit function, used to enqueue a mbuf to the hardware send queue (and start transmission if needed). A very simple example: static int urtwn_transmit(struct ieee80211com *ic, struct mbuf *m) { struct urtwn_softc *sc = ic->ic_softc; int s; s = splnet(); IF_ENQUEUE(&sc->sc_sendq, m); splx(s); if (!sc->sc_oactive) urtwn_start(sc); return 0; } A pointer to this function is assigned to the `ic->ic_transmit` member in the attach function. * [!]*usbwifi*{X} Your driver needs a raw_xmit function which is close to what the old driver called *_tx(), it looks like: static int urtwn_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *bpfp) and the "missing" arguments are derived like: struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct urtwn_softc *sc = ic->ic_softc; The check for encrypting packets often was testing the `ieee80211_frame` `wh->i_fc[1] & IEEE80211_FC1_WE` before encapsulating the mbuf via `ieee80211_crypto_encap()`. The check now is for `wh->i_fc[1] & IEEE80211_FC1_PROTECTED`, since WEP is a thing of the past and there are other protocols. Also the `ieee80211_crypto_encap` function lost its first argument (the radio common ic pointer). The transmit function queues the mbuf for transmit and makes sure when transmission is done by hardware to call `ieee80211_tx_complete` with both `mbuf` and `ni`. This function also needs to be called in all error cases, with an error value as status (last argument). The actual value does not matter, the stack just checks for 0 (success) and non zero (failure). This call will free the mbuf and the node, and in some cases also cause further state changes (like waking up VAPs, scheduling a new scan later, or even causing immediate state machine transitions and retrying in error cases). * [!]*usbwifi*{X} If the `driver_activate()` function only passes deactivation requests on to if_deactivate() you can replace it by a shim that gets your softc and calls `ieee80211_activate()`, like: static int urtwn_activate(device_t self, enum devact act) { struct urtwn_softc *sc = device_private(self); return ieee80211_activate(&sc->sc_ic, act); } * When the hardware received a full frame, move it into a mbuf (with proper bound checks) and simply call // fill mbuf m with the received data ieee80211_rx_enqueue(ic, m, rssi); Old code often has tests for too short or too long packets and increments error counters in the struct ifnet of the interface that used to belong to the wlan driver. Since there is no such single interface anymore, errors are accounted later (at the VAP level) if a VAP/node is identified for the packet. Otherwise just increment the global `ic->ic_ierrors` counter for errors with incoming packets that can not be associated with a concrete network (or similar `ic->ic_oerrors` for output errors). * Various BPF changes: Many drivers avoid complex setup options for BPF listeners if there are none. With virtual APs this became a bit more complex, but the framework tracks it for the driver. To test if any VAP on the radio hardware is in MONITOR mode and has an active BPF peer, simply test the `ic->ic_montaps` counter - it will be zero if there are none. For most drivers it should not be necessary to do anything special for BPF, the taps are in the framework. Some drivers provide special low level radiotap data, often via a driver specific `sc->sc_drvbpf`. This is replaced by a generic radiotap bpf listener created by the framework code and stored as `vap-> iv_rawbpf`. A driver signals support for radiotap by making `ic->ic_th` point to a radiotap transmit header (usually in the softc, initialized before attach and updated on transmit) and similar `ic->ic_rh` point to a receive tap header (updated on every received packet). Make sure to initialize the tap headers `it_len` fields to the proper size (in little endian byte order) and `it_present` flags (also in little endian byte order) to the relevant `IEEE80211_RADIOTAP_*` bits. To avoid updating the receive/transmit tap headers unessecarily the driver can check the `ic->ic_flags_ext` for the `IEEE80211_FEXT_BPF` bit being set - if not, there is no VAP with bpf listener on radiotap headers. The driver needs to update `ic_rh` (usually only the storage for a single header it points to) on every received packet. This is used as a side channel for the radiotap data when passing packet up the stack via `ieee80211_rx_enqueue`. Or in the NetBSD case for USB drivers when calling `usbwifi_enqueue`. The stack automatically dispatches this to the radiotap bpf tap associated with any relevant VAP and also makes sure it is seen by VAPs in MONITOR mode. * Watchdog functions move to callout: Since there is no 1:1 ifnet and its global watchdog mechanism, all driver watchdog functions are converted to a callout. A typical implementation looks like: static void urtwn_watchdog(void *arg) { struct urtwn_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (sc->tx_timer > 0) { if (--sc->tx_timer == 0) { aprint_error_dev(sc->sc_dev, "device timeout\n"); ieee80211_stat_add(&ic->ic_oerrors, 1); ieee80211_restart_all(ic); return; } callout_schedule(&sc->sc_watchdog_to, hz); } } There is no `ieee80211_watchdog()` function any more. * ioctl functions Typicaly there are no ioctl functions in a wlan driver. The ioctls are handled in generic code and the ic_parent function is called to update the device state accordingly. XXXX - is this true? implemented? Or one of ic_scan_start/ic_scan_end/ic_set_channel * device_parent function You need to add a new function to power up the radio when the first VAP is activated or shut it down after the last VAP goes down (or is destroyed). The function looks like: static void rtwn_parent(struct ieee80211com *ic) { struct rtwn_softc *sc = ic->ic_softc; bool startall = false; mutex_enter(&sc->sc_tx_mtx); if (ic->ic_nrunning > 0 && !sc->sc_running) startall = true; else if (ic->ic_nrunning == 0 && sc->sc_running) rtwn_stop(sc); mutex_exit(&sc->sc_tx_mtx); if (startall) ieee80211_start_all(ic); } * Caveats: When selecting a TX queue the driver is probably extracting a Quality Of Service value from the IEEE802.11 header. There are new variants out there now, so carefully check and update code that does it like this: if ((hasqos = ieee80211_has_qos(wh))) { qos = ((struct ieee80211_qosframe *)wh)->i_qos[0]; } else { qos = 0; } to something like this: if ((hasqos = IEEE80211_QOS_HAS_SEQ(wh))) { uint8_t *frm; frm = ieee80211_getqos(wh); qos = le16toh(*(const uint16_t *)frm); } else { qos = 0; }