zephyr 内核服务(2)

 

设备驱动程序模型

  • 设备驱动程序模型

Device Driver Model 设备驱动程序模型

Introduction 简介

The Zephyr kernel supports a variety of device drivers. Whether a driver is available depends on the board and the driver.

Zephyr 内核支持多种设备驱动程序。驱动程序是否可用取决于主板和驱动程序。

The Zephyr device model provides a consistent device model for configuring the drivers that are part of a system. The device model is responsible for initializing all the drivers configured into the system.

Zephyr 设备模型为配置作为系统一部分的驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动程序。

Each type of driver (e.g. UART, SPI, I2C) is supported by a generic type API.

每种类型的驱动程序(例如 UART、 SPI、 I2C)都受泛型类型 API 的支持。

In this model the driver fills in the pointer to the structure containing the function pointers to its API functions during driver initialization. These structures are placed into the RAM section in initialization level order.

在这个模型中,驱动程序填充指向结构的指针,该结构包含在驱动程序初始化期间指向其 API 函数的函数指针。这些结构按照初始化级别的顺序放置在 RAM 部分中。

Device Driver Model

Standard Drivers 标准驱动

Device drivers which are present on all supported board configurations are listed below.

下面列出了所有支持的板配置上的设备驱动程序。

  • Interrupt controller: This device driver is used by the kernel’s interrupt management subsystem.

    中断控制器: 这个设备驱动程序由内核的中断管理子系统使用。

  • Timer: This device driver is used by the kernel’s system clock and hardware clock subsystem.

    Timer: 这个设备驱动程序由内核的系统时钟和硬件时钟子系统使用。

  • Serial communication: This device driver is used by the kernel’s system console subsystem.

    串行通信: 这个设备驱动程序由内核的系统控制台子系统使用。

  • Entropy: This device driver provides a source of entropy numbers for the random number generator subsystem.

    熵: 这个设备驱动程序为随机数发生器子系统提供了一个熵数源。

    Important 很重要

    Use the random API functions for random values. Entropy functions should not be directly used as a random number generator source as some hardware implementations are designed to be an entropy seed source for random number generators and will not provide cryptographically secure random number streams.

    对随机值使用随机 API 函数。熵函数不应该直接用作随机数生成器源,因为一些硬件实现被设计成随机数生成器的熵种子源,并且不能提供加密安全的随机数流。

Synchronous Calls 同步调用

Zephyr provides a set of device drivers for multiple boards. Each driver should support an interrupt-based implementation, rather than polling, unless the specific hardware does not provide any interrupt.

Zephyr 为多个主板提供了一组设备驱动程序。每个驱动程序都应该支持基于中断的实现,而不是轮询,除非特定的硬件不提供任何中断。

High-level calls accessed through device-specific APIs, such as i2c.h or spi.h, are usually intended as synchronous. Thus, these calls should be blocking.

通过特定于设备的 API (如 i2c.h 或 spi.h)访问的高级调用通常是同步的。因此,这些调用应该被阻塞。

Driver APIs 驱动程序接口

The following APIs for device drivers are provided by device.h. The APIs are intended for use in device drivers only and should not be used in applications.

设备驱动程序的以下 API 由 device.h 提供。API 仅用于设备驱动程序,不应用于应用程序。

  • DEVICE_DEFINE()

    Create device object and related data structures including setting it up for boot-time initialization.

    创建设备对象和相关数据结构,包括为启动时初始化设置它。

  • DEVICE_NAME_GET()

    Converts a device identifier to the global identifier for a device object.

    将设备标识符转换为设备对象的全局标识符。

  • DEVICE_GET()

    Obtain a pointer to a device object by name.

    按名称获取指向设备对象的指针。

  • DEVICE_DECLARE()

    Declare a device object. Use this when you need a forward reference to a device that has not yet been defined.

    声明设备对象。当您需要对尚未定义的设备进行转发引用时,请使用此选项。

Driver Data Structures 驱动程序数据结构

The device initialization macros populate some data structures at build time which are split into read-only and runtime-mutable parts. At a high level we have:

设备初始化宏在构建时填充一些数据结构,这些数据结构分为只读和运行时可变部分。在高层,我们有:

struct device {
      const char *name;
      const void *config;
      const void *api;  // 这个设备是由驱动定义的,所以也带有驱动定义的接口
      void *const data;
};

