smartmontools SVN Rev 5470
Utility to control and monitor storage systems with "S.M.A.R.T."
scsinvme.cpp
Go to the documentation of this file.
1/*
2 * scsinvme.cpp
3 *
4 * Home page of code is: https://www.smartmontools.org
5 *
6 * Copyright (C) 2020-21 Christian Franke
7 * Copyright (C) 2018 Harry Mallon <hjmallon@gmail.com>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12#include "config.h"
13
14#include "dev_interface.h"
15#include "dev_tunnelled.h"
16#include "nvmecmds.h"
17#include "scsicmds.h"
18#include "sg_unaligned.h"
19#include "utility.h"
20
21#include <errno.h>
22
23const char * scsinvme_cpp_svnid = "$Id: scsinvme.cpp 5337 2022-02-27 07:53:55Z dpgilbert $";
24
25// SNT (SCSI NVMe Translation) namespace and prefix
26namespace snt {
27
28/////////////////////////////////////////////////////////////////////////////
29// sntasmedia_device
30
32: public tunnelled_device<
33 /*implements*/ nvme_device,
34 /*by tunnelling through a*/ scsi_device
35 >
36{
37public:
39 const char * req_type, unsigned nsid);
40
41 virtual ~sntasmedia_device();
42
43 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
44};
45
47 const char * req_type, unsigned nsid)
48: smart_device(intf, scsidev->get_dev_name(), "sntasmedia", req_type),
50{
51 set_info().info_name = strprintf("%s [USB NVMe ASMedia]", scsidev->get_info_name());
52}
53
55{
56}
57
59{
60 unsigned size = in.size;
61 unsigned cdw10_hi = in.cdw10 >> 16;
62 switch (in.opcode) {
64 if (in.cdw10 == 0x0000001) // Identify controller
65 break;
66 if (in.cdw10 == 0x0000000) { // Identify namespace
67 if (in.nsid == 1)
68 break;
69 return set_err(ENOSYS, "NVMe Identify Namespace 0x%x not supported", in.nsid);
70 }
71 return set_err(ENOSYS, "NVMe Identify with CDW10=0x%08x not supported", in.cdw10);
73 if (!(in.nsid == 0xffffffff || !in.nsid))
74 return set_err(ENOSYS, "NVMe Get Log Page with NSID=0x%x not supported", in.nsid);
75 if (size > 0x200) { // Reading more results in command timeout
76 // TODO: Add ability to return short reads to caller
77 size = 0x200;
78 cdw10_hi = (size / 4) - 1;
79 pout("Warning: NVMe Get Log truncated to 0x%03x bytes, 0x%03x bytes zero filled\n", size, in.size - size);
80 }
81 break;
82 default:
83 return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
84 break;
85 }
86 if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15)
87 return set_err(ENOSYS, "Nonzero NVMe command dwords 11-15 not supported");
88
89 uint8_t cdb[16] = {0, };
90 cdb[0] = 0xe6;
91 cdb[1] = in.opcode;
92 //cdb[2] = ?
93 cdb[3] = (uint8_t)in.cdw10;
94 //cdb[4..6] = ?
95 cdb[7] = (uint8_t)cdw10_hi;
96 //cdb[8..15] = ?
97
98 scsi_cmnd_io io_hdr = {};
99 io_hdr.cmnd = cdb;
100 io_hdr.cmnd_len = sizeof(cdb);
102 io_hdr.dxferp = (uint8_t *)in.buffer;
103 io_hdr.dxfer_len = size;
104 memset(in.buffer, 0, in.size);
105
106 scsi_device * scsidev = get_tunnel_dev();
107 if (!scsidev->scsi_pass_through_and_check(&io_hdr, "sntasmedia_device::nvme_pass_through: "))
108 return set_err(scsidev->get_err());
109
110 //out.result = ?;
111 return true;
112}
113
114/////////////////////////////////////////////////////////////////////////////
115// sntjmicron_device
116
117#define SNT_JMICRON_NVME_SIGNATURE 0x454d564eu // 'NVME' reversed (little endian)
118#define SNT_JMICRON_CDB_LEN 12
119#define SNT_JMICRON_NVM_CMD_LEN 512
120
122: public tunnelled_device<
123 /*implements*/ nvme_device,
124 /*by tunnelling through a*/ scsi_device
125 >
126{
127public:
129 const char * req_type, unsigned nsid);
130
131 virtual ~sntjmicron_device();
132
133 virtual bool open() override;
134
135 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
136
137private:
138 enum {
141 };
142};
143
145 const char * req_type, unsigned nsid)
146: smart_device(intf, scsidev->get_dev_name(), "sntjmicron", req_type),
148{
149 set_info().info_name = strprintf("%s [USB NVMe JMicron]", scsidev->get_info_name());
150}
151
153{
154}
155
157{
158 // Open USB first
160 return false;
161
162 // No sure how multiple namespaces come up on device so we
163 // cannot detect e.g. /dev/sdX is NSID 2.
164 // Set to broadcast if not available
165 if (!get_nsid()) {
166 set_nsid(0xFFFFFFFF);
167 }
168
169 return true;
170}
171
172// cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1)
173// cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ]
174// cdb[2]: reserved
175// cdb[3]: parameter list length (23:16)
176// cdb[4]: parameter list length (15:08)
177// cdb[5]: parameter list length (07:00)
178// cdb[6]: reserved
179// cdb[7]: reserved
180// cdb[8]: reserved
181// cdb[9]: reserved
182// cdb[10]: reserved
183// cdb[11]: CONTROL (?)
185{
186 /* Only admin commands used */
187 constexpr bool admin = true;
188
189 // 1: "NVM Command Set Payload"
190 {
191 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
193 cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd;
195
196 unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
197 nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE;
198 // nvm_cmd[1]: reserved
199 nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future
200 nvm_cmd[3] = in.nsid;
201 // nvm_cmd[4-5]: reserved
202 // nvm_cmd[6-7]: metadata pointer
203 // nvm_cmd[8-11]: data ptr (?)
204 nvm_cmd[12] = in.cdw10;
205 nvm_cmd[13] = in.cdw11;
206 nvm_cmd[14] = in.cdw12;
207 nvm_cmd[15] = in.cdw13;
208 nvm_cmd[16] = in.cdw14;
209 nvm_cmd[17] = in.cdw15;
210 // nvm_cmd[18-127]: reserved
211
212 if (isbigendian())
213 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
214 swapx(&nvm_cmd[i]);
215
216 scsi_cmnd_io io_nvm = {};
217
218 io_nvm.cmnd = cdb;
220 io_nvm.dxfer_dir = DXFER_TO_DEVICE;
221 io_nvm.dxferp = (uint8_t *)nvm_cmd;
223
224 scsi_device * scsidev = get_tunnel_dev();
225 if (!scsidev->scsi_pass_through_and_check(&io_nvm,
226 "sntjmicron_device::nvme_pass_through:NVM: "))
227 return set_err(scsidev->get_err());
228 }
229
230 // 2: DMA or Non-Data
231 {
232 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
234
235 scsi_cmnd_io io_data = {};
236 io_data.cmnd = cdb;
238
239 switch (in.direction()) {
241 cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data;
242 io_data.dxfer_dir = DXFER_NONE;
243 break;
245 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out;
247 io_data.dxfer_dir = DXFER_TO_DEVICE;
248 io_data.dxferp = (uint8_t *)in.buffer;
249 io_data.dxfer_len = in.size;
250 break;
252 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in;
255 io_data.dxferp = (uint8_t *)in.buffer;
256 io_data.dxfer_len = in.size;
257 memset(in.buffer, 0, in.size);
258 break;
260 default:
261 return set_err(EINVAL);
262 }
263
264 scsi_device * scsidev = get_tunnel_dev();
265 if (!scsidev->scsi_pass_through_and_check(&io_data,
266 "sntjmicron_device::nvme_pass_through:Data: "))
267 return set_err(scsidev->get_err());
268 }
269
270 // 3: "Return Response Information"
271 {
272 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
274 cdb[1] = (admin ? 0x80 : 0x00) | proto_response;
276
277 unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
278
279 scsi_cmnd_io io_reply = {};
280
281 io_reply.cmnd = cdb;
282 io_reply.cmnd_len = SNT_JMICRON_CDB_LEN;
283 io_reply.dxfer_dir = DXFER_FROM_DEVICE;
284 io_reply.dxferp = (uint8_t *)nvm_reply;
286
287 scsi_device * scsidev = get_tunnel_dev();
288 if (!scsidev->scsi_pass_through_and_check(&io_reply,
289 "sntjmicron_device::nvme_pass_through:Reply: "))
290 return set_err(scsidev->get_err());
291
292 if (isbigendian())
293 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
294 swapx(&nvm_reply[i]);
295
296 if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE)
297 return set_err(EIO, "Out of spec JMicron NVMe reply");
298
299 int status = nvm_reply[5] >> 17;
300
301 if (status > 0)
302 return set_nvme_err(out, status);
303
304 out.result = nvm_reply[2];
305 }
306
307 return true;
308}
309
310/////////////////////////////////////////////////////////////////////////////
311// sntrealtek_device
312
314: public tunnelled_device<
315 /*implements*/ nvme_device,
316 /*by tunnelling through a*/ scsi_device
317 >
318{
319public:
321 const char * req_type, unsigned nsid);
322
323 virtual ~sntrealtek_device();
324
325 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
326};
327
329 const char * req_type, unsigned nsid)
330: smart_device(intf, scsidev->get_dev_name(), "sntrealtek", req_type),
332{
333 set_info().info_name = strprintf("%s [USB NVMe Realtek]", scsidev->get_info_name());
334}
335
337{
338}
339
341{
342 unsigned size = in.size;
343 switch (in.opcode) {
345 if (in.cdw10 == 0x0000001) // Identify controller
346 break;
347 if (in.cdw10 == 0x0000000) { // Identify namespace
348 if (in.nsid == 1)
349 break;
350 return set_err(ENOSYS, "NVMe Identify Namespace 0x%x not supported", in.nsid);
351 }
352 return set_err(ENOSYS, "NVMe Identify with CDW10=0x%08x not supported", in.cdw10);
354 if (!(in.nsid == 0xffffffff || !in.nsid))
355 return set_err(ENOSYS, "NVMe Get Log Page with NSID=0x%x not supported", in.nsid);
356 if (size > 0x200) { // Reading more apparently returns old data from previous command
357 // TODO: Add ability to return short reads to caller
358 size = 0x200;
359 pout("Warning: NVMe Get Log truncated to 0x%03x bytes, 0x%03x bytes zero filled\n", size, in.size - size);
360 }
361 break;
362 default:
363 return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
364 break;
365 }
366 if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15)
367 return set_err(ENOSYS, "Nonzero NVMe command dwords 11-15 not supported");
368
369 uint8_t cdb[16] = {0, };
370 cdb[0] = 0xe4;
372 cdb[3] = in.opcode;
373 cdb[4] = (uint8_t)in.cdw10;
374
375 scsi_cmnd_io io_hdr = {};
376 io_hdr.cmnd = cdb;
377 io_hdr.cmnd_len = sizeof(cdb);
378 io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
379 io_hdr.dxferp = (uint8_t *)in.buffer;
380 io_hdr.dxfer_len = size;
381 memset(in.buffer, 0, in.size);
382
383 scsi_device * scsidev = get_tunnel_dev();
384 if (!scsidev->scsi_pass_through_and_check(&io_hdr, "sntrealtek_device::nvme_pass_through: "))
385 return set_err(scsidev->get_err());
386
387 //out.result = ?; // TODO
388 return true;
389}
390
391
392} // namespace snt
393
394using namespace snt;
395
397{
398 if (!scsidev)
399 throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0");
400
401 // Take temporary ownership of 'scsidev' to delete it on error
402 scsi_device_auto_ptr scsidev_holder(scsidev);
403 nvme_device * sntdev = 0;
404
405 // TODO: Remove this and adjust drivedb entry accordingly when no longer EXPERIMENTAL
406 if (!strcmp(type, "sntjmicron#please_try")) {
407 set_err(EINVAL, "USB to NVMe bridge [please try '-d sntjmicron' and report result to: "
408 PACKAGE_BUGREPORT "]");
409 return 0;
410 }
411
412 if (!strcmp(type, "sntasmedia")) {
413 // No namespace supported
414 sntdev = new sntasmedia_device(this, scsidev, type, 0xffffffff);
415 }
416
417 else if (!strncmp(type, "sntjmicron", 10)) {
418 int n1 = -1, n2 = -1, len = strlen(type);
419 unsigned nsid = 0; // invalid namespace id -> use default
420 sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2);
421 if (!(n1 == len || n2 == len)) {
422 set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type);
423 return 0;
424 }
425 sntdev = new sntjmicron_device(this, scsidev, type, nsid);
426 }
427
428 else if (!strcmp(type, "sntrealtek")) {
429 // No namespace supported
430 sntdev = new sntrealtek_device(this, scsidev, type, 0xffffffff);
431 }
432
433 else {
434 set_err(EINVAL, "Unknown SNT device type '%s'", type);
435 return 0;
436 }
437
438 // 'scsidev' is now owned by 'sntdev'
439 scsidev_holder.release();
440 return sntdev;
441}
Smart pointer class for device pointers.
device_type * release()
Return the pointer and release ownership.
NVMe device access.
unsigned get_nsid() const
Get namespace id.
bool set_nvme_err(nvme_cmd_out &out, unsigned status, const char *msg=0)
Set last error number and message if pass-through returns NVMe error status.
void set_nsid(unsigned nsid)
Set namespace id.
SCSI device access.
bool scsi_pass_through_and_check(scsi_cmnd_io *iop, const char *msg="")
Base class for all devices.
Definition: dev_interface.h:33
const error_info & get_err() const
Get last error info struct.
bool set_err(int no, const char *msg,...) __attribute_format_printf(3
Set last error number and message.
const char * get_info_name() const
Get informal name.
device_info & set_info()
R/W access to device info struct.
The platform interface abstraction.
virtual nvme_device * get_snt_device(const char *type, scsi_device *scsidev)
Return NVMe->SCSI filter for a SNT or USB 'type'.
Definition: scsinvme.cpp:396
bool set_err(int no, const char *msg,...) __attribute_format_printf(3
Set last error number and message.
sntasmedia_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:46
virtual bool nvme_pass_through(const nvme_cmd_in &in, nvme_cmd_out &out) override
NVMe pass through.
Definition: scsinvme.cpp:58
virtual ~sntasmedia_device()
Definition: scsinvme.cpp:54
virtual bool nvme_pass_through(const nvme_cmd_in &in, nvme_cmd_out &out) override
NVMe pass through.
Definition: scsinvme.cpp:184
virtual ~sntjmicron_device()
Definition: scsinvme.cpp:152
virtual bool open() override
Open device, return false on error.
Definition: scsinvme.cpp:156
sntjmicron_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:144
virtual ~sntrealtek_device()
Definition: scsinvme.cpp:336
virtual bool nvme_pass_through(const nvme_cmd_in &in, nvme_cmd_out &out) override
NVMe pass through.
Definition: scsinvme.cpp:340
sntrealtek_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:328
Implement a device by tunneling through another device.
Definition: dev_tunnelled.h:56
u8 cdb[16]
Definition: megaraid.h:21
u32 size
Definition: megaraid.h:0
@ nvme_admin_identify
Definition: nvmecmds.h:208
@ nvme_admin_get_log_page
Definition: nvmecmds.h:205
Definition: scsinvme.cpp:26
uint32_t nsid
#define SAT_ATA_PASSTHROUGH_12
Definition: scsicmds.h:101
#define DXFER_NONE
Definition: scsicmds.h:108
#define DXFER_FROM_DEVICE
Definition: scsicmds.h:109
#define DXFER_TO_DEVICE
Definition: scsicmds.h:110
#define SNT_JMICRON_CDB_LEN
Definition: scsinvme.cpp:118
#define SNT_JMICRON_NVM_CMD_LEN
Definition: scsinvme.cpp:119
const char * scsinvme_cpp_svnid
Definition: scsinvme.cpp:23
#define SNT_JMICRON_NVME_SIGNATURE
Definition: scsinvme.cpp:117
static void sg_put_unaligned_be24(uint32_t val, void *p)
Definition: sg_unaligned.h:364
static void sg_put_unaligned_le16(uint16_t val, void *p)
Definition: sg_unaligned.h:309
void pout(const char *fmt,...)
Definition: smartd.cpp:1333
NVMe pass through input parameters.
unsigned char direction() const
Get I/O direction from opcode.
unsigned cdw10
unsigned cdw13
unsigned cdw11
unsigned char opcode
Opcode (CDW0 07:00)
unsigned size
Size of buffer.
unsigned cdw14
unsigned cdw15
Cmd specific.
unsigned nsid
Namespace ID.
unsigned cdw12
void * buffer
Pointer to data buffer.
NVMe pass through output parameters.
unsigned result
Command specific result (DW0)
uint8_t * dxferp
Definition: scsicmds.h:121
int dxfer_dir
Definition: scsicmds.h:119
size_t cmnd_len
Definition: scsicmds.h:118
size_t dxfer_len
Definition: scsicmds.h:122
uint8_t * cmnd
Definition: scsicmds.h:117
std::string info_name
Informal name.
Definition: dev_interface.h:46
std::string strprintf(const char *fmt,...)
Definition: utility.cpp:775
bool isbigendian()
Definition: utility.h:81
void swapx(unsigned short *p)
Definition: utility.h:94