blob: c828d2702b2eb29273da963a191f598616c748f7 [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;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103086 std::cerr << "Failed to configure device address 0x" << std::hex
87 << (int)addr << " for bus " << std::dec << bus << ": "
88 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +093089 goto cleanup_fds;
90 }
91
92 resp.reserve(UINT8_MAX + 1);
93
94 /* Issue the NVMe MI basic command */
95 size = i2c_smbus_read_block_data(dev, cmd, resp.data());
96 if (size < 0)
97 {
98 rc = size;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103099 std::cerr << "Failed to read block data from device 0x" << std::hex
100 << (int)addr << " on bus " << std::dec << bus << ": "
101 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930102 goto cleanup_fds;
103 }
104 else if (size > UINT8_MAX + 1)
105 {
106 rc = -EBADMSG;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030107 std::cerr << "Unexpected message length from device 0x" << std::hex
108 << (int)addr << " on bus " << std::dec << bus << ": " << size
109 << " (" << UINT8_MAX << ")\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930110 goto cleanup_fds;
111 }
112
113 rc = size;
114
115cleanup_fds:
116 if (::close(dev) == -1)
117 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030118 std::cerr << "Failed to close device descriptor " << std::dec << dev
119 << " for bus " << std::dec << bus << ": " << strerror(errno)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930120 << "\n";
121 }
122
123 return rc;
124}
125
126static ssize_t processBasicQueryStream(int in, int out)
127{
128 std::vector<uint8_t> resp{};
129 ssize_t rc;
130
131 while (true)
132 {
133 uint8_t device;
134 uint8_t offset;
135 uint8_t len;
136 int bus;
137
138 /* bus + address + command */
139 std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
140
141 /* Read the command parameters */
142 if ((rc = ::read(in, req.data(), req.size())) !=
143 static_cast<ssize_t>(req.size()))
144 {
145 assert(rc < 1);
146 rc = rc ? -errno : -EIO;
147 if (errno)
148 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030149 std::cerr << "Failed to read request from in descriptor ("
150 << std::dec << in << "): " << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930151 }
152 goto done;
153 }
154
155 decodeBasicQuery(req, bus, device, offset);
156
157 /* Execute the query */
158 rc = execBasicQuery(bus, device, offset, resp);
159
160 /* Bounds check the response */
161 if (rc < 0)
162 {
163 len = 0;
164 }
165 else if (rc > UINT8_MAX)
166 {
167 assert(rc == UINT8_MAX + 1);
168
169 /* YOLO: Lop off the PEC */
170 len = UINT8_MAX;
171 }
172 else
173 {
174 len = rc;
175 }
176
177 /* Write out the response length */
178 if ((rc = ::write(out, &len, sizeof(len))) != sizeof(len))
179 {
180 assert(rc < 1);
181 rc = rc ? -errno : -EIO;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030182 std::cerr << "Failed to write block (" << std::dec << len
183 << ") length to out descriptor (" << std::dec << out
184 << "): " << strerror(-rc) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930185 goto done;
186 }
187
188 /* Write out the response data */
189 uint8_t* cursor = resp.data();
190 while (len > 0)
191 {
192 ssize_t egress;
193
194 if ((egress = ::write(out, cursor, len)) == -1)
195 {
196 rc = -errno;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030197 std::cerr << "Failed to write block data of length " << std::dec
198 << len << " to out pipe: " << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930199 goto done;
200 }
201
202 cursor += egress;
203 len -= egress;
204 }
205 }
206
207done:
208 if (::close(in) == -1)
209 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030210 std::cerr << "Failed to close in descriptor " << std::dec << in << ": "
211 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930212 }
213
214 if (::close(out) == -1)
215 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030216 std::cerr << "Failed to close out descriptor " << std::dec << in << ": "
217 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930218 }
219
220 return rc;
221}
222
223/* Throws std::error_code on failure */
224/* FIXME: Probably shouldn't do fallible stuff in a constructor */
225NVMeBasicContext::NVMeBasicContext(boost::asio::io_service& io, int rootBus) :
226 NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
227{
228 std::array<int, 2> responsePipe;
229 std::array<int, 2> requestPipe;
230
231 /* Set up inter-thread communication */
232 if (::pipe(requestPipe.data()) == -1)
233 {
234 std::cerr << "Failed to create request pipe: " << strerror(errno)
235 << "\n";
236 throw std::error_code(errno, std::system_category());
237 }
238
239 if (::pipe(responsePipe.data()) == -1)
240 {
241 std::cerr << "Failed to create response pipe: " << strerror(errno)
242 << "\n";
243
244 if (::close(requestPipe[0]) == -1)
245 {
246 std::cerr << "Failed to close write fd of request pipe: "
247 << strerror(errno) << "\n";
248 }
249
250 if (::close(requestPipe[1]) == -1)
251 {
252 std::cerr << "Failed to close read fd of request pipe: "
253 << strerror(errno) << "\n";
254 }
255
256 throw std::error_code(errno, std::system_category());
257 }
258
259 reqStream.assign(requestPipe[1]);
260 int streamIn = requestPipe[0];
261 int streamOut = responsePipe[1];
262 respStream.assign(responsePipe[0]);
263
264 std::thread thread([streamIn, streamOut]() {
265 ssize_t rc;
266
267 if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
268 {
269 std::cerr << "Failure while processing query stream: "
270 << strerror(-rc) << "\n";
271 }
272
273 if (::close(streamIn) == -1)
274 {
275 std::cerr << "Failed to close streamIn descriptor: "
276 << strerror(errno) << "\n";
277 }
278
279 if (::close(streamOut) == -1)
280 {
281 std::cerr << "Failed to close streamOut descriptor: "
282 << strerror(errno) << "\n";
283 }
284
285 std::cerr << "Terminating basic query thread\n";
286 });
287 thread.detach();
288}
289
290void NVMeBasicContext::readAndProcessNVMeSensor()
291{
292 auto response = std::make_shared<boost::asio::streambuf>();
293
294 if (sensors.empty())
295 {
296 return;
297 }
298
299 std::shared_ptr<NVMeSensor>& sensor = sensors.front();
300
Andrew Jeffery3859c7f2021-10-29 15:51:16 +1030301 if (!sensor->readingStateGood())
302 {
303 sensor->markAvailable(false);
304 sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
305 return;
306 }
307
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930308 /* Ensure sensor query parameters are sensible */
309 if (sensor->bus < 0)
310 {
311 std::cerr << "Bus index cannot be negative: " << sensor->bus << "\n";
312
313 sensors.pop_front();
314 sensors.emplace_back(sensor);
315
316 return;
317 }
318
319 auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
320
321 /* Issue the request */
322 boost::asio::async_write(
323 reqStream, boost::asio::buffer(command->data(), command->size()),
324 [&command](boost::system::error_code ec, std::size_t) {
325 if (ec)
326 {
327 std::cerr << "Got error writing basic query: " << ec << "\n";
328 }
329 });
330
331 response->prepare(1);
332
333 /* Gather the response and dispatch for parsing */
334 boost::asio::async_read(
335 respStream, *response,
336 [response](const boost::system::error_code& ec, std::size_t n) {
337 if (ec)
338 {
339 std::cerr << "Got error completing basic query: " << ec << "\n";
340 return static_cast<std::size_t>(0);
341 }
342
343 if (n == 0)
344 {
345 return static_cast<std::size_t>(1);
346 }
347
348 std::istream is(response.get());
349 size_t len = static_cast<std::size_t>(is.peek());
350
351 if (n > len + 1)
352 {
353 std::cerr << "Query stream has become unsynchronised: "
354 << "n: " << n << ", "
355 << "len: " << len << "\n";
356 return static_cast<std::size_t>(0);
357 }
358
359 if (n == len + 1)
360 {
361 return static_cast<std::size_t>(0);
362 }
363
364 if (n > 1)
365 {
366 return len + 1 - n;
367 }
368
369 response->prepare(len);
370 return len;
371 },
372 [self{shared_from_this()},
373 response](const boost::system::error_code& ec, std::size_t length) {
374 if (ec)
375 {
376 std::cerr << "Got error reading basic query: " << ec << "\n";
377 return;
378 }
379
380 if (length == 0)
381 {
382 std::cerr << "Invalid message length: " << length << "\n";
383 return;
384 }
385
386 if (length == 1)
387 {
388 std::cerr << "Basic query failed\n";
389 return;
390 }
391
392 /* Deserialise the response */
393 response->consume(1); /* Drop the length byte */
394 std::istream is(response.get());
395 std::vector<char> data(response->size());
396 is.read(data.data(), response->size());
397
398 self->processResponse(data.data(), data.size());
399 });
400}
401
402void NVMeBasicContext::pollNVMeDevices()
403{
404 scanTimer.expires_from_now(boost::posix_time::seconds(1));
405 scanTimer.async_wait(
406 [self{shared_from_this()}](const boost::system::error_code errorCode) {
407 if (errorCode == boost::asio::error::operation_aborted)
408 {
409 return;
410 }
411
412 if (errorCode)
413 {
414 std::cerr << errorCode.message() << "\n";
415 return;
416 }
417
418 self->readAndProcessNVMeSensor();
419 self->pollNVMeDevices();
420 });
421}
422
423static double getTemperatureReading(int8_t reading)
424{
425 if (reading == static_cast<int8_t>(0x80) ||
426 reading == static_cast<int8_t>(0x81))
427 {
428 // 0x80 = No temperature data or temperature data is more the 5 s
429 // old 0x81 = Temperature sensor failure
430 return std::numeric_limits<double>::quiet_NaN();
431 }
432
433 return reading;
434}
435
436void NVMeBasicContext::processResponse(void* msg, size_t len)
437{
438 if (msg == nullptr)
439 {
440 std::cerr << "Bad message received\n";
441 return;
442 }
443
444 if (len < 6)
445 {
446 std::cerr << "Invalid message length: " << len << "\n";
447 return;
448 }
449
450 uint8_t* messageData = static_cast<uint8_t*>(msg);
451
452 std::shared_ptr<NVMeSensor> sensor = sensors.front();
453 double value = getTemperatureReading(messageData[2]);
454
455 if (std::isfinite(value))
456 {
457 sensor->updateValue(value);
458 }
459 else
460 {
461 sensor->markAvailable(false);
462 sensor->incrementError();
463 }
464
465 sensors.pop_front();
466 sensors.emplace_back(sensor);
467}