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