Zephyr 设备驱动与设备驱动实现

标准驱动程序 链接到标题

在 Zephyr 的文档中提到无论那种主板都需要实现下面 4 种驱动

  • Interrupt controller:内核中断管理子系统需要该驱动
  • Timer: 内核系统时钟与硬件时钟子系统需要该驱动
  • Serial communication: 内核系统控制台 (console) 需要该驱动
  • Entropy: 为随机数子系统提供熵数源

但从实践上来看** Entropy **驱动并不是必须。

驱动实现 链接到标题

一类标准的设备驱动 API 会对应多个设备驱动实现,具体那一种设备驱动实现被构建取决于硬件配置,例如 Zephyr 支持 ADC API,那么 Zephyr 中支持的 SOC 都会按照自己 ADC 的硬件特性进行实现,在drivers/adc下就会有多个 adc 驱动程序的实现:

adc_ad559x.c     adc_cc13xx_cc26xx.c  adc_handlers.c     adc_mchp_xec.c           adc_nrfx_adc.c         adc_sam.c              adc_vf610.c
adc_ads1112.c    adc_cc32xx.c         adc_ifx_cat1.c     adc_mcp320x.c            adc_nrfx_saadc.c       adc_shell.c            adc_xmc4xxx.c
adc_ads1119.c    adc_common.c         adc_ite_it8xxx2.c  adc_mcux_12b1msps_sar.c  adc_numaker.c          adc_smartbond_gpadc.c  iadc_gecko.c
adc_ads114s0x.c  adc_emul.c           adc_lmp90xxx.c     adc_mcux_adc12.c         adc_nxp_s32_adc_sar.c  adc_smartbond_sdadc.c
adc_ads1x1x.c    adc_ene_kb1200.c     adc_ltc2451.c      adc_mcux_adc16.c         adc_renesas_ra.c       adc_stm32.c
adc_ads7052.c    adc_esp32.c          adc_max11102_17.c  adc_mcux_gau_adc.c       adc_rpi_pico.c         adc_stm32wb0.c
adc_ambiq.c      adc_gd32.c           adc_max1125x.c     adc_mcux_lpadc.c         adc_sam0.c             adc_test.c
adc_b91.c        adc_gecko.c          adc_max32.c        adc_npcx.c               adc_sam_afec.c         adc_tla2021.c

例如 esp32 的 adc 设备驱动就实现在 adc_esp32.c 中,而 stm32 的 adc 设备驱动则实现在 adc_stm32.c。

驱动实现方法 链接到标题

本文所介绍是针对 Zephyr 驱动实现的特点而言,并不涉及具体的硬件知识。

Zephyr 的驱动模型中每一种标准驱动都在其头文件中定义了标准的 api 原型__subsystem struct <device>_driver_api,以 adc 为例,在include/zephyr/drivers/adc.h中就有:

__subsystem struct adc_driver_api {
	adc_api_channel_setup channel_setup;
	adc_api_read          read;
#ifdef CONFIG_ADC_ASYNC
	adc_api_read_async    read_async;
#endif
	uint16_t ref_internal;	/* mV */
};

实现驱动就是根据<device>_driver_api中函数指针的类型进行驱动函数的实现,对于 adc 来说需要实现下面三个驱动函数:

typedef int (*adc_api_channel_setup)(const struct device *dev,
				     const struct adc_channel_cfg *channel_cfg);

typedef int (*adc_api_read)(const struct device *dev,
			    const struct adc_sequence *sequence);

typedef int (*adc_api_read_async)(const struct device *dev,
				  const struct adc_sequence *sequence,
				  struct k_poll_signal *async);

在 adc_esp32.c 中实现了,其它的设备驱动也是做像似的设备驱动 API:

static int adc_esp32_read(const struct device *dev, const struct adc_sequence *seq)
static int adc_esp32_read_async(const struct device *dev,
				const struct adc_sequence *sequence,
				struct k_poll_signal *async)
static int adc_esp32_channel_setup(const struct device *dev, const struct adc_channel_cfg *cfg)

实际实现这些 API 可能还会需要实现一些子函数,但对于驱动实现来说这一组struct adc_driver_api才是必须实现的,其它的子函数都会被这一组 API 调用。

然后用这些函数的指针对一个全局的struct adc_driver_api结构体变量进行初始化:

static const struct adc_driver_api api_esp32_driver_api = {
	.channel_setup = adc_esp32_channel_setup,
	.read          = adc_esp32_read,
#ifdef CONFIG_ADC_ASYNC
	.read_async    = adc_esp32_read_async,
#endif /* CONFIG_ADC_ASYNC */
	.ref_internal  = ADC_ESP32_DEFAULT_VREF_INTERNAL,
};

一个基本的驱动就算是实现完成。

除了struct <device>_driver_api所指定的 API 外,根据驱动实现对中断的需求,还要实现一个void (*device_isr)(const struct device *dev)类型的中断服务函数。

综上一个 Zephyr 的驱动实现简单的来说就是实现struct <device>_driver_api指定的 API,并根据中断的需要实现一个void (*device_isr)(const struct device *dev)类型的中断服务函数。

驱动 API 实现注意点 链接到标题

同步调用 链接到标题

除非特定硬件根本不提供任何中断功能,Zephyr 中每个驱动程序都应支持基于中断的实现方式,而不应采用非轮询方式。

除非标准 API 有明确要用异步(例如前面的 read_async)驱动函数实现都应该是同步阻塞的。

多实例 链接到标题

由于 Zephyr 的设备驱动要支持多实例(例如一个 adc 驱动代码要同时支持多个 adc 设备),这要求在驱动函数和中断服务函数实现时通过传参来处理设备驱动数据和配置,尽量避免使用全局变量。

参考 链接到标题

https://docs.zephyrproject.org/latest/kernel/drivers/index.html