Character Device Driver Foundations
Developing a Linux character device driver generally follows a standard sequence of operations:
- Acquire a major device number, either statically assigned or dynamically allocated by the kernel.
- Declare a
file_operationsstructure to define the driver's supported interactions. - Implement the requisite handlers (such as
open,read,write, andrelease) and bind them to thefile_operationsinstance. - Register the driver with the kernel using
register_chrdev. - Provide an entry function triggered upon module insertion to execute the registration routine.
- Provide an exit function triggered upon module removal to invoke
unregister_chrdevand clean up resources. - Automatically generate device nodes in
/devusingclass_createanddevice_createto expose device information to userspace.
Driver Layering for Multi-Board Support
To design an LED driver that seamlessly supports different hardware platforms, a layered architecture is essential. The implementation must be divided into a hardware-independent upper layer (the core driver logic) and a hardware-dependent lower layer (board-specific operations).
By adopting an object-oriented approach, we abstract the hadrware interactions into a shared interface:
struct led_hw_ops {
int (*setup)(int id); /* Initialize the specified LED, id represents the LED index */
int (*set_state)(int id, bool is_on); /* Control the LED state, id is the LED index, is_on determines active status */
};
Each board-specific module implements its own version of the led_hw_ops structure, which the generic upper-layer driver queries and invokes.
Layered Implementation Code
led_core.c (Hardware-Independent Layer)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include "led_hw_ops.h"
#define MAX_LEDS 2
static int dev_major;
static struct class *led_dev_class;
static struct led_hw_ops *hw_callbacks;
static int led_open(struct inode *inode, struct file *filp)
{
int minor_id = iminor(inode);
hw_callbacks->setup(minor_id);
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *user_buf, size_t count, loff_t *f_pos)
{
unsigned char state_val;
int minor_id = iminor(file_inode(filp));
if (copy_from_user(&state_val, user_buf, 1))
return -EFAULT;
hw_callbacks->set_state(minor_id, state_val);
return 1;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
static int __init led_core_init(void)
{
int i;
dev_major = register_chrdev(0, "generic_led", &led_fops);
led_dev_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(led_dev_class)) {
unregister_chrdev(dev_major, "generic_led");
return PTR_ERR(led_dev_class);
}
for (i = 0; i < MAX_LEDS; i++) {
device_create(led_dev_class, NULL, MKDEV(dev_major, i), NULL, "myled%d", i);
}
hw_callbacks = fetch_led_hw_ops();
return 0;
}
static void __exit led_core_exit(void)
{
int i;
for (i = 0; i < MAX_LEDS; i++) {
device_destroy(led_dev_class, MKDEV(dev_major, i));
}
class_destroy(led_dev_class);
unregister_chrdev(dev_major, "generic_led");
}
module_init(led_core_init);
module_exit(led_core_exit);
MODULE_LICENSE("GPL");
led_hw_ops.h (Abstraction Header)
#ifndef _LED_HW_OPS_H
#define _LED_HW_OPS_H
#include <linux/types.h>
struct led_hw_ops {
int (*setup)(int id);
int (*set_state)(int id, bool is_on);
};
struct led_hw_ops *fetch_led_hw_ops(void);
#endif
board_impl.c (Hardware-Dependent Layer)
#include <linux/module.h>
#include <linux/kernel.h>
#include "led_hw_ops.h"
static int demo_led_setup(int id)
{
printk(KERN_INFO "Initializing LED %d on demo board\n", id);
return 0;
}
static int demo_led_set_state(int id, bool is_on)
{
printk(KERN_INFO "Setting LED %d state to %s\n", id, is_on ? "ACTIVE" : "INACTIVE");
return 0;
}
static struct led_hw_ops demo_ops = {
.setup = demo_led_setup,
.set_state = demo_led_set_state,
};
struct led_hw_ops *fetch_led_hw_ops(void)
{
return &demo_ops;
}
app_test.c (Userspace Testing Tool)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int dev_fd;
unsigned char brightness;
if (argc != 3) {
fprintf(stderr, "Usage: %s <device_node> <on|off>\n", argv[0]);
return EXIT_FAILURE;
}
dev_fd = open(argv[1], O_RDWR);
if (dev_fd < 0) {
perror("Failed to open device");
return EXIT_FAILURE;
}
if (strcmp(argv[2], "on") == 0) {
brightness = 1;
} else {
brightness = 0;
}
write(dev_fd, &brightness, 1);
close(dev_fd);
return EXIT_SUCCESS;
}