本文简要地从 Linux 内核和 NVMe 驱动的角度对 APST 相关问题及分析、解决进行不完全总结 1

更新:2023 / 2 / 13



背景信息

快速可靠的 NVMe SSD 彻底改变了数据存储。但是,NVMe SSD 技术有一个缺点高功耗。

幸运地是 NVMe Spec 提供很多电源管理功能。


为什么电源管理

NVMe 电源管理可以帮助在平台散热和 SSD 消耗的总功率之间达成平衡。

即使规定了 SSD 的最大功率,主机 Host 也可以主动发起功率状态改变来改变 SSD 的功耗。

由于客户端 NVMe 设备大部分时间处于空闲状态,因此使用非工作电源状态( Non Operational State )可以延长设备寿命。


如何实现电源管理

方式

主机 Host 可以通过 3 种不同的方式访问 NVMe 电源管理功能:


主机 Host 使用 Set Feature 命令 ( FID=0xCAPST ) 为自主电源状态转换功能( Autonomous Power State TransitionAPST )设置电源状态( Power State )。
客户端 SSD 将根据设置的条件转换到不同的电源状态。


主机 Host 使用 Set Feature 命令以更改客户端 SSD 当前的电源状态。


主机 Host 使用 Set Feature 命令用于主机控制的热管理,以建立两个温度阈值。
客户端 SSD 达到设定温度后将自动转换到低功耗状态。


场景

针对工作负载和可用的散热平台,对于客户端、数据中心和企业级 SSD3 种使用场景,会有所不同。

  • 对于客户端 SSD,设备大多数时候都处于空闲( Idle )状态,因此 ① APST NVMe 电源管理是最佳方法。
    因为设备将根据设置的空闲时间限制自动过渡到功耗较低的电源状态。
  • 对于数据中心,通常使用 ② 的方法来限制特定工作负载的最大固态硬盘性能。
    因为可以在性能和散热预算要求之间取得平衡。
  • 对于企业级 SSD,设备大多数时候都处于活跃( Busy )状态,因此使用 ③ 热管理温度阈值。
    因为可以确保设备不会过热并触发热关机条件。

电源管理的风险

NVMe 电源管理功能可在电源、性能、产品可靠性和客户体验之间找到可接受的平衡。
但是,如果未正确配置 NVMe 电源管理功能,则存在风险。
这些风险包括以下内容:

  • 无法正确管理 SSD 的功耗,可能会导致产生过多热量,SSD 可能达到其热关机极限并且会关机。
  • 在非工作模式( Non Operational State )时设备无法转换为低功耗模式,将继续消耗功率。
  • 最低功耗状态将花费最长的时间以进入和退出。不考虑进入和退出延迟可能会导致性能降低或响应时间延长。

自主电源状态切换(APST)

参考 2


系统

从系统和驱动的角度看 NVMe SSDAPST 功能是如何实现的:


概念

配置 APST

linux/drivers/nvme/host/core.c 3 中,关于配置 APST 的源码部分如下,

/*
 * APST (Autonomous Power State Transition) lets us program a table of power
 * state transitions that the controller will perform automatically.
 *
 * Depending on module params, one of the two supported techniques will be used:
 *
 * - If the parameters provide explicit timeouts and tolerances, they will be
 *   used to build a table with up to 2 non-operational states to transition to.
 *   The default parameter values were selected based on the values used by
 *   Microsoft's and Intel's NVMe drivers. Yet, since we don't implement dynamic
 *   regeneration of the APST table in the event of switching between external
 *   and battery power, the timeouts and tolerances reflect a compromise
 *   between values used by Microsoft for AC and battery scenarios.
 * - If not, we'll configure the table with a simple heuristic: we are willing
 *   to spend at most 2% of the time transitioning between power states.
 *   Therefore, when running in any given state, we will enter the next
 *   lower-power non-operational state after waiting 50 * (enlat + exlat)
 *   microseconds, as long as that state's exit latency is under the requested
 *   maximum latency.
 *
 * We will not autonomously enter any non-operational state for which the total
 * latency exceeds ps_max_latency_us.
 *
 * Users can set ps_max_latency_us to zero to turn off APST.
 */
