How to Write Embedded Linux Device Drivers

How to Write Embedded Linux Device Drivers
Word Count:4500 Content Index:⭐⭐⭐⭐⭐
How to Write Embedded Linux Device DriversConcept of Linux Device Driver
System calls are the interface between the operating system kernel and application programs, while device drivers serve as the interface between the operating system kernel and machine hardware. Device drivers abstract hardware details from application programs, allowing them to interact with hardware devices as if they were just regular files. Device drivers are part of the kernel and perform the following functions:
1. Initialize and release devices;
2. Transfer data from the kernel to hardware and read data from hardware;
3. Read data sent by application programs to device files and return the requested data to application programs;
4. Detect and handle device errors.
Under the Linux operating system, there are three main types of device files: character devices, block devices, and network devices. The main difference between character devices and block devices is that for character devices, actual hardware I/O typically occurs immediately after a read/write request is made, while block devices use a system memory buffer. When a user process requests a device, if the request can be fulfilled, the requested data is returned; if not, a request function is called to perform the actual I/O operation. Block devices are primarily designed for slow devices like disks to prevent excessive CPU time from being wasted on waiting.
As mentioned, user processes interact with actual hardware through device files. Each device file has its file attributes (c/b) indicating whether it is a character or block device. Additionally, 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 distinguishes different hardware devices using the same driver. For example, two floppy disks can be distinguished using the minor device number. The major device number of the device file must match the major device number requested by the driver during registration; otherwise, the user process will not be able to access the driver.
Lastly, it is important to mention that when a user process calls a driver, the system enters kernel mode, and preemptive scheduling is no longer in effect. This means that the system must wait for your driver’s sub-function to return before it can perform other work. If your driver enters an infinite loop, unfortunately, you will have to restart the machine, followed by a lengthy fsck process.

How to Write Embedded Linux Device DriversCase Analysis

Let’s write the simplest character device driver. Although it does nothing, it helps us understand how Linux device drivers work. Enter the following C code into your machine, and you will get a real device driver.
User processes interact with hardware through device files, and operations on device files generally involve system calls such as open, read, write, close… Note that these are not fopen, fread. But how do we associate system calls 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 *, unsigned 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); }
Each member of this structure corresponds to a system call. When user processes perform operations like read/write on device files using system calls, the system call finds the corresponding device driver using the major device number of the device file, then reads the function pointer from this data structure, and finally hands over control to that function. This is the basic principle of how Linux device drivers work. Therefore, the main task of writing a device driver is to write the sub-functions and fill in the various fields of file_operations.
Now let’s start writing the subprograms.
#include <linux/types.h> // Basic type definitions#include <linux/fs.h> // File system 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 spaceif (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 invoked, which writes 1 to the entire user buffer. buf is one of the parameters of the read call. However, when read_test is called, the system enters kernel mode. Therefore, you cannot use the buf address directly; you must use __put_user(), a function provided by the kernel to send data to the user. Additionally, there are many similar functions. Note that before copying data to user space, you must verify that buf is usable. This is done using the verify_area function.
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; // Increment module count, indicating the current kernel has this device loaded return 0; } static void release_test(struct inode *inode, struct file *file) { MOD_DEC_USE_COUNT; }
These functions are all no-ops. They do nothing when actually called; they simply provide function pointers for the structure below.
struct file_operations test_fops = {read_test, write_test, open_test, release_test, };
The main body of the device driver can be considered complete. 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 as a module. Compiling it into the kernel increases its size and requires modifications to the kernel’s source files, and it cannot be dynamically unloaded, which is not conducive to debugging. Therefore, it is recommended to use the module approach.
int init_module(void) { int result; result = register_chrdev(0, "test", &test_fops); // Registering 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 performs a single task: registering a character device with the system’s character device table. register_chrdev requires three parameters: the first is the desired device number; if it is zero, the system will choose an unoccupied device number and return it. The second is the device file name, and the third is a pointer to the functions that perform the operations of the driver.
If registration is successful, it returns the major device number; if unsuccessful, it returns a negative value.
void cleanup_module(void) { unregister_chrdev(test_major, "test"); }
When the rmmod command is used to unload the module, the cleanup_module function is called, which releases the entry occupied by the character device test in the system’s character device table.
An extremely simple character device can be considered written, and let’s name the file test.c.
Now to compile:
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c // -c denotes outputting the specified name, automatically generating .o file
The resulting file test.o is a device driver.
If the device driver has multiple files, compile each file using the command line above, then
ld -r file1.o file2.o -o modulename.
The driver is now compiled, and we need 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 along with its major device number. To unload it, run:
$ rmmod test
The next step is to create a device file.
mknod /dev/test c major minor
c indicates a character device; major is the major device number seen in /proc/devices.
To get the major device number, use the shell command
$ cat /proc/devices
You can include the above command line in your shell script.
minor is the minor device number, which can be set to 0.
Now we can 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("Can'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 to see if it prints all 1s?
The above is just a simple demonstration. A practical driver is much more complex and must handle issues like interrupts, DMA, I/O ports, etc. These are the real challenges. The above provides a simple framework and principles for writing a character device driver; more complex writing requires thorough study of the Linux kernel’s operating mechanisms and the specific mechanisms of the device in question. I hope everyone can master the methods for writing Linux device drivers well.
Disclaimer: The content of this article comes from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact for removal.
How to Write Embedded Linux Device Drivers
How to Write Embedded Linux Device Drivers

1

8000字干货 | 为什么要用C语言实现面向对象?

2

单片机技术即将被淘汰??

3

读完的都收藏啦!!!万字干货嵌入式系统设计开发大全……

How to Write Embedded Linux Device Drivers

01

02

03

04

05

How to Write Embedded Linux Device Drivers
How to Write Embedded Linux Device Drivers

Leave a Comment