ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

01-设备驱动概述与开发环境构建

01-设备驱动概述与开发环境构建 1.1 设备驱动的作用计算机系统可以抽象为三层硬件、内核含驱动、应用程序。硬件不懂读写文件应用程序也不懂寄存器位域中间必须有人翻译。设备驱动就是这段翻译层对上向内核其他子系统或用户空间提供统一的抽象接口字符设备文件、块设备、网络接口、sysfs 属性、input 事件……。对下把抽象操作翻译成具体硬件动作读写寄存器、搬运 DMA 缓冲、收发总线报文、处理中断。驱动不是给某个芯片写一段代码这么简单。现代 Linux 驱动真正的职责是发现设备设备树 / ACPI / 总线枚举 / 软件节点告诉内核这里有个设备。申请资源内存映射寄存器、中断号、时钟、复位线、GPIO、regulator、DMA 通道。注册到子系统字符设备、块设备、网络设备、input、RTC、IIO、DRM……处理事件中断、工作队列、定时器、NAPI 轮询。生命周期管理probe/remove、系统睡眠/唤醒、runtime PM、热插拔、错误恢复。驱动开发的核心矛盾硬件千差万别内核接口必须统一驱动就是把这个矛盾收敛到一处。1.2 无操作系统时的设备驱动在裸机或裸机循环 中断环境下驱动通常长这样/* 裸机 LED 闪烁无任何抽象直接操作寄存器 */ #define GPIO_BASE 0x0209C000UL #define GPIO_DIR (*(volatile unsigned long *)(GPIO_BASE 0x04)) #define GPIO_DATA (*(volatile unsigned long *)(GPIO_BASE 0x00)) static void led_on(void) { GPIO_DATA | (1u 3); } static void led_off(void) { GPIO_DATA ~(1u 3); } int main(void) { GPIO_DIR | (1u 3); /* 配置为输出 */ for (;;) { led_on(); delay_ms(500); led_off(); delay_ms(500); } }裸机驱动的特点也是它的局限无并发模型只有一个执行流主循环 中断加锁问题靠关中断解决。无内存保护一个野指针可以覆盖任何地址包括驱动自己。无统一接口每换一个外设应用程序就要改代码。资源独占一个外设只能被一段代码使用。无设备模型设备是否存在、是否就绪全靠人写死。理解裸机驱动的价值在于它让你看清驱动最终要做什么。后面所有 Linux 驱动框架本质都是在裸机操作外面套上内核提供的并发、资源、生命周期和抽象层。1.3 有操作系统时的设备驱动引入操作系统后驱动的位置发生了变化操作系统为驱动提供的东西能力提供的机制对应源码位置并发控制自旋锁、mutex、RCU、原子操作、完成量kernel/locking/、include/linux/spinlock.h、include/linux/rcupdate.h内存管理kmalloc/vmalloc/页分配器/ioremapmm/、include/linux/slab.h中断管理request_threaded_irq、IRQ domain、软中断kernel/irq/、include/linux/interrupt.h时间管理定时器、hrtimer、延时、jiffieskernel/time/、include/linux/timer.h设备模型struct device/driver/bus/classdrivers/base/资源管理devm_*托管分配drivers/base/devres.c电源管理dev_pm_ops、runtime PM、genpddrivers/base/power/调试printk、ftrace、debugfs、KASANkernel/trace/、mm/kasan/同时操作系统也给驱动加了约束不能睡的地方不能睡、用户指针必须校验、错误路径必须回滚、中断上下文不能睡眠。1.4 Linux 设备驱动1.4.1 设备的分类及特点Linux 把设备分成三大类这个分类从 2.6 一直延续到 7.2.5类别抽象接口访问单位典型例子本手册章节字符设备/dev/xxxfile_operations字节流串口、LED、按键、EEPROM、RTC第 6 章块设备/dev/sdX、/dev/ram0block_device_operations扇区512B 或 4K磁盘、SSD、U 盘、ramdisk第 13 章网络设备eth0/wlan0net_device_ops数据包网卡、Wi-Fi、虚拟网卡第 14 章补充说明7.2.5 内核很多设备其实同时属于多个子系统。例如 USB 无线网卡USB 设备驱动 网络设备驱动一块带 MMC 控制器的 SoCplatform 驱动 MMC 子系统 块设备层。还有一批非字符/非块/非网络的子系统它们最终也大多落到字符设备或 sysfs 上input/dev/input/eventX、IIO/dev/iio:deviceX、DRM/dev/dri/cardX、V4L2/dev/videoX、ALSA/dev/snd/*。现代 Linux 更强调用现成的子系统而不是自己造字符设备。造一个私有 ioctl 字符设备是最容易出错的方案能用 input/IIO/RTC/GPIO/leds/hwmon 就优先用它们。1.4.2 驱动与整个软硬件系统的关系关键认识驱动永远站在子系统接口和硬件接口之间。写驱动之前先问两个问题这个设备应该挂到哪个子系统决定你要实现哪套 ops这个设备的硬件资源从哪来决定你要用设备树、ACPI、PCI/USB 枚举还是软件节点1.4.3 驱动开发的重点与难点原书列出的重点难点在 7.2.5 上依然成立而且被强化了难点4.0 时代的表现7.2.5 的表现并发与竞态自旋锁/mutex/RCU增加了 KCSAN、lockdep 更严格的检查原子上下文规则更明确中断处理上半部/下半部、tasklet主推 threaded IRQtasklet 只用于维护旧代码内存与 DMAdma_alloc_coherent/cache 一致性加上 IOMMU、DMA API debug、dma_map_sgtable资源生命周期goto错误处理devm_*托管 cleanup.h的__free()自动清理硬件描述ARM 板级文件 部分设备树设备树/ACPI 全面取代板级文件overlay 动态加载电源管理系统睡眠为主runtime PM genpd OPP interconnect 组合拳调试printk GDB ftrace/perf/eBPF/KASAN/KCSAN/lockdep/pstore上游协作邮件列表补丁邮件列表补丁 b4checkpatch 更严格的 API 约定1.5 开发环境构建1.5.1 PC 上的 Linux 环境推荐 Ubuntu/Debian x86_64安装工具链与实验依赖sudo apt update sudo apt install -y \ build-essential bc bison flex libssl-dev libelf-dev libncurses-dev \ dwarves cpio kmod \ gcc-aarch64-linux-gnu \ qemu-system-x86 qemu-system-arm qemu-utils \ device-tree-compiler \ busybox-static e2fsprogs dosfstools \ i2c-tools spi-tools gpiod libgpiod-dev \ python3 perl tar xz-utils逐项说明bison flex内核 Kconfig/词法分析器生成需要7.2.5 构建时缺了会直接报错。libelf-dev dwarves(pahole)CONFIG_DEBUG_INFO_BTF需要没有 pahole 时 BTF 会编译失败。libssl-dev模块签名、sign-file需要。qemu-system-x86x86_64 实验主力qemu-system-arm提供qemu-system-aarch64。device-tree-compiler提供dtc/fdtget/fdtdump第 18 章必备。busybox-static制作 initramfs 的 shell 与基本命令。i2c-tools/gpiod用户空间验证 I²C 与 GPIO 设备。验证安装gcc --version make --version qemu-system-x86_64 --version qemu-system-aarch64 --version dtc --version busybox | head -11.5.2 QEMU 实验平台本手册的全部实验都设计为不需要真实开发板。QEMU 提供三类能力虚拟平台qemu-system-x86_64PC 风格含 PCI/virtio与qemu-system-aarch64 -machine virt嵌入式风格含设备树。虚拟外设virtio-blk、virtio-net、virtio-console、virtio-input、virtio-i2c、virtio-spi、virtio-gpio、USB xHCI/存储/键盘等。软件模拟外设内核自带的gpio-sim、i2c-stub、spi-loopback-test、dummy_hcd、brd、null_blk、gpio-virtuser完全不依赖 QEMU 是否支持该设备型号。查看当前 QEMU 支持哪些设备这是最可靠的判断方式不要凭记忆猜设备名qemu-system-x86_64 -device help | grep -Ei virtio|i2c|spi|gpio|usb|xhci qemu-system-aarch64 -machine virt -device help | grep -Ei virtio|i2c|spi|gpio完整的环境搭建、initramfs 制作、9p 共享、GDB 调试流程见 《22-QEMU实验总览》。本章只需要确认一件事你能编译内核并进入 QEMU 的 shell。7.2.5 的重要变化阅读源码前必读本版本的设备模型头文件已经拆分不能再按旧书去include/linux/device.h里找全部定义内容7.2.5 路径struct class、class_create()、class_register()include/linux/device/class.hstruct bus_typeinclude/linux/device/bus.hstruct device_driver、module_driver()include/linux/device/driver.hstruct of_device_id、platform_device_id、i2c_device_idinclude/linux/device-id/*.hstruct device、device_create()、devm_*()include/linux/device.h也就是说include/linux/device-id/of.h里才是struct of_device_id的定义include/linux/device/driver.h里才有of_match_table字段。旧书乃至很多网上博客里写的include/linux/mod_devicetable.h现在是汇总转发头只做#include device-id/*.h。编辑代码的硬性要求内核代码必须用Tab 缩进8 列必须能通过scripts/checkpatch.pl --no-tree --strict -f your_driver.c1.6 设备驱动 Hello WorldLED 驱动1.6.1 无操作系统时的 LED 驱动见 1.2 节的裸机代码。要点直接访问物理地址 忙等延时没有任何资源申请与释放的概念。1.6.2 Linux 下的 LED 驱动在 Linux 里点亮一个 LED有三条路从最贴近硬件到最贴近子系统路径 A裸字符设备 寄存器操作教学用理解 ioctl 与并发不申请 GPIO 子系统直接把一个物理地址ioremap进来通过 ioctl 控制。第 6 章会完整实现。它适合理解字符设备框架但不适合产品代码。路径 BGPIO 子系统 自写 platform 驱动推荐理解驱动模型/* hello-led.c —— 基于 GPIO 子系统的最小 LED 平台驱动Linux 7.2.5 */ #include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/gpio/consumer.h #include linux/mod_devicetable.h struct hello_led { struct gpio_desc *led; }; static int hello_led_probe(struct platform_device *pdev) { struct hello_led *priv; priv devm_kzalloc(pdev-dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; /* 从设备树 / 软件节点 / gpio-sim 提供的 led-gpios 属性取 GPIO */ priv-led devm_gpiod_get(pdev-dev, led, GPIOD_OUT_LOW); if (IS_ERR(priv-led)) return dev_err_probe(pdev-dev, PTR_ERR(priv-led), failed to get led gpio\n); platform_set_drvdata(pdev, priv); dev_info(pdev-dev, hello led probed, gpio %d\n, desc_to_gpio(priv-led)); return 0; } static void hello_led_remove(struct platform_device *pdev) { dev_info(pdev-dev, hello led removed\n); } static const struct of_device_id hello_led_of_match[] { { .compatible hello,led }, { } }; MODULE_DEVICE_TABLE(of, hello_led_of_match); static struct platform_driver hello_led_driver { .probe hello_led_probe, .remove hello_led_remove, .driver { .name hello-led, .of_match_table hello_led_of_match, }, }; module_platform_driver(hello_led_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Kevin); MODULE_DESCRIPTION(Minimal LED platform driver for QEMU experiments);注意 7.2.5 的签名细节旧书里不是这样struct platform_driver的.remove返回void不是int。.probe参数是struct platform_device *。设备匹配优先用.of_match_table设备树或.id_tableplatform_device_id而不是靠名字字符串。使用devm_gpiod_get()GPIOD_OUT_LOW不要再用旧书的gpio_request()/gpio_direction_output()/gpio_set_value()这些是已废弃的整数 GPIO 接口新代码禁止使用。dev_err_probe()是现代 probe 函数报告错误的推荐方式它会把-EPROBE_DEFER记录为 debug 级别而不刷屏。# Makefile obj-m hello-led.o KDIR ? ~/study/build/linux-7.2.5_out PWD : $(CURDIR) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean路径 C直接用内核现成的 LED 子系统内核已有drivers/leds/子系统设备树写一个leds { led { gpios ...; } }节点leds-gpio驱动就会创建/sys/class/leds/name/brightness。产品代码几乎都走这条路。实验 1-A用 gpio-sim hello-led 点亮虚拟 LEDx86_64 QEMUgpio-sim 是内核自带的 configfs GPIO 模拟器Documentation/admin-guide/gpio/gpio-sim.rst在 QEMU 里创建虚拟 GPIO 芯片然后把我们的驱动绑上去。内核配置make menuconfig或直接改.configCONFIG_CONFIGFS_FSy CONFIG_GPIO_SIMm # 注意gpio-sim 只能是模块依赖 CONFIGFS_FS 与 IRQ_SIM CONFIG_GPIOLIBy CONFIG_OFy CONFIG_OF_OVERLAYy在 guest 中mount -t configfs none /sys/kernel/config cd /sys/kernel/config/gpio-sim mkdir -p gpio-device0/bank0 echo 8 gpio-device0/bank0/num_lines echo 1 gpio-device0/live # 实例化虚拟芯片 ls /sys/class/gpio/ # 会看到 gpiochipN ls /dev/ # 会看到 gpiochipN 字符设备此时你已经有了虚拟 LED 引脚。把上面的hello-led.c编译成模块用gpio-sim提供的 line 名字或设备树 overlay 绑定。最简做法是用gpio-virtuser或直接写一个platform_device的软件节点见第 12 章。实验 1-B用 QEMU 真实 virtio 设备做 Hello World如果只想先感受设备出现在内核里用 virtio-console 最快qemu-system-x86_64 -m 2G -smp 2 \ -kernel $KBUILD/arch/x86/boot/bzImage \ -initrd $INITRD \ -append consolettyS0 -nographic \ -device virtio-serial-pciguest 中dmesg | grep -i virtio ls /sys/bus/virtio/devices/ ls /sys/bus/virtio/drivers/你会看到virtio-pci、virtio_console驱动与设备在 sysfs 中的绑定关系——这就是 7.2.5 的设备模型在说话。观察点dmesg中能看到hello led probed说明 probe 被调用、GPIO 申请成功。ls /sys/bus/platform/drivers/hello-led/下出现指向设备的符号链接说明驱动与设备已绑定。rmmod hello_led后再insmodprobe/remove 会再次成对出现。故意把.compatible写成不匹配的值观察驱动不 probe说明匹配失败。常见错误现象原因处理insmod: ERROR: could not insert module: Invalid module format模块与内核版本/配置不一致模块必须用同一个O输出目录编译见第 4 章probe 不执行dmesg无任何输出设备树compatible与.of_match_table不匹配用cat /sys/firmware/devicetree/base/.../compatible核对-EPROBE_DEFER反复出现GPIO/时钟/regulator provider 尚未就绪正常现象用dev_err_probe()抑制噪音卸载模块时 oopsremove里释放了devm_已托管的资源托管资源不要手工释放QEMU 中看不到gpiochipCONFIG_GPIO_SIM未开或没mkdirlive1回到实验 1-A 步骤小结驱动的本质是抽象接口 ↔ 硬件动作的翻译层操作系统为其提供并发、内存、中断、时间、设备模型和资源管理。设备分为字符/块/网络三大类但现代驱动应优先复用成熟子系统。7.2.5 的源码结构与 4.0 已有明显差异设备模型头文件拆分、device-id 目录必须按当前源码阅读。无开发板也能学驱动QEMU 虚拟设备 内核自带软件模拟外设覆盖了本手册绝大多数实验。
返回列表