RaspberryPi Zero WでOS無しベアメタル環境でXBOX360ゲームコントローラの入力取得用にDWC2ドライバを書いて遊ぶ実験

![asdfasdfasdf.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34428/68fdc08e-00b4-571b-bfd7-38fb3d3fdc9b.jpeg)

# 初めに
Raspberry Pi アドベントカレンダー 2022の記事です。
https://qiita.com/advent-calendar/2022/raspberry-pi
https://adventar.org/calendars/7440
Raspberry Pi Zero Wを使って遊んだ記録をシェアします。
記事中、Raspberry Pi Zero WをRPIZWって書きます。

ラズパイ最近鳴りを潜めているようですが5が出るんかな...

https://twitter.com/Raspberry_Pi/status/1603436411314659333




# この記事の目標
- RPIZWに搭載されているUSBコントローラDWC2で
- OS無しでARMv6起動後をスクラッチで(raspbianとか使わないで)
- HostモードでXBOX360ゲームコントローラのConfigrationを行って
- Hostから入力レポートを取るための簡単なドライバを書きます
- 取ったXBOX360ゲームコントローラのレポートをparseして使います
- おまけでDMA転送で画面に絵を出しながら様子を可視化して動いていることをアピールします

# この記事ではほとんど触れないこと
- uart(ここではGPU14, 15)とPC接続してのシリアル接続。デバッグ用。
- RaspberryPiの基本的なセットアップとか
- RaspberryPiを使ったLinuxの起動とかそういうの
- RaspberryPi用のbinary生成系とかmakeとか(今回はLinuxでARMv6系のコードがはけるようなものをubuntu上に拵えてFTP経由でSDCardに転送、電源OFF/ONという素朴な方法を用いました)
- XBOX360コントローラの挿抜には対応しません

# 前提とか
- RPIZWで採用されているUSBコントローラのレジスタ仕様が公式で提供されていません
- linuxのsourceをみたり、EDK2のソースを見たり、実際にモノを繋いだときに合間にレジスタをRead/Writeしながら挙動を把握しつつ実験しました。つまり「正しさ」が無い状況です。
- USBの基本的な処理などは本当に初心者レベルで調べながら書いたことなので間違ってたらツッコミください
- あとは、USBのGetDescriptorでとった値を真面目にparseしません。あらかじめUbuntuとかで調べた値を使ってとにかく入力を取ることに専念しています
※なぜXBOX360コントローラなのかというとその辺に転がってたからです。が、なんかspecを見ると色々怪しいところがあるが気にせず。

# 使う機材
- Raspberry Pi Zero W
https://www.raspberrypi.com/products/raspberry-pi-zero-w/
無い人はRaspberry Pi2でもOK(なはず
- XBOX360コントローラ(有線)
- USB シリアル変換機 (uartとteratermがつながればなんでもいいです)
- 適当なmicroUSB用電源
- microUSB to USB変換アダプタ(XBOX360コントローラとつなぐときに必要)
- HDMI to USB変換機 (PCからはカメラデバイスとして見える)
ワシはこれをつかいました : https://www.amazon.co.jp/dp/B07X5VJQ3D
- OBS Studio
 
https://obsproject.com/


OBSはyoutubeとかの配信系でよくつかわれるソフトです。
HDMI to USB変換で認識したカメラデバイスをOBSに接続すればディスプレイをケチることができます。
以下のようにつないで環境作ります

```mermaid
  graph TD;
      RPIZW---|microUSB PWR|何か適当なUSB5V供給系;
      RPIZW-->|microUSB USB|microUSBToUSB_A;
      microUSBToUSB_A-->|USB|XBOX360controller;
      RPIZW-->|miniHDMI|miniHDMIToHDMI;
      miniHDMIToHDMI-->|HDMI|HDMItoUSB;
      HDMItoUSB-->|USB|PCUSB_PortA;
      PCUSB_PortA-->|WindowsDevice|OBSStudio_Camera;
      RPIZW-->|uart:GPIO14, 15|UartToUSBConverter
      UartToUSBConverter-->|USB|PCUSB_PortB;
```


こうすれば貴重なディスプレイを消費することなくひとまず遊ぶことができます。つまりOBSでカメラデバイスとしてHDMIを認識できればなんでもいいです。
ディスプレイ余ってる方は普通につないでください。ただ、スクショとかとる時OBS便利なんですよね。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34428/1140c966-a202-870b-637f-1b8d889ce982.png)

