Embedded driver development process

Source: Internet
Author: User

In an embedded system, the operating system uses various drivers to control hardware. The device driver is the interface between the operating system kernel and the hardware device. It shields the application from hardware details. In this way, the hardware device is just a device file, you can perform operations on hardware devices like operating common files. The device driver is part of the kernel and implements the following functions:
◇ Driver registration and logout.
◇ Enable and release the device.
◇ Read/write operations on the device.
◇ Device control operations.
◇ Device interruption and polling.
Linux mainly divides devices into three categories: character devices, Block devices, and network devices. A character device is a device that sends and receives data in the form of characters without a buffer zone. A block device is a device that sends and receives data in the form of a buffer zone; A network device is a BSD socket interface accessed by a network device. The following uses a character device as an example to describe its driver compiling framework:
1. Compile the driver initialization Function
The driver initialization is completed in the xxx_init () function, including hardware initialization, interrupt functions, and driver registration to the kernel.
First, understand the hardware structure, find out its functions, interface registers, and how the CPU can access and control these registers.
Then, register the driver with the kernel. The device driver can be directly compiled into the kernel, initialized during system startup, or dynamically loaded into the kernel as a module as needed. Each character device or block device is registered through the register_chrdev () function. After calling this function, you can apply for the master device number from the system. After the operation is successful, the device name will appear in/proc/devices.
In addition, when you disable a device, you need to cancel the registration of the original device. You need a clearing function, which is implemented through the unregister_chrdev () function in xxx_exit, the device will then disappear from/proc/devices.
When the driver program is compiled into a module, use insmod to load the module. The initialization function xxx_init () of the module is called to register the driver with the kernel. Use rmmod to uninstall the module, the module's cleanup function xxx_exit () is called.
2. Construct the member functions used in the file_operations Structure
The Linux operating system regards all devices as files and accesses the devices through operating files. Applications cannot directly operate on hardware and use unified interface functions to call hardware drivers. These interfaces are called by the system. Each system call has a corresponding function (open, release, read, write, ioctl, etc.). In the character driver, these functions are collected in a file_operations type data structure. Take a keyboard driver as an example:
Struct file_operations key7279_fops =
{
. Open = key7279_open,
. IOCTL = key7279_ioctl,
. Release = key7279_close,
. Read = key7279_read,
};
1. Enable and release the device
The open () function is used to open a device. In most device drivers, open the device to complete the following tasks:
◇ Incremental counter
◇ Check the special circumstances of a specific device
◇ Initialize the device
◇ Secondary device identification number
The release () function is used to release a device. When a process releases a device, other processes can continue to use the device, but the process temporarily stops using the device. When a process disables the device, other processes must re-open the device for use. Release completes the following tasks:
◇ Decrease count
◇ Disable the device when the device is last released
2. read/write operations on devices
The main task of reading and writing devices is to copy the data in the kernel space to the user space, or to the kernel space from the user space, that is, copy the data in the kernel space buffer to the user space buffer or vice versa. Character devices use their respective read () and write () functions to read and write data.
3. Device Control Operations
In addition to reading and writing capabilities, most devices can perform operations beyond simple data transmission. Therefore, device drivers must also be able to perform various hardware control operations. these operations are often supported by the ioctl method. Unlike read/write operations, IOCTL () usage is closely related to specific devices. Take key7279_ioctl as an example:
Static int key7279_ioctl (struct inode * inode, struct file * file, unsigned int cmd, unsigned long Arg)
{
Switch (CMD)
{
Case key7279_getkey:
Return key7279_getkey ();
Default:
Printk ("unkown Keyboard Command ID. \ n ");
}
Return 0;
}
The value and meaning of CMD are related to specific devices. In addition to IOCTL (), the device driver may also have other control functions, such as llseek.
When an application uses functions such as open and release to open a device, the corresponding member in the file_operations structure of the device driver will be called.
Iii. device interruption and polling
For devices that do not support interruption, You need to poll the device status during read/write and determine whether to continue data transmission. For example, a printer. If the device supports interruption, the device can be interrupted.
Before using the interrupt function, the module must request an interrupt channel (or IRQ interrupt request) and release it after use. Use the request_irq () function to register the interrupt, And the free_irq () function is released.
4. driver testing
Debugging of the driver can be done through printing, that is, by adding the printk () printing function to the driver to track the execution process of the driver, in order to judge the problem.
The above is my summary based on my own learning. It may be relatively simple to write. For complicated driver functions, more functions will be added, but the general framework is like this.
 
 
The driver based on the operating system is to add the operating system jacket to the hardware interface functions without the operating system.
The general process for implementing an embedded Linux device driver is as follows:
(L) view the schematic diagram and understand the working principle of the device.
(2) define the master device number. A device is identified by a primary device number and a secondary device number. The primary device ID uniquely identifies
The backup type, that is, the device driver type. It is the index of the device table items in the block device table or character device table. Device number only
The device driver identifies an independent device under the control of a device driver.
(3) Implement the initialization function. Register and uninstall the driver in the driver.
(4) design the file operations to be implemented and define the file-operations structure.
(5) implement the necessary file operation calls, such as read and write.
(6) implement service interruption and register with the kernel using request -- IRQ. interruption is not required by each device driver.
(7) Compile the driver to the kernel or use the insmod command to load the module.
(8) Test the device, write an application, and test the driver.
Typical Character Device Driver compiling framework:
1. Write hardware interface functions
2. Create an interface between the file system and the device driver, such as the struct file_operations struct.
3. register the device to the chrdevfs Global Array. You can register or deregister the device at any time. However, when the module is loaded, the device is deregistered when the module exits. (Module_init (); module_exit ();)
4. Compile the driver source code in the module mode and load it into the kernel.
5. Create a device node and mknode

