OpenBSDのif_rsu.cを読む


OpenBSDのif_rsu.cを読んでみました。

if_rsu.cはRealtekのWi-Fiチップ用のデバイスドライバのソースコードです。

実装されている機能がとても少ないので読みやすいです。

# 対象
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/dev/usb/if_rsu.c?rev=1.34&content-type=text/x-cvsweb-markup

# if_rsu.cの対応機能
* 802.11n 802.11g 802.11b
* STA
* 暗号化なし、WEP、WPA2 (暗号化はドライバ側で行う)
* 802.11e のQoS
* USB

# 対応チップ
* RTL8171
* RTL8172
* RTL8173
* RTL8174
* RTL8712
* RTL8713
* RTL8188SU
* RTL8191SU
* RTL8192SU


# USBのエンドポイント
* ディスクリプタのid->bNumEndpointsの数字によって異なり、変換テーブルを使ってqidからエンドポイントの数字を導出する。
* 4
 * rsu_qid2idx_4ep[]  = { 3, 3, 3, 1, 1, 2, 2, 0, 3, 0 }; 
* 6
 * rsu_qid2idx_6ep[]  = { 3, 3, 3, 1, 4, 2, 5, 0, 3, 0 }; 
* 11
 * rsu_qid2idx_11ep[] = { 7, 9, 8, 1, 4, 2, 5, 0, 3, 6 };

* qidの定数

```
/* Queue ids (used by soft only). */
#define RSU_QID_BCN     0
#define RSU_QID_MGT     1
#define RSU_QID_BMC     2
#define RSU_QID_VO      3
#define RSU_QID_VI      4
#define RSU_QID_BE      5
#define RSU_QID_BK      6
#define RSU_QID_RXOFF   7
#define RSU_QID_H2C     8
#define RSU_QID_C2H     9
```
* 使用するエンドポイント
 * 1 : データ送信 ( RSU_QID_BE )
 * 3 : コマンド送信 ( RSU_QID_H2C )



# コマンド
## コマンド送信

* Tx descriptorを用意
* command heeadrを用意
* command payloadを用意
* RSU_QID_H2Cに対応するendpoint 3に usbd_setup_xfer() usbd_transfer()で送信。


```c
int
rsu_fw_cmd(struct rsu_softc *sc, uint8_t code, void *buf, int len)
{
        struct rsu_tx_data *data;
        struct r92s_tx_desc *txd;
        struct r92s_fw_cmd_hdr *cmd;
        struct usbd_pipe *pipe;
        int cmdsz, xferlen;

        data = sc->fwcmd_data;

        /* Round-up command length to a multiple of 8 bytes. */
        cmdsz = (len + 7) & ~7;

        xferlen = sizeof(*txd) + sizeof(*cmd) + cmdsz;
        KASSERT(xferlen <= RSU_TXBUFSZ);
        memset(data->buf, 0, xferlen);

        /* Setup Tx descriptor. */
        txd = (struct r92s_tx_desc *)data->buf;
        txd->txdw0 = htole32(
            SM(R92S_TXDW0_OFFSET, sizeof(*txd)) |
            SM(R92S_TXDW0_PKTLEN, sizeof(*cmd) + cmdsz) |
            R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG);
        txd->txdw1 = htole32(SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_H2C));

        /* Setup command header. */
        cmd = (struct r92s_fw_cmd_hdr *)&txd[1];
        cmd->len = htole16(cmdsz);
        cmd->code = code;
        cmd->seq = sc->cmd_seq;
        sc->cmd_seq = (sc->cmd_seq + 1) & 0x7f;

        /* Copy command payload. */
        memcpy(&cmd[1], buf, len);

        DPRINTFN(2, ("Tx cmd code=%d len=%d\n", code, cmdsz));
        pipe = sc->pipe[sc->qid2idx[RSU_QID_H2C]];
        usbd_setup_xfer(data->xfer, pipe, NULL, data->buf, xferlen,
            USBD_SHORT_XFER_OK | USBD_NO_COPY | USBD_SYNCHRONOUS,
            RSU_CMD_TIMEOUT, NULL);
        return (usbd_transfer(data->xfer));
}
```

## コマンドの種類

* ソースコード内で使っているコマンド

```
#define R92S_CMD_JOIN_BSS               14
#define R92S_CMD_DISCONNECT             15
#define R92S_CMD_SET_OPMODE             17
#define R92S_CMD_SITE_SURVEY            18
#define R92S_CMD_SET_AUTH               19
#define R92S_CMD_SET_KEY                20
#define R92S_CMD_SET_PWR_MODE           36
#define R92S_CMD_SET_MAC_ADDRESS        58

```

# APに接続

* R92S_CMD_SET_OPMODEコマンドを送信
* R92S_CMD_SET_AUTHコマンドを送信 (WPA or Open)
* 引数niから接続先APの情報をbssに用意
* 付加するIEの情報をfrmに用意。
* R92S_CMD_JOIN_BSSコマンド

