xref: /openbmc/dbus-sensors/src/nvme/NVMeBasicContext.cpp (revision 76e0dd56c340cf11344e038979f88f59b71bff46)
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