Implementing a Power Controller

Name

Implementing a Power Controller -- adding power management support to device drivers and other packages

Implementing a Power Controller

A system will have some number of power controllers. Usually there will be one power controller for the cpu, power_controller_cpu, typically provided by one of the HAL packages and responsible for managing the processor itself and associated critical components such as memory. Some or all of the device drivers will provide power controllers, allowing the power consumption of the associated devices to be controlled. There may be some arbitrary number of other controllers present in the system. The power management package does not impose any restrictions on the number or nature of the power controllers in the system, other than insisting that at most one power_controller_cpu be provided.

Each power controller involves a single data structure of type PowerController, defined in the header file cyg/power/power.h. These data structures should all be placed in the table __POWER__, so that the power management package and other code can easily locate all the controllers in the system. This table is constructed at link-time, avoiding code-size or run-time overheads. To facilitate this the package provides two macros which should be used to define a power controller, POWER_CONTROLLER() and POWER_CONTROLLER_CPU().

The macro POWER_CONTROLLER takes four arguments:

  1. A variable name. This can be used to access the power controller directly, as well as via the table.

  2. A priority. The table of power controllers is sorted, such that power controllers with a numerically lower priority come earlier in the table. The special controller power_controller_cpu always comes at the end of the table. When moving from a high-power mode to a lower-powered mode, the power management package iterates through the table from front to back. When moving to a higher-powered mode the reverse direction is used. The intention is that the power controller for a software-only package such as a TCP/IP stack should appear near the start of the table, whereas the controllers for the ethernet and similar devices would be near the end of the table. Hence when the policy module initiates a mode change to a lower-powered mode the TCP/IP stack gets a chance to cancel this mode change, before the devices it depends on are powered down. Similarly when moving to a higher-powered mode the devices will be re-activated before any software that depends on those devices.

    The header file cyg/power/power.h defines three priorities PowerPri_Early, PowerPri_Typical and PowerPri_Late. For most controllers one of these priorities, possibly with a small number added or subtracted, will give sufficient control. If an application developer is uncertain about the relative priorities of the various controllers, a simple test program that iterates over the table will quickly eliminate any confusion.

  3. A constant string identifier. If the system has been configured without support for such identifiers (CYGIMP_POWER_PROVIDE_STRINGS) then this identifer will be discarded at compile-time. Otherwise it will be made available to higher-level code using the function power_get_controller_id.

  4. A function pointer. This will be invoked to perform actual mode changes, as described below.

A typical example of the use of the POWER_CONTROLLER macro would be as follows:

#include <pkgconf/system.h>

#ifdef CYGPKG_POWER
# include <cyg/power/power.h>

static void
xyzzy_device_power_mode_change(
    PowerController* controller,
    PowerMode        desired_mode,
    PowerModeChange  change)
{
   // Do the work
}

static POWER_CONTROLLER(xyzzy_power_controller, \
                        PowerPri_Late,          \
                        "xyzzy device",         \
                        &xyzzy_device_power_mode_change);
#endif

This creates a variable xyzzy_power_controller, which is a power controller data structure that will end up near the end of the table of power controllers. Higher-level code can iterate through this table and report the string "xyzzy device" to the user. Whenever there is a mode change operation that affects this controller, the function xyzzy_device_power_mode_change will be invoked. The variable is declared static so this controller cannot be manipulated by name in any other code. Alternatively, if the variable had not been declared static other code could manipulate this controller by name as well as through the table, especially if the package for the xyzzy device driver explicitly declared this variable in an exported header file. Obviously exporting the variable involves a slight risk of a name clash at link time.

The above code explicitly checks for the presence of the power management package before including that package's header file or providing any related functionality. Since power management functionality is optional, such checks are recommended.

The macro POWER_CONTROLLER_CPU only takes two arguments, a string identifier and a mode change function pointer. This macro always instantiates a variable power_controller_cpu so there is no need to provide a variable name. The resulting power controller structure always appears at the end of the table, so there is no need to specify a priority. Typical usage of the POWER_CONTROLLER_CPU macro would be:

static void
wumpus_processor_power_mode_change(
    PowerController* controller,
    PowerMode        desired_mode,
    PowerModeChange  change)
{
   // Do the work
}

POWER_CONTROLLER_CPU("wumpus processor", \
                     &wumpus_processor_power_mode_change);

