kernel/drivers/index.html · kernel/drivers/index.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Drivers

Device Driver Model

Build-time device metadata와 runtime state를 분리하고 공통 subsystem API를 각 driver 구현에 연결하는 Zephyr의 driver 기반 구조입니다.

Official pathkernel/drivers/index.html
Source filekernel/drivers/index.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Device object는 read-only config/API와 mutable data/state를 묶고, initialization level·priority·dependency handle을 통해 boot 순서와 사용 준비 상태를 관리합니다.

Device object

config · api · data · state

Initialization

level · priority · deferred

MMIO

ROM metadata · RAM mapping

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 E4B5E0B2555230CF1226F7B2AEFEA792EB578543342EAB4F8317E15F2028ED0C
.. _device_model_api:

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.

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.

Each type of driver (e.g. UART, SPI, I2C) is supported by a generic type 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.

.. image:: ../../../../kernel/drivers/device_driver_model.svg
   :width: 40%
   :align: center
   :alt: 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.

* **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 :ref:`random API functions <random_api>` for random
    values. :ref:`Entropy functions <entropy_api>` 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.

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.

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

Driver APIs
***********

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

:c:func:`DEVICE_DEFINE()`
   Create device object and related data structures including setting it
   up for boot-time initialization.

:c:func:`DEVICE_NAME_GET()`
   Converts a device identifier to the global identifier for a device
   object.

:c:func:`DEVICE_GET()`
   Obtain a pointer to a device object by name.

:c:func:`DEVICE_DECLARE()`
   Declare a device object.  Use this when you need a forward reference
   to a device that has not yet been defined.

.. _device_struct:

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:

.. code-block:: C

  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.

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.

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.


Subsystems and API Structures
*****************************

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.

A subsystem API definition typically looks like this:

.. code-block:: C

  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:

.. code-block:: C

  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()``.

.. 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.

Device-Specific API Extensions
******************************

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.

A device-specific API definition typically looks like this:

.. code-block:: C

   #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 <zephyr/syscalls/specific.h>

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

.. code-block:: C

   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/internal/syscall_handler.h>

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

   #include <zephyr/syscalls/specific_from_user_mrsh.c>

   #endif /* CONFIG_USERSPACE */

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

.. 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``.

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.

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.

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``:

.. code-block:: C

  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:

.. code-block:: C

  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:

.. code-block:: C

  #if CONFIG_MY_DRIVER_0

  DEVICE_DECLARE(my_driver_0);

  static void my_driver_config_irq_0(const struct device *dev)
  {
        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.

Initialization Levels
*********************

Drivers may depend on other drivers being initialized first, or require
the use of kernel services. :c:func:`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:

``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.

``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.

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

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``).

Drivers and other system utilities can determine whether startup is
still in pre-kernel states by using the :c:func:`k_is_pre_kernel`
function.

Deferred initialization
***********************

Initialization of devices can also be deferred to a later time. In this case,
the device is not automatically initialized by Zephyr at boot time. Instead,
the device is initialized when the application calls :c:func:`device_init`.
To defer a device driver initialization, add the property ``zephyr,deferred-init``
to the associated device node in the DTS file. For example:

.. code-block:: devicetree

   / {
           a-driver@40000000 {
                   reg = <0x40000000 0x1000>;
                   zephyr,deferred-init;
           };
   };

System Drivers
**************

In some cases you may just need to run a function at boot. For such cases, the
:c:macro:`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.

Inspecting the initialization sequence
**************************************

Device drivers declared with :c:macro:`DEVICE_DEFINE` (or any variations of it)
and :c:macro:`SYS_INIT` are processed at boot time and the corresponding
initialization functions are called sequentially according to their specified
level and priority.

Sometimes it's useful to inspect the final sequence of initialization function
call as produced by the linker. To do that, use the ``initlevels`` CMake
target, for example ``west build -t initlevels``.

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.

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

Memory Mapping
**************

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

- The I/O ranges must be probed at runtime from the bus, such as with
  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.

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.

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
:c:func:`device_map`.

Device Model Drivers with one MMIO region
=========================================

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:

.. code-block:: C

   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.

Device Model Drivers with multiple MMIO regions
===============================================

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.

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:

.. code-block:: C

   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
===================================================================

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

.. code-block:: devicetree

   /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:

.. code-block:: C

   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
===========================================

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.

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

.. code-block:: C

   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
===========================

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

.. code-block:: C

   void some_init_code(...)
   {
      ...
      struct pcie_bar 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.

API Reference
**************

.. doxygengroup:: device_model

Part 3

한국어 전문 번역

소개

Zephyr kernel은 다양한 device driver를 지원합니다. 실제 driver 사용 가능 여부는 board와 driver 구성에 따라 달라집니다.

Zephyr device model은 system에 포함된 driver를 일관된 방식으로 구성하고, build에 선택된 모든 driver를 초기화할 책임을 집니다.

UART, SPI, I2C 같은 각 driver 형식은 공통 generic type API로 지원됩니다.

이 model에서 driver는 초기화 중 자신의 API function pointer를 담은 구조체 pointer를 채웁니다. 이 구조체는 initialization level 순서에 맞춰 RAM section에 배치됩니다.

Zephyr Device Driver Model
Zephyr Device Driver Model. 공통 subsystem API가 각 device instance의 API 구조체를 거쳐 실제 driver 구현으로 연결됩니다.

표준 Driver

지원되는 모든 board 구성에 존재하는 표준 device driver 종류는 다음과 같습니다.

  • Interrupt controller: kernel interrupt management subsystem이 사용합니다.
  • Timer: kernel system clock과 hardware clock subsystem이 사용합니다.
  • Serial communication: kernel system console subsystem이 사용합니다.
  • Entropy: random number generator subsystem에 entropy number source를 제공합니다.

동기 호출

Zephyr는 여러 board를 위한 device driver 집합을 제공합니다. 특정 hardware에 interrupt가 없다면 예외지만, 각 driver는 polling보다 interrupt 기반 구현을 지원해야 합니다.

i2c.h나 spi.h처럼 device별 API를 통하는 high-level call은 보통 synchronous 용도이므로 blocking call이어야 합니다.

Driver API

device.h가 제공하는 다음 API는 device driver 내부용이며 application이 직접 사용하면 안 됩니다.

DEVICE_DEFINE()

Device object와 관련 data structure를 만들고 boot-time initialization을 설정합니다.

DEVICE_NAME_GET()

Device identifier를 전역 device object identifier로 변환합니다.

DEVICE_GET()

이름으로 device object pointer를 얻습니다.

DEVICE_DECLARE()

아직 정의되지 않은 device를 forward reference해야 할 때 device object를 선언합니다.

Driver Data 구조

Device initialization macro는 build time에 일부 data structure를 채우고, 이를 read-only 부분과 runtime mutable 부분으로 나눕니다. 상위 수준의 구조는 다음과 같습니다.

struct device {
      const char *name;
      const void *config;
      const void *api;
      void * const data;
};

config member는 build time에 정해지는 read-only 구성 data입니다. Base MMIO address, IRQ line number, 그 밖의 고정된 device 물리 특성이 여기에 들어갑니다. DEVICE_DEFINE()과 관련 macro에 넘기는 config pointer입니다.

data 구조체는 RAM에 유지되며 driver instance별 runtime 관리에 씁니다. Reference count, semaphore, scratch buffer 등을 담을 수 있습니다.

api 구조체는 generic subsystem API를 driver의 device-specific 구현에 연결합니다. 보통 read-only이고 build time에 채워집니다.

Subsystem과 API 구조체

대부분의 driver는 device-independent subsystem API를 구현합니다. Application은 이 generic API만 대상으로 작성하므로 특정 driver 구현에 종속되지 않습니다.

전형적인 subsystem 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);
}

특정 subsystem을 구현하는 driver는 실제 API 함수를 정의하고 subsystem_api 구조체 instance를 채웁니다.

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
};

그다음 my_driver_api_funcs를 DEVICE_DEFINE()의 api argument로 넘깁니다.

Device별 API 확장

어떤 device는 GPIO 같은 표준 driver subsystem으로 표현할 수 있지만 공통 API로 노출할 수 없는 추가 기능도 제공합니다. 이 경우 subsystem operation과 device-specific header에 정의한 전용 API를 함께 사용합니다.

전형적인 device-specific 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 <zephyr/syscalls/specific.h>

확장 기능을 구현하는 driver는 generic subsystem 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/internal/syscall_handler.h>

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

#include <zephyr/syscalls/specific_from_user_mrsh.c>

#endif /* CONFIG_USERSPACE */

Application은 subsystem API와 specific API 양쪽으로 device를 사용합니다.

Driver 하나의 여러 Instance

