Detailed explanation of ioctl function in device driver

         ioctl is the abbreviation of iocontrol, which is IO control.


         In short, if you encounter some IO operations when writing the driver, which cannot be logically classified as read or write, it can be regarded as part of ioctl.

         Read and write should be used to write and read data, and should be processed as a simple way of data exchange. ioctl controls the read and write options.

         For example, you have made a general driver module for reading and writing IO ports. Read and write read and write data from the port, but how to change the read and write port? Obviously, ioctl is more reasonable.

         For example, your read and write can be blocked or cannot be blocked, or the reading and writing of device files can be concurrent or not. These can be written as situations that can be configured with ioctl. Later, in order to realize different IO characteristics of modules with ioctl.

         On parameters

         The general parameter format of ioctl is command word (constant) + command parameter.

         The parameter formats of read and write are data buffer + data destination pointer + length.

1, What is ioctl

          ioctl is a function in the device driver to manage the I/O channel of the device. The so-called I/O channel management is to control some characteristics of the equipment, such as serial port transmission baud rate, motor speed and so on. The following is its source code definition:

Function name: ioctl

Function: control I/O devices

Usage: int ioctl(int handle, int cmd,[int *argdx, int argcx]);
Parameter: fd is the file identifier returned by the open function when the user program opens the device. cmd is the control command of the user program on the device, followed by some supplementary parameters, usually up to one. The existence of this parameter is related to the meaning of cmd.

Comments for macros defined in include/asm/ioctl.h:

#define    _ IOC_NRBITS          eight                               // Bit width of ordinal number field, 8bits
#define    _ IOC_TYPEBITS        eight                               // Bit width of magic number (type) field, 8bits
#define    _ IOC_SIZEBITS        fourteen                              // Bit width of size field, 14bits
#define    _ IOC_DIRBITS         two                               // Bit width of direction field, 2bits
#define    _ IOC_NRMASK         ((1 << _IOC_NRBITS)-1)     // Mask of ordinal field, 0x000000FF
#define    _ IOC_TYPEMASK       ((1 << _IOC_TYPEBITS)-1)   // Mask of magic field, 0x000000FF
#define    _ IOC_SIZEMASK       ((1 << _IOC_SIZEBITS)-1)    // Mask for size field, 0x00003FFF
#define    _ IOC_DIRMASK        ((1 << _IOC_DIRBITS)-1)     // Mask of direction field, 0x00000003
#define    _ IOC_NRSHIFT       0                                // Displacement of ordinal field in the whole field, 0
#define    _ IOC_TYPESHIFT     (_IOC_NRSHIFT+_IOC_NRBITS)        // Displacement of magic field, 8
#define    _ IOC_SIZESHIFT     (_IOC_TYPESHIFT+_IOC_TYPEBITS)    // Displacement of size field, 16
#define    _ IOC_DIRSHIFT      (_IOC_SIZESHIFT+_IOC_SIZEBITS)    // Displacement of direction field, 30
#define _IOC_NONE      0U      // No data transfer
#define _IOC_WRITE     1U      // To write data to the device, the driver must read the data from user space
#define _IOC_READ      2U      // To read data from the device, the driver must write data to user space
#define _IOC(dir,type,nr,size) \
       (((dir)  << _IOC_DIRSHIFT) | \
        ((type) << _IOC_TYPESHIFT) | \
        ((nr)   << _IOC_NRSHIFT) | \
        ((size) << _IOC_SIZESHIFT))
//Construct command number without parameters
#define _IO(type,nr)             _IOC(_IOC_NONE,(type),(nr),0)
//Construct the command number to read data from the driver
#define _IOR(type,nr,size)     _IOC(_IOC_READ,(type),(nr),sizeof(size))
//Command to write data to the driver
#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//For bidirectional transmission
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
//Analyze the data direction from the command parameters, i.e. write in or read out
#define _IOC_DIR(nr)          (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//Parse the magic number type from the command parameters
#define _IOC_TYPE(nr)              (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//Parses the ordinal number from the command arguments
#define _IOC_NR(nr)           (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//Parse the user data size from the command parameters
#define _IOC_SIZE(nr)         (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
#define IOC_IN            (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT           (_IOC_READ << _IOC_DIRSHIFT)

