smartmontools SVN Rev 5650
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 5650 2025-01-15 10:31:46Z samm2 $";
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 == nvme_broadcast_nsid || !in.nsid))
74 return set_err(ENOSYS, "NVMe Get Log Page with NSID=0x%x not supported", in.nsid);
75 break;
76 default:
77 return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
78 break;
79 }
80 if (in.cdw11 || in.cdw14 || in.cdw15)
81 return set_err(ENOSYS, "Nonzero NVMe command dwords 11, 14, or 15 not supported");
82
83 uint8_t cdb[16] = {0, };
84 cdb[0] = 0xe6;
85 cdb[1] = in.opcode;
86 //cdb[2] = 0
87 cdb[3] = (uint8_t)in.cdw10;
88 //cdb[4..5] = 0
89 cdb[6] = (uint8_t)(cdw10_hi >> 8);
90 cdb[7] = (uint8_t)cdw10_hi;
91 cdb[8] = (uint8_t)(in.cdw13 >> 24);
92 cdb[9] = (uint8_t)(in.cdw13 >> 16);
93 cdb[10] = (uint8_t)(in.cdw13 >> 8);
94 cdb[11] = (uint8_t)in.cdw13;
95 cdb[12] = (uint8_t)(in.cdw12 >> 24);
96 cdb[13] = (uint8_t)(in.cdw12 >> 16);
97 cdb[14] = (uint8_t)(in.cdw12 >> 8);
98 cdb[15] = (uint8_t)in.cdw12;
99
100 scsi_cmnd_io io_hdr = {};
101 io_hdr.cmnd = cdb;
102 io_hdr.cmnd_len = sizeof(cdb);
103 io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
104 io_hdr.dxferp = (uint8_t *)in.buffer;
105 io_hdr.dxfer_len = size;
106 memset(in.buffer, 0, in.size);
107
108 scsi_device * scsidev = get_tunnel_dev();
109 if (!scsidev->scsi_pass_through_and_check(&io_hdr, "sntasmedia_device::nvme_pass_through: "))
110 return set_err(scsidev->get_err());
111
112 //out.result = ?;
113 return true;
114}
115
116/////////////////////////////////////////////////////////////////////////////
117// sntjmicron_device
118
119#define SNT_JMICRON_NVME_SIGNATURE 0x454d564eu // 'NVME' reversed (little endian)
120#define SNT_JMICRON_CDB_LEN 12
121#define SNT_JMICRON_NVM_CMD_LEN 512
122
124: public tunnelled_device<
125 /*implements*/ nvme_device,
126 /*by tunnelling through a*/ scsi_device
127 >
128{
129public:
131 const char * req_type, unsigned nsid);
132
133 virtual ~sntjmicron_device();
134
135 virtual bool open() override;
136
137 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
138
139private:
140 enum {
143 };
144};
145
147 const char * req_type, unsigned nsid)
148: smart_device(intf, scsidev->get_dev_name(), "sntjmicron", req_type),
150{
151 set_info().info_name = strprintf("%s [USB NVMe JMicron]", scsidev->get_info_name());
152}
153
155{
156}
157
159{
160 // Open USB first
162 return false;
163
164 // No sure how multiple namespaces come up on device so we
165 // cannot detect e.g. /dev/sdX is NSID 2.
166 // Set to broadcast if not available
167 if (!get_nsid()) {
169 }
170
171 return true;
172}
173
174// cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1)
175// cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ]
176// cdb[2]: reserved
177// cdb[3]: parameter list length (23:16)
178// cdb[4]: parameter list length (15:08)
179// cdb[5]: parameter list length (07:00)
180// cdb[6]: reserved
181// cdb[7]: reserved
182// cdb[8]: reserved
183// cdb[9]: reserved
184// cdb[10]: reserved
185// cdb[11]: CONTROL (?)
187{
188 /* Only admin commands used */
189 constexpr bool admin = true;
190
191 // 1: "NVM Command Set Payload"
192 {
193 // for whatever reason selftest log causing controller to hang if size is set > 0x230b
194 // see GH issue #256 for the details. Patching it to include last 19 log records instead
195 unsigned cdw10 = in.cdw10;
197 unsigned int lid = in.cdw10 & 0xFF;
198 if (lid == 0x6 && in.size > 0x218) {
199 unsigned size = 0x218;
200 cdw10 = lid | ((size/4 - 1) << 16);
201 pout("Warning: self-test output truncated to 19 items to workaround controller bug\n");
202 }
203 }
204 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
206 cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd;
208
209 unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
210 nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE;
211 // nvm_cmd[1]: reserved
212 nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future
213 nvm_cmd[3] = in.nsid;
214 // nvm_cmd[4-5]: reserved
215 // nvm_cmd[6-7]: metadata pointer
216 // nvm_cmd[8-11]: data ptr (?)
217 nvm_cmd[12] = cdw10;
218 nvm_cmd[13] = in.cdw11;
219 nvm_cmd[14] = in.cdw12;
220 nvm_cmd[15] = in.cdw13;
221 nvm_cmd[16] = in.cdw14;
222 nvm_cmd[17] = in.cdw15;
223 // nvm_cmd[18-127]: reserved
224
225 if (isbigendian())
226 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
227 swapx(&nvm_cmd[i]);
228
229 scsi_cmnd_io io_nvm = {};
230
231 io_nvm.cmnd = cdb;
233 io_nvm.dxfer_dir = DXFER_TO_DEVICE;
234 io_nvm.dxferp = (uint8_t *)nvm_cmd;
236
237 scsi_device * scsidev = get_tunnel_dev();
238 if (!scsidev->scsi_pass_through_and_check(&io_nvm,
239 "sntjmicron_device::nvme_pass_through:NVM: "))
240 return set_err(scsidev->get_err());
241 }
242
243 // 2: DMA or Non-Data
244 {
245 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
247
248 scsi_cmnd_io io_data = {};
249 io_data.cmnd = cdb;
251
252 switch (in.direction()) {
254 cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data;
255 io_data.dxfer_dir = DXFER_NONE;
256 break;
258 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out;
260 io_data.dxfer_dir = DXFER_TO_DEVICE;
261 io_data.dxferp = (uint8_t *)in.buffer;
262 io_data.dxfer_len = in.size;
263 break;
265 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in;
268 io_data.dxferp = (uint8_t *)in.buffer;
269 io_data.dxfer_len = in.size;
270 memset(in.buffer, 0, in.size);
271 break;
273 default:
274 return set_err(EINVAL);
275 }
276
277 scsi_device * scsidev = get_tunnel_dev();
278 if (!scsidev->scsi_pass_through_and_check(&io_data,
279 "sntjmicron_device::nvme_pass_through:Data: "))
280 return set_err(scsidev->get_err());
281 }
282
283 // 3: "Return Response Information"
284 {
285 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
287 cdb[1] = (admin ? 0x80 : 0x00) | proto_response;
289
290 unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
291
292 scsi_cmnd_io io_reply = {};
293
294 io_reply.cmnd = cdb;
295 io_reply.cmnd_len = SNT_JMICRON_CDB_LEN;
296 io_reply.dxfer_dir = DXFER_FROM_DEVICE;
297 io_reply.dxferp = (uint8_t *)nvm_reply;
299
300 scsi_device * scsidev = get_tunnel_dev();
301 if (!scsidev->scsi_pass_through_and_check(&io_reply,
302 "sntjmicron_device::nvme_pass_through:Reply: "))
303 return set_err(scsidev->get_err());
304
305 if (isbigendian())
306 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
307 swapx(&nvm_reply[i]);
308
309 if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE)
310 return set_err(EIO, "Out of spec JMicron NVMe reply");
311
312 int status = nvm_reply[5] >> 17;
313
314 if (status > 0)
315 return set_nvme_err(out, status);
316
317 out.result = nvm_reply[2];
318 }
319
320 return true;
321}
322
323/////////////////////////////////////////////////////////////////////////////
324// sntrealtek_device
325
327: public tunnelled_device<
328 /*implements*/ nvme_device,
329 /*by tunnelling through a*/ scsi_device
330 >
331{
332public:
334 const char * req_type, unsigned nsid);
335
336 virtual ~sntrealtek_device();
337
338 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
339};
340
342 const char * req_type, unsigned nsid)
343: smart_device(intf, scsidev->get_dev_name(), "sntrealtek", req_type),
345{
346 set_info().info_name = strprintf("%s [USB NVMe Realtek]", scsidev->get_info_name());
347}
348
350{
351}
352
354{
355 unsigned size = in.size;
356 switch (in.opcode) {
358 if (in.cdw10 == 0x0000001) // Identify controller
359 break;
360 if (in.cdw10 == 0x0000000) { // Identify namespace
361 if (in.nsid == 1)
362 break;
363 return set_err(ENOSYS, "NVMe Identify Namespace 0x%x not supported", in.nsid);
364 }
365 return set_err(ENOSYS, "NVMe Identify with CDW10=0x%08x not supported", in.cdw10);
367 if (!(in.nsid == nvme_broadcast_nsid || !in.nsid))
368 return set_err(ENOSYS, "NVMe Get Log Page with NSID=0x%x not supported", in.nsid);
369 if (size > 0x200) { // Reading more apparently returns old data from previous command
370 // TODO: Add ability to return short reads to caller
371 size = 0x200;
372 pout("Warning: NVMe Get Log truncated to 0x%03x bytes, 0x%03x bytes zero filled\n", size, in.size - size);
373 }
374 break;
375 default:
376 return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
377 break;
378 }
379 if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15)
380 return set_err(ENOSYS, "Nonzero NVMe command dwords 11-15 not supported");
381
382 uint8_t cdb[16] = {0, };
383 cdb[0] = 0xe4;
385 cdb[3] = in.opcode;
386 cdb[4] = (uint8_t)in.cdw10;
387
388 scsi_cmnd_io io_hdr = {};
389 io_hdr.cmnd = cdb;
390 io_hdr.cmnd_len = sizeof(cdb);
391 io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
392 io_hdr.dxferp = (uint8_t *)in.buffer;
393 io_hdr.dxfer_len = size;
394 memset(in.buffer, 0, in.size);
395
396 scsi_device * scsidev = get_tunnel_dev();
397 if (!scsidev->scsi_pass_through_and_check(&io_hdr, "sntrealtek_device::nvme_pass_through: "))
398 return set_err(scsidev->get_err());
399
400 //out.result = ?; // TODO
401 return true;
402}
403
404
405} // namespace snt
406
407using namespace snt;
408
410{
411 if (!scsidev)
412 throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0");
413
414 // Take temporary ownership of 'scsidev' to delete it on error
415 scsi_device_auto_ptr scsidev_holder(scsidev);
416 nvme_device * sntdev = 0;
417
418 // TODO: Remove this and adjust drivedb entry accordingly when no longer EXPERIMENTAL
419 if (!strcmp(type, "sntjmicron#please_try")) {
420 set_err(EINVAL, "USB to NVMe bridge [please try '-d sntjmicron' and report result to: "
421 PACKAGE_BUGREPORT "]");
422 return 0;
423 }
424
425 if (!strcmp(type, "sntasmedia")) {
426 // No namespace supported
427 sntdev = new sntasmedia_device(this, scsidev, type, nvme_broadcast_nsid);
428 }
429
430 else if (!strncmp(type, "sntjmicron", 10)) {
431 int n1 = -1, n2 = -1, len = strlen(type);
432 unsigned nsid = 0; // invalid namespace id -> use default
433 sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2);
434 if (!(n1 == len || n2 == len)) {
435 set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type);
436 return 0;
437 }
438 sntdev = new sntjmicron_device(this, scsidev, type, nsid);
439 }
440
441 else if (!strcmp(type, "sntrealtek")) {
442 // No namespace supported
443 sntdev = new sntrealtek_device(this, scsidev, type, nvme_broadcast_nsid);
444 }
445
446 else {
447 set_err(EINVAL, "Unknown SNT device type '%s'", type);
448 return 0;
449 }
450
451 // 'scsidev' is now owned by 'sntdev'
452 scsidev_holder.release();
453 return sntdev;
454}
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:409
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:186
virtual ~sntjmicron_device()
Definition: scsinvme.cpp:154
virtual bool open() override
Open device, return false on error.
Definition: scsinvme.cpp:158
sntjmicron_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:146
virtual ~sntrealtek_device()
Definition: scsinvme.cpp:349
virtual bool nvme_pass_through(const nvme_cmd_in &in, nvme_cmd_out &out) override
NVMe pass through.
Definition: scsinvme.cpp:353
sntrealtek_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:341
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:210
@ nvme_admin_get_log_page
Definition: nvmecmds.h:207
Definition: scsinvme.cpp:26
uint32_t cdw10
uint32_t nsid
constexpr uint32_t nvme_broadcast_nsid
Definition: nvmecmds.h:257
#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:120
#define SNT_JMICRON_NVM_CMD_LEN
Definition: scsinvme.cpp:121
const char * scsinvme_cpp_svnid
Definition: scsinvme.cpp:23
#define SNT_JMICRON_NVME_SIGNATURE
Definition: scsinvme.cpp:119
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:1347
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:799
bool isbigendian()
Definition: utility.h:82
void swapx(unsigned short *p)
Definition: utility.h:95