USBコントローラのドライバメモ

誰得

  1. USBコントローラとメモリバスをつないだ論理をFPGAに焼いてもらい、プロセッサからメモリアクセスでUSBコントローラのレジスタにアクセスできるようにしてもらう。(要: 約1年)
    • ほんとは割込み線もつないでもらうのだが、無くても耐える(爆笑)のと外部割込みは2本しかなくてほんとにUSBから正しく割込みがかかっているのかよくわかんないためスルー(爆笑)。
  2. そのうちに、各種調査を進める。
    • とりあえずUSBコントローラのマニュアルをガン見。レジスタアドレスに#defineで名前を付けてヘッダに書いておく。あと抜き差しバスリセットあたりの分かりやすい割込みハンドラを書いておく。
    • 量はマジキチだがとりあえずUSB2.0の仕様書をダウンロード。
    • インタフェース*1のUSBでのシリアルポート実装事例を読む(←重要(笑))。
    • シリアル通信の標準クラスはCommunication Device Class(CDC)というらしい。メリット: これに従うと、PC側のドライバを書く必要がない(OSに標準で用意されている)。OSごとにそのOS用のドライバがある←ドライバとかどう考えても互換性が残当なので重要。デメリット: ただのシリアル通信と言ってもなんやかんやで仕様がしっかりしててちょっとめんどくさい。
    • 記事に従ってCDC1.2の仕様書も持ってくる。なんかエラッタとか書いてあって右に修正点を書くスペースが入ってて印刷するとメインエリアが小さくなって納得がいかない。
  3. USBのケーブルを買ってきて、つないでみる。
    • 割込みがかかるので、マニュアルの通りに状態遷移させてやるとPCからコントロール転送が飛んでくるのをキャッチできる。
  4. コントロール転送はエンドポイント0(EP0)で行う。要はEP0からの割込みをハンドリングする。
    • USBの通信は(多分全てが)ホスト主導で行う。なのでペリフェラル側はイベント駆動コードだけでいいのできっと楽な方。
    • ホストからのコントロール要求を受信するとEP0から割込みがかかる。コントローラのレジスタ(16bit)4つに受信データが入っているのでデコードする。
      • bmRequestType(8bit): 転送方向とかそういうの。bRequestからでも判断できちゃいそうではある。
      • bRequest(8): リクエスト番号。最重要。
      • wValue(16): パラメータ。bRequest依存。
      • wIndex(16): パラメータその2。bRequest依存。
      • wLength(16): データ長。
  5. 上記のリクエストを出力してPCのドライバが何を送ってきているかを確認し、その意味を調べ、適切な応答をする。以下繰り返し。


コントロール転送シーケンス
多分最後まで行くとPCのOSのデバイスマネージャ的なものから確認できる、と思う。lsusbとかもろそのまんま。

  1. USBに何か刺さるとデバイスディスクリプタを問い合わせてくる。wLength=0x40で。
  2. これに応答するとバスリセットされる(←初狩り)。USBアナライザ的な何かで見たとかいうページによると、数バイト受信したらリセットをかけてしまうらしい。なんやねん*2
  3. SET_ADDRESSが送られてくる。これはwValueに値が入っていて完結しており、その後の送信または受信はない。マニュアル通りにレジスタにwValueを設定してやる。
  4. その後、改めてデバイスディスクリプタをwLength=18(サイズぴったり)で問い合わせてくる。構造体で用意しておいたディスクリプタを送信してやる。
  5. コンフィギュレーションディスクリプタをwLength=9で問い合わせてくる。
    • この中にwTotalLengthというフィールドがある。この後ろにインタフェースがN個、インタフェースの中にエンドポイントがM個、のようなデータが続き、それを全部含めたサイズをここに入れるらしい。←詰み
    • この後、PCは(wLength=さっきのwTotalLength)でもう1回コンフィギュレーションディスクリプタを問い合わせてくるらしい。つまり、コンフィギュレーション情報を全部パディングなしで詰めたものをメモリに用意しておいてそのまま流すのが自然ってことですね…。


