
RP2040 ファームウェア。Toshiba MK4001MTD 0.85インチ SDIO マイクロドライブを USB マスストレージデバイスとしてブリッジし、PIO アクセラレーションによる読み書きと不良セクタリカバリを備えた完全な SDIO-ATA プロトコルスタックをゼロから実装します。
東芝MK4001MTD 0.85インチSDIOマイクロドライブをUSBマスストレージデバイスとしてブリッジするRP2040 Picoファームウェア。

MK4001MTDは、フラッシュストレージがまだかなり高価だった時代に、ノキアN91ミュージックフォンや一部のMP3プレーヤー、USBドライブなどのデバイスで元々使用されていた4GBのマイクロドライブです。
このドライブがMMCプロトコルを使用していると主張する紹介を見たことがあるかもしれませんが、それは実際には間違いです。私はしばらく調査してきました:8ビットMMCplusカードリーダーを構築しようとし、さまざまなSD/MMCリーダーをテストしましたが成功しませんでした。最後の手段として、ノキアN91を購入してロジックトレースを取得し、実際に使用しているプロトコルを確認しました。
8ビットMMCPlusリーダーボードで使用しようとしたときの写真です。MMCではないことが判明しました:(

そこでN91を入手してトレースを収集しました:

標準のATA/CFマイクロドライブとは異なり、ATAコマンドをCMD52/CMD53でトンネリングするSDIOインターフェースを使用しています。既存のドライバーはこのプロトコルをサポートしていないため、このファームウェアはフルスタックをゼロから実装しています。
CE-ATAと呼ばれるSDIO-to-ATA規格が存在するため、これは驚きでした。しかし、リリースタイムラインをよく見ると、CE-ATAはこのドライブよりも後に登場しています。その結果、このドライブは完全にSDIOコマンドに依存しており、CE-ATAは利用できません。CE-ATAには2つの新しいコマンドCMD60/CMD61があり、CMD12/39を利用しますが、トレースからはそれらのいずれも使用していないことがわかります。
言及すべき2つ目のハードウェアのポイントは、8ビットMMCPlusカードであると主張する誤情報が広まっていることですが、それは虚偽であるだけでなく、ピン配置もMMC標準に従っていません。ノキアN91のサービスマニュアルにはピン配置に関するドキュメントがあります。ピン番号はMMCPlus標準に従っていますが、ピンのマッピングは異なります。これは自分で配線する場合の重要な詳細です。同じMMCコネクタを使用しますが、ピンマッピングが異なります。詳細はハードウェアセクションで。
最後に、これはClaude/OpenClawとの共同開発であることに注意してください。私が手動でロジックトレースを収集し、OpenClawが開発を反復するためのクローズドループテストステーションをセットアップしました(トレースの分析と機能の実装)。ドキュメントは主にClaudeによって書かれます。私もインラインでメモを追加します。ドキュメントは私自身が読み、再確認しており、信頼性が高くわかりやすいものになっているはずです。
N91トレースの分析に関する洞察は、/docs/N91_TRACE_ANALYSIS.mdにあります。生のロジックトレースとともにN91サービスマニュアルもそこに置いてあります。
詳細はブログ記事を参照: https://www.willwhang.dev/Reading-MK4001MTD/
動作の様子: https://youtu.be/GC4xil3_Bbc
PIOアクセラレーションによる読み取り/書き込みとアイドル時電力管理を備えた完全機能のUSBマスストレージ。
| 指標 | 値 |
|---|---|
| 読み取り速度 | ~985 kB/s (USBフルスピード制限) |
| 書き込み速度 | ~920 kB/s (USBフルスピード制限、アドバタイズされた書き込みキャッシュ) |
| 生SDIO側速度 | ~2.35 MB/s 読み取り / ~2.15 MB/s 書き込み (ドライブ制限) |
| 容量 | 3.75 GB (7,862,400セクタ) |
| ファイルシステム | FAT32確認済み (マウント/アンマウント/fsck正常) |
| データ整合性 | 書き込み+読み戻し確認済み; 全4DATラインでブロックごとのCRC16 |
| アイドルスタンバイ | 5秒アイドルまたはUSBサスペンド → STANDBY IMMEDIATE + パワーゲート |
USB Host ←→ USB MSC (TinyUSB) ←→ ATA Layer ←→ SDIO Layer (PIO) ←→ MK4001MTD
ファームウェアは4つのレイヤーで構成されています:
USB MSC (msc_device.c) — TinyUSBマスストレージクラス。SCSI READ(10)/WRITE(10)をATAセクター操作に変換します。32KBのEPバッファ、USB転送あたり最大64セクターをバッチ処理。ドライブI/Oは双方向でUSBとオーバーラップしており、キャッシュディスクを備えた実際のATA-USBブリッジのように動作します:シーケンシャルリードプリフェッチャーは前のチャンクがホストにストリーミングされている間に次のチャンクを取得し、書き込みはUSBが次のピースを受信している間にステージングされてフラッシュされます。デバイスは書き込みキャッシュ(キャッシングモードページ、WCE=1 — ホストは「Write cache: enabled」を報告し、fsync/アンマウント/サスペンド時にSYNCHRONIZE CACHEを発行します。ファームウェアはこれを尊重します)をアドバタイズします。バックグラウンドフラッシュの失敗は、次のWRITEまたはSYNCHRONIZE CACHEでMEDIUM ERRORとして現れます。既知の不良セクタへの書き込みは、厳密な同期パスを取ります。
ATA-over-SDIO (ata_sdio.c) — CMD52を介してSDIOファンクション1アドレス空間にマッピングされたATAレジスタに書き込み、CMD53を介してセクタデータを転送することにより、ATAコマンド(IDENTIFY、READ SECTORS、WRITE SECTORS)を実装します。CMD、データ、ATAレベルの3層リトライロジック。
PIO SDIO (sdio_pio.c、sdio.pio) — RP2040のPIOペリフェラルを使用したハードウェアアクセラレーションSDIO(4ビットバス、10 MHz、入力シンクロナイザーをバイパスした1ビットあたり4 PIOサイクル)。3つのPIOプログラムが動的プログラムスワッピングを介して単一のステートマシンを共有します:
ピン/電源 (sdio_hw.c) — GPIO初期化とHDD電源制御。すべてのSDIO通信はPIOを使用します。
人間のメモ:興味深いことに、ClaudeはPIOでSDIOを実装することに非常に消極的で、PIOとビットバンギングの間を行き来する多くの開発サイクルが無駄になりました。
MK4001MTDは、1つのI/O機能を持つSDIOカードとして認識されます。標準のSDIOカード初期化(CMD5/CMD3/CMD7)でバスを設定し、その後SDIOコマンドを介してATAレジスタにアクセスします:
レジスタアクセス (CMD52): 各ATAレジスタはファンクション1のアドレスにマッピングされています:
| アドレス | レジスタ | 用途 |
|---|---|---|
| 0x00 | DATA | セクタデータ用CMD53ターゲット |
| 0x01 | ERR/FEAT | エラー (読み取り) / フィーチャ (書き込み) |
| 0x02 | SECCOUNT | セクタ数 |
| 0x03 | LBA_LO | LBAビット0-7 |
| 0x04 | LBA_MID | LBAビット8-15 |
| 0x05 | LBA_HI | LBAビット16-23 |
| 0x06 | DEV/HEAD | デバイス/ヘッド + LBAビット24-27 |
| 0x07 | CMD/STATUS | コマンド (書き込み) / ステータス (読み取り) |
データ転送 (CMD53): セクタデータは、DATAレジスタ(アドレス0x00)をターゲットにしてブロックモードでCMD53を発行することで転送されます。マルチセクタ読み取りの場合、block_count=Nの単一CMD53で、1回のSDIOマルチブロックトランザクションでN×512バイトを転送します。
割り込みシグナリング: ドライブは、SDIO割り込み(CCCRレジスタ0x05のINT_PENDINGビット1)をアサートすることでセクタの準備完了を通知します。ATA STATUSレジスタを読み取ると割り込みがクリアされます。
16セクターの読み取りの場合:
1. PIO CMD52経由でATAレジスタに書き込み:
SECCOUNT=16, LBA_LO/MID/HI, DEV/HEAD=0xE0, CMD=0x20
2. CMD52経由でSTATUSをポーリングし、DRQ (ビット3) がセットされるまで待機
3. PIOをDAT読み取りプログラムにスワップ
4. CMD53を送信:block_mode=1, fn=1, addr=0x0000, block_count=16
5. PIO DAT読み取り:16ブロックのそれぞれについて:
a. スタートビット(全DATラインLow)を待機
b. PIO RX FIFOからバッファに1024ニブル(512バイト)をDMA転送
c. SMがCRC+エンドニブルのクロック出力を完了するのを待機(SM PCをポーリング)
d. ニブルをインプレースでバイトに再パック
6. PIOをCMDプログラムにスワップバック
16セクターの書き込みの場合:
1. PIO CMD52経由でATAレジスタに書き込み:
SECCOUNT=16, LBA, DEV/HEAD=0xE0, CMD=0x30
2. CMD52経由でSTATUSをポーリングし、DRQ (ビット3) がセットされるまで待機
(STATUS 0xD8 = BSY+DRQはDRQ準備完了として扱う。N91トレースより)
3. PIOをDAT書き込みプログラムにスワップ
4. CMD53を送信:block_mode=1, fn=1, addr=0x0000, block_count=16
5. PIO DAT書き込み:16ブロックのそれぞれについて:
a. DATラインごとにCRC16-CCITTを事前計算(4つの独立CRC)
b. ニブルストリームを構築:start(0x0) + data(1024ニブル) + CRC(16) + end(0xF)
c. ニブルストリームをPIO TX FIFOにDMA転送
d. PIOがすべてのニブルをクロック出力した後:
- DATを入力に切り替え
- カードからのCRCステータス用に16サイクルクロック
- DAT0がカードのビジー解除までポーリング
- IRQ 0を発生させてブロック完了を通知
6. PIOをCMDプログラムにスワップバック
RP2040のPIOは、ブロックあたり32命令スロットを持っています。3つのプログラムは合計55命令であるため、共存できません。代わりに、PIO0上の単一のSM0を使用し、PIO命令メモリに直接書き込むことでプログラムをスワップします:
static void load_program_raw(const pio_program_t *program) {
for (uint i = 0; i < program->length; i++)
pio->instr_mem[FIXED_OFFSET + i] = program->instructions[i];
}
これにより、SDKのpio_add_program/pio_remove_programアロケータがバイパスされます。プログラムスワップには約1 µsかかります。各スワップの後、ピン割り当て、シフト方向、クロック分周器を設定するプログラム固有の再初期化が続きます。
ノキアN91のロジックトレース分析により、積極的な電力管理が明らかになりました:
ファームウェアはこの動作を設定可能なアイドルタイムアウトで再現します:
#define IDLE_STANDBY_MS 5000 // main.c内
HDDのパワーゲーティングをトリガーする2つのパス:
両方のパスでATA STANDBY IMMEDIATE (0xE0)を送信して書き込みキャッシュをフラッシュし、ヘッドをパークした後、GP9を介して電源を遮断します。
ウェイクシーケンス (ゲート後の最初のREAD/WRITEでトリガー):
マルチセクタ転送が不良セクタにヒットした場合:
STATUS/ERRORビットをキャプチャするのに十分な時間待機MEDIUM ERROR(読み取り:03/11/00、書き込み:03/0C/00)で失敗させる