zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.5k stars 6.43k forks source link

RFC: AP Power Sequence Driver #50562

Closed perezpri closed 2 months ago

perezpri commented 1 year ago

Introduction

An Embedded Controller (EC) is essentially a microcontroller installed on a mobile PC platform that performs important system level operations such as: fan control, thermal measurement, LED indication toggling, and communication ports enablement, among others.

One of the key roles of EC is to monitor power rails and supply Application Processor (AP) with required signaling to drive its power state and achieve optimal platform performance.

Problem description

There is currently no support for AP Power Sequence driver in zephyr. This is not to be confused with Power Management (PM) module, the purpose of this driver to allow host microcontroller to manipulate signals wired to main CPU in the platform and achieve power state transitions.

Proposed change

Proposing a driver that allows interaction with AP Power Sequence subsystem and sets an underlying framework to easily add or extend AP Power Sequence routines.

Architecture_2

This driver is composed of the following parts:

ACPI

The ACPI Specification defines power states for an ACPI-compliant computer system. AP Power Sequence Driver implementation consists of defining operations to be performed on every power state handler and specify state transition utilizing mechanism provided by state machine API.

Power states are defined as below: S0: the run state. In this state, all the power rails are up and the machine is fully running. S1, S2, S3: a low wake-latency sleep state. In this state, memory contexts are held (RAM on) but CPU contexts are lost. S4: the lowest-power sleep state, longest wake-latency sleeping state supported by ACPI, AP is suspended to non-volatile disk. S5: the soft-off state. All activity will stop, and all contexts are lost. G3: soft-off state, the system is completely off and consumes no power.

State Machine

State machine is integrated into the driver through the use of Zephyr State Machine Framework (SMF), each SMF state is represented by three functions or action handlers that define operations performed on state entry, run and exit.

ACPI’s global state (G3) and its six sleep power states (S0, S1, S2, S3, S4, S5) are present within the driver state machine domain. All these ACPI states are divided in three levels, each level is a SMF state with a hierarchical relation with others action handlers of the same ACPI state, state handlers at higher levels accomplishing the most common processing of corresponding ACPI power states.

Architecture is the highest level of the hierarchy, SMF states at this level drive signals that are specific to AP CPU architecture, example: X86 (intel), ARM.

In the middle is the chipset level; these SMF states carry out operations to drive power of components that are required for the AP chip. Any bus signal or internal power rail that is vital for chip execution is a good fit to be handled in these SMF states. Examples of chipsets are: Tiger Lake and Jasper Lake, these are Intel chipsets that use X86 architecture, and MT8186 and MT8192 are Mediatek chipsets using ARM architecture.

Application is at the bottom level of the hierarchy, and these SMF states are reserved for action handlers intended to address board or application specific computations. Example: Hatch, jacuzzi, volteer, asurada, etc…

Hierarchical SMF feature will coordinate execution of entry, run & exit functions accordingly; given that implementation is responsible for doing state transitions, the following considerations should be taken when implementing action state handlers:

Please refer to Zephyr SMF documentation.

State transitions are dictated by the implementation. It is not possible to advance directly into architecture or chipset level SMF state handler within an ACPI state without following parent child execution flow mentioned earlier, this means, transitions are made into application level SMF state handler, in case is not defined, upper level will be called instead.

Transitioning into an ACPI state with no SMF state action handlers implemented for any level will result in blocking state machine execution indefinitely.

State Implementation

For ACPI states, SMF action handlers are defined using the following macros:

AP_POWER_ARCH_STATE_DEFINE(name, entry, run, exit)

AP_POWER_CHIPSET_STATE_DEFINE(name, entry, run, exit)

AP_POWER_APP_STATE_DEFINE(name, entry, run exit)

All macros receive four parameters, first one is to determine which ACPI state is being defined and the following three are to set functions for entry, run and exit action handlers respectively.

This example shows how implementation creates its state machine transitions using run functions: there are three ACPI state with corresponding SMF state run action handlers defined with following code snippet:

void arch_s5_run_handler(void *ctx)
{
    ...
}
AP_POWER_ARCH_STATE_DEFINE(AP_POWER_STATE_S5, NULL,
               arch_s5_run_handler, NULL)

void chipset_s5_run_handler(void *ctx)
{
    ...
}
AP_POWER_CHIPSET_STATE_DEFINE(AP_POWER_STATE_S5, NULL,
                  chipset_s5_run_handler, NULL)

void app_s5_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S4);
    }
    ...
}
AP_POWER_APP_STATE_DEFINE(AP_POWER_STATE_S5, NULL,
              app_s5_run_handler, NULL)

void arch_s4_run_handler(void *ctx)
{
    ...
}
AP_POWER_ARCH_STATE_DEFINE(AP_POWER_STATE_S4, NULL,
               arch_s4_run_handler, NULL)

void chipset_s4_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S3);
    } else if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S5);
    }
    ...
}
AP_POWER_CHIPSET_STATE_DEFINE(AP_POWER_STATE_S4, NULL,
                  chipset_s4_run_handler, NULL)

void chipset_s3_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S5);
    }
    ...
}
AP_POWER_CHIPSET_STATE_DEFINE(AP_POWER_STATE_S3, NULL,
                  chipset_s3_run_handler, NULL)

