xref: /illumos-gate/usr/src/lib/libnvme/common/libnvme_sandisk.c (revision fe5224a3e61e4d791ea239810e79697fc96f7b16)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * libnvme pieces specific to Sandisk.
18  *
19  * Sandisk was spun out of WDC. The controller family changed in approximately
20  * the SN861 family versus the prior SNx40 and SNx50 parts. We use this as a
21  * somewhat arbitrary delimiter between the two devices and thus add
22  * vendor-specific commands starting with this family with the 'sandisk' name.
23  */
24 
25 #include <sys/sysmacros.h>
26 #include <sys/nvme/wdc.h>
27 
28 #include "libnvme_impl.h"
29 
30 /*
31  * This is a default timeout (seconds) that we think should be good enough for
32  * most of these operations. As we expand this, this should become more fine
33  * grained.
34  */
35 static const uint32_t nvme_sndk_timeout = 20;
36 
37 static const nvme_vsd_ident_t sandisk_sn861_idents[] = {
38 	{
39 		.nvdi_vid = WDC_PCI_VID,
40 		.nvdi_did = WDC_SN861_DID_U2,
41 		.nvdi_human = "SanDisk DC SN861 U.2",
42 	}, {
43 		.nvdi_vid = WDC_PCI_VID,
44 		.nvdi_did = WDC_SN861_DID_E3,
45 		.nvdi_human = "SanDisk DC SN861 E3.S",
46 	}, {
47 		.nvdi_vid = WDC_PCI_VID,
48 		.nvdi_did = WDC_SN861_DID_E1,
49 		.nvdi_human = "SanDisk DC SN861 E1.S",
50 	}
51 };
52 
53 static const nvme_log_page_info_t *sandisk_sn861_log_pages[] = {
54 	&ocp_log_smart, &ocp_log_errrec, &ocp_log_fwact, &ocp_log_lat,
55 	&ocp_log_devcap, &ocp_log_unsup
56 };
57 
58 static const nvme_vuc_disc_t sndk_sn861_vuc[] = { {
59 	.nvd_short = "sandisk/pci-eye",
60 	.nvd_desc = "per-lane PCI eye diagram",
61 	.nvd_opc = WDC_SN861_VUC_EYE_OPC,
62 	.nvd_dt = NVME_VUC_DISC_IO_OUTPUT,
63 	.nvd_lock = NVME_VUC_DISC_LOCK_NONE
64 }, {
65 	.nvd_short = "sandisk/hwrev",
66 	.nvd_desc = "print hardware revision",
67 	.nvd_opc = WDC_SN861_VUC_HWREV_OPC,
68 	.nvd_dt = NVME_VUC_DISC_IO_OUTPUT,
69 	.nvd_lock = NVME_VUC_DISC_LOCK_NONE
70 } };
71 
72 const nvme_vsd_t sandisk_sn861 = {
73 	.nvd_ident = sandisk_sn861_idents,
74 	.nvd_nident = ARRAY_SIZE(sandisk_sn861_idents),
75 	.nvd_logs = sandisk_sn861_log_pages,
76 	.nvd_nlogs = ARRAY_SIZE(sandisk_sn861_log_pages),
77 	.nvd_vuc = sndk_sn861_vuc,
78 	.nvd_nvuc = ARRAY_SIZE(sndk_sn861_vuc)
79 };
80 
81 bool
nvme_sndk_pci_eye(nvme_ctrl_t * ctrl,uint8_t lane,void * buf,size_t len)82 nvme_sndk_pci_eye(nvme_ctrl_t *ctrl, uint8_t lane, void *buf, size_t len)
83 {
84 	nvme_vuc_req_t *req = NULL;
85 
86 	if (buf == NULL) {
87 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
88 		    "encountered invalid eye diagram buffer output pointer: %p",
89 		    buf));
90 	}
91 
92 	if (len < WDC_SN861_VUC_EYE_LEN) {
93 		return (nvme_ctrl_error(ctrl, NVME_ERR_PCIE_EYE_BUF_RANGE, 0,
94 		    "eye diagram buffer output size is too small: found 0x%zx "
95 		    "bytes, but need at least 0x%x", len,
96 		    WDC_SN861_VUC_EYE_LEN));
97 	}
98 
99 	if (lane >= 4) {
100 		return (nvme_ctrl_error(ctrl, NVME_ERR_PCIE_LANE_RANGE, 0,
101 		    "invalid PCIe lane %u: must be between 0-3", lane));
102 	}
103 
104 	if (!nvme_vendor_vuc_supported(ctrl, "sandisk/pci-eye")) {
105 		return (false);
106 	}
107 
108 	if (!nvme_vuc_req_init(ctrl, &req)) {
109 		return (false);
110 	}
111 
112 
113 	if (!nvme_vuc_req_set_opcode(req, WDC_SN861_VUC_EYE_OPC) ||
114 	    !nvme_vuc_req_set_cdw12(req, WDC_SN861_VUC_EYE_CDW12) ||
115 	    !nvme_vuc_req_set_cdw13(req, lane) ||
116 	    !nvme_vuc_req_set_timeout(req, nvme_sndk_timeout) ||
117 	    !nvme_vuc_req_set_output(req, buf, len) ||
118 	    !nvme_vuc_req_exec(req)) {
119 		nvme_vuc_req_fini(req);
120 		return (false);
121 	}
122 
123 	nvme_vuc_req_fini(req);
124 	return (nvme_ctrl_success(ctrl));
125 }
126 
127 bool
nvme_sndk_hw_rev(nvme_ctrl_t * ctrl,uint8_t * majorp,uint8_t * minorp)128 nvme_sndk_hw_rev(nvme_ctrl_t *ctrl, uint8_t *majorp, uint8_t *minorp)
129 {
130 	uint32_t vers;
131 	nvme_vuc_req_t *req = NULL;
132 
133 	if (majorp == NULL) {
134 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
135 		    "encountered invalid major number output pointer: %p",
136 		    majorp));
137 	}
138 
139 	if (minorp == NULL) {
140 		return (nvme_ctrl_error(ctrl, NVME_ERR_BAD_PTR, 0,
141 		    "encountered invalid minor number output pointer: %p",
142 		    majorp));
143 	}
144 
145 	if (!nvme_vendor_vuc_supported(ctrl, "sandisk/hwrev")) {
146 		return (false);
147 	}
148 
149 	if (!nvme_vuc_req_init(ctrl, &req)) {
150 		return (false);
151 	}
152 
153 	if (!nvme_vuc_req_set_opcode(req, WDC_SN861_VUC_HWREV_OPC) ||
154 	    !nvme_vuc_req_set_cdw12(req, WDC_SN861_VUC_HWREV_CDW12) ||
155 	    !nvme_vuc_req_set_timeout(req, nvme_sndk_timeout) ||
156 	    !nvme_vuc_req_set_output(req, &vers, sizeof (vers)) ||
157 	    !nvme_vuc_req_exec(req)) {
158 		nvme_vuc_req_fini(req);
159 		return (false);
160 	}
161 	nvme_vuc_req_fini(req);
162 
163 	/*
164 	 * The major and minor version are the first and second 10s digit of
165 	 * this value. If we have something that is larger than that then we are
166 	 * going to treat this as an error.
167 	 */
168 	if (vers > 100) {
169 		return (nvme_ctrl_error(ctrl, NVME_ERR_INTERNAL, 0, "returned "
170 		    "version is in an unexpected format and cannot be parsed "
171 		    "into its major and minor number: 0x%x", vers));
172 	}
173 
174 	*minorp = vers % 10;
175 	*majorp = (vers / 10) % 10;
176 
177 	return (nvme_ctrl_success(ctrl));
178 }
179