한 system에 같은 driver instance가 여러 개 있을 수 있습니다. GPIO bank나 UART가 여러 개인 경우 각 instance는 서로 다른 config 구조체와 data 구조체를 가집니다.

Instance마다 interrupt line이 다르면 IRQ_CONNECT() parameter가 build time에 해석되어야 하므로 instance별 configuration function으로 설정합니다. 두 my_driver instance를 예로 들면 header에 다음 형식을 둡니다.

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;
};

공통 init function은 instance config에서 interrupt 구성 callback을 꺼내 실행합니다.

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;
}

각 instance를 선언할 때는 build-time constant로 IRQ를 연결하는 전용 callback과 config/data를 제공합니다.

#if CONFIG_MY_DRIVER_0

DEVICE_DECLARE(my_driver_0);

static void my_driver_config_irq_0(const struct device *dev)
{
      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 */

DEVICE_DECLARE()는 IRQ handler argument에 device pointer가 필요하면서 DEVICE_DEFINE() 자체에도 init 함수가 필요한 순환 의존성을 피합니다.

초기화 단계

Driver는 다른 driver가 먼저 초기화되어야 하거나 kernel service가 필요할 수 있습니다. DEVICE_DEFINE()과 관련 API는 boot sequence에서 init function을 실행할 시점을 지정합니다. 원문은 네 initialization level이라고 표현하지만 이 절과 생성 API는 아래 세 단계만 열거합니다.

PRE_KERNEL_1

Processor/SoC에 있는 hardware만 의존하는 등 dependency가 없는 device에 씁니다. Kernel service는 아직 없지만 interrupt subsystem은 구성되어 있어 interrupt 설정은 가능합니다. Init 함수는 interrupt stack에서 실행됩니다.

PRE_KERNEL_2

PRE_KERNEL_1 단계 device 초기화에 의존하는 device에 씁니다. 아직 kernel service는 사용할 수 없고 init 함수는 interrupt stack에서 실행됩니다.

POST_KERNEL

구성 중 kernel service가 필요한 device에 씁니다. Init 함수는 kernel main task context에서 실행됩니다.

각 단계 안에서 0~99 integer priority를 지정하며 작은 값이 먼저 초기화됩니다. Priority는 leading zero나 sign이 없는 decimal integer literal 또는 같은 값을 나타내는 symbol이어야 합니다. #define MY_INIT_PRIO 32는 허용되지만 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5 같은 symbolic expression은 허용되지 않습니다.

Driver와 system utility는 k_is_pre_kernel()로 startup이 아직 pre-kernel 상태인지 확인할 수 있습니다.

지연 초기화

Device 초기화를 나중으로 미룰 수 있습니다. 이 device는 boot 때 자동 초기화되지 않고 application이 device_init()을 호출할 때 초기화됩니다. DTS의 해당 node에 zephyr,deferred-init property를 추가합니다.

/ {
        a-driver@40000000 {
                reg = <0x40000000 0x1000>;
                zephyr,deferred-init;
        };
};

System Driver

Boot 때 함수 하나만 실행하면 되는 경우 SYS_INIT을 사용합니다. Config나 runtime data structure를 받지 않으며 나중에 이름으로 device pointer를 얻는 방법도 없습니다. Initialization level과 priority 정책은 device와 같습니다.

초기화 순서 검사

DEVICE_DEFINE 변형이나 SYS_INIT으로 선언한 항목은 boot 때 지정 level과 priority 순서로 initialization function이 하나씩 호출됩니다.

Linker가 만든 최종 initialization call 순서는 initlevels CMake target으로 확인합니다. 예: west build -t initlevels.

오류 처리

정상 동작 중 예상 가능한 실패, 예를 들어 storage full 같은 경우가 아니라면 return value를 전파하기보다 __ASSERT() macro를 쓰는 편이 좋습니다. 잘못된 parameter, programming error, consistency check, pathological 또는 복구 불가능 실패는 assertion으로 처리해야 합니다.

호출자가 검사할 오류를 반환해야 할 때는 성공 시 0, 실패 시 POSIX errno.h code를 반환합니다. 상세 규칙은 Zephyr Naming Conventions의 return codes를 따릅니다.

Memory Mapping

어떤 system은 peripheral MMIO 영역의 linear address를 build time에 알 수 없습니다. PCI Express처럼 bus를 runtime에 probe해야 하거나, MMU가 활성화되어 MMIO physical address를 kernel이 선택한 virtual address의 page table에 map해야 하는 경우입니다.

이 system은 MMIO 범위를 저장할 RAM 공간을 유지하고 driver init function에서 mapping을 만들어야 합니다. 다른 system은 DTS의 physical address를 직접 쓸 수 있어 RAM 저장이 필요 없습니다.

두 경우를 모두 지원할 driver에는 DEVICE_MMIO 범위의 API와 device_map() mapping 함수를 제공합니다.

MMIO 영역 하나인 Device Model Driver

MMIO 영역 하나를 유지하는 가장 단순한 driver는 config_info와 driver_data 구조체에 DEVICE_MMIO_ROM과 DEVICE_MMIO_RAM을 사용합니다. DTS에서 DEVICE_MMIO_ROM_INIT으로 config를 초기화하고 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));
   ...
}

