Linux 驱动框架设计详解Linux 内核通过分离设备信息与驱动逻辑解耦、分层子系统核心层 具体驱动以及总线–设备–驱动模型含 Platform、设备树等使同一套驱动逻辑可在不同板级配置上复用。下文从设计思想、层次结构、匹配机制、常见子系统与开发流程进一步归纳I2C/SPI 子设备、中断与延后处理、DMA及电源管理与设备模型的衔接方式。目录分离、分层与总线模型Platform、物理总线与设备树示例DM9000 网卡自用户空间到硬件的层次设备描述与 probe 中的资源获取子系统分层TTY 与 Input总线匹配与 Platform 注册流程probe、remove 与 devm_ 资源管理驱动类型与子系统对照典型 SoC 外设驱动开发流程用户空间、设备节点与 sysfsI2C 与 SPI 子设备驱动中断上下文、tasklet 与工作队列DMA一致映射、流式映射与 IOMMU电源管理与设备模型参考链接分离、分层与总线模型分离驱动与设备解耦驱动侧实现通用逻辑如open/read寄存器基址、中断号等由设备描述板级文件、设备树节点等单独给出。驱动通过platform_get_resource()等接口在运行时取得资源避免在源码中硬编码具体硬件。分层子系统核心层对字符设备、Input、V4L2、网络等内核提供子系统核心层负责通用流程如输入事件的上报与分发与 SoC / 外设相关的寄存器操作由下层驱动实现减少重复代码。总线与设备模型设备与驱动向总线注册由总线的匹配规则名称、compatible等决定绑定关系匹配成功后调用驱动的probe()完成初始化。Platform、物理总线与设备树总线–设备–驱动是通用模型除Platform外PCI/PCIe、USB、I2C、SPI、AMBA 等也各自有bus_type与匹配规则例如 PCI 用厂商/设备 ID、设备树或 ACPII2C 常用of_match_table与从机地址。片上外设多走 Platform外接芯片则常挂在 I2C/SPI 等控制器之下由控制器驱动 子设备驱动或 MFD、子节点协同描述。Platform 总线面向 SoC 内无独立线缆的外设platform_device描述资源platform_driver承载驱动逻辑把板级差异从驱动源码中剥离。设备树板级描述通常写在.dts/.dtsi中经dtc编译为二进制.dtb由引导加载程序在启动时交给内核。节点中常见compatible匹配驱动、reg寄存器区间、interrupts/interrupt-parent中断路由、clocks/resets时钟与复位依 SoC 绑定方式而定等。驱动侧除platform_get_resource()外还会用到of_property_read_*、devm_platform_ioremap_resource()等接口解析属性。示例DM9000 网卡设备侧在设备树或板级描述中给出寄存器映射、中断、MAC 等与板子相关的信息。驱动侧实现platform_driver在probe()中通过platform_get_resource()或of_*获取资源完成芯片初始化并注册为网络设备。迁移硬件平台多数情况下只需更新设备树或板级描述驱动源文件可保持不变。自用户空间到硬件的层次从抽象上可区分为硬件相关层直接访问寄存器、DMA、中断控制器封装不同芯片差异。内核通用服务内存分配、中断注册、调度等驱动通过kmalloc、request_irq等使用。设备模型与总线struct device、struct bus_type等统一设备视图支撑枚举、依赖关系与电源管理等。自顶向下示意--------------------------- | 用户空间应用 | | open/read/write /dev/xxx | -------------------------- | v --------------------------- | 系统调用接口层 | | sys_open / sys_read ... | -------------------------- | v --------------------------- | 设备驱动核心/子系统 | | 字符/块/网络/Input 等 | -------------------------- | v --------------------------- | 硬件抽象层 (HAL) | | 寄存器读写 / DMA / 中断 | -------------------------- | v --------------------------- | 硬件 (SoC/外设) | ---------------------------上图中的「HAL」仅为便于对照的示意分层Linux 内核文档并不强调该缩写实际代码往往体现为具体子系统驱动与 SoC 厂商提供的时钟、引脚pinctrl、电源域等支撑代码的组合。设备描述与 probe 中的资源获取硬编码地址与中断不利于移植#defineLED_REG0x12340000#defineLED_IRQ25更常见的做法是在设备树中描述节点在probe中动态获取staticintmy_led_probe(structplatform_device*pdev){structresource*res;intirq;resplatform_get_resource(pdev,IORESOURCE_MEM,0);irqplatform_get_irq(pdev,0);/* 使用 res、irq 初始化硬件 */return0;}子系统分层TTY 与 InputTTY可粗分为应用与 tty 层 → TTY 核心线路规程、termios→ 具体串口驱动寄存器级操作。Input底层驱动采集 GPIO 等硬件状态核心层提供input_allocate_device、input_report_key等Event Handler 层对应/dev/input/eventX等节点。多数输入类驱动只需通过核心层 API 上报事件而不必自行实现字符设备细节。总线匹配与 Platform 注册流程内核用Bus / Device / Driver管理硬件各bus_type上的match回调将设备与驱动配对。structbus_type{constchar*name;int(*match)(structdevice*dev,structdevice_driver*drv);/* ... */};platform_device描述资源设备树场景下常由内核根据.dts生成platform_driver提供probe/remove。任一侧注册后总线遍历对端并调用match成功则执行probe()。步骤触发方主要动作1platform_device_register()设备加入 platform 设备列表触发device_attach()2platform_driver_register()驱动加入驱动列表触发driver_attach()3device_attach()/driver_attach()调用bus-match()成功则调用probe()设备树节点示例led0 { compatible my,led; reg 0x0 0x100; gpios gpio0 3 GPIO_ACTIVE_HIGH; label user-led; };驱动中通过of_device_id表声明支持的compatible在probe内解析reg、GPIO、中断等资源并完成初始化。probe、remove 与 devm_ 资源管理probe()在设备与驱动绑定成功后执行负责映射 MMIO、申请 IRQ、注册子系统等失败时应返回负错误码并释放已在该路径上申请的资源。remove()或shutdown()等视场景而定在解绑或关机路径上调用应与probe()对称注销设备、释放 IRQ、取消映射等避免卸载模块或设备消失后仍持有资源。devm_*系列如devm_request_irq、devm_ioremap_resource将资源生命周期挂接到struct device设备卸载时由内核统一回收减少手写错误路径上的遗漏在现代驱动中很常见。驱动类型与子系统对照类型核心结构/概念典型接口或子系统典型设备字符设备cdev,file_operationsalloc_chrdev_regioncdev_init/cdev_add老接口register_chrdev仍可见于遗留代码串口、传感器块设备gendisk,request_queueadd_disk,blk_init_queue硬盘、SSD网络设备net_device,ethtool_opsregister_netdev, NAPI以太网、无线输入设备input_devinput_register_device,input_report_key按键、触摸屏RTCrtc_devicertc_device_register,rtc_class_opsRTC 芯片帧缓冲fb_info,fb_opsregister_framebufferLCD 控制器Miscmiscdevicemisc_register小型杂项设备典型 SoC 外设驱动开发流程在.dts中增加设备节点写明compatible、reg、中断等。定义platform_driver实现probe/remove在of_match_table中列出支持的compatible。在probe中用platform_get_resource()、of_*等获取资源按需request_irq、申请 DMA初始化硬件并向对应子系统注册如input_register_device。实现寄存器访问、中断处理等硬件相关代码。使用module_platform_driver()等宏注册platform_driver。模块边界通过MODULE_DEVICE_TABLE(of, ...)等导出别名表使模块可按设备树自动加载modprobe行为依赖发行版配置。调试时可用dmesg、/sys/bus/platform/devices/等查看设备是否注册、驱动是否绑定。用户空间、设备节点与 sysfs应用通过设备文件如/dev/input/eventX、自定义cdev注册的节点配合ioctl / read / write / mmap等与驱动交互具体能力由子系统与file_operations决定。sysfs挂载于/sys暴露struct device、struct bus_type等内核对象下的属性驱动可通过DEVICE_ATTR/DEVICE_ATTR_RO、device_add_groups等机制或沿用子系统已有属性向用户空间导出状态或可写参数。udev根据 sysfs 与规则文件创建/dev下节点、设置权限与符号链接替代手工mknod的静态维护方式。I2C 与 SPI 子设备驱动片上I2C/SPI 控制器常作为Platform或 AMBA 等设备出现先由控制器驱动注册struct i2c_adapter/struct spi_master再在总线上枚举子设备。子设备在设备树中多挂在控制器节点下由内核在控制器probe完成后创建对应的struct i2c_client/struct spi_device并匹配i2c_driver/spi_driver。I2C驱动实现probe(struct i2c_client *client, const struct i2c_device_id *id)或设备树场景下主要依赖of_match_table。设备树里从机地址一般在子节点reg中给出传输可用i2c_master_send/i2c_master_recv、i2c_transfer或符合芯片手册时走SMBus辅助函数。注册常用module_i2c_driver()。无设备树的老平台曾用板文件i2c_board_info静态声明从机新平台以 DT 为主。SPI驱动实现probe(struct spi_device *spi)。设备树中常见spi-max-frequency、reg片选索引、spi-cpol/spi-cpha等属性片选除硬件 CS 外也可能经cs-gpios描述为 GPIO。数据传输多通过spi_sync()、spi_write()/spi_read()等在probe或open路径上可调用spi_setup()应用mode、max_speed_hz等到控制器。注册常用module_spi_driver()。控制器与子设备驱动分层控制器负责时序与 FIFO子设备驱动只面向具体芯片协议。同一控制器节点下可有多个子节点对应多条总线上的从机。中断上下文、tasklet 与工作队列硬中断处理函数top half在中断上下文执行应尽量短小只做读状态、清中断源、调度延后处理等不可睡眠不能调用可能阻塞的 API如带GFP_KERNEL的内存分配、mutex、显式睡眠。需要更多工作时典型做法包括tasklet或tasklet_setup在软中断语境下运行仍不能睡眠适合较短延后逻辑同一 tasklet 实例不会并行重入。workqueuestruct work_struct任务在内核线程中执行处于进程上下文可以睡眠适合 I/O、与上层交互、耗时操作。常用queue_work()/schedule_work()。request_threaded_irq()上半部只做极短处理下半部在专用 irq 线程里跑语义上接近「可睡眠的下半部」许多设备驱动采用。probe()一般运行在可睡眠的进程上下文内核线程路径可以msleep、持mutex、用GFP_KERNEL分配内存、注册request_irq等。注意在probe里注册的中断处理函数仍须遵守中断上下文约束若在中断里要唤醒阻塞的read()常配合wait_queue在中断里仅wake_up()具体拷贝数据放在read或 workqueue 中完成。锁的选取中断与进程上下文共享数据时常使用spin_lock_irqsave/spin_unlock_irqrestore保护中断里也会触达的临界区仅在进程上下文之间协调时可用mutex。DMA一致映射、流式映射与 IOMMUDMA APIinclude/linux/dma-mapping.h把「设备可见的 DMA 地址」与 CPU 虚拟地址的映射关系抽象出来由平台 / IOMMU 代码实现具体行为。一致映射coherent / consistentdma_alloc_coherent()及devm_dma_alloc_coherent()分配一块CPU 与设备对非缓存或硬件一致性有约定的内存适合小体积、长期驻留的控制结构、环形容器描述符等。使用完毕后dma_free_coherent()成对释放。流式映射streaming针对已有的缓冲区如网络 skb 内 payload、用户write下来的页用dma_map_single()/dma_map_page()/dma_map_sg()得到dma_addr_t在设备完成本次 DMA 前不应假设 CPU 仍可随意写该缓冲区除非按规范做dma_sync_single_for_cpu()等同步。传输结束后dma_unmap_*()。错误使用易导致缓存一致性问题或 IOMMU 故障。IOMMU/SMMU在启用 IOMMU 的平台上设备看到的地址常为IOVA由 IOMMU 映射到物理页驱动仍通过上述 DMA API 获取dma_addr_t不要假设其等于物理地址。调试时可关注CONFIG_DMA_API_DEBUG及发行版相关选项对 DMA API 误用的检测。电源管理与设备模型系统级休眠suspend/hibernate路径上内核按设备树依赖与注册顺序调用各设备的struct dev_pm_ops中的回调如suspend/resume、freeze/thaw、poweroff/restore等视电源状态而定。驱动需在休眠前保存必要硬件上下文、关闭时钟或进入低功耗模式在恢复时还原与runtime PM协同时要避免双重关断或引用已休眠父设备。Runtime PM运行时电源管理基于引用计数设备空闲时可 clock-gate 或断电有访问请求时再上电。驱动侧常见调用包括pm_runtime_enable()、在打开设备或开始传输时pm_runtime_get_sync()或_get、结束时pm_runtime_put()/pm_runtime_put_autosuspend()并可用pm_runtime_set_autosuspend_delay()防抖。总线类型与子系统可能对struct device封装默认的 runtime PM 行为具体需对照所用子系统文档。CONFIG_PM/CONFIG_PM_SLEEP关闭时部分回调或整个电源路径在编译期被省略驱动中可用#ifdef CONFIG_PM_SLEEP等包裹与休眠相关的代码。父子设备、device_link等会影响挂起/恢复的先后顺序复杂 SoC 上需与 clock、pinctrl、regulator 等框架一并设计。参考链接内核驱动接口总览随版本更新Driver API — The Linux Kernel documentation设备树使用模型Usage model — DevicetreeDMA API 说明Dynamic DMA mapping using the generic DMA API电源管理Power Management — The Linux Kernel documentation背景阅读第三方文章https://mp.weixin.qq.com/s/foPlc0ckMqlbTND3cDmivg本文用于梳理常见结构与术语具体行为与 API 以当前内核版本文档及源码为准。