Device driver under Linux

zhaozj2021-02-08  218

Third, the equipment driver under the UNIX system 3.1, the basic structure of the device driver under UNIX In the UNIX system, in terms of the user program, the device driver hides the specific details of the device, providing a consistent interface for a variety of different devices. Generally, it is to map the device into a special device file, and the user program can operate on this device file like other files. UNIX supports two standard interfaces for hardware devices: block special equipment files and character special equipment files, the device accessed by the block (character) special device file is called a block (character) device or a block (character) device interface. The block device interface only supports block-oriented I / O operations, all I / O operations are performed in the I / O buffer in the kernel address space, which can support almost any length and any position I / O request, ie Provide random access. The character device interface supports the character-oriented I / O operation, which is not responsible for managing your own buffer structure without the fast cache of the system. The character device interface only supports sequential access, generally cannot perform an I / O request of any length, but to limit the length of the I / O request must be a multiplication of the basic blocks required by the device. Obviously, the serial card driven by this program can only provide sequential access, which belongs to a character device, so the following discussion is only related to the characteristic device interface when the two devices are different. The device is identified by a master number and a secondary device number. The main device number uniquely identifies the device type, that is, the device driver type, which is the index of the device entry in the block device table or character device table. The secondary device number is only interpreted by the device driver, typically used to identify the device involved in the I / O request in several possible hardware devices. The device driver can be divided into three main components: (1) Automatic configuration and initialization subroutine, which is responsible for detecting whether the hardware device to be driven does not work properly. If the device is normally, the software status of this device and its related, device driver need is initialized. This part of the driver is called once when it is initialized. (2) Serving the subroutine of the I / O request, also known as the upper half of the driver. Calling this part is due to the result of the system call. This part of the program is executing, the system still thinks that the process of being called belongs to the same process, but the user state has become a core state, with the operating environment of the user program that is called by this system, so it can be called SLEEP. () Wait with the function of the process running environment. (3) Interrupt service subroutine, also known as the lower half of the driver. In the UNIX system, the interrupt service subroutine of the device driver is not called directly from the interrupt vector table, but is received by the UNIX system to receive the hardware interrupt, and then the interrupt service subroutine is called. The interrupt can be generated when any process is running, so when the interrupt service program is called, it cannot rely on any process, and any function related to the process running environment cannot be called. Since the device driver typically supports several devices of the same type, it is generally included with one or more parameters when the system calls the interrupt service subroutine, and the device that uniquely identifies the request service. In the system, the access of the I / O device is performed by a set of fixed entry points, which is provided by the device driver of each device. In general, the character device driver provides the following entry points: (1) Open entry point. Open the device to prepare the I / O operation. Opening the character special equipment file, the Open entry point of the device is called. The Open subroutine must prepare the I / O operation to be made, such as clearing the buffer. If the device is exclusive, only one program can access this device at the same time, then the Open subroutine must set some flags to indicate that the device is busy. (2) Close entry point. Close a device. When the device is used last time, the Close subroutine is called. Excise equipment must mark the device again. (3) READ entry point. Read the data from the device.

For I / O operations with buffers, it is generally read from the buffer. The read subroutine will be called for the character special device file. (4) WRITE entry point. Write data on the device. For I / O operations with buffers, it is generally written to the buffer in the buffer. Write subroutine is called to write a Write subroutine to write a word special device file. (5) IOCTL entry point. Perform a read and write operations. (6) SELECT entry point. Check the device to see if the data is readable or if the device can be used to write data. The SELECT system call uses the SELECT entry point when checking the file descriptor related to the device special file. If the device driver does not provide one of the above entry points, the system will replace it with the default subroutine. There are also other entry points for different systems. 3.2, the device driver under the Linux system is specifically to the Linux system, the set of entry points provided by the device driver are described by a structure to the system, this structure is defined as: #include struct file_operations {INT (* Lseek) (Struct File * Filp, OFF_T OFF, INT POS); INT (* Read) (Struct File * Filp, Char * BUF, INT Count); int * Write) (Struct Inode * Inode, Struct File * Filp, Char * BUF, INT Count); INT (* READDIR) (Struct File * Filp, Struct Dirent * Dirent, Int Count); int (* select) (struct inode * inode, struct file * filp, int sel_type, select_table * wait); int (* ioctl) (struct inode * inode, struct file * filp, unsigned int cmd, unsigned int arg); int (* mmap (void);