This defines a power controller structure power_controller_cpu. It should not be declared static since higher-level code may well want to manipulate the cpu's power mode directly, and the variable is declared by the power management package's header file.

Some care has to be taken to ensure that the power controllers actually end up in the final executable. If a power controller variable ends up in an ordinary library and is never referenced directly then typically the linker will believe that the variable is not needed and it will not end up in the executable. For eCos packages this can be achieved in the CDL, by specifying that the containing source file should end up in libextras.a rather than the default libtarget.a:

cdl_package CYGPKG_HAL_WUMPUS_ARCH {
    …
    compile -library=libextras.a data.c
}

If the file data.c instantiates a power controller this is now guaranteed to end up in the final executable, as intended. Typically HAL and device driver packages will already have some data that must not be eliminated by the linker, so they will already contain a file that gets built into libextras.a. For power controllers defined inside application code it is important that the power controllers end up in .o object files rather than in .a library archive files.

All the real work of a power controller is done by the mode change function. If the power management package has been configured to use a separate thread then this mode change function will be invoked by that thread (except for the special case of power_set_controller_mode_now). If no separate thread is used then the mode change function will be invoked directly by power_set_mode or power_set_controller_mode.

The mode change function will be invoked with three arguments. The first argument identifies the power controller. Usually this argument is not actually required since a given mode change function will only ever be invoked for a single power controller. For example, xyzzy_device_power_mode_change will only ever be used in conjunction with xyzzy_power_controller. However there may be some packages which contain multiple controllers, all of which can share a single mode change function, and in that case it is essential to identify the specific controller. The second argument specifies the mode the controller should switch to, if possible: it will be one of PowerMode_Active, PowerMode_Idle, PowerMode_Sleep or PowerMode_Off. The final argument will be one of PowerModeChange_Controller, PowerModeChange_ControllerNow, or PowerModeChange_Global, and identifies the call that caused this invocation. For example, if the mode change function was invoked because of a call to power_set_mode then this argument will be PowerModeChange_Global. It is up to each controller to decide how to interpret this final argument. A typical controller might reject a global request to switch to off mode if the associated device is still busy, but if the request was aimed specifically at this controller then it could instead abort any current I/O operations and switch off the device.

The PowerController data structure contains one field, mode, that needs to be updated by the power mode change function. At all times it should indicate the current mode for this controller. When a mode change is requested the desired mode is passed as the second argument. The exact operation of the power mode change function depends very much on what is being controlled and the current circumstances, but some guidelines are possible:

  1. If the request can be satisfied without obvious detriment, do so and update the mode field. Reducing the power consumption of a device that is not currently being used is generally harmless.

  2. If a request is a no-op, for example if the system is switching from idle to sleep mode and the controller does not distinguish between these modes, simply act as if the request was satisfied.

  3. If a request is felt to be unsafe, for example shutting down a device that is still in use, then the controller may decide to reject this request. This is especially true if the request was a global mode change as opposed to one intended specifically for this controller: in the latter case the policy module should be given due deference. There are a number of ways in which a request can be rejected:

    1. If the request cannot be satisfied immediately but may be feasible in a short while, leave the mode field unchanged. Higher-level code in the policy module can interpret this as a hint to retry the operation a little bit later. This approach is also useful if the mode change can be started but will take some time to complete, for example shutting down a socket connection, and additional processing will be needed later on.

    2. If the request is felt to be inappropriate, for example switching off a device that is still in use, the mode change function can call power_set_controller_mode to reset the desired mode for this controller back to the current mode. Higher-level code can then interpret this as a hint that there is more activity in the system than had been apparent.

    3. For a global mode change, if the new mode is felt to be inappropriate then the power controller can call power_set_mode to indicate this. An example of this would be the policy module deciding to switch off the whole unit while there is still I/O activity.

Mode change functions should not directly manipulate any other fields in the PowerController data structure. If it is necessary to keep track of additional data then static variables can be used.

It should be noted that the above are only guidelines. Their application in any given situation may be unclear. In addition the detailed requirements of specific systems will vary, so even if the power controller for a given device driver follows the above guidelines exactly it may turn out that slightly different behaviour would be more appropriate for the actual system that is being developed. Fortunately the open source nature of eCos allows system developers to fine-tune power controllers to meet their exact requirements.