NVMeドライバをLinux上でuioを用いて開発してみたが間に合わなかった

はじめに

いいですか。「開発してみました」というのは、「完成しました」ではないのです…。

「黙って現在の進捗を差し出せ!」という方はこちらをご参照ください。

github.com

この記事はなに

自作OSアドベントカレンダーの7日目として書かれた記事です。

17日を書こうと思ったら、すでに登録されている方がいらっしゃいましたので、2番目に好きな素数を選びました。

話は6か月前にさかのぼる…

6ヶ月ほど前でしたでしょうか。Liva師からこんな話が降って来たのは。

Liva師「hikaliumさん、うちの自作OS(Raphine)向けにNVMeのドライバとか書いてみたくない?」

hikalium「いいですね〜」

いいですねーと言ってしまったものの、私は実は本当のドライバというものを書いたことがありませんでした。

これでは、自作OS開発者失格ですね。

というわけで、とりあえずnvmeの仕様をざっと読んでみることにしました。

NVMeってなに?

Non-Volatile Memory Expressの略で、不揮発性メモリ(SSDとか)を高速に読み書きできるインターフェイスを提供するよ!というものです。やっぱり速いのは正義ですね。 最近はこの、NVMeのインターフェイスで通信するSSDもだいぶ増えてきていて、ノートPCにも内蔵されていたりします。 これは自作OSとしてはサポートするしかありませんね!

どれくらいすごいインターフェイスなのかは、検索するなりWikipediaでも眺めておいてください。 私たちが知りたいのはそれよりも下の、どうやって制御すればいいのか、というところですので。

というわけで、まずは仕様を読んでみました。

仕様はどんな感じ?

NVMeの仕組み自体は、そこまで難しいものではありません。

まずは、データ構造の話から。仕様書の4章をみてください。

Submission Queue / Completion Queue

NVMeのコントローラとコマンドやデータをやり取りするためには、Submission Queue / Completion Queueという、リングバッファのような構造を用います。この解説が、4.1に書いてあります。

Submission Queue(略してSQ)は、コントローラに対してコマンドを送るために使用します。たとえば、読み書きを行ったり、設定情報を読み書きしたりするときには、このSQに私たちがコマンドを書き込んであげるわけです。

Completion Queue(略してCQ)は、逆に、コントローラから「コマンドが完了したよー」もしくは「エラーがおきた!」ということを私たちに通知するために使用されます。

NVMeのすごいところは、これらのQueueのセットを、複数作成することができる、という点です。 これにより、複数のCPUコアに、Queueのセットを1つずつ持たせることなども可能になり、コア間でQueueのロックを取り合って性能が低下することを避けることができます。

さて、これらのQueueは、あくまでもコントローラーに対する「命令」と、その「結果」をやり取りするためのものです。 そのため、これらのQueueのエントリの大きさは、固定されています。では、例えばSSDに対して読み書きなどを行いたい場合、どのようにコントローラとの間でデータをやり取りするのでしょうか?

Physical Region Page Entry / List

ということで、コントローラーとの間で大きなデータをやり取りする際に用いられるのが、Physical Region Page(PRP)エントリおよびリストです。詳細は、仕様書の4.3を確認してください。

PRPエントリは、物理メモリの場所を指し示すポインタです。そのため、64bitの幅を持っています。 これが指すメモリ領域の大きさはコントローラのレジスタCC.MPSで定義します。つまり、PRPエントリひとつあたりが指すメモリ領域は固定です。最低でも、4KBの大きさがあります。(ページングみたいですね。)

コントローラに対して送るコマンドの構造体には、このPRPエントリを書ける場所が2箇所ありますが、4*2=8KBしか一度に転送できない、とかいうわけではありません。もっと大きなメモリ空間を指定したい場合に用いられる構造が、PRP Listです。

PRP List は、PRPエントリが指し示すメモリ領域に、PRPエントリを敷き詰めることで構成されます。 コマンドのPRPフィールドに、PRPエントリを指定するのか、PRPリストを指定するのかは、コマンドによって異なるようです。

ちなみに、PRP List / Entry以外にも、Scatter Gather Listとかいうかっこいい構造を使うこともできるのですが、今回の実装では使用していないので省略します。(知りたい人は4.4を読んでください。)

