320 lines
11 KiB
ReStructuredText
320 lines
11 KiB
ReStructuredText
.. _device_drivers:
|
|
|
|
Device Drivers and Device Model
|
|
###############################
|
|
|
|
Introduction
|
|
************
|
|
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
|
|
numbers.
|
|
|
|
.. 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
|
|
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_INIT()`
|
|
create device object and set it up for boot time initialization.
|
|
|
|
:c:func:`DEVICE_AND_API_INIT()`
|
|
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.
|
|
|
|
:c:func:`DEVICE_NAME_GET()`
|
|
Expands to the full name of a global device object.
|
|
|
|
:c:func:`DEVICE_GET()`
|
|
Obtain a pointer to a device object by name.
|
|
|
|
:c:func:`DEVICE_DECLARE()`
|
|
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 https://wiki.zephyrproject.org/view/Coding_conventions#Return_Codes 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 */
|
|
...
|
|
|
|
config->config_func(device);
|
|
|
|
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
|
|
{
|
|
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:
|
|
|
|
`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 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 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.
|
|
|
|
`POST_KERNEL`
|
|
Used for devices that require kernel services during configuration.
|
|
Init functions at this level run in context of the kernel main task.
|
|
|
|
`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.
|
|
|
|
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`).
|
|
|
|
|
|
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.
|
|
|
|
:c:func:`SYS_INIT()`
|
|
|
|
:c:func:`SYS_INIT_PM()`
|