組込み Linux デバイスドライバの作り方 その2  デバイス特殊ファイル作成・操作

『その1』では、簡単なモジュールを作成しましたが、そのモジュールをKernelに登録するだけで、デバイス特殊ファイルを生成していませんでした。 『その2』ではキャラクタ型のデバイスドライバの雛形を作って、デバイス特殊ファイルを生成してドライバとして動作させてみます。 デバイス特殊ファイルにするにもコードや生成方法があるのでここに記して行きたいと思います。

デバイスドライバとは

Kernelモジュールの一種で、ハードウェアにアクセスするモジュールの事です。 アプリケーションやシェルからなど、ユーザーとして制御するには、デバイス特殊ファイルを生成して、デバイス特殊ファイルを操作することによりハードウェアを制御します。

メジャー番号、マイナー番号

ドライバには4つが関連されます。

  1. ソースコードファイル名:test.c
  2. モジュール(ドライバ)ファイル名:test.ko — test.cから生成
  3. モジュール(ドライバ)名:test.cに指定。 下のコードでは『my_gpio_driver』
  4. デバイス特殊ファイル名:下の例では、/dev/MyDev0 —生成する際にメジャー番号とマイナー番号が必要

Kernelはメジャー番号でハードウェアの種類を管理しています。 同一のハードウェアの中で分かれているものをマイナー番号で管理しています。 同一のハードウェアには一つのモジュールになります。 デバイス特殊ファイルは、メジャー番号とマイナー番号から生成されます。

メジャー番号は、主要なハードウェアは既に割り当てられています。(device number – The Linux Kernel ArchivesLinux allocated devices (4.x+ version)) 空いている場所もあるので、今回はKernelに空いているメジャー番号を自動的に割り付けてもらいます。(234-254、384-511はDynamic(自動割り当て)用)

また、デバイス特殊ファイル名もLinux Kernelで既に定義されているものもあるため、たとダブらない様にしなければならない。 Linux Kernelでは、『公表する前に、相談してください。』と書いてあります。%

コード作成

Makefileは前回『その1』と同じです。 なので.cのファイル名も『その1』と同じです。 コードの説明は後でします。

~/test/drivers/test.c
#include <linux/module.h>   // MODULE macro, THIS_MODULE
#include <linux/kernel.h>   // printk()
#include <linux/proc_fs.h>  // alloc_chrdev_region(), unregister_chrdev_region()
#include <linux/cdev.h>     // cdev_int(), cdev_add(), cdev_del()
#include <asm/uaccess.h>    // copy_from_user(), copy_to_user()

#define DRIVER_NAME "my_gpio_driver"
#define MINOR_COUNT 4       // 接続するマイナー番号 0 ~ 3

static dev_t dev_id;        // デバイスのメジャー番号とマイナー番号保持用
static struct cdev c_dev;   // キャラクタ型デバイス構造体

static int my_gpio_open(struct inode *inode, struct file *file){
    printk("my_gpio_open\n");
    //printk(KERN_INFO "cdev_open[minor#%d]\n", MINOR(inode->i_rdev));
    return 0;
}
static int my_gpio_release(struct inode *inode, struct file *file){
    printk(KERN_INFO "cdev_release done\n");
    return 0;
}
static ssize_t my_gpio_read(struct file *file, char *buf, size_t count, loff_t *offset){
    printk("myDevice_read\n");
    buf[0] = 'A';
    return 1;
}
static ssize_t my_gpio_write(struct file *file, const char *buf, size_t count, loff_t *offset){
    printk("myDevice_write\n");
    return 1;
}

// file_operations構造体
static struct file_operations my_cdev_fops = {
    .owner     = THIS_MODULE,
    .read      = my_gpio_read,
    .write     = my_gpio_write,
    .open      = my_gpio_open,
    .release   = my_gpio_release,
    .unlocked_ioctl = NULL
};

