《嵌入式操作系统》_驱动框架_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_devicedev 成员,填 &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

相关推荐
淮北也生橘122 小时前
Linux应用开发:全链路 OTA 升级架构
linux·架构·ota·linux应用开发
小黑要努力2 小时前
json-c安装以及amixer使用
linux·运维·json
JiMoKuangXiangQu2 小时前
Linux:ARM64 启动流程
linux·arm64 boot
sdm0704272 小时前
Linux-进程2
运维·服务器
柯儿的天空2 小时前
【OpenClaw 全面解析:从零到精通】第 016 篇:OpenClaw 实战案例——代码开发助手,从代码生成到部署自动化的全流程
运维·人工智能·ai作画·自动化·aigc·ai写作
我科绝伦(Huanhuan Zhou)2 小时前
从自动化到自主化—AI Agent引领的运维范式变革
运维·人工智能·自动化
TroubleMakerQi3 小时前
[虚拟机环境配置]07_Ubuntu中安装vscode教程
linux·人工智能·vscode·ubuntu
源远流长jerry3 小时前
RDMA vs 传统以太网:寻址粒度为何决定性能天花板
linux·网络
zzzsde3 小时前
【Linux】进程控制(1):进程创建&&进程终止
linux·运维·服务器