```c
int
rsu_join_bss(struct rsu_softc *sc, struct ieee80211_node *ni)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ndis_wlan_bssid_ex *bss;
        struct ndis_802_11_fixed_ies *fixed;
        struct r92s_fw_cmd_auth auth;
        uint8_t buf[sizeof(*bss) + 128], *frm;
        uint8_t opmode;
        int error;

        /* Let the FW decide the opmode based on the capinfo field. */
        opmode = NDIS802_11AUTOUNKNOWN;
        DPRINTF(("setting operating mode to %d\n", opmode));
        error = rsu_fw_cmd(sc, R92S_CMD_SET_OPMODE, &opmode, sizeof(opmode));
        if (error != 0)
                return (error);

        memset(&auth, 0, sizeof(auth));
        if (ic->ic_flags & IEEE80211_F_RSNON) {
                auth.mode = R92S_AUTHMODE_WPA;
                auth.dot1x = ieee80211_is_8021x_akm(ni->ni_rsnakms);
        } else
                auth.mode = R92S_AUTHMODE_OPEN;
        DPRINTF(("setting auth mode to %d\n", auth.mode));
        error = rsu_fw_cmd(sc, R92S_CMD_SET_AUTH, &auth, sizeof(auth));
        if (error != 0)
                return (error);

        memset(buf, 0, sizeof(buf));
        bss = (struct ndis_wlan_bssid_ex *)buf;
        IEEE80211_ADDR_COPY(bss->macaddr, ni->ni_bssid);
        bss->ssid.ssidlen = htole32(ni->ni_esslen);
        memcpy(bss->ssid.ssid, ni->ni_essid, ni->ni_esslen);
        if (ic->ic_flags & (IEEE80211_F_WEPON | IEEE80211_F_RSNON))
                bss->privacy = htole32(1);
        bss->rssi = htole32(ni->ni_rssi);
        if (ic->ic_curmode == IEEE80211_MODE_11B)
                bss->networktype = htole32(NDIS802_11DS);
        else
                bss->networktype = htole32(NDIS802_11OFDM24);
        bss->config.len = htole32(sizeof(bss->config));
        bss->config.bintval = htole32(ni->ni_intval);
        bss->config.dsconfig = htole32(ieee80211_chan2ieee(ic, ni->ni_chan));
        bss->inframode = htole32(NDIS802_11INFRASTRUCTURE);
        memcpy(bss->supprates, ni->ni_rates.rs_rates,
            ni->ni_rates.rs_nrates);
        /* Write the fixed fields of the beacon frame. */
        fixed = (struct ndis_802_11_fixed_ies *)&bss[1];
        memcpy(&fixed->tstamp, ni->ni_tstamp, 8);
        fixed->bintval = htole16(ni->ni_intval);
        fixed->capabilities = htole16(ni->ni_capinfo);
        /* Write IEs to be included in the association request. */
        frm = (uint8_t *)&fixed[1];
        if ((ic->ic_flags & IEEE80211_F_RSNON) &&
            (ni->ni_rsnprotos & IEEE80211_PROTO_RSN))
                frm = ieee80211_add_rsn(frm, ic, ni);
        if (ni->ni_flags & IEEE80211_NODE_QOS)
                frm = ieee80211_add_qos_capability(frm, ic);
        if (ni->ni_flags & IEEE80211_NODE_QOS)
                frm = ieee80211_add_qos_capability(frm, ic);
        if (ni->ni_flags & IEEE80211_NODE_HT)
                frm = ieee80211_add_htcaps(frm, ic);
        if ((ic->ic_flags & IEEE80211_F_RSNON) &&
            (ni->ni_rsnprotos & IEEE80211_PROTO_WPA))
                frm = ieee80211_add_wpa(frm, ic, ni);
        bss->ieslen = htole32(frm - (uint8_t *)fixed);
        bss->len = htole32(((frm - buf) + 3) & ~3);
        DPRINTF(("sending join bss command to %s chan %d\n",
            ether_sprintf(bss->macaddr), letoh32(bss->config.dsconfig)));
        return (rsu_fw_cmd(sc, R92S_CMD_JOIN_BSS, buf, sizeof(buf)));
}
```

# APから切断

* R92S_CMD_DISCONNECTコマンドを送信

```c
int
rsu_disconnect(struct rsu_softc *sc)
{
        uint32_t zero = 0;      /* :-) */

        /* Disassociate from our current BSS. */
        DPRINTF(("sending disconnect command\n"));
        return (rsu_fw_cmd(sc, R92S_CMD_DISCONNECT, &zero, sizeof(zero)));
}
```

# データ送信

