Skip to content

Commit

Permalink
device: refactor to simplify maintenance
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
pabigot authored and carlescufi committed Jun 22, 2020
1 parent 2fe35e8 commit 72ebf70
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 102 deletions.
36 changes: 23 additions & 13 deletions doc/reference/drivers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
applications.

:c:func:`DEVICE_INIT()`
create device object and set it up for boot time initialization.
:c:func:`DEVICE_DEFINE()`
Create device object and related data structures including setting 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.
Like :c:func:`DEVICE_DEFINE()` but without support for device power
management.

:c:func:`DEVICE_INIT()`
Like :c:func:`DEVICE_AND_API_INIT()` but without providing an API
pointer.

: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()`
Obtain a pointer to a device object by name.

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

Expand All @@ -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
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.
passed to ``DEVICE_DEFINE()`` and related 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,
Expand Down Expand Up @@ -337,10 +343,11 @@ 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 four initialization levels:
Drivers may depend on other drivers being initialized first, or require
the use of kernel services. :c:func:`DEVICE_DEFINE()` and related 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 four
initialization levels:

``PRE_KERNEL_1``
Used for devices that have no dependencies, such as those that rely
Expand Down Expand Up @@ -382,7 +389,7 @@ System Drivers
**************

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
isn't a way
to later get a device pointer by name. The same policies for initialization
Expand All @@ -392,8 +399,11 @@ For ``SYS_DEVICE_DEFINE()`` you can obtain pointers by name, see
:ref:`power management <power_management_api>` section.

:c:func:`SYS_INIT()`
Run an initialization function at boot at specified priority.

: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
**************
Expand Down
154 changes: 65 additions & 89 deletions include/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ extern "C" {
* @brief Expands to the full name of a global device object
*
* @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
* 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))

Expand All @@ -50,24 +50,46 @@ extern "C" {
* @brief Run an initialization function at boot at specified priority,
* and define device PM control function.
*
* @details This macro lets you run a function at system boot.
*
* @param drv_name Name of this system device
* @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.
* @details Invokes DEVICE_DEFINE() with no power management support
* (@p pm_control_fn), no API (@p api), and a device name derived from
* the @p init_fn name (@p dev_name).
*/
#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)

/**
* @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
* configured by the kernel during system initialization. Note that
Expand All @@ -83,105 +105,38 @@ extern "C" {
*
* @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 cfg_info The address to the structure containing the
* 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
* Z_INIT_ENTRY_DEFINE 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.
* SYS_INIT() for details.
*
* @copydetails DEVICE_INIT
* @param api Provides an initial pointer to the API function struct
* 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, \
data, cfg_info, level, prio, api) \
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), \
}; \
Z_DEVICE_DEFINE_PM(dev_name) \
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), \
.device_pm_control = (pm_control_fn), \
.pm = &_CONCAT(__pm_, dev_name), \
Z_DEVICE_DEFINE_PM_INIT(dev_name, pm_control_fn) \
}; \
Z_INIT_ENTRY_DEFINE(_CONCAT(__device_, dev_name), init_fn, \
(&_CONCAT(__device_, dev_name)), level, prio)

#endif

/**
* @def DEVICE_GET
*
Expand Down Expand Up @@ -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
}
#endif
Expand Down

0 comments on commit 72ebf70

Please sign in to comment.