2018年03月02日

Raspberry Pi用ベアメタルUSBデバイスドライバ「csud」の調査(3)

前回の続きです。
以前にリストアップした中でちょっと触れていたcsudの64bit対応の派生版について、もう少し詳しく調べてみました。

下記のリポジトリにはコンパイル済みのバイナリがあり、マウスとキーボードの値が画面表示されます。

USB peripherals in bare metal? - Raspberry Pi Forums

Raspberry-Pi/Arm32_64_USB at master ・ LdB-ECM/Raspberry-Pi

以前調べたときはこのバイナリをそのまま動かして動作を確認しただけでした。
今回は、この中身を調べて、シリアルポートにキーコードを出力するサンプルを作ってみました。

このドライバは、完全に新規なドライバというわけではなく、csudを書き直して

・RPi2 / RPi3(32bit) / RPi3(64bit) 対応
・主なファイルを1つだけに

したもののようです。

実際、内部を見ると関数や全体の構造はかなり似ています。
一方で、csudのコードがそのまま残っているわけでもなく、大幅に整理されています。
csudでは個別のファイルに分かれていたUSBハブやHIDデバイスのためのコードも含まれています。
主なコードは「rpi-USB.[ch]」だけで、合わせて4300行足らずです。

csudよりもさらに行数は減っていますが、そのためか、USBキーボードなど個々のデバイスのサポート用関数などは減っています。

csudで作った、押されたキーのキーコードをシリアルポートに書き出すプログラムをrpi-USBで書き直したものを下記に置いておきます。

bare_matal_rpi_zero/usb_kbd2 at master ・ boochow/bare_matal_rpi_zero

Raspberry Pi Zero Wで動かす際にcsudにあった不具合は、そのまま引き継がれているようで、キーボードはUSBハブ経由で接続する必要があります。
ラベル:USB
posted by boochow at 01:21| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年02月25日

Raspberry Pi用ベアメタルUSBデバイスドライバ「csud」の調査(2)

baremetal-usb-keyboard1.png

昨日に引き続き、Raspberry PiのベアメタルUSBデバイスドライバ「csud」のRPi Zero W対応をしています。
昨日はUSBキーボードがRPi Zeroに直結された場合に、USB2.0のSplitパケットを送信しないように修正しましたが、これだけですとUSBキーボードは認識されるものの、しばらく時間がたつとキーボードに反応しなくなってしまっていました。

プロトコルアナライザで通信の様子を見てみると、ホスト側からのパケットに対してデバイスがNAKを返しているのに、ホスト側は立て続けにパケットを送りつけています。
コードを見てみると、この送信処理はHcdChannelSendWaitOneという関数の中で行われています。
デバイス側の応答に応じた処理の部分が以下のようになっていました。
if (Host->Channel[channel].SplitControl.SplitEnable) {
if (Host->Channel[channel].Interrupt.Acknowledgement) {

for (tries = 0; tries < 3; tries++) {
/* Complete Splitパケットの送信処理 */
}

if (tries == 3) {
MicroDelay(25000);
continue;
} else if (Host->Channel[channel].Interrupt.NegativeAcknowledgement) {
globalTries--;
MicroDelay(25000);
continue;
} else if (Host->Channel[channel].Interrupt.TransactionError) {
MicroDelay(25000);
continue;
}
HcdChannelInterruptToError(device, Host->Channel[channel].Interrupt, false);
} else if (Host->Channel[channel].Interrupt.NegativeAcknowledgement) {
globalTries--;
MicroDelay(25000);
continue;
} else if (Host->Channel[channel].Interrupt.TransactionError) {
MicroDelay(25000);
continue;
}
} else {
HcdChannelInterruptToError(device, Host->Channel[channel].Interrupt, !Host->Channel[channel].SplitControl.SplitEnable);
}

