4 * Home page of code is: http://www.smartmontools.org
6 * Copyright (C) 2018 Harry Mallon <hjmallon@gmail.com>
8 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include "dev_interface.h"
15 #include "dev_tunnelled.h"
17 #include "sg_unaligned.h"
22 // SNT (SCSI NVMe Translation) namespace and prefix
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
29 class sntjmicron_device
30 : public tunnelled_device
<
31 /*implements*/ nvme_device
,
32 /*by tunnelling through a*/ scsi_device
36 sntjmicron_device(smart_interface
* intf
, scsi_device
* scsidev
,
37 const char * req_type
, unsigned nsid
);
39 virtual ~sntjmicron_device() throw();
43 virtual bool nvme_pass_through(const nvme_cmd_in
& in
, nvme_cmd_out
& out
);
47 proto_nvm_cmd
= 0x0, proto_non_data
= 0x1, proto_dma_in
= 0x2,
48 proto_dma_out
= 0x3, proto_response
= 0xF
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
)
57 set_info().info_name
= strprintf("%s [USB NVMe JMicron]", scsidev
->get_info_name());
60 sntjmicron_device::~sntjmicron_device() throw()
64 bool sntjmicron_device::open()
67 if (!tunnelled_device
<nvme_device
, scsi_device
>::open())
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
80 // cdb[0]: ATA PASS THROUGH (12) SCSI command opcode byte (0xa1)
81 // cdb[1]: [ is admin cmd: 1 ] [ protocol : 7 ]
83 // cdb[3]: parameter list length (23:16)
84 // cdb[4]: parameter list length (15:08)
85 // cdb[5]: parameter list length (07:00)
91 // cdb[11]: CONTROL (?)
92 bool sntjmicron_device::nvme_pass_through(const nvme_cmd_in
& in
, nvme_cmd_out
& out
)
94 /* Only admin commands used */
97 // 1: "NVM Command Set Payload"
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]);
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
121 for (unsigned i
= 0; i
< (SNT_JMICRON_NVM_CMD_LEN
/ sizeof(uint32_t)); i
++)
125 memset(&io_nvm
, 0, sizeof(io_nvm
));
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
;
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());
139 // 2: DMA or Non-Data
141 unsigned char cdb
[SNT_JMICRON_CDB_LEN
] = { 0 };
142 cdb
[0] = SAT_ATA_PASSTHROUGH_12
;
144 scsi_cmnd_io io_data
;
145 memset(&io_data
, 0, sizeof(io_data
));
147 io_data
.cmnd_len
= SNT_JMICRON_CDB_LEN
;
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
;
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
;
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
);
169 case nvme_cmd_in::data_io
:
171 return set_err(EINVAL
);
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());
180 // 3: "Return Response Information"
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]);
187 unsigned nvm_reply
[SNT_JMICRON_NVM_CMD_LEN
/ sizeof(unsigned)] = { 0 };
189 scsi_cmnd_io io_reply
;
190 memset(&io_reply
, 0, sizeof(io_reply
));
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
;
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());
204 for (unsigned i
= 0; i
< (SNT_JMICRON_NVM_CMD_LEN
/ sizeof(uint32_t)); i
++)
205 swapx(&nvm_reply
[i
]);
207 if (nvm_reply
[0] != SNT_JMICRON_NVME_SIGNATURE
)
208 return set_err(EIO
, "Out of spec JMicron NVMe reply");
210 int status
= nvm_reply
[5] >> 17;
213 return set_nvme_err(out
, status
);
215 out
.result
= nvm_reply
[2];
225 nvme_device
* smart_interface::get_snt_device(const char * type
, scsi_device
* scsidev
)
228 throw std::logic_error("smart_interface: get_snt_device() called with scsidev=0");
230 // Take temporary ownership of 'scsidev' to delete it on error
231 scsi_device_auto_ptr
scsidev_holder(scsidev
);
232 nvme_device
* sntdev
= 0;
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
"]");
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
);
249 sntdev
= new sntjmicron_device(this, scsidev
, type
, nsid
);
252 set_err(EINVAL
, "Unknown SNT device type '%s'", type
);
256 // 'scsidev' is now owned by 'sntdev'
257 scsidev_holder
.release();