6. Write Applications to access underlying Devices

Ii. instance analysis

Let's write a simple character device driver. Although it does not do anything, it can be used to understand the operating principle of the Linux Device Driver. input the following C code into the machine and you will get a real device driver. however, my kernel is 2.0.34, which may cause problems in earlier versions. I have not 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 it is not very useful, it is also necessary. johnsonm said that all drivers must start with <Linux/config. h>, but I don't think so.

Because a user process deals with hardware through a device file, the operation on the device file is similar to some system calls, such as open, read, write, close ...., note: It's not fopen or fread. But how can we associate system calls with 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 in this structure corresponds to a system call. when a user process uses a system call to perform read/write operations on device files, the system calls the system to find the corresponding device driver through the master device number of the device file, then read the corresponding function pointer of the data structure, and then give the control to the function. this is the basic principle of Linux device drivers. in this case, the main task of writing a device driver is to write a sub-function and fill in the fields of file_operations.

It's quite simple, isn't it?

The following describes the subprogram.

# 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 read is called, read_test () is called, which writes all the user's buffers. 1.buf is a parameter called by read. it is an address of the user's process space. however, when read_test is called, The system enters the core state. therefore, you cannot use the Buf address. You must use _ put_user (). This is a function provided by kernel to transmit data to users. there are also many functions with similar functions. see. before copying data to a user space, 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 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 we need to embed the driver into the kernel. The driver can be compiled in two ways. One is to compile into the kernel, and the other is to compile into the module. If compiled into the kernel, it will increase the size of the kernel and change the source file of the kernel, it cannot be dynamically uninstalled, which is not conducive to debugging. Therefore, we recommend that you use the module method.

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 called when compiled modules are transferred to the memory using the insmod command. Here, init_module only registers a character device to the system's character device table. Register_chrdev requires three parameters. The first parameter is the device number to be obtained. If it is zero, the system selects an unused device number and returns it. The second parameter is the device file name. The third parameter is used to register 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 table items that the character device test occupies in the system 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 "}"

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 <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 and run the program to see if all 1 is printed?

The above is just a simple demonstration. Real and practical drivers need to be complicated, 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 port is indispensable for dealing with hardware. The old ISA Device often occupies the actual I/O port. in Linux, the operating system does not block the I/O port. 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.

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 the registered IO port in the/proc/ioports file.

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 to accessing a memory segment. Frequently, we need to obtain the physical address of a piece of memory. In a DOS environment (the reason is not to mention the DOS Operating System is because I think DOS is not an operating system at all. It is too simple and too insecure) as long as the segment is used: offset. In window95, 95ddk provides a vmm call _ maplineartophys to convert a linear address to 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 directly apply for a page using get_free_pages. Kfree or free_pages are used to release the memory. Note that the physical address is returned by functions such as kmalloc! Malloc and other returned linear addresses! I am not quite clear about the physical address returned by kmalloc: Since the conversion from a linear address to a physical address is completed by the compaction CPU hardware, the operand of the Assembly command should be a linear address, the driver cannot directly use a physical address but a linear address. But in fact, kmalloc does return a physical address and can directly access the actual Ram through it. I think this can be explained in two ways: 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 equivalent to the linear address. I don't know what I think, right? Please advise me.

To put it bluntly, note that kmalloc can only be opened up to-16, and 16 bytes are occupied by the page descriptor structure. For more information about kmalloc usage, see KHg.

Memory-mapped I/O Ports, registers, or RAM (such as memory) of hardware devices generally occupy address space above f0000000. Cannot be accessed directly in the driver. You need to use the kernel function vremap to obtain the address after re ing.

In addition, many hardware require a large continuous memory for DMA transmission. This memory must remain in the memory and cannot be exchanged 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, your machine uses 32 MB of memory in Lilo. in the conf startup parameter, the mem = 30 m is added. In this way, Linux considers that your machine has only 30 m of memory, and the remaining 2 M has vremap, which can be used for DMA.

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 the C language to write some low-level hardware operations, GCC will usually optimize your program, so that the timing is wrong. If you write it in a sink, GCC will also optimize the Assembly Code unless you modify it with the volatile keyword. The safest way is to disable optimization. Of course, this can only be a part of your own code. If you do not optimize all the code, you will find that the driver cannot be loaded at all. This is because some GCC extension features are used to compile the driver, which must be reflected only after the optimization options are added.

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.