API GUIDE / AON / MIDDLEWARE

AON Middleware 接口

1. 接口规范

1.1 配置与初始化

1.1.1 im_pm_config_default

void im_pm_config_default(im_pm_config_t *config);

将配置结构清零,并把 resume_from_retained_record 初始化为 false。

  • config == NULL 时函数安全返回,不写入任何内容。
  • 该函数只初始化配置值,不访问 HAL,也不改变 PM 状态。

1.1.2 im_pm_init

error_code_t im_pm_init(const im_pm_config_t *config);

初始化和启动顺序:

  1. 校验配置和静态客户端链表,失败返回 IM_ERR_INVALID_PARAM。
  2. 加锁;状态不是 RESET 或 READY 时返回 IM_ERR_BUSY。
  3. 调用 hal_pm_init(),失败则记录 fault 并进入 FAULT。
  4. 调用 hal_pm_get_capabilities() 保存能力快照,失败同样进入 FAULT。
  5. 若 resume_from_retained_record=true,校验恢复记录与当前镜像是否匹配。
  6. 匹配则恢复到 SLEEPING 并重建挂起客户端栈,否则回到 READY。

注意:

  • 第 4 步失败时当前实现不会调用 hal_pm_deinit() 回滚第 3 步的初始化,状态直接进入 FAULT。
  • FAULT 没有公共退出路径,im_pm_deinit() 也要求 READY。
  • im_pm_init() 在 READY 状态下可再次调用,会重新执行上述流程。
  • 配置、链表节点和回调表必须在 im_pm_deinit() 前保持有效。

1.1.3 im_pm_deinit

error_code_t im_pm_deinit(void);
  • 只允许在 READY 状态调用;成功回到 RESET。
  • 状态不是 READY 时返回 IM_ERR_BUSY。
  • HAL 取消失败时进入 FAULT。
  • 不会免费或回收调用方提供的客户端链表。

1.2 模式请求

1.2.1 im_pm_request

error_code_t im_pm_request(im_pm_mode_t mode,
                           const hal_pm_request_config_t *request_config);

执行顺序:

  1. 校验 mode 在 ACTIVE 与 COUNT 之间,校验唤醒源及 V1_TIMER/IDLE 组合。
  2. 加锁,状态必须为 READY,否则返回 IM_ERR_BUSY。
  3. 检查能力掩码,不支持时返回 IM_ERR_UNSUPPORTED。
  4. 将状态置为 PREPARING,按链表正序对匹配客户端调用 prepare。
  5. 将状态置为 SUSPENDING,按链表正序对已 prepare 客户端调用 suspend。
  6. 提交 ARMED 恢复记录,状态置为 ARMED。
  7. 调用对应 HAL enter 接口。
  8. 成功后提交 SLEEPING 恢复记录并进入 SLEEPING,返回 IM_OK。

失败处理:

失败点 行为 最终状态
prepare 失败 对此前 prepare 成功的客户端按正序调用 complete(prepare_error) READY
suspend 失败 按逆序恢复已 suspend 客户端,恢复成功的客户端收到 complete(suspend_error);再对剩余已 prepare 客户端正序 complete READY,恢复失败则 FAULT
HAL 进入失败且 HAL 自身回滚成功 按逆序恢复客户端,恢复成功的客户端收到 complete(enter_error);正序 complete 剩余客户端;清除恢复记录 READY,恢复失败则 FAULT
HAL 进入失败且 HAL 自身回滚也失败 直接进入 FAULT,当前实现不恢复客户端、不失效记录 FAULT

im_pm_request() 成功返回 SLEEPING 后不会自行休眠 CPU。平台路径需在返回后完成 WFI、V1 上下文保存、V0/V1 握手等动作,并在唤醒后调用 im_pm_resume()。

1.3 唤醒恢复

1.3.1 im_pm_resume

error_code_t im_pm_resume(void);

执行顺序:

  1. 加锁,状态必须为 SLEEPING,否则返回 IM_ERR_BUSY。
  2. 重新校验恢复记录(含 owner、客户端数量、挂起掩码、模式、序列号)。
  3. 状态置为 WAKING,调用对应 HAL exit 接口。
  4. 提交 RESUMING 记录,状态置为 RESUMING。
  5. 按相反顺序调用所有已 suspend 客户端的 resume_early。
  6. 按相反顺序对 resume_early 成功的客户端调用 resume,并逐个调用 complete(IM_OK);resume 失败的客户端收到 complete(resume_error)。
  7. 清除恢复记录,mode=ACTIVE、fault=IM_OK、状态回到 READY。

