Quotation from the sky
Linux is a variant of the UNIX operating system. The principles and ideas written under Linux are completely similar to other UNIX systems, but the drivers in the DOS or Window environment have great differences. Design drivers in the Linux environment, simple ideas, convenient operation, and powerful, but support function, can only rely on the function in kernel, some commonly used operations should be written, and debugging is not convenient. I have developed a driver for a multimedia card developed by the laboratory in these weeks, gaining some experience, is willing to share with Linux Fans, please correct.
Some of the following words are primarily derived from KHG, Johnsonm Write Linux Device Driver, Brennan's Guide to Inline Assembly, The Linux AZ, and some information about Device Driver on Tsinghua BBS. These materials have been out of time, some have Some errors, I made corrections according to my own test results.
I. Concept of Linux Device Driver
The system call is an interface between the operating system kernel and the application. The device driver is an interface between the operating system kernel and the machine hardware. The device driver is the details of the hardware for the application, so that the hardware device is in the application. Just a device file, the application can operate on the hardware device like a normal file. The device driver is part of the kernel, which complete the following features:
1. Initialize and release the device.
2. Transfer the data from the kernel to the hardware and read data from the hardware.
3. Read the application to transfer the data for the device file and the data requested by the application.
4. Detect and process errors appearing.
There are two types of primary device file types under the Linux operating system, one is a character device, the other is a block device. The main difference between the character device and block device is: when reading / writing on the character device, actual Hardware I / O is generally immediately, and block devices, it uses a system memory to make buffers, when the user process requests the device to meet the user's requirements, returns the request data, if not, call the request function To perform actual I / O operation. Block equipment is mainly designed for slow devices such as disk, so as not to consume too much CPU time to wait.
It has been mentioned that the user process is to deal with the actual hardware through the device file. Each device file has its file attribute (C / B), indicating that the character device is still strong? Different each file has two Equipment number, the first is the main device number, identifies the driver, the second is from the device number, identifies different hardware devices that use the same device driver, such as two floppy disk, can be used from the device number Distinguish between them. The main device number of the device file must match the main device number requested by the device driver at registration, otherwise the user process will not be able to access the driver.
Finally, it must be mentioned that when the user process calls the driver, the system enters the core state, then it is no longer a predecessor schedule. That is to say, the system must return to the child's subunies to make other work. If your driver is falling into a dead loop, unfortunately, you only restart the machine, then the long fsck.//hehe
When reading / writing, it first checks the contents of the buffer, if the data of the buffer
How to write device drivers under Linux operating systems
Second, the example analysis
Let's write a simplest character device driver. Although it doesn't do anything, it can understand the working principle of Linux device drivers. Enter the machine below, you will get a real device driver. But my kernel is 2.0.34, There may be a problem on the low version of the Kernel, I haven't tested .//xixi#define __no_version__ #include
CHAR KERNEL_VERSION [] = UTS_RELEASE;
This paragraph defines some version information, although it is not very large, but it is indispensable. Johnsonm said that all drivers must include
Since the user process is dealing with hardware through the device file, the operation mode of the device file is nothing more than the system call, such as Open, Read, Write, Close ...., notice, not fopen, fread, but how to call the system And the driver is associated? This requires a very critical data structure:
Struct file_operations {
INT (* SEEK) (Struct Inode *, Struct File *, OFF_T, INT); Struct Inode *, Struct File *, CHAR, INT); INT (* Write) (Struct Inode *, Struct File *, OFF_T, INT; INTDIREN, STRUCT INODE *, STRUCT FILE *, STRUCT DIRENT *, INT); INT (* SELECT) (Struct Inode *, Struct File *, Int, SELECT_TABLE *); int ( * ioctl) (struct inode *, struct file *, unsined int, unsigned long); int (* mmap) (struct inode *, struct file *, struct vm_area_struct *); int (* open) (struct inode *, struct file *); int (* release *, struct file *); int (* fsync) (struct inode *, struct file *); int (* Fasync) (Struct Inode *, Struct File *, int); INT (* check_media_change) (struct inode *, struct file *); int (* revALIDATE) (DEV_T dev);}
The names of each member of this structure correspond to a system call. The user process uses the system call to find the corresponding device driver by the main device number of the device file using the system call, the system calls the main device number of the device file, and then read Take the corresponding function pointer of this data structure, then handed the control power to the function. This is the basic principle of Linux device driver work. Since this is the case, the main job of writing the device driver is to write a subunies and populate the file_Operations Each domain. Quite simple, isn't it?
Let's start writing a subroutine below.
#include
Static int tent_test (struct inode * node, struct file * file, char * buf, int count) {
INT LEFT;
IF (Verify_Write, BUF, Count) == -EFAULT) Return-Efault;
For (left = count; left> 0; left -) {__put_user (1, buf, 1); buf ;} return count;
This function is ready for Read_Test () when the read_test () is called for the read_test (), which writes all the users' buffers. 1.BUF is a parameter for the read call. It is an address of the user's process space. But in Read_Test When called, the system enters the core state. So you can't use the buf address, you must use __put_user (), which is a function provided by the Kernel to transfer data to the user. There are many similar functions. Please refer to. Before copying the data to the user space, you must verify that the BUF is available.
This uses the function verify_area.
Static int WRITE_TIBET (Struct Inode * Inode, Struct File * File, Const Char * BUF, INT Count) {Return Count;}
Static int open_tibet (struct inode * inode, struct file * file) {mod_inc_use_count; return 0;}
Static void release_tibet (struct inode * inode, struct file * file) {mod_dec_use_count;}
These functions are empty. What doesn't do when actually calling occurs, they only provide function pointers for the following structure.
struct file_operations test_fops = {NULL, read_test, write_test, NULL, / * test_readdir * / NULL, NULL, / * test_ioctl * / NULL, / * test_mmap * / open_test, release_test, NULL, / * test_fsync * / NULL, / * test_fasync * / / * Nothing more, fill with nulls * /}; the main body of the device driver can be said to be written. Now embed the driver into the kernel. The driver can be compiled in two ways. One is to compile into kernel, and the other is to compile the module. If you compile into the kernel, increase the size of the kernel, and change the source file of the kernel, and cannot be dynamically uninstalled, it is not conducive to debugging, so recommended Use module mode.
INT init_module (void) {int result;
Result = register_chrdev (0, "Test", & Test_fops);
IF (Result <0) {Printk (kern_info "test: can't get major number / n"); return result;}
IF (Test_major == 0) Test_major = Result; / * Dynamic * / Return 0;}
When you transfer the compiled module to the memory with the insmod command, the init_module function is called. Here, INIT_MODULE has only one thing, that is, register a character device to the character device table of the system. Register_chrdev requires three parameters. The parameter first is the device number you want to obtain. If it is zero, the system will select a device number that is not occupied. The parameter 2 is the device file name, the parameter three is used to register a pointer to the actual function of the actual execution operation.
If the registration is successful, return to the main device number of the device, unsuccessful, return a negative value.
Void cleanup_module (void) {unregister_chrdev (Test_major, "Test");}
When you use the RMMOD to uninstall the module, the Cleanup_Module function is called, which releases the character device TEST posses in the system character device table.
An extremely simple character device can be said to be written, the file name is called Test.c.
Below compilation
$ gcc -o2 -dmodule -d__kernel__ -c Test.c
Get the file test.O is a device driver.
If there are multiple files in the device driver, compile each file to the command line above, then
LD -R file1.o file2.o -o modulename.
The driver has been compiled, and now install it into the system.
$ insmod -f test.o
If the installation is successful, you can see the device TEST in the / proc / defices file, and you can see its main device number.
To uninstall, run
$ RMMOD TEST
Next step to create a device file.
MKNOD / DEV / TEST C MAJOR Minor
c is a character device, and Major is the primary device number, which is seen in / proc / devices.
Use shell command
$ CAT / Proc / Devices | AWK "}" You can get the main device number, you can add the above command line to your shell script.
Minor is from the device number and setting it into 0.
We can now access our drivers through device files. Write a small test program.
#include
Main () {Int Testdev; Int i; char buf [10];
TestDev = Open ("/ dev / test", o_rdwr);
IF (TestDev == -1) {Printf ("cann't open file / n"); exit (0);
Read (TestDev, BUF, 10);
For (i = 0; i <10; i ) Printf ("% d / n", buf [i]);
Close (TestDev);
Compile operation, see if you print all 1?
The above is just a simple demonstration. Really practical drivers are more complicated, to handle problems such as interrupts, DMA, I / O Port. These are truly difficult. Please see the next step, the actual situation is processed.
How to write device drivers under Linux operating systems
Third, some specific problems in the equipment driver
1. I / O Port.
And hardware dealing with I / O port, old ISA equipment often occupies the actual I / O port, under Linux, the operating system does not shield I / O port, that is, any driver can be arbitrary The I / O port operation is easy to cause confusion. Each driver should avoid misuse ports.
There are two important kernel functions to ensure that the driver does this.
1) Check_Region (int io_port, int off_set)
This function looks at the I / O table of the system to see if there are other drivers to occupy a certain paragraph I / O port.
Parameter 1: The base address of the IO port,
Parameter 2: The range of IO ports.
Return value: 0 is not occupied, non-0, has been occupied.
2) Request_Region (int io_port, int off_set, char * devname)
If this I / O port is not occupied, you can use it in our driver. Before use, you must register with the system to prevent occupying by other programs. After registering, you can see the IO port you registered in the / proc / Ioports file.
Parameter 1: The base address of the IO port.
Parameter 2: The range of IO ports.
Parameter 3: Using the device name of this IO address.
After registering the I / O port, you can use INB (), OUTB (), and a letter.
In some PCI devices, the I / O port is mapped to a period of memory, and to access these ports is equivalent to accessing a memory. Regular, we have to get a physical address of a memory. In the DOS environment, the DOS operating system is because I think DOS is not an operating system, it is too simple, too unsafe) Just use the segment: The shift is OK. In Window95, 95DDK offers a VMM call_maplineArtophys to convert linear addresses to physical addresses. But what do you do in Linux? 2. Memory operation
Dynamically open the memory in the device driver, not using Malloc, but kmalloc, or directly applied with GET_FREE_PAGES. Release storage is Kfree, or Free_PAGES. Please note that functions such as kmalloc returns the physical address! Malloc, etc. are linear addresses! About Kmalloc returning is that this person is a bit unclear: Since the conversion from the linear address to the physical address is completed by 386CPU hardware, the number of assembly instructions should be linear addresses, and the driver cannot directly use physical use. Address but linear address. But in fact, Kmalloc is indeed a physical address, and it can also pass it directly to access the actual RAM, I think this can be explained by two explanations, one is in the core state disabled paging, but this seems not realistic; another It is Linux's page directory and page table item design that is just like the physical address is equivalent to the linear address. My thoughts don't know right, please advise.
For the words, it is important to note that Kmalloc can only open up to 128K-16, and 16 bytes are occupied by the page descriptor structure. Kmalloc usage See KHG.
The I / O port of memory maps, registers, or RAMs of the hardware device generally occupy an address space above the f0000000. You cannot access directly in the driver, you want to get the address after re-mapping through the Kernel function VREMAP.
In addition, many hardware require a relatively large continuous memory for DMA transmission. This memory needs to stay in memory and cannot be swapped into the file. But Kmalloc can only open up to 128K memory.
This can be solved by sacrificing some system memory.
The specific approach is: For example, your machine consists of 32M memory, plus MEM = 30M in the launch parameters of lilo.conf, so linux thinks that your machine has only 30m memory, and the remaining 2M has a VREMAP. It is used for DMA.
Keep in mind that the memory released by VREMAP is not used, and the page table will be wasted when you use the unremap.
3. Interrupt treatment
Like the processing I / O port, 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: It is an interrupt to apply.
Handle: Interrupt Processing Function Pointer.
Flags: sa_interrupt requests a quick interrupt, 0 normal interrupt.
Device: Device Name.
If the registration is successful, returns 0, then you can see the interrupt you requested in the / proc / interrupts file.
4. Some common problems.