Macro expansion은 구성에 따라 달라집니다. MMU나 PCI-e가 없는 device에서는 DEVICE_MMIO_MAP과 DEVICE_MMIO_RAM이 아무것도 생성하지 않습니다.

MMIO 영역이 여러 개인 Device Model Driver

Driver가 여러 MMIO 영역을 가지거나 inheritance 때문에 config_info와 driver_data의 첫 위치에 다른 data를 둬야 할 수 있습니다. 이 경우 DEVICE_MMIO_NAMED 변형을 사용하고 DEV_CFG()와 DEV_DATA()가 올바른 형식의 config/data pointer를 얻도록 정의합니다.

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));
   ...
}

같은 DT node에 MMIO 영역이 여러 개인 Driver

한 DT device node의 여러 MMIO 영역은 reg-names property로 구분할 수 있습니다.

/dts-v1/;

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

앞 절과 같은 named 방식을 쓰되 config 구조체 초기화에서 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(...)),
   ...
}

Zephyr Device Model을 쓰지 않는 Driver

Timer driver나 interrupt controller code처럼 Zephyr device model을 사용하지 않는 driver 또는 driver-like code는 MMIO data를 위한 별도 storage를 마련해야 합니다. DEVICE_MMIO_TOPLEVEL macro 집합을 사용합니다.

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);
   ...
}

DTS를 쓰지 않는 Driver

PCI-E처럼 DTS에서 MMIO physical address를 얻지 않는 driver는 device_map()을 직접 사용할 수 있습니다.

void some_init_code(...)
{
   ...
   struct pcie_bar 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);
   ...
}

이 경우 DEVICE_MMIO_ROM directive는 생략할 수 있습니다.

API 참고

device_model group은 device 정의, devicetree reference, compact dependency handle, readiness와 deferred initialization API를 제공합니다.

매크로와 정의

DEVICE_HANDLE_NULL

알 수 없거나 어떤 Zephyr device에도 대응하지 않는 handle을 나타내는 flag 값입니다.

DEVICE_NAME_GET(dev_id)

DEVICE_DEFINE()에 넘긴 dev_id로 생성된 전역 device object symbol의 전체 C identifier를 만듭니다. device::name 문자열 pointer가 아니라 전역 variable 이름이며, driver source에서 extern 선언 뒤 DEVICE_GET()을 사용할 때 주로 씁니다. 일반 code에서는 대개 device_get_binding()을 찾는 편이 맞습니다.

매개변수

  • dev_id: device identifier

반환값: Device 정의 macro가 만든 전역 device object 이름입니다.

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

Devicetree node에서 할당하지 않는 device object를 만들고 boot-time 자동 초기화 대상으로 등록합니다. Devicetree 기반 device에는 DEVICE_DT_DEFINE() 또는 DEVICE_DT_INST_DEFINE()을 사용해야 합니다.

매개변수

  • dev_id: 전역 device 구조체 이름에 쓰는 고유 C identifier token
  • name: device::name에 저장할 문자열. User mode lookup을 위해 종료 NULL을 포함해 Z_DEVICE_MAX_NAME_LEN보다 짧아야 합니다.
  • init_fn: system initialization 중 kernel이 실행할 초기화 함수 pointer이며 NULL일 수 있습니다.
  • pm: device::pm에 저장할 pm_device power-management resource pointer이며 PM을 쓰지 않으면 NULL입니다.
  • data: device::data에 저장할 private mutable data pointer
  • config: device::config에 저장할 private constant data pointer
  • level: PRE_KERNEL_1, PRE_KERNEL_2 또는 POST_KERNEL 초기화 단계
  • prio: 같은 초기화 단계 안의 우선순위
  • api: device API 구조체 pointer이며 NULL일 수 있습니다.