若干ネタバレになりますが↑こんな感じ。実はHDMI Audioも対応している機材ならAudio Deviceとしても認識するので、HDMI音声を取ることができます。
遅延もまぁ若干許せないレベルですが、別件でswitchとかを繋いで遊ぶこともできます（すべてはディスプレイをケチるため）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34428/d31a636e-8928-8388-c0d6-ddb9cf77204f.png)
こんな感じよ。

# まずは何もしないkernel.imgを作る(Lチカで動くか確認程度)
- RPIZWのSDCardからstart.elfが読み込まれてGPU側が起動した後、RPIZWはSDCardのkernel.imgを読んでconfig.txtの仕様に基づきRAMにロード、ARM側が動き始めます。
- 今回はまずは無限ループを作ってひとまず起動できているかをLEDチカチカ(Lチカ)なりで確認します
- サンプルは以下です。ここではタイマをつかってRPIZWの表面に実装されているLEDがチカチカできていればひとまずOKです

https://github.com/kumaashi/RaspberryPI/tree/master/RPIZEROW/Sample_TimerArmIRQ

- こいつをベースにUSBでやり取りする機能を追加しました。


# RPIZW上に実装されているUSBコントローラについて
- RPIZW側の仕様は以下の"15.USB"に記載されています。

https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

 
- 該当PDFのUSBの章をみると"Synopsys IP" とあり、さらにraspbianの設定を見るとDWC2のUSBcoreが使われるようです。そして初期configrationが決まっています。本家のBCM2835のPDFにはレジスタの情報がほぼ無いです。したがっていろんなsourceを見たりなどしてレジスタの仕様を理解するしかないです。
- DWC2のUSBコントローラは様々なSoC系だったりにカスタムされてIPとして実装されているらしくかなり多くのリファレンスがあります。併せて細かい違いがありますがそれも参考にしました。
一番参考にしたページはどうも同じIPが入っていそうな以下のリファレンスでした。

https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html#sfo1410069623936.html#sfo1410069623936


# そもそものUSBの仕様について
- 私はこんなフラットな環境でUSB周りのdriverを書いたことがなかったのでまずはべ－シックな仕様を理解するために以下のPDFを参考に読みました。

https://avr.jp/user/EC/PDF/USBspcs.pdf

- DWC2がやってくれるのはUSBの仕様に基づいてトポロジカルな通信の部分行ってくれるだけで、意味のあるpacketを組み立ててどんなsetupして出てきた意味を解釈してout/inするかは全部ソフトで行います。



# XBOX360コントローラの入力について
- 最終目標はXBOX360コントローラで入力を取ることなので、以下を見てdata formatを理解しておきます。先人に感謝です。
- ref:https://web.archive.org/web/20190320082905/http://euc.jp/periphs/xbox-controller.ja.html
- ref:https://web.archive.org/web/20150304063604/http://free60.org/wiki/GamePad

また、lsusbでとったdescriptorをあらかじめ以下の通りメモっておきます。

https://github.com/kumaashi/RaspberryPI/blob/master/Memo/usb/POWER_A_xbox_controller.txt

- まず、そもそもlibusbでusbの値readできるかをlibusbpyで遊んでみました。以下でチェックしました。

https://github.com/kumaashi/dumped/tree/master/Python/usb/readusb

- 遊んでみると、Configration0, Interface0, Endpoint0x81(IN)で、interrupt転送でデータが受信できることが分かったので同じようにできればいいはずで、XBOX360コントローラ以外はつながっていないという前提だと以下だけでやり取りできそうです

