Author: Roy G preface Linux is a variant of the Unix operating system, the principles and ideas to write drivers under Linux completely similar to other Unix systems, but it dos drivers under the window or the environment are very different in. Design drivers in the Linux environment, simple ideas, convenient operation, and function are also very strong, but support functions, can only rely on the function in kernel, some common operations must be written by themselves, and debugging is not convenient. I am a few weeks To develop a driver for a multimedia card developed by the laboratory, I have some experience, I am willing to share with Linux fans, if you have inappropriate, please give it to the following words mainly come from KHG, Johnsonm Write Linux Device Driver. Brennan's Guide to Inline Assembly, The Linux AZ, as well as some information about Device Driver on Tsinghua BBS. These materials have been outdated, some have some errors, and I have been corrected according to my own test results. One. Linux Device Driver's Concept 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 in the application It seems that the hardware device is 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 completes the following features: 1. Initialize and release the device. 2. Put the data Pass from the kernel to the hardware and read data from the hardware. 3. Read the application to the data files to the device file and the data requested by the application request. 4. Detect and process the error in the device. There are two types under the Linux operating system. The main device file type, one is the character device, the other is the block device. The main difference between the character device and block device is that the actual hardware I / O is generally followed when the character device is read / write. If the block device has, it uses a system memory to make a buffer. When the user process requests the device to meet the requirements of the user, return the requested data. If not, the request function is called to perform the actual I / O operation. The block device is mainly designed for slow-speed devices such as disk, so as not to consume too much CPU time to wait. It has been mentioned that the user process is derived with the actual hardware through the device file. Each device file has its files. Attribute (C / B), indicating that the character device is still strong? In addition, each file has two device numbers, the first is the main device number, the logo, the second is from the device number, the logo Different hardware devices of the same device driver, such as two floppy disks, you can use from device numbers to distinguish them. Device files The main device number must be consistent with the primary device number requested at the time of registration, otherwise the user process will not be able to access the driver. Finally, the system enters the core state when the user process calls the driver, at this time It is no longer a predecessor. That is to say, the system must return to other work after returning the child function of your driver. If your driver is falling into a dead loop, unfortunately, you only have to restart the machine, then Long fsck.//HEHE II. Examples analyze us to write a simplet-simple character device driver. Although it doesn't do anything, it can understand the working principle of Linux device drivers. Enter the following C code input Machine, you will get a real device driver. But my kernel is 2.0.34, may have problems on the low version of the kernel, I haven't tested it .//xixi #define __no_version__ #include
INT (* inode *, struct file *, char, int); int (* write) (struct inode *, struct file *, off_t, int); int (* readdir) (Struct Inode *, Struct File *, struct direct *, 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) (strunt inode *, struct file *); int (* release *, struct file *); int (* FSYNC) (Struct Inode *, Struct File *); int (* fas *, struct file *, int); int); struct inode *, struct file *; int (* revALIDATE) DEV_T dev);} The name of each member of this structure corresponds to a system call. The user process uses the system call to find the corresponding device with the main device number of the device file using the system call. Driver, then read the function pointer corresponding to this data structure, then handle the control to the function. This is the basic principle of Linux device driver work. Since this is the case, the main job of writing device drivers is to write Function, and populate the fields of file_operations. Quite simple, isn't it? Here you start writing a subroutine. #Include
} 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;} operation these functions are actually empty. Nothing when calling, 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"); return results;} IF TEST_MAJOR == 0) Test_major = result; / * Dynamic * / return 0;} The init_module function is called when the compiled module is transferred to the memory with the insmod command. 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 using the RMMOD to uninstall the module, the cleanup_module function is called, which releases the character device TEST in the system character device table. An extremely simple character device can be said to be written, the file name is called Test.c. The following is compiled $ gcc -o2 -dmodule -d__kernel__ -c test.c gets the file Test.O is a device driver. If there are multiple files, press each file to compile each file, then ld -r file1.o file2.o -o modulename. 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 the RMMOD TEST Next to create a device file.
MKNOD / DEV / TEST C MAJOR Minor C is a character device, and Major is a primary device number, which is seen in / proc / devices. Use shell command $ cat / proc / devices | awk "/ $ 2 ==" {print / $ 1} "to 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
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 opens up 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. Memory maps of I / O ports, registers, or RAMs of hardware devices generally take up 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 processing is the same as the processing I / O port, 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: It is an interrupt to be applied. 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 are important to hardware, sometimes timing. But if you write some low-level hardware operations with a C language, GCC often optimizes your program so that timing is wrong. If you write it with a compilation, GCC will also optimize assembly code unless you use Volatile keyword modified. The most insurance method is to prohibit optimization. Of course, you can only write the code you wrote in your own. If you don't optimize all the code, you will find that the driver cannot be loaded at all. This is because some of the extended features of GCC is used when compiling drivers, and these extended features must be reflected after the optimization option is added. About Kernel debugging tools, I have not found appropriate. Does anyone know, please tell me, it is inciting. I have always printed the debug information in Printk, and I will still make together.