DEVICE_DT_NAME(node_id)

Devicetree node identifier에서 device 이름으로 쓸 string literal을 만듭니다.

매개변수

  • node_id: devicetree node identifier

반환값: label property가 있으면 그 값, 아니면 node-name@unit-address 형식의 전체 node 이름입니다.

DEVICE_DT_DEFER(node_id)

Devicetree node 초기화를 연기해야 하는지 판정합니다.

매개변수

  • node_id: devicetree node identifier

반환값: 초기화를 연기해야 하면 true인 boolean입니다.

DEVICE_DT_DEFINE(node_id, init_fn, pm, data, config, level, prio, api, ...)

Devicetree node identifier에서 device object를 만들고 boot-time 초기화 대상으로 등록합니다. 전역 C identifier는 node dependency ordinal에서 만들고 device::name은 DEVICE_DT_NAME(node_id)로 설정합니다. Extern visibility를 가지므로 zephyr/device.h를 include한 어느 source에서도 DEVICE_DT_GET(node_id)로 pointer를 얻을 수 있으며, 사용 전 device_is_ready()로 확인해야 합니다.

매개변수

  • node_id: devicetree node identifier
  • init_fn: system initialization 중 실행할 함수 pointer이며 NULL일 수 있습니다.
  • pm: power-management resource pointer이며 PM을 쓰지 않으면 NULL입니다.
  • data: private mutable data pointer
  • config: private constant data pointer
  • level: PRE_KERNEL_1, PRE_KERNEL_2 또는 POST_KERNEL
  • prio: 같은 단계 안의 우선순위
  • api: API 구조체 pointer이며 NULL일 수 있습니다.
DEVICE_DT_INST_DEFINE(inst, ...)

Node identifier 대신 DT_DRV_COMPAT compatible instance를 사용하는 DEVICE_DT_DEFINE() 변형입니다.

매개변수

  • inst: DT_DRV_INST(inst)로 바뀌는 instance 번호
  • ...: DEVICE_DT_DEFINE()이 받는 나머지 parameter
DEVICE_DT_NAME_GET(node_id)

DEVICE_DT_DEFINE() 또는 DEVICE_DT_INST_DEFINE()으로 할당한 node의 전역 device 구조체 C identifier를 반환합니다. 일반 code에서는 대개 DEVICE_DT_GET()을 사용합니다.

매개변수

  • node_id: devicetree node identifier

반환값: Device object의 C identifier 이름입니다.

DEVICE_DT_GET(node_id)

Devicetree node에서 driver가 만든 device object pointer를 얻습니다. Device가 할당되지 않았다면 linker에서 undefined reference to __device_dts_ord_ 형태로 실패하므로, 필요한 Kconfig가 driver를 build에 포함하는지 확인해야 합니다.

매개변수

  • node_id: devicetree node identifier

반환값: 해당 node용 device object pointer입니다.

DEVICE_DT_INST_GET(inst)

DT_DRV_COMPAT instance용 device reference를 얻습니다. DEVICE_DT_GET(DT_DRV_INST(inst))와 같습니다.

매개변수

  • inst: DT_DRV_COMPAT instance 번호

반환값: 해당 instance의 device object pointer입니다.

DEVICE_DT_GET_ANY(compat)

주어진 compatible의 enabled node 중 device object가 만들어진 임의의 하나를 반환합니다. 여러 개면 어느 것이 선택되는지 보장하지 않으며, non-NULL이면 사용 전에 readiness를 검사해야 합니다.

매개변수

  • compat: 소문자와 underscore로 쓴 devicetree compatible

반환값: Device pointer이며 없으면 NULL입니다.

DEVICE_DT_GET_ONE(compat)

주어진 compatible의 enabled node에서 device reference 하나를 얻습니다. 없으면 compile time에 실패하고 여러 개면 임의의 하나를 반환합니다. 사용 전에 device_is_ready()로 확인해야 합니다.

매개변수

  • compat: 소문자와 underscore로 쓴 devicetree compatible

반환값: Device pointer입니다.

DEVICE_DT_GET_OR_NULL(node_id)

선택적인 device reference를 얻습니다. Node status가 okay이면 DEVICE_DT_GET(node_id), 아니면 NULL입니다.

