OpenBSDのif_urtwn.cを読む

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

if_urtwn.cはRealtekのWi-Fiチップのデバイスドライバです。

前回 [「OpenBSDのif_rsu.cを読む」](http://qiita.com/eggman/items/efa941c3ab8a13975a7b)でif_rsu.cをちょっと読んでみたので、if_urwtn.cも読んでみました。

if_urtwn.c はsoftmacなドライバなので、AP接続などのMAC処理はドライバより上側で処理する構成でした。


# ソースコード
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/dev/usb/if_urtwn.c?rev=1.65&content-type=text/x-cvsweb-markup
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/dev/ic/rtwn.c?rev=1.9&content-type=text/x-cvsweb-markup
* rtwn.cはホストインタフェースに依存しない部分のコードです。PCIE, CardBus、USBで共通なコードです。
* rsu.cに比べて、ソースコードの行数が倍くらいになっています。　でも、Linuxドライバよりは全然コードが少ないので、楽に読めます。

# 対応チップ
* RTL8188CTV
* RTL8188CU
* RTL8188CUS
* RTL8188ETV
* RTL8188EU
* RTL8192CU

# エンドポイント
## 送信エンドポイント
* データ送信用にエンドポイントを使う。
* 11eのACに応じてendpointを選択する。 
* 送信エンドポイントの総数ntxはチップによって異なる。

```c
	/* Map 802.11 access categories to USB pipes. */
	sc->ac2idx[EDCA_AC_BK] =
	sc->ac2idx[EDCA_AC_BE] = (ntx == 3) ? 2 : ((ntx == 2) ? 1 : 0);
	sc->ac2idx[EDCA_AC_VI] = (ntx == 3) ? 1 : 0;
	sc->ac2idx[EDCA_AC_VO] = 0;	/* Always use highest prio. */
```

## 受信エンドポイント
* 不明

# コマンド
* コマンドはコントロール転送を使う。
## コマンド送信
* コマンドを書き込むアドレスはsc->fwcurで管理
* コマンド送信可能かどうかを確認。
* コマンドを用意
* rtwn_write_2()とrtwn_write_4()で計６バイトを書き込み
* sc->fwcurをアップデート

```c
int
rtwn_fw_cmd(struct rtwn_softc *sc, uint8_t id, const void *buf, int len)
{
	struct r92c_fw_cmd cmd;
	int ntries;

	/* Wait for current FW box to be empty. */
	for (ntries = 0; ntries < 100; ntries++) {
		if (!(rtwn_read_1(sc, R92C_HMETFR) & (1 << sc->fwcur)))
			break;
		DELAY(1);
	}
	if (ntries == 100) {
		printf("%s: could not send firmware command %d\n",
		    sc->sc_pdev->dv_xname, id);
		return (ETIMEDOUT);
	}
	memset(&cmd, 0, sizeof(cmd));
	cmd.id = id;
	if (len > 3)
		cmd.id |= R92C_CMD_FLAG_EXT;
	KASSERT(len <= sizeof(cmd.msg));
	memcpy(cmd.msg, buf, len);

	/* Write the first word last since that will trigger the FW. */
	rtwn_write_2(sc, R92C_HMEBOX_EXT(sc->fwcur), *((uint8_t *)&cmd + 4));
	rtwn_write_4(sc, R92C_HMEBOX(sc->fwcur), *((uint8_t *)&cmd + 0));

	sc->fwcur = (sc->fwcur + 1) % R92C_H2C_NBOX;

	if (sc->chip & RTWN_CHIP_PCI) {
		/* Give firmware some time for processing. */
		DELAY(2000);
	}

	return (0);
}
```


# APに接続
* このドライバでは、APに接続する処理は実装されていない。
* ということは、APに接続する処理はカーネルのieee80211レイヤでソフト的に実装されているようです。
* ieee80211_proto.c にAuthフレーム、Association Reqフレーム、Association Respフレームの処理があるので、そちらで行っているようです。

# データ送信
* WPAなら ieee80211_encrypt()で暗号化する
* qidからendpointを選択
* TXバッファから取得
* TXデスクリプタを用意
* マルチキャストなら、マルチキャスト送信用の設定をする。(APのみ)
* TXデスクリプタにシーケンス番号を付加
* 送信データをバッファにコピー
* usbd_setup_xfer（）とusbd_transfer()でUSBに転送する。

```c
int
urtwn_tx(void *cookie, struct mbuf *m, struct ieee80211_node *ni)
{
	struct urtwn_softc *sc = cookie;
	struct ieee80211com *ic = &sc->sc_sc.sc_ic;
	struct ieee80211_frame *wh;
	struct ieee80211_key *k = NULL;
	struct urtwn_tx_data *data;
	struct r92c_tx_desc_usb *txd;
	struct usbd_pipe *pipe;
	uint16_t qos, sum;
	uint8_t raid, type, tid, qid;
	int i, 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 = ieee80211_up_to_ac(ic, tid);
	} else if (type != IEEE80211_FC0_TYPE_DATA) {
		/* Use AC VO for management frames. */
		qid = EDCA_AC_VO;
	} else
		qid = EDCA_AC_BE;

	/* Get the USB pipe to use for this AC. */
	pipe = sc->tx_pipe[sc->ac2idx[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 r92c_tx_desc_usb *)data->buf;
	memset(txd, 0, sizeof(*txd));

	txd->txdw0 |= htole32(
	    SM(R92C_TXDW0_PKTLEN, m->m_pkthdr.len) |
	    SM(R92C_TXDW0_OFFSET, sizeof(*txd)) |
	    R92C_TXDW0_OWN | R92C_TXDW0_FSG | R92C_TXDW0_LSG);
	if (IEEE80211_IS_MULTICAST(wh->i_addr1))
		txd->txdw0 |= htole32(R92C_TXDW0_BMCAST);

#ifdef notyet
	if (k != NULL) {
		switch (k->k_cipher) {
		case IEEE80211_CIPHER_WEP40:
		case IEEE80211_CIPHER_WEP104:
		case IEEE80211_CIPHER_TKIP:
			cipher = R92C_TXDW1_CIPHER_RC4;
			break;
		case IEEE80211_CIPHER_CCMP:
			cipher = R92C_TXDW1_CIPHER_AES;
			break;
		default:
			cipher = R92C_TXDW1_CIPHER_NONE;
		}
		txd->txdw1 |= htole32(SM(R92C_TXDW1_CIPHER, cipher));
	}
#endif
	if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
	    type == IEEE80211_FC0_TYPE_DATA) {
		if (ic->ic_curmode == IEEE80211_MODE_11B ||
		    (sc->sc_sc.sc_flags & RTWN_FLAG_FORCE_RAID_11B))
			raid = R92C_RAID_11B;
		else
			raid = R92C_RAID_11BG;
		if (sc->sc_sc.chip & RTWN_CHIP_88E) {
			txd->txdw1 |= htole32(
			    SM(R88E_TXDW1_MACID, R92C_MACID_BSS) |
			    SM(R92C_TXDW1_QSEL, R92C_TXDW1_QSEL_BE) |
			    SM(R92C_TXDW1_RAID, raid));
			txd->txdw2 |= htole32(R88E_TXDW2_AGGBK);
		} else {
			txd->txdw1 |= htole32(
			    SM(R92C_TXDW1_MACID, R92C_MACID_BSS) |
			    SM(R92C_TXDW1_QSEL, R92C_TXDW1_QSEL_BE) |
			    SM(R92C_TXDW1_RAID, raid) | R92C_TXDW1_AGGBK);
		}

		if (ic->ic_flags & IEEE80211_F_USEPROT) {
			if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) {
				txd->txdw4 |= htole32(R92C_TXDW4_CTS2SELF |
				    R92C_TXDW4_HWRTSEN);
			} else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) {
				txd->txdw4 |= htole32(R92C_TXDW4_RTSEN |
				    R92C_TXDW4_HWRTSEN);
			}
		}
		/* Send RTS at OFDM24. */
		txd->txdw4 |= htole32(SM(R92C_TXDW4_RTSRATE, 8));
		txd->txdw5 |= htole32(0x0001ff00);
		/* Send data at OFDM54. */
		txd->txdw5 |= htole32(SM(R92C_TXDW5_DATARATE, 11));

	} else {
		txd->txdw1 |= htole32(
		    SM(R92C_TXDW1_MACID, 0) |
		    SM(R92C_TXDW1_QSEL, R92C_TXDW1_QSEL_MGNT) |
		    SM(R92C_TXDW1_RAID, R92C_RAID_11B));

		/* Force CCK1. */
		txd->txdw4 |= htole32(R92C_TXDW4_DRVRATE);
		txd->txdw5 |= htole32(SM(R92C_TXDW5_DATARATE, 0));
	}
	/* Set sequence number (already little endian). */
	txd->txdseq |= *(uint16_t *)wh->i_seq;

	if (!hasqos) {
		/* Use HW sequence numbering for non-QoS frames. */
		txd->txdw4  |= htole32(R92C_TXDW4_HWSEQ);
		txd->txdseq |= htole16(0x8000);		/* WTF? */
	} else
		txd->txdw4 |= htole32(R92C_TXDW4_QOS);

	/* Compute Tx descriptor checksum. */
	sum = 0;
	for (i = 0; i < sizeof(*txd) / 2; i++)
		sum ^= ((uint16_t *)txd)[i];
	txd->txdsum = sum;	/* NB: already little endian. */

#if NBPFILTER > 0
	if (__predict_false(sc->sc_drvbpf != NULL)) {
		struct urtwn_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, URTWN_TX_TIMEOUT,
	    urtwn_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);
}
```