In-Depth Character Device Driver: Usage of the ioctl Function

1. Function of the ioctl Function

A function for device control is provided in the kernel source code, and device control is implemented through the ioctl function, while data transmission is achieved through read/write.

Example: For instance, the control of the camera’s operating mode is implemented through ioctl, while the reading and writing of camera data is done through the read/write functions.

The kernel standardizes the control protocol.

2. Analysis of the ioctl Function

2.1 Application Layer ioctl Function

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
    Function: Device control function    Parameters:    @ fd : Opened file descriptor    @ request : Request code/command code        Detailed analysis of the request code is below    @ ... : Variable parameters   If a third parameter is passed, it is an address    Return value:    Returns 0 on success, -1 on failure, and errno is set

2.2 Kernel Layer ioctl Function

 .unlocked_ioctl = myled_ioctl,
long myled_ioctl(struct file *file, unsigned int cmd , unsigned long arg); Function: Kernel layer ioctl function  Parameters:   @ cmd : Request code   @ arg : Value passed as a variable parameter

In-Depth Character Device Driver: Usage of the ioctl Function2.3 Detailed Analysis of Request Codes

 The kernel standardizes the control-related protocols. ====== ================================== bits   meaning ====== ==================================                                                                                 31-30     -----> Direction bits        00 - no parameters: uses _IO macro        10 - read: _IOR        01 - write: _IOW        11 - read/write: _IOWR
 29-16     -----> Specifies the size of the third parameter of the ioctl function        size of arguments
 15-8      -----> Each driver has a unique ASCII character for identification        ascii character supposedly     unique to each driver
 7-0       -----> Function, for example: the LED has two functions 0 means off  1 means on        function # ====== ==================================

How to Encapsulate Command Codes:

#define _IO(type,nr)        _IOC(0,(type),(nr),0)
#define _IOR(type,nr,size)    _IOC(2,(type),(nr),(sizeof(size)))
#define _IOW(type,nr,size)    _IOC(1,(type),(nr),(sizeof(size)))
#define _IOWR(type,nr,size)    _IOC(3,(type),(nr),(sizeof(size)))
#define _IOC(dir,type,nr,size)   (((dir)  << 30) | ((type) << 8) | ((nr)  << 0) | ((size) << 16))                                     |                 |              |                |                                    Direction          Type (unique character)      Function code           Size of the third parameter                                  [31:30]            [15:8]          [7:0]           [29:16]

In-Depth Character Device Driver: Usage of the ioctl Function

// Example
#define  LED_ON      _IOW('L',1,int)   // 01  00_0000_0000_0100  0100_1100 0000_0001
#define  LED_OFF     _IOW('L',0,int)   // 01  00_0000_0000_0100  0100_1100 0000_0000

3. ioctl Example

ioctl_example.h File

#ifndef IOCTL_EXAMPLE_H
#define IOCTL_EXAMPLE_H
#include <linux/ioctl.h>
// Define magic number to distinguish different devices
#define IOCTL_MAGIC 'k'
// Define ioctl commands
#define IOCTL_SET_VALUE _IOW(IOCTL_MAGIC, 0, int)
#define IOCTL_GET_VALUE _IOR(IOCTL_MAGIC, 1, int)
#define IOCTL_MAX_NR 1
// Device name
#define DEVICE_NAME "ioctl_example"
#endif

ioctl_module.c File

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "ioctl_example.h"
struct ioctl_device {    int value;    struct cdev cdev;} dev;
static dev_t dev_num;
static int ioctl_open(struct inode *inode, struct file *file) {    file->private_data = &dev;    printk(KERN_INFO "ioctl device opened\n");    return 0;}
static int ioctl_release(struct inode *inode, struct file *file) {    printk(KERN_INFO "ioctl device closed\n");    return 0;}
// ioctl handler function
static long ioctl_unlocked(struct file *file, unsigned int cmd, unsigned long arg) {    struct ioctl_device *dev = file->private_data;    int ret = 0;    int val;    // Check command validity    if (_IOC_TYPE(cmd) != IOCTL_MAGIC) return -ENOTTY;    if (_IOC_NR(cmd) > IOCTL_MAX_NR) return -ENOTTY;    // Handle based on command type    switch (cmd) {        case IOCTL_SET_VALUE:            // Get data from user space            if (copy_from_user(&val, (int __user *)arg, sizeof(val))) {                return -EFAULT;            }            dev->value = val;            printk(KERN_INFO "Set value to %d\n", dev->value);            break;        case IOCTL_GET_VALUE:            // Send data to user space            val = dev->value;            if (copy_to_user((int __user *)arg, &val, sizeof(val))) {                return -EFAULT;            }            printk(KERN_INFO "Get value %d\n", dev->value);            break;        default:            return -ENOTTY;    }    return ret;}
static const struct file_operations fops = {    .owner = THIS_MODULE,    .open = ioctl_open,    .release = ioctl_release,    .unlocked_ioctl = ioctl_unlocked,};
static int __init ioctl_init(void) {    int ret;    // Allocate device number    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);    if (ret < 0) {        printk(KERN_ERR "Failed to allocate device number\n");        return ret;    }    // Initialize character device    cdev_init(&dev.cdev, &fops);    dev.cdev.owner = THIS_MODULE;    // Add character device to system    ret = cdev_add(&dev.cdev, dev_num, 1);    if (ret < 0) {        printk(KERN_ERR "Failed to add cdev\n");        unregister_chrdev_region(dev_num, 1);        return ret;    }    dev.value = 0;    printk(KERN_INFO "ioctl example module loaded\n");    return 0;}
static void __exit ioctl_exit(void) {    cdev_del(&dev.cdev);    unregister_chrdev_region(dev_num, 1);    printk(KERN_INFO "ioctl example module unloaded\n");}
module_init(ioctl_init);
module_exit(ioctl_exit);

ioctl_user.c File

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "ioctl_example.h"
int main() {    int fd;    int value, ret;
    // Open device    fd = open("/dev/" DEVICE_NAME, O_RDWR);    if (fd < 0) {        perror("Failed to open device");        return EXIT_FAILURE;    }    // Set value    value = 123;    ret = ioctl(fd, IOCTL_SET_VALUE, &value);    if (ret < 0) {        perror("IOCTL_SET_VALUE failed");        close(fd);        return EXIT_FAILURE;    }    printf("Set value to %d\n", value);    // Get value    ret = ioctl(fd, IOCTL_GET_VALUE, &value);    if (ret < 0) {        perror("IOCTL_GET_VALUE failed");        close(fd);        return EXIT_FAILURE;    }    printf("Got value: %d\n", value);    // Set a different value again    value = 456;    ret = ioctl(fd, IOCTL_SET_VALUE, &value);    if (ret < 0) {        perror("IOCTL_SET_VALUE failed");        close(fd);        return EXIT_FAILURE;    }    printf("Set value to %d\n", value);    // Get value again    ret = ioctl(fd, IOCTL_GET_VALUE, &value);    if (ret < 0) {        perror("IOCTL_GET_VALUE failed");        close(fd);        return EXIT_FAILURE;    }
    printf("Got value: %d\n", value);    // Close device    close(fd);    return EXIT_SUCCESS;}

Please open in the WeChat client

Leave a Comment