2397 views|3 replies

119

Posts

0

Resources
The OP
 

How to Write Embedded Linux Device Drivers [Copy link]

This post was last edited by CokezzZ on 2021-5-22 11:19

1. The concept of Linux device driver

System calls are the interface between the operating system kernel and the application, and device drivers are the interface between the operating system kernel and the machine hardware. The device driver shields the application from the details of the hardware, so that from the application's point of view, the hardware device is just a device file, and the application can operate the hardware device like an ordinary file. The device driver is part of the kernel and performs the following functions:

1. Initialize and release the device;

2. Transfer data from the kernel to the hardware and read data from the hardware;

3. Read the data sent by the application to the device file and send back the data requested by the application;

4. Detect and handle equipment errors.

There are three main types of device files in the Linux operating system: character devices, block devices, and network devices. The main difference between character devices and block devices is that when a read/write request is issued to a character device, the actual hardware I/O generally occurs immediately, but this is not the case with block devices. It uses a piece of system memory as a buffer. When the user process's device request can meet the user's requirements, it returns the requested data. If not, it calls the request function to perform the actual I/O operation. Block devices are mainly designed for slow devices such as disks to avoid wasting too much CPU time waiting.

As mentioned above, user processes interact with actual hardware through device files. Each device file has its file attributes (c/b), indicating whether it is a character device or a block device. In addition, each file has two device numbers. The first is the major device number, which identifies the driver, and the second is the minor device number, which identifies different hardware devices using the same device driver. For example, if there are two floppy disks, the minor device number can be used to distinguish them. The major device number of the device file must be consistent with the major device number applied by the device driver when registering, 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 kernel state, which is no longer preemptive scheduling. In other words, the system must wait until your driver's subroutine returns before doing other work. If your driver falls into an infinite loop, unfortunately you have no choice but to restart the machine, and then there is a long fsck.

2. Example Analysis

Let's write a simplest character device driver. Although it does nothing, it can help you understand how Linux device drivers work. Enter the following C code into the machine and you will get a real device driver.

Since the user process interacts with the hardware through device files, the operation methods of device files are nothing more than some system calls, such as open, read, write, close... Note that it is not fopen, fread, but how to associate the system call with the driver? This requires understanding 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. When the user process uses the system call to perform operations such as read/write on the device file, the system call finds the corresponding device driver through the major device number of the device file, then reads the corresponding function pointer of this data structure, and then transfers control to the function. This is the basic principle of the working of Linux device drivers. In this case, the main work of writing device drivers is to write sub-functions and fill in the fields of file_operations.

Now let’s start writing the subroutine.

#include <linux/types.h> Basic type definitions

#include <linux/fs.h> File system usage related header files

#include <linux/mm.h>

#include <linux/errno.h>

#include <asm/segment.h>

unsigned int test_major = 0 ;

static int read_test (struct inode *inode, struct file *file, char *buf, int count)

{

int left; user space and kernel space

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 1s to the user's buffer. buf is a parameter of the read call. It is an address in the user process space. But when read_test is called, the system enters kernel mode. So the address buf cannot be used. You must use __put_user(), which is a function provided by the kernel to transfer data to the user. There are many other functions with similar functions. Please refer to it. Before copying data to user space, you must verify whether buf is available. This is where the function verify_area is used. In order to verify whether BUF is available.

static int write_test ( struct inode *inode, struct file *file, const char *buf, int count)

{

return count;

}

static int open_test ( struct inode *inode, struct file *file)

{

MOD_INC_USE_COUNT; The module count is added, indicating that the current kernel has a device loaded into the kernel.

return 0 ;

}

static void release_test ( struct inode *inode, struct file *file)

{

MOD_DEC_USE_COUNT;

}

These functions are no-ops. They do nothing when actually called, they just provide function pointers for the following structures.

struct file_operations test_fops = {?

read_test,

write_test,

open_test,

release_test,

};

The main body of the device driver has been written. Now we need to embed the driver into the kernel. The driver can be compiled in two ways. One is to compile it into the kernel, and the other is to compile it into modules. If it is compiled into the kernel, the size of the kernel will increase, and the kernel source files must be modified. It cannot be dynamically uninstalled, which is not conducive to debugging. Therefore, it is recommended to use the module method.

int init_module ( void )

{

int result;

result = register_chrdev( 0 , "test" , &test_fops); The entire interface for device operations

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 the compiled module is loaded into memory using the insmod command, the init_module function is called. Here, init_module only does one thing, which is to register a character device in the system's character device table. register_chrdev requires three parameters. Parameter one is the device number you want to obtain. If it is zero, the system will select an unoccupied device number to return. Parameter two is the device file name, and parameter three is used to register the pointer to the function that actually performs the operation of the driver.

If the registration is successful, the major device number of the device is returned. If it is unsuccessful, a negative value is returned.

void cleanup_module ( void )

{

unregister_chrdev(test_major, "test" );

}

When the module is uninstalled with rmmod, the cleanup_module function is called, which releases the entry occupied by the character device test in the system character device table.

An extremely simple character device has been written. Let's call the file test.c.

Compile below:

$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c means output the specified name and automatically generate .o file

The file test.o is a device driver.

If the device driver has multiple files, compile each file according to the above command line, and then

ld ?-r ?file1.o ?file2.o ?-o ?modulename.

The driver has been compiled, now it is time to install it into the system.

$ insmod ?–f ?test.o

If the installation is successful, you can see the device test in the /proc/devices file and its major device number. To uninstall, run:

$ rmmod test

The next step is to create the device files.

mknod /dev/test c major minor

c refers to the character device, major is the major device number, which is what you see in /proc/devices.

Use shell command

$ cat /proc/devices

You can get the major device number and add the above command line to your shell script.

Minor is the slave device number, just set it to 0.

We can now access our driver through the device file. Let's 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 );

read(testdev, buf, 10 );

printf ( "%d\n" , buf);

close(testdev);

}

Compile and run to see if all 1s are printed?

The above is just a simple demonstration. A truly practical driver is much more complicated, and needs to handle issues such as interrupts, DMA, I/O ports, etc. These are the real difficulties. The above gives a simple framework and principle for writing character device drivers. For more complex writing, you need to carefully study the operating mechanism of the LINUX kernel and the operating mechanism of specific devices, etc. I hope everyone can master the method of writing LINUX device drivers.

There are more embedded materials, you can add Q: 31334934, 18827648

You can also click on the link below and choose your favorite free online course to browse and study.

http://www.makeru.com.cn/live/5413_2094.html?s=11

The article is organized to spread related technologies, the copyright belongs to the original author
If there is any infringement, please contact us to delete

This post is from ARM Technology

Latest reply

The device file types required under the Linux operating system are character devices, block devices, and network devices. A basic Linux device driver development environment needs to be built.   Details Published on 2024-10-1 12:28
 

6570

Posts

0

Resources
2
 

The device file types required under the Linux operating system are character devices, block devices, and network devices. A basic Linux device driver development environment needs to be built.

This post is from ARM Technology
 
 
 

7422

Posts

2

Resources
3
 

Thanks for sharing!

This post is from ARM Technology
 
Personal signature

默认摸鱼,再摸鱼。2022、9、28

 
 

413

Posts

0

Resources
4
 

The device file types required under the Linux operating system are character devices, block devices, and network devices. A basic Linux device driver development environment needs to be built.

This post is from ARM Technology
 
 
 

Guess Your Favourite
Find a datasheet?

EEWorld Datasheet Technical Support

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号
快速回复 返回顶部 Return list