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 <--------------------- This time Contents __ -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/06_01
Until the last time, I explained how to implement basic system calls (open, close, read, write). Also, using them, I actually made a device driver for GPIO of Raspberry Pi. This time, I will additionally implement a system call called ioctl.
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
The system call defined above. Enter the file descriptor (fd) obtained by open in the first argument. The second argument is called a request, but it's a command. The third argument (variadic length) is a parameter. By using this ioctl, you can freely add interfaces on the driver side.
So far, I've been using cdev to create device drivers. As a result, it is recognized by the kernel as a character-type device driver. Therefore, when communicating with the user application by read and write, the character type was used. This is fine for simple interactions, but it's not enough when you actually control the device. For example, it is necessary to specify numbers for SPI communication speed setting and I2C slave address setting, but these cannot be done only by read and write. In such a case, add an interface on the device driver side. Use ioctls for that.
As mentioned above, ioctl defines its own commands and parameters for each device driver. Therefore, the specification (header) to be checked when using ioctl is not ioctl.h, but the header (or source code) prepared by each device driver. This time, I will try to make a device driver by myself, so I think you can get a feel for it.
Again, ioctl defines its own commands and parameters on the device driver side. Commands are int numbers, and parameters are usually structures. (No parameters are required). Create a header with these definitions. Since it will be referenced when the user uses it, it will be a separate file.
myDeviceDriver.h
#ifndef MY_DEVICE_DRIVER_H_
#define MY_DEVICE_DRIVER_H_
#include <linux/ioctl.h>
/***Parameters for ioctl(3rd argument)Definition of***/
struct mydevice_values {
int val1;
int val2;
};
/***Command for ioctl(request,2nd argument)Definition of***/
/*The type of command for IOCTL used by this device driver. Anything is fine'M'Try to*/
#define MYDEVICE_IOC_TYPE 'M'
/*A command to set a value for the driver. The parameter is mydevice_values type*/
#define MYDEVICE_SET_VALUES _IOW(MYDEVICE_IOC_TYPE, 1, struct mydevice_values)
/*A command to get a value from a driver. The parameter is mydevice_values type*/
#define MYDEVICE_GET_VALUES _IOR(MYDEVICE_IOC_TYPE, 2, struct mydevice_values)
#endif /* MY_DEVICE_DRIVER_H_ */
For the time being, this time, I will create a command to read and write two values (val1 and val2). This has no particular meaning. The command (request) names should be MYDEVICE_SET_VALUES
and MYDEVICE_GET_VALUES
. This command works in logic even if you type the numbers directly. For example, you can do the following.
#define MYDEVICE_SET_VALUES 3 //The numbers have no particular meaning. Anything unique within this driver is OK
#define MYDEVICE_GET_VALUES 4 //The numbers have no particular meaning. Anything unique within this driver is OK
Actually, from the information such as "whether the parameter is Read or Write, Read / Write", "type", "unique number", "parameter size", _IO
, _IOW
, _IOR
, It seems to be common to generate using the _IOWR
macro. This time, the device driver name is "myDeviceDriver", so I set the type to'M'.
As a parameter type, also define a structure struct mydevice_values
that has two values.
The code on the device driver side only needs a handler function to use when the ioctl is called and a registration in the struct file_operations
table. Below is the code excerpted only from the relevant parts.
myDeviceDriver.c
#include "myDeviceDriver.h"
/*Variables that hold values for ioctl tests*/
static struct mydevice_values stored_values;
/*Functions called during ioctl*/
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("mydevice_ioctl\n");
switch (cmd) {
case MYDEVICE_SET_VALUES:
printk("MYDEVICE_SET_VALUES\n");
if (copy_from_user(&stored_values, (void __user *)arg, sizeof(stored_values))) {
return -EFAULT;
}
break;
case MYDEVICE_GET_VALUES:
printk("MYDEVICE_GET_VALUES\n");
if (copy_to_user((void __user *)arg, &stored_values, sizeof(stored_values))) {
return -EFAULT;
}
break;
default:
printk(KERN_WARNING "unsupported command %d\n", cmd);
return -EFAULT;
}
return 0;
}
/*Handler table for various system calls*/
static struct file_operations mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
.unlocked_ioctl = mydevice_ioctl,
.compat_ioctl = mydevice_ioctl, // for 32-bit App
};
The commands and parameters are defined in the header file, so they are included. Also, this time I made a command to set and GET the value as a trial, so I prepared a static variable to hold it. In the real implementation, please manage it properly by using private_data in the file structure.
The mydevice_ioctl
function is called by ioctl. As you can see, there is only a switch-case statement for the command (request). The pointer to the parameter is in arg, so cast it appropriately to read and write. This is the same as read and write. Finally, register it in the struct file_operations
table. Although not described here, this table is registered with cdev_init, cdev_add
when the kernel is loaded.
The content of this article is in line with the content of "Linux Device Driver Programming (Yutaka Hirata)".
Register the function in struct file_operations
to register the ioctl handler. In the book, I registered using the .ioctl
member. However, it is now obsolete. Use .unlocked_ioctl
and .compat_ioctl
instead. There seem to be two reasons for supporting 32-bit applications in a 64-bit environment. Details were explained at here.
In the book, in order to support both 32-bit / 64-bit environments, support methods such as adding padding were also introduced. Perhaps .compat_ioctl
has been added to avoid doing this. maybe.
Try calling ioctl of the implemented device driver with the following test program. Set the value ({1, 2}) with MYDEVICE_SET_VALUES. After that, try to get the value with MYDEVICE_GET_VALUES.
test.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include "myDeviceDriver.h"
int main()
{
int fd;
struct mydevice_values values_set;
struct mydevice_values values_get;
values_set.val1 = 1;
values_set.val2 = 2;
if ((fd = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
if (ioctl(fd, MYDEVICE_SET_VALUES, &values_set) < 0) perror("ioctl_set");
if (ioctl(fd, MYDEVICE_GET_VALUES, &values_get) < 0) perror("ioctl_get");
printf("val1 = %d, val2 = %d\n", values_get.val1, values_get.val2);
if (close(fd) != 0) perror("close");
return 0;
}
First, build and load the device driver. Then build and run the test program.
make
sudo insmod MyDeviceModule.ko
gcc test.c
./a.out
val1 = 1, val2 = 2
Then, you can see that the set value can be read like this.
Even if you are a user who does not write the device driver yourself, ioctl is always required when you touch any device. It was always messy and I wasn't sure, but I was able to understand it by implementing it myself.
Recommended Posts