The config member is for read-only configuration data set at build time. For example, base memory mapped IO addresses, IRQ line numbers, or other fixed physical characteristics of the device. This is the config pointer passed to DEVICE_DEFINE() and related macros.

配置成员用于生成时的只读配置数据集。例如,基本内存映射的 IO 地址、 IRQ 行号或设备的其他固定物理特性。这是传递给 DEVICE_DEFINE() 和相关宏的配置指针。

The data struct is kept in RAM, and is used by the driver for per-instance runtime housekeeping. For example, it may contain reference counts, semaphores, scratch buffers, etc.

数据结构保存在 RAM 中,由驱动程序用于每个实例的运行时管理。例如,它可能包含引用计数、信号量、高速缓冲区等。

The api struct maps generic subsystem APIs to the device-specific implementations in the driver. It is typically read-only and populated at build time. The next section describes this in more detail.

Api 结构将通用子系统 API 映射到驱动程序中特定于设备的实现。它通常是只读的,并在构建时填充。下一节将对此进行更详细的描述。

Subsystems and API Structures 子系统和 API 结构

Most drivers will be implementing a device-independent subsystem API. Applications can simply program to that generic API, and application code is not specific to any particular driver implementation.

大多数驱动程序将实现与设备无关的子系统 API。应用程序可以简单地编程到该通用 API,并且应用程序代码不特定于任何特定的驱动程序实现。

A subsystem API definition typically looks like this:

子系统 API 定义通常如下所示:

typedef int (*subsystem_do_this_t)(const struct device *dev, int foo, int bar);
typedef void (*subsystem_do_that_t)(const struct device *dev, void *baz);

struct subsystem_api {
      subsystem_do_this_t do_this;
      subsystem_do_that_t do_that;
};

static inline int subsystem_do_this(const struct device *dev, int foo, int bar)
{
      struct subsystem_api *api;

      api = (struct subsystem_api *)dev->api;
      return api->do_this(dev, foo, bar);
}

static inline void subsystem_do_that(const struct device *dev, void *baz)
{
      struct subsystem_api *api;

      api = (struct subsystem_api *)dev->api;
      api->do_that(dev, baz);
}

A driver implementing a particular subsystem will define the real implementation of these APIs, and populate an instance of subsystem_api structure:

实现特定子系统的驱动程序将定义这些 API 的实际实现,并填充 subsystem_api 结构的实例:

static int my_driver_do_this(const struct device *dev, int foo, int bar)
{
      ...
}

static void my_driver_do_that(const struct device *dev, void *baz)
{
      ...
}

static struct subsystem_api my_driver_api_funcs = {
      .do_this = my_driver_do_this,
      .do_that = my_driver_do_that
};

The driver would then pass my_driver_api_funcs as the api argument to DEVICE_DEFINE().

然后,驱动程序将 my_driver_api_funcs 作为 api 参数传递给 DEVICE_DEFINE()

Note 注意

Since pointers to the API functions are referenced in the api struct, they will always be included in the binary even if unused; gc-sections linker option will always see at least one reference to them. Providing for link-time size optimizations with driver APIs in most cases requires that the optional feature be controlled by a Kconfig option.

由于 API 函数的指针是在 API 结构中引用的,因此即使未使用,它们也总是包含在二进制文件中; gc-section 链接器选项总是至少看到一个对它们的引用。在大多数情况下,使用链接时大小优化驱动程序 API 需要使用 Kconfig 选项来控制可选特性。

Device-Specific API Extensions 特定于设备的 API 扩展

Some devices can be cast as an instance of a driver subsystem such as GPIO, but provide additional functionality that cannot be exposed through the standard API. These devices combine subsystem operations with device-specific APIs, described in a device-specific header.

有些设备可以被强制转换为驱动程序子系统(如 GPIO)的实例,但是提供了无法通过标准 API 公开的附加功能。这些设备将子系统操作与特定于设备的 API 结合在一起,这些 API 在特定于设备的头文件中进行描述。

A device-specific API definition typically looks like this:

特定于设备的 API 定义通常如下所示:

#include <zephyr/drivers/subsystem.h>

/* When extensions need not be invoked from user mode threads */
int specific_do_that(const struct device *dev, int foo);

/* When extensions must be invokable from user mode threads */
__syscall int specific_from_user(const struct device *dev, int bar);

