mikanOS 6章

## 6.2 USBホストドライバ
USBマウスに対応する
→Universal Serial Bus
シリアルバス: バスには様々な機器からの信号がのる。シリアルとは、1本の信号線を使って、信号が1bitずつ送られるって意味。

USBホストコントローラー: USB機器とOSを繋ぐための制御チップ。OSはこのコントローラーを制御することで、USB機器と通信できるようになる。
ドライバ:USBホストコントローラーのような制御チップを操作するためのソフトウェアのこと。

- USBドライバの階層
    - クラスドライバ: USBターゲットの種類ごと。キーボードやマウスとかオーディオ機器とか。
    - USBバスドライバ: ホストコントローラーの詳細を隠してUSB規格で定められたAPIを提供する。e.g.) USBターゲットがどんな機器かを識別するGET_DESCRIPTORとか。
    - ホストコントローラドライバ: ホストコントローラーを制御する。
- PCIバスドライバ?

ホストコントローラードライバはホストコントローラーの規格別に作る必要がある。この本だとxHCLのみ対応。これに準拠したホストコントローラのことをxHCと呼ぶ。

## 6.3 PCIデバイスの探索
USBドライバを使ってマウスからデータを入力する。

流れ
- PCIバスに接続されたPCIデバイスを全て列挙する。
    - PCI: 部品とマザーボードを繋ぐための規格。xHCとかGPUとかネットワークカードとかがこれによって接続されている。
- 列挙されたデバイスの中からxHCを探す。
- xHCを初期化。
- USBバス上で、マウスを探す。
- マウスの初期化。
- マウスからデータを受信。

各PCIデバイスはPCIコンフィギュレーション空間を持っている。これを読むには、CONFIG_ADDRESSレジスタと、CONFIG_DATAレジスタを使う。CONFIG_ADDRESSレジスタに読み書きしたいPCIコンフィギュレーション空間の位置を設定してから、CONFIG_DATAを読み書きすることで、PCIコンフィギュレーション空間を読み書きできる。

- CONFIG_ADDRESSの構造:
ビット位置31: Enable bit
30-24: 予約領域
23-16: バス番号(0-31)
15-11: デバイス番号(0-31 1つのバスは最大32個のデバイスを持てる)
10-9:  ファンクション番号(0-7 1つのデバイスには8つのファンクションを持てる。)
7-0: レジスタオフセット(0-255 4バイト単位のオフセット)

```
  // #@@range_begin(make_address)
  /** @brief CONFIG_ADDRESS 用の 32 ビット整数を生成する */
  uint32_t MakeAddress(uint8_t bus, uint8_t device,
                       uint8_t function, uint8_t reg_addr) {
    // xを左にbitsだけだけビットシフト
    // ラムダ式は関数の内部で定義できる。
    auto shl = [](uint32_t x, unsigned int bits) {
        return x << bits;
    };

    return shl(1, 31)  // enable bit
        | shl(bus, 16)
        | shl(device, 11)
        | shl(function, 8)
        | (reg_addr & 0xfcu);
  }
  // #@@range_end(make_address)
  ```

- CONFIG_DATAとCONFIG_ADDRESSレジスタを読み書きする方法

メモリアドレス空間は、メインメモリ用。IOアドレス空間は周辺機器用という区別があり、PCIコンフィギュレーション空間は周辺機器なのでIOアドレス空間につながっている。

  - IOアドレス空間
      - コンピュータシステムでCPUが外部デバイス（キーボード、マウス、ディスクドライブなど）と通信するために使用する特別なアドレス空間


```
global IoOut32  ; void IoOut32(uint16_t addr, uint32_t data);
IoOut32:
// 引数で指定されたaddrに対して、32ビット整数dataを出力する。ABIの仕様により、引数addrはRDIレジスタ、dataはRSIレジスタに設定される。DIはRDIの下位16bit、ESIはRSIの下位32bit.
    mov dx, di    ; dx = addr
    mov eax, esi  ; eax = data
    out dx, eax ; IOMEM[dx] = eax
    ret

global IoIn32  ; uint32_t IoIn32(uint16_t addr);
IoIn32:
    mov dx, di    ; dx = addr
    in eax, dx ; eax = IOMEM[dx]. eaxに設定された値が戻り値になる。
    ret
```