* WPA有効なら、ieee80211_get_txkey()の鍵を使ってieee80211_encrypt()で暗号化する。
* endpointを選択。QoSを使わないなら RSU_QID_BEに対応するEndpoint 0を選択。
* Tx descriptorを用意
* 送信データを用意
* usbd_setup_xfer() usbd_transfer()で送信。


```c
int
rsu_tx(struct rsu_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
{
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_frame *wh;
        struct ieee80211_key *k = NULL;
        struct rsu_tx_data *data;
        struct r92s_tx_desc *txd;
        struct usbd_pipe *pipe;
        uint16_t qos;
        uint8_t type, qid, tid = 0;
        int hasqos, xferlen, error;

        wh = mtod(m, struct ieee80211_frame *);
        type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;

        if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
                k = ieee80211_get_txkey(ic, wh, ni);
                if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
                        return (ENOBUFS);
                wh = mtod(m, struct ieee80211_frame *);
        }
        if ((hasqos = ieee80211_has_qos(wh))) {
                qos = ieee80211_get_qos(wh);
                tid = qos & IEEE80211_QOS_TID;
                qid = rsu_ac2qid[ieee80211_up_to_ac(ic, tid)];
        } else
                qid = RSU_QID_BE;

        /* Get the USB pipe to use for this queue id. */
        pipe = sc->pipe[sc->qid2idx[qid]];

        /* Grab a Tx buffer from our free list. */
        data = TAILQ_FIRST(&sc->tx_free_list);
        TAILQ_REMOVE(&sc->tx_free_list, data, next);

        /* Fill Tx descriptor. */
        txd = (struct r92s_tx_desc *)data->buf;
        memset(txd, 0, sizeof(*txd));

        txd->txdw0 |= htole32(
            SM(R92S_TXDW0_PKTLEN, m->m_pkthdr.len) |
            SM(R92S_TXDW0_OFFSET, sizeof(*txd)) |
            R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG);

        txd->txdw1 |= htole32(
            SM(R92S_TXDW1_MACID, R92S_MACID_BSS) |
            SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_BE));
        if (!hasqos)
                txd->txdw1 |= htole32(R92S_TXDW1_NONQOS);
#ifdef notyet
        if (k != NULL) {
                switch (k->k_cipher) {
                case IEEE80211_CIPHER_WEP40:
                case IEEE80211_CIPHER_WEP104:
                        cipher = R92S_TXDW1_CIPHER_WEP;
                        break;
                case IEEE80211_CIPHER_TKIP:
                        cipher = R92S_TXDW1_CIPHER_TKIP;
                        break;
                case IEEE80211_CIPHER_CCMP:
                        cipher = R92S_TXDW1_CIPHER_AES;
                       break;
                default:
                        cipher = R92S_TXDW1_CIPHER_NONE;
                }
                txd->txdw1 |= htole32(
                    SM(R92S_TXDW1_CIPHER, cipher) |
                    SM(R92S_TXDW1_KEYIDX, k->k_id));
        }
#endif
        txd->txdw2 |= htole32(R92S_TXDW2_BK);
        if (IEEE80211_IS_MULTICAST(wh->i_addr1))
                txd->txdw2 |= htole32(R92S_TXDW2_BMCAST);
        /*
         * Firmware will use and increment the sequence number for the
         * specified TID.
         */
        txd->txdw3 |= htole32(SM(R92S_TXDW3_SEQ, tid));

#if NBPFILTER > 0
        if (__predict_false(sc->sc_drvbpf != NULL)) {
                struct rsu_tx_radiotap_header *tap = &sc->sc_txtap;
                struct mbuf mb;

                tap->wt_flags = 0;
                tap->wt_chan_freq = htole16(ic->ic_bss->ni_chan->ic_freq);
                tap->wt_chan_flags = htole16(ic->ic_bss->ni_chan->ic_flags);

                mb.m_data = (caddr_t)tap;
                mb.m_len = sc->sc_txtap_len;
                mb.m_next = m;
                mb.m_nextpkt = NULL;
                mb.m_type = 0;
                mb.m_flags = 0;
                bpf_mtap(sc->sc_drvbpf, &mb, BPF_DIRECTION_OUT);
        }
#endif

        xferlen = sizeof(*txd) + m->m_pkthdr.len;
        m_copydata(m, 0, m->m_pkthdr.len, (caddr_t)&txd[1]);
        m_freem(m);

        data->pipe = pipe;
        usbd_setup_xfer(data->xfer, pipe, data, data->buf, xferlen,
            USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RSU_TX_TIMEOUT,
            rsu_txeof);
        error = usbd_transfer(data->xfer);
        if (__predict_false(error != USBD_IN_PROGRESS && error != 0)) {
                /* Put this Tx buffer back to our free list. */
                TAILQ_INSERT_TAIL(&sc->tx_free_list, data, next);
                return (error);
        }
        ieee80211_release_node(ic, ni);
        return (0);
}
```