Marvell Wi-Fi driverのmulti port aggregation を読む


Marvell Wi-Fi driverのmulti port aggregation についてLinuxカーネルに含まれているソースコードを調べてみました。

この機能は、11n の frame aggregation を SDIOの書き込み手順を工夫して効率的に行う方法です。

# まとめ
* multi port aggregation は SDIOのCMD53の１回の書き込み時に複数のパケットをまとめて書き込むことで、チップにまとめて複数のパケットを渡し、チップで11nのMPDUフレームアグリゲーションを効率的に行う機能です。
* 複数のパケットを書き込む際に、CMD53の書き込みアドレスに、パケット数などの情報をエンコードして渡して、チップ内のHWアシスト機能を有効に使います。
* アグリゲートは最大8個または16個をまとめて書き込む。
* ドライバでは、なるだけアグリゲーションするパケット数を増やしたい。そうするとレイテンシは犠牲になるがスループットは増える。
* sdio.cのmwifiex_host_to_card_mp_aggr()にアグリゲートのアルゴリズムが記述してある。
 * 送信キューの残りデータの有無で、アルゴリズムを変える。、
 * 送信キューに残りデータが無いなら、データをすぐ送信する。
 * 送信キューに残りデータがあるなら、なるべく、アグリゲートバッファにデータをコピーするだけにする。 バッファが足りなくなった等の事象が発生したら送信を行う。

# 準備
* Linux kernel 4.6.2
* drivers/net/wireless/marvell/mwiflex
* 送信のみを対象とする。

# mpa の構造体
* 送信のmpa構造体です。 mpaはmulti port aggregationの事です。

```c:sdio.h
/* data structure for SDIO MPA TX */
struct mwifiex_sdio_mpa_tx {
        /* multiport tx aggregation buffer pointer */
        u8 *buf;     //バッファ  
        u32 buf_len; //バッファの使用量
        u32 pkt_cnt; //パケット数
        u32 ports;   // mpaのports 使用予定のbitmap
        u16 start_port; //mpaのports 開始位置
        u8 enabled;  // 1なら multiport aggregation 有効、0なら無効
        u32 buf_size; // バッファbufのサイズ　チップによって異なる 87xx は16KB
        u32 pkt_aggr_limit; // アグリゲートする最大パケット数 チップによって異なる 87xxは8個
};
```
* この構造体が sdio_cardのメンバにあり card->mpa_tx のように参照できます。

# mpaのステータス
* mpaのステータスを見るマクロなどを見てみます。

* 送信のmpaを使用中かどうか。pkt_cntが正なら使用中です。

```c
/* SDIO Tx aggregation in progress ? */
#define MP_TX_AGGR_IN_PROGRESS(a) (a->mpa_tx.pkt_cnt > 0)
```

* mpat_tx.bufに長さlenの空きがあるかどうか確認

```c
/* SDIO Tx aggregation buffer room for next packet ? */
#define MP_TX_AGGR_BUF_HAS_ROOM(a, len) ((a->mpa_tx.buf_len+len)    \
                        <= a->mpa_tx.buf_size)
```

* payloadからpky_lenだけ mpat_tx.bufにデータをコピー。 pkt_cnt, start_port, portsをアップデート

```c
/* Copy current packet (SDIO Tx aggregation buffer) to SDIO buffer */
#define MP_TX_AGGR_BUF_PUT(a, payload, pkt_len, port) do {      \
    memmove(&a->mpa_tx.buf[a->mpa_tx.buf_len],          \
            payload, pkt_len);              \
    a->mpa_tx.buf_len += pkt_len;                   \
    if (!a->mpa_tx.pkt_cnt)                     \
        a->mpa_tx.start_port = port;                \
    if (a->mpa_tx.start_port <= port)               \
        a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt));        \
    else                                \
        a->mpa_tx.ports |= (1<<(a->mpa_tx.pkt_cnt+1+        \
                        (a->max_ports - \
                        a->mp_end_port)));  \
    a->mpa_tx.pkt_cnt++;                        \
} while (0)
```

* アグリゲーション最大個数に達したかどうか確認

