System PM

CONFIG_PM开关。用来控制整个SoC的状态。

Device tree

zephyr,power-state.yaml介绍了zephyr power state的device tree binding。

一个设备树的例子:

power-states {
	idle: idle {
		compatible = "zephyr,power-state";
		power-state-name = "runtime-idle";
	};

	stop: stop {
		compatible = "zephyr,power-state";
		power-state-name = "suspend-to-idle"; // pm状态,见yaml
		substate-id = <0>; // 如果有两个相同的power-state-name,需要用substate-id来区分
		min-residency-us = <1000>; // 最少在该状态停留的时间
		exit-latency-us = <2>; // 离开该状态最worse的时间
	};

	pstop1: pstop1 {
		compatible = "zephyr,power-state";
		power-state-name = "suspend-to-idle";
		substate-id = <1>;
		min-residency-us = <1000>;
		exit-latency-us = <2>;
	};
};

PM policy

有两种PM policy:

  • Residency based
  • Application defined

第一种policy根据在设备中定义的min-residency-usexit-latency-us, 如果进入idle状态,距离下一个要调度的事件时间time_to_next_scheduled_event>min-residency-us+exit-latency-us,那么就进入该pm state。并在几种都满足的pm state的情况下,选择最省电的。

第二种policy是app自定义一个pm_notifier结构体,定义state_entrystate_exit回调函数,利用注册接口,pm_notifier_register注册,之后在系统进入idle状态后,会调用回调函数state_entry

static struct pm_notifier notifier = {
	.state_entry = notify_pm_state_entry,
	.state_exit = notify_pm_state_exit,
};

void pm_notifier_register(struct pm_notifier *notifier)

Source code

state.h 列出了所有支持的pm状态,越靠下的越省电,当然唤醒的流程和时间也更长:

enum pm_state {
	PM_STATE_ACTIVE,
	// cpu cores都进入idle状态,不改变devices状态。
	PM_STATE_RUNTIME_IDLE,
	// cpu cores都进入idle状态,可以让外设进入低功耗状态。
	PM_STATE_SUSPEND_TO_IDLE,
	// 单核和suspend to idle一样,多核会把non-boot cpus都关掉。
	PM_STATE_STANDBY,
	// 内存进入自刷新模式,devices和cpus状态都保存到memory,resume从rom code起来。
	PM_STATE_SUSPEND_TO_RAM,
	// 内存也关掉,内容都保存到flash/disk,启动再读出来。
	PM_STATE_SUSPEND_TO_DISK,
	// 全部关掉,cpu和memory内容也不保存。
	PM_STATE_SOFT_OFF,
	PM_STATE_COUNT,
};

在调用idle()函数后,会调用pm_system_suspend

bool pm_system_suspend(int32_t ticks)
{
	uint8_t id = _current_cpu->id;
	k_spinlock_key_t key;

	key = k_spin_lock(&pm_forced_state_lock);
	if (z_cpus_pm_forced_state[id].state != PM_STATE_ACTIVE) {
		z_cpus_pm_state[id] = z_cpus_pm_forced_state[id];
		z_cpus_pm_forced_state[id].state = PM_STATE_ACTIVE;
	} else {
		const struct pm_state_info *info;

		info = pm_policy_next_state(id, ticks);
		if (info != NULL) {
			z_cpus_pm_state[id] = *info;
		}
	}
	k_spin_unlock(&pm_forced_state_lock, key);

	if (z_cpus_pm_state[id].state == PM_STATE_ACTIVE) {
		LOG_DBG("No PM operations done.");
		SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
				   z_cpus_pm_state[id].state);
		return false;
	}
// 如果打开了PM_DEVICE,并且不是只由runtime pm管理,会把所有devices都suspend
#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
	if (atomic_sub(&_cpus_active, 1) == 1) {
		if (z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) {
			pm_suspend_devices();
		}
	}
#endif

	if (ticks != K_TICKS_FOREVER) {
		/*
		 * We need to set the timer to interrupt a little bit early to
		 * accommodate the time required by the CPU to fully wake up.
		 */
		sys_clock_set_timeout(ticks -
		     k_us_to_ticks_ceil32(
			     z_cpus_pm_state[id].exit_latency_us),
				     true);
	}

	k_sched_lock();
	pm_stats_start();
	/* Enter power state */
	// 调用app注册的回调函数。
	pm_state_notify(true);
	atomic_set_bit(z_post_ops_required, id);
	// 这个函数是SoC自定义的进入不同状态的操作函数。
	// 一般会进入suspend/sleep状态,进入WFI等待前面设置的timeout中断。
	pm_state_set(z_cpus_pm_state[id].state, z_cpus_pm_state[id].substate_id);
	pm_stats_stop();

	/* Wake up sequence starts here */