/* Only needed when extensions include syscalls */
#include <syscalls/specific.h>

A driver implementing extensions to the subsystem will define the real implementation of both the subsystem API and the specific APIs:

实现子系统扩展的驱动程序将定义子系统 API 和特定 API 的实际实现:

static int generic_do_this(const struct device *dev, void *arg)
{
   ...
}

static struct generic_api api {
   ...
   .do_this = generic_do_this,
   ...
};

/* supervisor-only API is globally visible */
int specific_do_that(const struct device *dev, int foo)
{
   ...
}

/* syscall API passes through a translation */
int z_impl_specific_from_user(const struct device *dev, int bar)
{
   ...
}

#ifdef CONFIG_USERSPACE

#include <zephyr/syscall_handler.h>

int z_vrfy_specific_from_user(const struct device *dev, int bar)
{
    Z_OOPS(Z_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_GENERIC, &api));
    return z_impl_specific_do_that(dev, bar)
}

#include <syscalls/specific_from_user_mrsh.c>

#endif /* CONFIG_USERSPACE */

Applications use the device through both the subsystem and specific APIs.

应用程序通过子系统和特定 API 使用设备。

Note 注意

Public API for device-specific extensions should be prefixed with the compatible for the device to which it applies. For example, if adding special functions to support the Maxim DS3231 the identifier fragment specific in the examples above would be maxim_ds3231.

针对特定于设备的扩展的公共 API 应该以适用它的设备的兼容性作为前缀。例如,如果添加特殊函数来支持 Maxim DS3231,那么上面示例中特定的标识符片段就是 maxim_ds3231

Single Driver, Multiple Instances 单驱动程序,多实例

Some drivers may be instantiated multiple times in a given system. For example there can be multiple GPIO banks, or multiple UARTS. Each instance of the driver will have a different config struct and data struct.

在给定的系统中,有些驱动程序可能被多次实例化。例如,可以有多个 GPIO 组,或多个 UARTS。驱动程序的每个实例将具有不同的配置结构和数据结构。

Configuring interrupts for multiple drivers instances is a special case. If each instance needs to configure a different interrupt line, this can be accomplished through the use of per-instance configuration functions, since the parameters to IRQ_CONNECT() need to be resolvable at build time.

为多个驱动程序实例配置中断是一种特殊情况。如果每个实例需要配置不同的中断管线,那么可以通过使用每个实例的配置函数来实现,因为 IRQ_CONNECT()的参数在构建时需要可解析。

For example, let’s say we need to configure two instances of my_driver, each with a different interrupt line. In drivers/subsystem/subsystem_my_driver.h:

例如,假设我们需要配置 my_driver 的两个实例,每个实例具有不同的中断行:

typedef void (*my_driver_config_irq_t)(const struct device *dev);

struct my_driver_config {
      DEVICE_MMIO_ROM;
      my_driver_config_irq_t config_func;
};

In the implementation of the common init function:

在实现通用 init 函数时:

void my_driver_isr(const struct device *dev)
{
      /* Handle interrupt */
      ...
}

int my_driver_init(const struct device *dev)
{
      const struct my_driver_config *config = dev->config;

      DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);

      /* Do other initialization stuff */
      ...

      config->config_func(dev);

      return 0;
}

Then when the particular instance is declared:

然后,当特定实例被声明时:

#if CONFIG_MY_DRIVER_0

DEVICE_DECLARE(my_driver_0);

static void my_driver_config_irq_0(void)
{
      IRQ_CONNECT(MY_DRIVER_0_IRQ, MY_DRIVER_0_PRI, my_driver_isr,
                  DEVICE_GET(my_driver_0), MY_DRIVER_0_FLAGS);
}

const static struct my_driver_config my_driver_config_0 = {
      DEVICE_MMIO_ROM_INIT(DT_DRV_INST(0)),
      .config_func = my_driver_config_irq_0
}

static struct my_data_0;

DEVICE_DEFINE(my_driver_0, MY_DRIVER_0_NAME, my_driver_init,
              NULL, &my_data_0, &my_driver_config_0,
              POST_KERNEL, MY_DRIVER_0_PRIORITY, &my_api_funcs);

#endif /* CONFIG_MY_DRIVER_0 */

Note the use of DEVICE_DECLARE() to avoid a circular dependency on providing the IRQ handler argument and the definition of the device itself.

