blob: 228743185603b2b5158c1144728b44226a7359f4 [file] [log] [blame]
Andrew Jefferye3e3c972021-05-26 14:37:07 +09301#include "NVMeBasicContext.hpp"
2
3#include <endian.h>
4#include <sys/ioctl.h>
5#include <unistd.h>
6
7#include <boost/asio/read.hpp>
8#include <boost/asio/streambuf.hpp>
9#include <boost/asio/write.hpp>
10
11#include <cassert>
12#include <cerrno>
13#include <cinttypes>
14#include <cstdio>
15#include <cstring>
16#include <system_error>
17#include <thread>
18
19extern "C"
20{
21#include <i2c/smbus.h>
22#include <linux/i2c-dev.h>
23}
24
25/*
26 * NVMe-MI Basic Management Command
27 *
28 * https://nvmexpress.org/wp-content/uploads/NVMe_Management_-_Technical_Note_on_Basic_Management_Command.pdf
29 */
30
31static std::shared_ptr<std::array<uint8_t, 6>>
32 encodeBasicQuery(int bus, uint8_t device, uint8_t offset)
33{
34 if (bus < 0)
35 {
36 throw std::domain_error("Invalid bus argument");
37 }
38
39 /* bus + address + command */
40 uint32_t busle = htole32(static_cast<uint32_t>(bus));
41 auto command =
42 std::make_shared<std::array<uint8_t, sizeof(busle) + 1 + 1>>();
43 memcpy(command->data(), &busle, sizeof(busle));
44 (*command)[sizeof(busle) + 0] = device;
45 (*command)[sizeof(busle) + 1] = offset;
46
47 return command;
48}
49
50static void decodeBasicQuery(const std::array<uint8_t, 6>& req, int& bus,
51 uint8_t& device, uint8_t& offset)
52{
53 uint32_t busle;
54
55 memcpy(&busle, req.data(), sizeof(busle));
56 bus = le32toh(busle);
57 device = req[sizeof(busle) + 0];
58 offset = req[sizeof(busle) + 1];
59}
60
61static ssize_t execBasicQuery(int bus, uint8_t addr, uint8_t cmd,
62 std::vector<uint8_t>& resp)
63{
64 char devpath[PATH_MAX]{};
65 int32_t size;
66
67 ssize_t rc = snprintf(devpath, sizeof(devpath), "/dev/i2c-%" PRIu32, bus);
68 if ((size_t)rc > sizeof(devpath))
69 {
70 std::cerr << "Failed to format device path for bus " << bus << "\n";
71 return -EINVAL;
72 }
73
74 int dev = ::open(devpath, O_RDWR);
75 if (dev == -1)
76 {
77 std::cerr << "Failed to open bus device " << devpath << ": "
78 << strerror(errno) << "\n";
79 return -errno;
80 }
81
82 /* Select the target device */
83 if (::ioctl(dev, I2C_SLAVE, addr) == -1)
84 {
85 rc = -errno;
86 std::cerr << "Failed to configure device address: " << strerror(errno)
87 << "\n";
88 goto cleanup_fds;
89 }
90
91 resp.reserve(UINT8_MAX + 1);
92
93 /* Issue the NVMe MI basic command */
94 size = i2c_smbus_read_block_data(dev, cmd, resp.data());
95 if (size < 0)
96 {
97 rc = size;
98 std::cerr << "Failed to read block data: " << strerror(errno) << "\n";
99 goto cleanup_fds;
100 }
101 else if (size > UINT8_MAX + 1)
102 {
103 rc = -EBADMSG;
104 std::cerr << "Unexpected message length: " << size << " (" << UINT8_MAX
105 << ")\n";
106 goto cleanup_fds;
107 }
108
109 rc = size;
110
111cleanup_fds:
112 if (::close(dev) == -1)
113 {
114 std::cerr << "Failed to close device descriptor: " << strerror(errno)
115 << "\n";
116 }
117
118 return rc;
119}
120
121static ssize_t processBasicQueryStream(int in, int out)
122{
123 std::vector<uint8_t> resp{};
124 ssize_t rc;
125
126 while (true)
127 {
128 uint8_t device;
129 uint8_t offset;
130 uint8_t len;
131 int bus;
132
133 /* bus + address + command */
134 std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
135
136 /* Read the command parameters */
137 if ((rc = ::read(in, req.data(), req.size())) !=
138 static_cast<ssize_t>(req.size()))
139 {
140 assert(rc < 1);
141 rc = rc ? -errno : -EIO;
142 if (errno)
143 {
144 std::cerr << "Failed to read request: " << strerror(errno)
145 << "\n";
146 }
147 goto done;
148 }
149
150 decodeBasicQuery(req, bus, device, offset);
151
152 /* Execute the query */
153 rc = execBasicQuery(bus, device, offset, resp);
154
155 /* Bounds check the response */
156 if (rc < 0)
157 {
158 len = 0;
159 }
160 else if (rc > UINT8_MAX)
161 {
162 assert(rc == UINT8_MAX + 1);
163
164 /* YOLO: Lop off the PEC */
165 len = UINT8_MAX;
166 }
167 else
168 {
169 len = rc;
170 }
171
172 /* Write out the response length */
173 if ((rc = ::write(out, &len, sizeof(len))) != sizeof(len))
174 {
175 assert(rc < 1);
176 rc = rc ? -errno : -EIO;
177 std::cerr << "Failed to write block length to out pipe: "
178 << strerror(-rc) << "\n";
179 goto done;
180 }
181
182 /* Write out the response data */
183 uint8_t* cursor = resp.data();
184 while (len > 0)
185 {
186 ssize_t egress;
187
188 if ((egress = ::write(out, cursor, len)) == -1)
189 {
190 rc = -errno;
191 std::cerr << "Failed to write block data to out pipe: "
192 << strerror(errno) << "\n";
193 goto done;
194 }
195
196 cursor += egress;
197 len -= egress;
198 }
199 }
200
201done:
202 if (::close(in) == -1)
203 {
204 std::cerr << "Failed to close in descriptor: " << strerror(errno)
205 << "\n";
206 }
207
208 if (::close(out) == -1)
209 {
210 std::cerr << "Failed to close out descriptor: " << strerror(errno)
211 << "\n";
212 }
213
214 return rc;
215}
216
217/* Throws std::error_code on failure */
218/* FIXME: Probably shouldn't do fallible stuff in a constructor */
219NVMeBasicContext::NVMeBasicContext(boost::asio::io_service& io, int rootBus) :
220 NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
221{
222 std::array<int, 2> responsePipe;
223 std::array<int, 2> requestPipe;
224
225 /* Set up inter-thread communication */
226 if (::pipe(requestPipe.data()) == -1)
227 {
228 std::cerr << "Failed to create request pipe: " << strerror(errno)
229 << "\n";
230 throw std::error_code(errno, std::system_category());
231 }
232
233 if (::pipe(responsePipe.data()) == -1)
234 {
235 std::cerr << "Failed to create response pipe: " << strerror(errno)
236 << "\n";
237
238 if (::close(requestPipe[0]) == -1)
239 {
240 std::cerr << "Failed to close write fd of request pipe: "
241 << strerror(errno) << "\n";
242 }
243
244 if (::close(requestPipe[1]) == -1)
245 {
246 std::cerr << "Failed to close read fd of request pipe: "
247 << strerror(errno) << "\n";
248 }
249
250 throw std::error_code(errno, std::system_category());
251 }
252
253 reqStream.assign(requestPipe[1]);
254 int streamIn = requestPipe[0];
255 int streamOut = responsePipe[1];
256 respStream.assign(responsePipe[0]);
257
258 std::thread thread([streamIn, streamOut]() {
259 ssize_t rc;
260
261 if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
262 {
263 std::cerr << "Failure while processing query stream: "
264 << strerror(-rc) << "\n";
265 }
266
267 if (::close(streamIn) == -1)
268 {
269 std::cerr << "Failed to close streamIn descriptor: "
270 << strerror(errno) << "\n";
271 }
272
273 if (::close(streamOut) == -1)
274 {
275 std::cerr << "Failed to close streamOut descriptor: "
276 << strerror(errno) << "\n";
277 }
278
279 std::cerr << "Terminating basic query thread\n";
280 });
281 thread.detach();
282}
283
284void NVMeBasicContext::readAndProcessNVMeSensor()
285{
286 auto response = std::make_shared<boost::asio::streambuf>();
287
288 if (sensors.empty())
289 {
290 return;
291 }
292
293 std::shared_ptr<NVMeSensor>& sensor = sensors.front();
294
295 /* Ensure sensor query parameters are sensible */
296 if (sensor->bus < 0)
297 {
298 std::cerr << "Bus index cannot be negative: " << sensor->bus << "\n";
299
300 sensors.pop_front();
301 sensors.emplace_back(sensor);
302
303 return;
304 }
305
306 auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
307
308 /* Issue the request */
309 boost::asio::async_write(
310 reqStream, boost::asio::buffer(command->data(), command->size()),
311 [&command](boost::system::error_code ec, std::size_t) {
312 if (ec)
313 {
314 std::cerr << "Got error writing basic query: " << ec << "\n";
315 }
316 });
317
318 response->prepare(1);
319
320 /* Gather the response and dispatch for parsing */
321 boost::asio::async_read(
322 respStream, *response,
323 [response](const boost::system::error_code& ec, std::size_t n) {
324 if (ec)
325 {
326 std::cerr << "Got error completing basic query: " << ec << "\n";
327 return static_cast<std::size_t>(0);
328 }
329
330 if (n == 0)
331 {
332 return static_cast<std::size_t>(1);
333 }
334
335 std::istream is(response.get());
336 size_t len = static_cast<std::size_t>(is.peek());
337
338 if (n > len + 1)
339 {
340 std::cerr << "Query stream has become unsynchronised: "
341 << "n: " << n << ", "
342 << "len: " << len << "\n";
343 return static_cast<std::size_t>(0);
344 }
345
346 if (n == len + 1)
347 {
348 return static_cast<std::size_t>(0);
349 }
350
351 if (n > 1)
352 {
353 return len + 1 - n;
354 }
355
356 response->prepare(len);
357 return len;
358 },
359 [self{shared_from_this()},
360 response](const boost::system::error_code& ec, std::size_t length) {
361 if (ec)
362 {
363 std::cerr << "Got error reading basic query: " << ec << "\n";
364 return;
365 }
366
367 if (length == 0)
368 {
369 std::cerr << "Invalid message length: " << length << "\n";
370 return;
371 }
372
373 if (length == 1)
374 {
375 std::cerr << "Basic query failed\n";
376 return;
377 }
378
379 /* Deserialise the response */
380 response->consume(1); /* Drop the length byte */
381 std::istream is(response.get());
382 std::vector<char> data(response->size());
383 is.read(data.data(), response->size());
384
385 self->processResponse(data.data(), data.size());
386 });
387}
388
389void NVMeBasicContext::pollNVMeDevices()
390{
391 scanTimer.expires_from_now(boost::posix_time::seconds(1));
392 scanTimer.async_wait(
393 [self{shared_from_this()}](const boost::system::error_code errorCode) {
394 if (errorCode == boost::asio::error::operation_aborted)
395 {
396 return;
397 }
398
399 if (errorCode)
400 {
401 std::cerr << errorCode.message() << "\n";
402 return;
403 }
404
405 self->readAndProcessNVMeSensor();
406 self->pollNVMeDevices();
407 });
408}
409
410static double getTemperatureReading(int8_t reading)
411{
412 if (reading == static_cast<int8_t>(0x80) ||
413 reading == static_cast<int8_t>(0x81))
414 {
415 // 0x80 = No temperature data or temperature data is more the 5 s
416 // old 0x81 = Temperature sensor failure
417 return std::numeric_limits<double>::quiet_NaN();
418 }
419
420 return reading;
421}
422
423void NVMeBasicContext::processResponse(void* msg, size_t len)
424{
425 if (msg == nullptr)
426 {
427 std::cerr << "Bad message received\n";
428 return;
429 }
430
431 if (len < 6)
432 {
433 std::cerr << "Invalid message length: " << len << "\n";
434 return;
435 }
436
437 uint8_t* messageData = static_cast<uint8_t*>(msg);
438
439 std::shared_ptr<NVMeSensor> sensor = sensors.front();
440 double value = getTemperatureReading(messageData[2]);
441
442 if (std::isfinite(value))
443 {
444 sensor->updateValue(value);
445 }
446 else
447 {
448 sensor->markAvailable(false);
449 sensor->incrementError();
450 }
451
452 sensors.pop_front();
453 sensors.emplace_back(sensor);
454}