本文简要地从 Linux
内核和 NVMe
驱动的角度对 APST
相关问题及分析、解决进行不完全总结 1。
更新:2023 / 2 / 13
Linux / NVMe | APST 不完全总结
- 背景信息
- 为什么电源管理
- 如何实现电源管理
- 方式
- 场景
- 电源管理的风险
- 自主电源状态切换(APST)
- 系统
- 概念
- 配置 APST
- 初始化 NVMe Controller
- 风险
- 问题描述
- 解决方案
- 1.
- 方法
- 影响
- 2.
- 方法
- 影响
- 3.
- 方法
- 影响
- 参考链接
背景信息
快速可靠的 NVMe
SSD
彻底改变了数据存储。但是,NVMe
SSD
技术有一个缺点高功耗。
幸运地是 NVMe
Spec
提供很多电源管理功能。
为什么电源管理
NVMe
电源管理可以帮助在平台散热和 SSD
消耗的总功率之间达成平衡。
即使规定了
SSD
的最大功率,主机Host
也可以主动发起功率状态改变来改变SSD
的功耗。
由于客户端 NVMe
设备大部分时间处于空闲状态,因此使用非工作电源状态( Non Operational State
)可以延长设备寿命。
如何实现电源管理
方式
主机 Host
可以通过 3
种不同的方式访问 NVMe
电源管理功能:
①
主机 Host
使用 Set Feature
命令 ( FID=0xC
,APST
) 为自主电源状态转换功能( Autonomous Power State Transition
,APST
)设置电源状态( Power State
)。
客户端 SSD
将根据设置的条件转换到不同的电源状态。
②
主机 Host
使用 Set Feature
命令以更改客户端 SSD
当前的电源状态。
③
主机 Host
使用 Set Feature
命令用于主机控制的热管理,以建立两个温度阈值。
客户端 SSD
达到设定温度后将自动转换到低功耗状态。
场景
针对工作负载和可用的散热平台,对于客户端、数据中心和企业级 SSD
这 3
种使用场景,会有所不同。
- 对于客户端
SSD
,设备大多数时候都处于空闲(Idle
)状态,因此 ①APST
NVMe
电源管理是最佳方法。
因为设备将根据设置的空闲时间限制自动过渡到功耗较低的电源状态。 - 对于数据中心,通常使用 ② 的方法来限制特定工作负载的最大固态硬盘性能。
因为可以在性能和散热预算要求之间取得平衡。 - 对于企业级
SSD
,设备大多数时候都处于活跃(Busy
)状态,因此使用 ③ 热管理温度阈值。
因为可以确保设备不会过热并触发热关机条件。
电源管理的风险
NVMe
电源管理功能可在电源、性能、产品可靠性和客户体验之间找到可接受的平衡。
但是,如果未正确配置 NVMe
电源管理功能,则存在风险。
这些风险包括以下内容:
- 无法正确管理
SSD
的功耗,可能会导致产生过多热量,SSD
可能达到其热关机极限并且会关机。 - 在非工作模式(
Non Operational State
)时设备无法转换为低功耗模式,将继续消耗功率。 - 最低功耗状态将花费最长的时间以进入和退出。不考虑进入和退出延迟可能会导致性能降低或响应时间延长。
自主电源状态切换(APST)
参考 2
系统
从系统和驱动的角度看 NVMe
SSD
的 APST
功能是如何实现的:
概念
配置 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 APSTif (!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");elsedev_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
SSD
的 APST
是处于 Enabled
状态还是 Disabled
的状态。
使用 $ sudo nvme set-feature -f 0x0c -v=0 /dev/nvme0
可以临时将 NVMe SSD
的 APST
功能禁止。
如果系统 BIOS
在 NVMe
SSD
加载 NVMe
驱动前开启了 NVMe
SSD
的 APST
功能,则内核中用于禁止 APST
的相关参数将会被忽略,或者说,不起任何作用。
在 APST
开启时,如果 NVMe
SSD
自动进入低功耗模式( Non-Operational State
)后,无法在预计时间内被唤醒为正常工作模式( Operational State
)或者只有重启 NVme Controller
才能被唤醒的话,会让 kernel
“不高兴”。
此时,可以在内核日志中看到类似于下面的日志记录 4:
vme nvme0: I/O 566 QID 7 timeout, abortingnvme nvme0: I/O 989 QID 1 timeout, abortingnvme nvme0: I/O 990 QID 1 timeout, abortingnvme nvme0: I/O 840 QID 6 timeout, reset controllernvme nvme0: I/O 24 QID 0 timeout, reset controllernvme nvme0: Device not ready; aborting reset, CSTS=0x1...nvme nvme0: Device not ready; aborting reset, CSTS=0x1nvme nvme0: Device not ready; aborting reset, CSTS=0x1nvme 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
5’ 6 文件中相应的行为 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 SSD
的 APST
被禁止后,意味着其只能进入主机 Host
设置的电源状态,即功耗节能将不如 APST
功能开启时的功耗管理效果。
2.
方法
通过 NVMe
SSD
固件确保其 APST
功能中的不同电源状态的进入、退出时间是合适的。
影响
需要进行相应版本的固件升级且每个电源状态的最大进入时间和最大退出时间都是合适的。
3.
方法
通过修改内核设置来避免 NVMe
SSD
进入某些深低功耗状态。
在内核 kernel
中许多设备驱动都有 quirk table
用于特定硬件模型的临时解决方法 7。
对于 NVMe
驱动 8,quirk 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_PS
的 quirk
设置,它可以避免进入到最深的电源状态。
如果你的 NVMe
SSD
的 APST
相关问题与 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
不再进入最深的低功耗状态。
参考链接
为什么以及如何部署NVMe电源管理 ↩︎
clarifying nvme apst problems for linux ↩︎ ↩︎
linux/drivers/nvme/host/core.c ↩︎
Controller failure due to broken APST support ↩︎ ↩︎ ↩︎
APST gets enabled against explicit kernel option ↩︎
Kernel parameters ↩︎
What are PCI quirks? ↩︎
linux/drivers/nvme/host/pci.c ↩︎