LoginSignup
2
1

More than 1 year has passed since last update.

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

Posted at

はじめに

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を下さい。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1