Basic concepts of Linux Device Drivers
A system call is an interface between the operating system kernel and applications, and a device driver is an interface between the operating system kernel and the machine hardware. The device driver shields the application from hardware details. In this way, the hardware device is only a device file, and the application can operate the hardware device like a common file. The device driver is part of the kernel and implements the following functions:
1. initialize and release the device. 2. Transmit data from the kernel to the hardware and read data from the hardware. 3. Read the data that the application sends to the device file and send back the data requested by the application. 4. Detect and handle device errors. In Linux, there are two main types of device files: character devices and Block devices. The difference is that the actual hardware I/O occurs immediately after a read/write request is sent to the character device. A block device does not use a system memory as a buffer. When a user process requests a device to meet the user's requirements, the requested data is returned. If not, you can call the request function to perform actual I/O operations. Block devices are designed for disks and other slow devices to avoid excessive CPU time consumption.
The user process deals with the actual hardware through the device file. Each device file has its File Attribute (C/B), indicating whether it is a character device or a block device. Each file has two device numbers. The first is the master device number, which identifies the driver; the second is the secondary device number, which identifies different hardware devices that use the same device driver, for example, if there are two floppy disks, you can use the secondary device number to distinguish them. The master device number of the device file must be the same as the master device number applied by the device driver during registration. Otherwise, the user process will not be able to access the device driver. Note: When a user's process calls the driver, the system enters the core State and is no longer preemptible. That is to say, the system must return the sub-function of the driver before other work can be performed. If the driver is in an endless loop, the entire kernel system will crash and only restart the machine.
Linux Device Driver Design ExampleIn Linux, the device driver is an important part of the operating system kernel, and a standard abstract interface is established with the hardware device. With this interface, you can open, close, read/write, and perform other operations on hardware devices just like you are processing common files. By analyzing and designing device drivers, you can gain a deep understanding of the Linux system and perform system development. This article uses a simple example to describe the design of the device driver program. 1. program list # ifndef _ KERNEL __
# DEFINE _ KERNEL _ // compile by kernel module
# Endif
# Ifndef Module
# Define module // compile the device driver module
# Endif
# Define device_name "mydev"
# Define openspk 1
# Define closespk 2 // necessary header file
# Include <Linux/module. h> // same as kernel. H, the most basic kernel module header file
# Include <Linux/kernel. h> // same as module. H, the most basic kernel module header file
# Include <Linux/sched. h> // contains the macro used to check the correctness.
# Include <Linux/fs. h> // header files required by the File System
# Include <ASM/uaccess. h> // contains
FUNCTION macro # include <ASM/IO. h> // I/O access
Int my_major = 0; // master device number
Static int device_open = 0;
Static char message [] = "this is from device driver ";
Char * message_ptr;
Int my_open (struct inode * inode, struct file * file)
{// This function is called whenever an application opens a device with open
Printk ("ndevice_open (% P, % P) N", inode, file );
If (device_open)
Return-ebusy; // It can only be opened by one application at the same time
Device_open ++;
Mod_inc_use_count; // uninstall prohibited when the device is on
Return 0;
}
Static void my_release (struct inode * inode, struct file * file)
{// This function is called whenever the application closes the device with close
Printk ("ndevice_release (% P, % P) N", inode, file );
Device_open --;
Mod_dec_use_count; // The reference count minus 1.
}
Ssize_t my_read (struct file * F, char * Buf, int size, loff_t off)
{// This function is called whenever the application accesses the device using read
Int bytes_read = 0;
# Ifdef debug
Printk ("nmy_read is called. user buffer is % P, size is DN", Buf, size );
# Endif
If (verify_area (verify_write, Buf, size) =-efault)
Return-efault;
Message_ptr = message;
While (size & * message_ptr)
{
If (put_user (* (message_ptr ++), Buf ++) // write data to the user space
Return-einval;
Size --;
Bytes_read ++;
}
Return bytes_read;
}
Ssize_t my_write (struct file * F, const char * Buf, int size, loff_t off)
{// This function is called whenever the application accesses the device with write
Int I;
Unsigned char UC;
# Ifdef debug
Printk ("nmy_write is called. user buffer is % P, size is DN", Buf, size );
# Endif
If (verify_area (verify_write, Buf, size) =-efault)
Return-efault;
Printk ("ndata below is from user program: N ");
For (I = 0; I <size; I ++)
If (! Get_user (UC, Buf ++) // read data from the user space
Printk ("% 02x", UC );
Return size;
}
Int my_ioctl (struct inode * inod, struct file * F, unsigned int arg1,
Unsigned int arg2)
{// This function is called whenever the application accesses the device with IOCTL
# Ifdef debug
Printk ("nmy_ioctl is called. parameter is % P, size is % DN", arg1 );
# Endif
Switch (arg1)
{
Case openspk:
Printk ("Nnow, open PC's speaker. N ");
Outb (INB (0x61) | 3, 0x61); // open the computer's speaker
Break;
Case closespk:
Printk ("Nnow, close PC's speaker .");
Outb (INB (0x61) & 0xfc, 0x61); // close the computer speaker
Break;
}
}
Struct file_operations my_fops = {
Null,
/* Lseek */
My_read,
My_write,
Null,
Null,
My_ioctl,
Null,
My_open,
My_release,
/* Nothing more, fill with nulls */
};
Int init_module (void)
{// The system automatically calls this function whenever a device driver is assembled.
Int result;
Result = register_chrdev (my_major, device_name, & my_fops );
If (result <0) return result;
If (my_major = 0)
My_major = result;
Printk ("nregister OK. Major-number = % DN", result );
Return 0;
}
Void cleanup_module (void)
{// The system automatically calls this function whenever the device driver is uninstalled.
Printk ("nunloadn ");
Unregister_chrdev (my_major, device_name );
}
2. Device Driver Design
Linux devices include character devices, Block devices, and network devices. Character devices do not need to buffer but directly
Read/write devices, such as serial ports, keyboards, and mouse. In this example, the character device driver is used to access Block devices.
Buffer is usually required for reading and writing, such as disk devices, in units of data blocks.
A special device that can be accessed by text.
1) interfaces of device drivers and kernels and Applications
Regardless of the type of device, Linux maintains a special device control block in the kernel
Backup driver interface. There is an important data structure in the control blocks of character devices and Block devices.
File_operations, which contains various types of hardware devices that the driver provides to applications to access.
Method, which is defined as follows (see fs. h ):
Struct file_operations {
Loff_t (* llseek) (struct file *, loff_t, INT );
// Response to the function pointer called by lseek in the Application
Ssize_t (* read) (struct file *, char *, size_t, loff_t *);
// Response to the READ function pointer in the Application
Ssize_t (* write) (struct file *, const char *, size_t, loff_t *);
// Response to the write call function pointer in the Application
INT (* readdir) (struct file *, void *, filldir_t );
// Response to the function pointer called by readdir in the Application
Unsigned int (* poll) (struct file *, struct poll_table_struct *);
// Response to the function pointer called by select in the Application
INT (* IOCTL) (struct inode *, struct file *, unsigned int, unsigned long );
// Response to the function pointer called by IOCTL in the Application
INT (* MMAP) (struct file *, struct vm_area_struct *);
// Response to the MMAP function pointer in the Application
INT (* open) (struct inode *, struct file *);
// Response to the open call function pointer in the Application
INT (* flush) (struct file *);
INT (* release) (struct inode *, struct file *);
// Response to the function pointer called by close in the Application
INT (* fsync) (struct file *, struct dentry *);
INT (* fasync) (INT, struct file *, INT );
INT (* check_media_change) (kdev_t Dev );
INT (* revalidate) (kdev_t Dev );
INT (* Lock) (struct file *, Int, struct file_lock *);
};
In most cases, you only need to write service functions for a few methods in the above structure, and set others to null.
Each device driver that can be assembled must have two functions: init_module and cleanup_module. when the device is loaded and detached, the kernel automatically calls these two functions. In init_module, in addition to checking and initializing hardware devices, you must also call the register _ * function to register the devices to the system. In this example, register_chrdev is used for registration.
Register_blkdev and register_netdev. The main function of register_chrdev is to set
The slave name and structure file_operations are registered in the system's device control block.
2) data exchange with Applications
Because the device drivers work in the kernel storage space, they cannot exchange data with applications simply using methods such as "=" and "memcpy. The Methods put_user (x, PTR) and
Get_user (x, PTR) is used for data exchange between the kernel space and the user space. The value x is based on the pointer.
For more information about how to determine the PTR type, see the my_read and my_write functions in the source code.
3) interfaces with hardware devices
Linux provides a simple method for device drivers to access the I/O port, hardware interruption, and DMA. the header files are Io. H, IRQ. H, and DMA. h. Due to spoke limitations, this example only involves access to the I/O port. Linux provides the following methods to access the I/O Ports: INB (), inw (), outb (), outw (), inb_p (), inw_p (), and outb_p () and outw_p. Note that before using the port, the device driver should first use check_region () to check the port usage. If the specified port is available, then use request_region () to register with the system. The header files of check_region () and request_region () are ioport. h.
4) Memory Allocation
The device driver is part of the kernel and cannot use virtual memory.
Kmalloc () and kfree () to apply for and release the kernel storage space. Kmalloc () has two parameters, the first
The amount of memory to be applied. In earlier versions, this quantity must be an integer power of 2, such as 128 and 256.
. For usage of kmalloc () and kfree (), refer to the malloc. h and slab. C Programs in the kernel source program.
.
3. Program compilation and access
In this example, Linux 2.2.x.x can be compiled and run successfully. Compile with the following command line:
Gcc-wall-O2-C mydev. c. In this command line, the parameter-wall tells the Compilation Program to display a warning message. The parameter-O2 is about the code optimization settings. Note that the kernel module must be optimized; parameter-C requires only compilation and assembly, without connection. After compilation, the mydev. o file is generated. You can input insmod mydev. O to load this program. If the Assembly is successful, the following message is displayed: Register OK. Major-number = xx. The lsmod command shows that the module is assembled into the system. To access this module, run the mknode command to create a device file. The following applications can access the created device files.
# Include <stdio. h>
# Include <fcntl. h>
# Include <sys/IOCTL. h>
# Define device_name mydev
# Define openspk 1
# Define closespk 2
Char Buf [128];
Int main (){
Int F = open (device_name, o_rdrw );
If (F =-1) return 1;
Printf ("nhit enter key to read device ...");
Read (F, Buf, 128); printf (BUF );
Printf ("nhit enter key to write device ...");
Write (F, "test", 4 );
Printf ("nhit enter key to open PC's speaker ...");
IOCTL (F, openspk );
Printf ("nhit enter key to close PC's speaker ...");
IOCTL (F, closespk );
Close (f );
}
Precautions for Linux Device Drivers
1. I/O Ports and hardware are inseparable from I/O ports. In Linux, the operating system does not block I/O Ports. That is to say, any driver can operate on any I/O port, which can easily cause confusion. Each driver should avoid misuse of ports by itself. There are two important kernel functions to ensure that the driver can do this. The check_region (INT io_port, int off_set) function checks the system's I/O table to see if other drivers are occupying a certain I/O port. Parameter 1: The base address of the I/O port. Parameter 2: The range occupied by the I/O port. Return Value: 0-not in use; non-0-already in use. Request_region (INT io_port, int off_set, char * devname) if the I/O port is not occupied, you can use it in the driver. Before use, you must register with the system to avoid being occupied by other programs. After registration, you can see the registered I/O port in the/proc/ioports file.
Parameter 1: The base address of the I/O port. Parameter 2: The range occupied by the I/O port. Parameter 3: The device name that uses this I/O address. After registering the I/O port, you can use functions such as INB () and outb () to access it with confidence. 2. The Memory Operation dynamically opens up the memory in the device driver, instead of using malloc, but kmalloc, or directly apply for a page using get_free_pages. Kfree or free_pages are used to release the memory. 3. Just like handling I/O ports, to use an interrupt, you must first register with the system. Int request_irq (unsigned int IRQ, void (* handle) (INT, void *, struct pt_regs *), unsigned int long flags, const char * Device); IRQ: the application interruption. Bandle: the pointer of the interrupt processing function. Flags: sa_interrupt-a fast request interruption; 0-normal interruption. Device: device name. If the registration is successful, 0 is returned, and the request interruption is displayed in the/proc/interrupts file. 4. dialog with the device file the driver provides interfaces for device operations, and implements the basic functions required for basic operations in the program, the user program indirectly operates the device by accessing the device file. The Linux system provides the ioctl () function to conveniently implement this operation. Int IOCTL (int fd, int cmd,...); FD: the object identifier returned by the open function when the user program opens the device. CMD: the user program controls the device. Ellipsis: Some Supplementary parameters, generally more than one, have or are not related to the meaning of CMD. Note: The control command selection of the CMD device should select the appropriate control word according to the standard control word provided in the Linux document. If the selection is inappropriate, it will cause conflicts with other devices in the system.