//初期化ルーチン
static int init_my_gpio(void){
    int ret;
    printk(KERN_WARNING "ichiri*****init_my_gpio %d, %s\n",MINOR_COUNT, DRIVER_NAME);
    // キャラクタデバイスメジャー番号とマイナー番号の動的取得 
    ret = alloc_chrdev_region(&dev_id, 0, MINOR_COUNT, DRIVER_NAME);
    printk("ret =%d, dev_id=%x\n", ret, dev_id);
    if(ret!=0){
        printk(KERN_WARNING "init: alloc_chrdev_region failed. ret = %d\n", ret);
        return -1;
    }
    printk(KERN_WARNING "ichiri*****init_my_gpio before cdev_init\n");

    // キャラクタデバイス初期化
    cdev_init(&c_dev, &my_file_func);

    printk(KERN_WARNING "ichiri*****init_my_gpio before cdev_add\n");

    // キャラクタデバイスのKernelへの登録 
    ret = cdev_add(&c_dev, dev_id, MINOR_COUNT);
    if(ret != 0){
        printk(KERN_WARNING "init:cdev_add failed. ret = %d\n", ret);
        unregister_chrdev_region(dev_id, MINOR_COUNT);
        return -1;
    }
    printk("before return in init");
    return 0;
}
/* モジュール解放関数 */
static void exit_my_gpio(void)
{
    /* キャラクタデバイス削除 */
    cdev_del(&c_dev);

    /* デバイス番号の解放 */
    unregister_chrdev_region(dev_id, MINOR_COUNT);

    printk(KERN_WARNING "exited my_gpio");
}

module_init(init_my_gpio);
module_exit(exit_my_gpio);

MODULE_DESCRIPTION(DRIVER_NAME);
MODULE_LICENSE("Dual BSD/GPL");

モジュール、デバイス特殊ファイル作成

  1. 上記のコードをmakeでビルドして、
  2. インサート(insmod)して、lsmodで登録されたかどうか確認します。
  3. cat /proc/devices | grep DRIVER_NAME でメジャー番号を確かめます。 DRIVER_NAMEはコード内で記述したDRIVER_NAMEに置き換えてください。 上記コードの場合は、my_gpio_driverです。
~/test/drivers/
$ cd ~/test/drivers //test.cやMakefileと同じディレクトリ 
$ make clean        // 前回作成した内容を削除
$ make              // 今回のコードでビルド
$ sudo insmod test.ko
$ lsmod | grep test  // これで登録されたかどうか分かります
Module                  Size  Used by
test                  16384  0

$ cat /proc/devices | grep my_ // ここにファイル名のtestでなく、DRIVER_NAMEで表示されます。 10進数のメジャー番号も表示されます。
234 my_gpio_driver

$ dmesg | tail -20   // /var/log/syslogのKernelメッセージの最新20行表示。 ここでもprintkのdev_idの左の数字が16進数でメジャー番号。
[12557.529949] test: loading out-of-tree module taints kernel.
[12557.530032] test: module verification failed: signature and/or required key missing - tainting kernel
//Secure Bootモードでなければ、上記"failed"は動作上問題ない。 表示しないようにするにはsignatureと鍵をKernelに登録する必要がある。
[12557.530224] ichiri*****init_my_gpio 4, my_gpio_driver
[12557.530229] ret =0, dev_id=ea00000     //<--  eaがメジャー番号(234)。 00000がマイナー番号。
[12557.530230] ichiri*****init_my_gpio before cdev_init
[12557.530231] ichiri*****init_my_gpio before cdev_add

キャラクタ型デバイス特殊ファイル作成

  1. mknodで以下4点をしてしてキャラクタ型デバイス特殊ファイルを作成
    1. ファイル名:『/dev』の下にシステムや他のファイルとかぶらない、任意のファイル名を指定。 ここでは『MyDev0』としています。
    2. キャラクタ型なので『c』とします。 *DDR、SSD、SDなどブロック単位でアクセスしなければならないデバイスを除いたらほとんどはキャラクタ型です。
    3. 先程調べたメジャー番号を入れる。 私の場合は『234』でした。
    4. マイナー番号を入れる。 『0』から、20bit分使えると思いますが、分かりやすいように『0』でしてみます。
  2. アクセスできるようにchmodにPermissionを与えておきます。
  3. lsで/dev/MyDev0を確認してみます。 Permissionの先頭についている『c』がキャラクタ型を示しています。
$ sudo mknod /dev/MyDev0 c 234 0   // ユーザーが扱えるデバイス特殊ファイルを生成する
$ sudo chmod 666 /dev/MyDev0
$ ls -la /dev |grep MyDev          
crw-rw-rw-   1 root   root    234,   0  1月 11 11:10 MyDev0    //キャラクタ型ファイルが出来ています。