static int nvme_configure_apst(struct nvme_ctrl *ctrl)
{
	struct nvme_feat_auto_pst *table;
	unsigned apste = 0;
	u64 max_lat_us = 0;
	__le64 target = 0;
	int max_ps = -1;
	int state;
	int ret;
	unsigned last_lt_index = UINT_MAX;

	/*
	 * If APST isn't supported or if we haven't been initialized yet,
	 * then don't do anything.
	 */
	if (!ctrl->apsta)
		return 0;

	if (ctrl->npss > 31) {
		dev_warn(ctrl->device, "NPSS is invalid; not using APST\n");
		return 0;
	}

	table = kzalloc(sizeof(*table), GFP_KERNEL);
	if (!table)
		return 0;
	
	// 此处说明如果 `ps_max_latency_us` == 0,将会disable APST
	if (!ctrl->apst_enabled || ctrl->ps_max_latency_us == 0) {
		/* Turn off APST. */
		dev_dbg(ctrl->device, "APST disabled\n");
		goto done;
	}

	/*
	 * Walk through all states from lowest- to highest-power.
	 * According to the spec, lower-numbered states use more power.  NPSS,
	 * despite the name, is the index of the lowest-power state, not the
	 * number of states.
	 */
	for (state = (int)ctrl->npss; state >= 0; state--) {
		u64 total_latency_us, exit_latency_us, transition_ms;

		if (target)
			table->entries[state] = target;

		/*
		 * Don't allow transitions to the deepest state if it's quirked
		 * off.
		 */
		// 如果配置了 `NVME_QUIRK_NO_DEEPEST_PS`,说明不允许设备进入最深的低功耗状态
		if (state == ctrl->npss &&
		    (ctrl->quirks & NVME_QUIRK_NO_DEEPEST_PS))
			continue;

		/*
		 * Is this state a useful non-operational state for higher-power
		 * states to autonomously transition to?
		 */
		if (!(ctrl->psd[state].flags & NVME_PS_FLAGS_NON_OP_STATE))
			continue;

		exit_latency_us = (u64)le32_to_cpu(ctrl->psd[state].exit_lat);
		if (exit_latency_us > ctrl->ps_max_latency_us)
			continue;

		total_latency_us = exit_latency_us +
			le32_to_cpu(ctrl->psd[state].entry_lat);

		/*
		 * This state is good. It can be used as the APST idle target
		 * for higher power states.
		 */
		if (apst_primary_timeout_ms && apst_primary_latency_tol_us) {
			if (!nvme_apst_get_transition_time(total_latency_us,
					&transition_ms, &last_lt_index))
				continue;
		} else {
			transition_ms = total_latency_us + 19;
			do_div(transition_ms, 20);
			if (transition_ms > (1 << 24) - 1)
				transition_ms = (1 << 24) - 1;
		}

		target = cpu_to_le64((state << 3) | (transition_ms << 8));
		if (max_ps == -1)
			max_ps = state;
		if (total_latency_us > max_lat_us)
			max_lat_us = total_latency_us;
	}

	if (max_ps == -1)
		dev_dbg(ctrl->device, "APST enabled but no non-operational states are available\n");
	else
		dev_dbg(ctrl->device, "APST enabled: max PS = %d, max round-trip latency = %lluus, table = %*phN\n",
			max_ps, max_lat_us, (int)sizeof(*table), table);
	apste = 1;

done:
	ret = nvme_set_features(ctrl, NVME_FEAT_AUTO_PST, apste,
				table, sizeof(*table), NULL);
	if (ret)
		dev_err(ctrl->device, "failed to set APST feature (%d)\n", ret);
	kfree(table);
	return ret;
}

从上面可知,APST 是一项允许 NVMe SSD 内的 NVMe Controller 按照可配置规则在电源管理状态之间自主切换的功能。

NVMe Controller 指定进入和退出每个电源状态需要多少微秒,内核 kernel 也将使用此信息进行相应的配置 2

