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 | このブログの読者になる | 更新情報をチェックする

Alexaのスキル開発チュートリアルを読んでみた

Amazon Echo Dotを昨年購入したものの、今ひとつ活用できていません。
もったいないので、自分でAlexaのスキルを開発するためのチュートリアルを読んでみました。

Alexa | アレクサ | Alexaスキル開発トレーニング

サンプルスキルの実装までを実際に試してみました。
音声認識/合成があったり、CGIじゃなくてLambdaで機能を実装できるところがちょっと目新しいくらいで、あとはネット家電としては割と普通だなという感想を持ちました。

とはいえ、これを支えるインフラを超安価に提供するところが、やはりAmazonの強みですね。
普通の会社がやろうとしたら、できるかもしれないけれど超高価なサービスになってしまうでしょう。

あと、実際にEchoを使うと、スキルの呼び出し呪文をそんなに沢山は覚えられないよなあ・・・と思います。
このあたりのユーザーインタフェースの課題は、まだクリアされていないように思います。
まだこれから何年か改良が続きそうですね。
posted by boochow at 17:06| Comment(0) | 日記 | このブログの読者になる | 更新情報をチェックする

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月17日

700円ロジアナでUSBパケットを観測してみた

usbsignal.jpg

昨日使ってみたロジアナですが、Salae Logicのプロトコル解析機能にはUSBもあることに気付きました。これはAnalog Discoveryには無い機能です。

この機能を使えば、先日Raspberry Piのベアメタルプログラミングでぶつかった、USBハブを通さないとキーボードが認識できない問題の解析に役立つかも?と思い、上の写真のようなD+とD-を外部から観測する冶具を作って試しに使ってみました。

右向きについているのがUSBオス、上向きについているのがUSBメスのコネクタです。
どちらもaitendoで購入した「基板with USBコネクタ」シリーズで、2P-UAF-S092P-UAM19です。同様の製品は各種あるのですが、こういう目的には基板に信号名がシルク印刷してあるものが良いと思います。

キャプチャしたUSB信号はこんな感じで表示されます。
salae2.png

解析できる信号はUSB FS(12Mbps)とLS(1.5Mbps)です。サンプリングレートが24Mspsなので当然ですが、480MbpsのHSは計測できません。
また、解析結果も、バイト列、パケット、コントロール転送までで、デバイスに特化したプロトコルの解析はできません。
とはいえ、USBプロトコルスタックのデバッグにはとても役立ちそうな気がします。
ラベル:USB
posted by boochow at 14:33| Comment(0) | 日記 | このブログの読者になる | 更新情報をチェックする
人気記事