操作

  1. echoで、デバイス特殊ファイルの/dev/MyDev0に書き込んでみます。
  2. dmesgで見てみると、test.cのmy_gpio_write()内のprintkで書いた内容が出力されていますね。
  3. 今度はcatで読み出しです。 永遠に『A』が表示されるので、Ctrl+Cで止めてください。
  4. 停止後、dmesgで確認。 my_gpio_read()が呼ばれている事が分かります。
  5. test.koはKernel空間で実行されていて、dmesgで表示されるKernelメッセージを出力しています。 ユーザー空間のシェルコマンドのechoやcatを使って、デバイス特殊ファイル経由で、Kernel空間のtest.koアクセスしているのです。
$ echo "abc" > /dev/MyDev0  //"abc"を書き込んでwriteの確認
$ dmesg | tail -10
       :
[13779.132961] my_gpio_open
[13779.132986] myDevice_write
[13779.132988] myDevice_write
[13779.132989] myDevice_write
[13779.132990] myDevice_write
[13779.132994] cdev_release done
// myDevice_writeが4回呼ばれています。 "abc"の3文字と\nだと思います。  echo -n "abc" > とすると"\n"なしになります。

$ cat /dev/MyDev0    // readの確認。 "A"が無限に表示されるので、Ctrl+Cで止めます。
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA^C
$ dmesg | tail -30
       :
[13957.704105] myDevice_read     // ちゃんとreadが動作しています。 "A"の数だけreadが表示されます。
[13957.704108] myDevice_read
[13957.704111] myDevice_read
[13957.704114] myDevice_read
[13957.704117] myDevice_read
[13957.704120] myDevice_read
[13957.704123] myDevice_read
[13957.704126] myDevice_read
[13957.704129] myDevice_read
[13957.704132] myDevice_read
[13957.704135] myDevice_read
[13957.704137] myDevice_read
[13957.704140] myDevice_read
[13957.704147] myDevice_read
[13957.704151] myDevice_read
[13957.704154] myDevice_read
[13957.704160] myDevice_read
[13957.704165] myDevice_read
[13957.704402] cdev_release done

終了処理

終了は、2ステップです。

  1. rmでデバイス特殊ファイルの削除。
  2. そしてrmmodでtest.koのKernelへの登録を削除します。

『その2』はここまでです。 test.cコードの説明はこの下にあります。 『その3』以降で、コードでデバイス特殊ファイルの作成や削除が出来る用にしていきます。

$ sudo rm /dev/MyDev0    //  デバイス特殊ファイルは、ファイルなのでファイル削除の『rm』で削除します
$ sudo rmmod test        //  lsmodで登録されているモジュール名でKernelの登録を削除します

一度、/dev/MyDev0が作られたら、/dev/MyDev0がrmされるまで、rmmodすると、/dev/MyDev0は使えなくなりますが、insmodするとmknodせずに/dev/MyDev0を操作できます。

コードの説明

初期化、Kernelへの登録

  1. Kernelがドライバーモジュールをロード(isnmod)すると、linux/module.h内のマクロmodule_init()が呼び出され、module_init()の引数のinit_my_gpio関数が実行されます。
  2. init_my_gpio内でalloc_chrdev_region()関数でキャラクタ型デバイスドライバとしてのメジャー番号とマイナー番号を自動的にcdev_t型のdev_idに出力させます。
  3. この時点で、/proc/devicesに登録されます。
  4. cdev_init()で、デバイスの初期化をします。 ここで、このモジュールで実行するメソッドや値をfile_operations構造体として登録します。
  5. cdev_add()でKernelに登録します。 ここで、insmodできるようになります。
  6. このcdev_init処理でfile_operations構造体のメソッドも登録して、キャラクタ型デバイスの雛形を作ってcdev_addでKernelに登録しているので、ユーザーが操作できるデバイス特殊ファイルを作ることができます。 『その1』ではこの処理がないため、デバイス特殊ファイルを操作できないのです。

file_operations構造体

