blob: 3c8b74dadecc09c00071112027777c08baa1ba3a [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
Andrew Jeffery3859c7f2021-10-29 15:51:16 +1030295 if (!sensor->readingStateGood())
296 {
297 sensor->markAvailable(false);
298 sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
299 return;
300 }
301
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930302 /* Ensure sensor query parameters are sensible */
303 if (sensor->bus < 0)
304 {
305 std::cerr << "Bus index cannot be negative: " << sensor->bus << "\n";
306
307 sensors.pop_front();
308 sensors.emplace_back(sensor);
309
310 return;
311 }
312
313 auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
314
315 /* Issue the request */
316 boost::asio::async_write(
317 reqStream, boost::asio::buffer(command->data(), command->size()),
318 [&command](boost::system::error_code ec, std::size_t) {
319 if (ec)
320 {
321 std::cerr << "Got error writing basic query: " << ec << "\n";
322 }
323 });
324
325 response->prepare(1);
326
327 /* Gather the response and dispatch for parsing */
328 boost::asio::async_read(
329 respStream, *response,
330 [response](const boost::system::error_code& ec, std::size_t n) {
331 if (ec)
332 {
333 std::cerr << "Got error completing basic query: " << ec << "\n";
334 return static_cast<std::size_t>(0);
335 }
336
337 if (n == 0)
338 {
339 return static_cast<std::size_t>(1);
340 }
341
342 std::istream is(response.get());
343 size_t len = static_cast<std::size_t>(is.peek());
344
345 if (n > len + 1)
346 {
347 std::cerr << "Query stream has become unsynchronised: "
348 << "n: " << n << ", "
349 << "len: " << len << "\n";
350 return static_cast<std::size_t>(0);
351 }
352
353 if (n == len + 1)
354 {
355 return static_cast<std::size_t>(0);
356 }
357
358 if (n > 1)
359 {
360 return len + 1 - n;
361 }
362
363 response->prepare(len);
364 return len;
365 },
366 [self{shared_from_this()},
367 response](const boost::system::error_code& ec, std::size_t length) {
368 if (ec)
369 {
370 std::cerr << "Got error reading basic query: " << ec << "\n";
371 return;
372 }
373
374 if (length == 0)
375 {
376 std::cerr << "Invalid message length: " << length << "\n";
377 return;
378 }
379
380 if (length == 1)
381 {
382 std::cerr << "Basic query failed\n";
383 return;
384 }
385
386 /* Deserialise the response */
387 response->consume(1); /* Drop the length byte */
388 std::istream is(response.get());
389 std::vector<char> data(response->size());
390 is.read(data.data(), response->size());
391
392 self->processResponse(data.data(), data.size());
393 });
394}
395
396void NVMeBasicContext::pollNVMeDevices()
397{
398 scanTimer.expires_from_now(boost::posix_time::seconds(1));
399 scanTimer.async_wait(
400 [self{shared_from_this()}](const boost::system::error_code errorCode) {
401 if (errorCode == boost::asio::error::operation_aborted)
402 {
403 return;
404 }
405
406 if (errorCode)
407 {
408 std::cerr << errorCode.message() << "\n";
409 return;
410 }
411
412 self->readAndProcessNVMeSensor();
413 self->pollNVMeDevices();
414 });
415}
416
417static double getTemperatureReading(int8_t reading)
418{
419 if (reading == static_cast<int8_t>(0x80) ||
420 reading == static_cast<int8_t>(0x81))
421 {
422 // 0x80 = No temperature data or temperature data is more the 5 s
423 // old 0x81 = Temperature sensor failure
424 return std::numeric_limits<double>::quiet_NaN();
425 }
426
427 return reading;
428}
429
430void NVMeBasicContext::processResponse(void* msg, size_t len)
431{
432 if (msg == nullptr)
433 {
434 std::cerr << "Bad message received\n";
435 return;
436 }
437
438 if (len < 6)
439 {
440 std::cerr << "Invalid message length: " << len << "\n";
441 return;
442 }
443
444 uint8_t* messageData = static_cast<uint8_t*>(msg);
445
446 std::shared_ptr<NVMeSensor> sensor = sensors.front();
447 double value = getTemperatureReading(messageData[2]);
448
449 if (std::isfinite(value))
450 {
451 sensor->updateValue(value);
452 }
453 else
454 {
455 sensor->markAvailable(false);
456 sensor->incrementError();
457 }
458
459 sensors.pop_front();
460 sensors.emplace_back(sensor);
461}