SDIOについて

SDIOについて

SDIOエミュレータを書いているので、情報をまとめています。 まだ途中です。

マイルストーン

CMD53のマルチブロックライト：
CMD53のマルチブロックリード：
CMD53のシングルブロックライト： 2018-06-24
CMD53のマルチバイトライト: 2018-06-24
CMD53のシングルブロックリード： 2018-06-24
CMD53のマルチバイトリード: 2018-06-22
Linuxカーネルが各ファンクションのCISを読み出す:2018-06-19
LinuxカーネルがFBRを読み出す:2018-06-16
LinuxカーネルがCommon CISを読み出す:2018-06-16
LinuxカーネルがCCCRを読み出す:2018-06-16


# SDIOで使うコマンド

最低限の動作に必要なコマンド

CMD0とCMD3とCMD7はSD仕様書に記載。
CMD5とCMD52とCMD53はSDIO仕様書に記載。

* CMD0 GO_IDLE_STATE
 * カードをIDLE状態にする。 
 * レスポンンス無し
* CMD3 SEND_REL_ADDR
 * 相対アドレスをホストが取得する。
 * レスポンスはR6 相対アドレス、ただしSDIOのみ対応のカードだとステータスビットの取り扱いが異なる。
* CMD5 IO_SEND_OP_COND
 * I/O OCR (24bit)などをやりとりする。カードが対応している電圧についての情報が記述されている。
 * SDIOの初期化を開始する。ReadyになるとCが1になる。
 * SDIOのファンクションの数が分かる。
 * レスポンスはR4 I/O OCR
* CMD7 SELECT/DESELECT_CARD
 * CMD3で取得した相対アドレスを送信し、カードを選択する。
 * カード選択をするとCommand Stateに遷移する。 
 * レスポンスはR1b 
* CMD52 IO_RW_DIRECT
 * 1バイトのデータを読み書きする。
 * 信号線はCMDのみを使う。DAT0-3の信号線は使わない。
 * レスポンスはR5
* CMD53 IO_RW_EXTENDED
 * マルチバイト、マルチブロックのデータを読み書きする。
 * 1bit widthの場合はDAT0信号線を使う。4bit widthの場合DAT0-3の信号線を使う。
 * レスポンスはR5

# SDIO初期化シーケンス

LinuxのSDIOをトレースして解析

* CMD52 FN0の0x6を読む stateが違うので応答しない
* CMD52 FN0の0x6に8を書き込む stateが違うので応答しない
* CMD0 GO_IDLE_STATE
* CMD8 SEND_IF_COND SDメモリなら応答するが、SDIOなので応答しない
* CMD5 IO_SEND_OP_COND SDIOなら応答
* CMD５ IO_SEND_OP_COND 電圧を指定
* CMD3 SEND_RELATIVE_ADDR 
* CMD7 SD SELECT/DESELECT_CARD

CMD７ CARD SELECT後はTransfer Stateになるので、CMD52とCMD53が使えるようになる。


# SDIO State

SDIOカードのステート

SDIOカードを主体として考える。

* Initialize State
 * 初期状態
 * CMD5 を受け付ける
 * CMD3を受信すると、Standbyに遷移
* Standby State
 * CMD7を受け付ける
 * 正しい相対アドレスのCMD7を受信するとCommandに遷移
* Command State
 * カードが選択されデータ転送コマンドを受け付ける状態
 * CMD52とCMD53を受け付ける。
* 　Transfer State
 * CMD53のデータ転送状態　
 * CMD52 Abortのみ受け付ける　 

SDIOカードのステートの情報はCMD52のレスポンスに含まれている。

* 00 : Initialize また 非活性な状態
* 01 : Command State
* 02 : Transfer State
* 03 : 予約

# ファンクション
あとで書く


# 設定レジスタ

## OCR
* カードの対応電圧の情報
* CMD5で取得する。

```
例: 20FFFF00
デコードすると
0 : C : not ready
2 : Number of I/O Function : Function1とFunction2がある
0 : Memory Present : no memory
0b11111111111100000000 : I/O OCR : 2.0-3.6に対応
```

## CCCR : Card Common Control Registers
* ２５６バイトのカード共通の制御レジスタ　
* CMD52, CMD53で読み書きする。
* バージョン情報
* ファンクションの情報
* ブロックサイズ
* CISのアドレス

