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


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

QEMUのRaspberry Pi 3モデルでUSBホストエミュレーション』の続きでベアメタルでUSBのコントロール転送を動かしてみました。

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

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

コードはgithubに置きました

コードの説明

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

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

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

  • チャンネルの割り込みを設定する。(なくてもよい)
  • チャンネルの方向を設定する。
    • チャンネル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のトレースです

[email protected]:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021003
[email protected]:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021003 old 0x00021003 result 0x00021001
[email protected]:usb_dwc2_hreg0_action disable PRTINT
[email protected]:usb_dwc2_lower_global_irq 0x01000000
[email protected]:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021001
[email protected]:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021101 old 0x00021001 result 0x00021101
[email protected]:usb_dwc2_hreg0_action disable PRTINT
[email protected]:usb_dwc2_hreg0_read   0x0440 HPRT0     val 0x00021101
[email protected]:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x00021001 old 0x00021101 result 0x0002100d
[email protected]:usb_dwc2_hreg0_action call usb_port_reset
[email protected]:usb_dwc2_detach port 0x7fbee969a170
[email protected]:usb_dwc2_bus_stop stop SOFs
[email protected]:usb_dwc2_raise_global_irq 0x01000000
[email protected]:usb_dwc2_attach port 0x7fbee969a170
[email protected]:usb_dwc2_attach_speed full-speed device attached
[email protected]:usb_dwc2_bus_start start SOFs
[email protected]:usb_hub_reset dev 0
[email protected]:usb_dwc2_hreg0_action enable PRTINT
[email protected]:usb_dwc2_glbreg_read  0x0008 GAHBCFG   val 0x00000000
[email protected]:usb_dwc2_glbreg_write 0x0008 GAHBCFG   val 0x00000001 old 0x00000000 result 0x00000001
[email protected]:usb_dwc2_glbreg_write 0x0018 GINTMSK   val 0x11000000 old 0x00000000 result 0x11000000
[email protected]:usb_dwc2_update_irq level=1
[email protected]:usb_dwc2_hreg0_write  0x0440 HPRT0     val 0x0000100a old 0x0002100d result 0x00021005
[email protected]:usb_dwc2_hreg0_action disable PRTINT
[email protected]:usb_dwc2_lower_global_irq 0x01000000
[email protected]:usb_dwc2_hreg0_read   0x0418 HAINTMSK  val 0x00000000
[email protected]:usb_dwc2_hreg0_write  0x0418 HAINTMSK  val 0x00000003 old 0x00000000 result 0x00000003
[email protected]:usb_dwc2_hreg1_read   0x050c HCINTMSK40 val 0x00000000
[email protected]:usb_dwc2_hreg1_write  0x050c HCINTMSK0 val 0x00000001 old 0x00000000 result 0x00000001
[email protected]:usb_dwc2_hreg1_read   0x052c HCINTMSK41 val 0x00000000
[email protected]:usb_dwc2_hreg1_write  0x052c HCINTMSK1 val 0x00000001 old 0x00000000 result 0x00000001
[email protected]:usb_dwc2_hreg1_read   0x0500 HCCHAR  40 val 0x00000000
[email protected]:usb_dwc2_hreg1_write  0x0500 HCCHAR  0 val 0x00000040 old 0x00000000 result 0x00000040
[email protected]:usb_dwc2_hreg1_read   0x0520 HCCHAR  41 val 0x00000000
[email protected]:usb_dwc2_hreg1_write  0x0520 HCCHAR  1 val 0x00008040 old 0x00000000 result 0x00008040
[email protected]:usb_dwc2_hreg1_write  0x0514 HCDMA   0 val 0x00087000 old 0x00000000 result 0x00087000
[email protected]:usb_dwc2_hreg1_read   0x0514 HCDMA   40 val 0x00087000
[email protected]:usb_dwc2_hreg1_write  0x0514 HCDMA   0 val 0xc0087000 old 0x00087000 result 0xc0087000
[email protected]:usb_dwc2_hreg1_write  0x0510 HCTSIZ  0 val 0x60080008 old 0x00000000 result 0x60080008
[email protected]:usb_dwc2_hreg1_read   0x0500 HCCHAR  40 val 0x00000040
[email protected]:usb_dwc2_hreg1_write  0x0500 HCCHAR  0 val 0x80000040 old 0x00000040 result 0x80000040
[email protected]:usb_dwc2_find_device 0
[email protected]:usb_dwc2_device_found device found on port 0
[email protected]:usb_dwc2_enable_chan ch 0 dev 0x556ae5e2fd00 pkt 0x7fbee969a1b8 ep 0
[email protected]:usb_dwc2_handle_packet ch 0 dev 0x556ae5e2fd00 pkt 0x7fbee969a1b8 ep 0 type Ctrl dir Out mps 64 len 8 pcnt 1
[email protected]:usb_dwc2_memory_read addr -1073188864 len 8
[email protected]:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a1b8, state undef -> setup
[email protected]:usb_hub_control dev 0, req 0x8006, value 256, index 0, langth 64
[email protected]:usb_desc_device dev 0 query device, len 64, ret 18
[email protected]:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a1b8, state setup -> complete
[email protected]:usb_dwc2_packet_status status USB_RET_SUCCESS len 8
[email protected]:usb_dwc2_packet_done status USB_RET_SUCCESS actual 8 len 0 pcnt 0
[email protected]:usb_dwc2_raise_host_irq 0x0001
[email protected]:usb_dwc2_raise_global_irq 0x02000000
[email protected]:usb_dwc2_hreg1_write  0x0534 HCDMA   1 val 0x00083000 old 0x00000000 result 0x00083000
[email protected]:usb_dwc2_hreg1_read   0x0534 HCDMA   41 val 0x00083000
[email protected]:usb_dwc2_hreg1_write  0x0534 HCDMA   1 val 0xc0083000 old 0x00083000 result 0xc0083000
[email protected]:usb_dwc2_hreg1_write  0x0530 HCTSIZ  1 val 0x40080040 old 0x00000000 result 0x40080040
[email protected]:usb_dwc2_hreg1_read   0x0520 HCCHAR  41 val 0x00008040
[email protected]:usb_dwc2_hreg1_write  0x0520 HCCHAR  1 val 0x80008040 old 0x00008040 result 0x80008040
[email protected]:usb_dwc2_find_device 0
[email protected]:usb_dwc2_device_found device found on port 0
[email protected]:usb_dwc2_enable_chan ch 1 dev 0x556ae5e2fd00 pkt 0x7fbee969a268 ep 0
[email protected]:usb_dwc2_handle_packet ch 1 dev 0x556ae5e2fd00 pkt 0x7fbee969a268 ep 0 type Ctrl dir In mps 64 len 64 pcnt 1
[email protected]:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a268, state undef -> setup
[email protected]:usb_packet_state_change bus 0, port 1, ep 0, packet 0x7fbee969a268, state setup -> complete
[email protected]:usb_dwc2_packet_status status USB_RET_SUCCESS len 18
[email protected]:usb_dwc2_memory_write addr -1073205248 len 18
[email protected]:usb_dwc2_packet_done status USB_RET_SUCCESS actual 18 len 46 pcnt 0
[email protected]:usb_dwc2_raise_host_irq 0x0002
[email protected]:usb_dwc2_glbreg_read  0x0014 GINTSTS   val 0x16000021
[email protected]:usb_dwc2_glbreg_write 0x0014 GINTSTS   val 0x16000021 old 0x16000021 result 0x06000021
[email protected]:usb_dwc2_update_irq level=0

トレースの設定ファイル

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

usb_*
usb_hub_*
usb_dwc2_*
-usb_dwc2_sof

リンク