RX64MにUSB MSCをゼロから実装してみた

# はじめに
GR-KAEDEが[秋月電子](https://akizukidenshi.com/catalog/c/cgr/)で在庫処分特価で売っていたので、買ってみました。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なので電源を落とすと、当然ながら中身は消えます。ソースコード１式は、[VLX](http://esoftjapan.com/)のダウンロードサイトにあります。どなたでも無料でダウンロードできます。また、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規格のコマンド数は１７個と少なく、プロトコルも簡単です。（文献3）では、SFF-8070iの採用は勧めていないようですが、本書では、SFF-8070iを採用することにします。また、通信プロトコルは、Bulk-InとBulk-Outの２つのエンドポイントのみを使用する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とすると、
```math
(512/2)*SecPerClus*x = DskSize-(RsvSecCnt+RootDirSectors+NumFATs*x)
```
xについてこれを解くと、
```math
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による全クリアーで症状は出なくなりますが、何故これで解決するのか十分に理解できていません。
```C
//マスストレージデータを受信
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](http://esoftjapan.com/VLX/support/jp_support.php)を下さい。