Raspberry Pi3上でキャタクタ型デバイスドライバを作る.
以下の文献を参考にした.特に3件目のコードをいじって動かした.
- Linuxデバイスドライバプログラミング
- 組み込みLinuxデバイスドライバの作り方 (1) - Qiita
- http://d.hatena.ne.jp/mmitou/20120707/1341681213
- http://www.hakodate-ct.ac.jp/~tokai/tokai/research/kmod.html
ロード・アンロードだけできるカーネルモジュール開発
カーネルヘッダ等をインストールする.
sudo apt-get install raspberrypi-kernel-headers
ロード・アンロードだけできるカーネルモジュール
#include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_LICENSE("Yusuke Ujitoko"); static int char_bufsize = 1; static int test_init(void) { printk(KERN_ALERT "driver loaded\n"); return 0; } static void test_exit(void) { printk(KERN_ALERT "driver unloaded\n"); return; } module_param(char_bufsize, int, S_IRUGO | S_IWUSR); module_init(test_init); module_exit(test_exit);
- module_initマクロに渡した関数がロード時に呼ばれる
- module_exitマクロに渡した関数がアンロード時に呼ばれる
makefileをかく。
obj-m := test.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) V=1 modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
ビルドしたカーネルモジュールをinsmod
や rmmod
でロード・アンロードできる。
その上でdmesgなどでカーネルバッファを確認できる。
メジャー番号とマイナー番号の動的な登録
カーネルからドライバを識別するためのメジャー番号や、 ドライバからデバイスを識別するためのマイナー番号を登録する。 静的に行う方法もあるが、動的に行うのが一般的。
メジャー番号の動的確保はalloc_chrdev_region()
で行う。
またメジャー番号の削除は unregister_chrdev_region()
で行う。
これらの処理を加えたコードを次に示す。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/kdev_t.h> #define DEVICE_NAME "char_dev" #define MINOR_BASE 0 #define MINOR_NUM 2 MODULE_LICENSE("Dual BSD/GPL"); MODULE_LICENSE("Yusuke Ujitoko"); static int char_bufsize = 1; static int char_major = 0; static int char_minor = 0; static int test_setup_device_number(void) { printk(KERN_ALERT "setup device number dynamically\n"); int alloc_ret = 0; int cdev_err = 0; dev_t dev; /* alloc free major number */ alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DEVICE_NAME); if (alloc_ret != 0){ printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret); return -1; } char_major = MAJOR(dev); char_minor = MINOR(dev); printk("major num: %d\n", char_major); printk("minor num: %d\n", char_minor); return 0; } static int test_clear_device_number(void) { dev_t dev = MKDEV(char_major, MINOR_BASE); unregister_chrdev_region(dev, MINOR_NUM); return 0; } static int test_init(void) { printk(KERN_ALERT "driver loaded\n"); test_setup_device_number(); return 0; } static void test_exit(void) { printk(KERN_ALERT "driver unloaded\n"); test_clear_device_number(); return; } module_param(char_bufsize, int, S_IRUGO | S_IWUSR); module_init(test_init); module_exit(test_exit);
カーネルモジュールをカーネルへ登録する
cdev_init()でシステムコードハンドラを登録し、 cdev_addでカーネルへドライバ登録する。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #define DEVICE_NAME "char_dev" #define MINOR_BASE 0 #define MINOR_NUM 2 MODULE_LICENSE("Dual BSD/GPL"); MODULE_LICENSE("Yusuke Ujitoko"); static int char_bufsize = 1; static int char_major = 0; static int char_minor = 0; static dev_t dev; static struct cdev my_device_cdev; struct file_operations my_device_fops = { }; static int test_setup_device_number(void) { printk(KERN_ALERT "setup device number dynamically\n"); int alloc_ret = 0; /* alloc free major number */ alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DEVICE_NAME); if (alloc_ret != 0){ printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret); return -1; } char_major = MAJOR(dev); char_minor = MINOR(dev); printk("major num: %d\n", char_major); printk("minor num: %d\n", char_minor); return 0; } static int test_setup_cdev(void) { int cdev_err = 0; cdev_init(&my_device_cdev, &my_device_fops); my_device_cdev.owner = THIS_MODULE; cdev_err = cdev_add(&my_device_cdev, dev, MINOR_NUM); if (cdev_err != 0){ printk(KERN_ERR "cdev_add = %d\n", cdev_err); unregister_chrdev_region(dev, MINOR_NUM); } } static int test_clear_device_number(void) { dev_t dev = MKDEV(char_major, MINOR_BASE); unregister_chrdev_region(dev, MINOR_NUM); return 0; } static int test_clear_cdev(void) { cdev_del(&my_device_cdev); return 0; } static int test_init(void) { int result; printk(KERN_ALERT "driver loaded\n"); result = test_setup_device_number(); if (result != 0){ return -1; } result = test_setup_cdev(); if (result != 0){ return -1; } return 0; } static void test_exit(void) { printk(KERN_ALERT "driver unloaded\n"); test_clear_cdev(); test_clear_device_number(); return; } module_param(char_bufsize, int, S_IRUGO | S_IWUSR); module_init(test_init); module_exit(test_exit);
open/close/read/writeハンドラを登録する
ユーザプロセスがデバイスファイに対してopen()やclose()などのシステムコールを発行できるようにするには、 ドライバがそれらに対応したハンドラをカーネルへ事前に登録しておく必要がある。 file_operations構造体でドライバのハンドラを設定する。
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #define DEVICE_NAME "char_dev" #define MINOR_BASE 0 #define MINOR_NUM 2 MODULE_LICENSE("Dual BSD/GPL"); MODULE_LICENSE("Yusuke Ujitoko"); static int char_bufsize = 1; static int char_major = 0; static int char_minor = 0; static dev_t dev; static struct cdev my_device_cdev; static int dev_open(struct inode *inode, struct file *file) { printk("device open\n"); return 0; } static int dev_close(struct inode *inode, struct file * file) { printk("device close\n"); } static ssize_t dev_read(struct file *filp, char __user * buf, size_t count, loff_t *f_pos) { printk("device read\n"); buf[0] = 'A'; return 1; } static ssize_t dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { printk("device write\n"); return 1; } struct file_operations my_device_fops = { .open = dev_open, .release = dev_close, .read = dev_read, .write = dev_write, }; static int test_setup_device_number(void) { printk(KERN_ALERT "setup device number dynamically\n"); int alloc_ret = 0; /* alloc free major number */ alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DEVICE_NAME); if (alloc_ret != 0){ printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret); return -1; } char_major = MAJOR(dev); char_minor = MINOR(dev); printk("major num: %d\n", char_major); printk("minor num: %d\n", char_minor); return 0; } static int test_setup_cdev(void) { int cdev_err = 0; cdev_init(&my_device_cdev, &my_device_fops); my_device_cdev.owner = THIS_MODULE; cdev_err = cdev_add(&my_device_cdev, dev, MINOR_NUM); if (cdev_err != 0){ printk(KERN_ERR "cdev_add = %d\n", cdev_err); unregister_chrdev_region(dev, MINOR_NUM); } } static int test_clear_device_number(void) { dev_t dev = MKDEV(char_major, MINOR_BASE); unregister_chrdev_region(dev, MINOR_NUM); return 0; } static int test_clear_cdev(void) { cdev_del(&my_device_cdev); return 0; } static int test_init(void) { int result; printk(KERN_ALERT "driver loaded\n"); result = test_setup_device_number(); if (result != 0){ return -1; } result = test_setup_cdev(); if (result != 0){ return -1; } return 0; } static void test_exit(void) { printk(KERN_ALERT "driver unloaded\n"); test_clear_cdev(); test_clear_device_number(); return; } module_param(char_bufsize, int, S_IRUGO | S_IWUSR); module_init(test_init); module_exit(test_exit);