失败处理:

  • 第 2 步记录不一致:失效记录,fault=IM_ERR_IO,进入 FAULT。
  • HAL 退出失败:失效记录,fault 为 HAL 返回码,进入 FAULT。
  • 客户端恢复失败:其余客户端仍会继续恢复并收到 complete,之后失效记录、进入 FAULT;im_pm_resume() 返回第一个恢复错误。

im_pm_resume() 必须与成功的 im_pm_request() 配对,且只调用一次。

1.4 状态与能力读取

1.4.1 im_pm_get_status

error_code_t im_pm_get_status(im_pm_status_t *status_out);

加锁后返回一致性快照,输出指针为空时返回 IM_ERR_INVALID_PARAM。

1.4.2 im_pm_get_capabilities

error_code_t im_pm_get_capabilities(im_pm_capabilities_t *capabilities_out);

返回 im_pm_init() 时保存的能力快照:

typedef struct {
    uint32_t supported_mode_mask;
    bool power_off_supported;
} im_pm_capabilities_t;
  • 输出指针为空时返回 IM_ERR_INVALID_PARAM。
  • 初始化前快照为全零;该方法不会重新查询 HAL。
  • 若需要当前编译配置的实时能力,可直接调用 hal_pm_get_capabilities(),该 HAL 接口不要求先初始化。

2. 行为约束与实现说明

2.1 客户端回调顺序与回滚

正常进入和恢复顺序:

prepare:       client 0 -> client 1 -> ... -> client N
suspend:       client 0 -> client 1 -> ... -> client N
resume_early:  client N -> ... -> client 1 -> client 0
resume:        client N -> ... -> client 1 -> client 0
  • complete 用于完成 prepare 阶段的簿记,成功或回滚都会按规则调用。
  • 回调在 MID 递归互斥锁内执行,不能阻塞等待另一个线程调用 im_pm_*()。
  • 回调应快速完成,并把耗时等待放到事务之外,避免拉长进入/退出窗口。
  • resume_early 用于恢复依赖,resume 用于恢复正常工作;两者都应按逆序执行。

2.2 恢复记录与跨启动校验

恢复记录保存在当前核 .pm_ctx 段,包含以下字段:

typedef struct {
    uint32_t magic;
    uint32_t version;
    uint32_t mode;
    uint32_t sequence;
    uint32_t client_count;
    uint32_t suspended_mask;
    uint32_t wake_source;
    uint32_t phase;
    uint32_t owner;
    uint32_t crc;
} im_pm_resume_record_t;

两层校验:

  • im_pm_resume_record_is_valid() 只检查记录自身:magic、version、模式范围、client_count <= 16、wake_source 范围、phase == SLEEPING、owner 为 1(V1)或 2(V0)、CRC。
  • im_pm_init(resume_from_retained_record=true) 还会检查当前 owner、当前客户端数量、挂起掩码范围、当前模式和序列号,并确认记录模式在当前能力范围内。
  • im_pm_resume() 还会检查当前 owner、当前客户端数量、挂起掩码范围、当前模式(s_mode)和序列号。

行为:

  • resume_from_retained_record=true 但记录不匹配:失效记录,返回 IM_ERR_IO,状态 FAULT。
  • im_pm_resume() 发现记录不一致:失效记录,返回 IM_ERR_IO,状态 FAULT。
  • 成功恢复后清除记录,mode=ACTIVE、fault=IM_OK、状态 READY。
  • 记录阶段依次为 ARMED -> SLEEPING -> RESUMING,任何失败路径都会失效记录,但“HAL 进入失败且 HAL 退出也失败”这一分支是例外。

2.3 V0/V1 能力与所有权

  • V1 支持 IDLE、LIGHT_NOR、LIGHT_PD。
  • V0-only 构建且 CONFIG_G32S10_V0_RUN_IN_SRAM_ONLY=y 时支持 LIGHT_NOR、LIGHT_PD、RETENTION、DEEP_SLEEP、HIBERNATE。
  • V0 未运行在 SRAM-only 区域时不支持任何低功耗模式,避免 HP 断电后从 PSRAM 取指失败。
  • LIGHT-PD 可由 V1 发起,也可由收到 V1 CTX_SAVE 确认的 V0 发起;两种路径都必须保存 V1 CPU 上下文。
  • RETENTION/DEEP_SLEEP/HIBERNATE 由 V0 控制;V1 在这些模式下断电,唤醒后按 AON_FLAG 选择恢复或冷启动路径。
  • IM_PM_MODE_POWER_OFF 需要板级 PMIC/外部电源控制,当前 HAL 返回不支持。

