キャラクタデバイスドライバを書く
概要
ラズパイのGPIOを対象としたデバイスドライバを作成した。
作成
システムコール対応の関数テーブル作成
今回は以下のような関数を実装した。
struct file_operations my_file_ops = { .owner = THIS_MODULE, .open = my_open, .release = my_close, .read = my_read, .write = my_write, .unlocked_ioctl = my_ioctl, /* 64 bits */ .compat_ioctl = my_ioctl, /* 32 bits */ };
open
及びrelease
で物理アドレスと仮想アドレスのマッピングを行い、ioctl
で操作対象のピンの変更、そして実際の読み書きはread
及びwrite
で行う。
file_operations
は実装できるシステムコールに対応した処理を保持する構造体で以下のように定義されている。
/* * NOTE: * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl * can be called without the big kernel lock held in all filesystems. */ 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 (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, 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 *); 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 (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); 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 (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); };
メジャー番号とマイナー番号の取得
以下ではalloc_chrdev_region
関数でメジャー番号の取得を行い、dev_t
型の変数からMAJOR
マクロを用いてメジャー番号を取得している。
dev_t dev; alloc_ret = alloc_chrdev_region(&dev, MINOR_NUMBER_START, NUMBER_MINOR_NUMBER, DRIVER_NAME); if (alloc_ret != 0) { printk(KERN_ERR "failed to alloc_chrdev_region()\n"); return -1; } major_number = MAJOR(dev);
dev_t
は以下のように定義されておりunsigned int
であることがわかる。そしてその変数から決められた上位12ビットをメジャー番号としている。
typedef __u32 __kernel_dev_t; typedef __kernel_dev_t dev_t; // include/linux/kdev_t.h #define MINORBITS 20 #define MINORMASK ((1U << MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
alloc_chrdev_region()
は以下のように定義されており__register_chrdev_region
の戻り値からメジャー番号とマイナー番号を設定している。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) { struct char_device_struct *cd; cd = __register_chrdev_region(0, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); *dev = MKDEV(cd->major, cd->baseminor); return 0; }
キャラクタデバイス構造体の初期化
/* initialize cdev and function table */ cdev_init(&my_char_dev, &my_file_ops); my_char_dev.owner = THIS_MODULE;
cdev_init()
は以下のように定義されておりキャラクタデバイス構造体のopsメンバに引数であるシステムコールハンドラ関数のテーブルを設定している。
void cdev_init(struct cdev *cdev, struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); cdev->kobj.ktype = &ktype_cdev_default; kobject_init(&cdev->kobj); cdev->ops = fops; }
cdev
は以下のように定義されており、先ほど作成したシステムコールテーブルを保持するメンバが存在する。
struct cdev { struct kobject kobj; struct module *owner; struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
ドライバの登録
デバイスドライバの登録はcdev_add()
で行う。キャラクタデバイス構造体やデバイス番号を保持している変数、当該デバイスに割り当てるマイナー番号の数などの設定を行う。
cdev_err = cdev_add(&my_char_dev, dev, NUMBER_MINOR_NUMBER); if (cdev_err != 0) { printk(KERN_ERR "failed to cdev_add()\n"); unregister_chrdev_region(dev, NUMBER_MINOR_NUMBER); return -1; }
デバイスのクラス登録
class_create()
でクラス登録を行う。当該処理で/sys/class/my_device/
が作成される。
my_char_dev_class = class_create(THIS_MODULE, DEVICE_NAME); if (IS_ERR(my_char_dev_class)) { printk(KERN_ERR "class_create()\n"); cdev_del(&my_char_dev); unregister_chrdev_region(dev, NUMBER_MINOR_NUMBER); return -1; }
デバイスファイルの作成
device_create()
で実際に指定のメジャー番号及びマイナー番号のデバイスを登録する。(ex. sys/class/my_device/my_device
)
device_create(my_char_dev_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
実装に関して
- module_init() & module_exit()
- PAGE_SIZE
- ioremap_nocache()
- get_user() & put_user();
module_init() & module_exit()
モジュールのロード及びアンロード時に呼び出される関数を登録。
先ほどの説明した一連の処理はmodule_init
の引数となっている関数内で行う。
module_init(my_init); module_exit(my_exit);
PAGE_SIZE
カーネルが仮想アドレス空間においてメモリをチャンクとして扱う際のサイズ。
// include/asm-i386/page.h #define PAGE_SHIFT 12 #define PAGE_SIZE (1UL << PAGE_SHIFT)
ioremap_nocache()
base_address = (int)ioremap_nocache( GPIO_ADDRESS, PAGE_SIZE); return 0;
get_user() & put_user()
ユーザ空間<->カーネル空間でデータのコピーを行う。
static ssize_t my_write(struct file* file, const char __user* buff, size_t count, loff_t *pos) { int mode; get_user(mode, &buff[0]); : (省略) :
static ssize_t my_read(struct file* file, char __user* buff, size_t count, loff_t *pos) { int num_reg = (pin_number / NUM_PIN_EACH_REG); int offset = (pin_number % NUM_PIN_EACH_REG); int addr = base_address + GPLEV0_OFFSET + (REG_GAP * num_reg); unsigned int reg_value = MEMORY(addr); int value = ((reg_value >> offset) & 1UL); put_user(value + '0', &buff[0]); return count; }
エコシステム
- udev
- uevent
- *.rules
- /lib/modules
udev
Linuxカーネル用のデバイス管理ツール。/dev
以下の管理やホットスワップファームウェアのロードなどを行う。Linux上ではデーモンとして動作する。
新たにデバイスが接続された際や解除された際にはnetlink
(カーネルモジュール及びユーザ空間のプロセスの間で行われる情報のやりとりに用いられる)ソケット経由でuevents
をudev
に送信する。udev
は/etc/udev/rules.d/*.rules
を参照しルールにマッチしたデバイスノードを作成する。
uevent
sudo udevadm monitor --env
を実行してecho
なんかで書き込むとudev
のメッセージを見ることができる(できなかった・・・)
ex.
$ sudo ls -l /sys/class/input/mice/uevent -rw-r--r-- 1 root root 4096 Nov 29 22:26 /sys/class/input/mice/uevent
*.rules
udev
がデバイスを認識してデバイスファイルを作成するために用いられるファイル。
$ sudo ls -l /etc/udev/rules.d/ total 0
中身は以下のようなフォーマットになっている
KERNEL=="nvram", GROUP="adm", MODE="0660"
/lib/modules
insmod
を使用せずとも以下のような箇所にドライバモジュールを配置しておくことでデバイス検出時にモジュールがロードされる。
$ sudo ls -l /lib/modules/4.4.0-116-generic/kernel/drivers/gpio/ | more total 560 -rw-r--r-- 1 root root 10622 Feb 13 2018 gpio-104-idio-16.ko -rw-r--r-- 1 root root 8966 Feb 13 2018 gpio-adp5520.ko -rw-r--r-- 1 root root 11254 Feb 13 2018 gpio-adp5588.ko -rw-r--r-- 1 root root 13646 Feb 13 2018 gpio-amd8111.ko -rw-r--r-- 1 root root 13662 Feb 13 2018 gpio-amdpt.ko -rw-r--r-- 1 root root 8774 Feb 13 2018 gpio-arizona.ko -rw-r--r-- 1 root root 13486 Feb 13 2018 gpio-crystalcove.ko -rw-r--r-- 1 root root 10158 Feb 13 2018 gpio-da9052.ko -rw-r--r-- 1 root root 9158 Feb 13 2018 gpio-da9055.ko -rw-r--r-- 1 root root 14854 Feb 13 2018 gpio-dln2.ko -rw-r--r-- 1 root root 19214 Feb 13 2018 gpio-dwapb.ko -rw-r--r-- 1 root root 24702 Feb 13 2018 gpio-f7188x.ko -rw-r--r-- 1 root root 15550 Feb 13 2018 gpio-generic.ko -rw-r--r-- 1 root root 15854 Feb 13 2018 gpio-ich.ko -rw-r--r-- 1 root root 11406 Feb 13 2018 gpio-it87.ko -rw-r--r-- 1 root root 9214 Feb 13 2018 gpio-janz-ttl.ko -rw-r--r-- 1 root root 9998 Feb 13 2018 gpio-kempld.ko -rw-r--r-- 1 root root 9294 Feb 13 2018 gpio-lp3943.ko -rw-r--r-- 1 root root 7478 Feb 13 2018 gpio-max7300.ko -rw-r--r-- 1 root root 7990 Feb 13 2018 gpio-max7301.ko -rw-r--r-- 1 root root 8294 Feb 13 2018 gpio-max730x.ko -rw-r--r-- 1 root root 13926 Feb 13 2018 gpio-max732x.ko -rw-r--r-- 1 root root 8942 Feb 13 2018 gpio-mc33880.ko -rw-r--r-- 1 root root 24566 Feb 13 2018 gpio-mcp23s08.ko -rw-r--r-- 1 root root 20630 Feb 13 2018 gpio-ml-ioh.ko -rw-r--r-- 1 root root 22366 Feb 13 2018 gpio-pca953x.ko -rw-r--r-- 1 root root 17542 Feb 13 2018 gpio-pcf857x.ko -rw-r--r-- 1 root root 10246 Feb 13 2018 gpio-rdc321x.ko -rw-r--r-- 1 root root 13230 Feb 13 2018 gpio-sch311x.ko -rw-r--r-- 1 root root 9446 Feb 13 2018 gpio-sch.ko -rw-r--r-- 1 root root 8526 Feb 13 2018 gpio-tps65912.ko -rw-r--r-- 1 root root 15550 Feb 13 2018 gpio-twl4030.ko -rw-r--r-- 1 root root 8014 Feb 13 2018 gpio-twl6040.ko -rw-r--r-- 1 root root 7982 Feb 13 2018 gpio-ucb1400.ko -rw-r--r-- 1 root root 12870 Feb 13 2018 gpio-viperboard.ko -rw-r--r-- 1 root root 11390 Feb 13 2018 gpio-vx855.ko -rw-r--r-- 1 root root 11414 Feb 13 2018 gpio-wm831x.ko -rw-r--r-- 1 root root 8782 Feb 13 2018 gpio-wm8350.ko -rw-r--r-- 1 root root 11998 Feb 13 2018 gpio-wm8994.ko
実装
https://github.com/k-onishi/gpio-driver
資料
このエントリは以下の勉強会に参加した際に発表したものと同様の内容である。
www.slideshare.net