Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

.. _device_drivers:

Device Drivers and Device Model

The Zephyr kernel supports a variety of device drivers. The specific set of
device drivers available for an application's board configuration varies
according to the associated hardware components and device driver software.

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

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.

* **Random number generator**: This device driver provides a source of random

  .. important::

    Certain implementations of this device driver do not generate sequences of
    values that are truly random.

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 i2c.h
or spi.h, are usually intended as synchronous. Thus, these calls should be

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

   create device object and set it up for boot time initialization.

   Create device object and set it up for boot time initialization.
   This also takes a pointer to driver API struct for link time
   pointer assignment.

   Expands to the full name of a global device object.

   Obtain a pointer to a device object by name.

   Declare a device object.

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 {
        struct device_config *config;
        void *driver_api;
        void *driver_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_info` structure
passed to the `DEVICE_*INIT()` macros.

The `driver_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 `driver_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 targeting 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)(struct device *device, int foo, int bar);
  typedef void (*subsystem_do_that_t)(struct device *device, void *baz);

  struct subsystem_api {
        subsystem_do_this_t do_this;
        subsystem_do_that_t do_that;

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

        api = (struct subsystem_api *)device->driver_api;
        return api->do_this(device, foo, bar);

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

        api = (struct subsystem_api *)device->driver_api;
        api->do_that(device, foo, bar);

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 errno.h code returned on failure.
See for
details about this.

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(struct device *device, int foo, int bar)

  static void my_driver_do_that(struct device *device, 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_AND_API_INIT()`, or manually assign it to `device->driver_api` in the
driver init function.

.. note::

        Since pointers to the API functions are referenced in the driver_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.

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_info` struct and `driver_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)(struct device *device);

  struct my_driver_config {
        uint32_t base_addr;
        my_driver_config_irq_t config_func;

In the implementation of the common init function:

.. code-block:: C

  void my_driver_isr(struct device *device)
        /* Handle interrupt */

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

        /* Do other initialization stuff */


        return 0;

Then when the particular instance is declared:

.. code-block:: C



  static void my_driver_config_irq_0
        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 = {
        .base_addr = MY_DRIVER_0_BASE_ADDR;
        .config_func = my_driver_config_irq_0;

  static struct my_driver_data_0;

  DEVICE_AND_API_INIT(my_driver_0, MY_DRIVER_0_NAME, my_driver_init,
                      &my_driver_data_0, &my_driver_config_0, SECONDARY,
                      MY_DRIVER_0_PRIORITY, &my_driver_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. The DEVICE_INIT() 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 five initialization levels:

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

        Used for devices that rely on the initialization of devices initialized
        as part of the PRIMARY 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.

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

        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.

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.

System Drivers

In some cases you may just need to run a function at boot. Special `SYS_INIT`
macros exist that map to `DEVICE_INIT()` or `DEVICE_INIT_PM()` calls.
For `SYS_INIT()` there are no config or runtime data structures and there isn't a way
to later get a device pointer by name. The same policies for initialization
level and priority apply.

For `SYS_INIT_PM()` you can obtain pointers by name, see :ref:`power management
<power_management>` section.