Introduction
The part 5 of Linux driver series covered the concept of device file operations how they can be implemented and shared a module example for “open, read, write, close” actions.
This article is a continuation to this series, and will present IOCTL system call how it can be implemented to perform file operations allowing communication between the kernel driver and the user space application.
IOCTL
IOCTL (Input Output Control) is a system call used to control device drivers and it’s used in most kernel drivers. It is useful for specific device operations that do not have appropriate system calls in the kernel.
In real world projects, drivers may need implementing device commands other than read() and write() such as format and reset…
Implementing IOCTL
IOCTL is to be implemented in both kernel driver and user application. Below a high level overview for steps to be followed to implement an IOCTL system call:
- Define a macro for the IOCTL command in the driver
- Define the IOCTL function in the driver
- Define IOCTL command in user application
- Use the IOCTL system call in user application
Defining a macro for the IOCTL command in the driver
IOCTL command has to be unique to avoid devices conflicts. Below is the macro to be used to define an IOCTL command:
#define ioctl_name __Direction(type, number, data_type)
- Direction
It specifies direction of data to be transferred. It can be _IO, _IOR, _IOW, or _IOWR for respectively IOCTL with no parameters, read, write or read/write. - type
It’s the magic number to be used. The already used magic numbers can be checked in “Documentation/ioctl/ioctl-number.txt” file. This field is an eight bits number (_IOC_TYPEBITS
). - number
It’s the ordinal nubmer AKA sequential number. This field is an eight bits number (_IOC_NRBITS
). It has to be unique in a chosen magic number. - data_type
It’s type of data involved.
Choosing IOCTL magic number and sequential number
The magic number and sequential number are two fields of the IOCTL command number that has to be unique.
To choose those numbers the file “Documentation/ioctl/ioctl-number.txt” is to be taken into account. The file lists information about used numbers in four columns:
- Code
It lists the used magic numbers. - Seq#
It lists the used sequential numbers for each magic number. - Include File
It tells the header files defining IOCTL commands macros with the reserved numbers. - Comments

