Linux platform デバイスドライバの設計と実装

Linux カーネルにおけるデバイスドライバは、モジュール性と再利用性を高めるために「分離」と「階層化」の設計原則に基づいて構築される。特に、I2C や SPI といったバスに接続されるデバイスだけでなく、SoC 内蔵のペリフェラル(例: GPIO、タイマ、UART など)に対しても、統一された抽象化フレームワークが必要となる。そのため、Linux は物理的なバスを持たないデバイス向けに platform 仮想バスを導入している。

platform バスの役割

platform バスは、bus_type 構造体で定義され、デバイスとドライバのマッチングを担当する。マッチングロジックは platform_match() 関数内で実装されており、以下の順で照合が行われる:

  1. driver_override による強制バインディング
  2. デバイスツリー(Device Tree)の compatible プロパティとの照合(of_driver_match_device
  3. ACPI ベースのマッチング
  4. id_table を用いた ID 照合
  5. ドライバ名とデバイス名の文字列一致

この柔軟なマッチング機構により、ハードウェア情報の記述方法(デバイスツリー or プラットフォームコード)に依存せず、共通のドライバを再利用できる。

platform ドライバの実装

platform_driver 構造体は、デバイスと紐づいた際に呼び出される probe() 関数を中心に構成される。以下に最小限のドライバ構造を示す:

static int sample_probe(struct platform_device *pdev)
{
    /* リソース取得、レジスタマッピング、初期化処理 */
    return 0;
}

static int sample_remove(struct platform_device *pdev)
{
    /* 後始末処理 */
    return 0;
}

static const struct of_device_id sample_of_match[] = {
    { .compatible = "vendor,sample-device" },
    { /* sentinel */ }
};

static struct platform_driver sample_driver = {
    .probe = sample_probe,
    .remove = sample_remove,
    .driver = {
        .name = "sample-device",
        .of_match_table = sample_of_match,
    },
};

module_platform_driver(sample_driver);
MODULE_LICENSE("GPL");

上記では、module_platform_driver() マクロにより、init/exit 関数と登録/解除処理が自動生成される。これは標準的なプラクティスである。

platform デバイスの記述

デバイスツリーが使用可能な環境では、platform_device の明示的な定義は不要であり、代わりに DTS(Device Tree Source)ファイルで以下のように記述する:

sample_device: sample@0 {
    compatible = "vendor,sample-device";
    reg = <0x40000000 0x1000>;
    interrupts = <35 4>;
};

一方、デバイスツリー未対応の旧来のカーネルでは、platform_device 構造体をコード内で定義し、platform_device_register() で登録する必要がある。リソース(メモリ領域、IRQ など)は struct resource 配列で指定し、flags には IORESOURCE_MEMIORESOURCE_IRQ などを使用する。

実装例:GPIO LED 制御ドライバ

以下は、STM32MP1 の GPIO を直接操作して LED を制御する platform ドライバの要点である。

ドライバ側(leddriver.c)の probe 処理抜粋:

static int led_probe(struct platform_device *pdev)
{
    struct resource *res;
    int i;

    for (i = 0; i < 6; i++) {
        res = platform_get_resource(pdev, IORESOURCE_MEM, i);
        if (!res) return -ENODEV;
        /* ioremap で仮想アドレスにマッピング */
    }

    /* RCC クロック有効化、GPIO モード設定、初期状態設定 */
    writel((readl(rcc_base) | BIT(8)), rcc_base);
    writel((readl(moder) & ~BIT(0)) | BIT(0), moder); // 出力モード
    writel(readl(bsrr) | BIT(16), bsrr); // LED OFF

    /* 文字デバイス登録 */
    alloc_chrdev_region(&leddev.devid, 0, 1, "platled");
    cdev_init(&leddev.cdev, &led_fops);
    cdev_add(&leddev.cdev, leddev.devid, 1);

    leddev.class = class_create(THIS_MODULE, "platled");
    device_create(leddev.class, NULL, leddev.devid, NULL, "platled");

    return 0;
}

ユーザ空間アプリケーション(ledplatformApp.c):

int main(int argc, char *argv[])
{
    int fd = open("/dev/platled", O_RDWR);
    unsigned char cmd = atoi(argv[1]); // 1=ON, 0=OFF
    write(fd, &cmd, 1);
    close(fd);
    return 0;
}

ビルドとテスト

カーネルモジュールとしてビルド後、開発ボード上で以下を実行:

depmod
modprobe leddevice.ko   # デバイス登録(デバイスツリー未使用時)
modprobe leddriver.ko   # ドライバ登録

./ledplatformApp 1      # LED 点灯
./ledplatformApp 0      # LED 消灯

rmmod leddriver leddevice

マッチング成功時にはカーネルログにメッセージが表示され、/sys/bus/platform/drivers/ および /sys/bus/platform/devices/ 配下にエントリが生成される。

タグ: Linux Kernel Platform Driver Device Tree Embedded Linux STM32MP1

5月28日 01:16 投稿