💮

Fuchsia のデバイスドライバ概要

2022/03/29に公開

本記事では、Fuchsia のデバイスドライバ(ARM プラットフォーム)について簡単に紹介します。

公式ドキュメント

デバイスドライバの種類

Fuchsia では大半のデバイスドライバはユーザー空間で動作します。

  • Kernel drivers
    カーネル空間で動作するごく一部のドライバ
  • Non-Kernel drivers
    ユーザー空間で動作する大多数のドライバ
    • Board Driver
      ボード固有のハードウェア情報(デバイスの Memory Mapped I/O アドレス、IRQ など)を記載したドライバ

Kernel drivers

カーネル起動に必要な最低限のドライバ(だと思われる)。

  • zircon/kernel/dev/

    • coresight

      高機能なデバッグとトレースを実現するためのハードウェア
      進化する Cortex-M シリーズ搭載マイコンのデバッグ

    • hdcp

      デジタルコンテンツの不正コピーを防ぐことを目的とした著作権保護技術
      HDCP とは何ですか?

    • hw_rng
      ハードウェア乱数生成器
    • hw_watchdog
      ウォッチドッグタイマー
    • interrupt
      Generic Interrupt Controller(GIC)、割り込みコントローラ
    • iommu

      IOMMU (Input/Output Memory Management Unit、IOMMU) とは DMA 可能な I/O バスと主記憶装置を接続するメモリ管理ユニット (MMU) である。
      IOMMU - Wikipedia

    • pcie

      PCI Express(ピーシーアイエクスプレス)は、2002 年に PCI-SIG(英語版)によって策定された、I/O シリアルインタフェース、拡張バスの一種である。
      PCI Express - Wikipedia

    • pdev
      おそらく、Platform DEVice。hw_watchdog、interrupt、psci、uart のスタブ(だと思われる)
    • psci
      Power State Coordination Interface
    • timer
      タイマ
    • uart
      デバッグ用 UART。Zircon カーネルが起動すると、シリアル通信でシェルが使用できる
    • udisplay
      不明。フレームバッファ関係
  • zircon/system/ulib/uart/include/lib/uart/
    physbootブートローダ(U-Boot から起動されるブートローダ。圧縮 Zircon カーネルを展開する)が使用する UART ドライバ。
    Zircon カーネルも(zircon/kernel/dev/uart/を使わないで)こちらを使用している模様

Kernel drivers に関係するボード固有の情報

Zircon カーネル内には、ボード固有の情報(Memory Mapped I/O アドレス、IRQ など)は一切含まれません。
これらの情報は、ブートローダ(U-Boot)内に記述し、起動時に SDRAM を通じて Zircon カーネルに渡します。
詳細はこちらの記事を参照ください。

U-Boot でボード固有の情報を記述
メモリマップ情報
static zbi_mem_range_t mem_config[] = {
    {
        .type = ZBI_MEM_RANGE_RAM,
        .length = 0x80000000,
    },
    {
        .type = ZBI_MEM_RANGE_PERIPHERAL,
        .paddr = 0xfe000000,
        .length = 0x02000000,
    },
    {
        .type = ZBI_MEM_RANGE_RESERVED,
        .paddr = 0x07400000,
        .length = 0x00100000,
    },
    {
        .type = ZBI_MEM_RANGE_RESERVED,
        .paddr = 0x05000000,
        .length = 0x02300000,
    },
};
...
  zircon_append_boot_item(zbi, ZBI_TYPE_MEM_CONFIG, 0, &mem_config, sizeof(mem_config));
...

zircon_append_boot_item() で、SDRAM 上の ZBI(Zircon Boot Image)に情報を追加します。

デバッグUART情報
static const dcfg_simple_t uart_driver = {
    .mmio_phys = 0xff803000,
    .irq = 225,
};
...
  zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_AMLOGIC_UART, &uart_driver,
                          sizeof(uart_driver));
...

デバッグ用 UART のメモリマップドアドレスと IRQ を追加します。

GIC(Generic Interrupt Controller)情報
static const dcfg_arm_gicv2_driver_t gicv2_driver = {
    .mmio_phys = 0xffc00000,
    .gicd_offset = 0x1000,
    .gicc_offset = 0x2000,
    .gich_offset = 0x4000,
    .gicv_offset = 0x6000,
    .ipi_base = 5,
};
...
  zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V2, &gicv2_driver,
                          sizeof(gicv2_driver));
...

割り込みコントローラ情報を追加します。

タイマ情報
static const dcfg_arm_generic_timer_driver_t timer_driver = {
    .irq_phys = 30,
};
...
  zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GENERIC_TIMER, &timer_driver,
                          sizeof(timer_driver));
...

タイマの IRQ を追加します。