2.4 中断、WFI 与平台职责

  • im_pm_request() 只完成事务准备和 HAL 硬件进入,不执行 WFI。
  • V1 控制的 IDLE/LIGHT-NOR 由 V1 中断路径自行唤醒;V1 控制的 LIGHT-PD 需要保存上下文后执行 WFI,并通过 retained resume entry 恢复。
  • V0 控制的 LIGHT-PD/RETENTION/DEEP_SLEEP/HIBERNATE 由 V0 在 LP 域继续运行或执行 WFI,具体唤醒源由板级路径配置。
  • 唤醒源选择、GPIO 路由、中断 pending 清理和定时器重新装载必须由应用/板级代码完成。
  • 进入前应停止或挂起 DMA、cache 和对外部存储的访问,避免 HP 断电或时钟停止期间产生未完成事务。

2.5 超时、错误与恢复

  • HAL 硬件等待超时以 IM_ERR_TIMEOUT 透传。
  • 参数、状态和记录校验错误分别使用 IM_ERR_INVALID_PARAM、IM_ERR_BUSY、IM_ERR_INVALID_STATE 和 IM_ERR_IO。
  • 当前核不支持的模式返回 IM_ERR_UNSUPPORTED。
  • 进入失败时 MID 会尽量按逆序回滚已 suspend 的客户端;回滚成功回到 READY,回滚失败进入 FAULT。
  • HAL 进入失败且 HAL 退出也失败时直接进入 FAULT,不再恢复客户端。
  • FAULT 没有公共恢复接口,必须复位后重新初始化。

2.6 已知限制与注意事项

  • IM_PM_STATE_ENTERING 是瞬时内部状态,公开状态查询通常看不到。
  • im_pm_get_capabilities() 返回初始化快照,不是实时硬件探测结果。
  • resume_from_retained_record=true 需要与当前镜像、owner、客户端数量和模式匹配的记录,否则初始化失败。
  • LIGHT-PD/RETENTION 的上下文恢复依赖 pm_context.S 和 retained resume entry,普通业务代码不应直接调用 pm_v1_ctx_save() / pm_v1_ctx_restore()。
  • 文中的流程是硬件参考流程,MID 的公开可请求模式只以 hal_pm_get_capabilities() 返回值为准。

3. 典型用法

3.1 初始化与请求 IDLE

im_pm_config_t config;
im_pm_status_t status;
hal_pm_request_config_t request = {
    .wake_source = HAL_PM_WAKE_V1_TIMER,
};

im_pm_config_default(&config);
if (im_pm_init(&config) != IM_OK) {
    /* 记录初始化错误。 */
}

if (im_pm_request(IM_PM_MODE_IDLE, &request) == IM_OK) {
    /* 平台路径:配置唤醒源、进入 WFI。 */
    /* 唤醒后完成硬件退出,再调用 im_pm_resume()。 */
}

if (im_pm_get_status(&status) != IM_OK) {
    /* 状态读取失败。 */
}

HAL_PM_WAKE_V1_TIMER 只允许用于 IDLE;其它模式应使用 HAL_PM_WAKE_NONE 或与应用实际唤醒源匹配的值。

3.2 Light-pd 双核流程

示例模式:

light_pd_v1

示例使用 im_pm_config_default() 的默认配置,client_count=0;因此它验证 LIGHT-PD 请求、双核握手和恢复流程,不验证客户端回调顺序或失败回滚。本节只描述 light_pd_v1,不能代表其他模式均已通过验证。

控制路径:

  • V1 发起 LIGHT-PD;
  • V0 作为唤醒伴随;
  • retained resume 入口恢复 V1 上下文。

V1 流程:

  1. im_pm_config_default() 与 im_pm_init()
  2. 连续打印 5 次计数
  3. 打印 [V1] enter light-pd
  4. im_pm_request(IM_PM_MODE_LIGHT_PD, ...)
  5. 关闭本地中断,设置 HAL_AON_CONTEXT_RESTORE_ENTRY 为 pm_v1_ctx_restore
  6. pm_v1_ctx_save() 保存 V1 上下文
  7. sys_cache_data_clean_all() 清理数据 cache
  8. 若 AON 已恢复为 ACTIVE,直接调用 im_pm_resume() 完成事务
  9. 否则发布 HAL_AON_PM_WAKE_REQUEST,执行 fence 后进入 WFI
  10. 唤醒后等待 AON 进入 ACTIVE;必要时发布 HAL_AON_PM_HANDSHAKE_RESUME
  11. 调用 im_pm_resume(),打印 [V1] light-pd resume complete