- Control転送 : GET_DESCRIPTOR用のpacketををout用のchannelのバッファに設定して、DMA転送有効にすると、レスポンスがin用のchannelに入ってくるので、USBの何なのかを取得できる

- Control転送 : SET_ADDRESSでUSBデバイスをやり取りするための一意にきまるindexをout用のchannelのバッファに設定して、DMA転送有効にするとUSB機器がそのADDRESSを使ってやり取りすると認識するはず。

- Control転送 : SET_CONFIGRATIONで使いたい機能を指定します。同様にout用のchannelにSET_CONFIGRATIONのpacketを設定してDMA有効 -> INにレスポンスが入ってくるので設定できているかをレスポンスで確認します。

- Interrupt転送 : レスポンスが正常だったら、あとは定期的にXBOXコンの割り込みを取るためにIN側のDMA転送を有効にしながらコントローラに入力された値を取り、有効値だったらparseして解釈します。無効値だったら無視としました


# RPIZW用にDWC2のレジスタリストを作る
- RPIZW用にDWC2のレジスタリストを作ります。このcontrollerのdriverをいくつか見ましたが大体同じようなマクロの構成になるか、構造体でfield切るようなものになるようです。以下のように書いてみました。

https://github.com/kumaashi/RaspberryPI/blob/master/RPIZEROW/Sample_DWC2_USB05/hw.h#L43