INT (* open * inode, struct file * inode, struct file * inf); int (* fsync) (Struct Inode * Inode, Struct File * Filp) ;}; Struct Inode provides information about special device file / dev / driver (assuming this device named driver), it is defined as: #include struct inode {dev_t i_dev; unsigned long i_ino; / * Inode number * / umode_t i_mode; / * Mode of the file * / nlink_t i_nlink; uid_t i_uid; gid_t i_gid; dev_t i_rdev; / * Device major and minor numbers * / off_t i_size; time_t i_atime; time_t i_mtime; time_t i_ctime; unsigned long i_blksize; unsigned long i_blocks; struct inode_operations * i_op; struct super_block * i_sb; struct wait_queue * i_wait; struct file_lock * i_flock; struct vm_area_struct * i_mmap; struct inode * i_next, * i_prev; struc t inode * i_hash_next, * i_hash_prev; struct inode * i_bound_to, * i_bound_by; unsigned short i_count; unsigned short i_flags; / * Mount flags (see fs.h) * / unsigned char i_lock; unsigned char i_dirt; unsigned char i_pipe; unsigned char i_mount; unsigned char i_seek; unsigned char i_update; union {struct pipe_inode_info pipe_i; struct minix_inode_info minix_i; struct ext_inode_info ext_i; struct msdos_inode_info msdos_i; struct iso_inode_info isofs_i; struct nfs_inode_info nfs_i;} u;};

Struct file is mainly used to use the device driver corresponding to the file system. Of course, other device drivers can also use it. It provides information about the file opened, defined as: #include struct file {mode_t f_mode; dev_t f_rdev; / * needed for / dev / tty * / off_t f_pos; / * curr. Posn in file * / unsigned short f_flags; / * The flags arg passed to open * / unsigned short f_count; / * Number of opens on this file * / unsigned short f_reada; struct inode * f_inode; / * pointer to the inode struct * / struct File_Operations * f_op; / * Pointer to the FOPS STRUCT * /};

In the structural file_operations, the entry point position provided by the device driver is: (1) Lseek, the position of the mobile file pointer, obviously can only be used to random access. (2) READ, read operation, parameter buf is a buffer that stores read results, and count is the length of the data to be read. The return value is an error indicating an error in the reading operation, otherwise returns the number of bytes actually read. For characters, the number of bytes required to be read and the number of actual read bytes returned must be inode-> i_blksize multiple. (3) WRITE, write operation, similar to read. (4) READDIR, obtain the next directory entry point, only the device driver related to the file system is used. (5) SELEC, select the selection, if the driver does not provide a SELECT entry, the SELECT operation will consider that the device is ready for any I / O operation. (6) IOCTL, other operations other than read, write, parameter cmd is a custom command. (7) MMAP, used to map the contents of the device to the address space, generally only the block device driver is used. (8) Open, open the device to prepare I / O operations. Returning 0 indicates that the opening is successful, and the return negative number is failed. If the driver does not provide an Open entry, as long as the / dev / driver file is considered to open success. (9) Release, ie the Close operation. The entry point provided by the device driver is registered to the system when the device driver is initialized so that the system is called when appropriate. In the Linux system, the character-type device driver is registered to the system by calling Register_chrdev. Register_chrdev is defined as: #include #include int register_chrdev (unsigned int major, const char * name, struct file_operations * fops); where Major is to the device driver The main device number of the application is dynamically assigned a master number to this driver for 0. Name is a device name. FOPS is an explanation of the entry point for each call. This function returns 0 means success. Returns -Einval means that the main device number of the application is illegally, generally said that the main device number is greater than the maximum device number allowed by the system. Returns -ebusy indicates that the main device number of the applied is being used by other device drivers. If it is dynamically assigned a master number, this function will return the assigned primary device number. If the register_chrdev operation is successful, the device name will appear in the / proc / defices file. The initialization part is generally responsible for applying system resources to the device driver, including memory, interrupts, clocks, I / O ports, etc., which can also be applied in Open subroutines or other places. When these resources are not used, they should be released to facilitate the sharing of resources. In the UNIX system, the processing of the interrupt is part of the system core, so if the device is interrupted between the device and the system, the driver of the device must be part of the system. The device driver applies for an interrupt by calling the request_irq function, and the interrupt is released by Free_IRQ.

They are defined as: #include int REQUEST_IRQ (Unsigned INT IRQ (* Handler) (INT IRQ, Void dev_id, struct pt_regs * regs), unsigned long flags, const char * device, void * DEV_ID); void free_irq (unsigned int IRQ, VOID * DEV_ID); parameter IRQ indicates the hardware interrupt number of the required application. Handler is invoked by the interrupt processing subroutine registered to the system, when the interrupt is invoked by the system, the parameter IRQ belongs to the interrupt number, and the dev_id tells the system to tell the system, and the register is when the register is when the interrupt occurs. Device is a device name and will appear in the / proc / interface. FLAG is an option when the application, which determines some of the features of the interrupt handler, where is the interrupt handler is a quick handler (setting sa_interrupt in the FLAG) or slow handler (not set sa_interrupt), fast handler operation When all interrupts are blocked, while the slow handler is running, other interrupts are not blocked except for the interrupted interrupt. In the Linux system, the interrupt can be shared by different interrupt handler, which requires each handler that shares this interrupt in the FLAGS in the FLAGS in the FLAGS, which distinguishes between DEV_ID. If the interrupt is exclusive by a handler, the dev_id can be NULL. Request_irq returns 0 means success, return -inval to IRQ> 15 or handler == NULL, return -ebusy indicates that the interrupt has been occupied and cannot be shared. As part of the system core, the device driver is not called Malloc and Free during application and release, and in order to call Kmalloc and KFree, they are defined as: #include void * kmalloc (unsigned int Len, int priority; void kfree (void * obj); parameter LEN is the number of bytes you want to apply, OBJ is the memory pointer to be released. Priority is the priority of allocation memory operations, ie, how to operate when there is enough free memory, generally using GFP_kernel. Unlike interrupts and memory, using an I / O port without an application does not cause an exception, it will not cause an error such as "Segmentation Fault". Any process can access any I / O port. At this point, the system cannot guarantee that the operation of the I / O port does not have conflicts, and it will even cause the system to crash. Therefore, before using the I / O port, you should also check if this I / O port has other programs in use. If not, mark this port as being used, release it after use.

Thus the need to use several functions as follows: int check_region (unsigned int from, unsigned int extent); void request_region (unsigned int from, unsigned int extent, const char * name); void release_region (unsigned int from, unsigned int extent); The parameter when calling these functions is: from the start address of the I / O port applied; EXTENT is the number of ports starting from the FROM; name is a device name, will appear in the / proc / Ioports file. Check_region returns 0 indicates that I / O port is idle, otherwise it is being used. After the I / O port is applied, you can access the I / O port: #include inline unsigned int inb (unsigned short port); inline unsigned int inb_p (unsigned short port) Inline void outb (char value, unsigned short port); inline void outb_p (char value, unsigned short port); in which INB_P and OUTB_P are inserted into certain delays to accommodate some slow I / O ports. In the device driver, the timing mechanism is generally required. In the Linux system, the clock is taken by the system, and the device driver can apply to the system. System calls related to the clock include: #include #include void add_timer (struct timer_list * timer; int del_timer); inline void init_timer (Struct Timer_List) * timer); defined struct timer_list as: struct timer_list {struct timer_list * next; struct timer_list * prev; unsigned long expires; unsigned long data; void (* function) (unsigned long d);}; where expires is to execute function time. The system core has a global variable Jiffies to represent the current time, usually Jiffies = Jiffies Num when calling add_timer, indicating that the Function is performed after the minimum time interval at NUM system. The minimum time interval of the system is related to the hardware platform used, defining the constant Hz represents the number of minimum time intervals within a second, then Num * Hz represents Num Second. The system timing calls function to a predetermined time, and delete this subroutine from the timed queue, so if you want to execute once every time time, you must call Add_Timer again in the function. Function's parameter d is the DATA item in Timer.

In the device driver, some system functions may also be used: #include #define CLI () __ASM___Volatile__ ("CLI" ::) # Define STI () __ASM____volatile__ ("STI ": :) These two functions are responsible for opening and closing interrupts. #include void memcpy_fromfs (void * to, const void * from, unsigned long n); void memcpy_tofs (void * to, const void * from, unsigned long n); call read, Write in user program When the operation of the process is changed to the core state by the user state, the address space has become the core address space. And the parameter buf in Write is a private address space of the user program, so it is not possible to access the user program's private address space by the above two system functions. Memcpy_fromfs replicates by the user program address space to the core address space, and Memcpy_TOFS is vice versa. The parameter TO is a replicated destination pointer, from the source pointer, n is the number of bytes to be copied. In the device driver, printk can be called to print some debug information, and the usage is similar to the Printf. Printk print information not only appears on the screen, but also records in the file syslog.

转载请注明原文地址:https://www.9cbs.com/read-2996.html

New Post(0)