Preface
Linux is a variant of the UNIX operating system. The principles and ideas of writing drivers in Linux are completely similar.
Similar to other UNIX systems, but the drivers in the DoS or window environment are very different. In the Linux environment
The design of the driver is concise, easy to operate, and has powerful functions. However, few functions are supported.
Depending on the functions in the kernel, some common operations should be compiled by yourself, and debugging is not convenient. I have been using this service for weeks.
We developed a driver for a multi-media card developed by the lab and gained some experience.
Fans sharing. please correct me if there are any mistakes.
The following text mainly comes from the write Linux Device of KHg and johnsonm.
Driver, Brennan's Guide to inline assembly, the Linux
A-Z, there are some device driver information on Tsinghua BBS.
Some of these materials are outdated and some are incorrect. I have corrected them based on my own test results.
I. Concepts of Linux Device Driver
System calling is the interface between the operating system kernel and applications, and the device driver is the operating system kernel.
The interface between the device and the machine hardware. The device driver shields the hardware details for the application.
It seems that the hardware device is just a device file,
Applications can operate on hardware devices like normal files. device drivers are part of the kernel.
, Which provides 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 main difference between character devices and Block devices is that the actual hardware I/O
Generally, Block devices do not use a system memory as a buffer.
If a device request meets user requirements, the requested data is returned. If not, the request function is called.
Actual I/O operations. Block devices are designed for disks and other slow devices to avoid excessive CPU time consumption.
Wait.
As mentioned, a user process deals with the actual hardware through device files. Each device file has
Its file attributes (C/B) indicate that the character devices are still very strong and weak? In addition, each file has two device numbers
Is the main device number, identify the driver, the second is from the device number, identify the use of the same device driver
Different hardware devices, for example, have two floppy disks, you can use the device number to differentiate the master settings of the device files.
The Sn must be the same as the primary device ID applied by the device driver during registration. Otherwise, the user process will not be able to access
Driver.
The last thing that must be mentioned is that when a user process calls the driver, the system enters the core State and is no longer
Preemptive scheduling. That is to say, the system must perform other work only after the sub-functions of your driver are returned.
. If your driver is in an endless loop, unfortunately you only have to restart the machine, and then there is a long FS
CK. // hehe
When reading/writing, it first looks at the buffer content, if the buffer data
How to compile a device driver in a Linux operating system
Ii. instance analysis
Let's write a simple character device driver. Although it does not do anything, it can
Understand how the Linux Device Driver works. input the following C code into the machine and you will get a real
But my kernel is 2.0.34, which may cause problems in earlier versions.
No tests have been conducted. // Xixi
# DEFINE _ no_version __
# Include
# Include
Char kernel_version [] = uts_release;
This section defines some version information. Although it is not very useful, it is also essential. johnsonm said all
The start of the driver must be included, but I don't think so.
Because a user process deals with hardware through device files, the operation on device files is similar
Some system calls, such as open, read, write, close..., note that not fopen,
Fread, but how do we associate system calls with drivers? This requires an understanding of 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 in this structure corresponds to a system call.
When a device file performs read/write operations, the system calls the device file to find the corresponding
Device Driver, then read the corresponding function pointer of the data structure, and then give the control to the function.
Is the basic principle of Linux device drivers.
Write the sub-function and fill in the fields of file_operations.
It's quite simple, isn't it?
The following describes the subprogram.
# Include
# Include
# Include
# Include
# Include
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 read calls. When read is called, read_test () is called, which calls
Buffer write all 1.buf
It is a parameter called by read. It is an address of the user's process space. But when read_test is called
Therefore, you cannot use the Buf address. You must use _ put_user (), which is provided by the kernel.
A function is used to transmit data to users. There are many other functions similar to this function. For more information, see.
Before copying data, you must verify whether the Buf is available.
The verify_area function is used.
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 all empty operations. When the actual call happens, nothing is done. They only provide the following structure
Function pointer.
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 we need to embed the driver into the kernel. The driver can
Compile in two ways. One is compiled into the kernel, and the other is compiled into the module.
If translated into the kernel, it will increase the kernel size and change the kernel source files, and cannot be dynamically uninstalled,
This is not conducive to debugging. Therefore, we recommend that you use modules.
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;
}
Init_module
The function is called. Here, init_module only registers one thing to the character device table of the system.
Characters. Register_chrdev requires three parameters. The first parameter is the device number to be obtained. If it is zero
Then, the system selects an unused device number and returns it. The second parameter is the device file name. The third parameter is used.
Registers the pointer of the function in which the driver actually performs the operation.
If the registration is successful, the system returns the device's master device number. If the registration fails, a negative value is returned.
Void cleanup_module (void)
{
Unregister_chrdev (test_major, "test ");
}
When you use rmmod to uninstall a module, the cleanup_module function is called, which releases the character device test in the system.
The table items occupied by the character device table.
A simple character device can be written. The file name is test. C.
Compile the following code
$ Gcc-O2-dmodule-d1_kernel _-C test. c
The file test. O is a device driver.
If the device driver has multiple files, compile each file according to the command line above, and then
LD-r file1.o file2.o-O modulename.
The driver has been compiled. Now install 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 its master
Device number.
Run
$ Rmmod Test
Next, create a device file.
Mknod/dev/test C major minor
C Indicates a character device. Major indicates the master device number, which is displayed in/proc/devices.
Use shell commands
$ CAT/proc/devices | awk "" {print }"
You can get the master device number and add the above command line to your shell script.
Minor is set from the device number to 0.
We can now access our driver through the device file. Write a small test program.
# Include
# Include
# Include
# 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 and run the program to see if all 1 is printed?
The above is just a simple demonstration. Real and practical drivers are much more complicated to handle, such as interrupt, DMA
And I/O port. These are the real difficulties. Please refer to the following section for more information.
How to compile a device driver in a Linux operating system
Iii. Specific problems in Device Drivers
1. I/O port.
I/O is essential for dealing with hardware.
Port, the old ISA Device often occupies the actual I/O port. in Linux, the operating system does not
That is to say, any driver can operate on any I/O port, which can easily cause confusion. Each
Drivers should avoid misuse of ports by themselves.
There are two important kernel functions to ensure that the driver can do this.
1) check_region (INT io_port, int off_set)
This function checks the system's I/O table to see if any other driver occupies an I/O port.
Parameter 1: The base address of the IO port,
Parameter 2: The range occupied by the I/O port.
Returned value: 0 is not in use. If it is not 0, it is already in use.
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 being occupied by other programs. After registration, you can see it in the/proc/ioports File
The IO port you registered.
Parameter 1: The base address of the IO port.
Parameter 2: The range occupied by the I/O port.
Parameter 3: The device name that uses this Io address.
After registering the I/O port, you can access it with letters such as INB () and outb () with confidence.
In some PCI devices, I/O ports are mapped to a memory segment. to access these ports is equivalent
Ask about memory. Frequently, we need to obtain the physical address of a piece of memory. In the DOS environment
It is a DOS operating system because I think DOS is not an operating system at all. It is too simple and not
Secure) you only need to use the segment: offset. In window95, 95ddk provides a vmm call
_ Maplineartophys is used to convert a linear address into a physical address. But in Linux, how does one do it?
2. Memory operations
Dynamically open the memory in the device driver, instead of using malloc, but kmalloc, or get_free
_ Pages direct application page. Kfree or free_pages are used to release the memory.
Note that functions such as kmalloc return physical addresses! Malloc and other returned linear addresses! About Kmal
Loc returns a physical address, which I do not quite understand: Since the conversion from a linear address to a physical address is
Completed by the worker CPU hardware, the operand of the Assembly command should be a linear address, and the driver cannot
Directly use a physical address but a linear address. But in fact, kmalloc returns a physical address, and
You can directly access the actual Ram through it. I think this can be explained in two ways. One is to disable paging in the core state.
But this seems unrealistic. The other is that the Linux page Directory and page table items are designed to make the physical address and so on.
It is equivalent to a linear address. I don't know what I think, right? Please advise me.
To put it bluntly, note that the maximum kmalloc value is-16, and the 16 bytes are occupied by the page descriptor structure.
Yes. For more information about kmalloc usage, see KHg.
Memory-mapped I/O Ports, registers, or ram of hardware devices (such as memory) generally occupy more than f0000000
. Cannot be accessed directly in the driver. You must use the kernel function vremap to obtain the re- ing
.
In addition, many hardware require a large continuous memory for DMA transmission. This memory needs to stay
In the memory, it cannot be switched to files. However, kmalloc can only open up to KB of memory.
This can be solved by sacrificing some system memory methods.
The specific method is as follows: for example, if your machine uses 32 MB memory, add mem = 3 to the startup parameter of Lilo. conf.
0 m. In this way, Linux considers that your machine has only 30 m of memory. After the remaining 2 M has vremap, you can set it to D.
Ma is used.
Remember that the memory mapped with vremap is released when unremap is not used. Otherwise, page tables are wasted.
3. interrupt handling
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 request is interrupted.
Handle: the pointer to the interrupt processing function.
Flags: a fast interrupt to the sa_interrupt request, with 0 normal interruptions.
Device: device name.
If the registration succeeds, 0 is returned. In this case, you can view the request interruption in the/proc/interrupts file.
4. Some common problems.
Sometimes timing is important for hardware operations. However, if you use C to write some low-level hardware operations, GC
C will usually optimize your program, so that the timing is wrong. If it is written in a sink, GCC will also
Code compilation for optimization, unless you modify it with the volatile keyword. The safest way is to disable optimization. Of course
You can only write a part of your own code. If you do not optimize all the code, you will find the driver Root
This cannot be loaded. This is because some GCC extension features are used to compile the driver.
The optimization options must be added before they can be reflected.
From: http://hi.baidu.com/iruler/item/91eb3921a85be1454799623c