file_operations構造体はデバイスを操作するための決められたメソッドや値へのポインタを保持するものです。 それぞれのシステムコールされた時にイベントとしてそれぞれのメソッドを呼び出すします。 ioctlは標準のシステムコール以外の特殊システムコールの時使用する。 以下は抜粋です。

  • open — デバイスを扱う最初に実行するメソッド。 タイマー等はリソースの初期化も実行する。
  • read, read_iter — デバイスからデータを読み出すメソッド。 通常はcopy_to_userでKernelからユーザー領域にデータを転送する。 成功した時は読み出したバイト数を返す。
  • write, write_iter — デバイスへデータを書き込むメソッド。 通常はcopy_from_userでユーザー領域からKernelにデータを転送する。 成功した時は書き込んだバイト数を返す。
  • release — デバイスをクローズする時のメソッド。
  • owner — モジュールの所有者。THIS_MODULE で初期化するのが普通。モ ジュールが削除されることを防ぐ。
  • llseek — ファイル中の読み出し/書き込み位置(fops:ファイルポジション)を変更するメソッド。  例えば、100文字が書き込まれていて、10文字目から読み出したいときなど。 whence= SEEK_SET|SEEK_CUR|SEEK_END の3種類でファイルポジションの設定方法を決めます。
  • unlocked_ioctl — システム・コール ioctl() が呼ばれた時の処理を書くメソッド。 処理が不要ならNULLを書く。
  • flush — ファイルを閉じる時に呼び出されるメソッド。

ユーザー空間のシェルから、catでデバイス特殊ファイルにアクセスすると、内部のシステムコールのreadが呼ばれ、.read= my_gpio_readで登録したmy_gpio_read()メソッドが実行されます。 上記コードでは、read,write,open,releaseの4つのメソッドを登録しています。

// file_operations構造体
static struct file_operations my_file_func = {
    .owner     = THIS_MODULE,
    .read      = my_gpio_read,
    .write     = my_gpio_write,
    .open      = my_gpio_open,
    .release   = my_gpio_release,
    .unlocked_ioctl = NULL
};

終了処理

  1. rmmodが実行されたら、module_exit(exit_my_gpio);が呼び出され、引数のexit_my_gpio()が実行される。
  2. cdev_add()の反対でcdev_del(&c_dev);を実行し、キャラクタデバイスを削除する。
  3. そして、unregister_chrdev_region(dev_id, MINOR_COUNT);でデバイスメジャー番号とマイナー番号を削除する。 これがないと、どんどんとメジャー番号を登録し空き番号がなくなるので注意。
  4. (備考)これらをしても作成されたデバイス特殊ファイルはrmしないと空ファイルとして残る。
/* キャラクタデバイス削除 */
cdev_del(&c_dev);

/* デバイス番号の解放 */
unregister_chrdev_region(dev_id, MINOR_COUNT);

(参考)

ドライバーの組込み方法 insmod

#のrootユーザーでなく$の一般ユーザーであればsudoで実行しなければならないと思います。

// Dynamicモジュールとしてコンパイル後
// これで組込みmodule_init()が実行される
$ insmod ~/test/drivers/test.ko   // <--- .koファイルが存在するディレクトリで実行するか、パスを指定して実行。

// 組み込まれたか確認 (組み込まれたモジュール一覧表示)
$ lsmod | grep test

// 組み込まれたモジュールを削除
$ rmmod test     // <----.ko ありでも無しでもok。 ディレクトリ指定は不要。

// 組み込まれたモジュールの上表表示
$ modinfo test   // <---モジュール名だけで.koは不要

//デバイスドライバメジャー番号確認
$ cat /proc/devices

// インサートされたら、次に、アプリケーションからアクセスするためのデバイススペシャルファイルを作成
// cat /proc/devicesで調べたメジャー番号を使用するが、下の例は
// mknod デバイス名 タイプ(キャラクタ型) メジャー番号
$ mknod --mode=666 /dev/my_gpio0 c 10 'grep my_gpio_driver /proc/devices | awk '{print $1;}'` 0
$ mknod --mode=666 /dev/my_gpio1 c 10 'grep my_gpio_driver /proc/devices | awk '{print $1;}'` 1

grep my_gpio_driver /proc/devices と cat /proc/devices | grep my_gpio_driver は同じ。

insmodとmodprobeの違い

insmodは、指定したモジュールのみKernelにインサート(ロード)する。 もしそのファイルが別のモジュールに依存する場合は、そのモジュールも個別にインサートしなければならない。

