11回目: デバイスツリーにI2Cデバイスを追加する
本連載について
組み込みLinuxのデバイスドライバをカーネルモジュールとして開発するためのHowTo記事です。本記事の内容は全てラズパイ(Raspberry Pi)上で動かせます。
- 1回目: ビルド環境準備と、簡単なカーネルモジュールの作成
- 2回目: システムコールハンドラとドライバの登録(静的な方法)
- 3回目: システムコールハンドラとドライバの登録(動的な方法)
- 4回目: read/writeの実装とメモリのお話
- 5回目: ラズパイ用のGPIOデバドラの実装
- 6回目: ioctlの実装
- 7回目: procfs用インタフェース
- 8回目: debugfs用インタフェース
- 9回目: 他のカーネルモジュールの関数を呼ぶ / GPIO制御関数を使う
- 10回目: I2Cを使ったデバイスドライバを作る
- 11回目: デバイスツリーにI2Cデバイスを追加する <--------------------- 今回の内容
- 12回目: 作成したデバイスドライバを起動時にロードする
本記事に登場するソースコード全体
今回の内容
これまで、いくつかのデバイスドライバを実装してきました。しかし、どのデバイスドライバも、「ハードウェア固有の情報」を直接デバイスドライバ内に持ってしまっていました。
例えば、5回目では、直接メモリマップドレジスタを叩くことでGPIOデバドラを実装しました。この時、オフセットアドレスなどのチップ(SoC)固有の情報をデバドラ内で持っていました。これは良くありません。(そもそも、GPIOそのもののデバドラを作ることはなく、Linuxカーネル標準の関数を普通は使います。)
仮に、Linuxカーネル標準の関数を使っても、まだハードウェア固有情報をデバドラは持ちえます。10回目では、Linuxカーネル標準のI2C制御関数を使用してI2Cデバイスにアクセスしました。しかし、対象となるI2Cデバイスが、どのI2Cバスにつながっているかや、スレーブアドレスといったボード固有の情報をデバドラ内に記載していました。
こういったことは、現在ではルール的に許されていません。理由は、SoC、ボードのバリエーション毎にコードがどんどん増加してしまうためです。現在では、こういったハードウェア固有情報はデバイスツリーによって管理しています。
前回、I2C接続された加速度センサ(LIS3DH)のデバイスドライバを作りました。しかし、このときは、手動でカーネルに対してI2C機器の情報を教えてあげる必要がありました。今回は、この情報をデバイスツリーに組込んでみようと思います。本記事ではRaspberryPi2 Model Bを対象として記載します
デバイスツリーに関連するファイル
「デバイスツリー」と言っていますが、その実態はバイナリ形式のファームウェアになります。拡張子は.dtbになります。これは、ラズパイでは/boot
の下にあります。例えば、RaspberryPi2 Model Bの場合は、/boot/bcm2709-rpi-2-b.dtb
になります。
ls /boot/*.dtb
/boot/bcm2708-rpi-0-w.dtb /boot/bcm2709-rpi-2-b.dtb /boot/bcm2835-rpi-a-plus.dtb /boot/bcm2835-rpi-zero.dtb
/boot/bcm2708-rpi-b.dtb /boot/bcm2710-rpi-3-b.dtb /boot/bcm2835-rpi-b.dtb /boot/bcm2836-rpi-2-b.dtb
/boot/bcm2708-rpi-b-plus.dtb /boot/bcm2710-rpi-cm3.dtb /boot/bcm2835-rpi-b-plus.dtb
/boot/bcm2708-rpi-cm.dtb /boot/bcm2835-rpi-a.dtb /boot/bcm2835-rpi-b-rev2.dtb
SoC固有情報はDTSIファイルに、ボード固有情報はDTSファイルに記載することになっています。これらをDTCという特別なコンパイラでコンパイルすることで、先ほどのDTBファイルが作られます。ラズパイ2の場合は/linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts
にあります。
dtbファイルを直接書き換える(手抜き版)
デバイスツリーに追加する
後で書きますが、Linuxのソースツリーを持ってきて、/linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts
を編集してコンパイルするのが正しい方法です。しかし、本連載ではこれまでラズパイ上でできるだけお手軽に確認してきました。今回も、まずはお手軽にやってみようと思います。
事前に、SDカード内のbcm2709-rpi-2-b.dts
をPC等にバックアップしておいてください。最悪、起動しなくなる可能性があります。また、デバイスツリー用のコンパイラ(DTC)をインストールします。sudo apt-get install device-tree-compiler
続いて、現在使用されているDTBファイルを逆コンパイルしてDTSに戻します。
dtc -I dtb -O dts /boot/bcm2709-rpi-2-b.dtb > dis_bcm2709-rpi-2-b.dts
これによって、dis_bcm2709-rpi-2-b.dts
というテキストファイルが作られます。このファイルには、このボード(ラスパイ2)上でのデバイスの接続情報がツリー状に記載されています。また、レジスタ番地なども記載されています。今回は、I2C_1にLIS3DH(スレーブアドレス=0x18)を接続するとします。まず、I2C1のノードを探します。アドレスで書かれてしまっていますが、i2c@7e804000
の所になります。そこに、mydevice
というノードを追加します。compatible
にmycompany,myoriginaldevice
を設定することで、「mycompanyというメーカー製のmyoriginaldeviceという名前のデバイス」だと指定します。また、reg
の所にはスレーブアドレスを設定します。これによってカーネルは、このボードには、I2C_1の0x18には"mycompany,myoriginaldevice"というデバイスがつながっている、と認識します。
省略
i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <0x2 0x15>;
clocks = <0x7 0x14>;
#address-cells = <0x1>;
#size-cells = <0x0>;
status = "disabled";
pinctrl-names = "default";
pinctrl-0 = <0x10>;
clock-frequency = <0x186a0>;
phandle = <0x20>;
/* 自分用のデバイスを追加 */
mydevice@18 {
compatible = "mycompany,myoriginaldevice";
reg = <0x18>;
};
};
省略
編集したら、以下のコマンドでコンパイルして、元のDTBファイルに上書きします。一応アクセス権の設定もしておきます。
dtc -O dtb -o bcm2709-rpi-2-b.dtb dis_bcm2709-rpi-2-b.dts
chmod 755 bcm2709-rpi-2-b.dtb
sudo cp bcm2709-rpi-2-b.dtb /boot/bcm2709-rpi-2-b.dtb
sudo reboot yes
/proc/device-tree
でデバイスツリーの情報を確認できます。再起動後に、I2C1の下にmydeviceが追加していれば成功です。
ls /proc/device-tree/soc/i2c@7e804000/mydevice@18/
compatible name reg
デバイスドライバ側の対応
デバイスドライバ側では、対応するデバイスを登録する必要があります。先ほど、デバイスツリーには"mycompany,myoriginaldevice"というデバイスを登録したので、それに対応するようにします。そのために、struct of_device_id
に"mycompany,myoriginaldevice"
を設定して、struct i2c_driver
の.of_match_table
に登録します。それ以外は、前回の実装内容と同じです。ソースコードを以下に示します。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/of_platform.h>
/*** このデバイスに関する情報 ***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/devices等で表示されるデバイス名 */
/* このデバイスドライバで取り扱うデバイスのマッチングテーブル */
/* dts内の、下記に対応
i2c@7e804000 {
mydevice@18 {
compatible = "mycompany,myoriginaldevice";
reg = <0x18>;
};
*/
static const struct of_device_id mydevice_of_match_table[] = {
{.compatible = "mycompany,myoriginaldevice",},
{ },
};
MODULE_DEVICE_TABLE(of, mydevice_of_match_table);
/* このデバイスドライバで取り扱うデバイスを識別するテーブルを登録する */
/* 重要なのは最初のnameフィールド。これでデバイス名を決める。後ろはこのドライバで自由に使えるデータ。ポインタや識別用数字を入れる */
static struct i2c_device_id mydevice_i2c_idtable[] = {
{"MyI2CDevice", 0},
{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_i2c_idtable);
static int mydevice_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
printk("mydevice_i2c_probe\n");
if(id != NULL)
printk("id.name = %s, id.driver_data = %d", id->name, id->driver_data);
if(client != NULL)
printk("slave address = 0x%02X\n", client->addr);
/* 通常はここで、このデバドラでサポートしているデバイスかどうかチェックする */
int version;
version = i2c_smbus_read_byte_data(client, 0x0f);
printk("id = 0x%02X\n", version);
return 0;
}
static int mydevice_i2c_remove(struct i2c_client *client)
{
printk("mydevice_i2c_remove\n");
return 0;
}
static struct i2c_driver mydevice_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = mydevice_of_match_table,
},
.id_table = mydevice_i2c_idtable, // このデバイスドライバがサポートするI2Cデバイス
.probe = mydevice_i2c_probe, // 対象とするI2Cデバイスが認識されたときに呼ばれる処理
.remove = mydevice_i2c_remove, // 対象とするI2Cデバイスが取り外されたときに呼ばれる処理
};
module_i2c_driver(mydevice_driver);
デバイスツリーに記載されている接続情報に基づいて、カーネルが対応するデバイスドライバを呼んでくれます。mydevice_i2c_probe()
に渡されるstruct i2c_client
内には、デバイスツリーで設定したスレーブアドレスが格納されています。
一点、注意点としては、この場合はstruct i2c_device_id
にはNULLが入ります。そのためか分かりませんが、.probe
はそのうち廃止されるようです。代わりに、.probe_new
を使用するようです。.probe_newではstruct i2c_device_id
が消されています。
動かしてみる
以下コマンドでビルド、ロードします。
make && sudo insmod MyDeviceModule.ko
dmesg
[ 3507.003163] mydevice_i2c_probe
[ 3507.003175] slave address = 0x18
[ 3507.003656] id = 0x33
ログを見ると、ちゃんとprobeが呼ばれて、I2C通信もできていることが分かります。前回は、手動でデバイス接続通知が必要でしたが、今回は不要だったという点が違いです
ソースコードからDTBを作る
環境準備
ちゃんと、ラズパイ用のLinuxソースコードからDTBを作ってみます。まず、ソースコードを取得します。また、ビルドに必要なツールもインストールしておきます。基本的には(https://www.raspberrypi.org/documentation/linux/kernel/building.md) の通りです。ネイティブビルドなので、他に準備は不要です。
sudo apt-get install git bc
git clone https://github.com/raspberrypi/linux.git
ラズパイ用のDTSファイルを編集してみる
Raspberry Pi 2 Model B用のDTSファイルは/linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts
になります。(このファイルは、先ほど逆コンパイルしたdtsファイルよりもだいぶすっきりしています。これは、複数ファイルに分かれているためです。このdtsファイルは、先頭でbcm2709.dtsi
をincludeしています。先ほど逆コンパイルしたdtsファイルは全部入りだからごちゃごちゃしているのだと思われます。)
/linux/arch/arm/boot/dts/bcm2709-rpi-2-b.dts
を先ほどと同じように編集します。I2C_1の所に、デバイスを追加します。
省略
&i2c1 {
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
clock-frequency = <100000>;
/* 自分用のデバイスを追加 */
mydevice@18 {
compatible = "mycompany,myoriginaldevice";
reg = <0x18>;
};
};
省略
DTBをビルドしてみる
DTSファイルの編集が終わったら、以下のコマンドでDTBだけをコンパイルします。ラズパイ上のコンパイルでも1分かからずに終わります。出来上がったDTBファイルを/bootにコピーします。
cd ~/linux
KERNEL=kernel7
make bcm2709_defconfig
make -j4 dtbs
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
ノート
overlays用DTBは、カーネル起動後に、動的に接続状態が変わるときに使うらしいです。なので、特にコピーはしないでもいいかも。
動かしてみる
デバイスドライバは、先ほどの手抜きバージョンと同じでOKです。再起動すると、同じ結果になるはずです。
おわりに
I2Cデバイスを例に、デバイスツリーをいじってみました。この記事で触れた内容はまだまだデバイスツリーの触りにすぎません。デバイスツリーについて、もっと深く知るためには、こちらの記事+リンク先をご参考にしてください。