Linux is a variant of the UNIX operating system, and the principle and idea of writing a driver in Linux is similar to that of other Unix systems, but it is very different for drivers in DOS or window environments. Design the driver in the Linux environment, the thought is concise, the operation is convenient, the function is also very formidable, but the support function is few, can only depend on the function in the kernel, some commonly used operation to write by oneself, and debugging also inconvenient. I have this week for the laboratory developed a multimedia card to develop a driver, gained some experience, is willing to share with Linux fans, there are improper, please correct me.
Some of the following text is mainly from Khg,johnsonm write Linux device Driver,brennan ' s Guide to Inline assembly,the Linux A-Z, and Tsinghua BBS on the device Some information about driver. Some of these materials are outdated and some are still wrong, and I have revised them according to my own test results.
First, the concept of Linux device driver
The system call is the interface between the operating system kernel and the application, and the device driver is the interface between the operating system kernel and the machine hardware. The device driver masks the details of the hardware for the application so that, in the view of the application, the hardware device is just a device file. An application can operate on a hardware device as if it were a normal file. The device driver is part of the kernel, which completes the following functions:
1. Initialize and release the equipment.
2. Transfer data from the kernel to the hardware and read the data from the hardware.
3. Read data that the application sends to device files and loopback application requests.
4. Detection and handling of equipment errors.
There are two main types of device files under the Linux operating system, one is a character device, the other is a block device. The main difference between a character device and a block device is that when a read/write request is made to a character device, the actual hardware I/O is typically followed by a block device, which uses a system memory as a buffer. When a user process requests the device to meet the requirements of the user, the requested data is returned and, if not, the request function is invoked to perform the actual I/O operation. The block device is designed primarily for slow-speed devices such as disks, lest it consume too much CPU time to wait.
It has been mentioned that the user process deals with the actual hardware through device files. Each device file has its file attributes (c/b), which means that the character device is also 蔤 strong Qiang? In addition, each file has two device numbers, the first is the main device number, identifies the driver, and the second is from the device number, Identifies different hardware devices that use the same device driver. For example, there are two floppy disks that can be used to differentiate them from the device number. The primary device number for the device file must be the same as the primary number that the device driver requested when registering, otherwise the user process will not be able to access the driver.
Finally, it must be mentioned that when the user process invokes the driver, the system enters the kernel mindset, This is no longer a preemptive dispatch. In other words, the system must return the child function of your driver before doing anything else. If your driver is stuck in a dead loop, unfortunately you only have to reboot the machine, and then the long Fsck.//hehe
When read/write, it first looks at the contents of the buffer, if the data of the buffer
How to write a device driver under a Linux operating system
Second, the case analysis
Let's write one of the simplest character device drivers. Although it does nothing, however, it is possible to understand how Linux's device drivers work. Put the following C code into the machine and you will get a real device driver. But my kernel is 2.0.34, there may be a problem on the lower version of kernel, I haven't tested it yet. Xixi
#define __NO_VERSION__
#include <linux/modules.h>
#include <linux/version.h>
Char kernel_version [] = uts_release;
This section defines some version information, although not very useful, but also essential. Johnsonm says that all drivers start with <linux/config.h>, but I don't think so.
Because the user process is through the device file with the hardware, the operation of the device file is just some system calls, such as open,read,write,close ..., note, not fopen, fread, but how to associate system calls and drivers? This requires an understanding of a very critical data structure:
struct File_operations {
Int (*seek) (struct inode *, struct file *, off_t, int);
Int (*read) (struct inode *, struct file *, char, int);
Int (*write) (struct inode *, struct file *, off_t, int);
Int (*readdir) (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 inode *, 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 name of each member of this structure corresponds to a system call. The user process utilizes system calls to find the appropriate device driver for a device file, such as a read/write operation, through the device file's main device number, and then reads the corresponding function pointer to the data structure. Then give control to the function. This is the basic principle of the Linux device driver. Since this is the case, the primary task of writing device drivers is to write child functions and populate the File_operations fields.
Pretty simple, isn't it?
Start writing subroutines below.
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test (struct inode *node,struct file *file,
Char *buf,int count)
{
int left;
if (Verify_area (verify_write,buf,count) = =-efault)
Return-efault;
for (left = count; left > 0; left--)
{
__put_user (1,buf,1);
buf++;
}
return count;
}
This function is prepared for the read call. When you call read, Read_test () is called, and it writes the user's buffer all 1.BUF is a parameter of the read call. It is an address of the user process space. But when Read_test is called, System into the nuclear mentality. So you can't use buf this address, you have to use __put_user (), which is a function provided by kernel to send data to the user. There are many functions that are similar. You must verify that the BUF is available before you copy data to user space.
This is used to 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 null operations. The actual call takes place without doing anything, and they provide function pointers only for the structure below.
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 * *
* No more, fill with NULLs * *
};
The body of the device driver can be said to have been written. Now embed the driver in the kernel. Drivers can be compiled in two different ways. One is compiled into the kernel, the other is compiled into a module (modules), if compiled into the kernel, will increase the size of the kernel, but also to change the core source files, and can not be dynamic uninstall, not conducive to debugging, so recommend the use of modular 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;
}
The Init_module function is invoked when the compiled module is transferred into memory with the Insmod command. Here, Init_module only does one thing, is to register a character device with the system's character device table. Register_chrdev requires three parameters, one is the desired device number, if it is 0, the system will select a not occupied device number returned. Parameter two is the device file name, and parameter three is used to register a pointer to the function that the driver actually performs.
If the registration succeeds, return the device's main device number, unsuccessful, and return a negative value.
void Cleanup_module (void)
{
Unregister_chrdev (test_major, "test");
}
When you uninstall a module with Rmmod, the Cleanup_module function is called, which frees the table entries that the character device test occupies in the System character device table.
A very simple character device can be said to write well, the filename is called TEST.c bar.
Compile below
$ gcc-o2-dmodule-d__kernel__-C test.c
Getting file TEST.O is a device driver.
If the device driver has more than one file, compile each file on the command line above, and then
Ld-r file1.o file2.o-o modulename.
The driver has been compiled and now installs it in the system.
$ insmod-f TEST.O
If the installation is successful, you can see the device test in the/proc/devices file, and you can see its main device number.
To uninstall the words, run
$ rmmod Test
The next step is to create a device file.
Mknod/dev/test C Major Minor
C refers to the character device, major is the main device number, is seen in the/proc/devices.
Use shell command
$ cat/proc/devices | awk "}"
To get the main device number, you can add the command line above to your shell script.
Minor is from the device number, set to 0 on it.
We can now access our drivers through the device files. Write a small test program.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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 the run to see if it prints out all 1?
The above is just a simple demo. Real-utility drivers are much more complex, dealing with issues such as interrupts, dma,i/o port, and so on. These are the real difficulties. Please see the next section, the actual treatment.
How to write a device driver under a Linux operating system
Third, some specific problems in the device driver program
1. I/O Port.
and hardware to deal with the I/O port, the old ISA device is often used to occupy the actual I/O port, Linux, the operating system does not have the I/O port screen, that is, any driver can operate on any I/O port, so it is easy to cause confusion. Each driver should avoid misuse of the port itself.
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 system's I/O table to see if there is another driver consuming a certain I/O port.
Parameter 1:io the base address of the port,
Parameter 2:io the port occupies the range.
Return value: 0 is not occupied, not 0, has been occupied.
2) request_region (int io_port, int off_set,char *devname)
If this I/O port is not occupied, it can be used in our driver. Before use, you must register with the system to prevent it from being consumed by other programs. After registering, you can see your registered IO port in the/proc/ioports file.
Parameter 1:io the base address of the port.
Parameter 2:io the port occupies the range.
Parameter 3: The name of the device using this IO address.
After registering the I/O port, I can safely use INB (), OUTB () and so on to visit the letter.
In some PCI devices, I/O ports are mapped to a section of memory, and access to those ports is equivalent to accessing a section of memory. Frequently, we want to get a physical address for a piece of memory. In a DOS environment, (DOS OS is not said to be because I think DOS is not an operating system, it is too simple, too insecure) as long as the use of paragraph: offset on it. In Window95, 95DDK provides a VMM call _maplineartophys to convert a linear address into a physical address. But how do you do it in Linux?
2. Memory operation
In the device driver dynamic development of memory, not with malloc, but kmalloc, or use get_free_pages Direct request page. Free memory is kfree, or free_pages. Notice that the Kmalloc function returns the physical address! and malloc and so on return is the linear address! About Kmalloc return is the physical address of this I am a bit confused: since the conversion from the linear address to the physical address is done by 386CPU hardware, the assembly instruction operand should be a linear address, and the driver cannot use the physical address directly but the linear address. But in fact Kmalloc is actually returning the physical address, and can also access the actual RAM directly through it, which I think can be explained in two different ways, one is to prohibit paging in the nuclear mentality, but it doesn't seem realistic. The other is that the Linux page directory and page table entries are designed just to make the physical address equal to the linear address. I do not know whether the idea is right, but also ask the master advice.
To be more careful, note that the largest kmalloc can only open 128k-16,16 byte is the page descriptor structure occupied. Kmalloc usage See KHG.
Memory-mapped I/O ports, registers, or RAM (such as video memory) of a hardware device generally occupy the address space above F0000000. cannot be accessed directly in the driver, the kernel function vremap to obtain a remap address.
In addition, many hardware require a larger contiguous memory to be used as a DMA transfer. This block of memory needs to reside in memory and cannot be exchanged to file. But Kmalloc can only open up to 128k of memory.
This can be solved by sacrificing some system memory.
For example, your machine by 32M of memory, in the lilo.conf boot parameters plus mem=30m, so that Linux think your machine only 30M of memory, the remaining 2 m memory after the VREMAP can be used for DMA.
Keep in mind that using vremap mapped memory, when not using Unremap release, will waste the page table.
3. Interrupt Processing
As with an 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: Is the interrupt to request.
Handle: Interrupt handler function pointer.
Flags:sa_interrupt Request a quick interrupt, 0 normal interrupt.
Device: Device name.
If the registration succeeds and returns 0, you can see the interrupt you requested in the/proc/interrupts file.
4. A number of common problems.
For hardware operations, sometimes timing is important. But if you write low-level hardware in C, GCC tends to optimize your program so that the timing is wrong. If you write by sinks, GCC will also optimize the assembly code unless you use the volatile keyword to modify it. The safest approach is to ban optimization. This, of course, can only be written to a subset of your own code. If none of the code is optimized, you will find that the driver is not loaded at all. This is because some of the extended features of GCC are used when compiling the driver, and these extended features must be reflected after the optimization option is added.