04 Input Output Control
Linux OS seperates memory into two parts: user space and kernel space. User space is where the application runs, and kernel space is where the OS runs. The kernel space is protected from user space by a mechanism called memory protection.
To communicate between user space and kernel space, there are several ways:
- ioctl
- procfs
- sysfs
- netlink
- configfs
- debugfs
- …
Introducing an ioctl
Input-output control (ioctl, in short) is a common operation or system call available with most of the driver categories. It provides a way for user space applications to communicate with device drivers. It allows user space to send control commands to the driver.
Purpose of using ioctl
- Configure device parameters.
- Control device behavior.
- Retrieve device status.
- It can handle a wide range of operations, such as setting device modes, getting device information, or performing specific actions.
The ioctl function is nothing but a simple function with switch…case implementation over the command implementing the corresponding functionalities.
There are some steps involved in implementing of ioctl in Linux Device Drivers:
- Create
ioctlcommand in the driver - Write the
ioctlfunction in the driver - Create
ioctlcommand in a Userspace application - Use the
ioctlsystem call in a Userspace
1. Create ioctl command in the driver
We need to know macro structure of creating command:
#define _IO(type, number) // For commands without data
#define _IOR(type, number, data) // For commands that read data
#define _IOW(type, number, data) // For commands that write data
#define _IOWR(type, number, data) // For commands that read and write data
Where:
_IO: Used for commands that do not require any data to be passed to or from the driver._IOR: Used for commands that read data from the driver to user space._IOW: Used for commands that write data from user space to the driver._IOWR: Used for commands that both read and write data.
and:
type: A unique character that identifies the driver or subsystem. This helps avoid conflicts between different drivers.number: A unique number for the command within the driver.data: The type of data being passed (e.g., int, struct, etc.).
Example:
#include <linux/ioctl.h>
typedef struct
{
int day, month, year;
} date_t;
// Define a unique character for the driver
#define MY_IOCTL_MAGIC 'M'
// Define command codes
#define MY_IOCTL_SET_VALUE _IOW(MY_IOCTL_MAGIC, 1, int) // Command to set a value
#define MY_IOCTL_GET_VALUE _IOR(MY_IOCTL_MAGIC, 2, int) // Command to get a value
#define MY_IOCTL_RESET _IO(MY_IOCTL_MAGIC, 3) // Command to reset the device
#define MY_IOCTL_GET_DATE _IOR(MY_IOCTL_MAGIC, 4, date_t* ) // Command to get a date
2. Write the ioctl function in the driver
In the struct file_operations has an attribute unlocked_ioctl. We need to implement that funtion to handle the ioctl requests.
In my code, i wrote the funtion my_ioctl and then link it to unlocked_iotcl.
// File operations structure
static struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = my_ioctl,
// Other file operations (open, read, write, etc.) can be added here
};
Below is the prototype of the function:
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
int my_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg){}
#else
long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg){}
#endif
Where:
inode: is the inode number of the file being worked on.file: is the file pointer to the file that was passed by the application.cmd: is the ioctl command that was called from the userspace.arg: are the arguments passed from the userspace
3. Create ioctl command in a Userspace application
Just define the ioctl command like how we defined it in the driver.
// Define a unique character for the driver
#define MY_IOCTL_MAGIC 'M'
// Define command codes
#define MY_IOCTL_SET_VALUE _IOW(MY_IOCTL_MAGIC, 1, int) // Command to set a value
#define MY_IOCTL_GET_VALUE _IOR(MY_IOCTL_MAGIC, 2, int) // Command to get a value
#define MY_IOCTL_RESET _IO(MY_IOCTL_MAGIC, 3) // Command to reset the device
#define MY_IOCTL_GET_DATE _IOR(MY_IOCTL_MAGIC, 4, date_t* ) // Command to get a date
4. Use the ioctl system call in a Userspace
After define command codes and a data structure that matches what the driver expects, open the device file, in userspace we use ioctl to send and receive data.
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long cmd, ...);
fdis file descriptorcmdis cmd...can be arguments
Our code
The code located in folder 04_input_output_control
# To build ko driver
make build-04
# To clean ko driver
make clean-04
# To upload ko driver to our board
make upload-04
Result
[root@luckfox root]# ls | grep -e user -e io
ioctrl.ko
user_app
[root@luckfox root]# insmod ioctrl.ko
[root@luckfox root]# ./user_app
*******User App IOCTL*******
Opening Driver
Reading Value from Driver
Day 13, month 3, year 2025
Closing Driver