Namespace

NVMeは、他のストレージインターフェイスと異なり、ストレージ空間をNamespaceというパーティションのようなものに分割することができます。仕様書の1.4 Theory of Operationには、図解入りでその説明が書いてありますが、パーティションのようなものだと思っておいていただければOKです。 この、Namespaceを区別するIDを、Namespace ID (NSID)と呼びます。16bit幅の値をもち、0は常にInvalidで、0xffffffffはBroadcast Valueといい、全Namespaceを表すIDとして使われます。

話はいいからさっさとドライバを書け

こんな感じで、いろいろと情報をまとめたのはよかったのですが、セキュリティキャンプでCPUの作り方を教えたり、大学の学園祭の準備をしていたり、CODEBLUEでNOCをしたりしていたら、いつのまにか冬になってしまいました。

私は、Liva師が非常に進捗を重視する方であると知っていたので、きっと進捗を出していない私はもう見向きもされないのだろうと思い、ひっそりと生きていこうと思っていたのですが、そんなときにLiva師から神の手が差し伸べられたのです。

Liva師「NVMeのドライバ、まだやる気があるなら、ちょっとおもしろいことをしてみようと思うので、いかがでしょうか。」

Liva師「uioというものがあってね…。それを使うと、Linuxでドライバが簡単に書けちゃうんだ。」

なんですって!これは書くしかありませんね。

uioはすごいぞ

uioというのは、Userspace IOの略で、なんとユーザー空間からIOを制御できるというスグレモノのカーネルモジュールです。

ドライバを書いたことがある人ならわかると思いますが、ユーザーランドでドライバを書けるということは本当にすごいことです(技術的にではなく心理的に)。 だって、

  • バグってもカーネルパニックになりにくい(たいていSEGVで済む)
  • いろいろ面倒をみてくれる
  • printfができる!!!
  • gdbがつかえる!!!

こんなことがドライバの開発でできるんですよ!! 画面の色やキーボードのLEDを変化させたり、がんばってシリアルポートを制御したりして苦労してデバッグをする時代は終わったんです! これからは、いくらでもprintfしていいんです!gdbであの変数の値を見ちゃってもいいんです。最高でしょ?

というわけで、まずはuioの使い方を知るところから実装は始まりました。

uioの使用例 〜Liva師のXHCIドライバ〜

心優しき、かのLiva師は、uioを用いてXHCIを実装してくださいました。

github.com

これと、上のドキュメントを読むのがもっとも正確ですが、要点をかいつまんで説明しましょう。

まずは、コマンドでPCIバイスの制御をuioに移します。

  • sudo modprobe uio_pci_genericを実行して、uio_pci_genericカーネルモジュールをロードする。
  • 制御したいデバイスのベンダIDとデバイスIDをスペース区切りで/sys/bus/pci/drivers/uio_pci_generic/new_idに書き込む。
  • 制御したいデバイスを、デフォルトのドライバからunbindする(/sys/bus/pci/drivers/$(TARGET_KERNEL_DRIVER)/unbindにバス番号を書き込む)
  • 制御したいデバイスを、uio_pci_genericドライバにbindする(/sys/bus/pci/drivers/uio_pci_generic/bindにバス番号を書き込む)

これが終わると、なんとプログラムからPCIレジスタ空間がファイル(/sys/class/uio/uio0など。最後の数字は複数attachしている場合は違う。)として見えるようになります。

使い方の簡単な例がドキュメントにありますが、簡単にいうと

  • /dev/uio0をreadすると、デバイスからの割り込みがかかるまでブロックされる。
  • /sys/class/uio/uio0/device/configmmapすれば、PCIレジスタ空間がメモリにマップされて触れるようになる
  • /sys/class/uio/uio0/device/resource0mmapすれば、BAR0のレジスタ空間を触れる(resource1にすればBAR1も読める)

これだけです。ここまで触れれば、PCIバイスのドライバは簡単に書けるはずです。 どうですかuio! みなさんも使ってみたくなりましたか?快適なドライバ開発ライフを私たちと一緒に送りませんか?

で、進捗どうですか