modprobeは、指定したモジュールが依存するモジュールも自動的にKernelにインサートする。(.koは不要) しかし、modprobeで適切に動作させるには、2つの条件がある。

  1. 扱うモジュールを全て規定のディレクトリに配置する。 通常は、/lib/modules/<KernelVersion>の下のディレクトリ
  2. それらのモジュールがmodules.depとバイナリ化されたmodule.dep.binに登録されていなければならない。 登録には『depmod』を使用する。

modprobeでロードされたモジュールのアンロード

$ modprobe -r g_ether

MODULE_LICENSE(“”)

以下を使用できる。

  • GPL種類
  • GPL v2
  • GPL and additional rights
  • Dual BSD/GPL
  • Dual MIT/GPL
  • Dual MPL/GPL
  • Proprietary # default
MODULE_LICENSE("Dual MIT/GPL")

printk Kernelメッセージ種類

Consoleログレベルより大きなメッセージレベル(msg lv)のメッセージはdmesgを使わないとTerminalに直接表示されません。uBuntuの場合、Consoleログレベルの初期値は4なので、KERN_INFOとしているdmesgを使わないと表示されません。

msg lv定数MACRO説明
1KERN_EMERGpr_emerg緊急メッセージ.通常クラッシュを起こすような状態を表す.
2KERN_ALERTpr_alertすぐに対応が必要な状況を表す.
3KERN_CRITpr_crit危機的状況を表す.
4KERN_ERRpr_errエラー状況(例:ハードウェアのトラブル)を通知する.
5KERN_WARNINGpr_warn疑わしい状況についてのワーニング.
6KERN_NOTICEpr_notice状況としては正常だが,注意が必要な状態.
7KERN_INFOpr_info情報メッセージ.設定時に検出したハードウェアに関する情報などを表示する.
8KERN_DEBUGpr_develデバッグ用のメッセージ。 ”DEBUG”が0の時はprintk出力しない。
cKERN_CONTpr_contログの継続(タイムスタンプの更新を回避。ブート中専用)
dKERN_DEFAULTデフォルトのログレベル

consoleログレベル確認・変更

cat /proc/sys/kernel/printk で確認出来ます。 右から、

  1. console log level — 現在のレベル
  2. default log level —printkで指定がなかった時のレベル
  3. minimum console log level — 設定できる最小のレベル
  4. default console log level —初期値のConsole ログレベル
$ cat /proc/sys/kernel/printk   //ログレベルの確認。 一番右に数字がログレベル。 
4	4	1	7
 
(注釈)数値は左から順に、
console_loglevel、default_message_level、minimum_console_loglevel、 default_console_loglevel

/* rootユーザーのみ変更可能 */
# echo "7 2 1 7" > /proc/sys/kernel/printk
$ cat /proc/sys/kernel/printk
7	2	1	7

Kernelへ割込ハンドラ登録

(ここでは使っていませんが今後の参考の為のメモ)

デバイスの動作完了をKernelに通知する為に使用される割込ハンドラの登録が必要です。 Kernelがこの割込を受けてデバイスドライバのトップハーフルーチンを起動します。

int request_irq(
    IRQ番号,
    割込ハンドラへのポインタ,
    フラグ(SA_INTERRUPT),
    デバイス名
);

各種構造体、関数定義

いろいろ沢山出てきてわからなくなるので、よく使う構造体や関数の定義を書き留めています。 完全ではないので逐次修正・追記していきます。

u32 dev_t --- types.hで定義された、メジャー番号とマイナー番号を保持する。 上位12bitがメジャー番号で界20bitがマイナー番号。

i-node構造体 --- Linuxではファイルはi-nodeという多くの管理情報があり、デバイスファイルならi-node内にメジャー番号とマイナー番号が存在し、メジャー番号からデバイスドライバを特定する。

loff_t --- long offsetデバイス

struct cdev {    //inode構造体の一要素でキャラクタデバイス。 dev_t型も含まれる。
    struct kobject kobj; 
    struct module *owner; 
    const struct file_operations *ops; 
    struct list_head list; 
    dev_t dev; 
    unsigned int count; 
};