而在哪一个阶段会配置 APST 呢?
—— 在初始化 NVMe Controller 的阶段。


初始化 NVMe Controller

初始化 NVMe Controller 又是如何进行的?如下所示:

/*
 * Initialize the cached copies of the Identify data and various controller
 * register in our nvme_ctrl structure.  This should be called as soon as
 * the admin queue is fully up and running.
 */
int nvme_init_ctrl_finish(struct nvme_ctrl *ctrl, bool was_suspended)
{
	int ret;

	ret = ctrl->ops->reg_read32(ctrl, NVME_REG_VS, &ctrl->vs);
	if (ret) {
		dev_err(ctrl->device, "Reading VS failed (%d)\n", ret);
		return ret;
	}

	ctrl->sqsize = min_t(u16, NVME_CAP_MQES(ctrl->cap), ctrl->sqsize);

	if (ctrl->vs >= NVME_VS(1, 1, 0))
		ctrl->subsystem = NVME_CAP_NSSRC(ctrl->cap);

	ret = nvme_init_identify(ctrl);
	if (ret)
		return ret;

	ret = nvme_configure_apst(ctrl);
	if (ret < 0)
		return ret;

	ret = nvme_configure_timestamp(ctrl);
	if (ret < 0)
		return ret;

	ret = nvme_configure_host_options(ctrl);
	if (ret < 0)
		return ret;

	nvme_configure_opal(ctrl, was_suspended);

	if (!ctrl->identified && !nvme_discovery_ctrl(ctrl)) {
		/*
		 * Do not return errors unless we are in a controller reset,
		 * the controller works perfectly fine without hwmon.
		 */
		ret = nvme_hwmon_init(ctrl);
		if (ret == -EINTR)
			return ret;
	}

	ctrl->identified = true;

	return 0;
}
EXPORT_SYMBOL_GPL(nvme_init_ctrl_finish);

由上面可知,会先后进行读取 NVME_REG_VS 寄存器的值、写入 NVME_CAP_MQES 寄存器、配置 APST、配置时间戳、配置主机设置等步骤来完成 NVMe Controller 的初始化。


风险

问题描述

使用 $ sudo nvme get-feature -f 0x0c -H /dev/nvme0 可从输出结果中确认 NVMe SSDAPST 是处于 Enabled 状态还是 Disabled 的状态。
使用 $ sudo nvme set-feature -f 0x0c -v=0 /dev/nvme0 可以临时将 NVMe SSDAPST 功能禁止。

如果系统 BIOSNVMe SSD 加载 NVMe 驱动前开启了 NVMe SSDAPST 功能,则内核中用于禁止 APST 的相关参数将会被忽略,或者说,不起任何作用。

APST 开启时,如果 NVMe SSD 自动进入低功耗模式( Non-Operational State)后,无法在预计时间内被唤醒为正常工作模式( Operational State )或者只有重启 NVme Controller 才能被唤醒的话,会让 kernel “不高兴”。
此时,可以在内核日志中看到类似于下面的日志记录 4

vme nvme0: I/O 566 QID 7 timeout, aborting
 nvme nvme0: I/O 989 QID 1 timeout, aborting
 nvme nvme0: I/O 990 QID 1 timeout, aborting
 nvme nvme0: I/O 840 QID 6 timeout, reset controller
 nvme nvme0: I/O 24 QID 0 timeout, reset controller
 nvme nvme0: Device not ready; aborting reset, CSTS=0x1
 ...
 nvme nvme0: Device not ready; aborting reset, CSTS=0x1
 nvme nvme0: Device not ready; aborting reset, CSTS=0x1
 nvme nvme0: failed to set APST feature (-19)

解决方案

1.
方法

可以通过 cat /sys/module/nvme_core/parameters/default_ps_max_latency_us 的输出结果先行确认当前该参数的值:

  • 如果输出结果为 0,则满足需求;
  • 如果输出结果不为 0,通过设定内核启动参数 nvme_core.default_ps_max_latency_us=0. 4 以禁止 APST

