Zephyr 设备模型、驱动与电源管理

Zephyr 设备模型、驱动与电源管理

1. struct device

设备模型公共定义在 include/zephyr/device.h。每个设备实例由静态 struct device 表示,主要关系:

1
2
3
4
5
6
7
8
struct device
├─ name
├─ config → 编译期常量、寄存器地址、DT规格
├─ data → 运行时状态、锁、callback、DMA状态
├─ api → class-specific vtable
├─ state → init result / initialized
├─ pm → device PM
└─ handles → required/supported/injected dependencies

设备对象不是动态驱动注册表;绝大部分在编译和链接期生成。

2. 设备定义宏

非 DTS 设备:

1
2
DEVICE_DEFINE(dev_id, name, init_fn, pm,
data, config, level, prio, api);

DTS 设备:

1
2
3
4
DEVICE_DT_DEFINE(node_id, init_fn, pm,
data, config, level, prio, api);

DEVICE_DT_INST_DEFINE(inst, ...);

宏内部完成:

  1. 定义 device state;
  2. 定义依赖 handle 数据;
  3. 创建 struct device
  4. 创建 struct init_entry
  5. 放入按 init level/priority 排序的 linker section。

3. Driver class API

每一类设备有统一 vtable,例如 GPIO:

1
2
3
4
5
6
7
struct gpio_driver_api
├─ pin_configure
├─ port_get_raw
├─ port_set_masked_raw
├─ port_toggle_bits
├─ pin_interrupt_configure
└─ manage_callback

public inline API:

1
2
3
gpio_pin_configure_dt()
→ gpio_pin_configure()
→ api->pin_configure(dev, pin, flags)

好处:

  • 应用与 vendor driver 解耦;
  • API 调用通常只有一次 indirect call;
  • class 可提供统一参数检查和 syscall verifier;
  • driver data/config 保持私有。

4. DTS 驱动实例化

典型驱动结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define DT_DRV_COMPAT vendor_peripheral

struct drv_config {
DEVICE_MMIO_ROM;
struct clock_control_dt_spec clock;
const struct pinctrl_dev_config *pcfg;
};

struct drv_data {
struct k_mutex lock;
};

static int drv_init(const struct device *dev) { ... }
static const struct api drv_api = { ... };

#define DRV_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
static struct drv_data data_##inst; \
static const struct drv_config config_##inst = { ... }; \
DEVICE_DT_INST_DEFINE(inst, drv_init, PM_DEVICE_DT_INST_GET(inst), \
&data_##inst, &config_##inst, \
POST_KERNEL, CONFIG_DRV_INIT_PRIORITY, &drv_api);

DT_INST_FOREACH_STATUS_OKAY(DRV_INIT)

只有 status okay 且对应驱动源码已由 Kconfig/CMake编译时,实例才存在。

5. 获取设备

编译期获取:

1
const struct device *dev = DEVICE_DT_GET(node);

它直接取得全局符号地址,没有运行时查找。之后必须:

1
2
3
if (!device_is_ready(dev)) {
return -ENODEV;
}

常见 spec:

1
2
static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);

spec ready 检查:

1
2
3
gpio_is_ready_dt(&led)
i2c_is_ready_dt(&spec)
spi_is_ready_dt(&spec)

device_get_binding(name) 进行运行时名称查找,适合 shell/动态配置;普通静态应用优先 DEVICE_DT_GET

6. Device init

启动时 z_sys_init_run_level() 遇到 device entry:

  1. 检查 deferred init;
  2. 调用 device init function;
  3. 保存 init result;
  4. 标记 initialized;
  5. device_is_ready() 只有在 initialized 且 result 为 0 时为 true。

依赖 controller 必须比 child 更早初始化。常用 priority:

  • interrupt controller、clock、pinctrl:PRE_KERNEL;
  • bus controller:PRE_KERNEL/POST_KERNEL;
  • bus child sensor:POST_KERNEL;
  • application service:APPLICATION。

仅调整 priority 不能修复循环依赖。

7. Device dependency handles

生成系统根据 DTS phandle/dependency ordinal 建立:

  • required devices;
  • supported devices;
  • injected dependencies。

handle 使用 int16_t,比 pointer list 紧凑。多阶段链接读取预链接设备信息后生成最终 dependency arrays。

用途:

  • 初始化依赖理解;
  • device PM suspend/resume 排序;
  • shell/debug dependency 查询。

8. Deferred initialization

节点可设置 zephyr,deferred-init。此时启动阶段不自动 init,应用稍后调用 device init API。

适合:

  • 供电域未开启;
  • 外部器件需业务控制的 reset sequence;
  • 减少启动时间。

使用方必须处理并发首次初始化和依赖设备状态。

9. GPIO

公共 API:include/zephyr/drivers/gpio.h

重点:

  • raw 与 logical API;
  • active-low 由 flags 转换;
  • pin/port 操作;
  • interrupt edge/level;
  • gpio_callback list;
  • gpio_dt_spec

