]>
Commit | Line | Data |
---|---|---|
a86ec89e GI |
1 | /* |
2 | * nvmecmds.cpp | |
3 | * | |
4 | * Home page of code is: http://www.smartmontools.org | |
5 | * | |
6 | * Copyright (C) 2016 Christian Franke | |
7 | * | |
ff28b140 | 8 | * SPDX-License-Identifier: GPL-2.0-or-later |
a86ec89e GI |
9 | */ |
10 | ||
11 | #include "config.h" | |
12 | #include "nvmecmds.h" | |
13 | ||
ff28b140 | 14 | const char * nvmecmds_cvsid = "$Id: nvmecmds.cpp 4760 2018-08-19 18:45:53Z chrfranke $" |
a86ec89e GI |
15 | NVMECMDS_H_CVSID; |
16 | ||
17 | #include "dev_interface.h" | |
18 | #include "atacmds.h" // swapx(), ASSERT_*(), dont_print_serial_number | |
19 | #include "scsicmds.h" // dStrHex() | |
20 | #include "utility.h" | |
21 | ||
22 | using namespace smartmontools; | |
23 | ||
24 | // Check nvme_* struct sizes | |
25 | ASSERT_SIZEOF_STRUCT(nvme_id_ctrl, 4096); | |
26 | ASSERT_SIZEOF_STRUCT(nvme_id_ns, 4096); | |
27 | ASSERT_SIZEOF_STRUCT(nvme_error_log_page, 64); | |
28 | ASSERT_SIZEOF_STRUCT(nvme_smart_log, 512); | |
29 | ||
30 | ||
31 | // Print NVMe debug messages? | |
32 | unsigned char nvme_debugmode = 0; | |
33 | ||
34 | // Dump up to 4096 bytes, do not dump trailing zero bytes. | |
35 | // TODO: Handle this by new unified function in utility.cpp | |
36 | static void debug_hex_dump(const void * data, unsigned size) | |
37 | { | |
38 | const unsigned char * p = (const unsigned char *)data; | |
39 | const unsigned limit = 4096; // sizeof(nvme_id_ctrl) | |
40 | unsigned sz = (size <= limit ? size : limit); | |
41 | ||
42 | while (sz > 0x10 && !p[sz-1]) | |
43 | sz--; | |
44 | if (sz < size) { | |
45 | if (sz & 0x0f) | |
46 | sz = (sz & ~0x0f) + 0x10; | |
47 | sz += 0x10; | |
48 | if (sz > size) | |
49 | sz = size; | |
50 | } | |
51 | ||
ff28b140 | 52 | dStrHex((const uint8_t *)p, sz, 0); |
a86ec89e GI |
53 | if (sz < size) |
54 | pout(" ...\n"); | |
55 | } | |
56 | ||
57 | // Call NVMe pass-through and print debug info if requested. | |
58 | static bool nvme_pass_through(nvme_device * device, const nvme_cmd_in & in, | |
59 | nvme_cmd_out & out) | |
60 | { | |
61 | int64_t start_usec = -1; | |
62 | ||
63 | if (nvme_debugmode) { | |
64 | pout(" [NVMe call: opcode=0x%02x, size=0x%04x, nsid=0x%08x, cdw10=0x%08x", | |
65 | in.opcode, in.size, in.nsid, in.cdw10); | |
66 | if (in.cdw11 || in.cdw12 || in.cdw13 || in.cdw14 || in.cdw15) | |
67 | pout(",\n cdw1x=0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x", | |
68 | in.cdw11, in.cdw12, in.cdw13, in.cdw14, in.cdw15); | |
69 | pout("]\n"); | |
70 | ||
71 | start_usec = smi()->get_timer_usec(); | |
72 | } | |
73 | ||
74 | bool ok = device->nvme_pass_through(in, out); | |
75 | ||
76 | if ( dont_print_serial_number && ok | |
77 | && in.opcode == nvme_admin_identify && in.cdw10 == 0x01) { | |
78 | // Invalidate serial number | |
79 | nvme_id_ctrl & id_ctrl = *reinterpret_cast<nvme_id_ctrl *>(in.buffer); | |
80 | memset(id_ctrl.sn, 'X', sizeof(id_ctrl.sn)); | |
81 | } | |
82 | ||
83 | if (nvme_debugmode) { | |
84 | if (start_usec >= 0) { | |
85 | int64_t duration_usec = smi()->get_timer_usec() - start_usec; | |
86 | if (duration_usec >= 500) | |
87 | pout(" [Duration: %.3fs]\n", duration_usec / 1000000.0); | |
88 | } | |
89 | ||
90 | if (!ok) { | |
91 | pout(" [NVMe call failed: "); | |
92 | if (out.status_valid) | |
93 | pout("NVMe Status=0x%04x", out.status); | |
94 | else | |
95 | pout("%s", device->get_errmsg()); | |
96 | } | |
97 | else { | |
98 | pout(" [NVMe call succeeded: result=0x%08x", out.result); | |
99 | if (nvme_debugmode > 1 && in.direction() == nvme_cmd_in::data_in) { | |
100 | pout("\n"); | |
101 | debug_hex_dump(in.buffer, in.size); | |
102 | pout(" "); | |
103 | } | |
104 | } | |
105 | pout("]\n"); | |
106 | } | |
107 | ||
108 | return ok; | |
109 | } | |
110 | ||
111 | // Call NVMe pass-through and print debug info if requested. | |
112 | // Version without output parameters. | |
113 | static bool nvme_pass_through(nvme_device * device, const nvme_cmd_in & in) | |
114 | { | |
115 | nvme_cmd_out out; | |
116 | return nvme_pass_through(device, in, out); | |
117 | } | |
118 | ||
119 | // Read NVMe identify info with controller/namespace field CNS. | |
120 | static bool nvme_read_identify(nvme_device * device, unsigned nsid, | |
121 | unsigned char cns, void * data, unsigned size) | |
122 | { | |
123 | memset(data, 0, size); | |
124 | nvme_cmd_in in; | |
125 | in.set_data_in(nvme_admin_identify, data, size); | |
126 | in.nsid = nsid; | |
127 | in.cdw10 = cns; | |
128 | ||
129 | return nvme_pass_through(device, in); | |
130 | } | |
131 | ||
132 | // Read NVMe Identify Controller data structure. | |
133 | bool nvme_read_id_ctrl(nvme_device * device, nvme_id_ctrl & id_ctrl) | |
134 | { | |
135 | if (!nvme_read_identify(device, 0, 0x01, &id_ctrl, sizeof(id_ctrl))) | |
136 | return false; | |
137 | ||
138 | if (isbigendian()) { | |
139 | swapx(&id_ctrl.vid); | |
140 | swapx(&id_ctrl.ssvid); | |
141 | swapx(&id_ctrl.cntlid); | |
142 | swapx(&id_ctrl.oacs); | |
143 | swapx(&id_ctrl.wctemp); | |
144 | swapx(&id_ctrl.cctemp); | |
145 | swapx(&id_ctrl.mtfa); | |
146 | swapx(&id_ctrl.hmpre); | |
147 | swapx(&id_ctrl.hmmin); | |
148 | swapx(&id_ctrl.rpmbs); | |
149 | swapx(&id_ctrl.nn); | |
150 | swapx(&id_ctrl.oncs); | |
151 | swapx(&id_ctrl.fuses); | |
152 | swapx(&id_ctrl.awun); | |
153 | swapx(&id_ctrl.awupf); | |
154 | swapx(&id_ctrl.acwu); | |
155 | swapx(&id_ctrl.sgls); | |
156 | for (int i = 0; i < 32; i++) { | |
157 | swapx(&id_ctrl.psd[i].max_power); | |
158 | swapx(&id_ctrl.psd[i].entry_lat); | |
159 | swapx(&id_ctrl.psd[i].exit_lat); | |
160 | swapx(&id_ctrl.psd[i].idle_power); | |
161 | swapx(&id_ctrl.psd[i].active_power); | |
162 | } | |
163 | } | |
164 | ||
165 | return true; | |
166 | } | |
167 | ||
168 | // Read NVMe Identify Namespace data structure for namespace NSID. | |
169 | bool nvme_read_id_ns(nvme_device * device, unsigned nsid, nvme_id_ns & id_ns) | |
170 | { | |
171 | if (!nvme_read_identify(device, nsid, 0x00, &id_ns, sizeof(id_ns))) | |
172 | return false; | |
173 | ||
174 | if (isbigendian()) { | |
175 | swapx(&id_ns.nsze); | |
176 | swapx(&id_ns.ncap); | |
177 | swapx(&id_ns.nuse); | |
178 | swapx(&id_ns.nawun); | |
179 | swapx(&id_ns.nawupf); | |
180 | swapx(&id_ns.nacwu); | |
181 | swapx(&id_ns.nabsn); | |
182 | swapx(&id_ns.nabo); | |
183 | swapx(&id_ns.nabspf); | |
184 | for (int i = 0; i < 16; i++) | |
185 | swapx(&id_ns.lbaf[i].ms); | |
186 | } | |
187 | ||
188 | return true; | |
189 | } | |
190 | ||
191 | // Read NVMe log page with identifier LID. | |
ff28b140 TL |
192 | bool nvme_read_log_page(nvme_device * device, unsigned char lid, void * data, |
193 | unsigned size, bool broadcast_nsid) | |
a86ec89e GI |
194 | { |
195 | if (!(4 <= size && size <= 0x4000 && (size % 4) == 0)) | |
196 | throw std::logic_error("nvme_read_log_page(): invalid size"); | |
197 | ||
198 | memset(data, 0, size); | |
199 | nvme_cmd_in in; | |
200 | in.set_data_in(nvme_admin_get_log_page, data, size); | |
ff28b140 | 201 | in.nsid = broadcast_nsid ? 0xffffffff : device->get_nsid(); |
a86ec89e GI |
202 | in.cdw10 = lid | (((size / 4) - 1) << 16); |
203 | ||
204 | return nvme_pass_through(device, in); | |
205 | } | |
206 | ||
207 | // Read NVMe Error Information Log. | |
208 | bool nvme_read_error_log(nvme_device * device, nvme_error_log_page * error_log, unsigned num_entries) | |
209 | { | |
ff28b140 | 210 | if (!nvme_read_log_page(device, 0x01, error_log, num_entries * sizeof(*error_log), true)) |
a86ec89e GI |
211 | return false; |
212 | ||
213 | if (isbigendian()) { | |
214 | for (unsigned i = 0; i < num_entries; i++) { | |
215 | swapx(&error_log[i].error_count); | |
216 | swapx(&error_log[i].sqid); | |
217 | swapx(&error_log[i].cmdid); | |
218 | swapx(&error_log[i].status_field); | |
219 | swapx(&error_log[i].parm_error_location); | |
220 | swapx(&error_log[i].lba); | |
221 | swapx(&error_log[i].nsid); | |
222 | } | |
223 | } | |
224 | ||
225 | return true; | |
226 | } | |
227 | ||
228 | // Read NVMe SMART/Health Information log. | |
229 | bool nvme_read_smart_log(nvme_device * device, nvme_smart_log & smart_log) | |
230 | { | |
ff28b140 | 231 | if (!nvme_read_log_page(device, 0x02, &smart_log, sizeof(smart_log), true)) |
a86ec89e GI |
232 | return false; |
233 | ||
234 | if (isbigendian()) { | |
235 | swapx(&smart_log.warning_temp_time); | |
236 | swapx(&smart_log.critical_comp_time); | |
237 | for (int i = 0; i < 8; i++) | |
238 | swapx(&smart_log.temp_sensor[i]); | |
239 | } | |
240 | ||
241 | return true; | |
242 | } |