struct file { //以下以外にも項目はあるが、Device Driverで使用するのは以下のみ
    mode_t f_mode;
    loff_t f_pos;
    unsigned init f_flags;
    struct file_operations *f_op;  // file operations but not saved for later refgerence.
    void *private_data;        // Device Driverが使用出来るエリア。 本来はreleaseでこの領域を開放しなければならない。
    struct dentry *f_dentry;   // directory entry. Device Driverでは、filp->f_dentry->d-inodeにアクセスするときしか使わない。
}

int alloc_chrdev_region ( // #include <linux/proc_fs.h>
    dev_t * dev,          //(出力)メジャー番号とマイナー番号が割り当てられて出力される
    unsigned baseminor,   //(入力)マイナー番号の開始番号
    unsigned count,       //(入力)マイナー番号の最大数
    const char * name);   //(入力)デバイスやドライバ名

void unregister_chrdev_region (	// #include <linux/proc_fs.h>
    dev_t from,           //(入力)削除する開始番号
    unsigned count);      //(入力)マイナー番号の最大数

int register_chrdev(      //メジャー番号を指定してキャラクタ型デバイスを登録
     unsigned int major,  //(入力)メジャー番号指定。0の時は自動割り当て
     const char *name,    //(入力)変数渡しの場合はポインタ。或いは直接""で記述。
     struct file_operations *ops  //(入力)
);

void cdev_init (                          // 
    struct cdev * cdev,                   // (入力)ここで指定したデバイスを初期化する
    const struct file_operations * fops); //(入力)このデバイスを制御するfile_operations構造体を指定する

int cdev_add (          // 登録失敗したら、負のエラーコードを返す
    struct cdev * p,    //(入力)ここで指定したデバイスがsystemに登録される 
    dev_t dev,          //(入力)最初のデバイス番号(メジャー番号&マイナー番号)
    unsigned count);    //(入力)このデバイスでのマイナー番号の数

void cdev_del (
    struct cdev * p);   //(入力)ここで指定したデバイスがsystemから削除される

struct cdev * cdev_alloc (void);  // cdev構造体を返す。 失敗時はNULLを返す。

my_class = class_create(
    THIS_MODULE,          //(入力)
    "my_driver_class");   //(入力)この名前で /sys/class/に表示される

struct device *device_create(  // デバイスノードを作る拡張機能、Kernelリソースにアクセスできるsysfs属性も作成する。 
        struct class *class,   //(入力)class_create()で出力した変数を入力
        struct device *parent, //(入力)この新しいデバイスの親 structデバイスのポインタ
        dev_t devt,            //(入力)この新しいデバイスのdev_t構造でのメジャー番号とマイナー番号 
        void *drvdata,         //(入力)この新しいデバイスのcallbackに渡すデータ
        const char *fmt,       //(入力)文字列:デバイス名  "my_dev%d", i  /dev/my_dev0, /dev/my_dev1 ... が作られる
        ...
)

void device_destroy(
    struct class *class,   //(入力)my_class
    dev_t devt);           //(入力)dev_id, major & minor numbers

void class_destroy(
    struct class *class);  //(入力)my_class

ssize_t my_cdev_read(
        struct file *filp,
        chr __user *buff,     //(入力) __userは、オプションでこのユーザーbuffのポインタはユーザーレベルで信用出来ないことを知らせている。
        size_t count,         // (入力)ユーザーによって与えられた読み出しカウント
        loff_t *f_pos         // (入力)readが始める現在のファイル位置のポインター
)

unsigned long copy_to_user(   // 成功0,コピー出来なかったバイト数を返す。 0でなかったら-EFAULTを返す事。
    void __user *to,          //(入力)ユーザー空間の送り先アドレス
    const void *from,         //(入力)Kernel空間の元アドレス
    unsigned long n);         //(入力)コピーするバイト数

unsigned long copy_from_user(   // 成功0,コピー出来なかったバイト数を返す。 0でなかったら-EFAULTを返す事。
    void *to,                   //(入力)Kernel空間の送り先アドレス
    const void __user *from,    //(入力)ユーザー空間の元アドレス
    unsigned long n);           //(入力)コピーするバイト数


struct file_operations {  // include/linux/fs.h
         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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
         ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
         int (*readdir) (struct file *, void *, filldir_t);
         unsigned int (*poll) (struct file *, struct poll_table_struct *);
         int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
         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 *);
         int (*open) (struct inode *, struct file *);
         int (*flush) (struct file *, fl_owner_t id);
         int (*release) (struct inode *, struct file *);
         int (*fsync) (struct file *, struct dentry *, int datasync);
         int (*aio_fsync) (struct kiocb *, 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 **);
};

