新千葉 ガーベージ・コレクション

FPGA マガジンやインターフェースで書けなかったこと等をちょぼちょぼ書いてます。@ryos36

EHCI で Get Descriptor

とりあえず達成。
Zynq で USB Host で EHCI で GetDescriptor できた。
f:id:ryos36:20160303011845p:plain
基本的には u-boot に書いてあるそのままを実行した。

ソースはというと、、、、

    uint32_t *qTD_Setup_Data = &qTD_Setup[1024];
    qTD_Setup[0] = (uint32_t)qTD_Setup_Data;
    qTD_Setup[1] = 1;
    qTD_Setup[2] = (0 << 31) | (8 << 16) | (3 << 10) | (2 << 8) | (1 << 7) ;
    qTD_Setup[3] = (uint32_t)&dbp[0];
    qTD_Setup[4] = qTD_Setup[5] = qTD_Setup[6] = qTD_Setup[7] = 0;

    dbp[0] = 0x80;
    dbp[1] = 0x06; // GET DESCRIPTOR
    dbp[2] = 0;
    dbp[3] = 1;    // DEVICE
    dbp[4] = 0;
    dbp[5] = 0;
    dbp[6] = 64;
    dbp[7] = 0;

こんなんだから、汎用性はない!!でも動く。
内容はこんな感じ。

xsdb% mrd 0x11d000 8
  11D000:   01100112
  11D004:   40000000
  11D008:   094004BB
  11D00C:   02010001
  11D010:   00000103
  11D014:   00000000
  11D018:   00000000

4 バイトごとに反転しているからわかりづらい、、、
0x12 0x01 0x10 0x01
とちゃんと USB の規約にのっとたデータが来てます(当たり前)。ここまでかけちゃうとオレオレ scheme か FORTH で書いてしまいたくなるなぁ。

EHCI を初めて試したが、それにしてもバッファを荒らす。メモリを使っていろんなところに書き込みをします。これ性能出るの?今後はアイソクロナスにもチャレンジしていきたい。MIDI データとか音楽データとかビデオ画像とかね。

Linux の EHCI のソースを読む

結局 EHCI を学ぶのに一番良いのは Linux のソースであることが判明した。あと参考資料としては USB コンプリートが結局一番。次に2014年のInteface 12月号の USB 探偵団。
ただし、USB 探偵団は記述が口語的。制御命令と書いてあるが、USB コンプリート(2.0 対応のも3.0 対応のも)には制御命令という記述は見当たらなかった。SETUP 命令とあるが、USB コンプリートでは SETUP トランザクションだ。制御命令は USB の仕様書の TERM にも見当たらない。
一方、USB の仕様では Device Request という言葉が見られるが USB コンプリートでは見当たらない(ので USB コンプリートが正確かというと、その原文を見ないと何とも言えない)


ダメな本は

  • 改訂新版:USBハード&ソフト開発のすべて => 断片的で記述があいまい
  • 組み込み機器への USB ホスト実装技術 => 断片的で記述があいまい
  • USB ターゲット機器開発のすべて => 断片的で記述があいまい
  • USB 組み込みホスト => 内容はほとんどゼロ。

ステータスステージに言及している本はすくない。USB コンプリートにはある。USB 3.0 設計のすべては買ったばかりで読んでいないがステータスステージの言及があった(なんとなくハードよりな感じがする)。ステータスステージは重要ではないのだろうか?

重要なことは Transfer(転送という訳がある) と Transaction とパケットを明確にすること。そして EHCI はこの概念を使って実装されており、典型的な例が qTD = Queue Element Transfer Descriptor。

いずれにせよ EHCI に深く言及した日本語資料はいまのところみつからない。なので、IntelEHCI ver 1.0 と ug585 の USB のくだりを読んで Linux のソースを読むのが一番ということになる。LinuxEHCI の読みどころは主に 2か所。ehci_urb_enqueue と ehci_urb_dequeue。とりあえずは enqueue を読むことにする。