As shown in above snippet from ioctl-number.txt file, the first magic number (0x00) in the table (line 64 in the file) has already a reserved range of sequential numbers from 00 to 1F defined in the linux/fs.h header file. And as stated in the “Comments” column, the same range is conflicting with other drivers header files scsi/scsi_ioctl.h and linux/fb.h.
So a free sequential number such as “20” can be used for the 0x00 magic number:
#include <linux/ioctl.h>
#define WRITE_IOCTL _IOW('0','0x20',int32_t*)
Defining the IOCTL function in the driver
The IOCTL function can be defined as described below:
static long example_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
- *filp
Pointer to the file passed from user application. - cmd
The IOCTL command called from user application. - arg
Arguments passed from user application.
The function then is to be registered in the struct file_operations (discussed in previous post) using the .unlock_ioctl member as shown in example below:
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = device_open,
.read = data_read,
.write = data_write,
.unlocked_ioctl = example_ioctl,
.release = device_release,
};
Defining IOCTL command in user application
Same as for the kernel module, the IOCTL command can be defined by the the same macro:
#define WRITE_IOCTL _IOW('0','0x20',int32_t*)
Using the IOCTL system call in user application
After including <sys/ioctl.h> file, the IOCTL can then be called from user application using the function:
int ioctl(int fd, int cmd, ...);
- fd
It refers to the open file descriptor (device file). - cmd
The IOCTL command to be executed. - …
The type of the third parameter depends on the passed IOCTL command. It can be an integer value, a pointer to other data or empty if the issued command takes no arguments. The pointer is used to exchange data between user space and kernel space. It is left “…” in the prototype definition to ignore type checking during compilation.
Example of using IOCTL system call:
ioctl(fd, WRITE_IOCTL, (int32_t*) &number);
Kernel module code example
/*******************************************************************************
* \file driver-example.c
*
* \details Simple driver example
*
* \author AJ Embedded Systems Consultant https://aj-ese.com
*
* *******************************************************************************/
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/err.h>
#include <linux/device.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include <linux/ioctl.h>
#define file_size 1024 // memory size to be reserved for the file
#define WRITE_IOCTL _IOW('0','0x20',int32_t*)
#define READ_IOCTL _IOR('0','0x21',int32_t*)
/* Associating Major and Minor number to a device*/
dev_t dev = 0;
const char * device_name = "AJ_Dev";
static struct class *device_class;
static struct cdev AJ_cdev;
uint8_t *kernel_buf;
int32_t user_value = 0;
static int __init AJ_driver_init(void);
static void __exit AJ_driver_exit(void);
static int device_open(struct inode *inode, struct file *file);
static ssize_t data_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t data_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static int device_release(struct inode *inode, struct file *file);
static long example_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = device_open,
.read = data_read,
.write = data_write,
.unlocked_ioctl = example_ioctl,
.release = device_release,
};
/* The function that will be called when the device file is open */
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File is opened...\n");
return 0;
}
/* The function that will be called when data is read from device file */
static ssize_t data_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
/* Managing position in file */
ssize_t remaining_bytes = (size_t)(file_size - *off); // Remaining bytes to read from file
if( copy_to_user(buf, kernel_buf, file_size) )
{
pr_err("Err : Cannot copy data to user !\n");
}
/* Update the current file position */
*off += remaining_bytes;
printk(KERN_INFO "Data Read : Successfull!\n");
return remaining_bytes;
}
/* The function that will be called when data is written to device file */
static ssize_t data_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
if( copy_from_user(kernel_buf, buf, len) )
{
pr_err("Err : Cannot write data from user !\n");
}
printk(KERN_INFO "Data Write : Successfull!\n");
return len;
}
/* The function that will be called when device file is closed */
static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "Device File is closed...!!!\n");
return 0;
}
/* This function will be called when we write IOCTL on the Device file */
static long example_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WRITE_IOCTL:
if( copy_from_user(&user_value ,(int32_t*) arg, sizeof(user_value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("The user value is: %d\n", user_value);
break;
case READ_IOCTL:
if( copy_to_user((int32_t*) arg, &user_value, sizeof(user_value)) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** Module Init function
*/
static int AJ_driver_init(void)
{
/* Creating a device with the reserved Major and Minor numbers */
if ( ( alloc_chrdev_region(&dev, 0, 1, device_name)) < 0 )
{
printk(KERN_INFO "Cannot allocate major number for device %s \n", device_name);
return -1;
}
/*Initializing cdev structure*/
cdev_init(&AJ_cdev,&fops);
/*Adding character device to the system*/
if((cdev_add(&AJ_cdev, dev, 1)) < 0)
{
printk(KERN_INFO "Cannot add the device to the system\n");
goto destroy_class;
}
/*Creating device class*/
device_class = class_create(THIS_MODULE,"AJ_class");
if(IS_ERR(device_class))
{
printk(KERN_ERR "Cannot create the struct class for device\n");
goto destroy_class;
}
/*Creating device*/
if(IS_ERR(device_create(device_class,NULL,dev,NULL,"AJ_device")))
{
printk(KERN_ERR "Cannot create the Device\n");
goto destroy_device;
}
/*Creating Physical memory */
if((kernel_buf = kmalloc(file_size , GFP_KERNEL)) == 0)
{
pr_info("Cannot allocate memory in kernel\n");
goto destroy_device;
}
printk(KERN_INFO "The device %s is created with Major = %d Minor = %d \n",device_name, MAJOR(dev), MINOR(dev));
printk(KERN_INFO "The AJ-driver is inserted successfully...\n");
return 0;
destroy_class:
class_destroy(device_class);
destroy_device:
device_destroy(device_class, dev);
return -1;
}
/*
** Module Exit function
*/
static void AJ_driver_exit(void)
{
kfree(kernel_buf);
device_destroy(device_class,dev);
class_destroy(device_class);
cdev_del(&AJ_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "The AJ-driver is removed successfully...\n");
}
module_init(AJ_driver_init);
module_exit(AJ_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AJ Embedded Systems Consultant https://aj-ese.com");
MODULE_DESCRIPTION("Simple driver example implementing IOCTL");
MODULE_VERSION("1:1.4");
User application code example
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>
#define WRITE_IOCTL _IOW('0','0x20',int32_t*)
#define READ_IOCTL _IOR('0','0x21',int32_t*)
int main()
{
int fd;
int32_t kernel_value; // The value to be read from kernel space (from device file)
int32_t number=1; // The value to be written to the kernel space (to device file)
printf("\nOpening device file...\n");
fd = open("/dev/AJ_device", O_RDWR);
if(fd < 0) {
printf("Cannot open device file...\n");
return 0;
}
printf("Writing Value to Driver\n");
ioctl(fd, WRITE_IOCTL, (int32_t*) &number); // Writing "number" to device file
printf("Reading Value from Driver\n");
ioctl(fd, READ_IOCTL, (int32_t*) &kernel_value); // Reading "kernel_value" from device file
printf("Kernel value is %d\n", kernel_value);
printf("Closing device file...\n");
close(fd);
}

Leave a Reply