CDC固有のインタフェースとエンドポイント構成

  • インタフェース0(制御用)
    • なんかよくわかんない固有ディスクリプタ4個
    • エンドポイント1(インタラプト)
  • インタフェース1(データ用)
    • エンドポイント2(バルク)
    • エンドポイント3(バルク)


サンプルあったらそこに入ってそうなC構造体定義プレゼントサービス
※USBのデータはほぼ8bit/16bitのデータ列で、アラインされていません。余裕で16bitデータが奇数オフセットに来ます。これは構造体のパディングを無効にしてコンパイラに頑張って変な代入をしてもらうのが一番読みやすいかな。
※ついでにbLengthにsizeof(構造体)を入れればよくなる。
※USBの複数バイトデータはリトルエンディアンです。ビッグエンディアンのプロセッサでは2byteデータの格納前にバイトスワップを挟んでください。

#define USB_U16(hw) USB_SWAP(hw)
#define USB_PACK __attribute__((packed))

#define USB_DEVICE_DESC_TYPE 1
typedef struct {
    usb_u8  bLength;
    usb_u8  bDescriptor;
    usb_u16 bcdUSB;
    usb_u8  bDeviceClass;
    usb_u8  bDeviceSubClass;
    usb_u8  bDeviceProtocol;
    usb_u8  bMaxPacketSize0;
    usb_u16 idVendor;
    usb_u16 idProduct;
    usb_u16 bcdDevice;
    usb_u8  iManufacturer;
    usb_u8  iProduct;
    usb_u8  iSerialNumber;
    usb_u8  bNumConfigurations;
} USB_PACK usb_device_desc;

#define USB_CONF_DESC_TYPE 2
typedef struct {
    usb_u8  bLength;
    usb_u8  bDescriptor;
    usb_u16 wTotalLength;
    usb_u8  bNumInterfaces;
    usb_u8  bConfigurationValue;
    usb_u8  iConfiguration;
    usb_u8  bmAttributes;
    usb_u8  bMaxPower;
} USB_PACK usb_conf_desc;

#define USB_IFACE_DESC_TYPE 4
typedef struct {
    usb_u8 bLength;
    usb_u8 bDescriptorType;
    usb_u8 bInterfaceNumber;
    usb_u8 bAlternateSetting;
    usb_u8 bNumEndpoints;
    usb_u8 bInterfaceClass;
    usb_u8 bInterfaceSubClass;
    usb_u8 bInterfaceProtocol;
    usb_u8 iInterface;
} USB_PACK usb_iface_desc;

#define USB_EP_DESC_TYPE 5
typedef struct {
    usb_u8  bLength;
    usb_u8  bDescriptorType;
    usb_u8  bEndpointAddress;
    usb_u8  bmAttributes;
    usb_u16 wMaxPacketSize;
    usb_u8 bInterval;
} USB_PACK usb_ep_desc;
/*
 * CDC Specific Descriptors
 */
// Header Functional Descriptor
typedef struct {
    usb_u8  bFunctionLength;
    usb_u8  bDescriptorType;
    usb_u8  bDescriptorSubtype;
    usb_u16 bcdCDC;
} USB_PACK usb_cdc_hfd;

// Abstract Control Management Functional Descriptor
typedef struct {
    usb_u8 bFunctionLength;
    usb_u8 bDescriptorType;
    usb_u8 bDescriptorSubtype;
    usb_u8 bmCapabilities;
} USB_PACK usb_cdc_acmfd;

// Union Functional Descriptor
typedef struct {
    usb_u8 bFunctionLength;
    usb_u8 bDescriptorType;
    usb_u8 bDescriptorSubtype;
    usb_u8 bMasterInterface;
    usb_u8 bSlaveInterface0;
} USB_PACK usb_cdc_ufd;

// Call Management Functiona; Descriptor
typedef struct {
    usb_u8 bFunctionLength;
    usb_u8 bDescriptorType;
    usb_u8 bDescriptorSubtype;
    usb_u8 bmCapabilities;
    usb_u8 bDataInterface;
} USB_PACK usb_cdc_cmfd;

*1:そういう雑誌。

*2:いや分からんでもないが…。