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