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なら応答
- CMD5 IO_SEND_OP_COND 電圧を指定
- CMD3 SEND_RELATIVE_ADDR
- CMD7 SD SELECT/DESELECT_CARD
CMD7 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
- 256バイトのカード共通の制御レジスタ
- 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と呼ぶ可変長のフォーマット、1バイト目が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にならない。 - 上記3箇所の条件を通るように修正したらデータを読み出せるようになった。だけどブロック単位で読んでしまうので、バイト単位になるように改造が必要
- SDIOの2回目のCMD53 バイトデータ転送ができない
- 1回目のDATA転送ができるようになったが、2回目の転送を行うと、CMD53が途中で消失する。
- data転送後の終了処理が呼ばれていないの原因っぽい
- sdhci_can_issue_command()の戻り値がfalseになるとコマンドを発行しない
- SDメモリの場合はsdhci_read_dataport()でデータ転送終了処理をやる。これがブロックサイズに依存しているのでCMD53バイト転送だと終了処理が呼ばれない。
- CMD53バイト転送後にデータ転送終了処理をすると、2回目のCMD53がカード側に届くようになった。
- また転送完了割り込みが入るようになったので、割り込みをクリアする処理を入れたが、その後のUART出力がおかしくなるので、転送完了割り込みは、テストアプリの初期設定でマスクすることにした。
- カード側で変数の初期化が抜けていたので、初期化を追加したら、やっと2回目のCMD53 バイトデータ転送ができるようになった。
参考情報
- SD仕様書
- SDIO仕様書
- SDHOST仕様書
- ROHM BU1805GUのソフトウェア開発仕様書