```c
/* SDIO Tx aggregation limit ? */
#define MP_TX_AGGR_PKT_LIMIT_REACHED(a)                 \
            (a->mpa_tx.pkt_cnt == a->mpa_tx.pkt_aggr_limit)
````

* アグリゲーションのカウンタと使用量をリセット

```c
/* Reset SDIO Tx aggregation buffer parameters */
#define MP_TX_AGGR_BUF_RESET(a) do {                    \
    a->mpa_tx.pkt_cnt = 0;                      \
    a->mpa_tx.buf_len = 0;                      \
    a->mpa_tx.ports = 0;                        \
    a->mpa_tx.start_port = 0;                   \
} while (0)
```


#送信
## mwifiex_host_to_card
* sdio.cで定義
* DATAかCMDかでportを選択
* 上位から来るデータをSDIOブロックサイズ単位になるように長さを調整、(CMD53のマルチブロック転送のため)
* mwifiex_host_to_card_mp_aggr()を呼ぶ
* エラーで実際の送信はなかったら、portsの情報からcard->curr_wr_portとcard->mp_wr_bitmapをアップデート

## mwifiex_host_to_card_mp_aggr
* sdio.cで定義
* cardのportがcontrol,cmd だったり、 mpaが無効な場合は1個単位で送信
* パケットが続いているなら
 * MPA実施中なら
  * MPAバッファに空きがあるなら f_precopy_cur_buf を1にする
  * 次のパケット分の空きが無いなら、f_send_aggr_buf = 1;にする
 * 実施中でないなら
  * MPAバッファに空きがあってかつ？？なら f_precopy_cur_buf = 1;
  * そうでないなら f_send_cur_buf = 1;

```c
    if (next_pkt_len) {
        /* More pkt in TX queue */
        mwifiex_dbg(adapter, INFO,
                "info: %s: more packets in queue.\n",
                __func__);

        if (MP_TX_AGGR_IN_PROGRESS(card)) {
            if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len)) {
                f_precopy_cur_buf = 1;

                if (!(card->mp_wr_bitmap &
                      (1 << card->curr_wr_port)) ||
                    !MP_TX_AGGR_BUF_HAS_ROOM(
                        card, pkt_len + next_pkt_len))
                    f_send_aggr_buf = 1;
            } else {
                /* No room in Aggr buf, send it */
                f_send_aggr_buf = 1;

                if (!(card->mp_wr_bitmap &
                      (1 << card->curr_wr_port)))
                    f_send_cur_buf = 1;
                else
                    f_postcopy_cur_buf = 1;
            }
        } else {
            if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len) &&
                (card->mp_wr_bitmap & (1 << card->curr_wr_port)))
                f_precopy_cur_buf = 1;
            else
                f_send_cur_buf = 1;
        }
    } else {
```

* パケットが続いてなく、最後のパケットなら
 * MPA実施中なら
  * f_send_aggr_buf = 1;
  * MPAバッファに空きがあるなら f_precopy_cur_buf を1にする
  * そうでないなら、f_send_cur_buf = 1;

```c
    } else {
        /* Last pkt in TX queue */
        mwifiex_dbg(adapter, INFO,
                "info: %s: Last packet in Tx Queue.\n",
                __func__);

        if (MP_TX_AGGR_IN_PROGRESS(card)) {
            /* some packs in Aggr buf already */
            f_send_aggr_buf = 1;

            if (MP_TX_AGGR_BUF_HAS_ROOM(card, pkt_len))
                f_precopy_cur_buf = 1;
            else
                /* No room in Aggr buf, send it */
                f_send_cur_buf = 1;
        } else {
            f_send_cur_buf = 1;
        }
    }
```


* f_precopy_cur_buf なら MP_TX_AGGR_BUF_PUT()でコピー
* f_send_aggr_buf なら
 * mport を設定
 * mportにmwifiex_write_data_to_card()で書き込み
 * MP_TX_AGGR_BUF_RESET(card);でmpaのカウンタをリセット
* f_send_cur_bufなら mwifiex_write_data_to_card()でシングル送信
* f_postcopy_cur_buf なら MP_TX_AGGR_BUF_PUT()でコピー



# mpa の port
* SDIO CMD52のポートのアドレスに、mpaのstart portとmpaのportsビットマップをエンコードしてチップに伝える。　アドレスにエンコードするのはチップのHWアシストを使うためだと考えられる。
* mpa の portのHW仕様は card->supports_sdio_new_mode のtrue/falseで異なる。
 * これは 87xxはfalseで 88xx, 89xxでtrueとなる
* falseの場合 mportの32bitの割り当て
 * 32 - 25 : adapter->ioport
 * 24 - 17 : SDIO_MPA_ADDR_BASE
 * 16 - 5  : mpa ports (1からのビットマップ)
 * 4 - 1   : mpa start_port
 * 設定例

```
            mport = (adapter->ioport | SDIO_MPA_ADDR_BASE |
                 (card->mpa_tx.ports << 4)) +
                 card->mpa_tx.start_port;
```

* trueの場合 mportの32bitの割り当て
 * 32 - 25 : adapter->ioport
 * 24 - 17 : SDIO_MPA_ADDR_BASE
 * 16 - 5  : mpa ports (portsを２進数にして含まれる1の個数 -1)
 * 4 - 1   : mpa start_port
 * 設定例

```c
            u32 port_count;
            int i;

            for (i = 0, port_count = 0; i < card->max_ports; i++)
                if (card->mpa_tx.ports & BIT(i))
                    port_count++;

            /* Writing data from "start_port + 0" to "start_port +
             * port_count -1", so decrease the count by 1
             */
            port_count--;
            mport = (adapter->ioport | SDIO_MPA_ADDR_BASE |
                 (port_count << 8)) + card->mpa_tx.start_port;
```

* ports の内容の遷移
 * 00000000 : 最初
 * 00000001 : １パケット追加
 * 00000011 : 1パケット追加
 * 00000111 : 1パケット追加
 * 00000000 : アグリゲート送信
 * 00001000 : 1パケット追加 
 * 00011000 : 1パケット追加 
 * 00111000 : 1パケット追加 
 * 01111000 : 1パケット追加 
 * 11111000 : 1パケット追加 
 * 11111001 : 1パケット追加 
 * 11111011 : 1パケット追加 
 * 00000000 : アグリゲート送信
 * 00000100 : 1パケット追加 

* mpaのports使用状況は割り込み毎にmwifiex_process_int_status()でcard->mp_wr_bitmapにアップデートされる。
* card->max_ports はチップによって異なる 16か32か64。

* データ送信のportはmparのportと通常の１個のデータ通信のポートの２個ある

# 分からないところ
* 送信完了の処理
* 割り込みの処理
* portsの更新と次の送信の制御