```
例
32 xx 00 00 xx xx xx 40
02 70 10 00 xx xx xx xx
xx xx 01 01

3 : SDIOのバージョン : SDIO 2.0
2 : CCCR/FBRのバージョン : CCCR/FBR 1.2
1 : SCSI Support Coontinus SPI interrupt : Yes
1 : SMB Support Multi Block : Yes
70 10 00 : Common CISのアドレス : 0x001070
1 : SMPC Support Master Power Control : Yes
1 : SHS Support High Speed : Yes
```

## FBR : Function Basic Registers 
* ファンクションの制御レジスタ。 ひとつのファンクション毎に256バイト。
* CMD52, CMD53で読み書きする。
* Functioon code
* ブロックサイズの初期値は0なので、Hostから設定する。

```
例
100: 00 xx xx xx xx xx xx xx 
xx xx 00 10 00
00 10 00 : Function CISのアドレス : 0x001000


200: 00 xx xx xx xx xx xx xx 
xx xx 38 10 00

38 10 00 : Function CISのアドレス : 0x001038

```

## CIS : Card Information Structure

* CISはPCMCIAの仕様を使っており、SD仕様書には情報が無い。
* PCMCIAの仕様書は有料。（だけど2018年現在、ロシアのサイトにPDFがある。)
* フォーマットはLinuxとかBSDのソースコードをみれば分かる。
* Tupleと呼ぶ可変長のフォーマット、１バイト目がCODE、2バイト目が長さ兼次のTuple、3バイト目以降が可変長データ

```
例
01000: 20 04 D0 02 A6 A9 21 02 0C 00 22 2A 01 00 00 00
01010: 00 00 00 00 00 00 00 00 40 00 00 FF FF 80 00 00
01020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
01030: 00 00 00 00 00 00 FF   

デコード
20 04 D0 02 A6 A9: MANFID が D0 02 A6 A9
21 02 0C 00 : FUNCID が 0C 00
22 2A 01 00 00 00 
00 00 00 00 00 00 00 00 40 00 00 FF FF 80 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00: FUNCE
FF : 終端

01030:                         20 04 D0 02 A6 A9 21 02
01040: 0C 00 22 2A 01 01 00 00 00 00 00 00 00 00 00 00
01050: 00 02 00 FF FF 80 00 00 00 00 00 00 00 00 00 00
01060: C8 00 00 00 00 00 00 00 00 00 00 00 00 00 FF

01070: 20 04 D0 02 A6 A9 21 02 0C 00 22 04 00 20 00 58
01080: 80 02 00 0B 80 03 02 01 11 80 03 1B 26 07 80 07
01090: 19 B8 27 EB AF DC 01 00 00 00 00 00 00 00 00 00
010A0: ここから
010B0:
010C0:
010D0:
010E0:
010F0:
01100:
01110:
01120:
01130:
01140:
01150:
01160:
01170:
01180:
01190: ここまで00
011A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF

デコード
20 04 D0 02 A6 A9
21 02 0C 00
22 04 00 20 00 58
80 02 00 0B: ベンダー独自
80 03 02 01 11: ベンダー独自
80 03 1B 26 07: ベンダー独自
80 07 19 B8 27 EB AF DC 01: ベンダー独自
空白
FF : 終端



```

# Linux で SDIOのコマンドをダンプする
* CMD5 と CMD52 と CMD53 を出力する。
* 変更するカーネルのファイルは drivers/mmc/core/sdio_ops.c

```
        printk("CMD5 %x %X \n", ocr, *rocr);
```
```
        if (write) {
                printk("CMD52W FN%d %05X %02X\n", fn, addr, in);
        } else {
                printk("CMD52R FN%d %05X %02X\n", fn, addr, cmd.resp[0] & 0xFF);
        }
```
```
        if (write) {
                printk("CMD53W FN%d %05X\n", fn, addr);
                print_hex_dump_bytes("CMD53W:", DUMP_PREFIX_NONE,
                                     buf, data.blksz * data.blocks);
        } else {
                printk("CMD53R FN%d %05X\n", fn, addr);
                print_hex_dump_bytes("CMD53R:", DUMP_PREFIX_NONE,
                                     buf, data.blksz * data.blocks);
        }j
```
のような感じです。
カーネルログ出力が大量に出るので、カーネルの起動オプションに```log_buf_len=16M```とかすると良いです。

# エミュレーションの実装
* SDIO_CARD
 * SD_CARDと並列にSDIO_CARDを作成
 * sd_busはSDメモリとSDIOで共用
 * sd_busからカードへアクセスする部分に、SD_CARDとSDIO_CARDの判別処理を加える。 
