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);

こんな感じ。