```c
#define USB_BASE                   (0x20980000)
#define USB_EX_MDIO_CNTL           ((volatile uint32_t *)(USB_BASE + 0x080))
#define USB_EX_MDIO_DATA           ((volatile uint32_t *)(USB_BASE + 0x084))
#define USB_EX_MDIO_VBUS           ((volatile uint32_t *)(USB_BASE + 0x088))

/* global */
#define USB_GOTGCTL                ((volatile uint32_t *)(USB_BASE + 0x000))
#define USB_GOTGINT                ((volatile uint32_t *)(USB_BASE + 0x004))
#define USB_GAHBCFG                ((volatile uint32_t *)(USB_BASE + 0x008))
#define USB_GUSBCFG                ((volatile uint32_t *)(USB_BASE + 0x00C))
#define USB_GRSTCTL                ((volatile uint32_t *)(USB_BASE + 0x010))
#define USB_GINTSTS                ((volatile uint32_t *)(USB_BASE + 0x014))
#define USB_GINTMSK                ((volatile uint32_t *)(USB_BASE + 0x018))
#define USB_GRXSTSR                ((volatile uint32_t *)(USB_BASE + 0x01C))
#define USB_GRXSTSP                ((volatile uint32_t *)(USB_BASE + 0x020))
#define USB_GRXFSIZ                ((volatile uint32_t *)(USB_BASE + 0x024))
#define USB_GNPTXFSIZ              ((volatile uint32_t *)(USB_BASE + 0x028))
#define USB_GNPTXSTS               ((volatile uint32_t *)(USB_BASE + 0x02C))
#define USB_GI2CCTL                ((volatile uint32_t *)(USB_BASE + 0x0030))
#define USB_GPVNDCTL               ((volatile uint32_t *)(USB_BASE + 0x0034))
#define USB_GGPIO                  ((volatile uint32_t *)(USB_BASE + 0x0038))
#define USB_GUID                   ((volatile uint32_t *)(USB_BASE + 0x003c))
#define USB_GSNPSID                ((volatile uint32_t *)(USB_BASE + 0x0040))
#define USB_GHWCFG1                ((volatile uint32_t *)(USB_BASE + 0x0044))
#define USB_GHWCFG2                ((volatile uint32_t *)(USB_BASE + 0x0048))
#define USB_GHWCFG3                ((volatile uint32_t *)(USB_BASE + 0x004c))
#define USB_GHWCFG4                ((volatile uint32_t *)(USB_BASE + 0x0050))
#define USB_GLPMCFG                ((volatile uint32_t *)(USB_BASE + 0x0054))
#define USB_GPWRDN                 ((volatile uint32_t *)(USB_BASE + 0x0058))
#define USB_GDFIFOCFG              ((volatile uint32_t *)(USB_BASE + 0x005c))
#define USB_GPTXFSIZ               ((volatile uint32_t *)(USB_BASE + 0x0100))
#define USB_DPTXFSIZN(_a)          ((volatile uint32_t *)(USB_BASE + 0x104 + (((_a) - 1) * 4)))
#define USB_DCFG                   ((volatile uint32_t *)(USB_BASE + 0x800))
#define USB_DCTL                   ((volatile uint32_t *)(USB_BASE + 0x804))
#define USB_DSTS                   ((volatile uint32_t *)(USB_BASE + 0x808))
#define USB_DIEPMSK                ((volatile uint32_t *)(USB_BASE + 0x810))
#define USB_DOEPMSK                ((volatile uint32_t *)(USB_BASE + 0x814))
#define USB_DAINT                  ((volatile uint32_t *)(USB_BASE + 0x818))
#define USB_DAINTMSK               ((volatile uint32_t *)(USB_BASE + 0x81C))
#define USB_DTKNQR1                ((volatile uint32_t *)(USB_BASE + 0x820))
#define USB_DTKNQR2                ((volatile uint32_t *)(USB_BASE + 0x824))
#define USB_DTKNQR3                ((volatile uint32_t *)(USB_BASE + 0x830))
#define USB_DTKNQR4                ((volatile uint32_t *)(USB_BASE + 0x834))
#define USB_DIEPEMPMSK             ((volatile uint32_t *)(USB_BASE + 0x834))
#define USB_DVBUSDIS               ((volatile uint32_t *)(USB_BASE + 0x828))
#define USB_DVBUSPULSE             ((volatile uint32_t *)(USB_BASE + 0x82C))
#define USB_DIEPCTL0               ((volatile uint32_t *)(USB_BASE + 0x900))
#define USB_DIEPCTL(_a)            ((volatile uint32_t *)(USB_BASE + 0x900 + ((_a) * 0x20)))
#define USB_DOEPCTL0               ((volatile uint32_t *)(USB_BASE + 0xB00))
#define USB_DOEPCTL(_a)            ((volatile uint32_t *)(USB_BASE + 0xB00 + ((_a) * 0x20)))
#define USB_DIEPINT(_a)            ((volatile uint32_t *)(USB_BASE + 0x908 + ((_a) * 0x20)))
#define USB_DOEPINT(_a)            ((volatile uint32_t *)(USB_BASE + 0xB08 + ((_a) * 0x20)))
#define USB_DIEPTSIZ0              ((volatile uint32_t *)(USB_BASE + 0x910))
#define USB_DOEPTSIZ0              ((volatile uint32_t *)(USB_BASE + 0xB10))
#define USB_DIEPTSIZ(_a)           ((volatile uint32_t *)(USB_BASE + 0x910 + ((_a) * 0x20)))
#define USB_DOEPTSIZ(_a)           ((volatile uint32_t *)(USB_BASE + 0xB10 + ((_a) * 0x20)))
#define USB_DIEPDMA(_a)            ((volatile uint32_t *)(USB_BASE + 0x914 + ((_a) * 0x20)))
#define USB_DOEPDMA(_a)            ((volatile uint32_t *)(USB_BASE + 0xB14 + ((_a) * 0x20)))
#define USB_DTXFSTS(_a)            ((volatile uint32_t *)(USB_BASE + 0x918 + ((_a) * 0x20)))
#define USB_HCFG                   ((volatile uint32_t *)(USB_BASE + 0x0400))
#define USB_HFIR                   ((volatile uint32_t *)(USB_BASE + 0x0404))
#define USB_HFNUM                  ((volatile uint32_t *)(USB_BASE + 0x0408))
#define USB_HPTXSTS                ((volatile uint32_t *)(USB_BASE + 0x0410))
#define USB_HAINT                  ((volatile uint32_t *)(USB_BASE + 0x0414))
#define USB_HAINTMSK               ((volatile uint32_t *)(USB_BASE + 0x0418))
#define USB_HFLBADDR               ((volatile uint32_t *)(USB_BASE + 0x041c))
#define USB_HPRT0                  ((volatile uint32_t *)(USB_BASE + 0x0440))
#define USB_HCCHAR(_ch)            ((volatile uint32_t *)(USB_BASE + 0x0500 + 0x20 * (_ch)))
#define USB_HCSPLT(_ch)            ((volatile uint32_t *)(USB_BASE + 0x0504 + 0x20 * (_ch)))
#define USB_HCINT(_ch)             ((volatile uint32_t *)(USB_BASE + 0x0508 + 0x20 * (_ch)))
#define USB_HCINTMSK(_ch)          ((volatile uint32_t *)(USB_BASE + 0x050c + 0x20 * (_ch)))
#define USB_HCTSIZ(_ch)            ((volatile uint32_t *)(USB_BASE + 0x0510 + 0x20 * (_ch)))
#define USB_HCDMA(_ch)             ((volatile uint32_t *)(USB_BASE + 0x0514 + 0x20 * (_ch)))
#define USB_HCDMAB(_ch)            ((volatile uint32_t *)(USB_BASE + 0x051c + 0x20 * (_ch)))
#define USB_PCGCTL                 ((volatile uint32_t *)(USB_BASE + 0x0e00))
#define USB_PCGCCTL1               ((volatile uint32_t *)(USB_BASE + 0xe04))
#define USB_HCFIFO(_ch)            ((volatile uint32_t *)(USB_BASE + 0x1000 + 0x1000 * (_ch)))
#define USB_EPFIFO(_a)             ((volatile uint32_t *)(USB_BASE + 0x1000 + ((_a) * 0x1000)))
#define USB_HPRT0_HCINT            (1 << 25)
#define USB_HPRT0_PRTINT           (1 << 24)
#define USB_HPRT0_INCOMPLP         (1 << 21)
```

