Linuxにおけるplatform device APIについて

はじめに

自分でドライバ(というかカーネルモジュール)を作ることなんてほとんどないし、 昨今はそういうローレベルな話は流行らないような気がするけど、 それでもやはりドライバを作りたいときがある。 今回は、Linuxが勝手に情報を見つけてよしなにやってくれないデバイスに関して ドライバを作る際に便利そうなplatform device APIを紹介する。

platform device APIとは、PCIeデバイスやUSBデバイスのようなLinux KernelとかBIOSが うまいこと情報を見つけてきてくれるようなデバイではないものに対して、 IRQ X番で割り込みが上げられるよ、とか、MMIO空間はアドレスxxxxからxxxxまでだよ、などの デバイスが持つ資源の情報を手動でカーネルに登録するためのもの。

そもそもそんなデバイスなんてあるのか

例えばOn Chipのデバイスがそうらしい。情報を出す規格があるわけではないので、MMIO空間は見えても、 何ができるか*1まではスペックシートを 見ないとわからんことが多い(と思う)。

なお、PCI(PCIeも含む)は規格でどういうことができるかを示すレジスタ(Capability Register)を 実装しなきゃいけないので、そういう心配は無用。 Capability Registerに関してはこれがわかりやすい。 BIOSがPCI Expressを初期化する手順が見えてきた: なひたふJTAG日記

USBデバイスもたぶんPCIバイスと同じ。

バイス登録までのおおよその流れ

以下の2ステップで終わる。簡単!

  1. platform deviceの資源情報を持つ構造体を作成する
  2. platform_device_register_simpleでplatform deviceを登録する

platform deviceの資源情報を持つ構造体の作成

これはまあ普通に構造体を作るだけ。新しく例を作るのも何なので、 The platform device API [LWN.net]に乗ってる例(MMIO空間が0x10000000~0x10001000まで、 IRQ 20に繋がれているデバイス)を以下に引用する。

#include <linux/platform_device.h>

static struct resource foomatic_resources[] = {
    {
        .start  = 0x10000000,
        .end    = 0x10001000,
        .flags  = IORESOURCE_MEM,
        .name   = "io-memory"
    },
    {
        .start  = 20,
        .end    = 20,
        .flags  = IORESOURCE_IRQ,
        .name   = "irq",
    }
}

platform_device_register_simpleでplatform deviceを登録する

platform_device_register_simpleのインタフェースは以下のとおり。

  • nameには、登録するデバイスの名前(自分でつけてよい)を
  • idには、(たぶん)連番でデバイスに番号を
  • *resには、先に定義した構造体のポインタを
  • numには、resの要素数

入れればよい。

// linux 4.9 include/linux/platform_device.h
136 static inline struct platform_device *platform_device_register_simple(
137                 const char *name, int id,
138                 const struct resource *res, unsigned int num)
139 {
140         return platform_device_register_resndata(NULL, name, id,
141                         res, num, NULL, 0);
142 }

platform deviceを登録したあとはどうすればいいの

platform deviceのdriverを登録する。 以下のplatform_driverの構造体のうち、 最低限

  • probe
  • remove
  • driver

があればOK。ID tableはなくてもよい。

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
};

要はこんな感じ(The platform device API [LWN.net]に乗ってる例を引用)。

static struct platform_driver i2c_gpio_driver = {
    .driver     = {
        .name   = "i2c-gpio",
        .owner  = THIS_MODULE,
    },
    .probe      = i2c_gpio_probe,
    .remove     = __devexit_p(i2c_gpio_remove),
};

driverを作るのに役に立つかもしれないAPI

このへんのAPIで登録したデバイス情報を引いてこられるけど、 どういう場面で使うのかいまいちよくわからない。 platform deviceのようなニッチデバイスの情報を登録する人とドライバ作る人が別なのかな。

    struct resource *platform_get_resource(struct platform_device *pdev, 
                       unsigned int type, unsigned int n);
    struct resource *platform_get_resource_byname(struct platform_device *pdev,
                       unsigned int type, const char *name);
    int platform_get_irq(struct platform_device *pdev, unsigned int n);

参考

*1:例えば割り込みがあげられるとか、そういうの