2, Necessity of ioctl  

        If ioctl is not used, the I/O channel of the device can also be controlled. For example, when we implement write in the driver, we can check whether there is a special agreed data flow. If so, it will be followed by the control command (this is often done in socket programming). However, if you do so, it will lead to unclear code division, chaotic program structure, and the programmers themselves will be dizzy. Therefore, we use ioctl to realize the function of control. Remember, all the user program does is tell the driver what it wants to do through the command code (cmd). As for how to interpret these commands and how to implement them, this is what the driver needs to do.

3, How to implement ioctl

        The ioctl function implemented in the driver actually has a switch{case} structure. Each case corresponds to a command code and makes some corresponding operations. How to implement these operations is every programmer's own business. Because the equipment is specific, it can't be said here. The key is how to organize the command code, because in ioctl, the command code is the only way to contact the user program command and driver support.

        There is some emphasis on the organization of command codes, because we must ensure that commands and devices correspond one by one, so that we will not send the correct commands to the wrong devices, or send the wrong commands to the right devices, or send the wrong commands to the wrong devices. These errors will lead to unexpected things. When programmers find these strange things, it will be very difficult to debug the program to find errors. Therefore, a command code is defined in the Linux core as follows:  

|Equipment type | serial number | direction | data size|


|     8 bit    |   8 bit  | 2 bit | 8~14 bit|


      In this way, a command becomes an integer command code; However, the command code is not intuitive, so some macros are provided in the Linux Kernel. These macros can generate command codes according to understandable strings, or get some strings that users can understand from the command codes to indicate the device type, device serial number, data transmission direction and data transmission size corresponding to the command.

For example, as shown above:

//Construct command number without parameters
#define _IO(type,nr)             _IOC(_IOC_NONE,(type),(nr),0)
//Construct the command number to read data from the driver
#define _IOR(type,nr,size)     _IOC(_IOC_READ,(type),(nr),sizeof(size))
//Command to write data to the driver
#define _IOW(type,nr,size)    _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//For bidirectional transmission
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

    We also defined the command macro in the PWM driver:

#define   MAGIC_NUMBER    'k'
#define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define   BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)

      What must be mentioned here is the magic number_ Number, "magic number" is a letter, and the data length is also 8 bits. A specific letter is used to indicate the equipment type, which is the same as using a number, but it is more conducive to memory and understanding.

4, How to get cmd parameters  

    Here we really want to say that the cmd parameter is generated by some macros on the user program side according to the device type, serial number, transmission direction, data size, etc. this integer is passed to the driver in the kernel through the system call, and then the driver uses the decoding macro to obtain the device type, serial number, transmission direction, data size and other information from this integer, and then switch{case} structure.

The following is part of the code for an example:

#define  MAGIC_NUMBER    'k'
#define  BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define  BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define  BEEP_FREQ  _IO(MAGIC_NUMBER    ,2)
#define  BEPP_IN_FREQ 100000
static void beep_freq(unsigned long arg)
    writel(BEPP_IN_FREQ/arg, timer_base +TCNTB0  );
    writel(BEPP_IN_FREQ/(2*arg), timer_base +TCMPB0 );
static long beep_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
        case BEEP_ON:
        case BEEP_OFF:
        case BEEP_FREQ:
            beep_freq( arg );
        default :
            return -EINVAL;

The test code is as follows:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#define  MAGIC_NUMBER    'k'
#define   BEEP_ON    _IO(MAGIC_NUMBER    ,0)
#define   BEEP_OFF   _IO(MAGIC_NUMBER    ,1)
#define   BEEP_FREQ   _IO(MAGIC_NUMBER    ,2)
    int fd;
    fd = open("/dev/beep",O_RDWR);
        perror("open fail \n");
        return ;

Original link:

Tags: Linux kernel

Posted on Sat, 09 Oct 2021 05:40:09 -0400 by msaspence