매개변수

  • node_id: devicetree node identifier

반환값: Device reference 또는 NULL입니다.

DEVICE_GET(dev_id)

DEVICE_DEFINE()에 넘긴 dev_id로 만든 device object 주소를 얻습니다.

매개변수

  • dev_id: device identifier

반환값: DEVICE_DEFINE()이 만든 device object pointer입니다.

DEVICE_DECLARE(dev_id)

DEVICE_DEFINE()의 전체 정의보다 앞에서 DEVICE_GET()을 사용할 수 있도록 static device object를 선언합니다. Init 또는 instance별 interrupt 설정 함수가 자기 device pointer를 요구해 생기는 순환 의존성을 피할 때 유용합니다.

매개변수

  • dev_id: device identifier
DEVICE_INIT_DT_GET(node_id)

Devicetree node에서 생성된 init_entry reference를 얻습니다.

매개변수

  • node_id: devicetree node identifier

반환값: 해당 node의 init_entry object pointer입니다.

DEVICE_INIT_GET(dev_id)

Device identifier에서 생성된 init_entry reference를 얻습니다.

매개변수

  • dev_id: device identifier

반환값: 해당 device의 init_entry object pointer입니다.

형식 별칭

typedef int16_t device_handle_t

Device handle을 나타내는 compact 형식입니다. Pointer보다 작으면서 device와 상호 변환할 수 있어 device 목록 저장에 주로 씁니다. 극값과 0은 특별한 의미를 가지며, 음수는 system clock이나 SYS_INIT() 함수처럼 Zephyr device가 아닌 기능을 나타냅니다.

typedef int (*device_visitor_callback_t)(const struct device *dev, void *context)

Device 집합을 순회할 때 호출하는 visitor 함수 prototype입니다. Non-negative를 반환하면 방문 성공으로 보고 계속하며, negative error code를 반환하면 순회를 중단합니다.

매개변수

  • dev: 현재 방문 중인 device
  • context: visitor를 지원하는 호출자 상태

반환값: 계속하려면 non-negative, 중단하려면 negative error code입니다.

함수

static inline device_handle_t device_handle_get(const struct device *dev)

주어진 device의 handle을 얻습니다.

매개변수

  • dev: handle이 필요한 device

반환값: Device handle이며 연결된 handle이 없으면 DEVICE_HANDLE_NULL입니다.

static inline const struct device *device_from_handle(device_handle_t dev_handle)

Handle에 대응하는 device를 얻습니다.

매개변수

  • dev_handle: device handle

반환값: 해당 device pointer이며 유효한 device를 식별하지 않으면 NULL입니다.

static inline const device_handle_t *device_required_handles_get(const struct device *dev, size_t *count)

Devicetree가 판정한 이 device의 직접 dependency handle 배열을 얻습니다. Transitive dependency는 포함하지 않으므로 필요하면 재귀적으로 구해야 합니다.

매개변수

  • dev: dependency를 조회할 device
  • count: 반환 배열 길이를 저장할 위치. NULL 반환 시 쓰지 않으며 dependency가 없으면 0일 수 있습니다.

반환값: count개 handle sequence pointer이며 dependency data가 없으면 NULL입니다.

static inline const device_handle_t *device_injected_handles_get(const struct device *dev, size_t *count)

Z_DEVICE_DEFINE의 추가 argument로 수동 주입한 직접 dependency handle 배열을 얻습니다. Transitive dependency는 포함하지 않습니다.

매개변수

  • dev: 주입 dependency를 조회할 device
  • count: 배열 길이를 저장할 위치. NULL 반환 시 쓰지 않으며 없으면 0일 수 있습니다.

반환값: *count개 handle sequence pointer이며 dependency data가 없으면 NULL입니다.

static inline const device_handle_t *device_supported_handles_get(const struct device *dev, size_t *count)

이 device를 직접 필요로 하는, 즉 이 device가 support하는 device handle 집합을 얻습니다. Devicetree의 직접 관계만 포함하며 transitive 관계는 재귀적으로 구해야 합니다.

매개변수

  • dev: support 관계를 조회할 device
  • count: 배열 길이를 저장할 위치. NULL 반환 시 쓰지 않으며 의존 device가 없으면 0일 수 있습니다.

반환값: *count개 handle sequence pointer이며 dependency data가 없으면 NULL입니다.

int device_required_foreach(const struct device *dev, device_visitor_callback_t visitor_cb, void *context)