さて、ただいま12/7の23:40です。

私がドライバを本格的に書き始めたのは、12/6の朝なのですが...。 さすがに、NVMeのSSDに対する読み書きを実行するまでは間に合いませんでした。ごめんなさい。 年内には完成させますので、お楽しみに!

f:id:hikalium:20171207234823p:plain

ちなみに現在は、Identifyというコマンドをコントローラに送信して、その返答の割り込みを処理するところまで書きました。しかも実機で動きます。というか逆にVirtualboxで動きません。原因究明中です…。

途中でuio_pci_genericがうまく動作しなくなったり、Virtualboxでは割り込みが再現しない(多分私のコードのミスだけど)といった問題に悩まされましたが、約1日でNVMeさんとお話をできるレベルにはなったわけです。実機だけorカーネルランドで開発していたら、1日ではここまで来れなかったでしょう。やはりuioはすごいですね!

ちなみに、uioで書かれたドライバを、Raphineという自作OSに容易に移植できるよう、クラスなどを整備する予定ですので、今みなさんがuioでドライバを書けば、Raphineに取り込まれるかもしれませんよ!ぜひやってみてください!

まとめ

  • NVMeのドライバは未完成
  • uioは便利だからみんな使おう!!!というかドライバ書こう!
  • やっぱり低レイヤは最高だね!

VirtualBox上のCentOS7でQuartus Primeを動かすためのVagrant Boxの使い方

タイトルが長すぎますね。反省。

作ったもの

概要

Quartus PrimeはMac向けのバイナリはない。そのため、Macで無理やり開発をするためには、VM上のWindowsLinuxにインストールする必要がある。

VM上のCentOS7にQuartus Primeを入れるためには、いろいろなパッケージやVirtualBox Guest Add-on を入れたりする必要がある。 これが面倒なので、それらの事前準備をすべて終えた状態のBoxを作成した。

Quartus Primeはインストールされていないので、以下の手順でインストールする必要がある。(容量とライセンスの関係から入れなかった。)

検証環境

手順

準備

事前に Oracle VM VirtualBoxVagrant by HashiCorp をインストールしておいてください。

また、事前にQuartus Prime Lite のLinux版を http://dl.altera.com/?edition=lite からダウンロードしておいてください。

さらに、以下のコマンドを実行して、Vagrantにscpのpluginをインストールしてください。

vagrant plugin install vagrant-scp

VMを作成・起動

適当なディレクトリで以下を実行(初回は仮想マシンイメージをダウンロードするので、ネットワーク接続のよいところで実行してください!)

git clone https://github.com/hikalium/centos-quartus.git
cd centos-quartus
vagrant up

QuartusのパッケージをVM内に転送する

vagrant scp <Quartus-lite-xxx.tarのパス> /home/vagrant/

ちょっと時間がかかります。

Quartusを展開してインストー

vagrant ssh
  • 以下はsshで接続したVM内での作業
tar -xvf <Quartus-lite-xxx.tar のファイル名>
./setup.sh
  • とりあえずEnterを連打して、ライセンスの条文を読む。
  • Do you accept ~?と出てきたら、yと入力してEnter
  • そのあとはすべてEnterでOK
  • インストールが開始する(少し時間がかかる)
  • インストールが終了したら、 「Launch Quartus ~ ?」だけはnと入力し、それ以外はyもしくは単にEnterでOK
  • VMGUI画面のデスクトップに、Quartusが追加されていたら成功です!

Verilog-HDLで算術右シフトを書く方法

結論

以下のように書けばよい。

重要な点は、signed>>>である。

何をどう勘違いしていたのか

算術右シフトをVerilog-HDLで書けるのか調べていたところ、以下の記事にぶつかった。

d.hatena.ne.jp

この記事では、>>>を使用すれば、算術シフト、>>を使用すれば論理シフトになるかのように読み取れる。

しかし、>>>を使用しても、私の手元では算術シフトにならず、論理シフトとして扱われていた。

当初、私はこれをエミュレータのバグではないかと疑っていた(icarus-verilogを使っていたため)。

しかし、Quartus付属のModelSimでも同様の動作であったため、これは根本的にコードが間違っているということに気づいた。

