device: refactor to simplify maintenance

DEVICE_AND_API_INIT and DEVICE_DEFINE are identical except that
DEVICE_DEFINE adds a parameter providing the device pm control
function, while DEVICE_AND_API_INIT does not.  This requires duplicate
implementations where if CONFIG_DEVICE_POWER_MANAGEMENT is enabled
then DEVICE_AND_API_INIT delegates to DEVICE_DEFINE with a dummy pm
control function, and if it is not enabled then DEVICE_DEFINE discards
the parameter and delegates to DEVICE_AND_API_INIT.

DEVICE_INIT is like DEVICE_AND_API_INIT but doesn't provide an API.

Clean this up by refactoring so that DEVICE_DEFINE is the core
implementation, providing with and without device power management
right next to each other where they can be compared and maintained.
Redefine DEVICE_INIT and DEVICE_AND_API_INIT delegate to
DEVICE_DEFINE.

Also remove duplicate code by extracting the variations due to
enabling device power management into macros.

Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
Peter Bigot 2020-06-09 14:39:26 -05:00 committed by Carles Cufí
commit 72ebf70543
2 changed files with 88 additions and 102 deletions

View file

@ -68,22 +68,28 @@ 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 are intended for use in device drivers only and should not be used in
applications. applications.
:c:func:`DEVICE_INIT()` :c:func:`DEVICE_DEFINE()`
create device object and set it up for boot time initialization. Create device object and related data structures including setting it
up for boot-time initialization.
:c:func:`DEVICE_AND_API_INIT()` :c:func:`DEVICE_AND_API_INIT()`
Create device object and set it up for boot time initialization. Like :c:func:`DEVICE_DEFINE()` but without support for device power
This also takes a pointer to driver API struct for link time management.
pointer assignment.
:c:func:`DEVICE_INIT()`
Like :c:func:`DEVICE_AND_API_INIT()` but without providing an API
pointer.
:c:func:`DEVICE_NAME_GET()` :c:func:`DEVICE_NAME_GET()`
Expands to the full name of a global device object. Converts a device identifier to the global identifier for a device
object.
:c:func:`DEVICE_GET()` :c:func:`DEVICE_GET()`
Obtain a pointer to a device object by name. Obtain a pointer to a device object by name.
:c:func:`DEVICE_DECLARE()` :c:func:`DEVICE_DECLARE()`
Declare a device object. Declare a device object. Use this when you need a forward reference
to a device that has not yet been defined.
.. _device_struct: .. _device_struct:
@ -106,7 +112,7 @@ split into read-only and runtime-mutable parts. At a high level we have:
The ``config_info`` member is for read-only configuration data set at build time. For The ``config_info`` member is for read-only configuration data set at build time. For
example, base memory mapped IO addresses, IRQ line numbers, or other fixed example, base memory mapped IO addresses, IRQ line numbers, or other fixed
physical characteristics of the device. This is the ``config_info`` structure physical characteristics of the device. This is the ``config_info`` structure
passed to the ``DEVICE_*INIT()`` macros. passed to ``DEVICE_DEFINE()`` and related macros.
The ``driver_data`` struct is kept in RAM, and is used by the driver for 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, per-instance runtime housekeeping. For example, it may contain reference counts,
@ -337,10 +343,11 @@ the IRQ handler argument and the definition of the device itself.
Initialization Levels Initialization Levels
********************* *********************
Drivers may depend on other drivers being initialized first, or Drivers may depend on other drivers being initialized first, or require
require the use of kernel services. The DEVICE_INIT() APIs allow the user to the use of kernel services. :c:func:`DEVICE_DEFINE()` and related APIs
specify at what time during the boot sequence the init function will be allow the user to specify at what time during the boot sequence the init
executed. Any driver will specify one of four initialization levels: function will be executed. Any driver will specify one of four
initialization levels:
``PRE_KERNEL_1`` ``PRE_KERNEL_1``
Used for devices that have no dependencies, such as those that rely Used for devices that have no dependencies, such as those that rely
@ -382,7 +389,7 @@ System Drivers
************** **************
In some cases you may just need to run a function at boot. Special ``SYS_*`` In some cases you may just need to run a function at boot. Special ``SYS_*``
macros exist that map to ``DEVICE_*INIT()`` calls. macros exist that map to ``DEVICE_DEFINE()`` calls.
For ``SYS_INIT()`` there are no config or runtime data structures and there For ``SYS_INIT()`` there are no config or runtime data structures and there
isn't a way isn't a way
to later get a device pointer by name. The same policies for initialization to later get a device pointer by name. The same policies for initialization
@ -392,8 +399,11 @@ For ``SYS_DEVICE_DEFINE()`` you can obtain pointers by name, see
:ref:`power management <power_management_api>` section. :ref:`power management <power_management_api>` section.
:c:func:`SYS_INIT()` :c:func:`SYS_INIT()`
Run an initialization function at boot at specified priority.
:c:func:`SYS_DEVICE_DEFINE()` :c:func:`SYS_DEVICE_DEFINE()`
Like :c:func:`DEVICE_DEFINE` without an API table and constructing
the device name from the init function name.
Error handling Error handling
************** **************

