blob: 0e04b160474b3de97ccd2a44c833922d4cdae119 [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 Tanous99c44092022-01-14 09:59:09 -080064 int rc = 0;
Ed Tanousa771f6a2022-01-14 09:36:51 -080065 int32_t size = 0;
Ed Tanous99c44092022-01-14 09:59:09 -080066 std::string devpath = "/dev/i2c-" + std::to_string(bus);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093067
Ed Tanous99c44092022-01-14 09:59:09 -080068 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
69 int dev = ::open(devpath.c_str(), O_RDWR);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093070 if (dev == -1)
71 {
Ed Tanous99c44092022-01-14 09:59:09 -080072 std::cerr << "Failed to open bus device " << devpath << ": "
Andrew Jefferye3e3c972021-05-26 14:37:07 +093073 << strerror(errno) << "\n";
74 return -errno;
75 }
76
77 /* Select the target device */
Ed Tanous99c44092022-01-14 09:59:09 -080078 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
Andrew Jefferye3e3c972021-05-26 14:37:07 +093079 if (::ioctl(dev, I2C_SLAVE, addr) == -1)
80 {
81 rc = -errno;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103082 std::cerr << "Failed to configure device address 0x" << std::hex
83 << (int)addr << " for bus " << std::dec << bus << ": "
84 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +093085 goto cleanup_fds;
86 }
87
88 resp.reserve(UINT8_MAX + 1);
89
90 /* Issue the NVMe MI basic command */
91 size = i2c_smbus_read_block_data(dev, cmd, resp.data());
92 if (size < 0)
93 {
94 rc = size;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103095 std::cerr << "Failed to read block data from device 0x" << std::hex
96 << (int)addr << " on bus " << std::dec << bus << ": "
97 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +093098 goto cleanup_fds;
99 }
100 else if (size > UINT8_MAX + 1)
101 {
102 rc = -EBADMSG;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030103 std::cerr << "Unexpected message length from device 0x" << std::hex
104 << (int)addr << " on bus " << std::dec << bus << ": " << size
105 << " (" << UINT8_MAX << ")\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930106 goto cleanup_fds;
107 }
108
109 rc = size;
110
111cleanup_fds:
112 if (::close(dev) == -1)
113 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030114 std::cerr << "Failed to close device descriptor " << std::dec << dev
115 << " for bus " << std::dec << bus << ": " << strerror(errno)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930116 << "\n";
117 }
118
119 return rc;
120}
121
122static ssize_t processBasicQueryStream(int in, int out)
123{
124 std::vector<uint8_t> resp{};
Ed Tanousa771f6a2022-01-14 09:36:51 -0800125 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930126
127 while (true)
128 {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800129 uint8_t device = 0;
130 uint8_t offset = 0;
131 uint8_t len = 0;
132 int bus = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930133
134 /* bus + address + command */
135 std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
136
137 /* Read the command parameters */
138 if ((rc = ::read(in, req.data(), req.size())) !=
139 static_cast<ssize_t>(req.size()))
140 {
141 assert(rc < 1);
142 rc = rc ? -errno : -EIO;
143 if (errno)
144 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030145 std::cerr << "Failed to read request from in descriptor ("
146 << std::dec << in << "): " << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930147 }
148 goto done;
149 }
150
151 decodeBasicQuery(req, bus, device, offset);
152
153 /* Execute the query */
154 rc = execBasicQuery(bus, device, offset, resp);
155
156 /* Bounds check the response */
157 if (rc < 0)
158 {
159 len = 0;
160 }
161 else if (rc > UINT8_MAX)
162 {
163 assert(rc == UINT8_MAX + 1);
164
165 /* YOLO: Lop off the PEC */
166 len = UINT8_MAX;
167 }
168 else
169 {
170 len = rc;
171 }
172
173 /* Write out the response length */
174 if ((rc = ::write(out, &len, sizeof(len))) != sizeof(len))
175 {
176 assert(rc < 1);
177 rc = rc ? -errno : -EIO;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030178 std::cerr << "Failed to write block (" << std::dec << len
179 << ") length to out descriptor (" << std::dec << out
180 << "): " << strerror(-rc) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930181 goto done;
182 }
183
184 /* Write out the response data */
185 uint8_t* cursor = resp.data();
186 while (len > 0)
187 {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800188 ssize_t egress = ::write(out, cursor, len);
189 if (egress == -1)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930190 {
191 rc = -errno;
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030192 std::cerr << "Failed to write block data of length " << std::dec
193 << len << " to out pipe: " << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930194 goto done;
195 }
196
197 cursor += egress;
198 len -= egress;
199 }
200 }
201
202done:
203 if (::close(in) == -1)
204 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030205 std::cerr << "Failed to close in descriptor " << std::dec << in << ": "
206 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930207 }
208
209 if (::close(out) == -1)
210 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030211 std::cerr << "Failed to close out descriptor " << std::dec << in << ": "
212 << strerror(errno) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930213 }
214
215 return rc;
216}
217
218/* Throws std::error_code on failure */
219/* FIXME: Probably shouldn't do fallible stuff in a constructor */
220NVMeBasicContext::NVMeBasicContext(boost::asio::io_service& io, int rootBus) :
221 NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
222{
Ed Tanousa771f6a2022-01-14 09:36:51 -0800223 std::array<int, 2> responsePipe{};
224 std::array<int, 2> requestPipe{};
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930225
226 /* Set up inter-thread communication */
227 if (::pipe(requestPipe.data()) == -1)
228 {
229 std::cerr << "Failed to create request pipe: " << strerror(errno)
230 << "\n";
231 throw std::error_code(errno, std::system_category());
232 }
233
234 if (::pipe(responsePipe.data()) == -1)
235 {
236 std::cerr << "Failed to create response pipe: " << strerror(errno)
237 << "\n";
238
239 if (::close(requestPipe[0]) == -1)
240 {
241 std::cerr << "Failed to close write fd of request pipe: "
242 << strerror(errno) << "\n";
243 }
244
245 if (::close(requestPipe[1]) == -1)
246 {
247 std::cerr << "Failed to close read fd of request pipe: "
248 << strerror(errno) << "\n";
249 }
250
251 throw std::error_code(errno, std::system_category());
252 }
253
254 reqStream.assign(requestPipe[1]);
255 int streamIn = requestPipe[0];
256 int streamOut = responsePipe[1];
257 respStream.assign(responsePipe[0]);
258
259 std::thread thread([streamIn, streamOut]() {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800260 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930261
262 if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
263 {
264 std::cerr << "Failure while processing query stream: "
Ed Tanousa771f6a2022-01-14 09:36:51 -0800265 << strerror(static_cast<int>(-rc)) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930266 }
267
268 if (::close(streamIn) == -1)
269 {
270 std::cerr << "Failed to close streamIn descriptor: "
271 << strerror(errno) << "\n";
272 }
273
274 if (::close(streamOut) == -1)
275 {
276 std::cerr << "Failed to close streamOut descriptor: "
277 << strerror(errno) << "\n";
278 }
279
280 std::cerr << "Terminating basic query thread\n";
281 });
282 thread.detach();
283}
284
285void NVMeBasicContext::readAndProcessNVMeSensor()
286{
287 auto response = std::make_shared<boost::asio::streambuf>();
288
289 if (sensors.empty())
290 {
291 return;
292 }
293
294 std::shared_ptr<NVMeSensor>& sensor = sensors.front();
295
Andrew Jeffery3859c7f2021-10-29 15:51:16 +1030296 if (!sensor->readingStateGood())
297 {
298 sensor->markAvailable(false);
299 sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
300 return;
301 }
302
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930303 /* Ensure sensor query parameters are sensible */
304 if (sensor->bus < 0)
305 {
306 std::cerr << "Bus index cannot be negative: " << sensor->bus << "\n";
307
308 sensors.pop_front();
309 sensors.emplace_back(sensor);
310
311 return;
312 }
313
314 auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
315
316 /* Issue the request */
317 boost::asio::async_write(
318 reqStream, boost::asio::buffer(command->data(), command->size()),
319 [&command](boost::system::error_code ec, std::size_t) {
320 if (ec)
321 {
322 std::cerr << "Got error writing basic query: " << ec << "\n";
323 }
324 });
325
326 response->prepare(1);
327
328 /* Gather the response and dispatch for parsing */
329 boost::asio::async_read(
330 respStream, *response,
331 [response](const boost::system::error_code& ec, std::size_t n) {
332 if (ec)
333 {
334 std::cerr << "Got error completing basic query: " << ec << "\n";
335 return static_cast<std::size_t>(0);
336 }
337
338 if (n == 0)
339 {
340 return static_cast<std::size_t>(1);
341 }
342
343 std::istream is(response.get());
344 size_t len = static_cast<std::size_t>(is.peek());
345
346 if (n > len + 1)
347 {
348 std::cerr << "Query stream has become unsynchronised: "
349 << "n: " << n << ", "
350 << "len: " << len << "\n";
351 return static_cast<std::size_t>(0);
352 }
353
354 if (n == len + 1)
355 {
356 return static_cast<std::size_t>(0);
357 }
358
359 if (n > 1)
360 {
361 return len + 1 - n;
362 }
363
364 response->prepare(len);
365 return len;
366 },
367 [self{shared_from_this()},
368 response](const boost::system::error_code& ec, std::size_t length) {
369 if (ec)
370 {
371 std::cerr << "Got error reading basic query: " << ec << "\n";
372 return;
373 }
374
375 if (length == 0)
376 {
377 std::cerr << "Invalid message length: " << length << "\n";
378 return;
379 }
380
381 if (length == 1)
382 {
383 std::cerr << "Basic query failed\n";
384 return;
385 }
386
387 /* Deserialise the response */
388 response->consume(1); /* Drop the length byte */
389 std::istream is(response.get());
390 std::vector<char> data(response->size());
391 is.read(data.data(), response->size());
392
393 self->processResponse(data.data(), data.size());
394 });
395}
396
397void NVMeBasicContext::pollNVMeDevices()
398{
399 scanTimer.expires_from_now(boost::posix_time::seconds(1));
400 scanTimer.async_wait(
401 [self{shared_from_this()}](const boost::system::error_code errorCode) {
402 if (errorCode == boost::asio::error::operation_aborted)
403 {
404 return;
405 }
406
407 if (errorCode)
408 {
409 std::cerr << errorCode.message() << "\n";
410 return;
411 }
412
413 self->readAndProcessNVMeSensor();
414 self->pollNVMeDevices();
415 });
416}
417
418static double getTemperatureReading(int8_t reading)
419{
420 if (reading == static_cast<int8_t>(0x80) ||
421 reading == static_cast<int8_t>(0x81))
422 {
423 // 0x80 = No temperature data or temperature data is more the 5 s
424 // old 0x81 = Temperature sensor failure
425 return std::numeric_limits<double>::quiet_NaN();
426 }
427
428 return reading;
429}
430
431void NVMeBasicContext::processResponse(void* msg, size_t len)
432{
433 if (msg == nullptr)
434 {
435 std::cerr << "Bad message received\n";
436 return;
437 }
438
439 if (len < 6)
440 {
441 std::cerr << "Invalid message length: " << len << "\n";
442 return;
443 }
444
445 uint8_t* messageData = static_cast<uint8_t*>(msg);
446
447 std::shared_ptr<NVMeSensor> sensor = sensors.front();
448 double value = getTemperatureReading(messageData[2]);
449
450 if (std::isfinite(value))
451 {
452 sensor->updateValue(value);
453 }
454 else
455 {
456 sensor->markAvailable(false);
457 sensor->incrementError();
458 }
459
460 sensors.pop_front();
461 sensors.emplace_back(sensor);
462}