- 今はひとまず全部書いていますがHostとして使う場合ほとんどの制御は、USB_GxxxxというGlobalな設定レジスタで全体的な設定を行い、Hostの設定はUSB_Hxxxとなるレジスタ群で行います。

# RPIZWのUSB機能部を電源ONする
- RPIZWのstart.elfを含め最近のRPIは省電力にかなり特化しているのか、起動に不必要な周辺機器や機能部は全部電源が入っていないようにみえていて、まずはUSB機能部に電源を入れなければなりません。
- firmware mailbox経由でUSBの電源を入れます。mailboxについては以下を参照してください。
ref : https://github.com/raspberrypi/linux/blob/75f1d14cee8cfb1964cbda21a30cb030a632c3c2/drivers/soc/bcm/bcm2835-power.c#L283
ref : https://github.com/raspberrypi/firmware/wiki/Mailboxes
ref : https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface

- もちろんconfig.txt経由でも電源入れられるようですがひとまずmailbox経由でマニュアルで入れておきます
- 書いたmailboxのコードは以下。power domainは公式のlinuxsourceを参考にしました
ref:https://github.com/kumaashi/RaspberryPI/blob/master/RPIZEROW/Sample_DWC2_USB05/mailbox.c
ref:https://github.com/raspberrypi/linux/blob/75f1d14cee8cfb1964cbda21a30cb030a632c3c2/drivers/soc/bcm/bcm2835-power.c#L283

- XBOX360controllerの場合電源が入ると、真ん中のシイタケマークが光るのでひとまずはこれでOKでしょう

# DWC2のリセット、HostPort0のリセットを行う

XBOX360コントローラをRPIZWにつなぎ、以下を行います。