void app_s3_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S0);
    }
    ...
}
AP_POWER_APP_STATE_DEFINE(AP_POWER_STATE_S3, NULL,
              app_s3_run_handler, NULL)

The following diagram depicts the resulting state machine. Using macros to define state action handlers establishes proper SMF state hierarchy within each ACPI state: State_Machine_example

State transitions are made by ap_s5_run_handler, chipset_s4_run_handler, app_s3_run_handler and chipset_s3_run_handler. Regardless of not having action handlers provided for AP_POWER_STATE_S4 in application level and AP_POWER_STATE_S3 architecture level, execution order is not impacted, undefined SMF state action handlers will be skipped.

Adding Substates

Any additional substate requires having state name to be defined using devicetree, see yaml file $(ZEPHYR_BASE)/dts/bindings/ap_pwrseq/ap-pwrseq-sub-states.yaml

Once substate name is defined, action handlers can be added using macros:

AP_POWER_CHIPSET_SUB_STATE_DEFINE(name, entry, run, exit, parent)

AP_POWER_APP_SUB_STATE_DEFINE(name, entry, run, exit, parent)

Deriving from fixed ACPI power states action handlers definition, these macros take one more parameter to define the enum name of its parent state in order to accommodate its position inside the hierarchy previously mentioned. It is possible to provide substates for application and chipset level only.

Following with previous example, moving from ACPI state S3 into S0; further substates are added, to illustrate this, refer to devicetree along with code snippet below:

added-states-test {
    compatible = "ap-pwrseq-sub-states";
    chipset = "AP_POWER_STATE_S0IX", "AP_POWER_STATE_S0IX_2";
    application = "AP_POWER_STATE_APP_S0IX";
};

With devicetree definition above, three substates have been declared, two of them will be placed at chipset level: AP_POWER_STATE_S0IX and AP_POWER_STATE_S0IX_2, and one is set at application level: AP_POWER_STATE_APP_S0IX.

Its complementary code will look like this, transitions into SMF states is accomplished by state machine API ap_pwrseq_sm_set_state and providing valid enum state name:

void app_s0_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_APP_S0IX);
    } else if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S0IX);
    }
    ...
}
AP_POWER_APP_STATE_DEFINE(AP_POWER_STATE_S0, NULL,
              app_s0_run_handler, NULL)

void chipset_s0ix_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S0IX_2);
    }
    ...
}
AP_POWER_CHIPSET_SUB_STATE_DEFINE(AP_POWER_STATE_S0IX, NULL,
                      chipset_s0ix_run_handler, NULL,
                      AP_POWER_STATE_S0)

void chipset_s0ix_2_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S0);
    }
    ...
}
AP_POWER_CHIPSET_SUB_STATE_DEFINE(AP_POWER_STATE_S0IX_2, NULL,
                      chipset_s0ix_2_run_handler, NULL,
                      AP_POWER_STATE_S0)

void app_s0ix_run_handler(void *ctx)
{
    ...
    if (...) {
        ap_pwrseq_sm_set_state(ctx, AP_POWER_STATE_S0IX);
    }
    ...
}
AP_POWER_APP_SUB_STATE_DEFINE(AP_POWER_STATE_APP_S0IX, NULL,
                  app_s0ix_run_handler, NULL,
                  AP_POWER_STATE_S0)

From code above, the following state machine diagram is constructed: Substate_Machine_example

Handled state

Zephyr SMF does not have a native way to facilitate implementation of Ultimate Hook design pattern. The following macros are intended to be used to inform parent SMF state handlers that further execution is not required due to state being handled.

#define SET_HANDLED(data)

#define IS_HANDLED(data)

#define RETURN_IF_HANDLED(data)

In order to promote this pattern, It is recommended for upper level action handlers to query if state has been handled.

Driver

Drivers mediate client communication with the underlying state machine and manage its execution. Upon initialization, the driver will create its own thread. States execution, state transition and any required callback invocation will be performed within the driver’s thread context. Driver’s thread is limited to only execute the run action of the current state while implementation is responsible to do state transitions to match AP requirements.

If no state transition has occurred during a thread iteration, the thread will block indefinitely waiting for any event to be posted by one of the driver's clients. Events posted will be passed to the state machine to be processed by the current state action handler, after this, all events received will be discarded prior to the next iteration.

Driver Interface

Driver does not require to have any API structure given that its functionality is trivial, on the other hand state machine implementations will vary. For this reason, driver does not provide implementation to any API structure, instead its interface is fixed.

Driver interface allows clients to post events to be processed by state machine implementation, and register callbacks to receive notifications associated with certain power state upon entrance, exit or execution.

Driver PR

Driver code and examples on how to add state action handlers can be found in the following draft PR #44481.

Dependencies

None, however this driver heavily depends on SMF module.

Concerns and Unresolved Questions

stephanosio commented 1 year ago

Why does this need to be part of the upstream Zephyr?

Should it not be part of the Embedded Controller firmware "app"?

albertofloyd commented 6 months ago

Why does this need to be part of the upstream Zephyr?

Should it not be part of the Embedded Controller firmware "app"?

Agree , this belongs in EC FW app