一番外側のifは、Splitパケットが有効ならば、という条件式ですが、then節の内部の処理はデバイスの速度がHigh Speedでない場合に実行すべき処理に見えます。
オリジナルのコードでは、「Splitパケットが有効」と「デバイスの速度がHigh Speedでない」は常に同値だったのですが、昨日行った変更ではSplitを有効にする条件を増やしましたので、両者は同値ではなくなりました。

then節の中で、Splitパケットが有効なときだけ実行すべき部分は、
if (Host->Channel[channel].Interrupt.Acknowledgement) {
}
の内部です。
ここではComplete Splitパケットの送信と、その送信によってエラーが生じた場合の処理を行っています。
その次のelse節以降は、Split制御が無効の場合も実行する必要があります。

そのため、

・外側のifは「if (pipe->Speed != High)」
・内側のifは「if ((Host->Channel[channel].Interrupt.Acknowledgement) && (Host->Channel[channel].SplitControl.SplitEnable))」

へと条件式を変更しました。
これにより、どうやらキーボードが無事に動作するようになりました。

ただ、キーボードを抜くと動作がおかしくなります。
オリジナルのコードでRoot Hubからデバイスを除去することが想定されていないためです。
RPi Zero WにUSBハブを接続している場合は、ハブからキーボードを抜くのは大丈夫ですが、ハブのほうを抜いてしまうと異常動作になります。
この対策については、USBハブ周辺のコードを調べてみる必要があります。

また、時間が経過するとハングアップすることもあるので、まだ実用的なレベルにするにはだいぶ手間がかかりそうです。
ラベル:USB
posted by boochow at 19:50| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年02月24日

Raspberry Pi用ベアメタルUSBデバイスドライバ「csud」の調査(1)

P2080864.jpg

引き続きRaspberry PiのベアメタルプログラミングでUSBデバイスを動かす調査です。
前回、Pi Zero WにUSBキーボードを直結するとcsudデバイスドライバがうまく動作しない原因は、本来送るべきでないSplitパケットが送られているためらしいということが分かりました。

Splitパケットが送られている箇所を修正するため、csudの中身を覗いてみました。
csudのソースツリーは以下のような構成です。
csud/source/
├── configuration.c
├── device
│   ├── hid
│   │   ├── hid.c
│   │   ├── keyboard.c
│   │   └── mouse.c
│   └── hub.c
├── hcd
│   └── dwc
│   ├── designware20.c
│   └── roothub.c
├── platform
│   ├── arm
│   │   ├── armv6.c
│   │   └── broadcom2835.c
│   └── platform.c
└── usbd
└── usbd.c

このうち、コントローラチップの制御を行っているのがdesignware20.cで、USBデバイス一般の処理がusbd.c、プラットホーム依存部分がplatformディレクトリ、それ以外が各種デバイスの処理となっています。

hcd/dwc/roothub.cで実装されるroothubは、コントローラ直下に存在する(仮想的な?)USBハブです。今回問題となっているのは、roothubにUSBキーボードを接続した場合の動作ということになります。

コードの流れを追ってみると、Raspberry Pi Zero Wにキーボードを接続したときの関数コールは概ね以下のようになっているようです。
UsbCheckForChange
→HubCheckForChange
 →HubCheckConnection
  →HubPortConnectionChanged
   →HubPortGetStatus
    UsbAttachDevice
    →UsbReadDeviceDescriptor
     →UsbGetDescriptor
      →UsbControlMessage
       →HcdSumbitControlMessage
        →HcdChannelSendWait
         →HcdPrepareChannel
          HcdChannelSendWaitOne

