联芸 1602 主控的国产固态在 PVE 中的识别问题

联芸 1602 主控的国产固态在 PVE 中的识别问题

家中 HomeLab 的主力是一台自组的 AMD 机器(以下简称 PRD 主机),使用 PVE 作为虚拟化系统,在系统中装黑群和 PCDN 的虚机,同时使用 PVE 中的 lxc 容器来装服务。

作为主力机器,我给机器配了致态的 TiPlus 7100 做系统盘,并且用 TiPlus 5000 做虚拟机系统数据盘。

最近 618 国产长江存储颗粒的 NVMe 的固态价格实在是非常便宜,而且个个都是 PCIe4.0 的满速盘。从认识 SSD 以来就没见过这么香的价格,所以我也在活动期间买了几块。主要是 2TB 的爱国者 P7000z、2 TB 的梵想 S790 和 4TB 的 HP FX900 Plus。

到货后都在 Windows 中使用 CDI 进行了检测,都是全新盘,没什么问题。接着将硬盘插到 PRD 主机中启动后发生了很诡异的事,P7000z 在 PVE 中无法识别出来。

诡异的硬盘冲突

当 P7000z 无法识别后,我立即就开始怀疑是不是 P7000z 掉盘了。毕竟前一天我才看了小飞机修的 P7000z 掉盘视频,而且爱国者和梵想都算是杂牌了,只是没想到这么快就掉盘。

把 P7000z 拿出来再次插到 Windows 中发现硬盘其实一切正常,好得很。我又开始怀疑这个新买的 UPQI PCIex16 转 nvme 4 盘位的转接板是不是不能兼容主板的 PCIe 拆分。

PRD 主机使用的是华硕的 TUF B550m Plus 主板,支持对第一个 PCIe 插糟进行拆分,虽然 BIOS 的拆分选项只有 8x8 和 PCIe RAID Mode。

B550m Plus 主板 PCIe 拆分说明

但我从 NGA 的贴里找到有人说 PCIe RAID Mode 就是 4x4x4x4 拆分。那理论上应该没问题,也许是转接板的问题?或者是插糟接触不良?

带着这些疑问,我不停地拨插和交换 NVMe 的顺序,发现只要梵想 s790 和 P7000z 这两个硬盘同时插到 PRD 主机,P7000z 就无法识别。我还是第一次见到,还有两个硬盘互相冲突的情况存在。

冲突的原因

带着这个问题进行一轮检索后,在 chiphell 论坛发现了这个问题的原因

总的来说,出现这个问题的原因是这些硬盘都使用了相同的联芸 MAP1602 主控且 VID/DID 相同,导致 Linux 内核只识别一个。致态的 TiPlus 7100 也是使用的这个同样的主控,但是并没有问题。说明这些杂牌就直接使用的公版固件,连 ID 都不改。

Windows 也会报硬盘 ID 重复的错误,只是没阻止正常加载。所以解决办法就是忽略 ID 重复的错误,Linux 内核代码里提供了相应的处理方法。CHH 贴子里提供了一个内核 patch 可以解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -3424,6 +3424,8 @@ static const struct pci_device_id nvme_id_table[] = {
.driver_data = NVME_QUIRK_BOGUS_NID, },
{ PCI_DEVICE(0x1e4B, 0x1202), /* MAXIO MAP1202 */
.driver_data = NVME_QUIRK_BOGUS_NID, },
+ { PCI_DEVICE(0x1e4B, 0x1602), /* MAXIO MAP1602 */
+ .driver_data = NVME_QUIRK_BOGUS_NID, },
{ PCI_DEVICE(0x1cc1, 0x5350), /* ADATA XPG GAMMIX S50 */
.driver_data = NVME_QUIRK_BOGUS_NID, },
{ PCI_DEVICE(0x1dbe, 0x5236), /* ADATA XPG GAMMIX S70 */

这个 Patch 已经提交到 Linux 内核,6.4 版本的内核会包括该 Patch。

不过 PVE 8 的内核版本 6.2.16-5 以后就应用了解决冲突的 Patch,直接升级到 PVE 的最新版本就可以解决这个问题。如果低于这个版本又不想升级,可以去 CHH 下载楼主编译好的包。

如果想自行编译可以参考本文后面章节,提供了编译的详细步骤。

PVE 8 的 Patch

前面说了 PVE 8 新内核修复了硬盘重复 ID 无法识别的问题,我们可以看一下官方是怎样解决的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Fixes: 2079f41ec6ff ("nvme: check that EUI/GUID/UUID are globally unique")
---
drivers/nvme/host/core.c | 36 +++++++++++++++++++++++++++++++++---
1 file changed, 33 insertions(+), 3 deletions(-)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index d567762545b0..f350df252d27 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -4162,10 +4162,40 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info)