请注意,使用 DEVICE_DECLARE()可以避免循环依赖于提供 IRQ 处理程序参数和设备本身的定义。

Initialization Levels 初始化级别

Drivers may depend on other drivers being initialized first, or require the use of kernel services. DEVICE_DEFINE() and related APIs allow the user to specify at what time during the boot sequence the init function will be executed. Any driver will specify one of four initialization levels:

驱动程序可能依赖于首先初始化的其他驱动程序,或者需要使用内核服务。DEVICE_DEFINE() 和相关的 API 允许用户指定在引导序列期间 init 函数将在何时执行。任何驱动程序都将指定四个初始化级别中的一个:

  • PRE_KERNEL_1

    Used for devices that have no dependencies, such as those that rely solely on hardware present in the processor/SOC. These devices cannot use any kernel services during configuration, since the kernel services are not yet available. The interrupt subsystem will be configured however so it’s OK to set up interrupts. Init functions at this level run on the interrupt stack.

    用于没有依赖关系的设备,例如那些仅仅依赖于处理器/SOC 中存在的硬件的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。然而,中断子系统将被配置,因此可以设置中断。这个级别的初始化函数在中断堆栈上运行。

  • PRE_KERNEL_2

    Used for devices that rely on the initialization of devices initialized as part of the PRE_KERNEL_1 level. These devices cannot use any kernel services during configuration, since the kernel services are not yet available. Init functions at this level run on the interrupt stack.

    用于依赖于作为 PRE_KERNEL_1级别的一部分初始化的设备的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。这个级别的初始化函数在中断堆栈上运行。

  • POST_KERNEL

    Used for devices that require kernel services during configuration. Init functions at this level run in context of the kernel main task.

    用于在配置期间需要内核服务的设备。这个级别的 Init 函数在内核主任务的上下文中运行。

  • APPLICATION

    Used for application components (i.e. non-kernel components) that need automatic configuration. These devices can use all services provided by the kernel during configuration. Init functions at this level run on the kernel main task.

    用于需要自动配置的应用程序组件(即非内核组件)。这些设备可以在配置期间使用内核提供的所有服务。这个级别的 Init 函数在内核主任务上运行。

