はじめに
GR-KAEDEが秋月電子で在庫処分特価で売っていたので、買ってみました。GR-KAEDEは、RX64Mを搭載した評価ボードで、イーサネットやUSBの他に256MビットのSDRAMが実装されています。本記事は、GR-KAEDEにUSB MSCのファンクション機能をゼロから実装したときの覚書です。e2studioを使う場合、FITが便利ですが、それでは勉強にはならないので、レジスターレベルでゼロから実装することにしました。
仕様
項目 | 仕様 | 備考 |
---|---|---|
ターゲットボード | GR-KAEDE | |
MCU | R5F564MLCDFB | 144ピン、RAM:512K/ROM:4M |
開発環境 | e2studio v2022.01 | |
RTOS | 使用しない | |
ツールチェイン | GCC for Renesa RX 8.3.0.29.201904 | バージョンに注意※1 |
エミュレータ | E1エミュレータ | |
その他のツール | usbmon、wireshark、他 |
※1,8.3.0.29.201904以降のバージョンでは、optlibが使えなくなりました。替わりのnewlibはoptlibに比べると数値計算が圧倒的に遅い。
テストプロジェクト
WindowsまたはLinux上でGR-KAEDEがUSBメモリーとして見えます。ただし、マスストレージはSDRAMなので電源を落とすと、当然ながら中身は消えます。ソースコード1式は、VLXのダウンロードサイトにあります。どなたでも無料でダウンロードできます。また、RX62N用のプロジェクトも一緒に置いてあります。
文献
[1]RX64Mグループユーザーズマニュアル ハードウェア編ルネサス32ビットマイクロコンピュータRXファミリ/RX600シリーズ
[2]Universal Serial Bus Specification Revision 2.0, April 27, 2000
[3]Universal Serial Bus Mass Storage Class Specification Overview, Rev 1.4, February 19, 2010
[4]Universal Serial Bus Mass Storage Bulk-Only Transport, Revision 1.0, September 31,1999
[5]Universal Serial Bus Mass Storage Specification For Bootability, Revision 1.0, October 25,2004
[6]SFF-8070i Specification for ARAPI Removal Rewritable Media Devices, Rev 1.2, Nov.4, 1998
[7]SCSI Commands Reference Manual, Rev.L, November 2020, Seagate
[8]Microsoft FAT Specification, August 30, 2005
プログラムの構成
概要
USB MSC(Mass Storage Class)の基本的な考え方は、SCSIやATAPIなどのストレージデバイスを模擬することです。模擬対象となるストレージデバイス用コマンドにUSB固有の項目を付加した拡大コマンドパケット(ラッパーと言う)をホスト・デバイス間で送受信します。模擬対象としてSCSIコマンドを採用するケースが多いようですが、SCSIはコマンド数が多いうえ、やや複雑です。一方、ATAPI用のSFF-8070i規格のコマンド数は17個と少なく、プロトコルも簡単です。(文献3)では、SFF-8070iの採用は勧めていないようですが、本書では、SFF-8070iを採用することにします。また、通信プロトコルは、Bulk-InとBulk-Outの2つのエンドポイントのみを使用するBulk Only Transfer(BOT)を使用します。Windowsの汎用USB MSCデバイスドライバーであるusbstor.sysがSFF-8070iとBOTに対応しているので、そのまま使用できます。
割り込み駆動
本プロジェクトでは、RTOSを使用していないので、すべての処理の起点は割り込みです。USBとしての基本機能は、デフォールトパイプの割り込み処理ルーチンが扱います。MSC固有の処理は、Bulk InとBulk Outの2つのエンドポイントが担当します。両者ともに割り込み要因はBRADYです。各エンドポイントごとのBRADY割り込みルーチンが処理します。
ファイルシステム
Linuxではddコマンドが使えるので、ファイルシステムが無くてもなんとかなりますが、Windowsの場合は、ファイルシステムに対応していないと、ドライブ文字が割り当てられません。USB MSCデバイス自体が見えないので、お手上げ状態になります。前述したように、マスストレージはSDRAM上にあるので、このデバイスを起動したときに、毎回マイコン側でFAT16でフォーマットするようにしました。
FAT領域サイズの計算
FAT領域のサイズの計算は(文献8)にも書いてありますが、少し補足します。下記は、Master Boot Recordを持ち、予備を含めてFAT領域を2つ作成した場合のFAT16ファイルシステムのレイアウトです。サイズの単位は、セクション数です。セクターは512バイト、クラスターサイズは8セクターと仮定しています。
領域 | サイズ | 説明 |
---|---|---|
MBR | 1 | Master Boot Record |
BPB | 1 | Bios Parameter Block |
空き | 6 | クラスターサイズで整列する |
FAT1 | 16 | 第1FAT領域 |
FAT2 | 16 | 予備FAT領域 |
RDE | 2 | Root Directory Entry |
Data | データ本体 |
FAT領域のセクション数をxとすると、
(512/2)*SecPerClus*x = DskSize-(RsvSecCnt+RootDirSectors+NumFATs*x)
xについてこれを解くと、
x=\frac{DskSize-(RsvSecCnt+RootDirSectots)}{256*SecPerClus+NumFATs}
ここで、SecPerClusはクラスターのセクター数=8、DskSizeはデバイスの全セクター数=32678、RsvSecCntはFAT以前のセクター数=8、RootDirSectorsはRDE領域のセクター数で、最大ファイル数を32と仮定すると、32*32/512=2となります。NumFATsはFAT領域の個数=2です。これらを代入するとx=15.98になります、切り上げてx=16です。
SFF-8070i規格
SFF-8070i(文献6)は、ATAPIインターフェースに対応したリム―バブルディスク用の通信規格です。17個のコマンドのうちすべてを実装する必要はないようです。本記事では、実装したのは、下記の7個だけです。それ以外は、STALLを返しています。
コマンド名 | コード | 説明 |
---|---|---|
TEST_UNIT_READY | 00h | デバイスの生存確認 |
INQUIRY | 12h | デバイスのタイプなどの問い合わせ |
MODE_SENSE10 | 5Ah | 特に何もしない |
READ10 | 28h | データの読み取り |
READ_CAPACITY | 25h | 最大セクター数とセクターのバイト長 |
REQUEST_SENSE | 03h | エラーコードを返す |
WRITE10 | 2Ah | データの書き込み |
デバッグ
開発の初期段階では、いつもの通り、全く動きません。プロトコルアナライザ―があれば、うれしいのですが、結構高いです。趣味の範囲を超えています。ソフトウエアで通信パケットが見られるツールを探したところ、usbmonとwiresharkが見つかりました。usbmonはLinux専用です。Windows用のusbpcapもありましたが、usbmonの方が使い勝手が良さそうです。このため、開発の初期段階ではLinuxでデバッグをします。ある程度動くようになったら、Windows上でWiresharkを使ってデバッグします。
苦労したところ
下記コードの断片は、ホストから送られてくるマスストレージデータを受信するルーチンです。すべてのデータを受信し終えたところで、ACLRMビットでPIPE1(Bulk In用)をクリアーします。これをしないと、次に送信するステータスのdCSWTagが前のままになり、通信不良が発生して、デバイスがリセットされてしまうと言う症状がありました。この症状は、usbmonでプロトコルを解析して見つけました。対処療法としては、ACLRMによる全クリアーで症状は出なくなりますが、何故これで解決するのか十分に理解できていません。
//マスストレージデータを受信
int recv_ms_data()
{
int dtln;
debug(DEBUG_LEVEL3,"recv_ms_data: rx_remaining=%d, wrp=%d\n",rx_remaining,wrp);
//read D1FIFO
dtln = USB0.D1FIFOCTR.WORD & DTLN;
for (int i=0; i<dtln; i++) {
if (wrp>=mass_storage_length) {
debug(DEBUG_ERROR,"recv_ms_data: invalid wrp=%d\n",wrp);
return -1;
}
uint8_t c = USB0.D1FIFO.BYTE.L;
mass_storage[wrp] = c;
wrp++;
}
rx_remaining -= dtln;
if (rx_remaining<0) {
debug(DEBUG_ERROR,"recv_ms_data: invalid rx_remaining\n");
rx_remaining = 0;
}
if (rx_remaining>0) {
//マスデータ受信を継続
bulkout_state = RECV_MS_STATE;
} else {
//*** 受信完了 ***
/**
* [重要]
* PIPE1(BulkIN)のダブルバッファをクリアーする。PIPE2(BulkOut)ではないことに注意。
* これをしないと、WRITEコマンドの次に受信するコマンド(TEST_UNIT_READYなど)に対するdCSWTagが、
* WRITEコマンド時のdCSWTagのままになっている。
* dCBWTagとdCSWTagの不一致が生じ、ホストはデバイスをリセットしてしまう。
* 理由は良く理解していない。
*/
USB0.PIPE1CTR.WORD |= ACLRM;
sleep_in_msec(0.1f);
USB0.PIPE1CTR.WORD &= ~ACLRM;
//BulkInの次のフェーズはCSWステータスを返す
bulkin_state = STATUS_STATE;
//応答を返すためBulkIn割り込みを許可
USB0.BRDYENB.WORD |= PIPE1_BITPOS;
//BulkOutの次のフェーズはCBWコマンド受信
bulkout_state = COMMAND_STATE;
}
return rx_remaining;
}
おわりに
最後まで読んでいただいてありがとうございます。ご感想等などがあれば、どんなことでも結構です、Mailを下さい。