blob: 5708ad843c002f556bdc3672b57550fb7f951302 [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
Ed Tanous73030632022-01-14 10:09:47 -08007#include <FileHandle.hpp>
Andrew Jefferye3e3c972021-05-26 14:37:07 +09308#include <boost/asio/read.hpp>
9#include <boost/asio/streambuf.hpp>
10#include <boost/asio/write.hpp>
11
12#include <cassert>
13#include <cerrno>
14#include <cinttypes>
15#include <cstdio>
16#include <cstring>
17#include <system_error>
18#include <thread>
19
20extern "C"
21{
22#include <i2c/smbus.h>
23#include <linux/i2c-dev.h>
24}
25
26/*
27 * NVMe-MI Basic Management Command
28 *
29 * https://nvmexpress.org/wp-content/uploads/NVMe_Management_-_Technical_Note_on_Basic_Management_Command.pdf
30 */
31
32static std::shared_ptr<std::array<uint8_t, 6>>
33 encodeBasicQuery(int bus, uint8_t device, uint8_t offset)
34{
35 if (bus < 0)
36 {
37 throw std::domain_error("Invalid bus argument");
38 }
39
40 /* bus + address + command */
41 uint32_t busle = htole32(static_cast<uint32_t>(bus));
42 auto command =
43 std::make_shared<std::array<uint8_t, sizeof(busle) + 1 + 1>>();
44 memcpy(command->data(), &busle, sizeof(busle));
45 (*command)[sizeof(busle) + 0] = device;
46 (*command)[sizeof(busle) + 1] = offset;
47
48 return command;
49}
50
51static void decodeBasicQuery(const std::array<uint8_t, 6>& req, int& bus,
52 uint8_t& device, uint8_t& offset)
53{
Ed Tanousa771f6a2022-01-14 09:36:51 -080054 uint32_t busle = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093055
56 memcpy(&busle, req.data(), sizeof(busle));
57 bus = le32toh(busle);
58 device = req[sizeof(busle) + 0];
59 offset = req[sizeof(busle) + 1];
60}
61
62static ssize_t execBasicQuery(int bus, uint8_t addr, uint8_t cmd,
63 std::vector<uint8_t>& resp)
64{
Ed Tanousa771f6a2022-01-14 09:36:51 -080065 int32_t size = 0;
Ed Tanous73030632022-01-14 10:09:47 -080066 std::filesystem::path devpath = "/dev/i2c-" + std::to_string(bus);
67 FileHandle fileHandle(devpath);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093068
69 /* Select the target device */
Ed Tanous99c44092022-01-14 09:59:09 -080070 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
Ed Tanous73030632022-01-14 10:09:47 -080071 if (::ioctl(fileHandle.handle(), I2C_SLAVE, addr) == -1)
Andrew Jefferye3e3c972021-05-26 14:37:07 +093072 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103073 std::cerr << "Failed to configure device address 0x" << std::hex
74 << (int)addr << " for bus " << std::dec << bus << ": "
75 << strerror(errno) << "\n";
Ed Tanous73030632022-01-14 10:09:47 -080076
77 return -errno;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093078 }
79
Andrew Jefferya7afacc2022-03-31 11:21:28 +103080 resp.resize(UINT8_MAX + 1);
Andrew Jefferye3e3c972021-05-26 14:37:07 +093081
82 /* Issue the NVMe MI basic command */
Ed Tanous73030632022-01-14 10:09:47 -080083 size = i2c_smbus_read_block_data(fileHandle.handle(), cmd, resp.data());
Andrew Jefferye3e3c972021-05-26 14:37:07 +093084 if (size < 0)
85 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103086 std::cerr << "Failed to read block data from device 0x" << std::hex
87 << (int)addr << " on bus " << std::dec << bus << ": "
88 << strerror(errno) << "\n";
Ed Tanous73030632022-01-14 10:09:47 -080089 return size;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093090 }
Ed Tanous73030632022-01-14 10:09:47 -080091 if (size > UINT8_MAX + 1)
Andrew Jefferye3e3c972021-05-26 14:37:07 +093092 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +103093 std::cerr << "Unexpected message length from device 0x" << std::hex
94 << (int)addr << " on bus " << std::dec << bus << ": " << size
95 << " (" << UINT8_MAX << ")\n";
Ed Tanous73030632022-01-14 10:09:47 -080096 return -EBADMSG;
Andrew Jefferye3e3c972021-05-26 14:37:07 +093097 }
98
Andrew Jefferya7afacc2022-03-31 11:21:28 +103099 resp.resize(size);
100
Ed Tanous73030632022-01-14 10:09:47 -0800101 return size;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930102}
103
Ed Tanous73030632022-01-14 10:09:47 -0800104static ssize_t processBasicQueryStream(FileHandle& in, FileHandle& out)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930105{
106 std::vector<uint8_t> resp{};
Ed Tanousa771f6a2022-01-14 09:36:51 -0800107 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930108
109 while (true)
110 {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800111 uint8_t device = 0;
112 uint8_t offset = 0;
113 uint8_t len = 0;
114 int bus = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930115
116 /* bus + address + command */
117 std::array<uint8_t, sizeof(uint32_t) + 1 + 1> req{};
118
119 /* Read the command parameters */
Ed Tanous73030632022-01-14 10:09:47 -0800120 ssize_t rc = ::read(in.handle(), req.data(), req.size());
121 if (rc != static_cast<ssize_t>(req.size()))
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930122 {
Ed Tanous73030632022-01-14 10:09:47 -0800123 std::cerr << "Failed to read request from in descriptor "
124 << strerror(errno) << "\n";
125 if (rc)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930126 {
Ed Tanous73030632022-01-14 10:09:47 -0800127 return -errno;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930128 }
Ed Tanous73030632022-01-14 10:09:47 -0800129 return -EIO;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930130 }
131
132 decodeBasicQuery(req, bus, device, offset);
133
134 /* Execute the query */
135 rc = execBasicQuery(bus, device, offset, resp);
136
137 /* Bounds check the response */
138 if (rc < 0)
139 {
140 len = 0;
141 }
142 else if (rc > UINT8_MAX)
143 {
144 assert(rc == UINT8_MAX + 1);
145
146 /* YOLO: Lop off the PEC */
147 len = UINT8_MAX;
148 }
149 else
150 {
151 len = rc;
152 }
153
154 /* Write out the response length */
Ed Tanous73030632022-01-14 10:09:47 -0800155 rc = ::write(out.handle(), &len, sizeof(len));
156 if (rc != sizeof(len))
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930157 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030158 std::cerr << "Failed to write block (" << std::dec << len
Ed Tanous73030632022-01-14 10:09:47 -0800159 << ") length to out descriptor: "
160 << strerror(static_cast<int>(-rc)) << "\n";
161 if (rc)
162 {
163 return -errno;
164 }
165 return -EIO;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930166 }
167
168 /* Write out the response data */
Ed Tanous73030632022-01-14 10:09:47 -0800169 std::vector<uint8_t>::iterator cursor = resp.begin();
170 while (cursor != resp.end())
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930171 {
Ed Tanous73030632022-01-14 10:09:47 -0800172 size_t lenRemaining = std::distance(cursor, resp.end());
173 ssize_t egress = ::write(out.handle(), &(*cursor), lenRemaining);
Ed Tanousa771f6a2022-01-14 09:36:51 -0800174 if (egress == -1)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930175 {
Andrew Jeffery9ca98ec2021-11-02 09:50:47 +1030176 std::cerr << "Failed to write block data of length " << std::dec
Ed Tanous73030632022-01-14 10:09:47 -0800177 << lenRemaining << " to out pipe: " << strerror(errno)
178 << "\n";
179 if (rc)
180 {
181 return -errno;
182 }
183 return -EIO;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930184 }
185
186 cursor += egress;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930187 }
188 }
189
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930190 return rc;
191}
192
193/* Throws std::error_code on failure */
194/* FIXME: Probably shouldn't do fallible stuff in a constructor */
195NVMeBasicContext::NVMeBasicContext(boost::asio::io_service& io, int rootBus) :
196 NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
197{
Ed Tanousa771f6a2022-01-14 09:36:51 -0800198 std::array<int, 2> responsePipe{};
199 std::array<int, 2> requestPipe{};
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930200
201 /* Set up inter-thread communication */
202 if (::pipe(requestPipe.data()) == -1)
203 {
204 std::cerr << "Failed to create request pipe: " << strerror(errno)
205 << "\n";
206 throw std::error_code(errno, std::system_category());
207 }
208
209 if (::pipe(responsePipe.data()) == -1)
210 {
211 std::cerr << "Failed to create response pipe: " << strerror(errno)
212 << "\n";
213
214 if (::close(requestPipe[0]) == -1)
215 {
216 std::cerr << "Failed to close write fd of request pipe: "
217 << strerror(errno) << "\n";
218 }
219
220 if (::close(requestPipe[1]) == -1)
221 {
222 std::cerr << "Failed to close read fd of request pipe: "
223 << strerror(errno) << "\n";
224 }
225
226 throw std::error_code(errno, std::system_category());
227 }
228
229 reqStream.assign(requestPipe[1]);
Ed Tanous73030632022-01-14 10:09:47 -0800230 FileHandle streamIn(requestPipe[0]);
231 FileHandle streamOut(responsePipe[1]);
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930232 respStream.assign(responsePipe[0]);
233
Ed Tanous73030632022-01-14 10:09:47 -0800234 std::thread thread([streamIn{std::move(streamIn)},
235 streamOut{std::move(streamOut)}]() mutable {
Ed Tanousa771f6a2022-01-14 09:36:51 -0800236 ssize_t rc = 0;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930237
238 if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
239 {
240 std::cerr << "Failure while processing query stream: "
Ed Tanousa771f6a2022-01-14 09:36:51 -0800241 << strerror(static_cast<int>(-rc)) << "\n";
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930242 }
243
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930244 std::cerr << "Terminating basic query thread\n";
245 });
246 thread.detach();
247}
248
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030249void NVMeBasicContext::readAndProcessNVMeSensor(
250 std::list<std::shared_ptr<NVMeSensor>>::iterator iter)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930251{
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030252 if (iter == sensors.end())
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930253 {
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030254 this->pollNVMeDevices();
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930255 return;
256 }
257
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030258 std::shared_ptr<NVMeSensor> sensor = *iter++;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930259
Andrew Jeffery3859c7f2021-10-29 15:51:16 +1030260 if (!sensor->readingStateGood())
261 {
262 sensor->markAvailable(false);
263 sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030264 readAndProcessNVMeSensor(iter);
Andrew Jeffery3859c7f2021-10-29 15:51:16 +1030265 return;
266 }
267
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030268 /* Potentially defer sampling the sensor sensor if it is in error */
269 if (!sensor->sample())
270 {
271 readAndProcessNVMeSensor(iter);
272 return;
273 }
274
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930275 auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
276
277 /* Issue the request */
278 boost::asio::async_write(
279 reqStream, boost::asio::buffer(command->data(), command->size()),
Ed Tanous76b2bc72022-02-18 09:48:16 -0800280 [command](boost::system::error_code ec, std::size_t) {
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930281 if (ec)
282 {
283 std::cerr << "Got error writing basic query: " << ec << "\n";
284 }
285 });
286
Andrew Jeffery84c16872022-03-15 21:50:59 +1030287 auto response = std::make_shared<boost::asio::streambuf>();
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930288 response->prepare(1);
289
290 /* Gather the response and dispatch for parsing */
291 boost::asio::async_read(
292 respStream, *response,
293 [response](const boost::system::error_code& ec, std::size_t n) {
294 if (ec)
295 {
296 std::cerr << "Got error completing basic query: " << ec << "\n";
297 return static_cast<std::size_t>(0);
298 }
299
300 if (n == 0)
301 {
302 return static_cast<std::size_t>(1);
303 }
304
305 std::istream is(response.get());
306 size_t len = static_cast<std::size_t>(is.peek());
307
308 if (n > len + 1)
309 {
310 std::cerr << "Query stream has become unsynchronised: "
311 << "n: " << n << ", "
312 << "len: " << len << "\n";
313 return static_cast<std::size_t>(0);
314 }
315
316 if (n == len + 1)
317 {
318 return static_cast<std::size_t>(0);
319 }
320
321 if (n > 1)
322 {
323 return len + 1 - n;
324 }
325
326 response->prepare(len);
327 return len;
328 },
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030329 [self{shared_from_this()}, iter, sensor, response](
330 const boost::system::error_code& ec, std::size_t length) mutable {
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930331 if (ec)
332 {
333 std::cerr << "Got error reading basic query: " << ec << "\n";
334 return;
335 }
336
337 if (length == 0)
338 {
339 std::cerr << "Invalid message length: " << length << "\n";
340 return;
341 }
342
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930343 /* Deserialise the response */
344 response->consume(1); /* Drop the length byte */
345 std::istream is(response.get());
346 std::vector<char> data(response->size());
347 is.read(data.data(), response->size());
348
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030349 /* Update the sensor */
350 self->processResponse(sensor, data.data(), data.size());
351
352 /* Enqueue processing of the next sensor */
353 self->readAndProcessNVMeSensor(iter);
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930354 });
355}
356
357void NVMeBasicContext::pollNVMeDevices()
358{
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030359 auto scan = sensors.begin();
360
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930361 scanTimer.expires_from_now(boost::posix_time::seconds(1));
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030362 scanTimer.async_wait([self{shared_from_this()},
363 scan](const boost::system::error_code errorCode) {
364 if (errorCode == boost::asio::error::operation_aborted)
365 {
366 return;
367 }
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930368
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030369 if (errorCode)
370 {
371 std::cerr << errorCode.message() << "\n";
372 return;
373 }
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930374
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030375 self->readAndProcessNVMeSensor(scan);
376 });
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930377}
378
379static double getTemperatureReading(int8_t reading)
380{
381 if (reading == static_cast<int8_t>(0x80) ||
382 reading == static_cast<int8_t>(0x81))
383 {
384 // 0x80 = No temperature data or temperature data is more the 5 s
385 // old 0x81 = Temperature sensor failure
386 return std::numeric_limits<double>::quiet_NaN();
387 }
388
389 return reading;
390}
391
Andrew Jeffery8c7074e2022-03-21 14:58:13 +1030392void NVMeBasicContext::processResponse(std::shared_ptr<NVMeSensor>& sensor,
393 void* msg, size_t len)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930394{
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030395 if (msg == nullptr || len < 6)
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930396 {
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030397 sensor->incrementError();
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930398 return;
399 }
400
401 uint8_t* messageData = static_cast<uint8_t*>(msg);
Andrew Jeffery7aeb1a52022-03-15 22:49:04 +1030402
403 uint8_t status = messageData[0];
404 if ((status & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) ||
405 !(status & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL))
406 {
407 sensor->markFunctional(false);
408 return;
409 }
410
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930411 double value = getTemperatureReading(messageData[2]);
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030412 if (!std::isfinite(value))
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930413 {
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930414 sensor->incrementError();
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030415 return;
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930416 }
Andrew Jeffery14108bb2022-03-21 15:00:16 +1030417
418 sensor->updateValue(value);
Andrew Jefferye3e3c972021-05-26 14:37:07 +0930419}