1. 什么是驱动框架
(1)内核中驱动部分维护者针对每个种类的驱动设计一套驱动实现。把不同厂家的同类硬件驱动中相同部分抽出来实现好,把不同部分留出接口来具体实现。
(2)内核有统一管控系统资源的体系,让内核能够对资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。
2. 相关文件:drivers/目录
(1)drivers/leds目录,以led举例,这个文件就是内核中规定led硬件驱动应该待的地方
(2)led-class.c和led-core.c这两个文件是内核开发者提供的驱动接口。可以认为是驱动的第1部分。
(3)leds-xxxx.c。这个是厂商自己实现的驱动第二部分
3. led-class.c
led-core.c内容很少,都是头文件。
我们分一下led-class.c,最后三条代码是添加属性。

函数卸载时的执行程序:
module_exit(leds_exit);
3.1 subsys_initcall(leds_init);
将其声明的函数放到一个特定的段:.initcall4.init
subsys_initcall(leds_init);
那为什么你不用module_init?分析module_init宏,可以看出它将函数放到了.initcall6.init段中
linux内核在启动过程执行的初始化程序.initcalln.init。n的值从0到8。内核开发者在编写内核代码时只要将函数设置合适的级别,这些函数就会被链接的时候放入特定的段,内核启动时再按照段顺序去依次执行各个段即可。如下链接文件:

所以subsys_initcall(leds_init);和module_init并没有什么太大区别,只是执行顺序不一样。
cpp
core_initcall
postcore_initcall
arch_initcall
subsys_initcall ← led-class 在这里!
fs_initcall
device_initcall ← 你写的驱动在这里!
late_initcall
系统启动:
- 内核自带的 led-class.c(LED 核心子系统) → 先执行 subsys_initcall
- 你自己写的 LED 硬件驱动 → 后 执行
module_init
卸载时顺序反过来:
- 你自己的驱动 先
module_exit - led-class 核心 最后
module_exit
3.2 通过sys目录实现应用层与软件层的通信
为什么要创建类?创建之后可以通过sys目录实现应用层与软件层的通信。attribute负责创建sys文件夹中的具体内容,实现了另一种操控驱动的方式
bash
root@lxy-JIAOLONG-Series:/sys/class/gpio# ls
export gpiochip768 unexport
怎么实现的?



show:属性的读取函数
store:属性的写入函数
4. led_classdev_register
led_classdev_register 是 Linux LED 子系统的核心函数,作用是注册一个 LED 设备到内核,让系统识别并管理该 LED,同时自动在 /sys/class/leds/ 下生成对应的 sysfs 控制节点(可通过文件系统控制亮灭、亮度、闪烁)。
它是 Linux 内核为 LED 设备驱动提供的标准注册接口,替代了手动注册字符设备、创建设备节点的繁琐操作。
cpp
int led_classdev_register(struct device *parent,
struct led_classdev *led_cdev);
| 参数 | 含义 |
|---|---|
parent |
LED 设备的父设备(通常是平台设备 platform_device 的 dev 成员,填 &pdev->dev;无父设备填 NULL) |
led_cdev |
LED 设备核心描述结构体,包含 LED 的名称、亮度、控制函数等所有属性 |
核心配套结构体
cpp
struct led_classdev {
const char *name; // LED 设备名(/sys/class/leds/下的节点名,如 "my_led")
enum led_brightness brightness; // 当前亮度(0=灭,最大亮度=LED_FULL)
enum led_brightness max_brightness; // 最大亮度(默认LED_FULL=255)
// 核心:LED 硬件控制函数(亮灭/亮度调节的底层实现)
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
struct device *dev; // 内核自动创建的设备指针(无需手动赋值)
// ... 其他扩展成员(闪烁、触发器等)
};
5. sys目录中的设备文件
我们打开sys/devices目录,重点看virtuall虚拟类设备、platform平台类设备、system系统类设备。
bash
root@lxy-JIAOLONG-Series:/sys/devices# ls
amd_iommu_0 ibs_fetch kprobe pci0000:00 power tracepoint
breakpoint ibs_op LNXSYSTM:00 platform software uprobe
cpu isa msr pnp0 system virtual
看下platform/x210-led目录
bash
[root@LXY210 platform]# cd x210-led/
[root@LXY210 x210-led]# ls
driver led2 led4 power uevent
led1 led3 modalias subsystem
这就是使用__ATTR创建的交互接口。尝试一下:
bash
[root@LXY210 x210-led]# cat led1
0
[root@LXY210 x210-led]# echo 1 > led1
[root@LXY210 x210-led]# cat led1
1
ok第一颗led亮了。你甚至可以自己写一个应用程序打开sys目录下的设备文件来操控led,这里是一样的
6. 在内核中添加或者去除某个驱动
6.1去除原厂驱动
在可视化配置界面修改就可以
bash
make menuconfig



取消x210这个驱动选项,然后保存,重新make编译生成zimage
cpp
root@lxy-JIAOLONG-Series:/home/lxy/qianrushi/driver/kernel# make -j16
root@lxy-JIAOLONG-Series:/home/lxy/qianrushi/driver/kernel# cp arch/arm/boot/zImage /tftpboot/
ok已经没了

原理是什么?如下:该设备文件是否被连接进入内核,由makefile中这个配置决定。

看一眼kconfig,甚至可以配置成m来装载。

你怎么知道menuconfig中的目录层次?
6.2添加驱动框架
你怎么知道哪个模块在哪?跟内核驱动文件夹路径相似。



然而led_class下面的驱动都不是我们想要的,我们需要自己移植。
好了先保存编译。ok此时sys/class目录下已经出现了leds类

leds类中出现了如下内容:mmc0:: mmc1:: mmc2:: mmc3::是因为在mmc驱动中调用了led设备注册程序,所以不必理会

6.3代码实现驱动
基于内核的驱动框架来完成。如下图标,红色框是系统提供的框架,蓝色框是厂商添加。

我们这里参考s3c24xx.c文件。
6.3.1 添加描述
cpp
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("LXY <qq1291280106@126.com>"); // 描述模块的作者
MODULE_DESCRIPTION("LED driver for LXY210"); // 描述模块的介绍信息
MODULE_ALIAS("lxy210_led"); // 描述模块的别名信息
6.3.2 构造析构
cpp
#define LED_ON 1
#define LED_OFF 0
static struct led_classdev my_led_cdev1; // 定义一个led_classdev结构体变量
static void lxy_led1_set(struct led_classdev *led_cdev,enum led_brightness value)
{
printk(KERN_INFO "LED1 brightness set to %d\n", value);
writel(0x11111111, S5PV210_GPJ0CON);
if (value == LED_ON) {
// 这里可以添加代码来控制LED亮起
writel((readl(S5PV210_GPJ0DAT) & ~(1<<3)), S5PV210_GPJ0DAT); // 将GPH3DAT寄存器映射到内核地址空间,并写入0x1来点亮LED
} else {
// 这里可以添加代码来控制LED熄灭
writel((readl(S5PV210_GPJ0DAT) | (1<<3)), S5PV210_GPJ0DAT); // 将GPH3DAT寄存器映射到内核地址空间,并写入0x0来熄灭LED
}
}
static int lxy210_led_init(void)
{
int ret = -1;
my_led_cdev1.name = "led1"; // 设置LED设备的名称
my_led_cdev1.brightness_set = lxy_led1_set; // 设置LED亮度设置函数指针(如果需要实现亮度控制功能,可以定义一个函数并将其地址赋值给这个成员)
ret = led_classdev_register(NULL, &my_led_cdev1);
if (ret < 0) {
return ret;
}
return 0;
}
static void lxy210_led_exit(void)
{
writel(0x11111111, S5PV210_GPJ0CON);
writel(((1<<3)|(1<<4)|(1<<5)), S5PV210_GPJ0DAT);
led_classdev_unregister(&my_led_cdev1);
}
module_init(lxy210_led_init);
module_exit(lxy210_led_exit);
6.3.3makefile
bash
KERN_DIR = /home/lxy/qianrushi/driver/kernel
obj-m += leds-lxy210.o
all:
make -C $(KERN_DIR) M=`pwd` modules
arm-linux-gcc app.c -o app
cp:
cp leds-lxy210.ko /nfs_rootfs/dri/
cp app /nfs_rootfs/dri/
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf app
6.3.4代码验证
cpp
[root@LXY210 ~]# cd /dri
[root@LXY210 dri]# insmod leds-lxy210.ko
[root@LXY210 dri]# ls /sys/class/leds/
lxy210_led mmc0:: mmc1:: mmc2:: mmc3::
[root@LXY210 lxy210_led]# ls
brightness max_brightness power subsystem uevent
为什么会出现这些属性?
led类在初始化时,均会执行该代码
cpp
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
return 0;
}
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
lxy210_led文件夹:class_create创建的
brightness和max_brightness文件:__ATTR创建的
uevent / subsystem / power。来自 Linux 内核设备模型底层,所有设备都会自动生成。
6.3.5 文件操作
当我们修改brightness文件时,my_led_cdev.brightness_set = lxy_led_set;该函数执行。为什么?
bash
[root@LXY210 lxy210_led]# echo 1 > brightness
[ 3465.976317] LED brightness set to 1
brightness 文件 绑定了一个内核写回调函数 brightness_store
当你写文件时 → 内核调用 brightness_store
这个函数内部 → 直接调用 led_cdev->brightness_set
bash
用户:echo 1 > /sys/class/leds/lxy210_led/brightness
↓
内核 VFS 层:找到文件对应的 store 函数
↓
led-class.c:brightness_store()
↓
led-class.c:led_set_brightness()
↓
led-class.c:led_cdev->brightness_set(led_cdev, value)
↓
你的驱动:lxy_led_set(struct led_classdev *led_cdev, enum led_brightness value)