#include "NVMeBasicContext.hpp" #include "NVMeContext.hpp" #include "NVMeSensor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } /* * NVMe-MI Basic Management Command * * https://nvmexpress.org/wp-content/uploads/NVMe_Management_-_Technical_Note_on_Basic_Management_Command.pdf */ static std::shared_ptr> encodeBasicQuery(int bus, uint8_t device, uint8_t offset) { if (bus < 0) { throw std::domain_error("Invalid bus argument"); } /* bus + address + command */ uint32_t busle = htole32(static_cast(bus)); auto command = std::make_shared>(); memcpy(command->data(), &busle, sizeof(busle)); (*command)[sizeof(busle) + 0] = device; (*command)[sizeof(busle) + 1] = offset; return command; } static void decodeBasicQuery(const std::array& req, int& bus, uint8_t& device, uint8_t& offset) { uint32_t busle = 0; memcpy(&busle, req.data(), sizeof(busle)); bus = le32toh(busle); device = req[sizeof(busle) + 0]; offset = req[sizeof(busle) + 1]; } static void execBasicQuery(int bus, uint8_t addr, uint8_t cmd, std::vector& resp) { int32_t size = 0; std::filesystem::path devpath = "/dev/i2c-" + std::to_string(bus); try { FileHandle fileHandle(devpath); /* Select the target device */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) if (::ioctl(fileHandle.handle(), I2C_SLAVE, addr) == -1) { std::cerr << "Failed to configure device address 0x" << std::hex << (int)addr << " for bus " << std::dec << bus << ": " << strerror(errno) << "\n"; resp.resize(0); return; } resp.resize(UINT8_MAX + 1); /* Issue the NVMe MI basic command */ size = i2c_smbus_read_block_data(fileHandle.handle(), cmd, resp.data()); if (size < 0) { std::cerr << "Failed to read block data from device 0x" << std::hex << (int)addr << " on bus " << std::dec << bus << ": " << strerror(errno) << "\n"; resp.resize(0); } else if (size > UINT8_MAX + 1) { std::cerr << "Unexpected message length from device 0x" << std::hex << (int)addr << " on bus " << std::dec << bus << ": " << size << " (" << UINT8_MAX << ")\n"; resp.resize(0); } else { resp.resize(size); } } catch (const std::out_of_range& e) { std::cerr << "Failed to create file handle for bus " << std::dec << bus << ": " << e.what() << "\n"; resp.resize(0); } } static ssize_t processBasicQueryStream(FileHandle& in, FileHandle& out) { std::vector resp{}; ssize_t rc = 0; while (true) { uint8_t device = 0; uint8_t offset = 0; uint8_t len = 0; int bus = 0; /* bus + address + command */ std::array req{}; /* Read the command parameters */ ssize_t rc = ::read(in.handle(), req.data(), req.size()); if (rc != static_cast(req.size())) { std::cerr << "Failed to read request from in descriptor " << strerror(errno) << "\n"; if (rc != 0) { return -errno; } return -EIO; } decodeBasicQuery(req, bus, device, offset); /* Execute the query */ execBasicQuery(bus, device, offset, resp); /* Write out the response length */ len = resp.size(); rc = ::write(out.handle(), &len, sizeof(len)); if (rc != sizeof(len)) { std::cerr << "Failed to write block (" << std::dec << len << ") length to out descriptor: " << strerror(static_cast(-rc)) << "\n"; if (rc != 0) { return -errno; } return -EIO; } /* Write out the response data */ std::vector::iterator cursor = resp.begin(); while (cursor != resp.end()) { size_t lenRemaining = std::distance(cursor, resp.end()); ssize_t egress = ::write(out.handle(), &(*cursor), lenRemaining); if (egress == -1) { std::cerr << "Failed to write block data of length " << std::dec << lenRemaining << " to out pipe: " << strerror(errno) << "\n"; if (rc != 0) { return -errno; } return -EIO; } cursor += egress; } } return rc; } /* Throws std::error_code on failure */ /* FIXME: Probably shouldn't do fallible stuff in a constructor */ NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) : NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io) { std::array responsePipe{}; std::array requestPipe{}; /* Set up inter-thread communication */ if (::pipe(requestPipe.data()) == -1) { std::cerr << "Failed to create request pipe: " << strerror(errno) << "\n"; throw std::error_code(errno, std::system_category()); } if (::pipe(responsePipe.data()) == -1) { std::cerr << "Failed to create response pipe: " << strerror(errno) << "\n"; if (::close(requestPipe[0]) == -1) { std::cerr << "Failed to close write fd of request pipe: " << strerror(errno) << "\n"; } if (::close(requestPipe[1]) == -1) { std::cerr << "Failed to close read fd of request pipe: " << strerror(errno) << "\n"; } throw std::error_code(errno, std::system_category()); } reqStream.assign(requestPipe[1]); FileHandle streamIn(requestPipe[0]); FileHandle streamOut(responsePipe[1]); respStream.assign(responsePipe[0]); thread = std::jthread([streamIn{std::move(streamIn)}, streamOut{std::move(streamOut)}]() mutable { ssize_t rc = processBasicQueryStream(streamIn, streamOut); if (rc < 0) { std::cerr << "Failure while processing query stream: " << strerror(static_cast(-rc)) << "\n"; } std::cerr << "Terminating basic query thread\n"; }); } void NVMeBasicContext::readAndProcessNVMeSensor() { if (pollCursor == sensors.end()) { this->pollNVMeDevices(); return; } std::shared_ptr sensor = *pollCursor++; if (!sensor->readingStateGood()) { sensor->markAvailable(false); sensor->updateValue(std::numeric_limits::quiet_NaN()); readAndProcessNVMeSensor(); return; } /* Potentially defer sampling the sensor sensor if it is in error */ if (!sensor->sample()) { readAndProcessNVMeSensor(); return; } auto command = encodeBasicQuery(sensor->bus, sensor->address, 0x00); /* Issue the request */ boost::asio::async_write( reqStream, boost::asio::buffer(command->data(), command->size()), [command](boost::system::error_code ec, std::size_t) { if (ec) { std::cerr << "Got error writing basic query: " << ec << "\n"; } }); auto response = std::make_shared(); response->prepare(1); /* Gather the response and dispatch for parsing */ boost::asio::async_read( respStream, *response, [response](const boost::system::error_code& ec, std::size_t n) { if (ec) { std::cerr << "Got error completing basic query: " << ec << "\n"; return static_cast(0); } if (n == 0) { return static_cast(1); } std::istream is(response.get()); size_t len = static_cast(is.peek()); if (n > len + 1) { std::cerr << "Query stream has become unsynchronised: " << "n: " << n << ", " << "len: " << len << "\n"; return static_cast(0); } if (n == len + 1) { return static_cast(0); } if (n > 1) { return len + 1 - n; } response->prepare(len); return len; }, [weakSelf{weak_from_this()}, sensor, response]( const boost::system::error_code& ec, std::size_t length) mutable { if (ec) { std::cerr << "Got error reading basic query: " << ec << "\n"; return; } if (length == 0) { std::cerr << "Invalid message length: " << length << "\n"; return; } if (auto self = weakSelf.lock()) { /* Deserialise the response */ response->consume(1); /* Drop the length byte */ std::istream is(response.get()); std::vector data(response->size()); is.read(data.data(), response->size()); /* Update the sensor */ self->processResponse(sensor, data.data(), data.size()); /* Enqueue processing of the next sensor */ self->readAndProcessNVMeSensor(); } }); } void NVMeBasicContext::pollNVMeDevices() { pollCursor = sensors.begin(); scanTimer.expires_after(std::chrono::seconds(1)); scanTimer.async_wait([weakSelf{weak_from_this()}]( const boost::system::error_code errorCode) { if (errorCode == boost::asio::error::operation_aborted) { return; } if (errorCode) { std::cerr << errorCode.message() << "\n"; return; } if (auto self = weakSelf.lock()) { self->readAndProcessNVMeSensor(); } }); } static double getTemperatureReading(int8_t reading) { if (reading == static_cast(0x80) || reading == static_cast(0x81)) { // 0x80 = No temperature data or temperature data is more the 5 s // old 0x81 = Temperature sensor failure return std::numeric_limits::quiet_NaN(); } return reading; } void NVMeBasicContext::processResponse(std::shared_ptr& sensor, void* msg, size_t len) { if (msg == nullptr || len < 6) { sensor->incrementError(); return; } uint8_t* messageData = static_cast(msg); uint8_t status = messageData[0]; if (((status & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) || ((status & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0)) { sensor->markFunctional(false); return; } double value = getTemperatureReading(messageData[2]); if (!std::isfinite(value)) { sensor->incrementError(); return; } sensor->updateValue(value); }