This is a HowTo article for developing embedded Linux device drivers as kernel modules. All the content of this article can be run on a Raspberry Pi.
-1st time: Build environment preparation and simple kernel module creation -Second time: System call handler and driver registration (static method) --__ Third time: System call handler and driver registration (dynamic method) <------------ --------- Contents of this time __ -4th: Read / write implementation and memory story -5th time: Implementation of GPIO driver for Raspberry Pi -6th time: Implementation of ioctl -7th time: Interface for procfs -8th time: Interface for debugfs -9th time: Call a function of another kernel module / Use GPIO control function -10th time: Create a device driver using I2C -11th time: Add I2C device to device tree -12th time: Load the created device driver at startup
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_02
Last time, I tried the method of setting the major number of the device statically and registering it in the kernel. However, this method does not seem to be recommended at this time. This time, we will show you how to set this dynamically. Also, use the udev mechanism to automatically create a device file.
--It seems that you need to show the license of the kernel module. Without it, you will get a warning at build time. Following the book, set the following licenses.
- MODULE_LICENSE("Dual BSD/GPL");
-(It has a dual license with BSD. Since it is Linux, I wonder if it will be forced to be all GPL? But is it okay to use BSD because it is a kernel module? I'm not sure.)
――I personally like the lower camel case as a way to attach variables and functions. In the case of C, the module name + _ is added at the beginning. However, looking at the source code of the Linux kernel, the snake case seems to be the mainstream, so I will match it.
--By default, the C version was C89, but it's so inconvenient that I modified the Makefile to use C99. From now on, use the following Makefile.
CFILES = myDeviceDriver.c
obj-m := MyDeviceModule.o
MyDeviceModule-objs := $(CFILES:.c=.o)
ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
all:
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
System call handler functions such as open / close / read / write are the same as last time. The important thing is the registration process inside the mydevice_init
function.
-(1) Dynamically get the free measure number by the ʻalloc_chrdev_regionfunction. At that time, also set the information about the minor number used by this device driver. This time I tried to use minor numbers 0 and 1. That is, assume that two devices can be connected. -(3) Initialize the cdev object with the
cdev_initfunction. Specifically, register the system call handler (open / close / read / write) table. -(4) Use the
cdev_add` function to register the cdev object initialized in 3. in the kernel.
myDeviceDriver.c
#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/device.h>
#include <asm/current.h>
#include <asm/uaccess.h>
MODULE_LICENSE("Dual BSD/GPL");
/* /proc/Device name displayed on devices etc.*/
#define DRIVER_NAME "MyDevice"
/*Starting number and number of minor numbers used in this device driver(=Number of devices) */
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM = 2; /*Minor number is 0~ 1 */
/*Major number of this device driver(Dynamically decide) */
static unsigned int mydevice_major;
/*Character device object*/
static struct cdev mydevice_cdev;
/*Function called at open*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
return 0;
}
/*Function called at close*/
static int mydevice_close(struct inode *inode, struct file *file)
{
printk("mydevice_close");
return 0;
}
/*Function called when reading*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read");
buf[0] = 'A';
return 1;
}
/*Function called when writing*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_write");
return 1;
}
/*Handler table for various system calls*/
struct file_operations s_mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
};
/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Secure a free major number*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/* 2.Obtained dev( =Major number+Minor number)Get the major number from and keep it*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Unnecessary? */
/* 3.Initialization of cdev structure and registration of system call handler table*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.This device driver(cdev)In the kernel*/
cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", cdev_err);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 5.This device driver(cdev)From the kernel*/
cdev_del(&mydevice_cdev);
/* 6.Remove the major number registration used in this device driver*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Build and load are the same as last time. Regarding the creation of a device file, since the major number is determined dynamically, it is troublesome to check each one, so I will summarize it with the following command. By the way, in the Raspberry Pi environment, the major number is 242.
make
sudo insmod MyDeviceModule.ko
sudo mknod --mode=666 /dev/mydevice0 c `grep MyDevice /proc/devices | awk '{print $1;}'` 0
sudo mknod --mode=666 /dev/mydevice1 c `grep MyDevice /proc/devices | awk '{print $1;}'` 1
sudo mknod --mode=666 /dev/mydevice2 c `grep MyDevice /proc/devices | awk '{print $1;}'` 2
I made / dev / mydevice0, / dev / mydevice1 and / dev / mydevice2 experimentally. When cdev_add
is done, the number of devices is specified as 2. So I can create / dev / mydevice2, but when I try to open or access it, I get an error like cat: / dev / mydevice2: No such device or addres
.
As with the static case, delete it below.
sudo rmmod MyDeviceModule
sudo rm /dev/mydevice0
sudo rm /dev/mydevice1
sudo rm /dev/mydevice2
For example, you may want to change the process between minor number 0 (/ dev / mydevice0) and minor number 1 (/ dev / mydevice1). In the read / write handler function, you can use the minor number to divide the processing by switch-case, but it can also be realized by separating the handler table to be registered. Specifically, in the above code, prepare s_mydevice_fops
for the number of minors whose processing you want to divide (for example, s_mydevice_fops0
and s_mydevice_fops1
). By making struct cdev mydevice_cdev;
into an array and setting differently in the processing contents of 3, 4 and 5 (cdev_init ()
,cdev_add ()
,cdev_del ()
, respectively), I can handle it.
With the above method, you can now load the device driver that is dynamically assigned the major number. However, you have to create the device file manually, which is tedious. Linux has a mechanism called udev. If you register a class in / sys / class / when loading the driver, a daemon called udevd will detect it and automatically create a device file for you. In terms of implementation, an additional process of "registering a class in / sys / class / when loading a driver" is required.
In fact udev seems to be used for the plug and play feature. If you put the device driver module (.ko) in a special location (/ lib / modules /), it seems that the corresponding device driver will be loaded automatically when the device is detected. Therefore, insmod is not necessary.
Only mydevice_init
and mydevice_exit
have changed, so I'll just extract them. Class registration and deletion processing has been added respectively.
myDeviceDriver.c(Partially omitted)
/*Device driver class object*/
static struct class *mydevice_class = NULL;
/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Secure a free major number*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/* 2.Obtained dev( =Major number+Minor number)Get the major number from and keep it*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Unnecessary? */
/* 3.Initialization of cdev structure and registration of system call handler table*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.This device driver(cdev)In the kernel*/
cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", alloc_ret);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 5.Register the class of this device(/sys/class/mydevice/make) */
mydevice_class = class_create(THIS_MODULE, "mydevice");
if (IS_ERR(mydevice_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&mydevice_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 6. /sys/class/mydevice/mydevice*make*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(mydevice_class, NULL, MKDEV(mydevice_major, minor), NULL, "mydevice%d", minor);
}
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 7. /sys/class/mydevice/mydevice*To delete*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
}
/* 8.Remove class registration for this device(/sys/class/mydevice/To delete) */
class_destroy(mydevice_class);
/* 9.This device driver(cdev)From the kernel*/
cdev_del(&mydevice_cdev);
/* 10.Remove the major number registration used in this device driver*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
With the above code, / sys / class / mydevice / mydevice0 / dev
will be created at the timing of insmod. The udev mechanism automatically creates the device file / dev / mydevice0
based on the information in / sys / class / mydevice / mydevice0 / dev
.
However, by default, you will not have access privileges for general users. Add a rule file to change the access permissions. The rules file is located in /etc/udev/rules.d/
. The rules file itself starts with a number and seems to be any file with a .rules extension. For Raspberry Pi, there is a rule file called /etc/udev/rules.d/99-com.rules
from the beginning, so you can add it there. I wanted to divide it, so I did the following. With Raspberry Pi, I couldn't create a new file in /etc/udev/rules.d/
even if I added sudo
, so I used sudo -i
to enter superuser mode once and then performed the operation.
sudo -i
echo 'KERNEL=="mydevice[0-9]*", GROUP="root", MODE="0666"' >> /etc/udev/rules.d/81-my.rules
exit
Try building and insmod. This time, the device files (/ dev / mydevice0 and / dev / mydevice1) are automatically created just by insmoding.
make
sudo insmod MyDeviceModule.ko
ls -a /dev/my*
/dev/mydevice0 /dev/mydevice1
In addition, a file containing device information is created in the / sys / class / mydevice / mydevice0 /
directory and under it. If udev doesn't work, check here as well.
cat /sys/class/mydevice/mydevice0/dev
242:0
cat /sys/class/mydevice/mydevice0/uevent
MAJOR=242
MINOR=0
DEVNAME=mydevice0
The content of this article is in line with the content of "Linux Device Driver Programming (Yutaka Hirata)".
The book uses class_device_create
to create the class. However, class_device_create
is now obsolete. This article uses device_create
instead.
Recommended Posts