プラットフォームID情報
static const zbi_platform_id_t platform_id = {
    .vid = PDEV_VID_KHADAS,
    .pid = PDEV_PID_VIM3,
    .board_name = "vim3",
};
...
  zircon_append_boot_item(zbi, ZBI_TYPE_PLATFORM_ID, 0, &platform_id, sizeof(platform_id));
...

プラットフォーム ID(ボードの識別子)を追加します。
この情報をもとに、ユーザー空間でどのデバイスドライバを使うかが決定されます。

Non-Kernel drivers

Non-Kernel(ユーザー空間の)デバイスドライバは共有ライブラリ(Dynamic Shared Library)となります。
ドライバホストプロセスがデバイスドライバ共有ライブラリをロードして使用します。

ドライバの種別ごとにプロセスが生成されます。
例)I2C ドライバホストプロセスが、VIM3 ボード用の I2C デバイスドライバをロードして使用する。

Non-Kernel drivers に関係するボード固有設定

次節で後述します。

デバイスの検出とドライバのロード

どうやってデバイスを検出するか?

x86_64

PCI コントローラがバスに接続されたデバイスを検出できます。

ARM

デバイス情報(ベンダ ID VID、プロダクト ID PID、Memory Mapped I/O アドレス、IRQ など)をコード中に記述します。
Linux では Device Tree に記述する内容です。