callback 常在 ISR 上下文执行,复杂处理应提交 work。

10. I2C / SPI

I2C

include/zephyr/drivers/i2c.h

  • controller configure;
  • i2c_transfer() messages;
  • write/read/write_read;
  • target mode;
  • i2c_dt_spec

device address 来自 child reg。驱动应区分 7-bit/10-bit 和 restart/stop flags。

SPI

include/zephyr/drivers/spi.h

  • spi_config:frequency、operation、slave、CS;
  • spi_buf_set
  • transceive/read/write;
  • async;
  • spi_dt_spec

CS 可以由 controller hardware 或 cs-gpios 管理。buffer 生命周期必须覆盖异步传输。

11. UART

include/zephyr/drivers/uart.h 提供三层 API:

  • polling;
  • interrupt-driven FIFO;
  • async DMA/event API。

console、shell、logging backend、mcumgr 可能同时竞争 UART。需要明确谁拥有 RX callback、是否共享 TX,以及 panic 阶段是否可轮询输出。

12. Flash

include/zephyr/drivers/flash.h

  • read/write/erase;
  • page layout;
  • write block size;
  • erase value;
  • protection。

上层 subsys/storage/flash_map 把 fixed-partitions 转成 flash_area。文件系统、settings、DFU 和 MCUboot 通常使用 flash map,而非硬编码地址。

Flash 写入注意:

  • 地址/长度写对齐;
  • erase block 边界;
  • 从 erased value 到 programmed value 的位变化限制;
  • erase/write 期间 XIP 冲突;
  • cache 一致性;
  • watchdog 和最长阻塞时间。

13. Clock 与 Pinctrl

Clock control

设备 config 保存 clock_control_subsys_t/DT spec,init 时:

1
2
3
clock_control_on
clock_control_set_rate
clock_control_get_rate

SoC clock tree 通常在 PRE_KERNEL 初始化。

Pinctrl

DT 定义 pinctrl-0pinctrl-1 等 state。驱动使用:

1
2
3
PINCTRL_DT_INST_DEFINE
PINCTRL_DT_INST_DEV_CONFIG_GET
pinctrl_apply_state

default/sleep state 可与 PM 联动。pinmux 冲突应在 DTS 设计阶段解决。

14. DMA

DMA API 描述:

  • channel;
  • direction;
  • source/destination data size;
  • burst;
  • block chain;
  • callback。

驱动使用 DMA 时必须考虑:

  • cache clean/invalidate;
  • buffer alignment;
  • memory region 是否 DMA 可访问;
  • callback ISR 上下文;
  • abort race;
  • peripheral request mapping。

15. Interrupt 连接

静态连接:

1
2
IRQ_CONNECT(irq, priority, isr, arg, flags);
irq_enable(irq);

DTS 驱动常使用:

1
2
DT_INST_IRQN(inst)
DT_INST_IRQ(inst, priority)

构建系统可能生成 software ISR table。direct ISR 可降低开销,但功能约束更多。zero-latency IRQ 绕过部分内核路径,ISR 不得调用普通内核 API。

16. Device PM

公共 API:

  • include/zephyr/pm/device.h
  • subsys/pm/device.c
  • subsys/pm/device_runtime.c

驱动通过:

1
PM_DEVICE_DT_INST_DEFINE(inst, action_cb);

实现 action:

  • suspend;
  • resume;
  • turn off/on;
  • low power。

System-managed PM

系统进入低功耗前按依赖顺序 suspend devices,唤醒后逆序 resume。

Runtime PM

使用计数控制单设备:

1
2
pm_device_runtime_get()
pm_device_runtime_put()

首次 get 恢复设备,最后 put 可 autosuspend。调用者必须保持 get/put 配对,driver 需处理与 system PM 的交互。

17. 驱动移植步骤

  1. 确认 class API,避免自造重复接口;
  2. 编写 binding;
  3. DTS 添加节点和依赖;
  4. Kconfig 定义 feature/vendor symbol;
  5. CMake 按 Kconfig 加源文件;
  6. 定义 config/data/api;
  7. DT_INST_FOREACH_STATUS_OKAY 实例化;
  8. 在 init 中检查 clock/pinctrl/bus;
  9. 实现 PM 和 error unwind;
  10. 添加 ztest、emulator 或测试 board;
  11. 检查多实例、disabled 节点、无 DT 实例时可编译;
  12. 测试 ISR、timeout、并发和异常恢复。

18. 常见驱动问题

  • DTS compatible 正确但 Kconfig 未开:无 device symbol;
  • controller 未 ready:child init 失败;
  • init priority 过早:使用了尚不可用的 kernel API;
  • data 错放为 const 或 config 可变;
  • callback 中阻塞;
  • DMA buffer 在 stack 上提前失效;
  • runtime PM 引用泄漏;
  • 未处理 active-low;
  • 外设 reset/clock 顺序错误;
  • DTS pinctrl state 与 board 实际连线不符。

文章互动

阅读 --

留言

0 条留言

正在加载留言…