我遇到过一个几乎相同的问题中描述的一种阿库本图社区。
与发布此问题的用户一样,我的系统配备了金士顿 NVME 磁盘,并且与该用户一样,我的问题通过在 grub 菜单中添加以下内核选项得到解决:nvme_core.default_ps_max_latency_us=0
。
用户声明的决议如下:
问题出在 SSD 功能上,自主电源状态转换 (APST) 导致冻结。为了缓解这种情况,在他们发布修复程序之前,请将该行包含
nvme_core.default_ps_max_latency_us=0
在GRUB_CMDLINE_LINUX_DEFAULT
选项中。
尽管有帮助,但此评论留下了几个悬而未决的问题,包括以下内容:
- 导致问题的具体缺陷是什么以及在哪里?
- 为了防止出现缺陷,解决方法进行了哪些更改?
- 由于这种解决方法,哪些功能或其他期望的效果会丢失?
- 特别是,需要修复什么,内核、存储介质固件、系统固件(即 UEFI/BIOS)或其他组件,才能提供适当的解决方案?
任何评论都有助于解决全部或部分这种混乱。
答案1
里面的代码注释drivers/nvme/host/core.c
在 Linux 内核源代码中似乎解释得最好:
/*
* 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)
因此,APST 是一项允许 NVMe 控制器(在 NVMe SSD 内)按照可配置规则在电源管理状态之间自主切换的功能。 NVMe控制器指定进入和退出每个省电状态需要多少微秒;内核使用此信息来配置 NVMe 控制器内的状态转换规则。
- 导致问题的具体缺陷是什么以及在哪里?
看起来这款特定的金士顿 NVMe SSD 要么对其唤醒时间估计过于乐观,要么在进入足够深度的省电状态后根本无法唤醒(无需完全重置控制器)。当获得使用 APST 的权限时,它显然会进入某种省电状态,然后无法在指定时间内返回到操作状态,这使内核不高兴。
- 为了防止出现缺陷,解决方法进行了哪些更改?
它告诉从 APST 电源管理状态唤醒的最大允许时间正好是 0 微秒,这会导致 APST 功能被禁用。
- 由于这种解决方法,哪些功能或其他期望的效果会丢失?
如果无法使用 NVMe 控制器的自主电源管理功能,则只有在内核明确请求时才允许控制器进入省电状态。这意味着节能效果很可能不会像使用 APST 时那么大。
- 特别是,需要修复什么,内核、存储介质固件、系统固件(即 UEFI/BIOS)或其他组件,以便用户体验正确的解决方案?
最佳解决方案是金士顿提供 NVMe 磁盘固件更新,使 APST 电源管理正常工作,或者至少使驱动器不承诺其无法提供的东西,即不宣布过渡时间过于乐观的 APST 模式,和/或根本不宣布任何会导致控制器在使用时发生故障的 APST 模式。
如果事实证明可以通过编程 APST 来完全避免最深的节能状态来避免该问题,则可能可以创建更具体的内核级解决方法。 Linux 内核中的许多设备驱动程序都有“怪异表”,指定特定硬件模型的解决方法。对于 NVMe,您可以在drivers/nvme/host/pci.c
Linux 内核源代码中:
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 |
NVME_QUIRK_IGNORE_DEV_SUBNQN, },
{ 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, },
{ PCI_VDEVICE(INTEL, 0xf1a6), /* Intel 760p/Pro 7600p */
.driver_data = NVME_QUIRK_IGNORE_DEV_SUBNQN, },
{ PCI_VDEVICE(INTEL, 0x5845), /* Qemu emulated controller */
.driver_data = NVME_QUIRK_IDENTIFY_CNS |
NVME_QUIRK_DISABLE_WRITE_ZEROES |
NVME_QUIRK_BOGUS_NID, },
{ PCI_VDEVICE(REDHAT, 0x0010), /* Qemu emulated controller */
.driver_data = NVME_QUIRK_BOGUS_NID, },
[...]
这里的各种NVME_QUIRK_
设置会触发驱动程序内的各种解决方法代码。
请注意,已经存在一个名为 的怪异设置,NVME_QUIRK_NO_DEEPEST_PS
该设置可防止状态转换到最深的电源管理状态。如果您的金士顿 NVMe 的 APST 问题与已为 Intel 600P/P3100 和 ADATA SX8200PNP 实现的解决方法相同,那么只需编写一个新的怪异表条目,如下所示(<angle brackets>
用适当的值替换其中的内容,你可以用lspci -nn
):
{ PCI_DEVICE(<PCI vendor ID>, <PCI product ID of the SSD>), /* <specify make/model of SSD here> */
.driver_data = NVME_QUIRK_NO_DEEPEST_PS, },
并通过此修改重新编译内核。
显然,需要真正拥有该 SSD 型号的人来测试这一点。如果您碰巧熟悉 C 编程基础知识以及如何编译自定义内核,这可能是您将自己的名字列入 Linux 内核贡献者长名单的机会!如果您有兴趣,您可能应该阅读kernelnewbies.org更多细节。
内核编程并不总是非常复杂:有很多简单的部分只需要一个拥有合适硬件和一些基本编程知识的人。我已经提交了一些像这样的小补丁。
如果设置NVME_QUIRK_NO_DEEPEST_PS
结果不能解决问题,那么可能需要实施新的怪癖。这可能会更复杂,并且可能需要进行一些实验或最好从金士顿获取信息,以找出到底需要做什么才能避免此问题,并且可能需要与 Linux NVMe 驱动程序维护人员讨论实现该问题的最佳方法。