1. DWC2のReset ( USB_GRSTCTL.bit0 = 1を書き込んで待つ )
2. DWC2のDMA転送を有効にする (USB_GAHBCFG.bit5 = 1)
3. HostPort0のスピードをFSにする
4. HostPort0の電源を入れる
5. HostPort0をリセットする
6. HostPort0をリセット解除する

こうするとあとはRPIZW側がHOSTとなりもろもろおしゃべりできるようになるはず。
1～6をコードにすると以下のような感じ
```c
void rpiusb_core_reset() {
	//Reset Clock
	*USB_PCGCTL = 0;
	SLEEP(0x100000);

	//RESET DWC2
	*USB_GRSTCTL = (1 << 0);
	SLEEP(0x400000);

	//ENABLE DMA
	*USB_GAHBCFG |= (1 << 5);

	//Enable Global Intr
	*USB_GAHBCFG |= (1 << 0);
}

void rpiusb_hc_prt_poweron_reset() {
	//set FS
	*USB_HPRT0 |= (1 << 17); //FS
	*USB_HPRT0 |= (1 << 3);

	// USB Host power on
	*USB_HPRT0 |= (1 << 12);
	SLEEP(WAIT_CNT * 10);

	//RESET
	*USB_HPRT0 |= (1 << 8);
	SLEEP(WAIT_CNT * 10);

	//RESET deassert
	*USB_HPRT0 &= ~(1 << 8);
	SLEEP(WAIT_CNT * 10);
}
```

https://github.com/kumaashi/RaspberryPI/blob/master/RPIZEROW/Sample_RPIUSB_00/usb.c#L312

# RPIZWからSETUP Packetを飛ばす
- DWC2はHostとして動くときにデータのやり取りをchannelという単位でやり取りする模様です
- channnl系のレジスタは、USB_HCxxxというレジスタ表記がなされているようです(ページ末のリファレンス周り参照)
- このchannelのDMA転送を使ってUSBのsetupパケットのout/inをおこないます。

また、USBのSETUPパケットを含めて何かしらの設定をするときのペイロードの仕様が決まっています。
PDFは本家の以下からdownloadできます。
https://www.usb.org/document-library/usb-20-specification

たとえば私はsetupでは以下の通り構造体を作って使ってみました
```c
//ref:https://github.com/kumaashi/RaspberryPI/blob/master/RPIZEROW/Sample_DWC2_USB05/usb.c#L12
typedef struct rpiusb_spec_request_t {
	uint8_t bmRequestType; 
	uint8_t bRequest; 
	uint16_t wValue; 
	uint16_t wIndex; 
	uint16_t wLength; 
} __attribute__((__packed__)) rpiusb_spec_request;
```
https://avr.jp/user/EC/PDF/USBspcs.pdf

↑から引用。以下の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34428/a6d2d9ea-ad74-bd12-28d2-537a4e88c33a.png)

で、DWC2のレジスタを使って、USBの機器にcontrol転送でsetup packetを飛ばす場合以下の手順で行います
(以下参考にしつつ)
https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html#sfo1410069623936.html#sfo1410069623936

```text
https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html#sfo1410069410840.html
1.USB_HCCHAR0を設定
eptype=0, epdir=out, epnum=0, mps=0x40, ec=1 (ch0, control転送で、hostからみてout, epnum0, mpsはとりあえずmax, 1 transaction)

2.USB_HCCHAR1を設定
eptype=0, epdir=in, epnum=0, mps=0x40, ec=1 (ch0, control転送で、hostからみてon, epnum0, mpsはとりあえずmax, 1 transaction)

https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html#sfo1410069414912.html
3.USB_HCTSIZ0を設定
doping=0, pid=3(Setup), pkgcnt=1, xfersize=setup packetのサイズ

4.USB_HCTSIZ1を設定
doping=0, pid=2(DATA1), pkgcnt=1, xfersize=responseのサイズ

5.USB_HCDMA0に送信packetのバッファを設定
6.USB_HCDMA1に受信packetのバッファを設定

7.USB_HCCHAR0のbit31=1 (DMA enable) 設定して送信開始
8.USB_HCCHAR1のbit31=1 (DMA enable) 設定して受信開始

https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html#sfo1410069413040.html
9.USB_HCINT0, USB_HCINT1をreadして成功、エラーなどのハンドリングを行う
10. ハンドリングが終わったらUSB_HCINT0, USB_HCINT1で終わったbitをclearする(writeするとbitが落ちる仕様のようです)
各bitがstatusに応じて1になるので、それぞれ頑張ってハンドリングします。

```
これで送信、受信ができます。

