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) -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 <--------------------- This time Contents __ -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/08_01
Last time, I used procfs to create an interface for debugging device drivers. However, procfs is not a good place to use it for debugging, as it places information about the process. It is recommended to use debugfs for debugging. (Thank you for pointing out from @rarul.)
This time, I will try to implement the same thing as last time using this debugfs. In particular,
/sys/kernel/debug/MyDevice/prm1
Allows you to read and write debug parameters by accessing. Please note that this path may change depending on the environment.
There are two main ways to create a debugfs interface. I will describe each method.
To create an interface for debugfs, just register the file name and the function you want to call during read / write with the function debugfs_create_file ()
where the driver is loaded (insmod). When registering, set the read / write handler function in the struct file_operations
table. This is exactly the same as the previous procfs and regular device driver handler registration.
However, this will create a file for debugfs access in the root directory of debugfs. Of course, there are also files for other kernel modules, so organize them in a directory. Use debugfs_create_dir ()
to create a directory for debugfs. By putting the return value (entry) of debugfs_create_dir ()
in the third argument of debugfs_create_file ()
, the file will be created under the created directory.
Sometimes you just want to read and write parameters for debugging, but defining a read / write function every time can be tedious. If you only need read / write operations, you can create debugfs with just one helper function. For example, if you want to access a 32-bit variable, use debugfs_create_u32 ()
(the definition was unsigned, but you could also input and output negative numbers). If you want to read and write in hexadecimal, use debugfs_create_x32 ()
. There are also bool types, register sets (addresses and numbers), and functions for blobs that can be used for binary data. For the time being, I will use only 32-bit numbers.
You need to delete the debugfs file you created when you unload the kernel. To remove it, use debugfs_remove ()
. You can now delete the created file. However, it is troublesome to erase them one by one. Above all, it is troublesome to remember the entry information when it was created. For that, debugfs_remove_recursive
() is provided. By putting the entry (return value of debugfs_create_dir ()
) of the created directory in this function, the files under it will be deleted recursively.
The code looks like this: It has debug_prm1
statically as a parameter for debugging. Make your own directory name under the debugfs root directory "MyDevice" and keep the entry information in debug_entry_dir
.
myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/debugfs.h>
/***Information about this device***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Device name displayed on devices etc.*/
/*Debug variables*/
struct dentry *debug_entry_dir; /*debugfs directory entry*/
static int debug_prm1; /*Debug parameters(for test) */
static int debug_read_size = 0; /*Number of bytes to read in one open*/
/* /sys/kernel/debug/MyDevice/debug_Function called when accessing prm1*/
static int mydevice_debug_open(struct inode *inode, struct file *file)
{
printk("mydevice_proc_open\n");
debug_read_size = 4; //Read 4 bytes at a time
return 0;
}
/* /sys/kernel/debug/MyDevice/debug_Function called when reading prm1*/
static ssize_t mydevice_debug_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_proc_read\n");
if (debug_read_size > 0) {
/*When there is still data to output*/
/*Integer type number that is held(debug_prm1)Is output as a character string*/
int len;
len = sprintf(buf, "%d\n", debug_prm1); //Actually copy_to_should be user
debug_read_size -= 4;
return len;
} else {
return 0;
}
}
/* /sys/kernel/debug/MyDevice/debug_Function called when writing prm1*/
static ssize_t mydevice_debug_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_proc_write\n");
/*The entered character string is an integer type number(debug_prm1)Hold as*/
sscanf(buf, "%d", &debug_prm1); //Actually copy_from_should be user
return count;
}
/*Handler table for debugfs*/
static struct file_operations debug_debug_prm1_fops = {
.owner = THIS_MODULE,
.open = mydevice_debug_open,
.read = mydevice_debug_read,
.write = mydevice_debug_write,
};
/*Road(insmod)Functions sometimes called*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*Create a directory for debufs*/
debug_entry_dir = debugfs_create_dir(DRIVER_NAME, NULL);
if (debug_entry_dir == NULL) {
printk(KERN_ERR "debugfs_create_dir\n");
return -ENOMEM;
}
/*Method 1:Handler table registration method*/
debugfs_create_file("prm1", S_IRUGO | S_IWUGO, debug_entry_dir, NULL, &debug_debug_prm1_fops);
/*Method 2:Helper function method*/
debugfs_create_u32("_prm1", S_IRUGO | S_IWUGO, debug_entry_dir, &debug_prm1);
debugfs_create_x32("_prm1_hex", S_IRUGO | S_IWUGO, debug_entry_dir, &debug_prm1);
return 0;
}
/*Unload(rmmod)Functions sometimes called*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
/*Get rid of for debufs(Child files are also deleted automatically) */
debugfs_remove_recursive(debug_entry_dir);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
With method 1, you had to define an entry function or handler table, but with method 2, you end up with one line. Of course, method 2 is easy, but if you need to process something at the same time as rewriting the parameters, it seems that you need to use method 1. (For example, when changing the register (parameter) of the device connected by i2c.)
Build and load with the following command.
make
sudo insmod MyDeviceModule.ko
sudo ls /sys/kernel/debug/MyDevice
prm1 _prm1 _prm1_hex
Then, you can see that prm1
, _prm1
, and _prm1_hex
are created under / sys / kernel / debug / MyDevice
. prm1
is implemented by method 1, and _prm1
and _prm1_hex
are implemented by method 2. All access to the same variable. (Since the name was implemented like this, it just became prm1 and has no particular meaning.)
sudo bash -c 'echo 12 > /sys/kernel/debug/MyDevice/prm1'
sudo cat /sys/kernel/debug/MyDevice/prm1
12
sudo cat /sys/kernel/debug/MyDevice/_prm1
12
sudo cat /sys/kernel/debug/MyDevice/_prm1_hex
0x0000000c
sudo bash -c 'echo 13 > /sys/kernel/debug/MyDevice/_prm1'
sudo bash -c 'echo 0x0f > /sys/kernel/debug/MyDevice/_prm1_hex'
After that, you can read and write with cat and echo. The result will be the same because all the values of the same variable are input and output. It seems that 0x is automatically added when it is a hexadecimal number.
This may only be a Raspberry Pi.
This time, when creating each debugfs file, I specified S_IRUGO | S_IWUGO
as the access right. This should be readable and writable by all users. But in reality, I had to add sudo
when running. This is because general user access rights are not granted to the parent directories / sys
, / sys / kernel
, and / sys / kernel / debug
.
I think this can be changed by changing the mount options for these directories (probably somewhere in the script that runs at startup). But I haven't followed it deeply and I don't think it should be changed.
Recommended Posts