USB_PIPETYPE のチェック

    switch (usb_pipetype (urb->pipe)) {
    case PIPE_CONTROL:
        /* qh_completions() code doesn't handle all the fault cases
         * in multi-TD control transfers.  Even 1KB is rare anyway.
         */
        if (urb->transfer_buffer_length > (16 * 1024))
            return -EMSGSIZE;
        /* FALLTHROUGH */
    /* case PIPE_BULK: */
    default:
        if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags))
            return -ENOMEM;
        return submit_async(ehci, urb, &qtd_list, mem_flags);

これでコントロールか BULK だと qh_urb_transaction と submit_async を呼んでいることがわかる。

qh_urb_transaction

コメントに
create a list of filled qtds for this URB; won't link into qh.
とあるように transaction のリンクを作る。

    if (usb_pipecontrol (urb->pipe)) {
        /* SETUP pid */
        qtd_fill(ehci, qtd, urb->setup_dma,
                sizeof (struct usb_ctrlrequest),
                token | (2 /* "setup" */ << 8), 8);

        /* ... and always at least one more pid */
        token ^= QTD_TOGGLE;
        qtd_prev = qtd;
        qtd = ehci_qtd_alloc (ehci, flags);
        if (unlikely (!qtd))
            goto cleanup;
        qtd->urb = urb;
        qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma);
        list_add_tail (&qtd->qtd_list, head);

        /* for zero length DATA stages, STATUS is always IN */
        if (len == 0)
            token |= (1 /* "in" */ << 8);
    }