ret = nvme_global_check_duplicate_ids(ctrl->subsys, &info->ids);
if (ret) {
- dev_err(ctrl->device,
- "globally duplicate IDs for nsid %d\n", info->nsid);
+ /*
+ * We've found two different namespaces on two different
+ * subsystems that report the same ID. This is pretty nasty
+ * for anything that actually requires unique device
+ * identification. In the kernel we need this for multipathing,
+ * and in user space the /dev/disk/by-id/ links rely on it.
+ *
+ * If the device also claims to be multi-path capable back off
+ * here now and refuse the probe the second device as this is a
+ * recipe for data corruption. If not this is probably a
+ * cheap consumer device if on the PCIe bus, so let the user
+ * proceed and use the shiny toy, but warn that with changing
+ * probing order (which due to our async probing could just be
+ * device taking longer to startup) the other device could show
+ * up at any time.
+ */
nvme_print_device_info(ctrl);
- return ret;
+ if ((ns->ctrl->ops->flags & NVME_F_FABRICS) || /* !PCIe */
+ ((ns->ctrl->subsys->cmic & NVME_CTRL_CMIC_MULTI_CTRL) &&
+ info->is_shared)) {
+ dev_err(ctrl->device,
+ "ignoring nsid %d because of duplicate IDs\n",
+ info->nsid);
+ return ret;
+ }
+
+ dev_err(ctrl->device,
+ "clearing duplicate IDs for nsid %d\n", info->nsid);
+ dev_err(ctrl->device,
+ "use of /dev/disk/by-id/ may cause data corruption\n");
+ memset(&info->ids.nguid, 0, sizeof(info->ids.nguid));
+ memset(&info->ids.uuid, 0, sizeof(info->ids.uuid));
+ memset(&info->ids.eui64, 0, sizeof(info->ids.eui64));
+ ctrl->quirks |= NVME_QUIRK_BOGUS_NID;
}

mutex_lock(&ctrl->subsys->lock);

PVE 8 的 patch 对 ID 重复问题进行了通用处理。

硬盘再次消失

升级了 PVE 版本后,硬盘冲突没再出现了。期待已久 的 HP FX900 Plus 4TB 也终于收到了,按习惯上机、检测、跑分后,装到 PRD 主机中启动,没问题,正常识别。

在某次重启后发现硬盘消失了!而且多次重启都没再出现。检查日志发现,报了这个错:

HP FX900 Plus 4TB 识别报错
1
nvme nvme2: Device not ready; aborting initialisation, CSTS=0x0

使用 PCIe 命令是可以看到正常识别的:

查看 PCIe 是否识别硬盘
1
2
3
4
5
6
7
8
$ lspci -vv | grep MAP1602
pcilib: sysfs_read_vpd: read failed: No such device
08:00.0 Non-Volatile memory controller: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602 (rev 01) (prog-if 02 [NVM Express])
Subsystem: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602
09:00.0 Non-Volatile memory controller: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602 (rev 01) (prog-if 02 [NVM Express])
Subsystem: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602
0a:00.0 Non-Volatile memory controller: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602 (rev 01) (prog-if 02 [NVM Express])
Subsystem: MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1602

爬文后,有文档建议增加内核启动参数:iommu=soft,试了一下没有任何效果。

最后又回到 CHH 的贴子,发现挺多人遇到同样的问题,都是 4TB 的才会有这个问题。可能是固件有 BUG,初始化的超时时间是 0,导致内核等待 0 秒超时,直接中断了初始化。

针对这个情况,用以下的 Patch,把超时时间乘 2 也能解决:

增加 NVME 超时时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index d567762545b0..1ca3d78da5b9 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2407,6 +2407,7 @@ int nvme_enable_ctrl(struct nvme_ctrl *ctrl)
} else {
timeout = NVME_CAP_TIMEOUT(ctrl->cap);
}
+ dev_info(ctrl->device, "[PATCH] nvme core got timeout %u\n",timeout);

ctrl->ctrl_config |= (NVME_CTRL_PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
ctrl->ctrl_config |= NVME_CC_AMS_RR | NVME_CC_SHN_NONE;
@@ -2424,8 +2425,9 @@ int nvme_enable_ctrl(struct nvme_ctrl *ctrl)
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
if (ret)
return ret;
+ dev_info(ctrl->device, "[PATCH] nvme_wait_ready now wait for %u, previously %u\n",(timeout + 1) * 2, (timeout + 1)/2);
return nvme_wait_ready(ctrl, NVME_CSTS_RDY, NVME_CSTS_RDY,
- (timeout + 1) / 2, "initialisation");
+ (timeout + 1) * 2, "initialisation");
}
EXPORT_SYMBOL_GPL(nvme_enable_ctrl);

目前我应用这个 Patch 后可以正常识别硬盘了,日志输出如下:

1
2
[    1.303728] nvme nvme2: [PATCH] nvme core got timeout 0
[ 1.303732] nvme nvme2: [PATCH] nvme_wait_ready now wait for 2, previously 0

CHH 的楼主也针对这个问题,提交了一个 Patch 给 Linux,不过不是使用 Timeout 增加的方法来解决的,而是在 pci.c 里配置 .driver_data,增加 NVME_QUIRK_DELAY_BEFORE_CHK_RDY 的方式。

CHH 的修复方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Problem and fix are verified with my MAP1602 controller SSD device.

Signed-off-by: Han Gao <highenthalpyh@xxxxxxxxx>
Signed-off-by: David Xu <xuwd1@xxxxxxxxxxx>
---
drivers/nvme/host/pci.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index 492f319ebdf3..f75c27730bde 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -3425,7 +3425,8 @@ static const struct pci_device_id nvme_id_table[] = {
{ PCI_DEVICE(0x1e4B, 0x1202), /* MAXIO MAP1202 */
.driver_data = NVME_QUIRK_BOGUS_NID, },
{ PCI_DEVICE(0x1e4B, 0x1602), /* MAXIO MAP1602 */
- .driver_data = NVME_QUIRK_BOGUS_NID, },
+ .driver_data = NVME_QUIRK_BOGUS_NID |
+ NVME_QUIRK_DELAY_BEFORE_CHK_RDY, },
{ PCI_DEVICE(0x1cc1, 0x5350), /* ADATA XPG GAMMIX S50 */
.driver_data = NVME_QUIRK_BOGUS_NID, },
{ PCI_DEVICE(0x1dbe, 0x5236), /* ADATA XPG GAMMIX S70 */

不过,在内核的 MailList 讨论中,有人认为这种方式并不能解决问题,因为 NVME_QUIRK_DELAY_BEFORE_CHK_RDY 影响的代码并不是 timeout 发生的代码。

编译内核

由于对应的 Patch 还在讨论,而且合并到 Kernel 后 PVE 最终能用上还需要一些时间。CHH 楼主编译的 PVE 内核版本(6.2.16-4)比我更新后的小,内核降级感觉不太合适,所以还得自己应用一下 Patch 进行编译。

环境准备

为了防止对 PRD 主机造成影响,我使用 Ubuntu 23.04 的 Template 创建了一个 LXC 容器,准备在里面进行内核构建。

我们先装一下构建需要的依赖:

1
2
3
4
wget -qO - http://download.proxmox.com/debian/proxmox-ve-release-6.x.gpg | apt-key add
echo "deb http://mirrors.ustc.edu.cn/proxmox/debian buster pve-no-subscription " | tee /etc/apt/sources.list.d/buster-pvetest.list
apt update && apt install libpve-common-perl
apt install git make dpkg-dev dh-python dh-make python3-sphinx lintian asciidoc-base bison dwarves flex libdw-dev libelf-dev libiberty-dev libnuma-dev libslang2-dev libssl-dev lz4 xmlto zlib1g-dev bc zstd

注意,libpve-common-perl 在 PVE 的软件源中才有,所以需要先安装 PVE 的软件源,这里使用 ustc 源。装完依赖后,就可以拉代码开始构建了。

构建 PVE 的内核只需要拉取 pve-kernel 就行,pve-kernel 通过 git submodule 的方式依赖 ubuntu 的 Kernel,构建的时候会自动拉取。

1
git clone git://git.proxmox.com/git/pve-kernel.git

注意

ubuntu kernel 有 1GB 多,所以建议把 git.proxmox.com 加入代理加速,不然下半天断了还得重下。

增加 Patch

在 pve-kernel 目录的 patches/kernel 子目录中增加文件 0013-nvme-multiple-timeout-nvme_wait_ready.patch,内容如下:

patches/kernel/0013-nvme-multiple-timeout-nvme_wait_ready.patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index d567762545b0..1ca3d78da5b9 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -2407,6 +2407,7 @@ int nvme_enable_ctrl(struct nvme_ctrl *ctrl)
} else {
timeout = NVME_CAP_TIMEOUT(ctrl->cap);
}
+ dev_info(ctrl->device, "[PATCH] nvme core got timeout %u\n",timeout);