そして、以下のような記事を発見した。

FPGAの部屋 Verilog HDL で unsigned, signed の演算をする1

やはりいつも最後にはmarseeさんの情報にたどり着く。この記事には、算術右シフトのことについては触れられていなかったが、もしかしてと思ってsignedとつけたら、うまく動作した。

結論

  • Verilog-HDLでは標準で符号なしなので、符号ありとして扱いたい場合はsignedをつける
    • 符号なしの値に対して>>>を使ったら警告くらい出してくれてもいいのに…(気づかなかっただけか?)
  • 私は無知すぎる
  • エミュレーターを疑う前に自分の頭を疑うべき
  • やはり http://marsee101.blog19.fc2.com/ の情報は有用なのでもっと読むべき

CentOS7でModelSimが起動しない問題の解決策

結論

以下のライブラリが不足しているので入れればよい。

sudo yum install libX11-devel.i686 libXext-devel.i686 libXft-devel.i686 ncurses-libs.i686

現象

Tools -> Run Simulation Tool -> RTL Simulationを選択すると、以下のエラーメッセージが出て起動しない。

ライセンス関係やパス、環境変数の問題に見えるが、実際はそうではなかった。

原因追求

エラーメッセージに記載のログファイルを開いたところ、modelsim_ase/bin/vsimを実行しようとして落ちていることまでわかった。そこで直接これを実行したところ、ライブラリが見つからない旨表示された。

指定されたライブラリを以下のコマンドで探索した。

yum whatprovides */libname

見つかったパッケージを.i686をつけてinstallし、再度vsimを起動させることを数回繰り返した結果、起動するようになった。

結論

  • yum whatprovides */libnameはめっちゃ便利
  • レポートは早めにやろう

QuartusのプロジェクトをGitで管理する

意外にも日本語の情報が少なかったので書いた。

QuartusのようなIDEが作ってくれるプロジェクトをGitで管理する場合、問題になるのは、どこまでが自動生成のファイルで、どこからが自動生成ではないファイルなのか、というところである。

端的に言うと、.gitignoreをどう書けばいいのか、という情報を知りたいわけだ。

quartus project gitで検索をかけたところ、Alteraのフォーラムなどで同じことを考えている人がたくさんいることは発見した。

また、altera wiki に以下のようなページがあった。

Version Control - Altera Wiki

さらに、GitHubで良さげな.gitignoreを書いている人も見つけた。

github.com

というわけで、上の二つの資料を参考に.gitignoreを書けば幸せになれそうだ。

最初の一歩:FPGAでLチカ(2)

はじめに

この記事は以下の記事の続きです。

hikalium.hatenablog.jp

Verilog HDLのソースファイルを作成しよう

というわけで、次はついにVerilog HDLのソースを書いてゆきます。

File->New のメニューからVerilog HDL Fileを選択します。

f:id:hikalium:20170528160243p:plain

すると、Verilog1.vというファイルが開かれた状態になります。

f:id:hikalium:20170528160255p:plain

とりあえず、ここに以下のソースを打ち込んでみてください。

できましたか?

VirtualBoxを使っている方は、デバイス->クリップボードの共有->双方向のチェックを入れると、ホストOSでコピーしたものを貼り付けられるので覚えておいてください。

では、このファイルを保存します。Ctrl-SもしくはFile->Saveを押してください。

f:id:hikalium:20170528160312p:plain

すると、ファイルの保存先を聞かれます。保存先ディレクトリがプロジェクトのルートディレクトリ、ファイル名がled.vとなっていることを確認して、Saveを押してください。

これでソースファイルはできあがりました!

とりあえずコンパイルしてみよう

では、今打ち込んだソースをコンパイルしてみます。

Ctrl-LもしくはProcessing->Start Compilationを押してください。

f:id:hikalium:20170528160332p:plain

すると、左下のTasksというところの進捗状況が進んで、下のMessagesウィンドウに色々と流れてきます。

全部うまくいくと、こんな感じになります。

f:id:hikalium:20170528160344p:plain

次は、ピン割り当てをしましょう。

ピン割り当てをする

先のコンパイルで、FPGA内に論理回路を構成することはできました。しかし、これだけでは使い物になりません。