#if defined(CONFIG_PM_DEVICE) && !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
	if (atomic_add(&_cpus_active, 1) == 0) {
		pm_resume_devices();
	}
#endif
	pm_stats_update(z_cpus_pm_state[id].state);
	pm_system_resume();
	k_sched_unlock();
	SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
				   z_cpus_pm_state[id].state);

	return true;
}

Device PM

CONFIG_DEVICE_PM

分为两种

  • Device runtime PM
  • System managed device PM

Device Runtime PM

如果启用了runtime pm,那么在System PM比如suspend to ram/disk…就不用suspend/resume devices了。

System-Managed Device PM

通常在System PM进入某种状态的过程中设置devices状态,在pm_system_suspend()中完成。

在下面场景可以选择System-Managed Device PM:

  • 没有device suspend/resume的时候需要blocking的操作。这时候比runtime PM操作更简单。
  • 某些device不用单独suspend/resume,完全由host控制,随cpu一起睡下去。

更推荐使用Device Runtime PM。

Driver Consumer

static int dummy_driver_pm_action(const struct device *dev,
                                  enum pm_device_action action)
{
    switch (action) {
    case PM_DEVICE_ACTION_SUSPEND:
        /* suspend the device */
        break;
    case PM_DEVICE_ACTION_RESUME:
        /* resume the device */
        break;
    case PM_DEVICE_ACTION_TURN_ON:
        /*
         * powered on the device, used when the power
         * domain this device belongs is resumed.
         */
        ...
        break;
    case PM_DEVICE_ACTION_TURN_OFF:
        /*
         * power off the device, used when the power
         * domain this device belongs is suspended.
         */
        break;
    default:
        return -ENOTSUP;
    }

    return 0;
}

// 定义inst0对应的pm_device结构体
PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action);

// 第三个参数把pm_device挂入device结构体
DEVICE_DT_INST_DEFINE(0, &dummy_init,
    PM_DEVICE_DT_INST_GET(0), NULL, NULL, POST_KERNEL,
    CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);

Source code

device.h 列出了支持的device pm状态:

enum pm_device_state {
	PM_DEVICE_STATE_ACTIVE,
	PM_DEVICE_STATE_SUSPENDED,
	/** Device is being suspended. */
	PM_DEVICE_STATE_SUSPENDING,
	/** Device is turned off (power removed).*/
	PM_DEVICE_STATE_OFF
};

分析下PM_DEVICE_DT_INST_DEFINE宏:

PM_DEVICE_DT_INST_DEFINE(0, dummy_driver_pm_action);

#define PM_DEVICE_DT_INST_DEFINE(idx, pm_action_cb, ...)		\
	Z_PM_DEVICE_DEFINE(DT_DRV_INST(idx),				\
			   Z_DEVICE_DT_DEV_ID(DT_DRV_INST(idx)),	\
			   pm_action_cb,				\
			   COND_CODE_1(IS_EMPTY(__VA_ARGS__), (0), (__VA_ARGS__)))

#define Z_PM_DEVICE_DEFINE(node_id, dev_id, pm_action_cb, isr_safe)		\
	Z_PM_DEVICE_DEFINE_SLOT(dev_id);					\
	static struct COND_CODE_1(isr_safe, (pm_device_isr), (pm_device))	\
		Z_PM_DEVICE_NAME(dev_id) =					\
		Z_PM_DEVICE_INIT(Z_PM_DEVICE_NAME(dev_id), node_id,		\
				 pm_action_cb, isr_safe)

#define Z_PM_DEVICE_INIT(obj, node_id, pm_action_cb, isr_safe)			\
	{									\
		.base = Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb,	\
				isr_safe ? BIT(PM_DEVICE_FLAG_ISR_SAFE) : 0),	\
		COND_CODE_1(isr_safe, (), (Z_PM_DEVICE_RUNTIME_INIT(obj)))	\
	}

