《嵌入式操作系统》_驱动框架_20260318

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

系统启动:

  1. 内核自带的 led-class.c(LED 核心子系统) → 先执行 subsys_initcall
  2. 你自己写的 LED 硬件驱动 → 后 执行 module_init

卸载时顺序反过来:

  1. 你自己的驱动 先 module_exit
  2. 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)

5.4.9

相关推荐
律宏阔2 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔2 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴2 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千2 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei372 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei372 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei372 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh2 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW2 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux
百万蹄蹄向前冲2 小时前
双端同步!云服务器装最新Node.js v26.10全过程追踪
服务器·人工智能·node.js