* CMD52
 * SDIOカード内のメモリ空間を定義する。
 * メモリ空間に初期値を設定する。
 * 与えられた入力で、メモリ空間をリードライトする。
 *　R5を生成する関数を作る。
* CCCR, FBR, CIS
 * Linuxなどで読み出している部分のログを取る。
 * ログからCCCR, FBR, CISの値を調べて、リード時に同じ値を返すようにする。
 * CCCRとFBRはライトもできるようにする。
 * これで、とりあえずは動いた。

ここまではカードとバスの修正だけでOK
CMD53はホストの修正が必要

* CMD53

* メモリ空間がどう見えるかはカードに依存する。
* メインメモリの領域を借りるにはどうしたら良いのか

# 不明点
* カードをInitialize State から　Command Stateに切り替えるには
 * CMD5 と CMD3 と CMD7を送信するとCommand Stateに遷移する。
* BCM43438だと認識するのは、どこを見てるのか？　
 * CISのvendorとclass
 * Functionの数が2
 * Function0の0x8000-0x8004を読み0x1541a9a6であること ここまでは共通の認識処理
 * todo:チップの型番認識
 * Marvell、RealtekはCISに型番を埋めてあるので、OSレベルで判別ができる。

# Linuxドライバ

* brcmf_sdio_register（）でstruct sdio_driverを登録すると、sdio_driver->id_tableとIDが一致したら、カーネルからsdio_driver->probe()が呼ばれる。
* 


# はまり点
* Common CISを読んで、バス幅設定後にコマンドタイムアウトが発生する。なのでSDIOカードの初期化が途中で止まる
 * 正常動作なら、コマンド完了割り込みが入ると、タイマ割り込みがクリアされる。
 * 小さなプログラムで。QEMUでも、コマンド完了割り込みが入ることを確認した。
 * SDHCIのレジスタアクセスをトレースしてみると、最後のコマンドから応答がない。カードが応答していない。SD CLOCKが止められている。
 * qemu sdhciのcapabのデフォルト値 0x057834b4
 * sdio_enable_hs（）、　mmc_set_clock（）あたり
 * sd clkが止まらない小細工をしたら、```mmc1: new high speed SDIO card at address 4567``` まで動いた。
 * とりあえずこの小細工をして進める。
* SDIOのDATA転送できない
 * CMD53のマルチブロックリードを送信後、データレジスタからデータを読み出せない。
 * sdhci_read_dataport()で```s->prnsts & SDHC_DATA_AVAILABLE``` を満たさないのでバッファが空のようだ。
 * このフラグを立てるのは、```sdhci_read_block_from_card()```
 * 遡ると```sdhci_send_command()``` -> ```sdhci_data_transfer()``` -> ```sdhci_read_block_from_card()```
 * sdhci_send_command() で ```if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT))```の条件に該当していなかったのか？
 * sdhci_data_transfer()で```if ((s->trnmod & SDHC_TRNS_READ) && sdbus_data_ready(&s->sdbus)) ```に該当していなかった
 * sd.cで```sd->state == sd_sendingdata_state```でないとdata_readyにならない。
 * 上記３箇所の条件を通るように修正したらデータを読み出せるようになった。だけどブロック単位で読んでしまうので、バイト単位になるように改造が必要
* SDIOの２回目のCMD53 バイトデータ転送ができない
 * １回目のDATA転送ができるようになったが、２回目の転送を行うと、CMD53が途中で消失する。
 * data転送後の終了処理が呼ばれていないの原因っぽい
 * sdhci_can_issue_command()の戻り値がfalseになるとコマンドを発行しない
 * SDメモリの場合はsdhci_read_dataport()でデータ転送終了処理をやる。これがブロックサイズに依存しているのでCMD53バイト転送だと終了処理が呼ばれない。
 * CMD53バイト転送後にデータ転送終了処理をすると、２回目のCMD53がカード側に届くようになった。
 * また転送完了割り込みが入るようになったので、割り込みをクリアする処理を入れたが、その後のUART出力がおかしくなるので、転送完了割り込みは、テストアプリの初期設定でマスクすることにした。
 * カード側で変数の初期化が抜けていたので、初期化を追加したら、やっと２回目のCMD53 バイトデータ転送ができるようになった。



# 参考情報
* SD仕様書
* SDIO仕様書
* SDHOST仕様書
* ROHM BU1805GUのソフトウェア開発仕様書