#define Z_PM_DEVICE_BASE_INIT(obj, node_id, pm_action_cb, _flags)	     \
	{								     \
		.action_cb = pm_action_cb,				     \
		.state = PM_DEVICE_STATE_ACTIVE,			     \
		.flags = ATOMIC_INIT(Z_PM_DEVICE_FLAGS(node_id) | (_flags)), \
		Z_PM_DEVICE_POWER_DOMAIN_INIT(node_id)			     \
	}

如果在PM_DEVICE_DT_INST_DEFINE没有传入第三个参数,则第11行创建了pm_device结构体,并在13行Z_PM_DEVICE_INIT中初始化:

static struct pm_device __pm_device_dev_id =
{
	.base.action_cb = action_cb,
	.base.state = PM_DEVICE_STATE_ACTIVE, // 初始状态为active
	.base.flag =  PM_DEVICE_FLAG_WS_CAPABLE | PM_DEVICE_FLAG_RUNTIME_AUTO | PM_DEVICE_FLAG_PD // 从设备树中获取wakeup_source, zephyr_pm_device_runtime_auto, power_domain属性,有则把对应flag置起,没有不置起。
	.base.domain =
	.lock = Z_SEM_INITIALIZER(obj.lock, 1, 1),
	.event = Z_EVENT_INITIALIZER(obj.event),
}

Shell command

CONFIG_PM_DEVICE_SHELL 提供了一些pm shell操作。

Device Rumtime PM

CONFIG_PM_DEVICE_RUNTIME开关。只有当app或其他subsys调用到driver api的时候,才会resume设备(pm_device_runtime_get()接口),一般处于suspend状态。

在driver init函数中需要调用pm_device_runtime_enable()来enable device runtime pm。

如果设备默认状态是physical suspended的,需要在pm_device_runtime_enable()之前调用pm_device_init_suspended修改pm状态为suspend,因为软件状态初始化的时候是active。防止在pm_device_runtime_enable()又进行一次suspend。

有一种不用在driver init函数中调用pm_device_runtime_enable()的方法,在device tree中对应的device加入zephyr,pm-device-runtime-auto;属性,这样会在kernel init中自动调用pm_device_runtime_enable()

// driver init function
static int mydev_init(const struct device *dev)
{
    /* OPTIONAL: mark device as suspended if it is physically suspended */
    pm_device_init_suspended(dev);

    /* enable device runtime power management */
    pm_device_runtime_enable(dev);
}
static inline void pm_device_init_suspended(const struct device *dev)
{
	struct pm_device_base *pm = dev->pm_base;

	pm->state = PM_DEVICE_STATE_SUSPENDED;
}
int pm_device_runtime_enable(const struct device *dev)
{
	int ret = 0;
	struct pm_device *pm = dev->pm;

	// 默认这个flag是不会置起来的,在这个函数末尾置起
	if (atomic_test_bit(&pm->base.flags, PM_DEVICE_FLAG_RUNTIME_ENABLED)) {
		goto end;
	}

	/* lazy init of PM fields */
	// pm_device初始化的时候并没有设置pm->dev,所以这里应该会跑进去,初始化system workqueue delayable work
	if (pm->dev == NULL) {
		pm->dev = dev;
		k_work_init_delayable(&pm->work, runtime_suspend_work);
	}
	// 默认状态是PM_DEVICE_STATE_ACTIVE,所以上来会调用一次driver回调函数进suspend。
	if (pm->base.state == PM_DEVICE_STATE_ACTIVE) {
		ret = pm->base.action_cb(pm->dev, PM_DEVICE_ACTION_SUSPEND);
		if (ret < 0) {
			goto unlock;
		}
		// 设置状态为suspend.
		pm->base.state = PM_DEVICE_STATE_SUSPENDED;
	}
	//初始化为没有人在使用该device
	pm->base.usage = 0U;
	//至此初始化完成,把runtime_enable置起
	atomic_set_bit(&pm->base.flags, PM_DEVICE_FLAG_RUNTIME_ENABLED);

end:
	return ret;
}

经过上面的初始化后,设备进入suspend状态,当app或subsys其他上层调用到该device后,在device driver中的操作函数中可以调用 pm_device_runtime_get(),会把usage++, 还会调用到resume的回调函数,把设备唤醒。

可以在driver的suspend函数中关clk, 在resume函数中开clk,这样实现节省功耗

还有一种异步的操作。

// Todo:

Power Domain