というのも、FPGAにはたくさんのピンがあるわけですが、どのピンに論理回路のどの入出力を割り当てるのか、Quartusさんは知りません。なので、内部の論理回路は正しくても、FPGAの外の世界と正しくつながっているかは、神のみぞ知る、といった状態です。それでは困りますよね。

今回の場合は、2つのLED(led1, led2)と1つのスイッチ(btn)、そして1つのクロック信号(clk)が必要ですから、その4つの信号を適切なピンに割り当てなければいけません。 そうしないと、意図したLEDが光らなかったり、スイッチだと思ったらLEDだったり、下手をするとショートして回路が壊れてしまったりするわけです。

ちなみに、この指定をしないでコンパイルした場合(さっきのコンパイル)は、Quartusさんが都合のいいように適当にピン配置を決めてくれてしまいます。 では早速、Quartusさんに適切なピン配置を教えてあげましょう。

Assignments->Pin Plannerを開いてください。

f:id:hikalium:20170528160519p:plain

すると、このような画面が出てきます。上半分にでている大きな正方形は、FPGAのイメージです。

そして、下の方にある表が、ピン配置の表です。Node Nameが、先ほど打ち込んだソース上での信号名を指しています。そして、今回設定するのはLocationの列です。

ここで、今回のボードにおける、各ピンとその先につながっているデバイスの表を見てみましょう。

ピン番号 つながっているもの
23 クロック(50MHz)
88 プッシュスイッチ1
87 LED1
86 LED2

なるほど。では早速これをPin Plannerで設定してゆきましょう!

といっても、作業は非常に単純です。変更したいLocationのセルをダブルクリックして、横に出てくる下矢印を選択すると、選択可能な信号の一覧が出てきます。この中から、適切な信号を選ぶだけです。

f:id:hikalium:20170528160550p:plain

すべて設定すると、こんな感じになります。できあがったら、Pin Plannerは閉じてしまってかまいません。

f:id:hikalium:20170528160603p:plain

再度コンパイルする

元の画面に戻ってくると、左側のTasksウィンドウに?がたくさん浮かんでいることに気づきます。これは、ピン配置を変更したため、配置配線(Place & Route)からやり直す必要があるためです。

というわけで、もう一回コンパイルしてあげましょう。

うまくいけば、また全て緑のチェックマークになります。

次はついに書き込みです!

FPGAに書き込む!

ここまでの手順で、FPGAに書き込むデータまでは完成しました!もうあとは書き込むだけです。

Tools->Programmerを開いてください。

f:id:hikalium:20170528160648p:plain

この段階で、USB BlasterをPCに接続し、FPGAボードの電源もオンにしておきましょう。

Virtual Boxを使っている方は、デバイス->USB->Altera USB Blasterにチェックを入れておきましょう。これにより、仮想マシンにUSB Blasterが接続されます。

次に、左上のHardware Setup ...ボタンを押してください。ここで、どの書き込み器を使用するか選択します。

f:id:hikalium:20170528160708p:plain

Available hardware items のところに、USB-Blasterという表示があれば、その名前をダブルクリックしてください。すると、その書き込み器が選択された状態になります。

f:id:hikalium:20170528160802p:plain

何も出てこない場合は、接続やVirtualBoxの設定等を確認し、Hardware Setupのウィンドウを再度開いたり、根気よく試してみてください。

どうしてもだめな場合は、書き込み器が壊れている場合があります(私も壊れたUSB-Blasterに当たってしまって困り果てました…)。

さて、うまくいったら、Hardware Setupのウィンドウは閉じてOKです。

次に、左にあるAuto Detectというボタンを押しましょう。

ここでも、よくコケます。根気よくいきましょう…。

うまくいくと、エラーが出ずにAuto Detectが終了します。

そうしたら、左上のStartというボタンを押して、書き込みましょう!

f:id:hikalium:20170528160733p:plain

うまくいけば、右上のProgressが100%(Successful)になり、ボード上ではあなたの書いたプログラムが動いています!!!

ちなみに、こんな感じに動けばOKです。

おつかれさまでした!これでもうなんでも作れますね!(さすがにきつい…。)