Skip to content
uchan-nos edited this page Jan 20, 2018 · 13 revisions

Configure a device

このページでは USB デバイスを通信できるように設定する方法を説明する.

Set address to a device

デバイスに対し Device Slot の割り当てが済んだら,次は Default Control Pipe が使えるように設定する.

Input Context を用意して,Slot Context と Endpoint Context 0 を有効化する.

struct InputContext inp_ctx;
inp_ctx.input_control_context.add_context_flags = 0x00000003u;

struct InputContext の具体的な構造は xHCI Spec 6.2.5 を参照.

Add Context Flags は 32 ビット幅である.ビット位置と Slot Context および Endpoint Context が対応付いていて,有効化したい Context のビットを 1 にすると有効化される.ビット位置と Context の関係は次の通り.

ビット位置(DCI) 対応する Context
0 Slot Context
1 EP Context 0
2 EP Context 1 OUT
3 EP Context 1 IN
... ...
30 EP Context 15 OUT
31 EP Context 15 IN

今回は Slot Context と EP Context 0 を有効化したいので Add Context Flags に 3 を設定する.

次に各 Context に値を設定する.まず Slot Context から.

inp_ctx.slot_context.route_string = 0;
inp_ctx.slot_context.root_hub_port_num = port_index + 1;
inp_ctx.slot_context.context_entries = 1;
inp_ctx.slot_context.speed =
    (port_register_set[port_index].PORTSC >> 10) & 0xfu;

Route String は,ハブを介さずホストコントローラにつながったデバイスなら 0 となる.Route String の詳しいフォーマットは USB Spec Figure 8-32 を参照. Context Entries は有効にする Endpoint Context の数を設定する.今は Endpoint Context 0 だけ有効化するので 1 とする. Speed に設定すべき値は,規格書を読んでも複雑で良くわからないので,取りあえず PORTSC レジスタの Port Speed フィールドの値をコピーしておく.(コピーしておけばエラーは起きないようだ)

次に Endpoint Context 0 用の Transfer Ring を用意する.Transfer Ring は Command Ring と基本的に同じものである.

alignas(64) struct RingManager tr_ep0;
memset(tr_ep0.buf, 0, sizeof(tr_ep0.buf));
tr_ep0.cycle_bit = 1;
tr_ep0.write_index = 0;

次に Endpoint Context 0 の設定を行う.

int dci = 1;
inp_ctx.ep_contexts[dci-1].ep_type = 4;
inp_ctx.ep_contexts[dci-1].tr_dequeue_pointer = (uint64_t)tr_ep0.buf;
inp_ctx.ep_contexts[dci-1].dequeue_cycle_state = tr_ep0.cycle_bit;
inp_ctx.ep_contexts[dci-1].max_burst_size = 0;
inp_ctx.ep_contexts[dci-1].mult = 0;
inp_ctx.ep_contexts[dci-1].interval = 0;
inp_ctx.ep_contexts[dci-1].max_primary_streams = 0;
inp_ctx.ep_contexts[dci-1].error_count = 3;

if (inp_ctx.slot_context.speed == 4) // Super Speed
    inp_ctx.ep_contexts[dci-1].max_packet_size = 512;
else if (inp_ctx.slot_context.speed == 3) // High Speed
    inp_ctx.ep_contexts[dci-1].max_packet_size = 64;
else
    inp_ctx.ep_contexts[dci-1].max_packet_size = 8;

Max Burst Size * Mult はホストコントローラが一度に送受信するパケットの数を決める数値だ.ただ,Low/Full Speed のポートでは Max Burst Size は 0 でなければならない. キーボードは恐らく Low/Full Speed で実装されているだろうから,ここは 0 を設定しておく.

Interval はポーリング間隔を指定する.実際に有効な値が xHCI Spec Table 65 に示されているが,値の選択基準はよく分からない.筆者の環境だと 0 にしても取りあえず動作はした.

Max Packet Size はそのエンドポイントが一度に送受信できるパケットの大きさ(バイト数)である.Default Control Pipe ではポートのスピードによって設定できる値が決まる.

Slot Context と Endpoint Context 0 を設定したら Address Device Command を発行する.

struct AddressDeviceCommandTRB cmd;
cmd.input_context_pointer = (uint64_t)&input_context;
cmd.slot_id = assigned_slot_id;

ring_push(&cr, &cmd);
DOORBELL[0] = 0;

実行が完了するのを待つ.

while (er_front(0)->cycle_bit != er_cycle_bit);
while (er_front(0)->cycle_bit == er_cycle_bit) er_pop(0);

Get device descriptor

Address Device Command の実行が成功すると,そのデバイスの Default Control Pipe が使えるようになる. するとデバイスの情報を取得してデバイス種別(HID とか)を判別したり,必要な Endpoint に関する情報を得られる.

まずは Device Descriptor を得る.

#define DESC_DEVICE 1
#define DESC_CONFIGURATION 2
#define DESC_INTERFACE 4
#define DESC_ENDPOINT 5

uint8_t buf[128];
get_descriptor(assigned_slot_id, DESC_DEVICE, 0, buf, sizeof(buf));

get_descriptor は指定したディスクリプタを配列に読み込む関数で,何度か使うので関数とした.定義は次のようになっている.

void get_descriptor(
    uint8_t slot_id, uint8_t desc_type, uint8_t desc_index,
    uint8_t* buf, size_t buf_size)
{
}

Configure endpoints

Clone this wiki locally