ファイルシステムを追加する(RPiベアメタルMicroPython)

ramdisk.png

今日もRaspberry PiへのMicroPythonの移植です。
これまで、ファイルシステムの実装は後回しにしてきました。
Raspberry PiのSDカードへアクセスするためのソースコードはいくつかネット上にあるのですが、ドキュメントが少なく、取り込むのはなかなか手間がかかりそうです。

ですが、REPLのペーストモードだけではこの先だんだん行き詰まりそうです。
ファイルシステムがあれば、ampyやuPyCraftのようなツールで、ホストからファイルを転送して実行することができるようになります。

そこで、SDカードは後回しにして、RAMディスクを実装してはどうかと考えました。
書き込んだ内容は電源を切れば消えてしまいますが、少なくとも一時的なファイル保存場所として、ホストからいろいろなファイルを転送して実行することはできそうです。

ということでMicroPythonのRAMディスク実装を探してみたところ、そもそもRAMディスクはMicroPythonで記述できることが分かりました。
サンプルが以下のページの最後のほうにあります。

uos – basic “operating system” services — MicroPython 1.9.3 documentation

MicroPythonの実装に含まれるCライブラリの中にvfsというモジュールの実装があり、これを使うと任意のブロックデバイスを用いてFATファイルシステムを構成することができます。(ちなみにFATライブラリはChaNさんのFatFsモジュールに手を加えたoofatfsです。)
このvfsをMicroPythonの実装に組み込めば、RAMも含め様々なブロックデバイス上でFATファイルシステムがPythonコードで実現できます。

上記のサンプルのRAMディスクは、MicroPythonのbytearrayを使ってブロックデバイスを実現しています。以下のコードは、このサンプルに特定のブロックの内容を16進数で表示するdump_block()というメソッドを追加したものです。

Raspberry Piベアメタル版MicroPythonにvfsを組み込むのは割合簡単です。
vfsはuosモジュールから利用しますので、ESP8266などの実装からuosモジュールを持ってきます。
uosモジュールは、vfsなどのライブラリ関数へのポインタをモジュールの関数にマップしているだけなので、プラットフォームに合わせたコードを書く必要はほぼありません。
基本的な移植作業はこれだけです。(他にmpconfigport.hで適切なオプション指定やextern宣言、、fopenなどのMicroPython組み込み関数にvfsを参照させるたっめの記述を追加する必要があります。)

これで早速上記のRAMディスクのコードを動かしてみたのですが、最初はエラーで止まってしまいました。
mkfsでフォーマットしたRAMディスクをマウントしようとすると、ENODEVエラーになってしまいます。
コードを調べてみると、oofatfsライブラリが「ファイルシステムを見つけられない」というエラーを返しているようです。

ところが、同じコードをQEMU上で動かすと問題なく動作しました。
上記で追加したdump_block()を使って、両者のmkfsでフォーマットした後の先頭ブロックのデータを比較してみると、以下のように違いがありました。

QEMUでの実行結果(正常)

>>> bdev.dump_block(0)
eb fe 90 4d 53 44 4f 53 35 2e 30 00 02 01 01 00
01 00 02 32 00 f8 01 00 3f 00 ff 00 00 00 00 00
00 00 00 00 80 00 29 00 00 00 00 4e 4f 20 4e 41
4d 45 20 20 20 20 46 41 54 20 20 20 20 20 00 00

Raspberry Piでの実行結果(異常)

>>> bdev.dump_block(0)
eb fe 90 00 53 44 4f 4d 53 35 30 00 02 01 01 00
01 00 02 32 00 f8 01 00 3f 00 ff 00 00 00 00 00
00 00 00 00 80 00 29 00 4e 4f 20 00 41 4d 45 4e
20 20 20 20 41 54 20 46 00 00 20 20 00 20 00 00

何箇所か違いがありますが、特に44バイト目からの文字列が目立ちます。
ディスクフォーマットする関数f_mkfsの処理では、

mem_cpy(buf + BS_VolLab, "NO NAME    " "FAT     ", 19);

となっていますので、44バイト目から「NO.NAME….FAT…..」(「.」はスペース)と入っているべきなのですが、Raspberry Piでの実行結果は40バイト目から「NO. AMEN….AT.F .. .」(スペースは0x00)というようにデータが崩れて入っています。

これは、きっとnaumsさんも嵌っていたアンアラインドアクセスの問題だと見当が付きました。
実は少し前に、アンアラインドアクセスのサポートをなくしてもMicroPythonの動作には特に問題なかったため、サポートをなくした状態にしていました。しかし、今回追加したoofatfsでは問題が出るようです。

ソースコードではmem_cpyを呼んでおり、これは#defineでmemcpyを呼ぶようになっています。
MicroPythonでは、lib/libc/string0.cというファイルでアンアラインドアクセスに配慮したmemcpy関数を用意しているので、問題ないはずです。

ところが、コンパイル結果をディスアセンブルしてみると、以下のようにmemcpyコールは行われずにロード/ストアで実現されていました。

    3a00:   e284202b    add r2, r4, #43 ; 0x2b
3a04:   e2831010    add r1, r3, #16
3a08:   e4930004    ldr r0, [r3], #4
3a0c:   e1530001    cmp r3, r1
3a10:   e4820004    str r0, [r2], #4
3a14:   1afffffb    bne 3a08 

memcpyをコールせず、このようにしたほうがオーバーヘッドが小さいという最適化のようです。
この最適化を抑制できればいいのですが、最適化が出現する条件がよく分からないので、アンアラインドアクセスのサポートを有効にしました。
その結果、RAMディスクが動作するところまで確認できました。

RAMディスクをマウントした状態でuPyCraftから接続し、エディタにマンデルブロ集合のコードを入れて、DownloadAndRunボタンを押してみると、ちゃんとファイルがダウンロードされ動作しました。

その後、gccのオプションで「-mno-unaligned-access」を指定すると、上記の最適化を禁止できることが分かったので、現在はMakefileでこのオプションを指定し、アンアラインドアクセスのサポートを再度無効にしています。

コメント