src/devices/board/drivers/vim3/vim3-gpio.cc
static pbus_dev_t gpio_dev = []() {
  pbus_dev_t dev = {};
  dev.name = "gpio";
  dev.vid = PDEV_VID_AMLOGIC;
  dev.pid = PDEV_PID_AMLOGIC_A311D;
  dev.did = PDEV_DID_AMLOGIC_GPIO;
  dev.mmio_list = gpio_mmios;
  dev.mmio_count = std::size(gpio_mmios);
  dev.irq_list = gpio_irqs;
  dev.irq_count = std::size(gpio_irqs);
  dev.metadata_list = gpio_metadata;
  dev.metadata_count = std::size(gpio_metadata);
  return dev;
}();
...
zx_status_t Vim3::GpioInit() {
  zx_status_t status = pbus_.ProtocolDeviceAdd(ZX_PROTOCOL_GPIO_IMPL, &gpio_dev);
...

これは、VIM3 ボードに GPIO デバイスがあることを登録する処理です。
デバイス固有の情報(VID、PID、デバイス ID DID、mmio アドレス、IRQ など)を登録します。

この VIM3 ボードドライバ /driver/vim3.so は、ブートファイルシステム(Linux の initrd 相当)に含まれます。

どうやってデバイスに対応するドライバを見つけるか?

デバイスは VID、PID、DID を持ちます(x86_64 ではデバイスから取得、ARM ではコード中に記載)。
デバイスドライバのディレクトリに、対応する(バインドする)デバイスの VID、PID、DID を記載するファイル(.bind)を用意します。

src/devices/gpio/drivers/aml-axg-gpio/aml-axg-gpio.bind
using fuchsia.amlogic.platform;
using fuchsia.platform;

fuchsia.BIND_PROTOCOL == fuchsia.platform.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.amlogic.platform.BIND_PLATFORM_DEV_VID.AMLOGIC;
accept fuchsia.BIND_PLATFORM_DEV_PID {
  fuchsia.amlogic.platform.BIND_PLATFORM_DEV_PID.A113,
  fuchsia.amlogic.platform.BIND_PLATFORM_DEV_PID.S905D2,
  fuchsia.amlogic.platform.BIND_PLATFORM_DEV_PID.T931,
  fuchsia.amlogic.platform.BIND_PLATFORM_DEV_PID.A311D,
}
fuchsia.BIND_PLATFORM_DEV_DID == fuchsia.amlogic.platform.BIND_PLATFORM_DEV_DID.GPIO;

これは VIM3 ボード(Amlogic 社の SoC 搭載)の、GPIO デバイスドライバのバインドファイルです。
このバインドファイルをもとに、デバイスを検出した場合に、対応するデバイスドライバの初期化コード(zx_driver_ops_t.bind)を実行するコードが自動で生成されます。
これはドライバマネージャープロセスが実行します。

Driver binding  |  Fuchsia

ボードのデバイス検出、ドライババインドの流れ

  1. Zircon カーネルは、U-Boot からプラットフォーム ID(VID、PID)を取得します。
  2. ドライバマネージャーは、このプラットフォーム ID にバインドする(バインドファイル情報に一致する)ボードドライバをロードします。
  3. ボードドライバには、ボードに搭載されたデバイスの情報が記載されており、それをシステムに登録します。
  4. ドライバマネージャーは登録されたデバイスにバインドするデバイスドライバをロードします。

Platform Bus Initialization

VIM3 の GPIO デバイス例で説明します。

  1. U-Boot から、プラットフォーム ID(PDEV_VID_KHADAS 0x04、PDEV_PID_VIM3 0x03)を取得します。
  2. VIM3 ボードドライバ(vim3.so)のバインドファイル(src/devices/board/drivers/vim3/vim3.bindfuchsia.khadas.platform.BIND_PLATFORM_DEV_VID.KHADAS 0x04fuchsia.khadas.platform.BIND_PLATFORM_DEV_PID.VIM3 0x03 がプラットフォーム ID に一致するのでロードされます。
  3. VIM3 ボードドライバが GPIO デバイス(PDEV_VID_AMLOGIC 0x05、PDEV_PID_AMLOGIC_A311D 0x06、PDEV_DID_AMLOGIC_GPIO 0x01)を登録します。
  4. Amlogic GPIO デバイスドライバ(aml-axg-gpio.so)のバインドファイル(src/devices/gpio/drivers/aml-axg-gpio/aml-axg-gpio.bindfuchsia.amlogic.platform.BIND_PLATFORM_DEV_VID.AMLOGIC 0x05fuchsia.amlogic.platform.BIND_PLATFORM_DEV_PID.A311D 0x06fuchsia.amlogic.platform.BIND_PLATFORM_DEV_DID.GPIO 0x01が GPIO デバイス情報に一致するのでロードされます。

デバイス情報の VID、PID 等はzircon/system/ulib/ddk-platform-defs/include/lib/ddk/platform-defs.h で定義されます。
バインドファイルの VID、PID 等はsrc/devices/bind/fuchsia.amlogic.platform/fuchsia.amlogic.platform.bindで定義されます。

ボードを移植する際は、これらのファイルに定義を追加する必要があります。

考察

ボードに依存する情報(U-Boot 内、ボードドライバ内)とデバイスドライバ(ユーザー空間共有ライブラリ)の両方が、カーネルから切り離されているのがわかります。
これにより、スマートフォンなどのメーカーを通さずに、Google が自らカーネルをアップデートして公開できると思います。

備考

Non-Kernel Drivers の種類

platform-bus

  • platform bus driver (src/devices/bus/drivers/)
    • ドライバを管理するフレームワーク
    • デバイスドライバツリーのルート?
    • x86_64 の場合、PCI
    • arm64 の場合、Platform
  • board driver
    • ボード固有のハードウェア情報(デバイスの Memory Mapped I/O アドレス、IRQ など)を記載したドライバ
    • VIM3 のボードドライバ src/devices/board/drivers/vim3/
  • platform device drivers
    • いわゆるデバイスドライバ
    • platform bus driver とは別プロセスで動作する
      • プロトコル(Ethernet など)を実装したドライバと同じプロセスで動作する
      • 性能とセキュリティのトレードオフ
        • セキュリティを追求したら、デバイスドライバごとに別プロセスがよい
        • 性能を追求したら、すべて同じプロセスがよい
    • VIM3 の USB PHY デバイスドライバ src/devices/usb/drivers/aml-usb-phy-v2/
    • VIM3 の Ethernet デバイスドライバ src/connectivity/ethernet/drivers/aml-ethernet-s912/
    • composite
  • protocol implementation drivers
    • platform bus driver と同一プロセスで動作するデバイスドライバ
    • VIM3 の GPIO デバイスドライバ src/devices/gpio/drivers/aml-axg-gpio/
    • VIM3 の Clock デバイスドライバ src/devices/clock/drivers/amlogic-clk/
    • 上記の platform device drivers はそれ単体ではユーザーに機能を提供できない(例:Ethernet デバイスドライバ)。さらに上位のドライバが必要(例:Ethernet プロトコルドライバなど)
    • GPIO や、Clock はそれ単体で機能を提供できる
      • 自身でプロトコルまで実装している、という意味で protocol implementation なのか?
    • 別の文脈で protocol implementation が出てくるので混同しないように注意
      • VIM3 の USB PHY デバイスドライバは UsbPhyProtocol クラス(インターフェース相当)を継承して実装します
      • このコメントで USB PHY protocol implementation とあります。これと protocol implementation drivers は(無関係ではないが)同一の意味ではないと思います
  • platform proxy driver
    • ドライバホストプロセス間で通信するためのドライバ
  • その他のドライバ
    • プラットフォームデバイス(ボード上のデバイス)に依存しないドライバ
    • プロトコルなど

Platform Bus

デバイスの種類

デバイス種類 デバイスドライバ種類 備考
Isolated platform device driver a
Protocol protocol implementation driver a
Fragment platform device driver a

Isolated

ddk::DeviceAdd()

Protocol

ddk::ProtocolDeviceAdd()

Fragment

ddk::CompositeDeviceAdd() ?

https://fuchsia.dev/fuchsia-src/development/drivers/concepts/device_driver_model/composite?hl=en

Discussion