ベアメタルでRaspberry Pi 3のUSBコントロール転送

![undefined]()
ベアメタルでRaspberry Pi 3のUSBコントロール転送を動かしてみました。

『[QEMUのRaspberry Pi 3モデルでUSBホストエミュレーション](https://qiita.com/eggman/items/0089feaf55f8910adc1e)』の続きでベアメタルでUSBのコントロール転送を動かしてみました。

出来上がったコードはとてもスッキリしています。

仕様書と既存のコードを見ても、なかなかUSBホストの制御方法を解読するのは難しいです。
チートみたいですが、Linuxで動かしたトレースとqemuのhcd_dwc2.cを見ながら動きを解析した方が手っ取り早く動きます。
エミュレーターでは、タイミングとかメチャクチャでもレジスタアクセス順番さえ正しければ、それっぽく動きます。

コードはgithubに置きました

* https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/usb02

# コードの説明


* USB Hostの電源を入れてリセット
* USB Host の全体の割り込みを有効
* USB Portを有効にしてデバイスの再検出を行う。(QEMUは起動時に接続済みなので、もう一度検出処理をしたい）

Raspberry Piに搭載されいるUSB　IPはSynopsysのDWC OTGです。このIPはChannelと呼ぶ仕組みを使ってUSB転送を行います。一度の転送ごとにChannelの設定をして転送を実行するというのを繰り返します。

この例では、転送が２回あるので２つのチャンネルを設定しています。

* チャンネルの割り込みを設定する。（なくてもよい）
* チャンネルの方向を設定する。
    * チャンネル0はOUT ( HOST -> DEVICE)
    * チャンネル1はIN ( HOST <- DEVICE)

まず送信から

* GET_DESCRIPTRのパケット生成する。パケットの仕様はUSBの規格書に書いててあるらしいです。
* 生成したパケットをusb_buffer0[]にコピーする。チャンネル0のDMAアドレスにusb_buffer0[]を設定する。
* 送信パケット長と送信パケット個数を設定する。このパケットは初回だからなのか、PidにSETUPを指定する。
* チャンネル0のChEnaを1にすると転送が実行される。

本当は転送結果をチェックするべきだが、やっていない。

次に受信です。

* チャンネル0のDMAアドレスにusb_buffer1[]を設定する。受信したパケットがこのアドレスにコピーされます。
* 受信パケット長と受信パケット個数を設定する。このパケットはPidにDATA2を設定します。
* チャンネル0のChEnaを1にすると転送が実行される。
* usb_buffer1[]に受信データ18バイトが格納されています。

```
void usbhost_start(void)
{
    uint32_t count = 0;

    // USB Host power on
    // HPRT.PrtPwr = 1'b1 -> HPRT.PrtRst = 1'b1 -> wait 60msec -> HPRT.PrtRst = 1'b0
    *USB_HOST_HPRT |= 1 << 12;
    *USB_HOST_HPRT |= 1 << 8;
    count = 0;
    do {
        ;
    } while (count++ >= 0x100000) ;
    *USB_HOST_HPRT &= ~(1 << 8);

    // enable irq
    // GAHBCFG.GlblIntrMsk = 1'b1
    // GINTMSK.ConIDStsChngMsk = 1'b1, GINTMSK.PrtIntMsk = 1'b1, GINTMSK.SofMsk = 1'b1
    *USB_CORE_GAHBCFG   |= 1;
    *USB_CORE_GINTMSK    =  1 << 28 | 1 << 24 | 0 << 3;

    // port enable and retry detect
    // HPRT.PrtPwr = 1'b1, HPRT.PrtEnChng = 1'b1, HPRT.PrtConnDet = 1'b1
    *USB_HOST_HPRT = 1 << 12 | 1 << 3 | 1 << 1; 

    // enable channel irq
    // HAINTMASK.HAINTMsk = 16'h3
    // HCINTMSKx.XferComplMsk = 1'b1
    *USB_HOST_HAINTMSK   |= 0x3;
    *USB_HOST_HCINTMSK0  |= 1;
    *USB_HOST_HCINTMSK1  |= 1;

    // HCCARx.EPDir = 1'b0 (OUT) / 1'b01(IN), HCCARx.MPS = 11'h40
    *USB_HOST_HCCHAR0   |= 0x40;            // OUT
    *USB_HOST_HCCHAR1   |= 1 << 15 | 0x40;  // IN

    // build packet
    memcpy(usb_buffer0, &(struct UsbDeviceRequest) {
                                .Type = 0x80,     // DEVICE_TO_HOST | STDANDAD | DEVICE
                                .Request = 0x06,  // GET_DESCRIPTOR
                                .Value = 0x0100,  // descriptor.type = 0x01, decriptor.index = 0x00
                                .Index = 0,
                                .Length = 64,
                      }, 8);

    // send setup & control packet
     // set dma buffer
    *USB_HOST_HCDMA0   = usb_buffer0;
    *USB_HOST_HCDMA0  |= 0xC0000000;
     // HCTSIZ0.Pid = 2'h3 (SETUP) , HCTSIZ0.PktCnt = 10'h1 , HCTSIZ0.XferSize = 18'd8
    *USB_HOST_HCTSIZ0 = 3 << 29 | 1 << 19 | 8;
     // HCCAR1.ChEna = 1'b1
    *USB_HOST_HCCHAR0 |= 1<<31;

    // recieve control packet
     // set dma buffer
    *USB_HOST_HCDMA1   = usb_buffer1;
    *USB_HOST_HCDMA1  |= 0xC0000000;
     // HCTSIZ1.Pid = 2'h2 (DATA1) , HCTSIZ1.PktCnt = 10'h1 , HCTSIZ1.XferSize = 18'd64
    *USB_HOST_HCTSIZ1  = 2 << 29 | 1 << 19 | 64;
     // HCCAR1.ChEna = 1'b1
    *USB_HOST_HCCHAR1 |= 1<<31;
}

```



# 動作例

USBコントロール転送を使って、USB接続されたRootHubのDEVICE DESCRIPTORを取得し表示します。

```
$ qemu-system-aarch64 -M raspi3 -m 1024 -serial null -serial mon:stdio -nographic -device usb-kbd -kernel kernel.elf
usb02
usb_buffer0:00087000
usb_buffer1:00083000
USB_CORE_GUID    00000000
USB_CORE_GSNPSID 4F54294A
16000021 usb interrupt
Connector ID Status Change
Periodic TxFIFO Empty
Host Channels Interrupt
Non-periodic TxFIFO Empty
GET_DESCRIPTOR
00000012
00000001
00000010
00000001
00000009
00000000
00000000
00000008
00000009
00000004
000000AA
00000055
00000001
00000001
00000001
00000002
00000003
00000001
```

QEMUのトレースです

```
15561@1588732795.056267:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021003
15561@1588732795.056280:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021003 old 0x00021003 result 0x00021001
15561@1588732795.056284:usb_dwc2_hreg0_action disable PRTINT
15561@1588732795.056287:usb_dwc2_lower_global_irq 0x01000000
15561@1588732795.056290:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021001
15561@1588732795.056293:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021101 old 0x00021001 result 0x00021101
15561@1588732795.056296:usb_dwc2_hreg0_action disable PRTINT
15561@1588732795.056299:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021101
15561@1588732795.056302:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021001 old 0x00021101 result 0x0002100d
15561@1588732795.056305:usb_dwc2_hreg0_action call usb_port_reset
15561@1588732795.056308:usb_dwc2_detach port 0x7fbee969a170
15561@1588732795.056311:usb_dwc2_bus_stop stop SOFs
15561@1588732795.056314:usb_dwc2_raise_global_irq 0x01000000
15561@1588732795.056317:usb_dwc2_attach port 0x7fbee969a170
15561@1588732795.056320:usb_dwc2_attach_speed full-speed device attached
15561@1588732795.056322:usb_dwc2_bus_start start SOFs
15561@1588732795.056329:usb_hub_reset dev 0
15561@1588732795.056333:usb_dwc2_hreg0_action enable PRTINT
15561@1588732795.056336:usb_dwc2_glbreg_read  0x0008 GAHBCFG   val 0x00000000
15561@1588732795.056339:usb_dwc2_glbreg_write 0x0008 GAHBCFG   val 0x00000001 old 0x00000000 result 0x00000001
15561@1588732795.056342:usb_dwc2_glbreg_write 0x0018 GINTMSK   val 0x11000000 old 0x00000000 result 0x11000000
15561@1588732795.056346:usb_dwc2_update_irq level=1
15561@1588732795.056351:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x0000100a old 0x0002100d result 0x00021005
15561@1588732795.056354:usb_dwc2_hreg0_action disable PRTINT
15561@1588732795.056357:usb_dwc2_lower_global_irq 0x01000000
15561@1588732795.056359:usb_dwc2_hreg0_read   0x0418 HAINTMSK  val 0x00000000
15561@1588732795.056362:usb_dwc2_hreg0_write  0x0418 HAINTMSK  val 0x00000003 old 0x00000000 result 0x00000003
15561@1588732795.056366:usb_dwc2_hreg1_read   0x050c HCINTMSK40 val 0x00000000
15561@1588732795.056369:usb_dwc2_hreg1_write  0x050c HCINTMSK0 val 0x00000001 old 0x00000000 result 0x00000001
15561@1588732795.056372:usb_dwc2_hreg1_read   0x052c HCINTMSK41 val 0x00000000
15561@1588732795.056426:usb_dwc2_hreg1_write  0x052c HCINTMSK1 val 0x00000001 old 0x00000000 result 0x00000001
15561@1588732795.056432:usb_dwc2_hreg1_read   0x0500 HCCHAR  40 val 0x00000000
15561@1588732795.056435:usb_dwc2_hreg1_write  0x0500 HCCHAR  0 val 0x00000040 old 0x00000000 result 0x00000040
15561@1588732795.056438:usb_dwc2_hreg1_read   0x0520 HCCHAR  41 val 0x00000000
15561@1588732795.056441:usb_dwc2_hreg1_write  0x0520 HCCHAR  1 val 0x00008040 old 0x00000000 result 0x00008040
15561@1588732795.056445:usb_dwc2_hreg1_write  0x0514 HCDMA   0 val 0x00087000 old 0x00000000 result 0x00087000
15561@1588732795.056448:usb_dwc2_hreg1_read   0x0514 HCDMA   40 val 0x00087000
15561@1588732795.056451:usb_dwc2_hreg1_write  0x0514 HCDMA   0 val 0xc0087000 old 0x00087000 result 0xc0087000
15561@1588732795.056454:usb_dwc2_hreg1_write  0x0510 HCTSIZ  0 val 0x60080008 old 0x00000000 result 0x60080008
15561@1588732795.056457:usb_dwc2_hreg1_read   0x0500 HCCHAR  40 val 0x00000040
15561@1588732795.056460:usb_dwc2_hreg1_write  0x0500 HCCHAR  0 val 0x80000040 old 0x00000040 result 0x80000040
15561@1588732795.056463:usb_dwc2_find_device 0
15561@1588732795.056466:usb_dwc2_device_found device found on port 0
15561@1588732795.056468:usb_dwc2_enable_chan ch 0 dev 0x556ae5e2fd00 pkt 0x7fbee969a1b8 ep 0
15561@1588732795.056472:usb_dwc2_handle_packet ch 0 dev 0x556ae5e2fd00 pkt 0x7fbee969a1b8 ep 0 type Ctrl dir Out mps 64 len 8 pcnt 1
15561@1588732795.056476:usb_dwc2_memory_read addr -1073188864 len 8
15561@1588732795.056482:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a1b8, state undef -> setup
15561@1588732795.056487:usb_hub_control dev 0, req 0x8006, value 256, index 0, langth 64
15561@1588732795.056491:usb_desc_device dev 0 query device, len 64, ret 18
15561@1588732795.056494:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a1b8, state setup -> complete
15561@1588732795.056497:usb_dwc2_packet_status status USB_RET_SUCCESS len 8
15561@1588732795.056501:usb_dwc2_packet_done status USB_RET_SUCCESS actual 8 len 0 pcnt 0
15561@1588732795.056503:usb_dwc2_raise_host_irq 0x0001
15561@1588732795.056506:usb_dwc2_raise_global_irq 0x02000000
15561@1588732795.056511:usb_dwc2_hreg1_write  0x0534 HCDMA   1 val 0x00083000 old 0x00000000 result 0x00083000
15561@1588732795.056514:usb_dwc2_hreg1_read   0x0534 HCDMA   41 val 0x00083000
15561@1588732795.056517:usb_dwc2_hreg1_write  0x0534 HCDMA   1 val 0xc0083000 old 0x00083000 result 0xc0083000
15561@1588732795.056520:usb_dwc2_hreg1_write  0x0530 HCTSIZ  1 val 0x40080040 old 0x00000000 result 0x40080040
15561@1588732795.056524:usb_dwc2_hreg1_read   0x0520 HCCHAR  41 val 0x00008040
15561@1588732795.056527:usb_dwc2_hreg1_write  0x0520 HCCHAR  1 val 0x80008040 old 0x00008040 result 0x80008040
15561@1588732795.056530:usb_dwc2_find_device 0
15561@1588732795.056532:usb_dwc2_device_found device found on port 0
15561@1588732795.056534:usb_dwc2_enable_chan ch 1 dev 0x556ae5e2fd00 pkt 0x7fbee969a268 ep 0
15561@1588732795.056537:usb_dwc2_handle_packet ch 1 dev 0x556ae5e2fd00 pkt 0x7fbee969a268 ep 0 type Ctrl dir In mps 64 len 64 pcnt 1
15561@1588732795.056540:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a268, state undef -> setup
15561@1588732795.056544:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a268, state setup -> complete
15561@1588732795.056546:usb_dwc2_packet_status status USB_RET_SUCCESS len 18
15561@1588732795.056549:usb_dwc2_memory_write addr -1073205248 len 18
15561@1588732795.056553:usb_dwc2_packet_done status USB_RET_SUCCESS actual 18 len 46 pcnt 0
15561@1588732795.056555:usb_dwc2_raise_host_irq 0x0002
15561@1588732795.056686:usb_dwc2_glbreg_read  0x0014 GINTSTS   val 0x16000021
15561@1588732795.056692:usb_dwc2_glbreg_write 0x0014 GINTSTS   val 0x16000021 old 0x16000021 result 0x06000021
15561@1588732795.056695:usb_dwc2_update_irq level=0

```

トレースの設定ファイル

USBのイベントのみトレースする。ただしsofのイベントはたくさん出現するので無効にする。

```
usb_*
usb_hub_*
usb_dwc2_*
-usb_dwc2_sof
```


# リンク
* CME M7 Family User Guide FUNCTIONAL DESCRIPTION 
 * http://www.capital-micro.com/PDF/CME-M7_Family_User_Guide_EN.pdf
 * p370　に USBの仕様が記載されており、レジスタの仕様が載っています。
* csud : Chadderz's Simple USB Driver for Raspberry Pi
  * https://github.com/Chadderz121/csud
* cusd 派生のドライバ
 * https://github.com/LdB-ECM/Raspberry-Pi/tree/master/Arm32_64_USB
 * ソースコードが２つなのでオリジナルより検索しやすい。
* USB 3.0 ホストドライバ自作入門
 * https://booth.pm/ja/items/1056355
 * USBホストドライバのアーキテクチャについてとても参考になる。