summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorAndrew Boie <andrew.p.boie@intel.com>2016-08-23 12:19:29 -0700
committerAnas Nashif <nashif@linux.intel.com>2016-09-08 23:47:25 +0000
commitb82009ee5802493ae60bd17a2710b6fc2d6bad7b (patch)
treedc675dbcf735ee3584e797bfdf0ad561d5fe36be /doc
parent30e76dfb7bf71d9e15f6c12467059b51a6d08865 (diff)
doc: drivers: more details on Zephyr driver model
Issue: ZEP-199 Change-Id: I0f9a4029d5b0dbdd94322bbcaaad29071e508d2e Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Diffstat (limited to 'doc')
-rw-r--r--doc/drivers/drivers.rst250
1 files changed, 250 insertions, 0 deletions
diff --git a/doc/drivers/drivers.rst b/doc/drivers/drivers.rst
index ac4a09333..0f08afa55 100644
--- a/doc/drivers/drivers.rst
+++ b/doc/drivers/drivers.rst
@@ -113,3 +113,253 @@ applications.
: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 condtitions 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)
+ {
+ 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);
+ }
+
+ 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:
+
+`PRIMARY`
+ 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.
+
+`SECONDARY`
+ 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 kerne services are not yet
+ available. Init functions at this level run on the interrupt stack.
+
+`NANOKERNEL`
+ Used for devices that require nanokernel services during configuration.
+ Init functions at this level run in context of the nanokernel
+ background task or microkernel idle task depending on kernel
+ configuration.
+
+`MICROKERNEL`
+ Used for devices that require microkernel services during
+ configuration. Init functions at this level run in context of the
+ microkernel idle task. This init level is skipped if the microkernel is
+ not enabled.
+
+`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
+ either the nanokernel background task or microkernel idle task
+ depending on kernel configuration.
+
+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()`