]> git.proxmox.com Git - mirror_smartmontools-debian.git/blob - scsinvme.cpp
import smartmontools 7.0
[mirror_smartmontools-debian.git] / scsinvme.cpp
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 }