修改方式为通过修改 /etc/default/grub 56 文件中相应的行为 nvme_core.default_ps_max_latency_us=0,然后使用 update-grub 命令更新内核启动参数。
确认修改结果可通过 $ cat /proc/cmdline 命令,

BOOT_IMAGE=/boot/vmlinuz-4.10.0-22-generic.efi.signed root=UUID=365f1a9c-9598-4ad5-a387-d02f771767a1 ro quiet splash nvme_core.default_ps_max_latency_us=0 vt.handoff=7

其中,nvme_core.default_ps_max_latency_us=0 出现,证明此前的修改已并入启动配置中。


影响

NMVe SSDAPST 被禁止后,意味着其只能进入主机 Host 设置的电源状态,即功耗节能将不如 APST 功能开启时的功耗管理效果。


2.
方法

通过 NVMe SSD 固件确保其 APST 功能中的不同电源状态的进入、退出时间是合适的。


影响

需要进行相应版本的固件升级且每个电源状态的最大进入时间和最大退出时间都是合适的。


3.
方法

通过修改内核设置来避免 NVMe SSD 进入某些深低功耗状态。

在内核 kernel 中许多设备驱动都有 quirk table 用于特定硬件模型的临时解决方法 7
对于 NVMe 驱动 8quirk table 如下所示:

static const struct pci_device_id nvme_id_table[] = {
    { PCI_VDEVICE(INTEL, 0x0953),   /* Intel 750/P3500/P3600/P3700 */
        .driver_data = NVME_QUIRK_STRIPE_SIZE |
                NVME_QUIRK_DEALLOCATE_ZEROES, },
    { PCI_VDEVICE(INTEL, 0x0a53),   /* Intel P3520 */
        .driver_data = NVME_QUIRK_STRIPE_SIZE |
                NVME_QUIRK_DEALLOCATE_ZEROES, },
    { PCI_VDEVICE(INTEL, 0x0a54),   /* Intel P4500/P4600 */
        .driver_data = NVME_QUIRK_STRIPE_SIZE |
                NVME_QUIRK_DEALLOCATE_ZEROES, },
    { PCI_VDEVICE(INTEL, 0x0a55),   /* Dell Express Flash P4600 */
        .driver_data = NVME_QUIRK_STRIPE_SIZE |
                NVME_QUIRK_DEALLOCATE_ZEROES, },
    { PCI_VDEVICE(INTEL, 0xf1a5),   /* Intel 600P/P3100 */
        .driver_data = NVME_QUIRK_NO_DEEPEST_PS |
                NVME_QUIRK_MEDIUM_PRIO_SQ |
                NVME_QUIRK_NO_TEMP_THRESH_CHANGE |
                NVME_QUIRK_DISABLE_WRITE_ZEROES, },
[...]

请注意一个已经存在的名为 NVME_QUIRK_NO_DEEPEST_PSquirk 设置,它可以避免进入到最深的电源状态。

如果你的 NVMe SSDAPST 相关问题与 Intel 600P/P3100 的解决方案相同,那么只需要以下面的格式编写一条新的 quirk table entry

  { PCI_DEVICE(<PCI vendor ID>, <PCI product ID of the SSD>),   /* <specify make/model of SSD here> */
        .driver_data = NVME_QUIRK_NO_DEEPEST_PS, },

并基于此修改重新编译内核 kernel

又或者,修改 nvme_core.default_ps_max_latency_us=2000 4 来禁止进入 PS4 电源状态。


影响

NVMe SSD 不再进入最深的低功耗状态。


参考链接


  1. 为什么以及如何部署NVMe电源管理 ↩︎

  2. clarifying nvme apst problems for linux ↩︎ ↩︎

  3. linux/drivers/nvme/host/core.c ↩︎

  4. Controller failure due to broken APST support ↩︎ ↩︎ ↩︎

  5. APST gets enabled against explicit kernel option ↩︎

  6. Kernel parameters ↩︎

  7. What are PCI quirks? ↩︎

  8. linux/drivers/nvme/host/pci.c ↩︎

Logo

更多推荐