これで pipe がコントロール用であれば setup パケットを生成している。つまり、要求では setup パケットを構築しない。コントロール用の pipe なら自動的に setup パケットが挿入されて setup transaction が構築される。でもって、len が 0 なら IN があると思って Transaction をつくる。
その後 sg (Scatter/Gather)の処理などをしながら

    if (likely (urb->transfer_buffer_length != 0)) {
        int one_more = 0;

        if (usb_pipecontrol (urb->pipe)) {
            one_more = 1;
            token ^= 0x0100;    /* "in" <--> "out"  */
            token |= QTD_TOGGLE;    /* force DATA1 */
        } else if (usb_pipeout(urb->pipe)
                && (urb->transfer_flags & URB_ZERO_PACKET)
                && !(urb->transfer_buffer_length % maxpacket)) {
            one_more = 1;
        }
        if (one_more) {

の処理をする。これは transfer_buffer_length が 0 じゃなかったらという処理になっている。さきの len = 0 の逆だ(len == tranfer_buffer_length なので)

submit_async

submit_async では最終的に tds を qh にリンクして qh_link_async を呼ぶ

qh_link_async

   if (!head->qh_next.qh) {
        u32 cmd = ehci_readl(ehci, &ehci->regs->command);

        if (!(cmd & CMD_ASE)) {
            /* in case a clear of CMD_ASE didn't take yet */
            (void)handshake(ehci, &ehci->regs->status,
                    STS_ASS, 0, 150);
            cmd |= CMD_ASE;
            ehci_writel(ehci, cmd, &ehci->regs->command);
            /* posted write need not be known to HC yet ... */
        }
    }

ステータスを見ながら要求をキックする。

root hub

urb でリクエストされたものが root hub だったらパケットを解析してあたかも transfer/transaction が実行されたかのようにふるまう。たとえば USB_REQ_SET_ADDRESS は root hub にセットアドレスはできないので(root hub はアドレスがないときまっているので)何もしていなかったりする。

使う側は hub だとおもって urb を出せばよい。例えば port の enable みたいなこと。こうすることで、LinuxWindows の実装は EHCI か独自 HCI かなどを気にしなくてよくなる。

使う側

        result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
            USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
            (USB_DT_STRING << 8) + index, langid, buf, size,
            USB_CTRL_GET_TIMEOUT);

こんな感じ。

USB と EHCI

EHCI の日本語資料が少ない。Intel の資料を見れば英語だけどだいたいわかるか。

出版されている本ではなかなかそもそも USB の概略がわからない。一番良い資料は インタフェースの 2014 年の 12 月号の「USB 探偵団」なるものがわかりやすい。

世の中で EHCI をどうしたらよいか解説しているものはない。だいたい USB のホストのソフトも Linux 以外は見当たらない。

USB ルートハブ

通常の USB のチップは1つのコントローラに複数のポートがある。それらは(1つのコントローラの配下にあるから)1つの USB のネットワーク(?とはいわないかも)の集まりになる。複数のポートをまとめるのが USB のルートハブ。ハブだけど、インタフェースは個々のコントローラとのやり取りになる。ポートが一つだとハブとはいわないかもしれない。

USB ハブ

複数の USB を容易につなげるために、USB ハブの機能は必須。ハブの先のポートへのデバイスの挿抜に対応する必要がある。

セットアップ・パケット(パケットというのか?トランザクション?)

通常のパケットと違い、セットアップを受信したデバイスは動作中であってもそのセットアップに応答しなければならない。restart がかかったようなものだ。まぁなんだよくわからんが最初からみたいなことが USB ではできるようになっている。いいのか悪いのか知らないが。

EHCI の FS/LS

Full と Low のデバイスは EHCI で管理されているポートに接続されると通常、未接続になる。そこで、コントローラは EHCI ではなく OHCIUHCI のインタフェースで USB1.1 相当のデバイスと通信をするような設計になっている。ただし、EHCI なのに FS/LS に接続可能なコントローラも存在する。

TT とスプリットトランザクション

HS のハブでは FSと LS を接続するために、スピードの変換とバスを占有しないために応答を分けるスプリットトランザクションが必要

EHCI の Asynchoronous Schedule

ASYNCLISTADDR につながった HQ で管理される要求を一つ一つ実行していく。つながったリストは循環するようにできていて、終了すると済のマークがつく。このリストに対して要求をインサートやリムーブをすることで処理を実行する。コントローラはすべてのリストに済マークがつくと一旦、処理をやめてしまう"Complete" な状態になってしまうので、自転車操業的にインサートしていくことで処理が進む。処理の効率化のためには済んでしまった HQ を取り除くことが望ましい。処理をインサートした時はすでに Complete しているとスケジュールがなされないので、その際にはキックしてやらないといけない。インサート、リムーブはポインタを使うので ECHI の資料にある手順で行う必要がある。

この情報でとりあえず bulk 転送はできる気がする。

なんちゃって USB ホストを作る

目標。

  • ポートの活性化
  • アドレスの設定
  • コンフィギャーの設定
  • 各種情報の取得設定

先送り項目

  • ルートハブ(使うのは1ポートに限定)
  • ハブ(ハブはつながない)
  • 割り込みも使わない

そのあとなんちゃって CDC をつくる。

lua, pforth, gforth の VM を読む

よむったってすぐには読めません。
lua の場合 4.0 までがスタック型で 5.0 以降はレジスタ型の VM になった模様(未確認)。で初期のコードがやっぱり読みやすい。lua-1.0 あるいは lua-1.1 のソースを読むとわかりやすい。lua.stx (おそらくシンタックス) をみて、そして、lua-1.1/src/opcode.h を見る。よくよくみると PUSHLOCAL の類と、STORELOCAL の類の opcode がある。PUSHGLOBAL も STOREGLOBAL もある。ということは、、、
どうやら巷のスタック型の VMコンパイルした結果を落とそうと思うと、ローカル変数を数え上げて、その分スタックをずらして変数領域を作り、基本的な演算はスタック操作で行うが結果の変数への出し入れは PUSHLOCAL や STORELOCAL で行う、、、、ということらしい。これなら確かにコンパイラが作ったコードをスタック型の VM でも別に違和感なくバイトコードの出力が出来そう。
ただし、SSA との相性が悪そうなのが気がかり。試したわけじゃないけど、コンパイルした中間言語SSACPS だと、無数にローカル変数ができてしまう。生き死にを勘定しながら最大のローカル変数の数はわかるからできないことはないのか?ローカル変数というよりローカル・レジスタになるね。
そうまでするなら fp なしで、直接 sp からアクセスできるようにしてしまえばよさそうだけど。そうするとだんだんスタックマシンじゃなくなる。

pforth はどうだ?csrc/pf_inner.c を見ればよい。switch/case で作ってあって見やすい。SP_STORE とか RP_STORE とかがあって、要はスタックポインタのアドレスやリターン用のスタックのアドレスを知ることが出来る。スタックをずらしてアドレスを知ればそこからアクセスできる。FORTH プログラマが積極的にこのようなことをするかどうかは知らないが、用意されているということは使うのでしょう。アドレスがわかれば2つあるスタックを上手に使ってフェッチとかしながら器用に使いまわせそうだ(それが FORTH プログラマ?)。

gforth は難しすぎ。だが、gforth-0.2.1 までさかのぼってみればだいぶみやすくなる。ついでに書くと gforth-0.4.0 から各 CPU 対応が明確になっている。alpha 対応をしているので 64bit の long long 対応をしているようだ。alpha のおかげで先駆けて 64bit 対応ができているんだね。primitives.i というのがコードのようだ。vmgen はこのころ明確にはなかったけど、すでに forth で forth を作っている模様。よくみると FTOS なんてのがある。浮動小数点専用のスタック。double にもできるようだ。

sp@ と sp! があるので、これでスタックポインタのアドレスを知ることが出来る。rp@ と rp! もあるようだ。これでローカル変数の定義ができる、、、、のだろう。ヘビーな FORTH プログラマにとっては。すげーな。と感心。もちろん、メタプログラミングをからませて複雑なことをするんだろう。アドレスを使ったメタプログラミングができるのは FORTH だけ!?FORTH 万歳。って今のところ、使いこなせないけどね。これ、はじめたら年単位の勉強になる。

そういや、Retro はどうなっているんだ。とふと気になった。vm/complete/retro.c で一見なにもしてなさそうだったのが、、、よーくみると、、、rxDeviceHandler でやってるんだ。Capabilities の -5 が SP を返して、-6 が RSP を返すようになっている。なんで opcode にいれてないんだ?なんか知らんがハードと opcode を分離したかったんだな。

スタック型のVM

コンパイラの結果をスタックマシン型の VM に落とすことを考える。JVM などでは iload なるものがある。きっと istore もある(あーあったあった)だから、スタックと"変数"への出し入れは自由なわけだ。

a = b + c

を考えると

iload b
iload c
+
istore a

とかになるのか?いやちょっと待て。a はいずれ使うだろ。だからスタックにとっておこうよ。とすると次に

e = f + g

とかが来ると、もう a はスタックの下の方に埋もれてしまう。なるべくスタックで処理をしようと思うと実行順序は大事だ。これ FORTH のプログラマはどう解決しているのだろう?

コンパイラに効率よい順序を考えさせるのは結構大変だと思う。(というかこの手のはだいたい NP 問題になっちゃうし)でおそらく経験的なプログラムをして、例外処理をするということになるだろう。FORTH でない言語を FORTH のようなスタックマシンに落とすとき(典型的な FORTH マシンはたぶん iload や istore はないと思う。)どうすればよいのだ。

FORTH プログラマはいつもこの順序を考えているということか?だとしたらいつも手で最適化しているということで、スゲーなということになっちゃう。誰か知っていたら教えてほしい。

おれおれ tiny scheme もついに VM コード生成まで来た

この2か月の成果。なんとか fib をコンパイルして VM のコードに落とすことに成功。

(:JUMP |:MAIN|)

MAIN
(:LABEL |:MAIN|)

EXIT
(:LABEL |:EXIT|)

|:EXIT|
(:HALT :R0)

|:MAIN|
(:LABEL |:MAIN|)
(:JUMP |:label0|)
(:LIVE-REG 0 (1 1 1 1 1 0 0 0 0 0))

|:FIBO-2|
(:JUMP-COND := (:R2 0) |:label1|)
(:- (:R2 1) (:R2))
(:+ (:R3 :R4) (:R9))
(:RECORD-REF (:R0 0) (:R8))
(:SWAP :R9 :R3)
(:MOVE :R9 :R4)
(:JUMP :R8)

|:label1|
(:RECORD-REF (:R1 0) (:R9))
(:MOVE :R1 :R0)
(:MOVE :R3 :R1)
(:JUMP :R9)
(:LIVE-REG 0 (1 1 1 0 0 0 0 0 0 0))

|:FIBO|
(:RECORD-OFFS (:R0 1) (:R0))
(:RECORD-REF (:R0 0) (:R9))
(:MOVEI 1 :R3)
(:MOVEI 0 :R4)
(:JUMP :R9)

|:label0|
(:HEAP ((:LABEL |:FIBO|) (:LABEL |:FIBO-2|)) (:R9))
(:RECORD-REF (:R9 0) (:R8))
(:MOVE :R9 :R0)
(:MOVEI (:ADDRESS EXIT) :R1)
(:MOVEI 8 :R2)
(:JUMP :R8)

JUMP 命令が多い気もするが、気にしないで行こう。:R9 とかも気にせずいこう。
さて、これをさらにバイナリに落とすと行きたいところだが、できてないので C のソースに落ちる。

#define __codes_MAIN__ 0x00000003
#define __codes_EXIT__ 0x00000004
#define __codes__EXIT__ 0x00000005
#define __codes__MAIN__ 0x00000007
#define __codes__FIBO_2__ 0x0000000A
#define __codes__label1__ 0x00000019
#define __codes__FIBO__ 0x00000021
#define __codes__label0__ 0x0000002B
(Inst *)INST_ADDR(jumpi32),
0x20000000,
__codes__MAIN__,
__codes__MAIN__,
__codes__EXIT__,
(Inst *)INST_ADDR(halt),
0x00000000,
(Inst *)INST_ADDR(jumpi32),
0x20000000,
__codes__label0__,
(Inst *)INST_ADDR(eqi8),
0x04020000,
__codes__label1__,
(Inst *)INST_ADDR(subi8),
0x04020102,
(Inst *)INST_ADDR(add),
0x00030409,
(Inst *)INST_ADDR(record_refi8),
0x04000008,
(Inst *)INST_ADDR(move),
0x00030400,
(Inst *)INST_ADDR(swap),
0x00090300,
(Inst *)INST_ADDR(jump),
0x00080000,
(Inst *)INST_ADDR(record_refi8),
0x04010009,
(Inst *)INST_ADDR(move),
0x00010000,
(Inst *)INST_ADDR(move),
0x00030100,
(Inst *)INST_ADDR(halt),
0x00010000,
(Inst *)INST_ADDR(record_offsi8),
0x04000100,
(Inst *)INST_ADDR(record_refi8),
0x04000009,
(Inst *)INST_ADDR(movei8),
0x04000103,
(Inst *)INST_ADDR(movei8),
0x04000004,
(Inst *)INST_ADDR(jump),
0x00090000,
(Inst *)INST_ADDR(heap),
0x04000209,
0x0000000A,
__codes__FIBO__,
__codes__FIBO_2__,
(Inst *)INST_ADDR(record_refi8),
0x04090008,
(Inst *)INST_ADDR(move),
0x00090000,
(Inst *)INST_ADDR(movei32),
0x20000001,
&pre_inst[__codes__EXIT__],
(Inst *)INST_ADDR(movei8),
0x04005b02,
(Inst *)INST_ADDR(jump),
0x00080000,

で一部改ざんして(orz) 0x5b を入力にする。実行。

> ./hvm
いろいろデバッグ用の出力があるので省略
move result regs[1] =  68a3dd8e61eccfbd
halt 7540113804746346429

こたえがでる。movei32 とかいうコードなのに、long が 64bit のために答えも 64bit。
こたえは 7540113804746346429 で 92(0x5c) のフィボナッチを計算した答えに一致!!

http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibtable.html

ん?0x5b をいれると 0x5c の答えが返ってくるという、、、仕様です。はい。