diff options
author | Andrew Boie <andrew.p.boie@intel.com> | 2016-08-23 12:19:29 -0700 |
---|---|---|
committer | Anas Nashif <nashif@linux.intel.com> | 2016-09-08 23:47:25 +0000 |
commit | b82009ee5802493ae60bd17a2710b6fc2d6bad7b (patch) | |
tree | dc675dbcf735ee3584e797bfdf0ad561d5fe36be /doc | |
parent | 30e76dfb7bf71d9e15f6c12467059b51a6d08865 (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.rst | 250 |
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()` |