In embedded Linux systems, drivers serve as the bridge between the kernel and hardware, while the Device Tree (DT) provides an abstraction for hardware information. Understanding the Linux driver model and device trees is crucial for embedded engineers to build a complete system from low-level hardware to user space. This article will gradually analyze the principles of driver and hardware interaction, covering the Linux driver model architecture, platform driver mechanisms, character and block devices, device tree parsing, and driver loading processes, while also providing practical experience and debugging techniques.
1. Overview of the Linux Driver Model
The Linux kernel employs a unified driver model to manage various hardware devices, with the core concepts being hardware abstraction, unified management, and dynamic loading.
1. Three Core Objects: Device, Driver, Bus
The three core objects in the driver model are:
-
Device: Represents an instance of a hardware device, such as UART1, I2C controller, or GPIO.
-
Driver: Provides an interface for operations on a specific device, including
<span>probe()</span>and<span>remove()</span>. -
Bus: Manages the registration and matching of devices and drivers, such as platform bus, PCI bus, and I2C bus.
During the boot or module loading phase, the kernel searches for a driver that matches the device through the bus. When a match is found, the driver’s <span>probe()</span> function is called to complete device initialization.
2. Driver Lifecycle
The lifecycle of a driver mainly includes:
-
Registering the driver: Using
<span>platform_driver_register()</span>or the macro<span>module_platform_driver()</span>. -
Matching the device: The kernel matches the driver based on the device name and the device tree compatible property using the bus’s
<span>match</span>method. -
Executing probe(): Completing resource allocation, memory mapping, interrupt registration, and other initializations.
-
Running phase: The device operates normally, responding to user space calls or kernel subsystem operations.
-
unprobe()/remove(): Unloading the driver and releasing resources.
The lifecycle design ensures the dynamic plug-and-play capability of drivers and the stability of the system.
2. Platform Driver
On embedded SoCs, most peripherals are managed through platform drivers.
1. Platform Device and Platform Driver
-
Platform Device: Describes hardware device information, typically registered through the device tree or board files, without relying on bus hardware scanning.
-
Platform Driver: Provides probe/remove interfaces to initialize and manage the Platform Device.
2. Driver Registration and Matching Example
static const struct of_device_id my_driver_dt_ids[] = {
{ .compatible = "vendor,mydevice", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_dt_ids);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.of_match_table = my_driver_dt_ids,
},
};
module_platform_driver(my_driver);
When the kernel starts or the DT scans to match the compatible field, <span>probe()</span> is automatically called, achieving automatic driver binding.
3. Core Process of the Probe Function
The driver probe function typically includes:
-
IO Memory Mapping:
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
-
Interrupt Registration:
irq = platform_get_irq(pdev, 0);
devm_request_irq(&pdev->dev, irq, my_irq_handler, 0, "mydev", dev);
-
Clock/Reset Control:
clk = devm_clk_get(&pdev->dev, NULL);
clk_prepare_enable(clk);
-
Device Registration:
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, devno, 1);
3. Character Devices and Block Devices
Common device types in embedded systems are divided into character devices and block devices.
1. Character Devices
-
Accessed by byte, suitable for UART, GPIO, I2C, etc.
-
Registering character device numbers:
alloc_chrdev_region(&devno, 0, 1, "mychar");
-
Initializing
<span>cdev</span>and registering:
cdev_init(&my_cdev, &fops);
cdev_add(&my_cdev, devno, 1);
-
Implementing
<span>file_operations</span>:
ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
2. Block Devices
-
Provide block access for disks, Flash, eMMC.
-
Registering block device numbers:
register_blkdev(major, "myblock");
-
Initializing request queues:
blk_init_queue(my_request, &my_lock);
-
Read/write operations are scheduled through request queues, supporting asynchronous I/O.
Understanding the operation interfaces of character/block devices aids in driver development, debugging, and file system mounting optimization.
4. Device Tree Parsing
The Device Tree provides hardware abstraction and is a key mechanism in embedded Linux.
1. Device Tree Structure
-
Node: Corresponds to a hardware device.
-
Property: Describes hardware information, such as
<span>reg</span>,<span>interrupts</span>, and<span>compatible</span>. -
Root Node (/): Describes SoC architecture and bus information.
2. Kernel Parsing Process
-
The Bootloader loads the DTB into memory.
-
The kernel early calls
<span>early_init_dt_scan()</span>. -
Builds the platform_device list.
-
Matches with drivers using the compatible field and
<span>of_match_table</span>. -
Calls the driver’s
<span>probe()</span>.
3. Example Node
uart1: serial@40011000 {
compatible = "vendor,my-uart";
reg = <0x40011000 0x1000>;
interrupts = <5>;
clocks = <&uart_clk>;
};
The driver binds through the compatible field, achieving automatic hardware recognition.
5. Driver Loading and Module Management
Linux supports dynamic driver loading:
-
Automatic loading at startup: Matches platform driver based on the device tree.
-
Manual loading:
insmod my_driver.ko
modprobe my_driver
-
Unloading:
rmmod my_driver
The modular mechanism ensures system flexibility and scalability, especially suitable for resource-constrained embedded platforms.
6. Debugging and Optimization Techniques
-
Driver Debugging
-
Use
<span>dmesg</span>to view probe/remove logs. -
Use early printk for serial output to locate driver initialization issues.
-
Use
<span>/sys/bus/platform/devices</span>to check platform device status.
Performance Optimization
-
Delay initialization of non-critical drivers.
-
Control the probe order of drivers.
-
Trim unnecessary driver modules to reduce kernel size.
System Reliability
-
Analyze dependency order to avoid driver conflicts.
-
Use Device Tree to accurately describe hardware, reducing hard coding.
7. Conclusion
Through this article, you should have mastered:
-
The core concepts of the Linux driver model: Device, Driver, Bus.
-
The working mechanism of platform drivers and the implementation of the probe function.
-
The key points in developing character and block devices.
-
The principles of Device Tree and driver binding methods.
-
Driver debugging and performance optimization techniques.
Drivers and device trees are the core links from hardware to user space in embedded systems. Mastering them allows you to establish a complete understanding from kernel boot to device operation.
#Embedded Panorama Series #Linux Kernel Architecture #Performance Optimization Practice
Next Article Preview
After mastering the driver model and device tree, the file system and storage management of embedded systems are essential topics. The next article “Embedded Systems Panorama: File System and Storage Management Practice” will take you deep into Rootfs construction, Flash/NAND driver principles, file system optimization, and storage performance tuning practices.