Advanced character device driver note collation for Linux kernel device drivers

  • 2020-12-22 17:50:47
  • OfStack


/******************
 *  Advanced character device driver 
 ******************/

(1)ioctl

In addition to reading and writing devices, most drivers require an additional capability to perform various types of hardware control through the device driver. Things like ejecting media, changing the baud rate, etc. These operations are supported through the ioctl method, which implements the system call of the same name.

In user space, the prototype for the ioctl system call is:

int ioctl(int fd, unsigned long cmd, ...); fd: Device file descriptor open cmd: command Parameter 3: Depending on the command, it can be an integer or a pointer, or no. Use "..." Is simply used to avoid compiler errors.

The driver's ioctl method prototype and the user-space version have 1 differences:

[

int (*ioctl) (struct inode *inode,
struct file *filp,
unsigned int cmd,
unsigned long arg);
inode/filp: fd for user space
cmd: cmd from user space
arg: Corresponds to the cmd parameter passed

]

Most ioctl implementations include an switch statement to select the corresponding operation based on the cmd parameter. The command numbers for user space and kernel space should be 1.

(2) Select the command number of ioctl

Before you write code for ioctl, select the number that corresponds to the different commands. You cannot simply select a number from 0 or 1, because linux requires that this command number be system-wide and only 1. linux kernel USES the conventional method for the driver to select ioctl number, you can refer to include/asm/ioctl h and Documentation/ioctl - number. txt.

One ioctl number is 32 bits, linux divides it into four parts, and the macros needed to build one ioctl number are defined in < linux/ioctl.h > :

type 8-bit magic number. It's just a matter of selecting a number for your driver. Reference ioctl - number. txt number 8-digit ordinal number. direction two. Defines the direction of data transmission. For example, _IOC_NONE(no data transfer), _IOC_READ|_IOC_WRITE(two-way data transfer). Note that this direction is for the user, so IOC_READ means that the data is being read from the device and the driver should be writing to user space. size 14. The size of the user data involved.

Can be used < linux/ioctl.h > In the macro construction 1 ioctl

_IO(type, nr) _IOR(type,nr,datatype) _IOW(type,nr,datatype)

The return value

For system calls, a positive return value is first protected, while a negative value is considered an error and is used to set the error variable for user space. If an undefined ioctl number is passed in when the ioctl method is called, the system returns an error value of -ENVAL and -ENOTTY

(3) Blocking and non-blocking operations

For operations such as read and write, the default operation is blocking, with the following features:

* If a process calls read but has no data to read, it must block. The process is woken up when the data arrives and returns the data to the caller, even if the number of data is less than that specified by the count parameter.

* If a process calls write but there is no space in the buffer, the process must block and must sleep on a different wait queue than the reading process. When some data is written to the hardware device to free up part of the output buffer, the process is awakened and the write call is successful.

Sometimes we want to change this 1 feature to be non-blocking, so that the read/write methods return immediately whether or not the device has data to read or write.

If you want to set a file to be non-blocking, set filp- > The O_NONBLOCK flag for f_flags. When working with non-blocking files, applications calling the stdio function must be very careful because it is easy to mistake a non-blocking return for EOF, so you must always check errno.

(4) Asynchronous notification

a. The role of asynchronous notifications

Most of the time a combination of blocking and non-blocking operations and the select method can be used to query devices effectively, but sometimes this technique is not as efficient. Asynchronous notification (asynchronous notification) is required in the face of some random or infrequent situations, such as typing CTRL+C via the keyboard.

b. How do user-space programs start asynchronous notifications

To start the asynchronous notification mechanism for a file, the user program must perform two steps:

01. Specify 1 process as "owner (owner)" of the device file. When a process executes the F_SETOWN command using the fcntl system call, the process ID number belonging to the main process is saved in filp- > f_owner. This 1 step is required to let the kernel know who to notify. 02. In order to actually start the asynchronous notification mechanism, the user program must also set the FASYNC flag in the device, which is done through the fchtl command F_SETFL. After performing these two steps, the device file can request to send an SIGIO signal when new data arrives. The signal is sent to the file- store > Processes in f_owner (process group if negative). Not all devices support asynchronous notifications, and applications often assume that only sockets and terminals have asynchronous notification capabilities.

(5) How to implement asynchronous notification in the driver

a. User space operations corresponding to the kernel

01. When F_SETOWN is set, for file- > f_owner assignment 02. When F_SETFL is executed to start FASYNC, the driver's fasync method is called. As long as filp - > This method is called when the FASYNC flag in f_flags (cleared by default when the file is opened) changes. When the data arrives, the kernel sends an SIGIO signal to all processes registered as asynchronous notifications

b. Adds a pointer to fasync_struct to the device structure

In this structure < linux/fs.h > In the definition:


struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
};

c. Drives the two functions to be invoked

These two functions are in < linux/fs.h > In a statement.

Defined in /fs/ fcntl.c.

The prototype is as follows:

01. int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa); 02. void kill_fasync(struct fasync_struct **fa, int sig, int band);

When the FASYNC flag for an open file is modified, fasync_helper is called to add or remove the file from the list of related processes, and kill_fasync notifies all related processes when the data arrives.

Example d.

01. Define fasync_struct dynamic data structure in the device type


struct my_pipe {
  struct fasync_struct *async_queue; /*  Asynchronous read structure  */
......
};

The fasync function in the driver calls fasync_helper


int my_fasync(fasync_file fd, struct file *filp, int mode)
{
  my_pipe *dev = filp->private_data;
  return fasync_helper(fd, filp, mode, &dev->async_queue);
}

03. Call kill_fasync when asynchronous notification criteria are met

The asynchronous notification is for 1 read process, so kill_fasync is sent using write.

Call kill_fasync to signal SIGIO to all processes in the asynchronous queue async_queue registered on the device.


ssize_t my_write(struct file *filp, const char *buf, size_t count,
        loff_t *f_pos)
{
......
if (dev->async_queue)
    kill_fasync(&dev->async_queue, SIGIO, POLL_IN); 
    ......
}

The fasync method must be called when the file is closed

The fasync method must be called when the file is closed in order to remove the file from the list of active asynchronous readers.

Call in release: scull_p_fasync(-1, filp, 0);

conclusion


Related articles: