Linux hardware driver programming principles

Source: Internet
Author: User

This article describes in detail how to write a hardware driver in Linux, how to specify kernel routines that will be called, how to initialize the driver, and how to allocate memory. You must have some knowledge about the Linux operating system. I will not go into details here. Okay. Here is a brief introduction to the device driver. As the name implies, drivers are used to control computer peripherals. Linux abstracts all peripheral devices into byte sequences and represents these devices in the form of files. Let's take a look at the Linux I/O subsystem (figure 1 ).


Figure 1 Linux I/O subsystem

From the figure, we can see that the kernel is closely surrounded by hardware. The kernel is a combination of some software packages that can directly access the system hardware, including processors, memory and I/O devices. The user process communicates with the kernel through the user service provided by the kernel, thus indirectly controlling the system hardware.

Figure 2 shows the details of these actions.


Figure 2 user-level, kernel-level, and hardware-level communication

The figure shows that user-level programs use standard system calls provided by the kernel to communicate with the kernel. These system calls include: open (), read (), write (), ioctl (), close () and so on.

Linux Kernel is an organic whole. Every user process is running as if there is a copy of the kernel. Every time a user process uses a system call, the running mode is automatically changed from user-level to kernel-level, in this case, the process runs in the address space of the kernel.


Figure 3 Linux I/O subsystem

The Linux kernel uses "device-independent" I/O subsystems to serve all devices. Each device provides a standard interface to the kernel to hide its features as much as possible. Figure 3 illustrates how a user program uses basic system calls to read data from devices and store the data in a buffer. We can see that every time a system call is used, the kernel is switched to the corresponding device driver routine to manipulate the hardware.

Every device in Linux looks like a file, which is stored in the/dev directory and called a "special file" or "device node ". You can use ls-l/dev/lp * to get the following output:

Crw-rw 1 root 6, 0 1_l 23 1994/dev/lp0

This line of output indicates that lp0 is a character device (the first character in the attribute field is 'C'), the master device number is 6, and the second device number is 0. The master device number is used to indicate to the kernel the type of the driver represented by this device node (for example, a device with the master device number being 3 is an IDE disk driver, the block device with the primary device number 8 is a SCSI disk driver). Each driver is responsible for managing several hardware instances it drives. These hardware instances are represented by the secondary device number (for example: the SCSI disk with the sub-device number 0 represents the entire "first" SCSI disk, and the disk with the sub-device number 1 to 15 represents the 15 partitions on the SCSI disk ).

Now you should have some knowledge about Linux devices. Next we can start with the question "device driver ".

A device driver is an I/O device software interface consisting of related subroutines and data in the kernel. Whenever the kernel realizes that it wants to perform special operations on a device, it calls the corresponding driver routine. This transfers the control from the user process to the driver routine. After the driver routine is completed, the control is returned to the user process. Figure 5 shows the above process.

Figure 5 Functions of the device driver

Each device driver has the following features:

L has a complete set of routines for communicating with hardware devices and provides a set of standard software interfaces to the operating system;

L a self-contained component that can be dynamically called and removed by the operating system;

L can control and manage data streams between user programs and physical devices.

Next, let's take a look at character devices and Block devices. They are two main peripheral devices in Linux. A common disk is a block device, while a terminal and a printer are character devices. Block devices are accessed by user programs through system buffering. In particular, there is no need for the system memory allocation and management process to act as a data transmitter who reads and writes data from peripherals. In contrast, character devices communicate directly with user programs, and the two do not seem to have a buffer zone. The Transmission Control Mechanism of Linux will correctly manipulate peripherals such as memory and disk to obtain data according to the needs of the user program. In Linux, the character drive is saved in the/usr/src/linux/drivers/char directory. The following describes how to develop the driver for character devices.

First, let's take a look at the Linux kernel programming environment. We know that each Linux User process runs in an independent system space and is isolated from the system zone and other user processes. This protects the running environment of a user process to avoid being damaged by other user processes. In this case, the device drivers run in the kernel mode, which has a great degree of freedom. These device drivers are assumed to be correct and reliable. They are part of the kernel and can handle system interrupt requests and access peripheral devices, at the same time, they effectively process interrupt requests so that the system scheduler can maintain a balance between system requirements. Therefore, the device driver can use the system zone out of system restrictions, such as the system buffer zone.

A device driver includes both the interrupt and synchronization areas. The interrupt area is driven by real-time events and device interruptions. The synchronization area is the remainder of the device and processes synchronization events. Therefore, when a device needs software services, it will initiate an "interruption", and then interrupt the processor to get the cause of the interruption and perform the corresponding action at the same time.

A Linux Process may wait until the event occurs. For example, a process may wait for information written to the hardware device to arrive. One of the methods is that the process can use sleep () and wakeup () system calls. The process first puts itself in sleep state, waiting for the arrival of the event. Once the event occurs, the process can be awakened. For example, the interruptible_sleep_on (& dev_wait_queue) function sleep the process and adds the process Number of the process to the process sleep list dev_wait_queue. Once the device is ready, the device sends an interruption, as a result, the corresponding routines in the device driver program are called. This driver routine sends a wake-up process signal after processing certain device requirements. Generally, wake_up_interruptible (& dev_wait_queue) is used) function, which can wake up all processes in the list shown in dev_wait_queue.

Note that if two or more processes share some public data zones, we must regard them as critical partitions, which ensure mutual access to public data between processes. In Linux, we can use cli () and sti () kernel routines to deal with this mutual exclusion. When a process accesses the critical section, it can use cli () to disable interruption, when leaving, use sti () to open the interrupt again, as shown in the following code:

Cli ()

Critical Section

Sti ()

In addition to the above, we also need to understand the concept of virtual file system switching (VFS.


Figure 6 Virtual File System Switching

The "file operation structure" in Figure 6 is defined in the/usr/include/linux/fs. h file, which contains the list of functions in the driver. The initialization routine xxx_init () in the figure registers "file operation structure" based on the VFS and the master device Number of the device ".
The following are the support functions of some device drivers (For details, refer to the Linux Programming Manual and use the man command ):

Add_timer ()

If the time is too long, the function execution can be triggered;

Cli ()

Disable interruption to prevent interruption capture;

End_request ()

Executed when a request is completed or revoked;

Free_irq ()

Releases an interrupt request previously captured by request_irq () and irqaction;

Get_fs *()

Allow a device driver to access user zone data (a memory zone that does not belong to the kernel );

Inb (), inb_p ()

Read one byte from a port, where inb_p () will be blocked until the byte is obtained from the port;

Irqaction ()

An error occurred while registering;

IS _ * (inode)

Test whether inode is on a mounted file system;

Kfree *()

Put the memory area previously allocated by kmalloc;

Kmalloc ()

Allocate a large memory area larger than 4096 bytes;

MAJOR ()

Returns the number of the primary device;

MINOR ()

Returns the number of the next device;

Memcpy _ * fs ()

Copy a large block of memory between the user zone and the kernel zone;

Outb (), outb_p ()

Write a byte to a port, where outb_p () is blocked until the writing is successful;

Printk ()

The printf () version used by the kernel;

Put_fs *()

Allow the device driver to write data to the user zone;

Register _ * dev ()

Register a device in the kernel;

Request_irq ()

Apply for an IRQ interrupt request from the kernel. If yes, install an interrupt request processor;

Select_wait ()

Add a process to the select_wait queue;

* Sleep_on ()

Sleep the process to wait for the event to arrive, and add the wait_queue entry point to the list to wake up the process when the event arrives;

Sti ()

Corresponds to cti () to resume interruption capture;

Sys_get *()

System Call to obtain information about the process;

Wake_up *()

Wake up the previous * sleep_on () sleep process;

Linux User processes cannot directly access the system's physical memory. Each user process has its own memory space (the user's virtual address space starts with the virtual 0 address ). The kernel also has its own memory space-system virtual address space. Whenever you use the system to call read () or write (), the device driver copies data between the kernel address space and the user program address space. Many Linux routines, such as memcpy _ * fs () and put_fs * (), allow device drivers to transmit data across the "user-System" boundary. In addition, data can be bytes, words, or data blocks of any length. For example, memcpy_fromfs () can transmit data blocks of any length from the user's memory space to the device, while get_fs_byte () transfers only one byte from the user's memory space; the same memcpy_tofs () and put_fs_byte (), but they write data to the user's memory.

However, data transmission between the kernel-accessible memory space and the device itself depends on the computer. Some computers need some special CPU input and output commands to do this, which is usually called DMA (Direct Memory Access ). Another solution is to use memory ing I/O. Generally, the system provides I/O functions, such as inb () and outb () to read from the I/O address (port) and output a single byte to the I/O address respectively. You can use the following statement:

Unsigned char inb (int port)

Outb (char data, int port)

Now let's take a look at the basic structure of the character device driver. As shown in figure 6, the xxx_write () Routine polls whether the device is ready to receive data. If the device is ready, the string of the specified length is sent from the user memory space to the character device. In addition, you can also use the interrupt to notify the device whether the device is ready, so that the program does not need to wait for polling, thus improving the CPU utilization. Xxx_table [] is a structured array that contains many member variables, including xxx_wait_queue and bytes_xfered (both of which are used for read/write operations ). Xxx_open () uses request_irq () or irqaction () to call the xxx_interrupt () routine.

In order for the device driver to be correctly initialized, The xxx_init () Routine must be called whenever the system starts. To ensure this operation, you need to add the statement mem_start = xxx_init (mem_start); to the end of the chr_drv_init () function in the/usr/src/linux/driver/char/mem. c file. The next step is to install the driver to the kernel (note: the driver of the character device can only be installed in/usr/src/linux/drivers/char. library ).

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.