これらを用いて下記のようにコンフィギュレーション空間を読みとる。
CONFIF_ADDRESSとかCONFIG_DATAレジスタとかは、IOアドレス空間の0xc8fと0xcfcに存在する。
```
  /** @brief CONFIG_ADDRESS レジスタの IO ポートアドレス */
  const uint16_t kConfigAddress = 0x0cf8;
  /** @brief CONFIG_DATA レジスタの IO ポートアドレス */
  const uint16_t kConfigData = 0x0cfc;
```
IOアドレス空間の読み書きには、専用のIO命令が必要。C++では表現できないのでアセンブリで書く。
```
  // #@@range_begin(config_addr_data)
  // CONFIG_ADDRESSにアドレスを書き込む。
  void WriteAddress(uint32_t address) {
    IoOut32(kConfigAddress, address);
  }

  void WriteData(uint32_t value) {
    IoOut32(kConfigData, value);
  }

  // CONFIG_DATAに値が出力されるからそれを読み取る。
  uint32_t ReadData() {
    return IoIn32(kConfigData);
  }

　// ベンダIDはレジスタオフセット0にあるからでオフセット0x00を指定。
  uint16_t ReadVendorId(uint8_t bus, uint8_t device, uint8_t function) {
    WriteAddress(MakeAddress(bus, device, function, 0x00));
    return ReadData() & 0xffffu;
  }
  // #@@range_end(config_addr_data)
```

(IOポートアドレスとかよくわかんない)

- PCIデバイスに繋がったデバイスを再帰的に探索する。

```
Error ScanAllBus() {
    num_device = 0;
    // バス0、デバイス0は必ずホストブリッジ。ホストブリッジのヘッダタイプを読み取る。ファンクション0のホストブリッジが単機能デバイスだったらそれはバス0を担当しているからScanBus(0)でok。他にもファンクションがあったら、ファンクション番号が担当バス番号を表す。
    auto header_type = ReadHeaderType(0, 0, 0);
    if (IsSingleFunctionDevice(header_type)) {
      return ScanBus(0);
    }

    for (uint8_t function = 1; function < 8; ++function) {
    // ホストブリッジのファンクション番号が担当バス番号を表すから、ScaBus(function)てしてる。
      if (ReadVendorId(0, 0, function) == 0xffffu) { // 実際のデバイスがあるかどうか。
        continue;
      }
      if (auto err = ScanBus(function)) {
        return err;
      }
    }
    return Error::kSuccess;
  }
  ```

  ```
    // #@@range_begin(scan_bus)
  /** @brief 指定のバス番号の各デバイスをスキャンする．
   * 有効なデバイスを見つけたら ScanDevice を実行する．
   */
  Error ScanBus(uint8_t bus) {
    for (uint8_t device = 0; device < 32; ++device) {
      if (ReadVendorId(bus, device, 0) == 0xffffu) {
        continue;
      }
      // 実際のデバイスがある。
      if (auto err = ScanDevice(bus, device)) {
        return err;
      }
    }
    return Error::kSuccess;
  }
  // #@@range_end(scan_bus)
  ```

  ```
    // #@@range_begin(scan_device)
  /** @brief 指定のデバイス番号の各ファンクションをスキャンする．
   * 有効なファンクションを見つけたら ScanFunction を実行する．
   */
  Error ScanDevice(uint8_t bus, uint8_t device) {
    if (auto err = ScanFunction(bus, device, 0)) {
      return err;
    }
    if (IsSingleFunctionDevice(ReadHeaderType(bus, device, 0))) {
      return Error::kSuccess;
    }

    for (uint8_t function = 1; function < 8; ++function) {
      if (ReadVendorId(bus, device, function) == 0xffffu) {
        continue;
      }
      if (auto err = ScanFunction(bus, device, function)) {
        return err;
      }
    }
    return Error::kSuccess;
  }
  // #@@range_end(scan_device)
  ```

```
  // #@@range_begin(scan_function)
  /** @brief 指定のファンクションを devices に追加する．
   * もし PCI-PCI ブリッジなら，セカンダリバスに対し ScanBus を実行する
   */
  Error ScanFunction(uint8_t bus, uint8_t device, uint8_t function) {
    auto header_type = ReadHeaderType(bus, device, function);
    if (auto err = AddDevice(bus, device, function, header_type)) {
      return err;
    }

    auto class_code = ReadClassCode(bus, device, function);
    uint8_t base = (class_code >> 24) & 0xffu;
    uint8_t sub = (class_code >> 16) & 0xffu;

    if (base == 0x06u && sub == 0x04u) {
      // standard PCI-PCI bridge 2つのPCIバスを繋ぐ。
      auto bus_numbers = ReadBusNumbers(bus, device, function);
      uint8_t secondary_bus = (bus_numbers >> 8) & 0xffu;
      return ScanBus(secondary_bus);
    }

    return Error::kSuccess;
  }
  // #@@range_end(scan_function)
```