View file

@ -33,14 +33,14 @@ extern "C" {
* @brief Expands to the full name of a global device object * @brief Expands to the full name of a global device object
* *
* @details Return the full name of a device object symbol created by * @details Return the full name of a device object symbol created by
* DEVICE_INIT(), using the dev_name provided to DEVICE_INIT(). * DEVICE_DEFINE(), using the dev_name provided to DEVICE_DEFINE().
* *
* It is meant to be used for declaring extern symbols pointing on device * It is meant to be used for declaring extern symbols pointing on device
* objects before using the DEVICE_GET macro to get the device object. * objects before using the DEVICE_GET macro to get the device object.
* *
* @param name The same as dev_name provided to DEVICE_INIT() * @param name The same as dev_name provided to DEVICE_DEFINE()
* *
* @return The expanded name of the device object created by DEVICE_INIT() * @return The expanded name of the device object created by DEVICE_DEFINE()
*/ */
#define DEVICE_NAME_GET(name) (_CONCAT(__device_, name)) #define DEVICE_NAME_GET(name) (_CONCAT(__device_, name))
@ -50,24 +50,46 @@ extern "C" {
* @brief Run an initialization function at boot at specified priority, * @brief Run an initialization function at boot at specified priority,
* and define device PM control function. * and define device PM control function.
* *
* @details This macro lets you run a function at system boot. * @details Invokes DEVICE_DEFINE() with no power management support
* * (@p pm_control_fn), no API (@p api), and a device name derived from
* @param drv_name Name of this system device * the @p init_fn name (@p dev_name).
* @param init_fn Pointer to the boot function to run
* @param pm_control_fn Pointer to device_pm_control function.
* Can be empty function (device_pm_control_nop) if not implemented.
* @param level The initialization level, See Z_INIT_ENTRY_DEFINE for details.
* @param prio Priority within the selected initialization level. See
* Z_INIT_ENTRY_DEFINE for details.
*/ */
#define SYS_DEVICE_DEFINE(drv_name, init_fn, pm_control_fn, level, prio) \ #define SYS_DEVICE_DEFINE(drv_name, init_fn, pm_control_fn, level, prio) \
DEVICE_DEFINE(Z_SYS_NAME(init_fn), drv_name, init_fn, pm_control_fn, \ DEVICE_DEFINE(Z_SYS_NAME(init_fn), drv_name, init_fn, \
pm_control_fn, \
NULL, NULL, level, prio, NULL) NULL, NULL, level, prio, NULL)
/** /**
* @def DEVICE_INIT * @def DEVICE_INIT
* *
* @brief Create device object and set it up for boot time initialization. * @brief Invoke DEVICE_DEFINE() with no power management support (@p
* pm_control_fn) and no API (@p api).
*/
#define DEVICE_INIT(dev_name, drv_name, init_fn, \
data, cfg_info, level, prio) \
DEVICE_DEFINE(dev_name, drv_name, init_fn, \
device_pm_control_nop, \
data, cfg_info, level, prio, NULL)
/**
* @def DEVICE_AND_API_INIT
*
* @brief Invoke DEVICE_DEFINE() with no power management support (@p
* pm_control_fn).
*/
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, \
data, cfg_info, level, prio, api) \
DEVICE_DEFINE(dev_name, drv_name, init_fn, \
device_pm_control_nop, \
data, cfg_info, level, prio, api)
/**
* @def DEVICE_DEFINE
*
* @brief Create device object and set it up for boot time initialization,
* with the option to device_pm_control. In case of Device Idle Power
* Management is enabled, make sure the device is in suspended state after
* initialization.
* *
* @details This macro defines a device object that is automatically * @details This macro defines a device object that is automatically
* configured by the kernel during system initialization. Note that * configured by the kernel during system initialization. Note that
@ -83,90 +105,26 @@ extern "C" {
* *
* @param init_fn Address to the init function of the driver. * @param init_fn Address to the init function of the driver.
* *
* @param pm_control_fn Pointer to device_pm_control function.
* Can be empty function (device_pm_control_nop) if not implemented.
*
* @param data Pointer to the device's private data. * @param data Pointer to the device's private data.
* *
* @param cfg_info The address to the structure containing the * @param cfg_info The address to the structure containing the
* configuration information for this instance of the driver. * configuration information for this instance of the driver.
* *
* @param level The initialization level, See Z_INIT_ENTRY_DEFINE for details. * @param level The initialization level. See SYS_INIT() for
* details.
* *
* @param prio Priority within the selected initialization level. See * @param prio Priority within the selected initialization level. See
* Z_INIT_ENTRY_DEFINE for details. * SYS_INIT() for details.
*/
#define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \
DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, \
data, cfg_info, level, prio, NULL)
/**
* @def DEVICE_AND_API_INIT
* *
* @brief Create device object and set it up for boot time initialization,
* with the option to set driver_api.
*
* @copydetails DEVICE_INIT
* @param api Provides an initial pointer to the API function struct * @param api Provides an initial pointer to the API function struct
* used by the driver. Can be NULL. * used by the driver. Can be NULL.
* @details The driver api is also set here, eliminating the need to do that
* during initialization.
*/ */
#ifndef CONFIG_DEVICE_POWER_MANAGEMENT
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api) \
static Z_DECL_ALIGN(struct device) \
DEVICE_NAME_GET(dev_name) __used \
__attribute__((__section__(".device_" #level STRINGIFY(prio)))) = { \
.name = drv_name, \
.config_info = (cfg_info), \
.driver_api = (api), \
.driver_data = (data), \
}; \
Z_INIT_ENTRY_DEFINE(_CONCAT(__device_, dev_name), init_fn, \
(&_CONCAT(__device_, dev_name)), level, prio)
#else
/*
* Use the default device_pm_control for devices that do not call the
* DEVICE_DEFINE macro so that caller of hook functions
* need not check device_pm_control != NULL.
*/
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api) \
DEVICE_DEFINE(dev_name, drv_name, init_fn, \
device_pm_control_nop, data, cfg_info, level, \
prio, api)
#endif
/**
* @def DEVICE_DEFINE
*
* @brief Create device object and set it up for boot time initialization,
* with the option to device_pm_control. In case of Device Idle Power
* Management is enabled, make sure the device is in suspended state after
* initialization.
*
* @copydetails DEVICE_AND_API_INIT
* @param pm_control_fn Pointer to device_pm_control function.
* Can be empty function (device_pm_control_nop) if not implemented.
*/
#ifndef CONFIG_DEVICE_POWER_MANAGEMENT
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
data, cfg_info, level, prio, api) \
DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api)
#else
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \ #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
data, cfg_info, level, prio, api) \ data, cfg_info, level, prio, api) \
static struct device_pm _CONCAT(__pm_, dev_name) __used = { \ Z_DEVICE_DEFINE_PM(dev_name) \
.usage = ATOMIC_INIT(0), \
.lock = Z_SEM_INITIALIZER( \
_CONCAT(__pm_, dev_name).lock, 1, 1), \
.signal = K_POLL_SIGNAL_INITIALIZER( \
_CONCAT(__pm_, dev_name).signal), \
.event = K_POLL_EVENT_INITIALIZER( \
K_POLL_TYPE_SIGNAL, \
K_POLL_MODE_NOTIFY_ONLY, \
&_CONCAT(__pm_, dev_name).signal), \
}; \
static Z_DECL_ALIGN(struct device) \ static Z_DECL_ALIGN(struct device) \
DEVICE_NAME_GET(dev_name) __used \ DEVICE_NAME_GET(dev_name) __used \
__attribute__((__section__(".device_" #level STRINGIFY(prio)))) = { \ __attribute__((__section__(".device_" #level STRINGIFY(prio)))) = { \
@ -174,14 +132,11 @@ extern "C" {
.config_info = (cfg_info), \ .config_info = (cfg_info), \
.driver_api = (api), \ .driver_api = (api), \
.driver_data = (data), \ .driver_data = (data), \
.device_pm_control = (pm_control_fn), \ Z_DEVICE_DEFINE_PM_INIT(dev_name, pm_control_fn) \
.pm = &_CONCAT(__pm_, dev_name), \
}; \ }; \
Z_INIT_ENTRY_DEFINE(_CONCAT(__device_, dev_name), init_fn, \ Z_INIT_ENTRY_DEFINE(_CONCAT(__device_, dev_name), init_fn, \
(&_CONCAT(__device_, dev_name)), level, prio) (&_CONCAT(__device_, dev_name)), level, prio)
#endif
/** /**
* @def DEVICE_GET * @def DEVICE_GET
* *
@ -585,6 +540,27 @@ static inline int device_pm_put_sync(struct device *dev) { return -ENOTSUP; }
* @} * @}
*/ */
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#define Z_DEVICE_DEFINE_PM(dev_name) \
static struct device_pm _CONCAT(__pm_, dev_name) __used = { \
.usage = ATOMIC_INIT(0), \
.lock = Z_SEM_INITIALIZER( \
_CONCAT(__pm_, dev_name).lock, 1, 1), \
.signal = K_POLL_SIGNAL_INITIALIZER( \
_CONCAT(__pm_, dev_name).signal), \
.event = K_POLL_EVENT_INITIALIZER( \
K_POLL_TYPE_SIGNAL, \
K_POLL_MODE_NOTIFY_ONLY, \
&_CONCAT(__pm_, dev_name).signal), \
};
#define Z_DEVICE_DEFINE_PM_INIT(dev_name, pm_control_fn) \
.device_pm_control = (pm_control_fn), \
.pm = &_CONCAT(__pm_, dev_name),
#else
#define Z_DEVICE_DEFINE_PM(dev_name)
#define Z_DEVICE_DEFINE_PM_INIT(dev_name, pm_control_fn)
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif