1 #include "NVMeBasicContext.hpp"
2
3 #include "NVMeContext.hpp"
4 #include "NVMeSensor.hpp"
5
6 #include <sys/ioctl.h>
7 #include <unistd.h>
8
9 #include <FileHandle.hpp>
10 #include <boost/asio/buffer.hpp>
11 #include <boost/asio/error.hpp>
12 #include <boost/asio/io_context.hpp>
13 #include <boost/asio/read.hpp>
14 #include <boost/asio/streambuf.hpp>
15 #include <boost/asio/write.hpp>
16 #include <phosphor-logging/lg2.hpp>
17 #include <phosphor-logging/lg2/flags.hpp>
18
19 #include <array>
20 #include <cerrno>
21 #include <chrono>
22 #include <cmath>
23 #include <cstdint>
24 #include <cstdio>
25 #include <cstring>
26 #include <filesystem>
27 #include <iostream>
28 #include <iterator>
29 #include <limits>
30 #include <memory>
31 #include <stdexcept>
32 #include <string>
33 #include <system_error>
34 #include <thread>
35 #include <utility>
36 #include <vector>
37
38 extern "C"
39 {
40 #include <i2c/smbus.h>
41 #include <linux/i2c-dev.h>
42 }
43
44 /*
45 * NVMe-MI Basic Management Command
46 *
47 * https://nvmexpress.org/wp-content/uploads/NVMe_Management_-_Technical_Note_on_Basic_Management_Command.pdf
48 */
49
50 struct BasicQueryCmd
51 {
52 int bus;
53 uint8_t device;
54 uint8_t offset;
55 bool smbusPEC;
56 };
57
execBasicQuery(int bus,uint8_t addr,uint8_t cmd,bool smbusPEC,std::vector<uint8_t> & resp)58 static void execBasicQuery(int bus, uint8_t addr, uint8_t cmd, bool smbusPEC,
59 std::vector<uint8_t>& resp)
60 {
61 int32_t size = 0;
62 std::filesystem::path devpath = "/dev/i2c-" + std::to_string(bus);
63
64 try
65 {
66 FileHandle fileHandle(devpath);
67
68 /* Select the target device */
69 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
70 if (::ioctl(fileHandle.handle(), I2C_SLAVE, addr) == -1)
71 {
72 lg2::error(
73 "Failed to configure device address '{ADDR}' for bus '{BUS}': '{ERRNO}'",
74 "ADDR", lg2::hex, addr, "BUS", bus, "ERRNO", lg2::hex, errno);
75 resp.resize(0);
76 return;
77 }
78
79 if (smbusPEC)
80 {
81 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
82 if (::ioctl(fileHandle.handle(), I2C_PEC, 1) < 0)
83 {
84 lg2::error(
85 "Could not set PEC to device '{ADDR}' for bus '{BUS}': '{ERRNO}'",
86 "ADDR", lg2::hex, addr, "BUS", bus, "ERRNO", errno);
87 return;
88 }
89 }
90
91 resp.resize(UINT8_MAX + 1);
92
93 /* Issue the NVMe MI basic command */
94 size = i2c_smbus_read_block_data(fileHandle.handle(), cmd, resp.data());
95 if (size < 0)
96 {
97 lg2::error(
98 "Failed to read block data from device '{ADDR}' on bus '{BUS}': ERRNO",
99 "ADDR", lg2::hex, addr, "BUS", bus, "ERRNO", lg2::hex, errno);
100 resp.resize(0);
101 }
102 else if (size > UINT8_MAX + 1)
103 {
104 lg2::error(
105 "Unexpected message length from device '{ADDR}' on bus '{BUS}': '{SIZE}' ({MAX})",
106 "ADDR", lg2::hex, addr, "BUS", bus, "SIZE", size, "MAX",
107 UINT8_MAX);
108 resp.resize(0);
109 }
110 else
111 {
112 resp.resize(size);
113 }
114 }
115 catch (const std::out_of_range& e)
116 {
117 lg2::error("Failed to create file handle for bus '{BUS}': '{ERR}'",
118 "BUS", bus, "ERR", e);
119 resp.resize(0);
120 }
121 }
122
processBasicQueryStream(FileHandle & in,FileHandle & out)123 static ssize_t processBasicQueryStream(FileHandle& in, FileHandle& out)
124 {
125 std::vector<uint8_t> resp{};
126 ssize_t rc = 0;
127
128 while (true)
129 {
130 uint8_t len = 0;
131 BasicQueryCmd req{};
132
133 /* Read the command parameters */
134 ssize_t rc = ::read(in.handle(), &req, sizeof(req));
135 if (rc != static_cast<ssize_t>(sizeof(req)))
136 {
137 lg2::error(
138 "Failed to read request from in descriptor '{ERROR_MESSAGE}'",
139 "ERROR_MESSAGE", strerror(errno));
140 if (rc != 0)
141 {
142 return -errno;
143 }
144 return -EIO;
145 }
146
147 /* Execute the query */
148 execBasicQuery(req.bus, req.device, req.offset, req.smbusPEC, resp);
149
150 /* Write out the response length */
151 len = resp.size();
152 rc = ::write(out.handle(), &len, sizeof(len));
153 if (rc != sizeof(len))
154 {
155 lg2::error(
156 "Failed to write block ({LEN}) length to out descriptor: '{ERRNO}'",
157 "LEN", len, "ERRNO", strerror(static_cast<int>(-rc)));
158 if (rc != 0)
159 {
160 return -errno;
161 }
162 return -EIO;
163 }
164
165 /* Write out the response data */
166 std::vector<uint8_t>::iterator cursor = resp.begin();
167 while (cursor != resp.end())
168 {
169 size_t lenRemaining = std::distance(cursor, resp.end());
170 ssize_t egress = ::write(out.handle(), &(*cursor), lenRemaining);
171 if (egress == -1)
172 {
173 lg2::error(
174 "Failed to write block data of length '{LEN}' to out pipe: '{ERROR_MESSAGE}'",
175 "LEN", lenRemaining, "ERROR_MESSAGE", strerror(errno));
176 if (rc != 0)
177 {
178 return -errno;
179 }
180 return -EIO;
181 }
182
183 cursor += egress;
184 }
185 }
186
187 return rc;
188 }
189
190 /* Throws std::error_code on failure */
191 /* FIXME: Probably shouldn't do fallible stuff in a constructor */
NVMeBasicContext(boost::asio::io_context & io,int rootBus)192 NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) :
193 NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
194 {
195 std::array<int, 2> responsePipe{};
196 std::array<int, 2> requestPipe{};
197
198 /* Set up inter-thread communication */
199 if (::pipe(requestPipe.data()) == -1)
200 {
201 lg2::error("Failed to create request pipe: '{ERROR}'", "ERROR",
202 strerror(errno));
203 throw std::error_code(errno, std::system_category());
204 }
205
206 if (::pipe(responsePipe.data()) == -1)
207 {
208 lg2::error("Failed to create response pipe: '{ERROR}'", "ERROR",
209 strerror(errno));
210
211 if (::close(requestPipe[0]) == -1)
212 {
213 lg2::error("Failed to close write fd of request pipe '{ERROR}'",
214 "ERROR", strerror(errno));
215 }
216
217 if (::close(requestPipe[1]) == -1)
218 {
219 lg2::error("Failed to close read fd of request pipe '{ERROR}'",
220 "ERROR", strerror(errno));
221 }
222
223 throw std::error_code(errno, std::system_category());
224 }
225
226 reqStream.assign(requestPipe[1]);
227 FileHandle streamIn(requestPipe[0]);
228 FileHandle streamOut(responsePipe[1]);
229 respStream.assign(responsePipe[0]);
230
231 thread = std::jthread([streamIn{std::move(streamIn)},
232 streamOut{std::move(streamOut)}]() mutable {
233 ssize_t rc = processBasicQueryStream(streamIn, streamOut);
234
235 if (rc < 0)
236 {
237 lg2::error("Failure while processing query stream: '{ERROR}'",
238 "ERROR", strerror(static_cast<int>(-rc)));
239 }
240
241 lg2::error("Terminating basic query thread");
242 });
243 }
244
readAndProcessNVMeSensor()245 void NVMeBasicContext::readAndProcessNVMeSensor()
246 {
247 if (pollCursor == sensors.end())
248 {
249 this->pollNVMeDevices();
250 return;
251 }
252
253 std::shared_ptr<NVMeSensor> sensor = *pollCursor++;
254
255 if (!sensor->readingStateGood())
256 {
257 sensor->markAvailable(false);
258 sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
259 readAndProcessNVMeSensor();
260 return;
261 }
262
263 /* Potentially defer sampling the sensor sensor if it is in error */
264 if (!sensor->sample())
265 {
266 readAndProcessNVMeSensor();
267 return;
268 }
269
270 if (sensor->bus < 0)
271 {
272 throw std::domain_error("Invalid bus argument");
273 }
274
275 BasicQueryCmd tmpCmd = {sensor->bus, sensor->address, 0x00,
276 sensor->smbusPEC};
277 auto command = std::make_shared<BasicQueryCmd>(tmpCmd);
278
279 /* Issue the request */
280 boost::asio::async_write(
281 reqStream, boost::asio::buffer(command.get(), sizeof(*command)),
282 [command](boost::system::error_code ec, std::size_t) {
283 if (ec)
284 {
285 lg2::error("Got error writing basic query: '{ERROR_MESSAGE}'",
286 "ERROR_MESSAGE", ec.message());
287 }
288 });
289
290 auto response = std::make_shared<boost::asio::streambuf>();
291 response->prepare(1);
292
293 /* Gather the response and dispatch for parsing */
294 boost::asio::async_read(
295 respStream, *response,
296 [response](const boost::system::error_code& ec, std::size_t n) {
297 if (ec)
298 {
299 lg2::error(
300 "Got error completing basic query: '{ERROR_MESSAGE}'",
301 "ERROR_MESSAGE", ec.message());
302 return static_cast<std::size_t>(0);
303 }
304
305 if (n == 0)
306 {
307 return static_cast<std::size_t>(1);
308 }
309
310 std::istream is(response.get());
311 size_t len = static_cast<std::size_t>(is.peek());
312
313 if (n > len + 1)
314 {
315 lg2::error(
316 "Query stream has become unsynchronised: n: {N}, len: {LEN}",
317 "N", n, "LEN", len);
318 return static_cast<std::size_t>(0);
319 }
320
321 if (n == len + 1)
322 {
323 return static_cast<std::size_t>(0);
324 }
325
326 if (n > 1)
327 {
328 return len + 1 - n;
329 }
330
331 response->prepare(len);
332 return len;
333 },
334 [weakSelf{weak_from_this()}, sensor, response](
335 const boost::system::error_code& ec, std::size_t length) mutable {
336 if (ec)
337 {
338 lg2::error("Got error reading basic query: '{ERROR_MESSAGE}'",
339 "ERROR_MESSAGE", ec.message());
340 return;
341 }
342
343 if (length == 0)
344 {
345 lg2::error("Invalid message length: '{LEN}'", "LEN", length);
346 return;
347 }
348
349 if (auto self = weakSelf.lock())
350 {
351 /* Deserialise the response */
352 response->consume(1); /* Drop the length byte */
353 std::istream is(response.get());
354 std::vector<char> data(response->size());
355 is.read(data.data(), response->size());
356
357 /* Update the sensor */
358 self->processResponse(sensor, data.data(), data.size());
359
360 /* Enqueue processing of the next sensor */
361 self->readAndProcessNVMeSensor();
362 }
363 });
364 }
365
pollNVMeDevices()366 void NVMeBasicContext::pollNVMeDevices()
367 {
368 pollCursor = sensors.begin();
369
370 scanTimer.expires_after(std::chrono::seconds(1));
371 scanTimer.async_wait([weakSelf{weak_from_this()}](
372 const boost::system::error_code errorCode) {
373 if (errorCode == boost::asio::error::operation_aborted)
374 {
375 return;
376 }
377
378 if (errorCode)
379 {
380 lg2::error("error code: '{ERROR_MESSAGE}'", "ERROR_MESSAGE",
381 errorCode.message());
382 return;
383 }
384
385 if (auto self = weakSelf.lock())
386 {
387 self->readAndProcessNVMeSensor();
388 }
389 });
390 }
391
getTemperatureReading(int8_t reading)392 static double getTemperatureReading(int8_t reading)
393 {
394 if (reading == static_cast<int8_t>(0x80) ||
395 reading == static_cast<int8_t>(0x81))
396 {
397 // 0x80 = No temperature data or temperature data is more the 5 s
398 // old 0x81 = Temperature sensor failure
399 return std::numeric_limits<double>::quiet_NaN();
400 }
401
402 return reading;
403 }
404
processResponse(std::shared_ptr<NVMeSensor> & sensor,void * msg,size_t len)405 void NVMeBasicContext::processResponse(std::shared_ptr<NVMeSensor>& sensor,
406 void* msg, size_t len)
407 {
408 if (msg == nullptr || len < 6)
409 {
410 sensor->incrementError();
411 return;
412 }
413
414 uint8_t* messageData = static_cast<uint8_t*>(msg);
415
416 uint8_t status = messageData[0];
417 if (((status & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) ||
418 ((status & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0))
419 {
420 sensor->markFunctional(false);
421 return;
422 }
423
424 double value = getTemperatureReading(messageData[2]);
425 if (!std::isfinite(value))
426 {
427 sensor->incrementError();
428 return;
429 }
430
431 sensor->updateValue(value);
432 }
433