smartmontools SVN Rev 5611
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 5568 2023-12-22 17:34:33Z chrfranke $";
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 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()) {
168 set_nsid(0xFFFFFFFF);
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 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
195 cdb[1] = (admin ? 0x80 : 0x00) | proto_nvm_cmd;
197
198 unsigned nvm_cmd[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
199 nvm_cmd[0] = SNT_JMICRON_NVME_SIGNATURE;
200 // nvm_cmd[1]: reserved
201 nvm_cmd[2] = in.opcode; // More of CDW0 may go in here in future
202 nvm_cmd[3] = in.nsid;
203 // nvm_cmd[4-5]: reserved
204 // nvm_cmd[6-7]: metadata pointer
205 // nvm_cmd[8-11]: data ptr (?)
206 nvm_cmd[12] = in.cdw10;
207 nvm_cmd[13] = in.cdw11;
208 nvm_cmd[14] = in.cdw12;
209 nvm_cmd[15] = in.cdw13;
210 nvm_cmd[16] = in.cdw14;
211 nvm_cmd[17] = in.cdw15;
212 // nvm_cmd[18-127]: reserved
213
214 if (isbigendian())
215 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
216 swapx(&nvm_cmd[i]);
217
218 scsi_cmnd_io io_nvm = {};
219
220 io_nvm.cmnd = cdb;
222 io_nvm.dxfer_dir = DXFER_TO_DEVICE;
223 io_nvm.dxferp = (uint8_t *)nvm_cmd;
225
226 scsi_device * scsidev = get_tunnel_dev();
227 if (!scsidev->scsi_pass_through_and_check(&io_nvm,
228 "sntjmicron_device::nvme_pass_through:NVM: "))
229 return set_err(scsidev->get_err());
230 }
231
232 // 2: DMA or Non-Data
233 {
234 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
236
237 scsi_cmnd_io io_data = {};
238 io_data.cmnd = cdb;
240
241 switch (in.direction()) {
243 cdb[1] = (admin ? 0x80 : 0x00) | proto_non_data;
244 io_data.dxfer_dir = DXFER_NONE;
245 break;
247 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_out;
249 io_data.dxfer_dir = DXFER_TO_DEVICE;
250 io_data.dxferp = (uint8_t *)in.buffer;
251 io_data.dxfer_len = in.size;
252 break;
254 cdb[1] = (admin ? 0x80 : 0x00) | proto_dma_in;
257 io_data.dxferp = (uint8_t *)in.buffer;
258 io_data.dxfer_len = in.size;
259 memset(in.buffer, 0, in.size);
260 break;
262 default:
263 return set_err(EINVAL);
264 }
265
266 scsi_device * scsidev = get_tunnel_dev();
267 if (!scsidev->scsi_pass_through_and_check(&io_data,
268 "sntjmicron_device::nvme_pass_through:Data: "))
269 return set_err(scsidev->get_err());
270 }
271
272 // 3: "Return Response Information"
273 {
274 unsigned char cdb[SNT_JMICRON_CDB_LEN] = { 0 };
276 cdb[1] = (admin ? 0x80 : 0x00) | proto_response;
278
279 unsigned nvm_reply[SNT_JMICRON_NVM_CMD_LEN / sizeof(unsigned)] = { 0 };
280
281 scsi_cmnd_io io_reply = {};
282
283 io_reply.cmnd = cdb;
284 io_reply.cmnd_len = SNT_JMICRON_CDB_LEN;
285 io_reply.dxfer_dir = DXFER_FROM_DEVICE;
286 io_reply.dxferp = (uint8_t *)nvm_reply;
288
289 scsi_device * scsidev = get_tunnel_dev();
290 if (!scsidev->scsi_pass_through_and_check(&io_reply,
291 "sntjmicron_device::nvme_pass_through:Reply: "))
292 return set_err(scsidev->get_err());
293
294 if (isbigendian())
295 for (unsigned i = 0; i < (SNT_JMICRON_NVM_CMD_LEN / sizeof(uint32_t)); i++)
296 swapx(&nvm_reply[i]);
297
298 if (nvm_reply[0] != SNT_JMICRON_NVME_SIGNATURE)
299 return set_err(EIO, "Out of spec JMicron NVMe reply");
300
301 int status = nvm_reply[5] >> 17;
302
303 if (status > 0)
304 return set_nvme_err(out, status);
305
306 out.result = nvm_reply[2];
307 }
308
309 return true;
310}
311
312/////////////////////////////////////////////////////////////////////////////
313// sntrealtek_device
314
316: public tunnelled_device<
317 /*implements*/ nvme_device,
318 /*by tunnelling through a*/ scsi_device
319 >
320{
321public:
323 const char * req_type, unsigned nsid);
324
325 virtual ~sntrealtek_device();
326
327 virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) override;
328};
329
331 const char * req_type, unsigned nsid)
332: smart_device(intf, scsidev->get_dev_name(), "sntrealtek", req_type),
334{
335 set_info().info_name = strprintf("%s [USB NVMe Realtek]", scsidev->get_info_name());
336}
337
339{
340}
341
343{
344 unsigned size = in.size;
345 switch (in.opcode) {
347 if (in.cdw10 == 0x0000001) // Identify controller
348 break;
349 if (in.cdw10 == 0x0000000) { // Identify namespace
350 if (in.nsid == 1)
351 break;
352 return set_err(ENOSYS, "NVMe Identify Namespace 0x%x not supported", in.nsid);
353 }
354 return set_err(ENOSYS, "NVMe Identify with CDW10=0x%08x not supported", in.cdw10);
356 if (!(in.nsid == 0xffffffff || !in.nsid))
357 return set_err(ENOSYS, "NVMe Get Log Page with NSID=0x%x not supported", in.nsid);
358 if (size > 0x200) { // Reading more apparently returns old data from previous command
359 // TODO: Add ability to return short reads to caller
360 size = 0x200;
361 pout("Warning: NVMe Get Log truncated to 0x%03x bytes, 0x%03x bytes zero filled\n", size, in.size - size);
362 }
363 break;
364 default:
365 return set_err(ENOSYS, "NVMe admin command 0x%02x not supported", in.opcode);
366 break;
367 }
368 if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15)
369 return set_err(ENOSYS, "Nonzero NVMe command dwords 11-15 not supported");
370
371 uint8_t cdb[16] = {0, };
372 cdb[0] = 0xe4;
374 cdb[3] = in.opcode;
375 cdb[4] = (uint8_t)in.cdw10;
376
377 scsi_cmnd_io io_hdr = {};
378 io_hdr.cmnd = cdb;
379 io_hdr.cmnd_len = sizeof(cdb);
380 io_hdr.dxfer_dir = DXFER_FROM_DEVICE;
381 io_hdr.dxferp = (uint8_t *)in.buffer;
382 io_hdr.dxfer_len = size;
383 memset(in.buffer, 0, in.size);
384
385 scsi_device * scsidev = get_tunnel_dev();
386 if (!scsidev->scsi_pass_through_and_check(&io_hdr, "sntrealtek_device::nvme_pass_through: "))
387 return set_err(scsidev->get_err());
388
389 //out.result = ?; // TODO
390 return true;
391}
392
393
394} // namespace snt
395
396using namespace snt;
397
399{
400 if (!scsidev)
401 throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0");
402
403 // Take temporary ownership of 'scsidev' to delete it on error
404 scsi_device_auto_ptr scsidev_holder(scsidev);
405 nvme_device * sntdev = 0;
406
407 // TODO: Remove this and adjust drivedb entry accordingly when no longer EXPERIMENTAL
408 if (!strcmp(type, "sntjmicron#please_try")) {
409 set_err(EINVAL, "USB to NVMe bridge [please try '-d sntjmicron' and report result to: "
410 PACKAGE_BUGREPORT "]");
411 return 0;
412 }
413
414 if (!strcmp(type, "sntasmedia")) {
415 // No namespace supported
416 sntdev = new sntasmedia_device(this, scsidev, type, 0xffffffff);
417 }
418
419 else if (!strncmp(type, "sntjmicron", 10)) {
420 int n1 = -1, n2 = -1, len = strlen(type);
421 unsigned nsid = 0; // invalid namespace id -> use default
422 sscanf(type, "sntjmicron%n,0x%x%n", &n1, &nsid, &n2);
423 if (!(n1 == len || n2 == len)) {
424 set_err(EINVAL, "Invalid NVMe namespace id in '%s'", type);
425 return 0;
426 }
427 sntdev = new sntjmicron_device(this, scsidev, type, nsid);
428 }
429
430 else if (!strcmp(type, "sntrealtek")) {
431 // No namespace supported
432 sntdev = new sntrealtek_device(this, scsidev, type, 0xffffffff);
433 }
434
435 else {
436 set_err(EINVAL, "Unknown SNT device type '%s'", type);
437 return 0;
438 }
439
440 // 'scsidev' is now owned by 'sntdev'
441 scsidev_holder.release();
442 return sntdev;
443}
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:398
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:338
virtual bool nvme_pass_through(const nvme_cmd_in &in, nvme_cmd_out &out) override
NVMe pass through.
Definition: scsinvme.cpp:342
sntrealtek_device(smart_interface *intf, scsi_device *scsidev, const char *req_type, unsigned nsid)
Definition: scsinvme.cpp:330
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 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: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:1338
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