]>
Commit | Line | Data |
---|---|---|
ff28b140 TL |
1 | /* |
2 | * scsinvme.cpp | |
3 | * | |
4 | * Home page of code is: http://www.smartmontools.org | |
5 | * | |
6 | * Copyright (C) 2018 Harry Mallon <hjmallon@gmail.com> | |
7 | * | |
8 | * SPDX-License-Identifier: GPL-2.0-or-later | |
9 | * | |
10 | */ | |
11 | ||
12 | #include "config.h" | |
13 | ||
14 | #include "dev_interface.h" | |
15 | #include "dev_tunnelled.h" | |
16 | #include "scsicmds.h" | |
17 | #include "sg_unaligned.h" | |
18 | #include "utility.h" | |
19 | ||
20 | #include <errno.h> | |
21 | ||
22 | // SNT (SCSI NVMe Translation) namespace and prefix | |
23 | namespace snt { | |
24 | ||
25 | #define SNT_JMICRON_NVME_SIGNATURE 0x454d564eu // 'NVME' reversed (little endian) | |
26 | #define SNT_JMICRON_CDB_LEN 12 | |
27 | #define SNT_JMICRON_NVM_CMD_LEN 512 | |
28 | ||
29 | class sntjmicron_device | |
30 | : public tunnelled_device< | |
31 | /*implements*/ nvme_device, | |
32 | /*by tunnelling through a*/ scsi_device | |
33 | > | |
34 | { | |
35 | public: | |
36 | sntjmicron_device(smart_interface * intf, scsi_device * scsidev, | |
37 | const char * req_type, unsigned nsid); | |
38 | ||
39 | virtual ~sntjmicron_device() throw(); | |
40 | ||
41 | virtual bool open(); | |
42 | ||
43 | virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); | |
44 | ||
45 | private: | |
46 | enum { | |
47 | proto_nvm_cmd = 0x0, proto_non_data = 0x1, proto_dma_in = 0x2, | |
48 | proto_dma_out = 0x3, proto_response = 0xF | |
49 | }; | |
50 | }; | |
51 | ||
52 | sntjmicron_device::sntjmicron_device(smart_interface * intf, scsi_device * scsidev, | |
53 | const char * req_type, unsigned nsid) | |
54 | : smart_device(intf, scsidev->get_dev_name(), "sntjmicron", req_type), | |
55 | tunnelled_device<nvme_device, scsi_device>(scsidev, nsid) | |
56 | { | |
57 | set_info().info_name = strprintf("%s [USB NVMe JMicron]", scsidev->get_info_name()); | |
58 | } | |
59 | ||
60 | sntjmicron_device::~sntjmicron_device() throw() | |
61 | { | |
62 | } | |
63 | ||
64 | bool sntjmicron_device::open() | |
65 | { | |
66 | // Open USB first | |
67 | if (!tunnelled_device<nvme_device, scsi_device>::open()) | |
68 | return false; | |
69 | ||
70 | // No sure how multiple namespaces come up on device so we | |
71 | // cannot detect e.g. /dev/sdX is NSID 2. | |
72 | // Set to broadcast if not available | |
73 | if (!get_nsid()) { | |
74 | set_nsid(0xFFFFFFFF); | |
75 | } | |
76 | ||
77 | return true; | |
78 | } | |
79 | ||
80 | // cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1) | |
81 | // cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ] | |
82 | // cdb[2]: reserved | |
83 | // cdb[3]: parameter list length (23:16) | |
84 | // cdb[4]: parameter list length (15:08) | |
85 | // cdb[5]: parameter list length (07:00) | |
86 | // cdb[6]: reserved | |
87 | // cdb[7]: reserved | |
88 | // cdb[8]: reserved | |
89 | // cdb[9]: reserved | |
90 | // cdb[10]: reserved | |
91 | // cdb[11]: CONTROL (?) | |
92 | bool sntjmicron_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) | |
93 | { | |
94 | /* Only admin commands used */ | |
95 | bool admin = true; | |
96 | ||
97 | // 1: "NVM Command Set Payload" | |
98 | { | |
99 | unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; | |
100 | cdb[0] = SAT_ATA_PASSTHROUGH_12; | |
101 | cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd; | |
102 | sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); | |
103 | ||
104 | unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; | |
105 | nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE; | |
106 | // nvm_cmd[1]: reserved | |
107 | nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future | |
108 | nvm_cmd[3] = in.nsid; | |
109 | // nvm_cmd[4-5]: reserved | |
110 | // nvm_cmd[6-7]: metadata pointer | |
111 | // nvm_cmd[8-11]: data ptr (?) | |
112 | nvm_cmd[12] = in.cdw10; | |
113 | nvm_cmd[13] = in.cdw11; | |
114 | nvm_cmd[14] = in.cdw12; | |
115 | nvm_cmd[15] = in.cdw13; | |
116 | nvm_cmd[16] = in.cdw14; | |
117 | nvm_cmd[17] = in.cdw15; | |
118 | // nvm_cmd[18-127]: reserved | |
119 | ||
120 | if (isbigendian()) | |
121 | for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) | |
122 | swapx(&nvm_cmd[i]); | |
123 | ||
124 | scsi_cmnd_io io_nvm; | |
125 | memset(&io_nvm, 0, sizeof(io_nvm)); | |
126 | ||
127 | io_nvm.cmnd = cdb; | |
128 | io_nvm.cmnd_len = SNT_JMICRON_CDB_LEN; | |
129 | io_nvm.dxfer_dir = DXFER_TO_DEVICE; | |
130 | io_nvm.dxferp = (uint8_t *)nvm_cmd; | |
131 | io_nvm.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; | |
132 | ||
133 | scsi_device * scsidev = get_tunnel_dev(); | |
134 | if (!scsidev->scsi_pass_through_and_check(&io_nvm, | |
135 | "sntjmicron_device::nvme_pass_through:NVM: ")) | |
136 | return set_err(scsidev->get_err()); | |
137 | } | |
138 | ||
139 | // 2: DMA or Non-Data | |
140 | { | |
141 | unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; | |
142 | cdb[0] = SAT_ATA_PASSTHROUGH_12; | |
143 | ||
144 | scsi_cmnd_io io_data; | |
145 | memset(&io_data, 0, sizeof(io_data)); | |
146 | io_data.cmnd = cdb; | |
147 | io_data.cmnd_len = SNT_JMICRON_CDB_LEN; | |
148 | ||
149 | switch (in.direction()) { | |
150 | case nvme_cmd_in::no_data: | |
151 | cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data; | |
152 | io_data.dxfer_dir = DXFER_NONE; | |
153 | break; | |
154 | case nvme_cmd_in::data_out: | |
155 | cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out; | |
156 | sg_put_unaligned_be24(in.size, &cdb[3]); | |
157 | io_data.dxfer_dir = DXFER_TO_DEVICE; | |
158 | io_data.dxferp = (uint8_t *)in.buffer; | |
159 | io_data.dxfer_len = in.size; | |
160 | break; | |
161 | case nvme_cmd_in::data_in: | |
162 | cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in; | |
163 | sg_put_unaligned_be24(in.size, &cdb[3]); | |
164 | io_data.dxfer_dir = DXFER_FROM_DEVICE; | |
165 | io_data.dxferp = (uint8_t *)in.buffer; | |
166 | io_data.dxfer_len = in.size; | |
167 | memset(in.buffer, 0, in.size); | |
168 | break; | |
169 | case nvme_cmd_in::data_io: | |
170 | default: | |
171 | return set_err(EINVAL); | |
172 | } | |
173 | ||
174 | scsi_device * scsidev = get_tunnel_dev(); | |
175 | if (!scsidev->scsi_pass_through_and_check(&io_data, | |
176 | "sntjmicron_device::nvme_pass_through:Data: ")) | |
177 | return set_err(scsidev->get_err()); | |
178 | } | |
179 | ||
180 | // 3: "Return Response Information" | |
181 | { | |
182 | unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 }; | |
183 | cdb[0] = SAT_ATA_PASSTHROUGH_12; | |
184 | cdb[1] = (admin ? 0x80 : 0x00) | proto_response; | |
185 | sg_put_unaligned_be24(SNT_JMICRON_NVM_CMD_LEN, &cdb[3]); | |
186 | ||
187 | unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 }; | |
188 | ||
189 | scsi_cmnd_io io_reply; | |
190 | memset(&io_reply, 0, sizeof(io_reply)); | |
191 | ||
192 | io_reply.cmnd = cdb; | |
193 | io_reply.cmnd_len = SNT_JMICRON_CDB_LEN; | |
194 | io_reply.dxfer_dir = DXFER_FROM_DEVICE; | |
195 | io_reply.dxferp = (uint8_t *)nvm_reply; | |
196 | io_reply.dxfer_len = SNT_JMICRON_NVM_CMD_LEN; | |
197 | ||
198 | scsi_device * scsidev = get_tunnel_dev(); | |
199 | if (!scsidev->scsi_pass_through_and_check(&io_reply, | |
200 | "sntjmicron_device::nvme_pass_through:Reply: ")) | |
201 | return set_err(scsidev->get_err()); | |
202 | ||
203 | if (isbigendian()) | |
204 | for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++) | |
205 | swapx(&nvm_reply[i]); | |
206 | ||
207 | if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE) | |
208 | return set_err(EIO, "Out of spec JMicron NVMe reply"); | |
209 | ||
210 | int status = nvm_reply[5] >> 17; | |
211 | ||
212 | if (status > 0) | |
213 | return set_nvme_err(out, status); | |
214 | ||
215 | out.result = nvm_reply[2]; | |
216 | } | |
217 | ||
218 | return true; | |
219 | } | |
220 | ||
221 | } // namespace snt | |
222 | ||
223 | using namespace snt; | |
224 | ||
225 | nvme_device * smart_interface::get_snt_device(const char * type, scsi_device * scsidev) | |
226 | { | |
227 | if (!scsidev) | |
228 | throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0"); | |
229 | ||
230 | // Take temporary ownership of 'scsidev' to delete it on error | |
231 | scsi_device_auto_ptr scsidev_holder(scsidev); | |
232 | nvme_device * sntdev = 0; | |
233 | ||
234 | // TODO: Remove this and adjust drivedb entry accordingly when no longer EXPERIMENTAL | |
235 | if (!strcmp(type, "sntjmicron#please_try")) { | |
236 | set_err(EINVAL, "USB to NVMe bridge [please try '-d sntjmicron' and report result to: " | |
237 | PACKAGE_BUGREPORT "]"); | |
238 | return 0; | |
239 | } | |
240 | ||
241 | if (!strncmp(type, "sntjmicron", 10)) { | |
242 | int n1 = -1, n2 = -1, len = strlen(type); | |
243 | unsigned nsid = 0; // invalid namespace id -> use default | |
244 | sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2); | |
245 | if (!(n1 == len || n2 == len)) { | |
246 | set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type); | |
247 | return 0; | |
248 | } | |
249 | sntdev = new sntjmicron_device(this, scsidev, type, nsid); | |
250 | } | |
251 | else { | |
252 | set_err(EINVAL, "Unknown SNT device type '%s'", type); | |
253 | return 0; | |
254 | } | |
255 | ||
256 | // 'scsidev' is now owned by 'sntdev' | |
257 | scsidev_holder.release(); | |
258 | return sntdev; | |
259 | } |