1. DWC2のReset ( USB_GRSTCTL.bit0 = 1を書き込んで待つ )
2. DWC2のDMA転送を有効にする (USB_GAHBCFG.bit5 = 1)
3. HostPort0のスピードをFSにする
4. HostPort0の電源を入れる
5. HostPort0をリセットする
6. HostPort0をリセット解除する
7. Control転送 : GET_DESCRIPTORでDescriptorを送信、受信できたDescriptorを本来ならここでparseして何の機器なのかを判別する
8. Control転送 : SET_ADDRESSで一意にやり取りするアドレスをUSB機器にControl転送で送信、statusを見る。
9. Control転送 : SET_DESCRIPTORでConfigrationを選択、SETUP_PACKETを送信、statusを見る
10. Interrupt転送 : HOST側からGET_DESCRIPTORでとれたpollingタイムを参考に実行、in_bufferに入ってくる値が有効なら値をparseする。
11. 10を繰り返す。

# 結果
で、出来上がったのがこちら。

https://github.com/kumaashi/RaspberryPI/tree/master/RPIZEROW/Sample_RPIUSB_00

動いているところは以下。

https://www.youtube.com/watch?v=nlzPOU9vqg0

画面に色々出ていますが、これは今回説明しないDMA転送の矩形転送を使ったフォント表示で何が起こっているのかを可視化しているものです。RPIのDMA転送は矩形転送があって、こんな感じでGPU使わなくても表示用メモリに高速に転送できます。

アナログスティックもPOVもトリガーの値も取れています。
これでひとまず入力は取れている状態です。


備考として素朴に色をただ取ってただけの頃は以下みたいな感じ
https://www.youtube.com/watch?v=T779XPdI1dY



# 色々トラブったこととか感想とか
- RPI用のUSBドライバを書くときの資料がLinuxのドライバとEDK2のDWC2用のシンプルなスクラッチドライバが参考になりました。あとはレジスタ名で調べると全然知らないSoCのIPで同じようなoffsetが切られたレジスタファイルの構成をしたものから推測しながら調べていきました。

- 一瞬USBの電源が入ってGET_DESCRIPTORが成功する
これが一番困ったところでなまじ最初USB機能部に電源が入っている状態になってGET_DESCRIPTORが成功して先に進み、その後二度とUSB経由で通信できなくなります。そもそも電源が継続的に印加されていないという事に気づいたのはいろいろ手を動かし始めて1week後の事でした。実際にUSBのVBUSにテスターを当てても常に電源が印加されている状態で、VBUSでもない、と気づいて初めてそこでbcm2835-power.cを読みログを入れてkernel再ビルドして動かして判明しました。最初から下調べすればよかったなぁと思います。

- USBのstackを真面目に対応しようとした場合とても複雑
今回の工作だととにかく通信して入力を割といいレスポンスで取れればいい、というのがゴールでした。さらに今XBOX360コントローラに特化しています。
別のデバイス(昔のPSコンとか安いPC用のcontrollerとか)も繋いでいろいろできるところを手元で作ったんですが、これ作るたびに色々変わるから全部対応するのはしんどいなぁドライバ書いている方本当にすごいなと思ったという小学生並みの感想がでました。USBコントローラも阿僧祇那由他の機器があるはずで周辺機器のドライバ書いている方に足を向けて寝れません。

