Raspberry Pi3上でキャラクタ型デバイスドライバを作る

Raspberry Pi3上でキャタクタ型デバイスドライバを作る.
以下の文献を参考にした.特に3件目のコードをいじって動かした.

ロード・アンロードだけできるカーネルモジュール開発

カーネルヘッダ等をインストールする.

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

ビルドしたカーネルモジュールをinsmodrmmod でロード・アンロードできる。 その上で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);