Dev가 직접 필요로 하는 모든 device를 순서 보장 없이 방문합니다. 정적 devicetree 관계와 runtime 등록 dependency를 포함할 수 있습니다. Visitor가 negative를 반환하면 즉시 멈추고 그 값을 반환합니다. Unprivileged thread에서는 사용할 수 없습니다.

매개변수

  • dev: dependency 집합의 기준 device이며 NULL이면 안 됩니다.
  • visitor_cb: 각 dependency에 호출할 함수이며 NULL이면 안 됩니다.
  • context: visitor에 전달할 상태이며 callback이 허용하면 NULL일 수 있습니다.

반환값: 모두 성공하면 방문한 device 수, 실패하면 첫 negative visitor 반환값입니다.

int device_supported_foreach(const struct device *dev, device_visitor_callback_t visitor_cb, void *context)

Dev가 직접 support하는 모든 device를 순서 보장 없이 방문합니다. 예를 들어 regulator가 켜질 때 연결 device를 열거할 수 있습니다. Visitor가 negative를 반환하면 멈추며 unprivileged thread에서는 사용할 수 없습니다.

매개변수

  • dev: support 집합의 기준 device이며 NULL이면 안 됩니다.
  • visitor_cb: 각 device에 호출할 함수이며 NULL이면 안 됩니다.
  • context: visitor에 전달할 상태이며 callback이 허용하면 NULL일 수 있습니다.

반환값: 모두 성공하면 방문 수, 실패하면 첫 negative visitor 반환값입니다.

const struct device *device_get_binding(const char *name)

System device를 순회해 device::name이 일치하고 boot-time 초기화에 성공한 device를 찾습니다. 이름은 존재하지만 초기화가 실패한 경우에도 NULL이며, 이 경우 driver init 함수에 breakpoint를 걸어 조사할 수 있습니다.

매개변수

  • name: 검색할 device 이름. NULL pointer 또는 빈 문자열이면 NULL을 반환합니다.

반환값: 이름이 일치하고 초기화에 성공한 device pointer이며, 없거나 초기화 실패면 NULL입니다.

bool device_is_ready(const struct device *dev)

Device pointer가 표준 API로 사용할 수 있는 상태인지 확인합니다. 최소 조건은 초기화 성공이며 DEVICE_DT_GET()으로 얻은 pointer에 특히 필요합니다.

매개변수

  • dev: 확인할 device pointer

반환값: 사용 가능하면 true, 준비되지 않았거나 dev가 NULL이면 false입니다.

int device_init(const struct device *dev)

Devicetree의 zephyr,deferred-init으로 초기화를 연기한 device를 수동 초기화합니다. 연기되지 않았는데 초기화에 실패한 일반 device를 재시도하는 용도로는 사용할 수 없습니다.

매개변수

  • dev: 초기화할 deferred device

반환값: -ENOENT는 device가 없거나 deferred 대상이 아님을 뜻하고, 다른 실패는 -errno입니다.

구조체와 공개 member

struct device_state

Driver instance마다 RAM에 놓이는 runtime dynamic state입니다. Field는 기본적으로 0으로 초기화되어야 하며, kernel driver infrastructure와 access 함수가 접근 전에 필요한 non-zero 초기화를 보장합니다.

uint8_t init_res

Device initialization 결과를 positive errno 값으로 저장합니다. Init 함수는 실패 시 negative errno를 반환하지만 Zephyr errno는 255를 넘지 않아 uint8_t에 양수 크기로 저장할 수 있습니다.

bool initialized

Device initialization 함수가 호출되었는지 나타냅니다.

struct device

Driver instance마다 ROM에 놓이는 runtime device 구조체입니다.

const char *name

Device instance 이름입니다.

const void *config

Device instance의 read-only configuration 정보 주소입니다.

const void *api

Device instance가 노출하는 API 구조체 주소입니다.

struct device_state *state

공통 device runtime state 주소입니다.

void *data

Device instance의 private mutable data 주소입니다.

const device_handle_t *deps

Device와 관계가 있는 handle 집합 sequence를 encode하는 선택적 dependency pointer입니다. device_required_handles_get() 같은 전용 API로 각 집합을 추출하며 CONFIG_DEVICE_DEPS가 활성화될 때만 존재합니다.

union device.[anonymous]

Device power-management resource reference를 담는 anonymous union입니다. CONFIG_PM_DEVICE가 활성화될 때만 존재합니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.