前書き
CrowPi2によるデバイスドライバ(1)にて、CrowPi2(RaspberryPi4搭載ノートパソコン)でのカーネルモジュールを、クロス開発するための環境整備まで書きました。
この記事は、その続きです。
改めて目標
さて、もう一度、作成するデバイスドライバの仕様に立ち戻ります。
今回の目標は、CrowPi2のGPIO1(BCM18)に接続されている圧電ブザーを鳴らすためのカーネルモジュールを作成することです。
もう一度、デバイスドライバの仕様をまとめておくことにします。
- デバイスドライバの名称は、「beep」とします。
- デバイスツリーオーバーレイ対応のモジュールとして実装します。
- キャラクタデバイスとして実装し、
/dev/beep0
を経由して操作できるようにします。 -
/dev/beep0
に対して'1'で始まる文字列を書き込むとブザーが鳴ります。 - 同じく、'0'で始まる文字列を書き込むとブザーが止まります。
- 書き込み文字列の2文字目以降は無視されます。ただし、出力はされたものとして扱います。
-
/dev/beep0
を読み出すと、常に、現在のブザーの状態を'0'、'1'の文字で返します。0が停止で、1が鳴動中です。 - 鳴動時間の設定を行えます。設定はmsec単位ですが、精度は10msecです。初期値は無期限とします。
- 設定値が0の時、無期限鳴動
- 設定値>0の時、設定値msec鳴動後、自動で鳴動停止。
- 鳴動時間の設定は、
ioctl
、又は、/sys/devices/platform/beep@0/beep_ringing_time
への書き込みで行います。後者は、現在値の読み出しも可能です。 - ioctlの呼び出し仕様は、cmdは常に1。第3パラメータ(unsigned long)に対して、unsigned intの範囲内でのmsec単位の時間を指定すると、時間を設定するようにします。cmd=1以外は一切無視します。unsigned intの範囲を超える数値は、上位を切り捨てます。(つまり、問答無用でキャストします。)
鳴動時間に関する仕様に関しては、今回の追加です。(実は、ついつい誘惑に負けて実装しちゃったので・・・というのは内緒=^・・;=)
要はブザーを鳴らして止めるだけ・・・なんですけど、ちゃんと書くと結構長ったらしくなりますね。
全体の構成
これから、ソースコードを細切れにして書くことが多くなります。全ソースコードは、末尾に掲載しました。必要に応じて、合わせてご参照ください。
中身は、明確に分けられるいくつかの部分から構成されます。
- デバイスドライバの登録
- デバイスドライバの初期化及び後始末
- cdev及びudevの登録
- cdevの為のAPIアクセス関数(open,close,write,read,ioctl)
- sysfsの登録とアクセス関数
全体の説明も、概ね、この順番で説明していきます。
デバイスドライバの登録
さて、何もしないカーネルモジュールでは、***_initと***_exitをmodule_driverマクロで登録しました。
今回は、初期化と終了をmodule_platform_driverでの登録に変更します。
これは、デバイスツリーに対応させるためです。
デバイスツリー
さてデバイスツリーとは何者でしょうか。
今回のようなデバイスドライバを作成しようとすると、結構ハードウェアの接続に関する情報が必要になります。今回ですと、圧電ブザーは、BCM18のGPIOに接続するといった具合の数字です。今回のデバイスドライバは、linuxのGPIO APIを使用するつもりなので、番号はこれだけで済むのですが、もっと低位で、直接ハードウェアを制御しようとすると、そのハードウェアを制御するためのレジスタのメモリアドレスとかビット操作のためのマスクやシフト量などなどのハードウェアで決められた数字をたくさん使うことになります。
このような数字は、#defineでマクロ定数にしたり、constで定数宣言したりといった形でドライバにハードコーティングするのが普通でした。
ところで、とある都合により、圧電ブザーの接続先がBCM27(GPIO2)になったら、どうしましょう? ソースコードの#defineのマクロ定数を書き換えることになります。でも、前に作った装置は、BCM18です。というわけで接続先だけが違うデバイスドライバ2つを管理していくことになります。
今回のように、自分だけが使うものなら被害は微小です。でも、普通、デバイスドライバはカーネルに付属して配布されます。そんなところで、こんなことをやっていたら、デバイスドライバばかりがどんどん増えていきます。
そこで、このようなハードウェアの接続先のような、変わる可能性があるハードウェアの情報は、別に管理したいという要求がでてきます。どこか、デバイスドライバの外から、圧電ブザーの接続先は、BCM18のGPIOですよと教えてもらえれば、デバイスドライバは黙ってその数字を使えば良いといったイメージです。接続が変わって、BCM27になっても、平気です。教えてもらった数字をそのまま使うだけですから。
PC-ATの流れをくむパソコンでは、このような数字を、BIOSが教えてくれるようになっています。設定が必要なら、BIOSで設定をします。そして、OSはそこから来た情報を、そのまま使えるわけです。
でも、BIOSがないコンピュータ(はい。今回のRaspberryPiもそんなコンピュータです)ではどうするか。
その答えの一つとしてあるのが、このデバイスツリーという仕組みです。dtbファイルというハードウェアの設定情報ファイルに、このようなハードウェアに関わる数字などを全部登録しておき、この設定情報を元に、デバイスドライバが動くという仕組みになっています。このdtbファイルは、raspbianですと、/boot
配下に****.dtbというファイル名で配置されています。このファイルを起動時に、linuxより先に起動してlinuxを起動する役割をもつ、ブートローダーがlinux起動時に起動パラメータとともにlinuxに渡すような仕組みになっています。この時に、どんなパラメータとdtbを渡すかを決めているのが/boot/config.txt
ファイルです。HDMIの設定などでお世話になった人もいるかもしれませんね。
このdtbファイルも、先にビルドしたlinuxの中に含まれています。
今回のように、自分のためのデバイスドライバを作るのに、カーネルソースを編集するのもちょっと気が引けます。このようなときのために、dtbファイルは、別のファイルで後から追加したり上書きしたりすることが出来るような仕組みが設けられています。これをデバイスツリーオーバーレイと呼びます。別ファイルで、元の設定を上書き変更するイメージです。今回は、この仕組みを使って、beepデバイスドライバに必要な設定を送り込むようにします。
デバイスプラットフォームとデバイスドライバ
さて、linuxカーネルは、デバイスツリーを受け取ると、ここに書かれている全てのハードウェア情報を元に、機器ごとにデバイスプラットフォームという形で登録します。そしてデバイスプラットフォームが、「デバイスドライバの外から接続情報を教えてくれる何か」の役割を果たします。
デバイスドライバがロードされると、このデバイスプラットフォームと紐づけをしてドライバを起動するので、デバイスドライバは自分自身のハードウェアの接続情報をここから取得することが出来るようになります。
dtbの中身
さて、では、今回のbeepハードウェア用のdtbの中身です。
実は、dtbはバイナリファイルです。dtsというソースをdtcというコンパイラでコンパイルした結果がdtbとなります。今回は、デバイスオーバーレイ用のファイルなので、末尾にoをつけます。
beep.dtsoは、このようになります。
/dts-v1/;
/plugin/;
/{
compatible = "brcm,bcm2835";
fragment@0 {
// Configure the gpio pin controller
target = <&gpio>;
__overlay__ {
pin_state: beep-pin@0 {
brcm,pins = <18>; // gpio number
brcm,function = <1>; // 0 = input, 1 = output
brcm,pull = <0>; // 0 = none, 1 = pull down, 2 = pull up
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
beep: beep@0 {
compatible = "crowpi2-beep";
pinctrl-names = "default";
pinctrl-0 = <&pin_state>;
gpios = <&gpio 18 0>;
status = "okay";
};
};
};
};
デバイス「ツリー」の名の通り、全体がツリー構成となっています。そのルートとなるのが、/{}
の部分となります。各name {...}
の組で一つのノードとなり、ノードの中に入れ子で木構造を構成していく形を取ります。最初の行は、対象となるハードウェアの識別情報です。RaspberryPiに使用されているSOCは、bcm2835シリーズです。RaspberryPiに適用することを宣言するために、compatible = "brcm,bcm2835";
の行を書きます。
次のfragment@0と@1ですが、ここが、本来のdtbを書き換えている本体です。今回は、1箇所の変更と、一箇所の追加が必要です。
@0の部分ですが、これは、元からあるgpioノードへの追加情報になります。指定している内容は、pin_state:beep-pin@0
のところです。各々、使用しているピンの番号、入力出力の方向、プルアップ(ダウン)抵抗の指定がなされています。(恐らくspiやi2cなどの特殊機能の指定も出来ると思われます。そこまでは今回はおいきれませんでした。) これは、linuxにもとからあるgpioピンコントローラドライバへの指示となります。
@1の部分が、今回のbeepドライバのための情報です。beep@0
がドライバの名前となります。そして、プラットフォームとドライバのヒモ付のための名前をcompatible = "crowpi2-beep";
と指定しました。この名前は、ドライバの登録の時に使います。gpios = <&gpio 18 0>;
のところが、gpio番号を指定している部分になります。18番ピンをアクティブハイで使用するという宣言になります。0を1に変えればアクティブローとなります。そして、status = "okay";
で、この指定が有効であることを表示しています。pinctrl-0とpinctrl-namesは、今回使用するgpioドライバが必要とする情報です。beepドライバでは直接は使用しません。
このファイルの使用方法に関しては、ビルドの時に説明します。
このあたりの詳細に関しては、RaspberryPi本家の、Device Trees, overlays, and parametersに情報が豊富です。
デバイスドライバの登録
これをうけて、デバイスドライバの登録は、次の部分です。
static const struct of_device_id of_beep_ids[] = {
{ .compatible = "crowpi2-beep" } ,
{ },
};
MODULE_DEVICE_TABLE(of, of_beep_ids);
// 略
static struct platform_driver beep_driver = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_beep_ids,
},
};
module_platform_driver(beep_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This is beep driver for crowpi2");
MODULE_AUTHOR("mito");
ソースコードのほぼ一番最後です。
of_device_id
構造体の配列の形で、先にdtbで指定したcompatible
の内容を指定します。これをキーにして、デバイスプラットフォームとデバイスドライバの紐づけをしています。ちなみに、linuxソースやincludeファイルでof_*****
という関数や構造体がたくさんあります。これ、ほとんどデバイスツリー絡みのなにかであることが多いです。
MODULE_DEVICE_TABLE
マクロは、型定義のためのマクロです。
platform_driver
構造体が、今回登録するドライバの定義情報を示しています。.probe
と.remove
が、初期化と後始末のための関数の指定です。.driver
の.of_match_table
が紐づけを行うcompatible
の内容を示しています。
この構造体を、module_platform_driver
マクロでシステムに登録して、ドライバのシステムへの登録は完了です。
ドライバの初期化
コードの前に・・・
これは、カーネルモジュール、つまり、linuxカーネルの一部です。この意味するところをもう一度振り返っておきましょう。もし、ここでなにか不正なことをやらかしたらどうなるか?・・・答えは、「運が良ければ、システム全体を道連れにフリーズします。」です。
普通のユーザー領域のアプリケーションであれば、OSとランタイムライブラリが協調しながら、重篤な不正や間違いに関しては検出して、最悪でもそのアプリケーションだけを静かに抹殺してくれます。
でも、ここは、カーネルの内部です。何だってできる代わりに、OSのご加護は一切無いと思ったほうが良いです。もし、間違ったことをすると、静かに抹殺される対象はカーネルそのものですから、結果は、わかりますね。下手すると、抹殺してくれる誰かさえいないのです。
さて、神のご加護は、ここでは期待できませんから、外からもらったデータ、関数の戻り値などは徹底的にチェックする姿勢が重要です。特に、ポインタを返す関数がエラーを戻したときは悲惨です。エラーコードをアドレスのつもりでジャンプしたらもう何が起きても何も言えません。
ですから、このコードも、行数のかなりの部分をエラーチェックがしめます。でも、一般的な説明コードにあるような「わかりやすくするためにエラーチェックは省きます」なんてことはしません。わたしが気がついた限りのチェックは全てインプリメントしてあります。(チェック漏れがあれば、それはバグです。多分、あるだろうなぁ。きっと。)
エラーチェックもロジックの一部だと思ってください。
beep_device_infoの定義
さて、最初に、このドライバ全域で利用する各種変数を格納するための構造体を定義します。この構造体の領域は、初期化時にカーネルヒープ領域に確保し、ドライバ各所でアレヤコレヤの手段で引っ張り出してきて使用します。
この手の情報をグローバル変数で確保するのも一つの方法ですが、これをやると、変数が散逸して何が何やらわからなくなります。できるだけ一つにまとめたほうがわかりやすいかなというのが動機です。
# define DRIVER_NAME "beep"
static const unsigned int MINOR_BASE = 0; // udev minor番号の始まり
static const unsigned int MINOR_NUM = 1; // udev minorの個数
// デバイス全域で使用する変数達
// beep_probeでメモリ確保する。
struct beep_device_info {
unsigned int major; // udev major番号
struct cdev cdev;
struct class *class;
struct gpio_desc *gpio;
struct timer_list ringing_timer;
unsigned long ringing_time_jiffies; // 鳴動時間(単位:jiffies) 0で永遠
};
変数の定義だけですので、今は、説明は省略します。実際に使用する時に、説明します。
ドライバ初期化ルーチン
ドライバの初期化ルーチンは、先に登録したとおり、beep_probeです。この関数は、次のようになります。
static int beep_probe(struct platform_device *p_dev) {
struct device *dev = &p_dev->dev;
struct beep_device_info *bdev;
int result;
if (!dev->of_node) {
pr_alert("%s:Not Exist of_node for BEEP DRIVER. Check DTB\n", __func__);
result = -ENODEV;
goto err;
}
// デバイス情報のメモリ確保と初期化
bdev = (struct beep_device_info*)devm_kzalloc(dev, sizeof(struct beep_device_info), GFP_KERNEL);
if (!bdev) {
pr_alert("%s: デバイス情報メモリ確保失敗\n", __func__);
result = -ENOMEM;
goto err;
}
dev_set_drvdata(dev, bdev);
// gpioの確保と初期化
bdev->gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(bdev->gpio)) {
result = -PTR_ERR(bdev->gpio);
pr_alert("%s: can not get GPIO.ERR(%d)\n", __func__, result);
goto err;
}
// udevの生成
result = make_udev(bdev, p_dev->name);
if (result != 0) {
pr_alert("%s:Fail make udev. gpio desc dispose!!!\n", __func__);
goto err_udev;
}
// sysfsの生成
result = make_sysfs(dev);
if (result != 0) {
pr_alert("%s: sysfs生成失敗\n", __func__);
goto err_sysfs;
}
// timerの生成
timer_setup(&bdev->ringing_timer, beep_off_when_timeup, 0);
bdev->ringing_time_jiffies = msecs_to_jiffies(0);
pr_info("%s:beep driver init\n",__func__);
return 0;
err_sysfs:
remove_udev(bdev);
err_udev:
gpiod_put(bdev->gpio);
err:
return result;
}
さて、この関数は、先に登録したplatform_driver構造体と、dtbの内容から導かれたplatform_device構造体を引数として取ります。返り値は、エラーを表す数字か、正常の意味の0です。
最初に確認するのは、デバイスツリーノードが本当にあるかどうかです。実は、この関数が呼ばれたということは、デバイスツリーが認識され、compatibleが一致したことが確認された上で、probe呼び出しがなされるので、本来ならないはずがないんです。ごく小さな可能性ですが、デバイスツリーからではなく、独自に作成したデバイスプラットフォームルーチンを元に呼び出されてしまうと、デバイスツリーノードがない状態が有りえます。一応確認だけはしておきます。これが、dev->of_node
の確認の意味です。ちなみに、このof_node
がデバイスツリー情報への入り口になります。デバイスツリー関係の関数は、大概がこの値を要求します。
さて、エラー時に返しているENODEV
という定数ですが、これは、linuxで定義されているエラー番号です。カーネル内でエラーが発生したときは、このエラー番号を使います。かなりたくさん定義されていています。ヘッダファイルを参照するのが本筋ですが、Linux Kernel: エラー番号の一覧のサイトが整理されていて詳しかったです。
また、このp_devの中に、struct device
構造体がメンバーとして格納されています。この構造体は、デバイスの内容を格納している重要な構造体です。あちらこちらで使用することになります。利用頻度が高いので、devとしてポインタを保持しておきます。
次に、先程示した、beep_device_info
構造体の領域確保です。devm_kzalloc
関数は、カーネルのヒープ領域からメモリを確保します。mallocのカーネル版です。このメモリは、デバイスの開放時に自動で開放されます。関数は、失敗するとNULLを返しますので、これを確認しておきます。
また、この構造体の内容は、今後、ドライバ全域で使用します。
ところで、device
構造体ですが、この内容も最後まで使用します。この構造体は、デバイスのためにドライバが自由に使用できるポインタを保持しています。ここに、今確保したばかりのbeep_device_info
も保存しておくことにします。それが、dev_set_drvdata
関数です。これで、device
構造体さえあればどこからでもbeep_device_info
にもアクセスが可能になります。
この後、gpioの初期化、cdev・udevの生成、sysfsの生成と続きます。これらに関しては、改めて項を起こします。
最後に、ビープ音を自動でoffにするためのタイマーを初期化します。この初期化は至って簡単で、struct timer_list
構造体を用意して、タイムアップ時の処理をする関数のポインタとともにtimer_setup
関数に渡すだけです。これで、タイマーが使用できるようになります。
timer_list
構造体と、鳴動時間を表すringing_time_jiffiesを、ともに、beep_device_info
に保存しておきます。
また、エラー処理ですが、C++のようにtry-catchなんて便利な構文がありませんので、エラー時には、goto文で関数末尾のエラー処理部に飛ばしています。エラー処理部では、すでに確保してしまった資源の開放処理を行っています。
この初期化関数、beep_probeがエラーを返すと、デバイスドライバの組み込みは有効になりません。そして、確保してしまった資源の開放の機会は永遠に失われます。ですから、エラーを返すときには、すでに確保してしまった資源は、ちゃんと開放しておく必要が有ります。(安直な事例では、「ドライバを組み込む→エラー発生→エラーの原因を探して直す」なんて手順を踏んだ後、もう一度ドライバを組み込もうとした時に、資源の二重確保は出来ないと怒られてドライバが組み込めないなんて事態になります。例えば、/dev/***
なんかに中途半端な不正な情報が見えたままになったりすることもあります。こうなったらリブートするしか手はありません。)
cdev,udevの登録
メジャー番号とマイナー番号
さて、続いて、このドライバをキャラクタデバイスとしてシステムに認識させるための登録作業です。
linuxのデバイスは、内部では、2つの番号で管理されています。一つは、ドライバそのものを表すMAJOR番号、もう一つは、同じデバイスを複数見せるためにあるMINOR番号です。
ちょっと見てみましょう。
ubuntu@linux:~$ ls -l /dev/sda*
合計 0
brw-rw---- 1 root disk 8, 0 2月 4 19:21 /dev/sda
brw-rw---- 1 root disk 8, 1 2月 4 19:22 /dev/sda1
brw-rw---- 1 root disk 8, 2 2月 4 19:22 /dev/sda2
brw-rw---- 1 root disk 8, 3 2月 4 19:22 /dev/sda3
brw-rw---- 1 root disk 8, 4 2月 4 19:22 /dev/sda4
母艦で実行した結果の冒頭です。普通のファイルを見た時にファイルサイズが出てくるところが少し変わっています。
これは、sda*
の結果ですので、実はブロックデバイスですが、基本は同じです。8と出ているのが、メジャー番号。0-4の番号がマイナー番号になります。8,0のsdaがハードディスク全体で、8,1-4が各パーティーションを表しています。
sdaの後ろについている数字は、マイナー番号です。/dev/****の構成と、名前付けはこのようにするのが普通のようです。
今回のbeepに関しては、デバイスは一つ。どこからどう見ても一つにしか見えませんので、マイナー番号は、一つしか使いません。
ですから、/dev/beep0
の一つのデバイスファイルを作ることになります。
全体の流れ
この一連の登録は、まぁ、あまり変わりようがない、一連の手続きからなります。
- メジャー番号の取得
- キャラクタデバイスの登録
- クラスの登録
- デバイスファイルの生成
の形になります。
ソースリスト
最初に、この一連の手続きのソースを一気に出します。
static int beep_probe(struct platform_device *p_dev) {
struct device *dev = &p_dev->dev;
struct beep_device_info *bdev;
int result;
// 中略
// udevの生成
result = make_udev(bdev, p_dev->name);
if (result != 0) {
pr_alert("%s:Fail make udev. gpio desc dispose!!!\n", __func__);
goto err_udev;
}
// 以下略
この手続きは、beep_brobeの初期化手続きの一環として実施します。
必要な情報は、beep_device_info
構造体と、platform_device
構造体が保有するドライバの名前です。
次は、ここで呼んでいるmake_udev
本体です。
/* ハンドラ テーブル */
struct file_operations beep_fops = {
.open = beep_open,
.release = beep_close,
.read = beep_read,
.write = beep_write,
.unlocked_ioctl = beep_ioctl,
.compat_ioctl = beep_ioctl,
};
// キャラクタデバイスの登録と、/dev/beep0の生成
static int make_udev(struct beep_device_info *bdev, const char* name) {
int ret = 0;
dev_t dev;
/* メジャー番号取得 */
ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, name);
if (ret != 0) {
pr_alert("%s: メジャー番号取得失敗(%d)\n", __func__, ret);
goto err;
}
bdev->major = MAJOR(dev);
/* カーネルへのキャラクタデバイスドライバ登録 */
cdev_init(&bdev->cdev, &beep_fops);
bdev->cdev.owner = THIS_MODULE;
ret = cdev_add(&bdev->cdev, dev, MINOR_NUM);
if (ret != 0) {
pr_alert("%s: キャラクタデバイス登録失敗(%d)\n", __func__, ret);
goto err_cdev_add;
}
/* カーネルクラス登録 */
bdev->class = class_create(THIS_MODULE, name);
if (IS_ERR(bdev->class)) {
pr_alert("%s: カーネルクラス登録失敗\n", __func__);
ret = -PTR_ERR(bdev->class);
goto err_class_create;
}
/* /dev/beep0 の生成 */
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(bdev->class, NULL, MKDEV(bdev->major, minor), NULL, "beep%d", minor);
}
return 0;
err_class_create:
cdev_del(&bdev->cdev);
err_cdev_add:
unregister_chrdev_region(dev, MINOR_NUM);
err:
return ret;
}
先の手順をそのまま順番に実行しているだけです。
メジャー番号の取得
この番号は、当然のことながら、システムで一意になる必要が有ります。これをまずカーネルに発行してもらいます。
alloc_chrdev_region
関数で、取得できます。ここで、引数のdev_t
型の変数dev
に取得したメジャー番号を返してきます。dev_t
型は、linux/types.h
で定義されていて、その実態は、整数です。一つの32ビット整数にメジャー番号とマイナー番号をパッキングしています。
この数字から、メジャー番号を取得するために、MAJOR(dev)マクロが定義されています。中身は、単に、devをシフトして上位のビットを取得しているだけです。(linux/kdev_t.h
で定義されています。)
MAJORマクロで取得したメジャー番号をbeep_device_info.major
に保存します。
キャラクタデバイスの登録
beep_device_info
構造体に、cdev構造体のcdevメンバーがあります。これは、このキャラクタデバイスを表す構造体です。この構造体を初期化して、システムに登録するという手順になります。
構造体の初期化は、cdev_init(&bdev->cdev, &beep_fops)
の形で実施します。&beep_fops
は、file_operations
構造体で、make_udevに先立って定義してあります。
この構造体には、このbeepデバイスがサポートするファイルAPI関数を定義します。内容は簡単で、サポートする関数に、実際のデバイスドライバ内の関数のポインタを保存しているだけです。今回は、open,release(close),read,write,ioctlの各関数を指定します。ここで指定した関数に、beepファイルに対して呼ばれた読み書きなどの実際の処理を書いていくことになります。
この構造体は、linux/fs.h
に定義されています。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
# ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
# endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
各メンバーの名前を見れば、ある程度、どんな関数を呼び出した結果かは想像がつきますね。この中から、サポートする関数を実装するのが、デバイスドライバのメインの仕事となります。
最後に、cdev_add(&bdev->cdev, dev, NINOR_NUM)
関数で、デバイスを登録します。NINOR_NUMは、マイナー番号をいくつ使用するかを指定します。今回は、ひとつしか使いませんので、#define NINOR_NUM 1
と定義しています。
クラスの登録
udevで、テバイスファイルを管理するにあたって、クラスを登録します。これにより、/sys/class
に、このモジュールのクラスが登録され、これを元に、udevはデバイスファイルを作成します。
class_create(THIS_MODULE, name)
で実施します。
デバイスファイルの生成
最後に、デバイスファイルの指定を行います。
device_create(bdev->class, NULL, MKDEV(bdev->major, minor),NULL,"beep%d",minor)
で実施します。
MKDEV
マクロは、指定したメジャー番号とマイナー番号の組をパッキングしてくれるマクロです。最後の2つのパラメータですが、printfの様に書式を利用できます。通常は、beep0の様に名前にマイナー番号をつけた形で登録します。for文で囲ってあるのは、複数のマイナー番号にも対応できるようにするためです・・・が、今回は、実質いらないですね。でも、一般化のために一応つけておきます。(初期化の時に一回だけの実行です。効率が・・・と声高に叫ぶ部分でもないでしょう。しかも多分、最適化でforループは抹殺されます。)
最後にエラー処理部をつけてあります。probeのときと同様、エラーを返すときには、今までに確保・登録した部分を逆順で開放しておかないといけません。その処理が書かれています。
ここまでの処理で、/dev/beep0
というデバイスファイルに対して、ファイル操作が可能となりました。
ファイルアクセス関数の実装
さて、ここが、圧電ブザーのコントロールの本体となります。各、ファイルアクセス関数の実装です。
でも、その前に、まだ書いてなかったGPIOの初期化を先に説明します。
GPIOの初期化
// gpioの確保と初期化
bdev->gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(bdev->gpio)) {
result = -PTR_ERR(bdev->gpio);
pr_alert("%s: can not get GPIO.ERR(%d)\n", __func__, result);
goto err;
}
さて、今回、gpioの入出力には、linuxのgpioデバイスドライバを経由して行います。定義は、linux/gpio/consumer.h
にかかれています。
本家のドキュメントで、GPIO Descriptor Consumer Interfaceに、このAPIの詳細な説明が有ります。consumerと言いながら、デバイスドライバのためのAPIです。この文書の近くに、「GPIOなんてものは、ユーザーランドのアプリケーションからダイレクトに制御するものではありません。」なんて書かれてますし・・・。
呼び出す関数は、ひとつだけ。devm_gpio_get
関数です。この関数は、結構たくさんの仕事をしてくれます。
第一引数のdevは、platform_device
構造体からもらってきたdevice
構造体です。この構造体のof_node
を根拠に、デバイスツリーからGPIO番号を取得してきます。
そして、該当のgpioの必要な初期化をした上で、gpioの入出力モードの設定をします。GPIOD_OUT_LOW
の指定部分です。このモードの指定は、次のような指定が可能です。
- GPIOD_ASIS なにも設定しない。
- GPIOD_IN 入力に設定する。
- GPIOD_OUT_LOW 出力に設定した上で、LOWを出力する。
- GPIOD_OUT_HIGH 出力に設定した上で、HIGHを出力する。
- GPIOD_OUT_LOW_OPEN_DRAIN オープンドレインのGPIOD_OUT_LOW。
- GPIOD_OUT_HIGH_OPEN_DRAIN オープンドレインのGPIOD_OUT_HIGH。
今回のモードは、GPIOD_OUT_LOWとなります。
初期化を終わると、gpioディスクリプタという識別情報へのポインタを返してくれます。エラーのときは、エラーコードを返します。ポインタかエラーコードのどちらを返してきたかを必ず確認する必要が有ります。これが、IS_ERR
のチェックです。このマクロは、帰ってきたポインタがエラーコードであれば真を返すので、そのときは、エラー処理を実施します。ここでコケたら、もうこのドライバは何も出来ませんので、素直にbeep_probe
をエラーで返し、ドライバの組み込みを中止します。
ここで取得したディスクリプタは、恒例のごとくbeep_device_info
に保存します。
open関数
// デバイス情報を取得し、file構造体に保存する。
static int beep_open(struct inode *inode, struct file *file) {
pr_devel("%s:beep open\n", __func__);
struct beep_device_info *bdev = container_of(inode->i_cdev, struct beep_device_info, cdev);
if (bdev==NULL) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return -EFAULT;
}
file->private_data = bdev;
return 0;
}
オープン関数は、デバイスファイルの情報を格納したinode
構造体と、ユーザーアプリケーションに返すファイルディスクリプタの背景となるfile
構造体を持ってきます。
さて、readやwrite関数は、このfile構造体を引数として持ちます。
gpioの制御には、gpioディスクリプタを保存したbeep_device_info
が必要です。
このopen関数の作業は、file
構造体にbeep_device_info
のポインタを保存することです。
container_of
さて、ここで少し、C言語の構造体について考えてみます。
C言語の構造体の実態は、単純化して言うとメンバーを順番にメモリ上に並べてあるだけです。メンバーへのアクセスは、よく配列とポインタの例で書かれる言い方ををしてみると、beep_device_info型の変数bdevに対して、bdev.cdevと書くのは、*(&bdev+cdev_OFFSET)と書くのと同じです。なんていい方も出来ます。ここで、cdev_OFFSETの値は、構造体の定義があれば計算することが出来ます。
そうであるならば、bdev.cdevへのポインタpを持っていれば、p-cdev_OFFSETでbdevのポインタが求められますね。
これをやってくれるのが、container_ofマクロです。linux/kernel.h
で、定義されています。
# define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
チェックが入り少々複雑ですが、最後の行で、まさに、これをやっています。
第一引数は、自分が持っている構造体メンバーへのポインタです。第二引数は、構造体の名前、第三引数は、構造体メンバーの名前となります。これで、ptrが所属する構造体本体へのポインタが取得できます。
beep_device_infoの取得
beep_device_info
への直接のポインタは、どこにもありません。でもinode
構造体は、beep_probe
で作成したcdev
構造体へのポインタをメンバーとして持っています。このcdev
構造体の実態は、beep_device_info
の中に有ります。
というわけでcontainer_of
でbeep_device_info
へのポインタを取得できます。
さて、file
構造体の中に、private_data
というvoid *型のメンバーが有ります。これ、ドライバの都合で自由に使って良いポインタです。ここに、取得したbeep_device_info
へのポインタを保存します。
これで、各ファイルアクセス関数から、beep_device_info
へのアクセスが可能となりました。
さてオープンの処理で、実質的に作成したものは何もありません。ですから、beep_close
関数でやることも、実は、何もありません。close関数が無いのは落ち着きませんので、一応、書くだけは書きましたが、その実態は、空です。
よって、説明も省略します。
read関数
では、read関数です。この関数は、現在のブザーの状態を返します。
static ssize_t beep_read(struct file *fp, char __user *buf, size_t count, loff_t *f_pos) {
struct beep_device_info *bdev = fp->private_data;
if (!bdev) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return -EFAULT;
}
put_user(gpiod_get_value(bdev->gpio)+'0', &buf[0]);
return 1;
}
さて、この関数は、先ほど説明したfile
構造体へのポインタと、読み出した値を返すためのバッファへのポインタ、そして、バッファのサイズを取ります。関数のプロトタイプを見ると、バッファへのポインタのところに、char __user *buf
と、__user
という妙な飾りがついています。
これは、注意が必要です。今、ドライバは、カーネルアドレス空間というカーネル専用の特殊なアドレス空間の中で処理をしています。このread関数を呼び出したユーザー空間のアプリケーションは、そのアプリケーション自身の仮想アドレス空間の中で実行されています。この2つのアドレス空間は、「別のアドレス空間」です。このアドレスを混同してはいけません。実は、カーネル空間からユーザーのプロセス空間の方向には、「偶然」同じポインタでアクセスできたりするのですが、それは、禁止されています。(正確には、「出来ることもある」です。linuxの実装次第で、真に別のアドレス空間になっている実装も有りえます。その場合は、当然直接アクセスは不可能です。)ちなみに、逆方向は絶対に出来ません。
では、どうするか。*buf
に直接アクセスすることは出来ませんから、copy_to_user
,copy_from_user
の2つの関数でカーネル空間からユーザー空間に、またはその逆に、データをコピーして、それを使用します。今回は、カーネル空間からユーザー空間に値を持っていきたいので、copy_to_user
を使用することになります。
ちなみに、便利関数がありまして、put_user
,get_user
という関数で、一変数分の内容を簡単にコピーすることが可能です。この関数は、マクロ定義になっていて、変数の宣言によりコピーする大きさを自動で判別するようになっています。今回の場合ですと、char *
ですから、charひとつ分、つまり、1バイトです。
さて、本体に移ります。
まず、openの時に、file.private_data
に保存したbeep_device_info
を持ってきます。
gpioからの値の取得は、gpiod_get_value
という関数で行います。gpioディスクリプタを渡すと、gpioの現在値を、0か1の整数で返してくれます。この数字をアスキーコードの'0'か'1'に変換した文字を、put_user
関数に渡して*buf
にコピーします。
この関数は、返したバイト数を戻り値として取りますので、今回は1を返します。
write関数
続いて、write関数です。この関数は、*bufの一バイト目の文字により、圧電ブザーのオン・オフを切り替えます。自動offタイマーが有効なときは、タイマーのセットも行います。
タイマーの割込み関数も同時に掲載します。
static ssize_t beep_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_pos) {
struct beep_device_info *bdev = fp->private_data;
char outValue;
unsigned long expires;
int result;
get_user(outValue, &buf[0]);
if (outValue=='0' || outValue=='1') {
gpiod_set_value(bdev->gpio , outValue - '0');
if ( outValue == '1' && bdev->ringing_time_jiffies > 0) {
expires = jiffies + bdev->ringing_time_jiffies;
result = mod_timer(&bdev->ringing_timer, expires);
pr_devel("%s: timer_start!(%lu jiffies)(active=%d)\n", __func__, expires, result);
}
pr_devel("%s: writed [%c] \n", __func__, outValue);
} else {
pr_info("%s: no writed. arg=\"%c\"\n", __func__ , outValue);
}
return count;
}
void beep_off_when_timeup(struct timer_list *timer) {
struct beep_device_info *bdev = container_of(timer, struct beep_device_info, ringing_timer);
if (!bdev) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return;
}
gpiod_set_value(bdev->gpio, 0);
}
この関数は、file
構造体へのポインタと、書き込む値を持つバッファのポインタ、バッファのサイズ、そして書き込み位置を引数として取ります。
beep_read
と同じく、まずは、beep_device_info
のポインタを取得します。
get_user
でバッファの値の1バイト目を取得し、この値が0
か1
なら、gpioをセットします。gpioのセットは、gpiod_set_value
関数で行います。gpioディスクリプタとセットする値を渡すと、指定したgpioをその値に切り替えてくれます。
次に、onに切り替える時、自動offタイマーが有効であれば、タイマーの起動を行います。これが、mod_timer
関数です。
タイマーの時間の単位は、jiffieと呼ばれます。jiffiesというカウンタがあり、1秒間にHZ回、jiffiesをインクリメントしています。ある一定の時間間隔で何かをするときのlinuxシステム内の標準時計として、このカウンタは機能します。いわば、linuxシステムの時刻ですね。ちなみに、HZ
は、カーネルビルド時に.configで設定されています。手元のRaspberryPi4では、HZ=100でした。つまり、10ms毎にjiffiesはカウントされています。linuxのタイマー関数も、このjiffiesを元に機能します。
タイマーの挙動は、jiffiesが指定した値になった時、カウントアップ関数が呼ばれるようになっています。
beep_device_info.ringing_time_jiffies
に何jiffieでカウントアップするかを設定しています。現在時刻は、jiffies
ですので、カウントアップ時刻は、jiffeis+ringing_time_jiffiesになります。
mod_timer
関数は、タイマー生成の時に作ったtimer_list
と、このカウントアップ時刻を引数として取ります。
さて、初期化時に指定した、タイムアップ時の関数が、beep_off_when_timeup
です。このタイムアップ関数に渡される引数のtimer_list
構造体も、beep_device_info
にあります。というわけで、container_of
を使用しbeep_device_info
を取得して、gpiod_set_value
関数で圧電ブザーのgpioをオフに設定しています。
beep_write
関数の戻り値は、書き込んだバイト数となります。
ちなみに、ドライバの仕様で、write関数は1バイト目のみを使用し、残りは書き込まれたものとして扱うと定義しましたので、戻り値として、バッファのサイズであるcountを、そのまま返します。
ioctl関数
ファイルアクセス関数の最後、ioctlです。この関数は、readやwriteで制御しきれない、パラメータの設定などに使用します。何に使用するか、何が起こるかは、一切がデバイスドライバに任されています。ioctlは、ユーザプログラムから呼び出すときは、ioctl(int fd, int cmd,...)
となっていて、第二引数にはコマンドを表す数字を、第三引数以降に関してはデバイスドライバ次第となっています。
というわけで、まぁ、なにからなにまでデバイスドライバ任せの関数です。
今回は、この関数で、自動offタイマーのタイムアップ時間を設定することにします。コマンドは一つ、必要な引数は、時間を表す数字が一つです。
この関数を使うときは、普通は、デバイスドライバの製作者が発行したヘッダファイルを見るのが普通ですが、今回は、単純極まりないので目一杯手を抜きます。ヘッダファイル作りません。
受け付けるコマンドは、1のみです。また第三パラメータには、時間をそのまま整数値で指定します。呼び出し形式は、ioctl(int fd, 1, unsigned long time_ms)
となります。time_msの引数はlongになっていますが、実際はintの範囲の数値しか取りません。それ以上の数値が指定されたら、強制的にintに丸めます。write関数で示したとおり、ここに0を指定するとタイマーが無効となります。
では、ソースです。
static long beep_ioctl(struct file *fp, unsigned int cmd, unsigned long palm) {
// cmd=1のみ有効。palmの値でringing_time_jiffiesを更新する。
if (cmd == 1) {
struct beep_device_info *bdev = fp->private_data;
bdev->ringing_time_jiffies = msecs_to_jiffies((unsigned int)palm);
pr_devel("%s: 鳴動時間変更(jiffies=%ld)(msec=%d)\n",
__func__, bdev->ringing_time_jiffies, (unsigned int)palm);
}
return 0;
}
この関数、普通は、cmd
の値を元にswitch文となることが多いのですが、今回は、シンプルです。cmdに1以外のものが来たら、無視します。
例のごとく、beep_device_info
を取得して、beep_device_info.ringing_time_jiffies
を設定します。
名前の通り、単位は、jiffiesです。入力は、msec単位ですので、変換する必要が有ります。jiffiesはHZの逆数ですから、sec*HZで計算できます。よく使う計算ですので、msecs_to_jiffies
という関数が用意されています。素直にこれを使わせてもらいます。
これで、ユーザーサイドから、自動offタイマーの設定が出来るようになりました。
sysfsの実装
さて、これが、最後の部分です。タイマーの設定は、ioctlで出来るようにしたわけですが、コマンドラインからは少々使いづらいです。
デバイスドライバの設定に使える機構として、linuxでは、sysfsという機構が用意されています。これは、デバイスドライバの設定を/sys/device
以下のファイルに読み書きすることで出来るようにするための機能です。
この実装の形は、cdevにファイル操作関数を登録した時と、とても良く似ています。
順番に見ていきます。
device_attribute構造体
まず、sysfs内に作成するファイルを定義するstruct device_attribute
構造体を定義します。
static struct device_attribute dev_attr_beep_ringing_time = {
.attr = {
.name = "beep_ringing_time",
.mode = S_IRUGO | S_IWUGO,
},
.show = read_beep_ringing_time,
.store = write_beep_ringing_time,
};
この構造体では、名前、そしてファイルのアクセスモード、そして、読み書きする時に実行する関数を定義します。ファイルのアクセスモードの書き方は、8進数の数字でも書けます。chmod
の時に書く数字と同じです。各種のアクセスモードが定数として定義されています。今回は、全ユーザーに読み書きを許可しています。S_IRUGOが読み出しをUとGとOに許可ですから全て許可ですね。S_IWUGOも同じで、書き込みを全てに許可です。
.showと、.storeの関数は、各々、読み出しと書き込み時の処理関数を示します。
sysfsの作成
sysfsの作成そのものはとても簡単です。
static int make_sysfs(struct device *dev) {
return device_create_file(dev, &dev_attr_beep_ringing_time);
}
static void remove_sysfs(struct device *dev) {
device_remove_file(dev, &dev_attr_beep_ringing_time);
}
device
構造体と、さっきのdevice_attribute
構造体をdevice_create_file
に渡すだけ。簡単ですね。
開放も、device_remove_file
を呼び出すだけです。
これで、/sys/device/platform/beep@0/beep_ringing_time
というファイルが作成されます。このファイルに、文字列で、msec単位の数字を書き込めば鳴動off時間の設定が出来るようにします。
さて、実は、device_attribute
の定義には、便利関数がいくつか定義されています。例えば、
# define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
のような感じです。
これを使うと、DEVICE_ATTR(beep_ringing_time, S_IRUGO | S_IWUGO,read_beep_ringing_time,write_beep_ringing_time);
なんて感じで、一行で構造体の定義が終わっちゃいます。
使わなかったのには、理由が有りまして・・・linuxのドキュメントを読んでいくと、sysfsの説明で、このファイルは一般ユーザーへの書き込みを禁止することを推奨しています。そして、このマクロなんですが、おせっかいなことにユーザーへの書き込みを許可するmodeフラグを受け付けないようになってるみたいです。
このチェックはコンパイル時のチェックなので、上の行をコンパイルすると、エラーになってはねられます。ちょっと残念。まぁ、推奨を守らないほうが悪いということで。てなわけで、この実装仕様は、推奨外です。まぁ、普通、ドライバの設定をユーザーが自由に触れる方がおかしいと言えばそのとおりなんですけどね。
読み書きの関数
次は、sysfsの読み書きの関数です。
// sysfs ringing_timeの読み込みと書き込み
static ssize_t read_beep_ringing_time(struct device *dev, struct device_attribute *attr, char *buf) {
struct beep_device_info *bdev = dev_get_drvdata(dev);
if (!bdev) {
pr_err("%s: デバイス情報の取得に失敗しました。\n", __func__);
return -EFAULT;
}
return snprintf(buf, PAGE_SIZE, "%d\n", jiffies_to_msecs(bdev->ringing_time_jiffies));
}
# define MAX_LENGTH_LONG_NUM 11 // unsigned intの最大値 4,294,967,295
static ssize_t write_beep_ringing_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
struct beep_device_info *bdev;
char source[MAX_LENGTH_LONG_NUM];
unsigned int time_ms;
int result;
bdev = dev_get_drvdata(dev);
if (!bdev) {
pr_err("%s: デバイス情報の取得に失敗しました。\n", __func__);
return -EFAULT;
}
if (count > MAX_LENGTH_LONG_NUM-1) {
pr_err("%s: 引数が長過ぎる(length=%d)\n", __func__, count);
return -EINVAL;
}
strncpy(source, buf, count);
source[count] = 0;
result = kstrtouint(source, 10, &time_ms);
if (result) {
pr_err("%s: 引数がおかしい(%s)\n",__func__, source);
return -EINVAL;
}
bdev->ringing_time_jiffies = msecs_to_jiffies(time_ms);
pr_devel("%s: ringing_timeをセットしました。(msec=%d)(jiffies=%ld)\n",
__func__, time_ms, bdev->ringing_time_jiffies);
return count;
}
read関数は、ごく素直に、ringing_time_jiffiesをmsecに変換して、文字列としてbuf
にかえしているだけです。
さて、この関数、不思議なことにbuf
の大きさが指定されていません。実は、このバッファの大きさは、PAGE_SIZE
に固定されています。普通は、そんなに大きなデータを返すコマンドではありません。設定一つを返すのが本来ですので。問題にはならないはずですが、必ずsnprintf
を使い値を返すように、linuxのドキュメントでは指定されています。
write関数も、bufでもらってきた文字列を変換してringing_ime_jiffies
に設定しているだけです。妙に長いのは、ひたすらbuf
内の文字列をチェックしているからです。また、万が一文字列がNULLで終わっていなかったときのために、文字列の最後のNULLを念の為追加しています。
この文字列は、ユーザーから与えられるものです。べらぼうに長いとかなどのイタズラも有りえます。長さも想定される最大値を超えていないことを確認します。想定される長さは、MAX_LENGTH_LONG_NUM
で定義しました。unsigned int
はこの長さに収まるはずです。
kstrtouint
関数は、標準ライブラリのatoi
のカーネル版です。linux/kernel.h
で定義されています。10進だけでなく、各種の進数をサポートしています。
各種の整数に変換するための関数群があります。ちなみに、戻り値は必ずチェックすることとわざわざ関数定義に書いてあります。
int __must_check kstrtouint(const char *s, unsigned int base, unsigned int *res);
定義を見ていると、こんな関数が結構たくさんあります。やっぱりチェックは大切なんですね。
ちなみに、引数が数字以外の文字列でないことの確認も、この関数内のチェックに依存しています。
後始末
さて、最後に、後始末です。ドライバが確保した各種の資源を開放します。
単純に開放のための関数をひたすら呼び出すだけです。
何を開放しているかは、関数名と引数で想定されるとおりです。
一気に、ソースだけ出します。
// キャラクタデバイス及び/dev/beep0の登録解除
static void remove_udev(struct beep_device_info *bdev) {
dev_t dev = MKDEV(bdev->major, MINOR_BASE);
for (int minor=MINOR_BASE; minor<MINOR_BASE+MINOR_NUM; minor++) {
/* /sys/class/mygpio の削除 */
device_destroy(bdev->class, MKDEV(bdev->major, minor));
}
class_destroy(bdev->class); /* クラス登録解除 */
cdev_del(&bdev->cdev); /* デバイス除去 */
unregister_chrdev_region(dev, MINOR_NUM); /* メジャー番号除去 */
}
static int beep_remove(struct platform_device *p_dev) {
struct beep_device_info *bdev = dev_get_drvdata(&p_dev->dev);
remove_udev(bdev);
remove_sysfs(&p_dev->dev);
// gpioデバイスの開放
if (bdev->gpio) {
gpiod_put(bdev->gpio);
}
del_timer(&bdev->ringing_timer);
pr_info("%s:beep driver unloaded\n",__func__);
return 0;
}
pr_develについて
関数の要所要所に、pr_devel
が埋め込んであります。この関数は、kprintf(DEBUG,...)
を呼び出す便利マクロです。pr_info
等も同じなんですが、この関数独自の挙動がひとつだけ有ります。pr_devel
は、#define DEDUG 1
が定義されていないと展開されません。beep.cソースの冒頭にこの定義を書きました。これをコメントアウトすれば、余計なメッセージがdmesgから一気に消えます。
kprintf
は便利なコマンドでは有りますが、出力にはシステムのロギング機構を動かします。あまり軽い処理ではありません。やりすぎるとシステムが重くなります。そこで、デバッグ時以外はなかったことにするようになっているわけです。
コンパイルとテスト
これ以降、ビルドしたlinuxカーネルが、~/crowpi2/linux
にあり、(1)の時の処理が全部終わっているとします。beepドライバのソースは、~/crowpi2/beep
にあると仮定します。ツリー構造が違う場合は摘便読み替えてください。
まず最初に、(1)の時に作ったmake_crowpiを適用します。必要な環境変数を設定するためです。
ubuntu@localhost:~/crowpi2$. ./make_crowpi
デバイスツリーファイルのコンパイル
デバイスツリーファイルですが、実際に使うのはバイナリファイルにする必要が有ります。コンパイルするには、dtcというツールを使います。ubuntuにはデフォルトでは多分入っていません。apt install device-tree-compiler
でインストールできます。が、コンパイルするだけなら、実は、インストールの必要はありません。linuxのソースをビルドした時に、dtcコンパイラもちゃんと作ってくれています。これは、crowpi2/linux/scripts/dtc/dtc
に有ります。これを使って、ビルドしたlinuxに含まれるdtbもコンパイルされています。バージョンの絡みなどもある可能性が有りますので、ビルドの時に作った、こっちのdtcを使ったほうが無難かもしれません。
使用方法は、~/crowpi2/beep
ディレクトリの中で
ubuntu@localhost:~/crowpi2/beep$../linux/scripts/dtc/dtc -@ -I dts -O dtb -o beep.dtbo beep.dtso
と実行します。デバッグ中など、dtcのパスを打つのが嫌になりますので、dtcのシンボリックリンクを作っておくと、少し楽になります。
オプションの-I
は入力するファイルの形式、-O
は出力するファイルの形式です。想像が出来るかもしれませんが、逆にすると、バイナリのdtbをdtsの形にリバースコンパイルすることが可能です。
-o
は出力するファイル名です。
今回は、デバイスツリーオーバーレイ用のファイルなので、本体のデバイスツリーにあるはずの識別子を使っています。定義は本体にあるわけですが、オーバーレイ用ファイルには無いため、識別子がないとエラーになります。これを防ぐために、-@
をつけます。オーバーレイ用のオプションです。
これを実行すると、beep.dtbo
というファイルが出来上がります。デバイスツリーの設定としてこのファイルを使用します。
デバイスドライバのビルド
ビルドには、下記のMakefile
を使用します。
CFILES = beep.c
obj-m := beep.o
gpioModule-objs := $(CFILES:.c=.o)
ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
PWD := $(shell pwd)
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
(1)のときより、少し賑やかになっています。標準のコンパイラ指定ですと、少し文法が古く、記述が面倒なところがあるので、c99を使用するようにccflags
を追加しています。その後ろの-W
関係は、ワーニングメッセージのオプションです。本来は-Wall
だけにして全てのワーニングが消えるまで頑張るべきです。でも、変数を関数冒頭以外で宣言するとワーニングになるのは少々やり過ぎかと。確かに、それが本来の姿だと言われればそのとおりなんですが・・・。というわけで、このワーニングだけはoffにしています。
コンパイルそのものは、至って簡単です。
ubuntu@localhost:~/crowpi2/beep$ make
この一言で、全てが終わります。必要なツールチェーンの指定やビルド対象のCPU設定などは、全部make_corwpi2で環境変数にセットしてますので、これですみます。
もし、無事、エラーがなければ、beep.ko
というファイルが出来上がります。これが、デバイスドライバ本体となります。
実機でのテスト
さて、実機の適当なディレクトリに、beep.dtbo
とbeep.ko
の2つのファイルをコピーします。
コピー先は、ホーム配下につくった適当なディレクトリでかまいません。例えば
ubuntu@localhost:~/crowpi2/beep$ scp beep.dtbo beep.ko __crowpi2:test/
みたいな感じで・・・。ここでは、ホームディレクトリにtest
ディレクトリを作成しておきます。__crowpi2
に関しては、ネットワーク環境に合わせて実機のパスになるように適時読み替えてください。マイクロSDに直接、母艦からコピーしても良いでしょう。
デバイスツリーオーバーレイの適用
さて、テストするために、作成したbeep.dtbo
をシステムに適用します。
デバイスツリーオーバーレイの機能の一つに、configFSという機構が有ります。この機構を使えば、再起動無しで、テスト的にdtbファイルをシステムに読み込ませることが出来ます。
次のような使い方になります。
pi@crowpi2:~/test$ sudo mkdir /sys/kernel/config/device-tree/overlays/beep
pi@crowpi2:~/test$ sudo cp beep.dtbo /sys/kernel/config/device-tree/overlays/beep/dtbo
これで、成功していれば、/sys/proc/device-tree
配下に、beep@0
というディレクトリが出現しているはずです。
この中をみてみると
pi@crowpi2:~$ ls /proc/device-tree/beep@0
compatible gpios name phandle pinctrl-0 pinctrl-names status
pi@crowpi2:~$ hexdump -C /proc/device-tree/beep@0/compatible
00000000 63 72 6f 77 70 69 32 2d 62 65 65 70 00 |crowpi2-beep.|
0000000d
pi@crowpi2:~$ hexdump -C /proc/device-tree/beep@0/gpios
00000000 00 00 00 0f 00 00 00 12 00 00 00 00 |............|
0000000c
pi@crowpi2:~$ hexdump -C /proc/device-tree/beep@0/status
00000000 6f 6b 61 79 00 |okay.|
00000005
pi@crowpi2:~$
ディレクトリの中身は、そのままbeep.dtso
で書いたパラメータが並んでいるのが確認できます。
項目の中身はバイナリなので、hexdumpして中身を見てみます。compatible
を見てみると、ちゃんと、指定した"crowpi2-beep"の文字が見えます。
gpios
の中身をダンプしてみると、中に、00000012h、つまり、10進数で18と指定したgpio番号があるのがわかります。その前の数字は、&gpio
が変換されたもので、dtb本体のgpioがもつハンドルです。後ろの00000000hは、アクティブハイを指定した0が書かれています。
ついでに、status
も見てみると、ちゃんと"okay."の文字列が見えますね。
こんな感じで、/proc/device-tree/
配下のフォルダを見ると、全てのデバイスツリーの内容をツリー状に確認することが出来ます。
ちなみに、解除したければ、
pi@crowpi2:~/test$ sudo rmdir /sys/kernel/config/device-tree/overlays/beep
と、さっき作ったディレクトリを削除すれば、システムからも削除されます。今は削除しないでくださいね。
デバイスドライバの組み込みと実行
デバイスドライバを組み込む前に、ちょっと準備をします。
実は、このままデバイスドライバを組み込むと、/dev/beep0
の操作にルート権限が必要になります。
変更するには、/dev/udev/rules.d
の配下に、ルールファイルを追加するだけです。/dev/udev/rules.d/99-mygpio.rules
ファイルを作って、次の内容にしてください。(当然のようにルート権限が必要です。)
KERNEL=="beep0", GROUP="root", MODE="0666"
これで、beep0ファイルが所有グループrootで、mode=0666、つまり誰でも書き込み可となります。modeの数字は、chmodなどで指定するファイルの読み書きの設定のフラグと同じです。
ここで、例えば、GROUPをbeepにして、modeを0664にすれば、beepグループに所属するひとだけを操作可能に出来ます。このパターンのデバイスも結構有りますね。
同じところにある、99-com.rules
の中身を見てみれば、ここで、/dev
配下のいろいろなファイルの読み書きの権限が設定されている様子が見られます。
デバイスドライバを手動で組み込むのは簡単です。
pi@crowpi2:~/test$ sudo insmod beep.ko
pi@crowpi2:~/test$
単に、これだけです。静かに終わっちゃいますが、これで組み込みまれています。dmesgを確認してみましょう
pi@crowpi2:~/test$ dmesg | tail -n 1
[ 4270.931075] beep_probe:beep driver init
ちゃんと組み込まれています。beep_probe
関数で出力したメッセージが見えますね。
さて、/dev
を確認してみましょう。
pi@crowpi2:~/test$ ls /dev/be*
/dev/beep0
ちゃんと、/dev/beep0
ファイルが出現しています。
pi@crowpi2:~/test$ echo 1 > /dev/beep0
pi@crowpi2:~/test$ echo 0 > /dev/beep0
とやってあげれば、ちゃんとブザー音がなり、2行目を実行すると止まります。
dmesgを再度確認してみます。
pi@crowpi2:~/test$ dmesg | tail -n 7
[ 4270.931075] beep_probe:beep driver init
[ 4601.011271] beep_open:beep open
[ 4601.011332] beep_write: writed [1]
[ 4601.011355] beep_close:beep closed
[ 4620.938252] beep_open:beep open
[ 4620.938300] beep_write: writed [0]
[ 4620.938321] beep_close:beep closed
オープン・書き込み・クローズが実行されている様子が確認できます。
さて、次に、/sys/device/platform
をlsで確認してみると、beep@0
のディレクトリが出来ているのが見られるはずです。
この中身を見てみると、
pi@crowpi2:~/test$ ls /sys/devices/platform/beep@0
beep_ringing_time driver driver_override modalias of_node power subsystem uevent
pi@crowpi2:~/test$
のように、sysfsの登録で登録したbeep_ringing_time
が見られると思います。
ここに、echoで値を書き込んでみます。
pi@crowpi2:~/test$ echo 5000 > /sys/devices/platform/beep@0/beep_ringing_time
pi@crowpi2:~/test$ dmesg | tail -n 10
[ 4270.930233] beep: loading out-of-tree module taints kernel.
[ 4270.931075] beep_probe:beep driver init
[ 4601.011271] beep_open:beep open
[ 4601.011323] beep_write: timer_start!(430393 jiffies)(active=0)
[ 4601.011332] beep_write: writed [1]
[ 4601.011355] beep_close:beep closed
[ 4620.938252] beep_open:beep open
[ 4620.938300] beep_write: writed [0]
[ 4620.938321] beep_close:beep closed
[ 5053.762000] write_beep_ringing_time: ringing_timeをセットしました。(msec=5000)(jiffies=500)
書き込み後にdmesgを確認すると、write_beep_ringing_time
が実行されている様子がでています。
もう一度、ブザー音を鳴らしてみましょう。
i@crowpi2:~/test$ echo 1 > /dev/beep0
pi@crowpi2:~/test$ dmesg | tail -n 10
[ 4601.011332] beep_write: writed [1]
[ 4601.011355] beep_close:beep closed
[ 4620.938252] beep_open:beep open
[ 4620.938300] beep_write: writed [0]
[ 4620.938321] beep_close:beep closed
[ 5053.762000] write_beep_ringing_time: ringing_timeをセットしました。(msec=5000)(jiffies=500)
[ 5196.523675] beep_open:beep open
[ 5196.523722] beep_write: timer_start!(490144 jiffies)(active=0)
[ 5196.523731] beep_write: writed [1]
[ 5196.523753] beep_close:beep closed
今度は、5秒後にブザー音が自動的に止まったはずです。dmesgを確認すると、beep_write
関数でタイマーを起動したメッセージがでていますね。
sysfsには、readも指定しましたので、これも確認してみましょう。
pi@crowpi2:~/test$ cat /sys/devices/platform/beep@0/beep_ringing_time
5000
さっき、書き込んだ値がちゃんとでてきました。
最後に、デバイスドライバを取り外してみます。
pi@crowpi2:~/test$ sudo rmmod beep
pi@crowpi2:~/test$ dmesg | tail -n 10
[ 5634.184233] beep_probe:beep driver init
[ 5688.797182] write_beep_ringing_time: ringing_timeをセットしました。(msec=0)(jiffies=0)
[ 5711.587503] beep_open:beep open
[ 5711.587543] beep_write: writed [1]
[ 5711.587563] beep_close:beep closed
[ 5721.523079] beep_open:beep open
[ 5721.523125] beep_write: writed [0]
[ 5721.523149] beep_close:beep closed
[ 5822.492913] write_beep_ringing_time: ringing_timeをセットしました。(msec=5000)(jiffies=500)
[ 6587.417274] beep_remove:beep driver unloaded
これも、簡単に出来ます。dmesgを見てみると、最後の行に、beep_remove
が実行された痕跡が残ってますね。
ドライバのインストール
このままですと、システムを再起動すると、なくなっちゃいますので、一応、ちゃんとインストールしてみます。
デバイスツリーオーバーレイの組み込み
まず、デバイスツリーオーバーレイファイルです。
これは、/boot/overlays
にコピーします。
その上で、/boot/config.txt
に次の行を追加します。
### ここまで省略 ###
hdmi_mode=1
hdmi_mode=87
hdmi_cvt 1920 1080 60 6 0 0 0
hdmi_drive=2
dtoverlay=w1-gpio
dtoverlay=beep
最後の行のdtoverlay=beep
が追加した行です。
ちゃんと正常に追加出来たかは、/proc/device-tree
を見てみればわかります。正常に追加できていれば、ここに、beep@0
のディレクトリがでてくるはずです。
pi@crowpi2:/proc/device-tree$ ls -d /proc/device-tree/beep*
/proc/device-tree/beep@0
ちゃんとでてるようです。
デバイスドライバの組み込み
まずは、beep.koを本来あるべき正しい場所にコピーします。
pi@crowpi2:~/test$ sudo cp beep.ko /lib/modules/$(uname -r)/kernel/drivers/
pi@crowpi2:~/test$ sudo depmod -ae
この2行目は、beep.koに対する依存関係の調査し登録するのためのコマンドで、システムに対して必要です。
この後、普通ですと、/etc/modules-load.d/modules.conf
に対する登録なんて作業が続くんですが、デバイスツリーに対応したドライバの場合は、不要です。デバイスツリーを展開する際にシステムがよしなに自動でやってくれます。
さて、再起動すれば、これで終わりです。テストの時にやった動作が全て再現できます。
デバッグメッセージの排除
printkの時に触れたように、あまり過剰にprintkを実行するのはよろしくありません。今の状態のドライバはとっても饒舌です。
もし、常時使うのであれば、ソースの冒頭の#define DEBUG 1
をコメントアウトします。
これで、pr_devel
はなかったことになります。
将来のデバッグ用に、ソースにだけは残しておくことができます。
全ソースコード
最後に、ソースコードを一括して掲載します。
【2/20追記】ソースコードをgithubにあげました。urlは、https://github.com/mitoneko/beep_driver_for_crowpi2.gitです。githubのコード本体には、この記事以降の若干の改定が含まれます。
CFILES = beep.c
obj-m := beep.o
gpioModule-objs := $(CFILES:.c=.o)
ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
PWD := $(shell pwd)
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
/dts-v1/;
/plugin/;
/{
compatible = "brcm,bcm2835";
fragment@0 {
// Configure the gpio pin controller
target = <&gpio>;
__overlay__ {
pin_state: beep-pin@0 {
brcm,pins = <18>; // gpio number
brcm,function = <1>; // 0 = input, 1 = output
brcm,pull = <0>; // 0 = none, 1 = pull down, 2 = pull up
};
};
};
fragment@1 {
target-path = "/";
__overlay__ {
beep: beep@0 {
compatible = "crowpi2-beep";
pinctrl-names = "default";
pinctrl-0 = <&pin_state>;
gpios = <&gpio 18 0>;
status = "okay";
};
};
};
};
# define DEBUG 1
# include <linux/init.h>
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/fs.h>
# include <linux/cdev.h>
# include <linux/sched.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/gpio/consumer.h>
# include <linux/timer.h>
# include <asm/current.h>
# include <asm/uaccess.h>
# define DRIVER_NAME "beep"
static const unsigned int MINOR_BASE = 0; // udev minor番号の始まり
static const unsigned int MINOR_NUM = 1; // udev minorの個数
// デバイス全域で使用する変数達
// beep_probeでメモリ確保する。
struct beep_device_info {
unsigned int major; // udev major番号
struct cdev cdev;
struct class *class;
struct gpio_desc *gpio;
struct timer_list ringing_timer;
unsigned long ringing_time_jiffies; // 鳴動時間(単位:jiffies) 0で永遠
};
// /dev/beep0配下のアクセス関数
// デバイス情報を取得し、file構造体に保存する。
static int beep_open(struct inode *inode, struct file *file) {
pr_devel("%s:beep open\n", __func__);
struct beep_device_info *bdev = container_of(inode->i_cdev, struct beep_device_info, cdev);
if (bdev==NULL) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return -EFAULT;
}
file->private_data = bdev;
return 0;
}
static int beep_close(struct inode *inode, struct file *file) {
//実質何もしない
pr_devel("%s:beep closed\n", __func__);
return 0;
}
static ssize_t beep_read(struct file *fp, char __user *buf, size_t count, loff_t *f_pos) {
struct beep_device_info *bdev = fp->private_data;
if (!bdev) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return -EFAULT;
}
put_user(gpiod_get_value(bdev->gpio)+'0', &buf[0]);
return 1;
}
static ssize_t beep_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_pos) {
struct beep_device_info *bdev = fp->private_data;
char outValue;
unsigned long expires;
int result;
get_user(outValue, &buf[0]);
if (outValue=='0' || outValue=='1') {
gpiod_set_value(bdev->gpio , outValue - '0');
if ( outValue == '1' && bdev->ringing_time_jiffies > 0) {
expires = jiffies + bdev->ringing_time_jiffies;
result = mod_timer(&bdev->ringing_timer, expires);
pr_devel("%s: timer_start!(%lu jiffies)(active=%d)\n", __func__, expires, result);
}
pr_devel("%s: writed [%c] \n", __func__, outValue);
} else {
pr_info("%s: no writed. arg=\"%c\"\n", __func__ , outValue);
}
return count;
}
void beep_off_when_timeup(struct timer_list *timer) {
struct beep_device_info *bdev = container_of(timer, struct beep_device_info, ringing_timer);
if (!bdev) {
pr_err("%s:デバイス情報取得失敗\n", __func__);
return;
}
gpiod_set_value(bdev->gpio, 0);
}
static long beep_ioctl(struct file *fp, unsigned int cmd, unsigned long palm) {
// cmd=1のみ有効。palmの値でringing_time_jiffiesを更新する。
if (cmd == 1) {
struct beep_device_info *bdev = fp->private_data;
bdev->ringing_time_jiffies = msecs_to_jiffies((unsigned int)palm);
pr_devel("%s: 鳴動時間変更(jiffies=%ld)(msec=%d)\n",
__func__, bdev->ringing_time_jiffies, (unsigned int)palm);
}
return 0;
}
/* ハンドラ テーブル */
struct file_operations beep_fops = {
.open = beep_open,
.release = beep_close,
.read = beep_read,
.write = beep_write,
.unlocked_ioctl = beep_ioctl,
.compat_ioctl = beep_ioctl,
};
// キャラクタデバイスの登録と、/dev/beep0の生成
static int make_udev(struct beep_device_info *bdev, const char* name) {
int ret = 0;
dev_t dev;
/* メジャー番号取得 */
ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, name);
if (ret != 0) {
pr_alert("%s: メジャー番号取得失敗(%d)\n", __func__, ret);
goto err;
}
bdev->major = MAJOR(dev);
/* カーネルへのキャラクタデバイスドライバ登録 */
cdev_init(&bdev->cdev, &beep_fops);
bdev->cdev.owner = THIS_MODULE;
ret = cdev_add(&bdev->cdev, dev, MINOR_NUM);
if (ret != 0) {
pr_alert("%s: キャラクタデバイス登録失敗(%d)\n", __func__, ret);
goto err_cdev_add;
}
/* カーネルクラス登録 */
bdev->class = class_create(THIS_MODULE, name);
if (IS_ERR(bdev->class)) {
pr_alert("%s: カーネルクラス登録失敗\n", __func__);
ret = -PTR_ERR(bdev->class);
goto err_class_create;
}
/* /sys/class/mygpio の生成 */
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(bdev->class, NULL, MKDEV(bdev->major, minor), NULL, "beep%d", minor);
}
return 0;
err_class_create:
cdev_del(&bdev->cdev);
err_cdev_add:
unregister_chrdev_region(dev, MINOR_NUM);
err:
return ret;
}
// キャラクタデバイス及び/dev/beep0の登録解除
static void remove_udev(struct beep_device_info *bdev) {
dev_t dev = MKDEV(bdev->major, MINOR_BASE);
for (int minor=MINOR_BASE; minor<MINOR_BASE+MINOR_NUM; minor++) {
/* /sys/class/mygpio の削除 */
device_destroy(bdev->class, MKDEV(bdev->major, minor));
}
class_destroy(bdev->class); /* クラス登録解除 */
cdev_del(&bdev->cdev); /* デバイス除去 */
unregister_chrdev_region(dev, MINOR_NUM); /* メジャー番号除去 */
}
// sysfs ringing_timeの読み込みと書き込み
static ssize_t read_beep_ringing_time(struct device *dev, struct device_attribute *attr, char *buf) {
struct beep_device_info *bdev = dev_get_drvdata(dev);
if (!bdev) {
pr_err("%s: デバイス情報の取得に失敗しました。\n", __func__);
return -EFAULT;
}
return snprintf(buf, PAGE_SIZE, "%d\n", jiffies_to_msecs(bdev->ringing_time_jiffies));
}
# define MAX_LENGTH_LONG_NUM 11 // unsigned intの最大値 4,294,967,295
static ssize_t write_beep_ringing_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
struct beep_device_info *bdev;
char source[MAX_LENGTH_LONG_NUM];
unsigned int time_ms;
int result;
bdev = dev_get_drvdata(dev);
if (!bdev) {
pr_err("%s: デバイス情報の取得に失敗しました。\n", __func__);
return -EFAULT;
}
if (count > MAX_LENGTH_LONG_NUM-1) {
pr_err("%s: 引数が長過ぎる(length=%d)\n", __func__, count);
return -EINVAL;
}
strncpy(source, buf, count);
source[count] = 0;
result = kstrtouint(source, 10, &time_ms);
if (result) {
pr_err("%s: 引数がおかしい(%s)\n",__func__, source);
return -EINVAL;
}
bdev->ringing_time_jiffies = msecs_to_jiffies(time_ms);
pr_devel("%s: ringing_timeをセットしました。(msec=%d)(jiffies=%ld)\n",
__func__, time_ms, bdev->ringing_time_jiffies);
return count;
}
// sysfs(/sys/device/platform/beep@0/beep_ringing_time)の生成
static struct device_attribute dev_attr_beep_ringing_time = {
.attr = {
.name = "beep_ringing_time",
.mode = S_IRUGO | S_IWUGO,
},
.show = read_beep_ringing_time,
.store = write_beep_ringing_time,
};
static int make_sysfs(struct device *dev) {
return device_create_file(dev, &dev_attr_beep_ringing_time);
}
static void remove_sysfs(struct device *dev) {
device_remove_file(dev, &dev_attr_beep_ringing_time);
}
// ドライバの初期化 及び 後始末
static const struct of_device_id of_beep_ids[] = {
{ .compatible = "crowpi2-beep" } ,
{ },
};
MODULE_DEVICE_TABLE(of, of_beep_ids);
static int beep_probe(struct platform_device *p_dev) {
struct device *dev = &p_dev->dev;
struct beep_device_info *bdev;
int result;
if (!dev->of_node) {
pr_alert("%s:Not Exist of_node for BEEP DRIVER. Check DTB\n", __func__);
result = -ENODEV;
goto err;
}
// デバイス情報のメモリ確保と初期化
bdev = (struct beep_device_info*)devm_kzalloc(dev, sizeof(struct beep_device_info), GFP_KERNEL);
if (!bdev) {
pr_alert("%s: デバイス情報メモリ確保失敗\n", __func__);
result = -ENOMEM;
goto err;
}
dev_set_drvdata(dev, bdev);
// gpioの確保と初期化
bdev->gpio = devm_gpiod_get(dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(bdev->gpio)) {
result = -PTR_ERR(bdev->gpio);
pr_alert("%s: can not get GPIO.ERR(%d)\n", __func__, result);
goto err;
}
// udevの生成
result = make_udev(bdev, p_dev->name);
if (result != 0) {
pr_alert("%s:Fail make udev. gpio desc dispose!!!\n", __func__);
goto err_udev;
}
// sysfsの生成
result = make_sysfs(dev);
if (result != 0) {
pr_alert("%s: sysfs生成失敗\n", __func__);
goto err_sysfs;
}
// timerの生成
timer_setup(&bdev->ringing_timer, beep_off_when_timeup, 0);
bdev->ringing_time_jiffies = msecs_to_jiffies(0);
pr_info("%s:beep driver init\n",__func__);
return 0;
err_sysfs:
remove_udev(bdev);
err_udev:
gpiod_put(bdev->gpio);
err:
return result;
}
static int beep_remove(struct platform_device *p_dev) {
struct beep_device_info *bdev = dev_get_drvdata(&p_dev->dev);
remove_udev(bdev);
remove_sysfs(&p_dev->dev);
// gpioデバイスの開放
if (bdev->gpio) {
gpiod_put(bdev->gpio);
}
del_timer(&bdev->ringing_timer);
pr_info("%s:beep driver unloaded\n",__func__);
return 0;
}
static struct platform_driver beep_driver = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_beep_ids,
},
};
module_platform_driver(beep_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("This is beep driver for crowpi2");
MODULE_AUTHOR("mito");