Linux カーネルにおけるデバイスドライバは、モジュール性と再利用性を高めるために「分離」と「階層化」の設計原則に基づいて構築される。特に、I2C や SPI といったバスに接続されるデバイスだけでなく、SoC 内蔵のペリフェラル(例: GPIO、タイマ、UART など)に対しても、統一された抽象化フレームワークが必要となる。そのため、Linux は物理的なバスを持たないデバイス向けに platform 仮想バスを導入している。
platform バスの役割
platform バスは、bus_type 構造体で定義され、デバイスとドライバのマッチングを担当する。マッチングロジックは platform_match() 関数内で実装されており、以下の順で照合が行われる:
driver_overrideによる強制バインディング- デバイスツリー(Device Tree)の
compatibleプロパティとの照合(of_driver_match_device) - ACPI ベースのマッチング
id_tableを用いた ID 照合- ドライバ名とデバイス名の文字列一致
この柔軟なマッチング機構により、ハードウェア情報の記述方法(デバイスツリー 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_MEM や IORESOURCE_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/ 配下にエントリが生成される。