blob: ea17c73e5403efd464125be5a72744bdaf3bca4c [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{
Ed Tanousa771f6a2022-01-14 09:36:51 -080053 uint32_t busle = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093054
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{
Ed Tanousa771f6a2022-01-14 09:36:51 -080064 std::array<char, PATH_MAX> devpath{};
65 int32_t size = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093066
Ed Tanousa771f6a2022-01-14 09:36:51 -080067 ssize_t rc =
68 snprintf(devpath.data(), devpath.size(), "/dev/i2c-%" PRIu32, bus);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093069 if ((size_t)rc > sizeof(devpath))
70 {
71 std::cerr << "Failed to format device path for bus " << bus << "\n";
72 return -EINVAL;
73 }
74
Ed Tanousa771f6a2022-01-14 09:36:51 -080075 int dev = ::open(devpath.data(), O_RDWR);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093076 if (dev == -1)
77 {
Ed Tanousa771f6a2022-01-14 09:36:51 -080078 std::cerr << "Failed to open bus device " << devpath.data() << ": "
Andrew Jefferye3e3c972021-05-26 14:37:07 +093079 << strerror(errno) << "\n";
80 return -errno;
81 }
82
83 /* Select the target device */
84 if (::ioctl(dev, I2C_SLAVE, addr) == -1)
85 {
86 rc = -errno;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103087 std::cerr << "Failed to configure device address 0x" << std::hex
88 << (int)addr << " for bus " << std::dec << bus << ": "
89 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +093090 goto cleanup_fds;
91 }
92
93 resp.reserve(UINT8_MAX + 1);
94
95 /* Issue the NVMe MI basic command */
96 size = i2c_smbus_read_block_data(dev, cmd, resp.data());
97 if (size < 0)
98 {
99 rc = size;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030100 std::cerr << "Failed to read block data from device 0x" << std::hex
101 << (int)addr << " on bus " << std::dec << bus << ": "
102 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930103 goto cleanup_fds;
104 }
105 else if (size > UINT8_MAX + 1)
106 {
107 rc = -EBADMSG;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030108 std::cerr << "Unexpected message length from device 0x" << std::hex
109 << (int)addr << " on bus " << std::dec << bus << ": " << size
110 << " (" << UINT8_MAX << ")\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930111 goto cleanup_fds;
112 }
113
114 rc = size;
115
116cleanup_fds:
117 if (::close(dev) == -1)
118 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030119 std::cerr << "Failed to close device descriptor " << std::dec << dev
120 << " for bus " << std::dec << bus << ": " << strerror(errno)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930121 << "\n";
122 }
123
124 return rc;
125}
126
127static ssize_t processBasicQueryStream(int in, int out)
128{
129 std::vector<uint8_t> resp{};
Ed Tanousa771f6a2022-01-14 09:36:51 -0800130 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930131
132 while (true)
133 {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800134 uint8_t device = 0;
135 uint8_t offset = 0;
136 uint8_t len = 0;
137 int bus = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930138
139 /* bus + address + command */
140 std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
141
142 /* Read the command parameters */
143 if ((rc = ::read(in, req.data(), req.size())) !=
144 static_cast<ssize_t>(req.size()))
145 {
146 assert(rc < 1);
147 rc = rc ? -errno : -EIO;
148 if (errno)
149 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030150 std::cerr << "Failed to read request from in descriptor ("
151 << std::dec << in << "): " << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930152 }
153 goto done;
154 }
155
156 decodeBasicQuery(req, bus, device, offset);
157
158 /* Execute the query */
159 rc = execBasicQuery(bus, device, offset, resp);
160
161 /* Bounds check the response */
162 if (rc < 0)
163 {
164 len = 0;
165 }
166 else if (rc > UINT8_MAX)
167 {
168 assert(rc == UINT8_MAX + 1);
169
170 /* YOLO: Lop off the PEC */
171 len = UINT8_MAX;
172 }
173 else
174 {
175 len = rc;
176 }
177
178 /* Write out the response length */
179 if ((rc = ::write(out, &len, sizeof(len))) != sizeof(len))
180 {
181 assert(rc < 1);
182 rc = rc ? -errno : -EIO;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030183 std::cerr << "Failed to write block (" << std::dec << len
184 << ") length to out descriptor (" << std::dec << out
185 << "): " << strerror(-rc) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930186 goto done;
187 }
188
189 /* Write out the response data */
190 uint8_t* cursor = resp.data();
191 while (len > 0)
192 {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800193 ssize_t egress = ::write(out, cursor, len);
194 if (egress == -1)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930195 {
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{
Ed Tanousa771f6a2022-01-14 09:36:51 -0800228 std::array<int, 2> responsePipe{};
229 std::array<int, 2> requestPipe{};
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930230
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]() {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800265 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930266
267 if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
268 {
269 std::cerr << "Failure while processing query stream: "
Ed Tanousa771f6a2022-01-14 09:36:51 -0800270 << strerror(static_cast<int>(-rc)) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930271 }
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}