insmod: ERROR: could not insert module test.ko: Operation not permitted

if文に{ }がなかったため、return -1で抜けてしまっていた為、モジュールとして登録されなかった。 この-1が Operation not permittedになったのかもしれない。(エラーコード一覧を探したけど見つからなかった)

    ret = alloc_chrdev_region(&dev_id, 0, MINOR_COUNT, DRIVER_NAME);
    printk("ret =%d, dev_id=%x\n", ret, dev_id);
    if(ret!=0)
        printk(KERN_WARNING "init: alloc_chrdev_region failed. ret = %d\n", ret);
        return -1;
    printk(KERN_WARNING "ichiri*****init_my_gpio before cdev_init\n");

LinuxがSecure Boot Enableで立ち上がっている場合方法で解決するらしいのですが、私の場合は解決しませんでした。 dmesg | grep Secure で検索したら、表示されなかったので、Secure Bootでないのでしょう。

//rootユーザーで以下のようにlockdownをdisableにしてもinsmodできなかった。 Operation not permitted
# echo 1 > /proc/sys/kernel/sysrq
# echo x > /proc/sysrq-trigger

linux/proc_fs.h not found

makeでモジュールを生成する時、本来headerの、/usr/src/$(shell uname -r)/include/linuxを見に行かないと行けないとこが、スタンダードCライブラリの/usr/include/linuxを見に行っているみたいです。 そして、確かにここにはproc_fs.hは存在しませんでした。 

/usr/src/$(shell uname -r)/include/linux は、 /lib/module/$(shell uname -r)/buildのシンボリックリンクなので、Makefileを以下のようにしたら何故かエラーが出なくなった。 Makefileが壊れていたのだと思います。

Makefile
obj-m := test1.o

all:TARGET1
TARGET1:test.c
        make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

cat /proc/devicesに沢山登録された時の対策

/proc/devicesは、単にメジャー番号が使用されているかどうかの管理用で、実際のモジュールになっていない。 私の場合、デバッグ中にinsmod失敗を何度か繰り返すと、モジュールとしてはlsmodで見れるようにインストールされなくても、alloc_chrdev_region()を実行した回数だけ/proc/devicesに登録されてしまう。 この下のプログラムを実行するかリブートすれば消せる。

$ cat /proc/devices
Character devices:
    :
    :
504 my_gpio_driver
505 my_gpio_driver
506 my_gpio_driver
507 my_gpio_driver
508 my_gpio_driver
509 my_gpio_driver
510 my_gpio_driver
511 my_gpio_driver

以下のコマンドで、メジャー番号をしていして、マイナー番号は0からとして、マイナー番号の個数の最大値を指定して実行すれば、/proc/devicesから消えました。 しかし、全て消えても、rmmod:ERROR:Module test2 is in useは消えず、rmmodできない。

#include <linux/proc_fs.h>
#    :
unregister_chrdev_region(MKDEV(234,0),4);

//普通のメッセージがmake.textに入っただけ
make 2>&1|tee make.txt
//rootユーザーで以下のようにlockdownをdisableにしてもinsmodできなかった。 Operation not permitted
# echo 1 > /proc/sys/kernel/sysrq
# echo x > /proc/sysrq-trigger

リンク

ここの説明も参考にさせてもらいました。

Linux cdev vs register_chrdev
I am reworking a driver and came across the cdev interface in LDD3. After reading I am more confused than enlightened. F...
KERNELSRCDIR = /lib/modules/$(shell uname -r)/build
ARCH=x86KERN_ERR  
#ARCH=arm
#CROSS_COMPILE=arm-none-linux-gnueabi-
VERBOSE = 0

obj-m := testmod.o

all:
    make -C $(KERNELSRCDIR) \
        M=$(PWD) \
        KBUILD_VERBOSE=$(VERBOSE) \
        ARCH=$(ARCH) \
        CROSS_COMPILE=$(CROSS_COMPILE) \
        modules

clean:
    make -C $(KERNELSRCDIR) M=$(PWD) KBUILD_VERBOSE=$(VERBOSE) ARCH=$(ARCH) clean

コメント