Within each initialization level you may specify a priority level, relative to other devices in the same initialization level. The priority level is specified as an integer value in the range 0 to 99; lower values indicate earlier initialization. The priority level must be a decimal integer literal without leading zeroes or sign (e.g. 32), or an equivalent symbolic name (e.g. \#define MY_INIT_PRIO 32); symbolic expressions are not permitted (e.g. CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5).

在每个初始化级别中,您可以指定相对于同一初始化级别中的其他设备的优先级别。优先级级别指定为0到99范围内的整数值; 较低的值表示较早的初始化。优先级级别必须是一个没有前导零或符号(例如32)的十进制整数文字,或者一个等价的符号名(例如 #define MY_INIT_PRIO 32) ; 符号表达式是不允许的(例如 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5)。

Drivers and other system utilities can determine whether startup is still in pre-kernel states by using the k_is_pre_kernel() function.

通过使用k_is_pre_kernel() 函数,驱动程序和其他系统实用程序可以确定启动是否仍处于内核前状态。

System Drivers 系统驱动

In some cases you may just need to run a function at boot. For such cases, the SYS_INIT can be used. This macro does not take any config or runtime data structures and there isn’t a way to later get a device pointer by name. The same device policies for initialization level and priority apply.

在某些情况下,您可能只需要在启动时运行一个函数。对于这种情况,可以使用 SYS_INIT。此宏不采用任何配置或运行时数据结构,以后也没有办法按名称获取设备指针。初始化级别和优先级的设备策略是相同的。

Error handling 错误处理

In general, it’s best to use __ASSERT() macros instead of propagating return values unless the failure is expected to occur during the normal course of operation (such as a storage device full). Bad parameters, programming errors, consistency checks, pathological/unrecoverable failures, etc., should be handled by assertions.

一般来说,最好使用 __ASSERT()宏来代替传播返回值,除非在正常操作过程中(比如存储设备已满)预计会发生故障。错误的参数、编程错误、一致性检查、病态/不可恢复的故障等等,都应该由断言来处理。

When it is appropriate to return error conditions for the caller to check, 0 should be returned on success and a POSIX errno.h code returned on failure. See https://github.com/zephyrproject-rtos/zephyr/wiki/Naming-Conventions#return-codes for details about this.

如果适合返回错误条件供调用方检查,则应在成功时返回0,在失败时返回 POSIX errno.h 代码。详情请参阅 https://github.com/zephyrproject-rtos/zephyr/wiki/naming-conventions#return-codes。

Memory Mapping 内存映射

On some systems, the linear address of peripheral memory-mapped I/O (MMIO) regions cannot be known at build time:

在某些系统中,外围内存映射 I/O (MMIO)区域的线性地址在构建时无法知道:

  • The I/O ranges must be probed at runtime from the bus, such as with PCI express

    必须在运行时从总线探测 I/O 范围,例如使用 PCI Express

  • A memory management unit (MMU) is active, and the physical address of the MMIO range must be mapped into the page tables at some virtual memory location determined by the kernel.

    内存管理单元(MMU)处于活动状态,MMIO 范围的物理地址必须映射到由内核确定的某个虚拟内存位置的页表中。

These systems must maintain storage for the MMIO range within RAM and establish the mapping within the driver’s init function. Other systems do not care about this and can use MMIO physical addresses directly from DTS and do not need any RAM-based storage for it.

这些系统必须在 RAM 内维护 MMIO 范围的存储,并在驱动程序的 init 函数内建立映射。其他系统不关心这一点,可以直接从 DTS 使用 MMIO 物理地址,不需要任何基于 RAM 的存储。

For drivers that may need to deal with this situation, a set of APIs under the DEVICE_MMIO scope are defined, along with a mapping function device_map().

对于可能需要处理这种情况的驱动程序,将定义一组在 DevICE _ MMIO 作用域下的 API,以及一个映射函数 DEVICE _ map ()。

Device Model Drivers with one MMIO region

具有一个 MMIO 区域的设备模型驱动程序

The simplest case is for drivers which need to maintain one MMIO region. These drivers will need to use the DEVICE_MMIO_ROM and DEVICE_MMIO_RAM macros in the definitions for their config_info and driver_data structures, with initialization of the config_info from DTS using DEVICE_MMIO_ROM_INIT. A call to DEVICE_MMIO_MAP() is made within the init function:

最简单的情况是需要维护一个 MMIO 区域的驱动程序。这些驱动程序将需要在它们的 config_infodriver_data 结构的定义中使用 DEVICE_MMIO_ROMDEVICE_MMIO_RAM 宏,使用 DEVICE_MMIO_ROM_INIT 从 DTS 初始化 config_info。在 init 函数中调用 DEVICE_MMIO_MAP() :

struct my_driver_config {
   DEVICE_MMIO_ROM; /* Must be first */
   ...
}

struct my_driver_dev_data {
   DEVICE_MMIO_RAM; /* Must be first */
   ...
}

const static struct my_driver_config my_driver_config_0 = {
   DEVICE_MMIO_ROM_INIT(DT_DRV_INST(...)),
   ...
}

int my_driver_init(const struct device *dev)
{
   ...
   DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
   ...
}

int my_driver_some_function(const struct device *dev)
{
   ...
   /* Write some data to the MMIO region */
   sys_write32(0xDEADBEEF, DEVICE_MMIO_GET(dev));
   ...
}

The particular expansion of these macros depends on configuration. On a device with no MMU or PCI-e, DEVICE_MMIO_MAP and DEVICE_MMIO_RAM expand to nothing.

这些宏的特定扩展取决于配置。在没有 MMU 或 PCI-e 的设备上,DEVICE_MMIO_MAPDEVICE_MMIO_RAM 扩展为零。

Device Model Drivers with multiple MMIO regions

具有多个 MMIO 区域的设备模型驱动程序

Some drivers may have multiple MMIO regions. In addition, some drivers may already be implementing a form of inheritance which requires some other data to be placed first in the config_info and driver_data structures.

一些驱动程序可能有多个 MMIO 区域。此外,一些驱动程序可能已经实现了一种继承形式,这种形式要求将其他一些数据首先放在 config_infodrive_data 结构中。

This can be managed with the DEVICE_MMIO_NAMED variant macros. These require that DEV_CFG() and DEV_DATA() macros be defined to obtain a properly typed pointer to the driver’s config_info or dev_data structs. For example:

这可以通过 DEVICE_MMIO_NAMED 变量宏来管理。这需要定义 DEV _ CFG ()和 DEV _ DATA ()宏,以获得指向驱动程序的 config _ info 或 DEV _ data 结构的正确类型的指针。例如:

struct my_driver_config {
   ...
     DEVICE_MMIO_NAMED_ROM(corge);
     DEVICE_MMIO_NAMED_ROM(grault);
   ...
}

struct my_driver_dev_data {
        ...
     DEVICE_MMIO_NAMED_RAM(corge);
     DEVICE_MMIO_NAMED_RAM(grault);
     ...
}

#define DEV_CFG(_dev) \
   ((const struct my_driver_config *)((_dev)->config))

#define DEV_DATA(_dev) \
   ((struct my_driver_dev_data *)((_dev)->data))

const static struct my_driver_config my_driver_config_0 = {
   ...
   DEVICE_MMIO_NAMED_ROM_INIT(corge, DT_DRV_INST(...)),
   DEVICE_MMIO_NAMED_ROM_INIT(grault, DT_DRV_INST(...)),
   ...
}

int my_driver_init(const struct device *dev)
{
   ...
   DEVICE_MMIO_NAMED_MAP(dev, corge, K_MEM_CACHE_NONE);
   DEVICE_MMIO_NAMED_MAP(dev, grault, K_MEM_CACHE_NONE);
   ...
}

int my_driver_some_function(const struct device *dev)
{
   ...
   /* Write some data to the MMIO regions */
   sys_write32(0xDEADBEEF, DEVICE_MMIO_GET(dev, grault));
   sys_write32(0xF0CCAC1A, DEVICE_MMIO_GET(dev, corge));
   ...
}

Device Model Drivers with multiple MMIO regions in the same DT node

具有同一 DT 节点中多个 MMIO 区域的设备模型驱动程序

Some drivers may have multiple MMIO regions defined into the same DT device node using the reg-names property to differentiate them, for example:

有些驱动程序可能有多个 MMIO 区域定义到同一个 DT 设备节点,使用 reg-name 属性来区分它们,例如:

/dts-v1/;

/ {
        a-driver@40000000 {
                reg = <0x40000000 0x1000>,
                      <0x40001000 0x1000>;
                reg-names = "corge", "grault";
        };
};

This can be managed as seen in the previous section but this time using the DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME macro instead. So the only difference would be in the driver config struct:

这可以按照上一节中的方式进行管理,但是这次使用的是 DevICE _ MMIO _ NAMED _ ROM _ INIT _ BY _ NAME 宏。因此,唯一的区别在于驱动程序的配置结构:

const static struct my_driver_config my_driver_config_0 = {
   ...
   DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(corge, DT_DRV_INST(...)),
   DEVICE_MMIO_NAMED_ROM_INIT_BY_NAME(grault, DT_DRV_INST(...)),
   ...
}

Drivers that do not use Zephyr Device Model

不使用 Zephyr 设备模型的驱动程序

Some drivers or driver-like code may not user Zephyr’s device model, and alternative storage must be arranged for the MMIO data. An example of this are timer drivers, or interrupt controller code.

一些驱动程序或类似驱动程序的代码可能不使用 Zephyr 的设备模型,并且必须为 MMIO 数据安排备用存储器。这方面的一个例子是定时器驱动程序或中断控制器代码。

This can be managed with the DEVICE_MMIO_TOPLEVEL set of macros, for example:

这可以通过 devICE _ MMIO _ TOPLEVEL 宏集来管理,例如:

DEVICE_MMIO_TOPLEVEL_STATIC(my_regs, DT_DRV_INST(..));

void some_init_code(...)
{
   ...
   DEVICE_MMIO_TOPLEVEL_MAP(my_regs, K_MEM_CACHE_NONE);
   ...
}

void some_function(...)
   ...
   sys_write32(DEVICE_MMIO_TOPLEVEL_GET(my_regs), 0xDEADBEEF);
   ...
}

Drivers that do not use DTS

不使用 DTS 的驱动程序

Some drivers may not obtain the MMIO physical address from DTS, such as is the case with PCI-E. In this case the device_map() function may be used directly:

有些驱动程序可能无法从 DTS 获得 MMIO 物理地址,例如 PCI-E。在这种情况下,device _ map ()函数可以直接使用:

void some_init_code(...)
{
   ...
   struct pcie_mbar mbar;
   bool bar_found = pcie_get_mbar(bdf, index, &mbar);

   device_map(DEVICE_MMIO_RAM_PTR(dev), mbar.phys_addr, mbar.size, K_MEM_CACHE_NONE);
   ...
}

For these cases, DEVICE_MMIO_ROM directives may be omitted.

对于这些情况,DevICE _ MMIO _ ROM 指令可以省略。

API 参考

驱动 API

DEVICE_NAME_GET(name)

  • 展开成全局设备对象变量的名称,如__device__xxx
  • 它是全局变量的名字,而不是指向设备名称字段的指针

DEVICE_DT_NAME(node_id)

  • 返回设备树节点的字符串名称
  • 它返回的是设备的name字段的文本,也就是设备树中节点的label;如果没有label节点的全名就是node-name@@unit-address的形式

DEVICE_DT_DEFINE(node_id, init_fn, …)

  • 从设备树节点标识符node_id(DT_NODELABLE(rcc))创建设备对象,并将其设置为启动时初始化
  • 这个宏定义了一个在系统初始化期间由内核自动配置的 struct 设备
  • 作为 C 标识符的全局设备对象的名称来自节点的依赖序号(The global device object’s name as a C identifier is derived from the node’s dependency ordinal)
  • 设备结构的 name 字段设置为 DEVICE_DT_NAME(node_id)
  • 可以通过 DEVICE_DT_GET(node_id) 从包含 device.h 的任何源文件获得指向全局设备对象的指针
  • 在使用指针之前,应该使用 device_is_ready() 检查被引用的对象

DEVICE_DT_INST_DEFINE(inst)

  • DEVICE_DT_DEFINE一样,不过是使用DT_DRV_COMPAT的实例编号而不是node_id(可以通过DT_DRV_INST(inst)获取)

DEVICE_DT_NAME_GET(node_id)

  • 通常只在驱动代码中使用
  • node_id的全局设备对象的变量名称
  • C 标识符(变量名)形式返回的全局设备结构的名称,这个设备必须是用DEVICE_DT_DEFINE()DEVICE_DT_INST_DEFINE()分配的
  • node_id就是node节点在C代码中的变量名

DEVICE_DT_GET(node_id)

  • 从node_id获取设备对象const struce devie*
  • 如果设备没有被分配,在链接时会有undefined reference to __device_dts_ord_<N>的问题

DEVICE_DT_INST_GET(inst)

  • 为兼容 DT_DRV_COMPAT 的实例获取一个 const struct device*
  • 等同于DEVICE_DT_GET(DT_DRV_INST(inst)) = DEVICE_DT_GET(node_id)
  • inst:实例编号

device_get_binding(name)

const struct device *device_get_binding(const char *name)
  • 根据它的name字段获取设备const struct devcie*
  • 这个函数在系统中的device中迭代,如果device.name == name,返回;否则返回NULL

device_is_ready(dev)

  • device设备有initialized的属性来表明是否初始化

设备树 API

设备树API在<devicetree.h>文件中

node_id

  • 节点标签识node_id(node identifier)是在 C 预处理器时引用设备树节点的一种方法,虽然节点标识符不是 C 值,但您可以使用它以C右值的形式访问设备树数据
  • /根结点的node_id为DT_ROOT
  • 可以使用DT_PATH(), DT_NODELABEL(), DT_ALIAS(), DT_INST()为其他设备树节点创建node_id
  • 可以使用DT_PARENT(), DT_CHILD()为父/子节点创建node_id
/ {
   aliases {
      my-serial = &serial1;
   };
   soc {
         serial1: serial@40001000 {
            status = "okay";
            current-speed = <115200>;
            ...
         };
   };
};

DT_PATH(…)

  • ...参数是从根开始到达所需节点所需的树中非根节点的名称
  • DT_PATH(soc, serial_40001000)

DT_NODELABEL(label)

  • 通过label获取node_id
  • DT_NODELABEL(serial1)

DT_ALIAS(alias)

  • /aliases节点中取出node_id
  • DT_ALIAS(my_serial)

DT_INST(inst, compat)

  • 通过comapt实例返回node_id
  • 可以通过实例号inst和compat(小写和_)得到这些节点的node_id
  • 实例编号的特性
    • 所有compatible节点的实例号是从0开始的
    • disable的实例号排在okay实例号后面
  • 不在乎获取的是哪个节点,可有此接口

DT_COMPAT_GET_ANY_STATUS_OKAY(compat

node-a { compatible = "vnd,device"; status = "okay"; };

node-b { compatible = "vnd,device"; status = "okay"; };

node-c { compatible = "vnd,device"; status = "disabled"; };
  • 获取状态“ okay”节点的节点标识符
  • 如果希望获得具有给定兼容性的任意启用节点,并且不在乎获得哪个节点,请使用此选项
  • DT_COMPAT_GET_ANY_STATUS_OKAY(vnd_device) 扩展为 node-anode-b

DT_NODE_PATH(node_id)

/ { soc { node: my-node@12345678 { ... }; };
};
  • DT_NODE_PATH(DT_NODELABEL(node)) -> “/soc/my-node@12345678”

DT_NODE_CHILD_IDX(node_id)

parent {
        c1: child-1 {};
        c2: child-2 {};
};
  • node_id在父节点中的索引
  • DT_NODE_CHILD_IDX(DT_NODELABEL(c1)) // 0

访问设备树节点的属性

DT_PROP(node_id, prop)

  • 访问节点 node_id 的属性值
  • 对于 phandle 属性,返回 node_id
  • 不同的属性类型,返回不同的形式,比如array, uint8-array, string-array可能返回 {0, 1, 2}, {"aaa", "bbb"}
  • 属性的类型是由绑定来定义的

DT_PROP_LEN(node_id, prop)

  • 属性的逻辑长度,即元素个数,非不是属性的字节长度
  • DT_PROP_LEN_OR(node_id, prop, default_value)没有属性则返回默认值

DT_PROP_HAS_IDX(node_id, prop, idx)

  • idx是否有效

DT_PROP_BY_IDX(node_id, prop, idx)

  • 获取数组类型属性中索引“ idx”处的值,相当于 node_id->prop[idx], 这里的prop是一个数组

DT_LABEL(node_id)

  • node_id的label属性值,相当于node_id.label
  • device_get_binding(DT_LABEL(node_id))

DT_PHA_BY_IDX(node_id, pha, idx, cell)

  • 相当于node->phandle_array[idx].cell
  • 设备树

     gpio0: gpio@... { #gpio-cells = <2>; };
    
     gpio1: gpio@... { #gpio-cells = <2>; };
    
     led: led_0 { gpios = <&gpio0 17 0x1>, <&gpio1 5 0x3>; };
    
  • 绑定文件

     gpio-cells:
        - pin
        - flags
    
  • 使用

     #define LED DT_NODELABEL(led)
    
     // led->gpios[0].0 = led_0.gpois[0].0
     DT_PHA_BY_IDX(LED, gpios, 0, pin)   // 17
    
     // led->gpios[1].1 = led_0.gpois[1].1
     DT_PHA_BY_IDX(LED, gpios, 1, flags) // 0x3
    

DT_PHA_BY_NAME(node_id, pha, name, cell)

  • 设备树文件

     n: node {
           io-channels = <&adc1 10>, <&adc2 20>;
           io-channel-names = "SENSOR", "BANDGAP";
     };
    
  • 绑定文件

     io-channel-cells:
        - input
    
  • 通过node中phanle-array的数据元素名的信元名,获取它的属性值
    • node_id相当于设备树中的节点
      • n:node{}节点
    • pha:节点的phandle-array
      • pha = io-channels = <&adc1 10>, <&adc2 20>
    • name:array中每个元素都有自己的名称
      • name: "SENSOR", "BANDGAP"
    • cell:元素中多个信元也有自己的名称
      • cell: io-chnnael-celss: input
  • 使用

     DT_PHA_BY_NAME(DT_NODELABEL(n), io_channels, sensor, input)  // 10
     DT_PHA_BY_NAME(DT_NODELABEL(n), io_channels, bandgap, input) // 20
    

DT_PHANDLE_BY_NAME(node_id, pha, name)

  • 从node_id的phandle数组元素名获取对应phandle节点的·node1_id`
adc1: adc@... { label = "ADC_1"; };
adc2: adc@... { label = "ADC_2"; };
n: node {
        io-channels = <&adc1 10>, <&adc2 20>;
        io-channel-names = "SENSOR", "BANDGAP";
};

使用

#define NODE DT_NODELABEL(n)

DT_PHANDLE_BY_NAME(NODE, io_channels, sensor)  // DT_NODELABEL(adc1)