Today, we will go to Chapter 6 advanced character driver operations in Linux Device Drivers (version 3rd.
I. IOCTL
In addition to reading and writing capabilities, most devices can perform operations beyond simple data transmission. Therefore, device drivers must also be able to perform various hardware control operations. these operations are often supported by the ioctl method, which has a prototype different from the user space version:
int (*ioctl) (struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg);
It should be noted that no matter whether the optional parameter Arg is given as an integer or a pointer by the user, it is transmitted in the form of an unsigned long. If the caller does not pass the ARG parameter, the ARG value received by the driver is undefined. Because the type check on the ARG parameter is disabled, if an invalid parameter is passed to ioctl, the compiler will not be able to issue an alert and any associated errors will be hard to be found.
Select IOCTL command
To prevent the wrong device from using the correct command, the command number should be unique within the system. To help programmers create a unique IOCTL command code, each command number is divided into multiple single-digit fields. To select the ioctl command number for the driver according to the agreed method of the Linux kernel, you should first look at include/ASM/IOCTL. h and documentation/ioctl-number.txt. The bit field symbol to be used is defined in <Linux/IOCTL. h>:
Type (magic number): 8-Bit Width (_ioc_typebits), refer to ioctl-number.txt to select a number and use it throughout the driver.
Number (ordinal number): sequential number, 8-Bit Width (_ ioc_nrbits ).
Direction: Possible values include _ ioc_none (no data transmission), _ ioc_read, _ ioc_write, and _ ioc_read | _ ioc_write (two-way data transmission ). This field is a single bit mask (two digits). Therefore, you can use the and operation to extract _ ioc_read and _ ioc_write.
Size (data size): the width is related to the architecture. Arm is 14 bits. Values of specific systems can be found in macro _ ioc_sizebits.
<ASM/IOCTL. h> in <Linux/IOCTL. h> defines some macros that construct command numbers:
_ IO (type, NR)/* command without parameters */_ ior (type, NR, datatype)/* read data from the driver */_ Iow (type, NR, datatype)/* write data */_ IOWR (type, NR, datatype)/* bidirectional transmission * // * The type and number members are passed as parameters, and the size member is obtained by applying the sizeof to the datatype parameter */
This header file also defines macros used to unbind this field:
_IOC_DIR(nr)_IOC_TYPE(nr)_IOC_NR(nr)_IOC_SIZE(nr)
The specific usage is shown in the experiment.
Return Value
POSIX standard stipulates that if an inappropriate IOCTL command number is used,-enotty should be returned. This error code is interpreted by the C library as "inappropriate device IOCTL. However, it returns-einval, which is still quite common.
Predefined commands
Some IOCTL commands are identified by the kernel. When these commands are used on their own devices, they will be decoded before our own file operations are called. therefore, if you select an IOCTL command number and the system predefines the same, you will never see the request for this command, and because of the conflict between IOCTL numbers, the behavior of the application cannot be predicted. Predefined commands are divided into three types:
(1) commands used for any files (General, device, FIFO, and socket)
(2) commands used only for regular files
(3) commands specific to the file system type
The following IOCTL commands are predefined to any file, including a specific file of the device:
-
Fioclex: Set the close-on-exec flag (File IOCTL close on Exec ).
-
Fionclex: Clear the close-no-exec flag (File IOCTL not close on Exec ).
-
Fioqsize: This command returns the size of a file or directory. When used as a device file, however, it returns an enotty error.
-
Fionbio: "file IOCTL non-blocking I/O" (described in the "blocking and non-blocking operations" section ).
Use IOCTL Parameters
When the optional Arg parameter of IOCTL is used, if an integer is passed, it can be directly used. If it is a pointer, you must be careful. When using a pointer to reference a user space, we must ensure that the user address is valid, and its verification (data is not transmitted) is implemented by the function access_ OK, which is defined in <ASM/uaccess. h>:
int access_ok(int type, const void *addr, unsigned long size);
The first parameter should be verify_read (read) or verify_write (read/write). The ADDR parameter is the user space address, the size is the number of bytes, and sizeof () can be used (). Access_ OK returns a Boolean value: 1 is successful (access is OK) and 0 is failed (access is faulty ). If it returns false, the driver should return-efault to the caller.
Note: first, access_ OK does not check the complete operation of memory access; it only checks whether the memory reference is within the memory range where the process has reasonable permissions, and ensures that the address does not point to the kernel space memory. Second, most of the driver code does not need to actually call access_ OK, but uses put_user (datum, PTR) and get_user (local, PTR) directly. They have the verification function, make sure that the process can write the given memory address. If the address is successful, 0 is returned, and-efault is returned in case of an error ..
put_user(datum, ptr)__put_user(datum, ptr)get_user(local, ptr)__get_user(local, ptr)
These macros are faster than copy_to_user and copy_from_user, and these macros have been written to allow passing pointer of any type, as long as they are a user space address. the size of the transmitted data depends on the type of the PRT parameter, and is determined by the macro creation in the compiler, such as sizeof and typeof, during compilation. They only transmit 1, 2, 4, or 8 bytes. If the above function is used to send a value of an unsuitable size, the result is often a strange message from the compiler, such as "coversion to non-scalar type requested ". in these cases, copy_to_user or copy_from_user must be used.
_ Put_user and _ get_user perform fewer checks (do not call access_ OK), but still fail. If the directed memory is not writable to the user, therefore, they should only use the access_ OK check in the memory zone. As a general rule: when implementing a read method, call _ put_user to save several cycles, or when you copy several items, so call access_ OK once before the first data transfer.
Capabilities and limited operations
The Linux Kernel provides a more flexible system called capabilities ). The kernel is designed for permission management and exports two system calls capget and capset, which can be defined in <Linux/capability. h>. The meaningful capabilities for device driver writers are as follows:
Cap_dac_override/* is beyond the access restrictions (Data Access control or DAC) on files and directories. */Cap_net_admin/* network management tasks, including the ability of tasks */cap_sys_module/* to load or remove kernel modules */cap_sys_rawio/* to perform "Raw" (bare) I/O operations. examples include the ability to access the port of the device or directly communicate with the USB device */cap_sys_admin/* to intercept the device port, provides many system management operations */cap_sys_tty_config/* the ability to execute tty configuration tasks */
Before performing a privileged operation, a device driver should check that the calling process has the appropriate capabilities. The check is performed using the capable function (defined in <Linux/sched. h>) Example:
if (! capable (CAP_SYS_ADMIN))return -EPERM;
Ii. positioning device (implemented by llseek)
Llseek is a system call to modify the current read/write location in a file. The default implementation in the kernel is shifted by modifying filp-> f_pos, which is the current read/write location in the file. For an lseek system call, the Read and Write methods must be used together by updating the offset they receive.
If the device is not allowed to be moved, you cannot just stop the statement llseek operation, because the default method allows the shift. In your open method, call nonseekable_open to notify the kernel that your device does not support llseek:
int nonseekable_open(struct inode *inode; struct file *filp);
For completeness, you should also set the llseek method in your file_operations structure to a special help function no_llseek (defined in <Linux/fs. h> ). The specific application is learned in the test program.
Iii. IOCTL and llseek experiments.
Module Program link: ioctl_and_llseek
Module Tester link: ioctl_and_llseek-test
The experiment of the arm9-lab Board is as follows:
[Tekkaman2440@SBC2440V4]#cd /lib/modules/[Tekkaman2440@SBC2440V4]#insmod scull.ko scull_nr_devs=1[Tekkaman2440@SBC2440V4]#cd /tmp/[Tekkaman2440@SBC2440V4]#./scull_test2open scull !SCULL_IOCSQUANTUM-SCULL_IOCQQUANTUM : scull_quantum=10SCULL_IOCTQUANTUM-SCULL_IOCGQUANTUM : scull_quantum=6SCULL_IOCXQUANTUM : scull_quantum=6 --> 10SCULL_IOCHQUANTUM : scull_quantum=10 --> 6SCULL_IOCSQSET-SCULL_IOCQQSET : scull_qset=2SCULL_IOCTQSET-SCULL_IOCGQSET : scull_qset=4SCULL_IOCXQSET : scull_qset=4 --> 2SCULL_IOCHQSET : scull_qset=2 --> 4before reset : scull_quantum=6 scull_qset=4close scull !reopen scull !reopen : scull_quantum=6 scull_qset=4write code=6 i=20write code=6 i=14write code=6 i=8write code=2lseek scull SEEK_SET-->0 !read code=6 i=20read code=6 i=14read code=6 i=8read code=2[0]=0 [1]=1 [2]=2 [3]=3 [4]=4[5]=5 [6]=6 [7]=7 [8]=8 [9]=9[10]=10 [11]=11 [12]=12 [13]=13 [14]=14[15]=15 [16]=16 [17]=17 [18]=18 [19]=19SCULL_IOCRESETafter reset : scull_quantum=4000 scull_qset=1000close scull !reopen scull !write code=20lseek scull SEEK_CUR-10-->10 !read code=10[0]=10 [1]=11 [2]=12 [3]=13 [4]=14[5]=15 [6]=16 [7]=17 [8]=18 [9]=19lseek scull SEEK_END-20-->0 !read code=20[0]=0 [1]=1 [2]=2 [3]=3 [4]=4[5]=5 [6]=6 [7]=7 [8]=8 [9]=9[10]=10 [11]=11 [12]=12 [13]=13 [14]=14[15]=15 [16]=16 [17]=17 [18]=18 [19]=19close scull ![Tekkaman2440@SBC2440V4]#cat /proc/scullseqDevice 0: qset 1000, q 4000, sz 20 item at c3dd3d74, qset at c3f54000 0: c3e71000[Tekkaman2440@SBC2440V4]#