How to make an embedded Linux device driver (6)

6th: Implementation of ioctl

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

The entire source code that appears in this article

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

Contents of this time

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.

What is 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.

Note

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.

Implementation of ioctl system call handler

Header definition

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.

Device driver implementation

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.

Note

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 from the user program

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

Execute

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.

in conclusion

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

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 Part 2-
How to make a hacking lab-Kali Linux (2020.1) VirtualBox 64-bit edition-
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 create an email user
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 make Word Cloud characters monochromatic
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 update php on Amazon linux 2
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