ARTICLE DETAIL

资讯详情

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

Zephyr BSP: 37-Vendor HAL 边界

Zephyr BSP: 37-Vendor HAL 边界 摘要:本文深入探讨 Zephyr 驱动开发中一个关键问题——Vendor HAL 与 Zephyr Driver 的职责边界。核心结论是:HAL 面向 Vendor Hardware,Driver 面向 Zephyr API。文章通过 UART 完整示例,系统梳理了 HAL 与 Driver 的定位差异、Clock/Reset/Pinctrl 等资源归属的判断标准、Devicetree 的归属,以及 HAL 不应依赖 Zephyr 的设计原则。最后给出 5 条工程规则,帮助你在接入公司已有 SDK/HAL 时,既不破坏 Zephyr 架构,又保持 HAL 的独立可复用性。Vendor HAL / Zephyr Driver 的边界这一篇非常关键。前面36 — Company HAL 怎么放进 Zephyr?我们解决的是:“公司的 HAL 代码应该放在哪里、怎么被 Zephyr 调用?”这一篇继续往前一步,解决一个更容易踩坑的问题:Vendor HAL 和 Zephyr Driver,到底谁负责什么?如果这个边界没有定义好,最后很容易变成:Zephyr Application ↓ Zephyr Driver ↓ Vendor HAL ↓ SoC Registers看起来很漂亮,但实际上 Driver 和 HAL 可能互相重复做事情:Zephyr Driver ├── 配 Clock ├── 配 Pinmux ├── 配 IRQ ├── 配 Register └── 调 HAL Vendor HAL ├── 配 Clock ├── 配 Pinmux ├── 配 IRQ ├── 配 Register └── 初始化 UART最终就会出现:谁才是硬件的真正 owner?1. 先给出核心结论一个比较健康的 SoC BSP 架构是:Zephyr Application │ ▼ Zephyr API / Subsystem │ ▼ ┌─────────────────┐ │ Zephyr Driver │ │ │ │ uart_xxx.c │ │ spi_xxx.c │ │ gpio_xxx.c │ └────────┬────────┘ │ HAL / LL interface │ ▼ ┌─────────────────┐ │ Vendor HAL │ │ │ │ UART HAL │ │ SPI HAL │ │ GPIO HAL │ │ Clock HAL │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ SoC Registers │ │ / Hardware │ └─────────────────┘但是:不是所有东西都必须经过 HAL。这是这一篇最重要的地方。2. HAL 到底是什么?Vendor HAL 的核心目标是:把 SoC 硬件细节封装成厂商自己的软件接口。例如 Company SoC 有:UART0 UART1 UART2硬件寄存器:#defineUART0_BASE0x40000000structuart_regs{uint32_tCTRL;uint32_tSTATUS;uint32_tDATA;uint32_tBAUD;};Vendor HAL 可以提供:voidcompany_uart_init(intid,uint32_tbaudrate);voidcompany_uart_write(intid,constuint8_t*data,size_tlen);voidcompany_uart_enable_irq(intid);HAL 使用:UART0-BAUD=... UART0-CTRL=...把这些寄存器操作隐藏起来。于是上层不用知道:CTRL bit7是什么 STATUS bit3是什么 BAUD register 怎么计算3. Zephyr Driver 又是什么?Zephyr Driver 的目标不一样。它不是简单封装寄存器。它的主要职责是:把硬件能力适配成 Zephyr 的标准 Driver API。例如 Zephyr UART API:uart_poll_out(dev, c);Application:const struct device\*uart=DEVICE_DT_GET(DT_NODELABEL(uart0));uart_poll_out(uart,'A');Application 不应该知道:company_uart_write()更不应该知道:UART0-DATA=c;所以:Application │ ▼ Zephyr UART API │ ▼ Company UART Driver │ ▼ Company UART HAL │ ▼ UART Registers4. 两者最大的区别可以用一句话区分:HAL 面向 Vendor Hardware;Driver 面向 Zephyr API。也就是:层面向谁Application应用Zephyr APIZephyrZephyr DriverZephyrVendor HALVendor / SoCRegistersHardware例如:Zephyr UART API │ │ ▼ uart_company.c │ │ ▼ company_uart.c │ │ ▼ UART IP5. 一个非常重要的边界假设 UART Driver:staticintcompany_uart_poll_out(conststructdevice*dev,unsignedcharc){conststructcompany_uart_config*cfg=dev-config;company_uart_write(cfg-id,c,1);return0;}这里:company_uart_write()是 HAL。而:company_uart_poll_out()是 Zephyr Driver。所以:uart_poll_out()│ ▼ company_uart_poll_out()│ ▼ company_uart_write()│ ▼ UART register5.1 资源归属判断决策树:Clock / Reset / Pinctrl 归谁?上面明确了「Driver 面向 Zephyr API,HAL 面向 Vendor Hardware」的边界,但落到 Clock / Reset / Pinctrl 这类资源时,很多人还是会犹豫。下面这张 ASCII 决策树,帮你从资源类型出发,快速判断它应该归 HAL 还是归 Driver:资源类型 │ ▼ ┌────────────┴────────────┐ │ │ SoC 级资源 外设级资源 (全局时钟、复位控制器、 (UART/SPI/I2C/GPIO 引脚复用控制器) 各自的时钟门、复位位) │ │ ▼ ▼ Zephyr 是否已建模? Zephyr 是否已建模? (clock_control / (clock_control / reset / pinctrl) reset / pinctrl) │ │ │ │ │ │ │ │ 是 │ │ 否 │ 是 │ 否 │ │ │ │ ▼ ▼ ▼ ▼ Zephyr Vendor HAL Zephyr Vendor HAL Driver (自包含 Driver (自包含 (subsystem 初始化序列, (subsystem 初始化序列, 统一管理) 跨平台复用) 统一管理) 跨平台复用)典型例子资源类型归属理由UART 外设时钟(clocks = clk UART0_CLK)外设级Zephyr DriverZephyr 已建模clock_control,Driver 调clock_control_on()即可,HAL 不碰时钟寄存器GPIO 引脚复用(pinctrl-0 = gpio5_1 ...)SoC 级Zephyr DriverZephyr 已建模pinctrl,Driver 调pinctrl_apply_state(),HAL 不碰 Pinmux 寄存器外设复位(resets = rst UART0_RESET)外设级Zephyr DriverZephyr 已建模reset,Driver 调reset_line_toggle(),HAL 不碰复位寄存器外设 IP 内部寄存器(UART-CTRL、UART-BAUD)外设级Vendor HAL属于 IP 特有逻辑,Zephyr 未建模,HAL 封装寄存器位操作5.2 HAL 与 Driver 职责对照表决策树给出了资源归属的判断框架,下面这张对照表则从「职责维度」出发,逐行列出 HAL 与 Driver 各自负责的内容,并标注典型 API 示例,方便你在写代码时快速对号入座:职责维度Zephyr Driver 负责Vendor HAL 负责典型 API 示例时钟配置调用 Zephyr Clock Control subsystem 打开/关闭外设时钟,不碰时钟寄存器不碰时钟;若 Zephyr 未建模才由 HAL 封装时钟门控Driver:clock_control_on()/clock_control_off();HAL:无(或company_hal_uart_clk_enable()仅限未建模场景)引脚复用(Pinctrl)调用 Zephyr Pinctrl subsystem 应用 DT 中的引脚复用状态不碰 Pinmux 寄存器;只接收已配置好的base地址Driver:pinctrl_apply_state();HAL:无复位(Reset)调用 Zephyr Reset subsystem 复位外设,确保从已知状态启动不碰复位寄存器Driver:reset_line_toggle()/reset_line_assert();HAL:无中断处理注册中断向量(IRQ_CONNECT)、实现 ISR、调用 Zephyr 回调只使能/屏蔽 UART IP 内部中断源,不碰中断控制器Driver:IRQ_CONNECT()/uart_irq_callback_set();HAL:company_hal_uart_irq_enable()寄存器操作不直接碰寄存器,只做参数传递与映射封装所有 SoC 寄存器位操作、硬件时序、IP 特有逻辑Driver:无;HAL:company_hal_uart_putc()/company_hal_uart_getc()DT 解析通过 DT 宏提取reg、clocks、resets、pinctrl-0等属性,填充config结构体不解析 Devicetree,只接收base等已解析参数Driver:DT_INST_REG_ADDR()/DT_CLOCKS_CTLR()/PINCTRL_DT_INST_DEV_CONFIG_GET();HAL:无设备模型定义struct device、config/data结构体、注册device_api不认识struct device,只暴露纯 C 函数Driver:DEVICE_DT_INST_DEFINE()/uart_driver_api;HAL:无互斥与并发用 Zephyr 的k_mutex/sys_mutex保护临界区不依赖任何 RTOS 原语,保持纯 C 可独立编译Driver:k_mutex_lock()/k_mutex_unlock();HAL:无电源管理(PM)实现 PM 回调,通过 Zephyr subsystem 同步 Clock/Reset 状态不感知 PM,只做寄存器级操作Driver:pm_device_runtime_get()/pm_action_cb;HAL:无硬件初始化序列编排初始化顺序:Clock → Reset → Pinctrl → HAL init只执行 IP 本身的寄存器初始化时序Driver:company_uart_init();HAL:company_hal_uart_init()一句话总结:凡是 Zephyr 已经建模的概念(Clock / Reset / Pinctrl / IRQ / DT / PM / 互斥),一律归 Driver 通过 Zephyr API 处理;凡是 SoC 寄存器位操作、硬件时序、IP 特有逻辑,一律归 HAL。Driver 面向 Zephyr API,HAL 面向 Vendor Hardware,两者通过base地址和纯 C 函数接口衔接,互不越界。一句话总结:先问「这个资源 Zephyr 是否已经建模?」——已建模(Clock / Reset / Pinctrl / GPIO / IRQ / PM)则归 Driver 通过 subsystem 管理;未建模或需跨平台复用的 IP 内部寄存器操作,才归 HAL。边界非常清楚。6. HAL 不应该知道 Zephyr这是一个非常重要的设计原则。Vendor HAL 最好不要出现:#includezephyr/device.h#includezephyr/kernel.h#includezephyr/drivers/uart.h例如不要写:voidcompany_uart_init(void){if(!device_is_ready(...)){...}}这是错误方向。因为:HAL 不应该依赖 Zephyr。理想情况下:┌──────────────┐ │ Zephyr │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ Driver │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ HAL │ └──────┬───────┘ │ ▼ Hardware而不是:HAL │ └──────► Zephyr7. 为什么这个边界这么重要?因为 Vendor HAL 往往还有其他消费者。例如 Company 有:Company SDK Linux BSP Bootloader RTOS Zephyr Bare-metal firmware都可能使用同一个 HAL。于是:Company HAL │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ Bare-metal Zephyr Bootloader │ │ ▼ ▼ Application Driver如果 HAL 里面大量出现:k_mutex_lock()device_is_ready()DEVICE_DT_GET()那么这个 HAL 就不能再独立复用。8. 但是 Driver 也不能把所有东西都丢给 HAL这是另一个极端。比如:company_uart_write()company_uart_read()company_uart_irq_enable()company_uart_irq_disable()company_uart_configure()company_uart_fifo_enable()company_uart_set_callback()...最后 HAL 变成:“把 Zephyr Driver 所有 API 原封不动复制一遍。”这其实也不好。因为 HAL 的 API 应该表达:Hardware capability而不是:Zephyr API9. 举一个 UART 的完整例子假设 Zephyr:uart_poll_out(dev,'A');最终:uart_poll_out()│ ▼ company_uart_poll_out()│ ▼ company_hal_uart_putc()│ ▼ UART_TX registerHAL:voidcompany_hal_uart_putc(uint32_tbase,uint8_tc){while(!(UART_STATUS(base)UART_STATUS_TX_READY));UART_DATA(base)=c;}Driver:staticvoidcompany_uart_poll_out(conststructdevice*dev,unsignedcharc){conststructcompany_uart_config*cfg=dev-config;company_hal_uart_putc(cfg-base,c);}9.1 完整的 Zephyr UART Driver + Vendor HAL 代码示例下面给出一个可直接对照阅读的完整示例,包含 Driver 的device_api结构体定义、HAL 的初始化与读写函数实现,并逐层标注职责。第一步:Vendor HAL(不依赖 Zephyr,可独立编译)/* hal_company/uart.h —— Vendor HAL 头文件 */#ifndefCOMPANY_HAL_UART_H#defineCOMPANY_HAL_UART_H#includestdint.h#includestddef.h/* HAL 层职责:封装 SoC 寄存器操作,暴露硬件能力。 * 注意:这里不 include 任何 zephyr/... 头文件。 *//* 寄存器基址由调用方(Driver)通过 DT 解析后传入 */voidcompany_hal_uart_init(uint32_tbase,uint32_tbaudrate);voidcompany_hal_uart_putc(uint32_tbase,uint8_tc);intcompany_hal_uart_getc(uint32_tbase,uint8_t*c);#endif/* COMPANY_HAL_UART_H *//* hal_company/uart.c —— Vendor HAL 实现 */#include"uart.h"/* 寄存器偏移(示意) */#defineUART_CTRL(base)(*(volatileuint32_t*)((base)+0x00))#defineUART_STATUS(base)(*(volatileuint32_t*)((base)+0x04))#defineUART_DATA(base)(*(volatileuint32_t*)((base)+0x08))#defineUART_BAUD(base)(*(volatileuint32_t*)((base)+0x0C))#defineUART_STATUS_TX_READY(1u0)#defineUART_STATUS_RX_READY(1u1)#defineUART_CTRL_ENABLE(1u0)/* HAL 职责:硬件初始化序列、寄存器位操作、IP 特有逻辑 */voidcompany_hal_uart_init(uint32_tbase,uint32_tbaudrate){/* 硬件时序:先关、再配波特率、最后使能 */UART_CTRL(base)=~UART_CTRL_ENABLE;UART_BAUD(base)=baudrate;/* 实际需按分频计算,此处示意 */UART_CTRL(base)|=UART_CTRL_ENABLE;}/* HAL 职责:轮询等待 TX 就绪,然后写数据寄存器 */voidcompany_hal_uart_putc(uint32_tbase,uint8_tc){while(!(UART_STATUS(base)UART_STATUS_TX_READY));UART_DATA(base)=c;}/* HAL 职责:轮询等待 RX 就绪,读取数据寄存器 */intcompany_hal_uart_getc(uint32_tbase,uint8_t*c){if(!(UART_STATUS(base)UART_STATUS_RX_READY)){return-1;/* 无数据 */}*c=(uint8_t)UART_DATA(base);return0;}第二步:Zephyr Driver(面向 Zephyr API,负责 DT / device model)/* drivers/serial/uart_company.c —— Zephyr UART Driver */#includezephyr/kernel.h#includezephyr/device.h#includezephyr/drivers/uart.h#includezephyr/devicetree.h#include"company_hal_uart.h"/* 只依赖 Vendor HAL 头文件 */#defineDT_DRV_COMPATcompany_uart/* Driver 职责:把 DT 配置解析成 HAL 需要的参数 */structcompany_uart_config{uint32_tbase;/* 来自 DT reg 属性 */uint32_tbaudrate;/* 来自 DT 或 Kconfig */};/* Driver 职责:实现 Zephyr UART API 的 poll_out */staticintcompany_uart_poll_out(conststructdevice*dev,unsignedcharc){conststructcompany_uart_config*cfg=dev-config;/* 只做参数传递,不碰寄存器 —— 寄存器归 HAL */company_hal_uart_putc(cfg-base,c);return0;}/* Driver 职责:实现 Zephyr UART API 的 poll_in */staticintcompany_uart_poll_in(conststructdevice*dev,unsignedchar*c){conststructcompany_uart_config*cfg=dev-config;returncompany_hal_uart_getc(cfg-base,c);}/* Driver 职责:实现 Zephyr UART API 的 init */staticintcompany_uart_init(conststructdevice*dev){conststructcompany_uart_config*cfg=dev-config;/* Driver 负责调用 HAL 完成硬件初始化 */company_hal_uart_init(cfg-base,cfg-baudrate);return0;}/* Driver 职责:把 Zephyr API 函数指针注册进 device_api */staticconststructuart_driver_apicompany_uart_api={.poll_in=company_uart_poll_in,.poll_out=company_uart_poll_out,};/* Driver 职责:从 DT 提取寄存器基址 */#defineCOMPANY_UART_INIT(n)\staticconststructcompany_uart_configcompany_uart_cfg_##n={\.base=DT_INST_REG_ADDR(n),\.baudrate=115200,\};
返回列表