How to make an embedded Linux device driver (3)

Third time: System call handler and driver registration (dynamic method)

About this series

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

The entire source code that appears in this article

https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_02

Contents of this time

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.

Minor correction

--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

Dynamically assign a major number and register it in the kernel

Device driver source code

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 thecdev_initfunction. Specifically, register the system call handler (open / close / read / write) table. -(4) Use thecdev_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

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.

End processing

As with the static case, delete it below.

sudo rmmod MyDeviceModule
sudo rm /dev/mydevice0
sudo rm /dev/mydevice1
sudo rm /dev/mydevice2

Note: If you want to change the processing for each minor number

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.

Automatically create device file (/ dev / XXX) for udev

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.

Note

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.

Device driver source code

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);

Add a rule file

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

Build and load

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

Caution

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

How to make an embedded Linux device driver (11)
How to make an embedded Linux device driver (8)
How to make an embedded Linux device driver (1)
How to make an embedded Linux device driver (4)
How to make an embedded Linux device driver (7)
How to make an embedded Linux device driver (2)
How to make an embedded Linux device driver (3)
How to make an embedded Linux device driver (6)
How to make an embedded Linux device driver (5)
How to make an embedded Linux device driver (10)
How to make an embedded Linux device driver (9)
How to make an embedded Linux device driver (12) (Complete)
[Blender x Python] How to make an animation
How to make Linux compatible with Japanese keyboard
How to make Yubico Yubikey recognized in Manjaro Linux
How to make an interactive CLI tool in Golang
How to make an HTTPS server with Go / Gin
[Python] How to make an adjacency matrix / adjacency list [Graph theory]
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit edition-
How to make a Python package (written for an intern)
How to create an ISO file (CD image) on Linux
How to make a Japanese-English translation
How to make a slack bot
How to install wkhtmltopdf (Amazon Linux2)
How to install VMware-Tools on Linux
How to make a crawler --Advanced
How to make a recursive function
How to install MBDyn (Linux Ubuntu)
How to make a deadman's switch
[Blender] How to make a Blender plugin
[Blender] How to make Blender scripts multilingual
How to make a crawler --Basic
How to build MongoDB C driver
How to check Linux OS version
How to make a string into an array or an array into a string in Python
How to get the printer driver for Oki Mac into Linux
How to build my own Linux server
How to make Selenium as light as possible
[Linux] How to subdivide files and folders
How to make an artificial intelligence LINE bot with Flask + LINE Messaging API
How to install aws-session-manager-plugin on Manajro Linux
[Python] How to make a class iterable
python3 How to install an external module
How to create an NVIDIA Docker environment
How to convert Python to an exe file
I want to know how LINUX works!
[Linux] How to use the echo command
How to use the Linux grep command
How to display emoji on Manjaro Linux
How to install packages on Alpine Linux
[Cocos2d-x] How to make Script Binding (Part 2)
How to install Anisble on Amazon Linux 2
How to operate Linux from the console
How to install Windows Subsystem For Linux
How to power off Linux with Ultra96-V2
How to update security on CentOS Linux 8
I want to make an automation program!
How to install php7.4 on Linux (Ubuntu)
How to make multi-boot USB (Windows 10 compatible)
How to make a Backtrader custom indicator
How to make a Pelican site map