SPLITパケットの送受信の有無はhcd/dwc/designware20.cで定義されているHcdPrepareChannelで設定されていました。
	if ((pipe->Speed != High) {
Host->Channel[channel].SplitControl.SplitEnable = true;

のようにフラグを設定しており、これがSplitパケットの送信をオンにしています。
pipe->Speedは、デバイスの速度に設定されています。

前回も書きましたが、この処理はRaspberry Pi Model 1では正しいです。
Model 1ではオンボードのLAN9512チップがUSB High Speed Hubになっていますので、root Hubは下図(A)や(B)のように、HS Hubとだけ通信していると想定できるからです。
rpi-usb-hs-fs-ls.png


一方、LAN9512チップを搭載していないZeroシリーズやModel Aシリーズ、Compute Moduleでは、図(C)のパターンが生じます。
速度はハードウェア側で自動調整されますが、上記のロジックだとこのパターンでもSplitEnableがtrueになり、Splitパケットが送信されてしまいます。
従って、図の3つのパターンのうち(B)だけがSplitパケットを送信するようにロジックを変更すれば良いということになります。

これに関して、ネットを調べていたところ、FreeBSD用のデバイスドライバで同様の問題が生じて解決されていることが分かりました。

[base] Diff of /head/sys/dev/usb/controller/dwc_otg.c

このコードでは dwc_otg_uses_splitという関数が定義されています。関数の中身は
459	static uint8_t
460 dwc_otg_uses_split(struct usb_device *udev)
461 {
462 /*
463 * When a LOW or FULL speed device is connected directly to
464 * the USB port we don't use split transactions:
465 */
466 return (udev->speed != USB_SPEED_HIGH &&
467 udev->parent_hs_hub != NULL &&
468 udev->parent_hs_hub->parent_hub != NULL);
469 }
となっています。この()内を翻訳すると、

・デバイスの速度がHigh Speedでない
・デバイスの上位のHubが存在し、かつHigh Speedである
・デバイスの上位のHubのさらに上位にHubが存在する

を全て満たす場合にSplitを有効にする、ということになります。
これなら、(B)のとき有効になり、(C)のとき無効になります(上位の上位にHubが無いため)ので、うまく動作しそうです。

同様のロジックをcsudに適用すると、前述のHcdPrepareChannel内での処理を以下のように変更することになります。
    if ((pipe->Speed != High) && (device->Parent != NULL) && (device->Parent->Speed == High) && (device->Parent->Parent != NULL)) {
Host->Channel[channel].SplitControl.SplitEnable = true;


このコードを試してみたところ、思ったとおりUSBキーボードが認識され、キーボードからの入力も行えました。
しかし今度は
・キーボードを抜いたときの処理が異常(切断と接続のループになる)
・時間が経過するとキーボードを見失ってしまう
といった問題が出ました。

前者は図(A)や(B)では生じない状況についての異常動作なので、この状況に対応するための実装が必要と思われます。
後者については、上記のFreeBSDのドライバではデバイスの速度に応じてパケット送信間隔を調整するコードが入っていましたので、同様の処理を行う必要がありそうです。
ラベル:USB
posted by boochow at 16:05| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年02月18日

Raspberry PiのベアメタルUSBドライバの信号を観測してみた

usbsignal2.jpg

以前調査したように、Raspberry PiのUSBコントローラのドライバには何種類かあり、最もシンプルな「csud」はベアメタルで使うには良さそうですが、Raspberry Pi Zero WではUSB Hubを使わないとデバイスが接続できないという問題がありました。

そこで、昨日作った治具

(A) Raspberry Pi Zero W → 治具 → USBキーボード
(B) Raspberry Pi Zero W → USBハブ → 治具 →  USBキーボード

のそれぞれでcsudを動作させてUSB信号を観測してみたところ、どうやら原因が解ったような気がします。

エラーはデバイスを接続した時点で初期化に失敗するというもので、以下のリンク先の記事にあるSETUPステージで起こります。

USBの基礎知識 ――パケットのフォーマットからプロトコルの詳細まで|Tech Village (テックビレッジ) / CQ出版株式会社

通信内容を(A)と(B)で比較してみたところ、(A)では(B)にはないエラーパケットが観測されました。
(B)では、エラーパケットはフィルタされてUSBキーボードへは送られていませんでした。

usbpacket.png

Error Packetを除けば(A)と(B)の違いはありません。
このパケットをUSBキーボードが解釈できないため、初期化が失敗しているようです。

このError Packetの中身を見てみると、以下のように規則正しい信号になっています。
つまり、ノイズ等によるエラーパケットではありません。
usb-split.png

出現位置も決まっていて、
00000001 00011110 10000000 00000001 00010101
SETUP DATA0 ACK
00000001 00011110 10000001 00000001 00001110
という具合に、SETUPの前後にエラーパケットが入っています。

1バイト目はSYNC、2バイト目はPID、最後はCRCというUSBパケットの形式に沿っていますので、エラーではなくて何かの信号だろうと思われましたので、調べてみたところ、これはUSB2.0で導入されたSplitパケット(PID=0001)だと解りました。
Error Packetと表示されていたのは、解析ソフトがUSB1.1(Low Speed/Full Speed)にしか対応していないためのようです。

USB Protocol: Types of USB Packets and USB Transfers | EngineersGarage

High Speed(480Mbps)で行うUSBハブとの通信を、Start Split(SSPLIT)とComplete Split(CSPLIT)で挟むことにより、USBハブ下部の低速なデバイスとの通信もホスト-USBハブ間ではHigh Speedで行えるようにする機能だそうです。

Split Transaction | ルネサス エレクトロニクス

USB規格書を見ると、SSPLIT、CSPLITは以下のようなフォーマットになっていました。
usb-split1.png

Hub AddressとPortは左のビットが低位ビット、SCは0ならStart、1ならCompleteです。
SとEは組で、High SpeedとFull Speedのデータ送信順を示すようです。
usb-split2.png

ETはEndpointのType、UはUnusedです。
usb-split3.png


この情報を元に、上記のError Packetの中身を見てみると、SETUPの前に送られているパケットがHub address = 1、StartSplit、Port = 0、High Speed Dataが先頭、エンドポイントタイプはControl、というパケットであることがわかります。
同様に、SETUPの後に送られているのは上記に対応するCompleteSplitパケットとなります。


本来、USBハブとの通信だけに使用するはずのSplitですが、csudドライバでは本体に直接接続されるデバイスでは常に使用されるようです。
これはcsudドライバが初代Raspberry Piを前提としているためと思われます。
初代Raspberry PiはオンボードにLAN9512チップが搭載されています。
このチップはEthernet兼USBハブになっていますので、初代Raspberry PiではBCM2835の下部には必ずUSBハブが接続されていました。
rpi1usb.png

逆にUSBハブが無いモデルはEthernetポートが無いモデル、すなわちModelA、A+、Zero、Zero W、Compute Moduleです。
これらのモデルにcsudを使ったソフトウェアを載せて、USBハブ抜きで直接デバイスを接続しようとした場合、上記の問題が起こると思われます。

これらのモデルでcsudを動かすには、ポートにUSBハブを接続した場合とそれ以外のデバイスを接続した場合で処理を分けるように、コードを追加する必要があります。
csudの中身をもう少し調べて検討してみようと思います。
ラベル:USB
posted by boochow at 10:54| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年02月12日

Raspberry PiベアメタルプログラミングでのUSBキーボード入力の調査

以前、Raspberry PiベアメタルでMicroPythonの調査をしたときに
ステップとしてはまずUARTからREPLを叩けるようにし、そのあと画面出力、次にUSBへと進んでいくべきでしょう。

と書きましたが、UARTからREPLを叩くところまでは一応できていますので、今回はUSBキーボード入力についてまとめておきます。

まず、Raspberry PiにはUSBホストコントローラが搭載されていますが、このコントローラはSynopsys社のIPコアを利用していることがマニュアルに書かれています。
このマニュアルに記載されているのは、Synopsysのコアとの差分のところだけで、それ以外はSynopsysのドキュメントを参照してくれ、となっています。

SynopsysのドキュメントはNDAを結ばないと入手できないもので、公開されているドキュメントはありません

デバイスドライバとしては、現在

・Synopsysが提供しているソースコードからの派生(Raspbian等のLinux系OS)
・ケンブリッジ大学の「Baking Pi」チュートリアルに含まれている「csud」ドライバ
・上記2つを参考にした「Circle OS」用のドライバ
・上記3つを参考にした「ultibo OS」用のドライバ

が公開されています。


まず、Synopsys版ドライバについては、以下のフォーラム投稿記事が詳しいです。

RISC OS Open: Forum: USB stack on Raspberry Pi

Synopsys版をベースにしたLinux用のデバイスドライバのコードはこちらにあります。

linux/drivers/usb/host at rpi-patches ・ raspberrypi/linux

他のドライバもそうですが、IPコアの名称が「DesignWare Hi-Speed USB 2.0 On-the-Go Controller」なので、dwc(おそらくDesignWare Controller)というプレフィックスがファイル名に付いています。

Synopsys版の再配布条件はかなり緩いものになっており、ソースコード(改変可)を再配布する場合はソースコードに記されているライセンス条件を変更しないことが条件になっています。
ドキュメントはソースコードからdoxygenで生成したものがあります。
以下の解説を見るとThreadが必要であるらしく、ベアメタル環境へ移植するにはその部分をどうするかが課題になりそうです。

Synopsys DWC Portability and Common Library for UWB

派生の一つにxinu-osのドライバがあります。(未調査)
ライセンスはBSD3となっています。

xinu/system/platforms/arm-rpi at master ・ xinu-os/xinu

Linux版よりシンプルになっているように思われますが、OSの一部なのでライブラリとして切り離して扱えるかどうかは分かりません。


csudはAlex Chadwickという方が作成したライブラリです。
ライセンスはMITライセンスです。
Baking PiチュートリアルのLesson 10、Lesson 11がUSBキーボード入力を扱っています。
ライブラリの使い方も詳しく解説されています。
このチュートリアルはプログラミング言語としてアセンブラを使っていますが、ライブラリはCで書かれています。

チュートリアルのダウンロードページでサンプルごとダウンロードできるほか、ライブラリ単体でも以下のGitHubで公開されています。

Chadderz121/csud: Chadderz's Simple USB Driver for Raspberry Pi

チュートリアルは平易な内容で、非常に読みやすいです。
Lesson 10の内容をCで書いてみると、キーボードを読むループはこんな感じになります。

キー入力結果をUARTへ出力するRaspberry Pi Zero W用のコード全体は以下で公開しています。

bare_matal_rpi_zero/usb_kbd at master ・ boochow/bare_matal_rpi_zero

ただ、このドライバは簡易版のドライバで、バルク転送やインタラプト転送がサポートされていません。
また、手持ちのキーボードをいくつか試してみたところ、Raspberry Pi Zero WのUSBポートに直接接続すると
HCD: Request to New Device (Not Ready) has failed 3 times.
HCD: Transfer was not acknowledged.
HCD: Transaction error in transfer.
HCD: Control message to 0x6380008: 80060001 00001200.
HCD: Request to New Device (Not Ready) failed.
HCD: Could not send SETUP to New Device (Not Ready).
USBD: Verifying New Device (Not Ready) is still connected.
USBD: Yes, New Device (Not Ready) is still connected.
USBD: Failed to get descriptor 0x1:0x0 for device New Device (Not Ready). Result 0xfffffffc.
USBD: Failed to read device descriptor for 2.
HUB: Could not connect to new device in USB Root Hub.Port1. Disabling.
USBD: Deallocating device 2: New Device (Not Ready).
というエラーメッセージが表示されて、デバイスを認識させることができませんでした。
しかし、USBハブを介在させると普通に認識できます。
(Zero W以外ではまだ試していません。)

同様の現象がRaspberry Piフォーラムでも報告されていますが、解決方法は不明です。
csudの64bit対応の派生版もありますが、上記のデバイス認識の問題は変化ありませんでした。

Raspberry-Pi/Arm32_64_USB at master ・ LdB-ECM/Raspberry-Pi

csudはコードは全体でも5000行程度、ドライバの中核であるsource/hcd/dwc/designware20.cは800行あまりと、かなりコンパクトにまとまっています。

オリジナルでは実装されていないバルク転送モードについては、csudに機能追加された下記のフォークが存在しています(未調査)。
バルク転送モードとインタラプト転送モードはよく似ているらしいので、もしかしたらインタラプト転送モードも追加可能かもしれません。

yeqingyan/csud: Chadderz's Simple USB Driver for Raspberry Pi

なお、USBの転送モードの違いについては、USBの技術解説がいくつかありますのでリンクしておきます。

USBの解説(picfun、PDFファイル)
USB通信プログラミングテクニック(picfun)
USBの基礎知識 ――パケットのフォーマットからプロトコルの詳細まで|Tech Village (テックビレッジ) / CQ出版株式会社


次に、Circle OS用のドライバ「USPi」です。
これはCircle OSのドライバ部分を切り出して単独でライブラリとしたもので、以下で公開されています。

rsta2/uspi: A bare metal USB driver for Raspberry Pi written in C

Introducing USPi - A bare metal USB driver written in C - Raspberry Pi Forums

ライブラリのREADMEには、Synopsys版とCSUDを参考にしたと記載があります。
ライセンスはGPLv3となっています。

このドライバもサンプルにキーボード入力がありますので、動かしてみました。(動作にはHDMI接続が必要です。)
こちらはCSUDと違い、USB Hubを介さずともキーボードを認識することができました。
使用方法も、キーボード入力に対するコールバック関数を登録するだけで、かなり簡単です。
また、サポートされるデバイスがMIDIやEthernetなど、CSUDより多いです。
その代わり、CSUDと比べるとかなり規模は大きく、ライブラリが9000行程度、USB以外のサポートライブラリが4000行程度となります。


最後にultibo OSのライブラリです。
ライセンスはLGPL2.1です。
ultibo OSのデモを動かしてみたのでスクリーンショットを載せておきます。
ultibo-demo.jpg

DOS+マルチタスク+フレームバッファという感じでしょうか。

ultibo OSは全体がPascalで書かれています。
個人的には昔Turbo PascalやDelphiを使っていましたので、Pascalに抵抗感はありませんが、正直「今どきPascal?」という気もちょっとします・・・。

それはさておき、ultibo OSのUSBドライバはコアの部分だけでも5000行あります。
ドライバ自体は、Linux版、FreeBSD版、USPiおよびCircle OSのドライバを参考にしたとあります。

Core/dwcotg.pas at master ・ ultibohub/Core

ただ、このソースコードは冒頭にコメントが100行ほどついており、DWCの解説にもなっていますので一読の価値はありそうです。


ということで4種類のUSBドライバを見てきましたが、一長一短ですね。
Linux版はライセンスが緩く機能も完璧ですが(チップの開発元が作っているから当然)、Threadが必要という要件もあり、ベアメタルへの移植は大変(そう)かもしれません。
CSUDはコンパクトで、MITライセンスですので、MicroPythonと一緒に使ってもMicroPython本体への影響が少なくて済みそうですが、USBハブを介さないとキーボードを認識できないのが残念なところです。
USPiは機能的に十分ですが、ライセンスがGPLです。この場合、ベアメタルRPi版MicroPythonは、MicroPythonに統合するのではなくMicroPythonの応用製品という位置づけにしないと、MicroPythonのライセンス(MITライセンス)と不整合が生じます。
ultibo OSはLGPLで、機能もUSPiに近いですが、ultibo OSの他のUnitにも依存しているので、ライブラリとして使うというよりMicroPythonをUltibo OSに移植するようなイメージになると思います。
ちなみに、Raspberry Pi版IchigoJamのREADMEにはultiboを開発環境として利用していると記載されていました。
ラベル:USB
posted by boochow at 08:00| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする
人気記事