## まとめ
やったこと: PCIバスに接続されたPCIデバイスを全て列挙する。
How?: 
- ホストブリッジ(バス0, デバイス0)が単機能かどうかによって処理を分ける。
  - 単機能だったらScanBus(0)をしておしまい。
  - 多機能だったらScanBus(1~7)をして各バスを見て回る。

- ScanBus→ScanDevice→ScanFunctionの順に進み、ScanFunction内で有効なデバイスだったら、devicesに追加する。


そもそもどうやってPCIデバイスを探すのか?:
- 各PCIデバイスはPCIコンフィギュレーション空間を持っていて、其れをみることによりPCIデバイスに関する基本的な情報がわかる。
- CONFIG_ADDRESSレジスタに見たいPCIコンフィギュレーション空間の位置を指定してやることで、CONFIG_DATAレジスタを読み書きしてPCIコンフィギュレーション空間を読み書きできる。

(無名名前空間とかinlineとかって何？なんのために？)

## 6.4 ポーリングでマウス入力
goal: 列挙したPCIデバイスからxHCを探して初期化し、USBマウスを使えるようにする。

1. xHCを探す
```
  // #@@range_begin(find_xhc)
  // Intel 製を優先して xHC を探す
  pci::Device* xhc_dev = nullptr;
  for (int i = 0; i < pci::num_device; ++i) {
    // クラスコードが下記のやつがxHC
    if (pci::devices[i].class_code.Match(0x0cu, 0x03u, 0x30u)) {
      xhc_dev = &pci::devices[i];

      // Intel製のxHC
      if (0x8086 == pci::ReadVendorId(*xhc_dev)) {
        break;
      }
    }
  }
```

2. xHCのレジスタ群が配置されてるメモリアドレスを取得する。
- MMIO(memory mapper IO)
    - メモリと同じように読み書きできるレジスタ。CPU内蔵のレジスタはRAXとかの名前で読み書きするけど、MMIOはメモリと同じように読み書きできるレジスタ(?)
    - MMIOアドレスはPCIコンフィギュレーション空間のBAR0に記録されることになっている。

```
  // #@@range_begin(read_bar)
  const WithError<uint64_t> xhc_bar = pci::ReadBar(*xhc_dev, 0);
  Log(kDebug, "ReadBar: %s\n", xhc_bar.error.Name());
  const uint64_t xhc_mmio_base = xhc_bar.value & ~static_cast<uint64_t>(0xf);
  Log(kDebug, "xHC mmio_base = %08lx\n", xhc_mmio_base);
  // #@@range_end(read_bar)
```

3. xHCの初期化

Intel製のxHCだった場合のみ、SwitchEhci2Xhciを呼び出す。
```
    // #@@range_begin(init_xhc)
  usb::xhci::Controller xhc{xhc_mmio_base};

  if (0x8086 == pci::ReadVendorId(*xhc_dev)) {
    SwitchEhci2Xhci(*xhc_dev);
  }
  {
    auto err = xhc.Initialize();
    Log(kDebug, "xhc.Initialize: %s\n", err.Name());
  }

  Log(kInfo, "xHC starting\n");
  xhc.Run();
  // #@@range_end(init_xhc)
```

4. 何かが接続されているポートの設定を行う

```
// #@@range_begin(configure_port)
  usb::HIDMouseDriver::default_observer = MouseObserver;

  for (int i = 1; i <= xhc.MaxPorts(); ++i) {
    auto port = xhc.PortAt(i);
    Log(kDebug, "Port %d: IsConnected=%d\n", i, port.IsConnected());

    if (port.IsConnected()) {
      if (auto err = ConfigurePort(xhc, port)) {
        Log(kError, "failed to configure port: %s at %s:%d\n",
            err.Name(), err.File(), err.Line());
        continue;
      }
    }
  }
  // #@@range_end(configure_port)
```

5. マウスを動かした時のイベントを処理する
```
  // #@@range_begin(receive_event)
  while (1) {
    if (auto err = ProcessEvent(xhc)) {
      Log(kError, "Error while ProcessEvent: %s at %s:%d\n",
          err.Name(), err.File(), err.Line());
    }
  }
  // #@@range_end(receive_event)
  ```