V0 流程:

  1. hal_pm_init()
  2. 每秒计数并轮询 AON 握手区
  3. 看到 HAL_AON_PM_WAKE_REQUEST 后清除请求,打印 [V0] light-pd wake requested
  4. LP 域继续计数约 5 秒
  5. 写 0x1210000C = 1 触发 V1 恢复,并等待 HAL_AON_PM_HANDSHAKE_RESUME
  6. 清除触发寄存器,将 AON 模式切回 HAL_AON_MODE_ACTIVE,清除握手控制
  7. 打印 [V0] light-pd resume complete

3.3 客户端注册骨架

static error_code_t sensor_prepare(void *ctx, im_pm_mode_t mode)
{
    (void)ctx;
    (void)mode;
    return IM_OK;
}

static error_code_t sensor_suspend(void *ctx, im_pm_mode_t mode)
{
    (void)ctx;
    (void)mode;
    /* 停止新请求、保存寄存器状态。 */
    return IM_OK;
}

static const im_pm_device_ops_t s_sensor_pm_ops = {
    .prepare = sensor_prepare,
    .suspend = sensor_suspend,
};

static const im_pm_client_t s_sensor_pm_client = {
    .name = "sensor",
    .ops = &s_sensor_pm_ops,
    .supported_mode_mask = IM_PM_MODE_MASK(IM_PM_MODE_LIGHT_PD),
};
  • 链表按“消费者在前、提供者在后”的顺序静态组织。
  • 回调表、客户端节点和 context 必须在整个 PM 生命周期内有效。
  • 可选回调未实现时填 NULL;不要用未初始化的函数指针占位。

3.4 状态诊断

im_pm_status_t status;
if (im_pm_get_status(&status) == IM_OK) {
    /* status.state、status.mode、status.fault */
}

调试建议:

  • 进入前确认 status.state == IM_PM_STATE_READY。
  • 请求成功后应看到 status.state == IM_PM_STATE_SLEEPING 且 status.mode 为目标模式。
  • 唤醒并恢复后应回到 READY、mode == ACTIVE、fault == IM_OK。
  • 出现 FAULT 时先保存 fault 和日志,再复位重新初始化;当前没有公共 API 清除 FAULT。

4. 示例程序与使用方式

示例入口位于 code/middleware/pm/example/。示例支持以下 8 种模式:

PM_EXAMPLE_MODE 控制路径
idle V1 控制 IDLE
light_nor_v1 V1 控制 LIGHT-NOR
light_nor_v0 V0 控制 LIGHT-NOR
light_pd_v1 V1 发起 LIGHT-PD,V0 负责唤醒伴随
light_pd_v0 V0 发起 LIGHT-PD,并负责 AON 休眠/唤醒
retention V0 控制 RETENTION,V1 保存并恢复上下文
deep_sleep V0 控制 DEEP_SLEEP,V1 冷启动
hibernate V0 控制 HIBERNATE,GPIO 唤醒

推荐使用两段式构建:

./code/middleware/pm/example/build.sh --mode light_pd_v1

脚本先用 pm_example_slt_v0_defconfig 编译 V0,再把 V0 镜像嵌入 V1 pm_example_slt_v1_defconfig 构建,最终产物位于 build/artifacts/。单配置路线可使用:

./code/middleware/pm/example/build.sh --single --mode light_pd_v1

正常 light_pd_v1 串口输出至少应包含:

[V1] enter light-pd
[V0] light-pd wake requested
[V1] light-pd resume complete
[V0] light-pd resume complete

恢复后 V1/V0 计数继续增长,说明 V1 上下文保存/恢复、V0 在 LP 域继续运行、V0/V1 跨核握手和 AON 模式恢复均成功。示例自身的跨核握手超时由示例代码返回 IM_ERR_TIMEOUT 或打印失败日志,不属于 im_pm_*() 的直接返回路径。

5. MID 错误码

im_pm_*() 返回 error_code_t。除参数和状态检查外,MID 还会透传客户端或 HAL 回调返回的错误。

返回值 含义
IM_OK 操作成功。
IM_ERR_INVALID_PARAM 模式、客户端列表、回调表、唤醒源或请求配置非法。
IM_ERR_NOT_READY HAL 尚未初始化或底层硬件未就绪;通常由 HAL 透传。
IM_ERR_BUSY 当前状态不允许初始化、发起新事务或执行 resume。
IM_ERR_UNSUPPORTED 当前核、构建配置或 G32S10 PM HAL 不支持目标模式。
IM_ERR_TIMEOUT HAL 硬件等待或客户端回调超时,经 MID 透传。
IM_ERR_IO MID 检测到无效恢复记录或事务元数据不一致。
IM_ERR_INVALID_STATE 硬件/事务状态不满足进入条件,例如 V0 进入 LIGHT-PD 前未收到 V1 上下文保存确认。