ctrl->ctrl_config |= (NVME_CTRL_PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
ctrl->ctrl_config |= NVME_CC_AMS_RR | NVME_CC_SHN_NONE;
@@ -2424,8 +2425,9 @@ int nvme_enable_ctrl(struct nvme_ctrl *ctrl)
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
if (ret)
return ret;
+ dev_info(ctrl->device, "[PATCH] nvme_wait_ready now wait for %u, previously %u\n",(timeout + 1) * 2, (timeout + 1)/2);
return nvme_wait_ready(ctrl, NVME_CSTS_RDY, NVME_CSTS_RDY,
- (timeout + 1) / 2, "initialisation");
+ (timeout + 1) * 2, "initialisation");
}
EXPORT_SYMBOL_GPL(nvme_enable_ctrl);

这里我们只需要增加一个超时的 Patch 既可,重复 ID 的在官方的 0010-nvme-don-t-reject-probe-due-to-duplicate-IDs-for-sin.patch 中已经包含。

开始构建

增加完 Patch 后,切换到 pve-kernel 根目录就可以执行构建了:

开始构建
1
$ make

大概等半个小时,构建完后会在 pve-kernel 目录生成几个 deb 文件:

查看构建结果
1
2
3
root@builder:~/pve/pve-kernel# ls *.deb
linux-tools-6.2_6.2.16-11_amd64.deb proxmox-headers-6.2_6.2.16-11_all.deb proxmox-kernel-6.2_6.2.16-11_all.deb
proxmox-headers-6.2.16-11-pve_6.2.16-11_amd64.deb proxmox-kernel-6.2.16-11-pve_6.2.16-11_amd64.deb proxmox-kernel-libc-dev_6.2.16-11_amd64.deb

我们把文件复制到 PVE 主机下,使用以下命令就可以安装新内核:

安装内核
1
dpkg -i *.deb

重启后就生效。

总结

便宜是有代价的。买之前没想到会因为硬盘遇到两个 Linux 内核问题,而且这也不算内核的 BUG,只是这些杂牌没有固件开发能力,直接使用公版固件导致的问题。硬盘 ID 改都不改,直接就用,就像 PVE patch 里备注说的很 nasty。

致态使用的相同的主控,这些奇怪问题就一个没有。最后,建议直接买致态,便宜是有代价的。

下载

如果不想自己编译,我这里汇总了编译好的文件,直接安装既可。可以通过 uname -srm 命令检查自己当前内核版本。

引用

  1. 联芸MAP1602主控的可以入了,掉坑里刚爬出来,P7000Z晚班车拿了四块,附内核
  2. nvme-pci: add NVME_QUIRK_DELAY_BEFORE_CHK_RDY for MAXIO MAP1602
  3. Re: nvme-pci: add NVME_QUIRK_DELAY_BEFORE_CHK_RDY for MAXIO MAP1602
  4. Solid state drive/NVMe: Controller failure due to broken suspend support
  5. TUF B550m PLUS Manual
作者

Jakes Lee

发布于

2023-09-04

更新于

2023-09-07

许可协议

评论