- USBのinterrupt転送についての誤解
USB割り込み自体はUSB機器側が作るんですがそれを割り込みと判断して通信するのはあくまでもHost側のポーリングにゆだねられていることに気づけませんでした。そのため、いつになったらデータが取れるのかわかりませんでした。Hostとして制御する場合仕様書を読むときに「HOSTから何が起こっているのか」を常に意識しながらじゃないと無理解があってそもそもどう通信しているのかすら絵にかいてもわからなくなっていました。

https://beyondlogic.org/usbnutshell/usb4.shtml

ここをひたすら読みました。まだ実動作と比較すると理解できていないことが多いです。

- エラーハンドリング
エラーだった場合の処理の答えが公式に提供されていないのでエラーが起きた場合はどうすべきかとても悩みました。大体はlinux側のハンドリングを参考にしましたが、大抵再送かもう一度最初から(retry)という流れです。DWC2の同じIPを使っていると思われるSoCの場合はいくつかのものにはどういう遷移をたどりましょうみたいなガイドラインがあってある程度参考にしました。現状はエラーだったら入力取得は失敗で、もう一度リトライという戦略に落ち着きました。

# しめの感想
baremetalな状態から割と素朴なUSBコントローラのドライバ書いても何となく通信できるのはうれしいし、USB自体は割としぶとい仕様になっていることも理解として深まりました。RPIZWで適当なUSBゲームコントローラだったり割り込みでレポートを取れるデバイスであればこれを下地に色々入力系統をBaremetalで書けるのかなと思います(ただしなんとか仕様が明らかになっているものに限る)
DWC2のドライバを適当に書いたら機能するものではなく、その先のUSB周りの仕様理解と実装でほとんどの時間を使いました。

# 参考にした資料URL

#### とても参考にした記事
https://qiita.com/eggman/items/5b9e4f5802f0ec22921f


#### USB系でとても参考にした記事
https://www.beyondlogic.org/usbnutshell/usb1.shtml


## ラズパイ系USB資料
https://raspberrypi.stackexchange.com/questions/47408/raspberry-pi-2-bare-metal-dma
https://paulwratt.github.io/rpi-internal-registers-online/Region_USB.html
https://github.com/tianocore/edk2-platforms/blob/master/Platform/RaspberryPi/Drivers/DwUsbHostDxe/DwUsbHostDxe.c
https://github.com/LdB-ECM/Raspberry-Pi/tree/master/Arm32_64_USB
https://github.com/eggman/raspberrypi/tree/master/qemu-raspi3/usb02
https://github.com/Chadderz121/csud/blob/master/source/hcd/dwc/designware20.c
https://elixir.bootlin.com/linux/latest/source/drivers/usb/dwc2/core_intr.c
https://elixir.bootlin.com/linux/latest/source/drivers/usb/dwc2/hcd.c#L2158


## XBOXコントローラのSource and 解析資料
https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
https://web.archive.org/web/20150304063604/http://free60.org/wiki/GamePad
https://web.archive.org/web/20190320082905/http://euc.jp/periphs/xbox-controller.ja.html
https://www.partsnotincluded.com/understanding-the-xbox-360-wired-controllers-usb-data/

## USB周りの資料
https://avr.jp/user/EC/PDF/USBspcs.pdf
https://usbmadesimple.co.uk/ums_4.htm
https://wiki.osdev.org/USB_Human_Interface_Devices
https://www.usb.org/sites/default/files/documents/hid1_11.pdf
https://ww1.microchip.com/downloads/en/Appnotes/01144a.pdf
https://www.mail-archive.com/edk2-devel@lists.01.org/msg50471.html
https://blog.digital-scurf.org/posts/building-usb-descriptors/
https://github.com/haiku/haiku/tree/master/src/add-ons/kernel/drivers/input/usb_hid
https://microchipdeveloper.com/usb:enumeration
http://www.picfun.com/usb02.html
https://beyondlogic.org/usbnutshell/usb4.shtml
https://beyondlogic.org/usbnutshell/usb6.shtml#StandardDeviceRequests



終わり