| #include "NVMeMi.hpp" |
| |
| #include "NVMeError.hpp" |
| #include "NVMeUtil.hpp" |
| |
| #include <endian.h> |
| |
| #include <boost/endian.hpp> |
| |
| #include <cassert> |
| #include <cerrno> |
| #include <fstream> |
| #include <iostream> |
| #include <stdexcept> |
| |
| namespace CommonErr = sdbusplus::xyz::openbmc_project::Common::Error; |
| |
| // libnvme-mi root service |
| nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL); |
| |
| constexpr size_t maxNVMeMILength = 4096; |
| constexpr int tcgDefaultTimeoutMS = 20 * 1000; |
| constexpr int namespaceDefaultTimeoutMS = 20 * 1000; |
| constexpr int sanitizeDefaultTimeoutMS = 20 * 1000; |
| constexpr int downloadDefaultTimeoutMS = 10 * 1000; |
| constexpr int initCmdTimeoutMS = 1000; |
| |
| NVMeMi::NVMeMi(boost::asio::io_context& io, |
| const std::shared_ptr<sdbusplus::asio::connection>& conn, |
| const std::shared_ptr<const MctpDevice>& device, |
| const std::shared_ptr<NVMeMiWorker>& worker, |
| PowerState readState) : |
| io(io), device(device), readState(readState), mctpStatus(Status::Reset), |
| mtu(64), nvmeEP(nullptr), restart(false), worker(worker) |
| { |
| // set update the worker thread |
| if (nvmeRoot == nullptr) |
| { |
| throw std::runtime_error("invalid NVMe root"); |
| } |
| |
| // setup the power state |
| if (readState == PowerState::on || readState == PowerState::biosPost || |
| readState == PowerState::chassisOn) |
| { |
| // life time of the callback is binding to the NVMeMi instance, so only |
| // this capture is required. |
| powerCallback = setupPowerMatchCallback(conn, [this](PowerState, bool) { |
| if (!::readingStateGood(this->readState)) |
| { |
| recover(); |
| } |
| }); |
| } |
| } |
| |
| void NVMeMi::epReset() |
| { |
| switch (mctpStatus) |
| { |
| case Status::Reset: |
| return; |
| case Status::Initiated: |
| if (optimizeTimer) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" |
| << "Cancel the optimization Timer for the endpoint" |
| << '\n'; |
| optimizeTimer->cancel(); |
| } |
| [[fallthrough]]; |
| case Status::Connected: |
| if (nvmeEP == nullptr) |
| { |
| throw std::logic_error( |
| "nvmeEP was unpopulated in Status::Initiated state"); |
| } |
| mctpStatus = Status::Terminating; |
| std::cerr << "[" << endpoint->describe() << "]" |
| << "start MCTP closure" << '\n'; |
| // Immediately reset endpoint so that we can capture the parameter |
| // from a subsequent invocation of start() while in |
| // Status::Terminating |
| endpoint.reset(); |
| // Invoke nvme_mi_close() via a lambda that we schedule via |
| // flushOperations(). Using flushOperations() ensures that any |
| // outstanding tasks are executed before nvme_mi_close() is invoked, |
| // invalidating their controller reference. |
| flushOperations([self{shared_from_this()}]() { |
| nvme_mi_close(self->nvmeEP); |
| self->mtu = 64; |
| self->nvmeEP = nullptr; |
| self->mctpStatus = Status::Reset; |
| std::cerr << "[" << self->device->describe() << "] " |
| << "end MCTP closure" << '\n'; |
| if (self->restart) |
| { |
| // If restart is true then we've captured the updated |
| // endpoint. We pass it to start() to recreate the |
| // connection. |
| self->restart = false; |
| self->start(self->endpoint); |
| } |
| }); |
| return; |
| case Status::Terminating: |
| return; |
| } |
| throw std::logic_error("Unreachable"); |
| } |
| |
| bool NVMeMi::epConnect(int lnid, uint8_t leid) |
| { |
| switch (mctpStatus) |
| { |
| case Status::Reset: |
| if (nvmeEP != nullptr) |
| { |
| throw std::logic_error( |
| "nvmeEP populated in Status::Reset state"); |
| } |
| nvmeEP = nvme_mi_open_mctp(nvmeRoot, lnid, leid); |
| if (nvmeEP != nullptr) |
| { |
| mctpStatus = Status::Initiated; |
| return true; |
| } |
| return false; |
| case Status::Initiated: |
| case Status::Connected: |
| return true; |
| case Status::Terminating: |
| // This isn't an error so much as we're just not ready yet |
| return false; |
| } |
| throw std::logic_error("Unreachable"); |
| } |
| |
| void NVMeMi::epOptimize() |
| { |
| switch (mctpStatus) |
| { |
| case Status::Reset: |
| throw std::logic_error("optimize called from Status::Reset"); |
| case Status::Initiated: |
| /* Continue with optimization below */ |
| break; |
| case Status::Connected: |
| /* Already optimized */ |
| return; |
| case Status::Terminating: |
| throw std::logic_error("optimize called from Status::Terminating"); |
| } |
| optimizeTimer = std::make_shared<boost::asio::steady_timer>( |
| io, std::chrono::milliseconds(500)); |
| optimizeTimer->async_wait([this](boost::system::error_code ec) { |
| if (ec) |
| { |
| std::cerr << "Endpoint optimize timer error " << ec << '\n'; |
| return; |
| } |
| miSetMCTPConfiguration( |
| [self{shared_from_this()}](const std::error_code& ec) { |
| self->optimizeTimer = nullptr; |
| if (ec) |
| { |
| std::cerr << "[" << self->device->describe() << "]" |
| << "Failed setting up MTU for the MCTP endpoint." |
| << '\n'; |
| self->recover(); |
| return; |
| } |
| self->configureLocalRouteMtu([self](const std::error_code& ec) { |
| if (ec) |
| { |
| self->recover(); |
| return; |
| } |
| self->mctpStatus = Status::Connected; |
| }); |
| }); |
| }); |
| } |
| |
| void NVMeMi::recover() |
| { |
| switch (mctpStatus) |
| { |
| case Status::Reset: |
| return; |
| case Status::Initiated: |
| case Status::Connected: |
| endpoint->recover(); |
| return; |
| case Status::Terminating: |
| return; |
| } |
| throw std::logic_error("Unreachable"); |
| } |
| |
| void NVMeMi::start(const std::shared_ptr<MctpEndpoint>& ep) |
| { |
| if (mctpStatus == Status::Terminating) |
| { |
| endpoint = ep; |
| this->restart = true; |
| return; |
| } |
| |
| if (mctpStatus == Status::Reset) |
| { |
| endpoint = ep; |
| // open mctp endpoint |
| if (!epConnect(endpoint->network(), endpoint->eid())) |
| { |
| epReset(); |
| std::cerr << "[" << ep->describe() << "]" |
| << "can't open MCTP endpoint " << '\n'; |
| return; |
| } |
| } |
| |
| if (mctpStatus == Status::Initiated) |
| { |
| epOptimize(); |
| } |
| } |
| |
| void NVMeMi::stop() |
| { |
| restart = false; |
| epReset(); |
| } |
| |
| std::optional<std::error_code> NVMeMi::isEndpointDegraded() const |
| { |
| switch (mctpStatus) |
| { |
| case Status::Reset: |
| return std::make_error_code(std::errc::no_such_device); |
| case Status::Initiated: |
| return std::make_error_code(std::errc::not_connected); |
| case Status::Connected: |
| return std::nullopt; |
| case Status::Terminating: |
| return std::make_error_code(std::errc::not_connected); |
| } |
| throw std::logic_error("Unreachable"); |
| } |
| |
| NVMeMiWorker::NVMeMiWorker() |
| { // start worker thread |
| thread = std::thread([&io = workerIO, &stop = workerStop, &mtx = workerMtx, |
| &cv = workerCv, &isNotified = workerIsNotified]() { |
| // With BOOST_ASIO_DISABLE_THREADS, boost::asio::executor_work_guard |
| // issues null_event across the thread, which caused invalid invokation. |
| // We implement a simple invoke machenism based std::condition_variable. |
| while (true) |
| { |
| io.run(); |
| io.restart(); |
| { |
| std::unique_lock<std::mutex> lock(mtx); |
| cv.wait(lock, [&]() { return isNotified; }); |
| isNotified = false; |
| |
| if (stop) |
| { |
| // exhaust all tasks and exit |
| io.run(); |
| break; |
| } |
| } |
| } |
| }); |
| } |
| |
| NVMeMiWorker::~NVMeMiWorker() |
| { |
| // close worker |
| workerStop = true; |
| { |
| std::unique_lock<std::mutex> lock(workerMtx); |
| workerIsNotified = true; |
| workerCv.notify_all(); |
| } |
| thread.join(); |
| } |
| NVMeMi::~NVMeMi() |
| { |
| // If we're being destructed the only thing left to do is to clean up the |
| // endpoint connection. We're in the destructor because the last shared |
| // reference has been dropped, which means it must be the case that no |
| // worker jobs remain queued that reference the instance. |
| // |
| // We can't call epReset() here via stop() as was originally the case. |
| // epReset() prepares the NVMeMi instance for a subsequent epConnect() using |
| // shared_from_this(), which will yield a std::bad_weak_ptr now that we're |
| // in the destructor. Moreover, a subsequent epConnect() is not possible |
| // beyond this point. |
| if (nvmeEP != nullptr) |
| { |
| nvme_mi_close(nvmeEP); |
| } |
| } |
| |
| void NVMeMiWorker::post(std::function<void(void)>&& func) |
| { |
| if (!workerStop) |
| { |
| std::unique_lock<std::mutex> lock(workerMtx); |
| if (!workerStop) |
| { |
| workerIsNotified = true; |
| workerIO.post(std::move(func)); |
| workerCv.notify_all(); |
| return; |
| } |
| } |
| throw std::runtime_error("NVMeMi has been stopped"); |
| } |
| |
| void NVMeMi::post(std::function<void(void)>&& func) |
| { |
| worker->post([func{std::move(func)}]() { func(); }); |
| } |
| |
| // Calls .post(), catching runtime_error and returning an error code on failure. |
| std::error_code NVMeMi::tryPost(std::function<void(void)>&& func) |
| { |
| try |
| { |
| post([self{shared_from_this()}, func{std::move(func)}]() { func(); }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << device->describe() << "]" << e.what() << '\n'; |
| return std::make_error_code(std::errc::no_such_device); |
| } |
| return {}; |
| } |
| |
| void NVMeMi::miConfigureSMBusFrequency( |
| uint8_t portId, uint8_t /*maxSupportedFreq*/, |
| std::function<void(const std::error_code&)>&& cb) |
| { |
| if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << "nvme endpoint is invalid" << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| return; |
| } |
| try |
| { |
| post([portId, ep{endpoint}, self{shared_from_this()}, |
| cb{std::move(cb)}]() mutable { |
| enum nvme_mi_config_smbus_freq smbusFreq = {}; |
| auto rc = nvme_mi_mi_config_get_smbus_freq(self->nvmeEP, portId, |
| &smbusFreq); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() |
| << "] failed to get the SMBus frequency " << '\n'; |
| } |
| else if (smbusFreq == NVME_MI_CONFIG_SMBUS_FREQ_100kHz) |
| { |
| std::cerr << "[" << ep->describe() |
| << "] Setting the SMBus frequency to 400kHz\n"; |
| rc = nvme_mi_mi_config_set_smbus_freq( |
| self->nvmeEP, portId, NVME_MI_CONFIG_SMBUS_FREQ_400kHz); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() |
| << "] failed to set the SMBus frequency\n"; |
| } |
| } |
| if (rc != 0) |
| { |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::bad_message)); |
| }); |
| return; |
| } |
| self->io.post([cb{std::move(cb)}]() { cb({}); }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << device->describe() << "]" << e.what() << '\n'; |
| return; |
| } |
| } |
| |
| void NVMeMi::miConfigureRemoteMCTP( |
| uint8_t port, uint16_t mtu, uint8_t maxSupportedFreq, |
| std::function<void(const std::error_code&)>&& cb) |
| { |
| if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating) |
| { |
| std::cerr << "[" << device->describe() << "] " |
| << "nvme endpoint is invalid" << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| return; |
| } |
| try |
| { |
| post([port, mtu, maxSupportedFreq, self{shared_from_this()}, |
| cb{std::move(cb)}]() mutable { |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, initCmdTimeoutMS); |
| auto rc = nvme_mi_mi_config_set_mctp_mtu(self->nvmeEP, port, mtu); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| if (rc != 0) |
| { |
| std::cerr << "[" << self->device->describe() << "]" |
| << " failed to set remote MCTP MTU for port :" |
| << unsigned(port) << '\n'; |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::bad_message)); |
| }); |
| return; |
| } |
| self->mtu = mtu; |
| if (maxSupportedFreq >= 2) |
| { |
| self->io.post([self, port, maxSupportedFreq, |
| cb{std::move(cb)}]() mutable { |
| self->miConfigureSMBusFrequency(port, maxSupportedFreq, |
| std::move(cb)); |
| }); |
| return; |
| } |
| self->io.post([cb{std::move(cb)}]() { cb({}); }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << device->describe() << "] " << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::miSetMCTPConfiguration( |
| std::function<void(const std::error_code&)>&& cb) |
| { |
| if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating) |
| { |
| std::cerr << "[" << device->describe() << "] " |
| << "nvme endpoint is invalid" << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| return; |
| } |
| try |
| { |
| post([cb{std::move(cb)}, self{shared_from_this()}]() mutable { |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, initCmdTimeoutMS); |
| struct nvme_mi_read_nvm_ss_info ssInfo = {}; |
| auto rc = nvme_mi_mi_read_mi_data_subsys(self->nvmeEP, &ssInfo); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| if (rc != 0) |
| { |
| std::cerr << "[" << self->device->describe() << "] " |
| << "Failed reading subsystem info failing " << '\n'; |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::bad_message)); |
| }); |
| return; |
| } |
| |
| for (uint8_t portId = 0; portId <= ssInfo.nump; portId++) |
| { |
| struct nvme_mi_read_port_info portInfo = {}; |
| auto rc = nvme_mi_mi_read_mi_data_port(self->nvmeEP, portId, |
| &portInfo); |
| if (rc != 0) |
| { |
| /* PCIe port might not be ready right after AC/DC cycle. */ |
| std::cerr << "[" << self->device->describe() |
| << "] failed reading port info for port_id: " |
| << unsigned(portId) << '\n'; |
| } |
| else if (portInfo.portt == 0x2) |
| { |
| // SMBus ports = 0x2 |
| uint16_t supportedMtu = portInfo.mmctptus; |
| uint8_t supportedFreq = portInfo.smb.mme_freq; // NOLINT |
| self->io.post([self, portId, supportedMtu, supportedFreq, |
| cb{std::move(cb)}]() mutable { |
| self->miConfigureRemoteMCTP( |
| portId, supportedMtu, supportedFreq, std::move(cb)); |
| }); |
| return; |
| } |
| } |
| // Didn't find the SMbus port |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << device->describe() << "]" << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device)); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::configureLocalRouteMtu( |
| std::function<void(const std::error_code& ec)>&& completed, int retries) |
| { |
| const uint32_t mctpMtu = mtu + 4; |
| std::function<void(const std::error_code& ec)> retry = |
| [this, cb{std::move(completed)}, |
| retries](const std::error_code& ec) mutable { |
| if (!endpoint) |
| { |
| std::cerr << "[" << device->describe() << "] " |
| << "failed to set MCTP path MTU: Status::Terminating" |
| << '\n'; |
| cb(ec); |
| return; |
| } |
| |
| if (!ec) |
| { |
| std::cout << "[" << endpoint->describe() << "] " |
| << "Finished MCTP initialization. MTU: " << mtu << '\n'; |
| cb(ec); |
| return; |
| } |
| |
| retries--; |
| if (retries == 0) |
| { |
| std::cerr << "[" << endpoint->describe() << "] " |
| << "failed to set MCTP path MTU: " << ec.message() |
| << '\n'; |
| cb(ec); |
| return; |
| } |
| |
| std::cerr << "[" << endpoint->describe() << "] " |
| << "retry to set MCTP path MTU" << '\n'; |
| configureLocalRouteMtu(std::move(cb), retries); |
| }; |
| endpoint->setMtu(mctpMtu, std::move(retry)); |
| } |
| |
| void NVMeMi::miSubsystemHealthStatusPoll( |
| std::function<void(const std::error_code&, nvme_mi_nvm_ss_health_status*)>&& |
| cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| |
| io.post([cb{std::move(cb)}, errc{degraded.value()}]() { |
| cb(errc, nullptr); |
| }); |
| return; |
| } |
| |
| try |
| { |
| post([self{shared_from_this()}, ep{endpoint}, cb{std::move(cb)}]() { |
| nvme_mi_nvm_ss_health_status ssHealth = {}; |
| auto rc = nvme_mi_mi_subsystem_health_status_poll(self->nvmeEP, |
| true, &ssHealth); |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << " fail to subsystem_health_status_poll: " |
| << std::strerror(errno) << '\n'; |
| self->io.post([cb{cb}, lastErrno{errno}]() mutable { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), |
| nullptr); |
| }); |
| return; |
| } |
| if (rc > 0) |
| { |
| std::string_view errMsg = |
| statusToString(static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr << "[" << ep->describe() << "]" |
| << " fail to subsystem_health_status_poll: " << errMsg |
| << '\n'; |
| self->io.post([cb{cb}]() mutable { |
| cb(std::make_error_code(std::errc::bad_message), nullptr); |
| }); |
| return; |
| } |
| |
| self->io.post( |
| [cb{cb}, ssHealth{ssHealth}]() mutable { cb({}, &ssHealth); }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << device->describe() << "]" << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), {}); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::miScanCtrl(std::function<void(const std::error_code&, |
| const std::vector<nvme_mi_ctrl_t>&)> |
| cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| |
| io.post( |
| [cb{std::move(cb)}, errc{degraded.value()}]() { cb(errc, {}); }); |
| return; |
| } |
| |
| try |
| { |
| post([self{shared_from_this()}, ep{endpoint}, cb{std::move(cb)}]() { |
| int rc = nvme_mi_scan_ep(self->nvmeEP, true); |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to scan controllers: " |
| << std::strerror(errno) << '\n'; |
| self->io.post([cb{cb}, lastErrno{errno}]() mutable { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), |
| {}); |
| }); |
| return; |
| } |
| if (rc > 0) |
| { |
| std::string_view errMsg = |
| statusToString(static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to scan controllers: " << errMsg << '\n'; |
| self->io.post([cb{cb}]() mutable { |
| cb(std::make_error_code(std::errc::bad_message), {}); |
| }); |
| return; |
| } |
| |
| std::vector<nvme_mi_ctrl_t> list; |
| nvme_mi_ctrl_t c = {}; |
| nvme_mi_for_each_ctrl(self->nvmeEP, c) |
| { |
| list.push_back(c); |
| } |
| self->io.post( |
| [cb{cb}, list{std::move(list)}]() mutable { cb({}, list); }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), {}); |
| }); |
| return; |
| } |
| } |
| |
| bool NVMeMi::flushOperations(std::function<void()>&& cb) |
| { |
| try |
| { |
| post([self{shared_from_this()}, cb{std::move(cb)}]() { |
| self->io.post(cb); |
| }); |
| |
| return true; |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "Runtime error: " << e.what() << '\n'; |
| return false; |
| } |
| } |
| |
| void NVMeMi::adminIdentify( |
| nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, uint16_t cntid, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| io.post([cb{std::move(cb)}, errc{degraded.value()}]() { |
| cb(makeLibNVMeError("nvme endpoint is degraded"), {}); |
| }); |
| return; |
| } |
| try |
| { |
| post([ctrl, cns, nsid, cntid, self{shared_from_this()}, ep{endpoint}, |
| cb{std::move(cb)}]() { |
| int rc = 0; |
| std::vector<uint8_t> data; |
| switch (cns) |
| { |
| case NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST: |
| { |
| data.resize(sizeof(nvme_secondary_ctrl_list)); |
| nvme_identify_args args{}; |
| memset(&args, 0, sizeof(args)); |
| args.result = nullptr; |
| args.data = data.data(); |
| args.args_size = sizeof(args); |
| args.cns = cns; |
| args.csi = NVME_CSI_NVM; |
| args.nsid = nsid; |
| args.cntid = cntid; |
| args.cns_specific_id = NVME_CNSSPECID_NONE; |
| args.uuidx = NVME_UUID_NONE, |
| |
| rc = nvme_mi_admin_identify_partial(ctrl, &args, 0, |
| data.size()); |
| |
| break; |
| } |
| |
| default: |
| { |
| data.resize(NVME_IDENTIFY_DATA_SIZE); |
| nvme_identify_args args{}; |
| memset(&args, 0, sizeof(args)); |
| args.result = nullptr; |
| args.data = data.data(); |
| args.args_size = sizeof(args); |
| args.cns = cns; |
| args.csi = NVME_CSI_NVM; |
| args.nsid = nsid; |
| args.cntid = cntid; |
| args.cns_specific_id = NVME_CNSSPECID_NONE; |
| args.uuidx = NVME_UUID_NONE, |
| |
| rc = nvme_mi_admin_identify(ctrl, &args); |
| } |
| } |
| |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to do nvme identify: " |
| << std::strerror(errno) << '\n'; |
| } |
| else if (rc > 0) |
| { |
| std::string_view errMsg = |
| statusToString(static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to do nvme identify: " << errMsg << '\n'; |
| } |
| |
| auto ex = makeLibNVMeError(errno, rc, "adminIdentify"); |
| if (ex) |
| { |
| std::cerr << "fail to do nvme identify: " << ex->description() |
| << '\n'; |
| } |
| |
| self->io.post([cb{cb}, ex, data{std::move(data)}]() mutable { |
| std::span<uint8_t> span{data.data(), data.size()}; |
| cb(ex, span); |
| }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" << e.what() << '\n'; |
| auto msg = std::string("Runtime error: ") + e.what(); |
| std::cerr << msg << '\n'; |
| io.post([cb{std::move(cb)}, msg]() { cb(makeLibNVMeError(msg), {}); }); |
| return; |
| } |
| } |
| |
| static int nvmeMiAdminGetLogTelemetryHostRae(nvme_mi_ctrl_t ctrl, bool /*rae*/, |
| __u64 offset, __u32 len, void* log) |
| { |
| return nvme_mi_admin_get_log_telemetry_host(ctrl, offset, len, log); |
| } |
| |
| // Get Temetery Log header and return the size for hdr + data area (Area 1, 2, |
| // 3, or maybe 4) |
| int getTelemetryLogSize(nvme_mi_ctrl_t ctrl, bool host, uint32_t& size) |
| { |
| int rc = 0; |
| nvme_telemetry_log log = {}; |
| auto func = host ? nvmeMiAdminGetLogTelemetryHostRae |
| : nvme_mi_admin_get_log_telemetry_ctrl; |
| |
| // Only host telemetry log requires create. |
| if (host) |
| { |
| rc = nvme_mi_admin_get_log_create_telemetry_host(ctrl, &log); |
| if (rc != 0) |
| { |
| std::cerr << "failed to create telemetry host log" << '\n'; |
| return rc; |
| } |
| } |
| |
| rc = func(ctrl, false, 0, sizeof(log), &log); |
| |
| if (rc != 0) |
| { |
| std::cerr << "failed to retain telemetry log for " |
| << (host ? "host" : "ctrl") << '\n'; |
| return rc; |
| } |
| |
| // Restrict the telemetry log to Data Area 1 and 2. Getting Data Area 3 |
| // OOB is not suitable due to its possible size. Data Area 3 can be up to |
| // 30000 data blocks with each block being 512 bytes in size. Restricting |
| // to Area 1 and 2. |
| size = static_cast<uint32_t>( |
| (boost::endian::little_to_native(log.dalb2) + 1)) * |
| NVME_LOG_TELEM_BLOCK_SIZE; |
| return rc; |
| } |
| |
| void NVMeMi::getTelemetryLogChunk( |
| nvme_mi_ctrl_t ctrl, bool host, uint64_t offset, |
| std::vector<uint8_t>&& data, |
| std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb) |
| { |
| if (offset >= data.size()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << "get telemetry log: offset exceed the log size. " |
| << "offset: " << offset << ", size: " << data.size() << '\n'; |
| cb(std::make_error_code(std::errc::invalid_argument), {}); |
| return; |
| } |
| |
| post([self{shared_from_this()}, ctrl, host, offset, data{std::move(data)}, |
| cb{std::move(cb)}]() mutable { |
| int rc = 0; |
| bool rae = true; |
| auto func = host ? nvmeMiAdminGetLogTelemetryHostRae |
| : nvme_mi_admin_get_log_telemetry_ctrl; |
| uint32_t size = 0; |
| |
| // final transaction |
| if (offset + nvmeMiXferSize >= data.size()) |
| { |
| rae = false; |
| } |
| size = std::min(static_cast<uint32_t>(nvmeMiXferSize), |
| static_cast<uint32_t>(data.size() - offset)); |
| |
| rc = func(ctrl, rae, offset, size, data.data() + offset); |
| |
| if (rc < 0) |
| { |
| std::cerr << "[" << self->device->describe() << "]" |
| << "fail to get chunk for telemetry log: " |
| << std::strerror(errno) << '\n'; |
| boost::asio::post(self->io, |
| [cb{std::move(cb)}, lastErrno{errno}]() { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), {}); |
| }); |
| return; |
| } |
| if (rc > 0) |
| { |
| std::string_view errMsg = |
| statusToString(static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr << "[" << self->device->describe() << "]" |
| << "fail to get chunk for telemetry log: " << errMsg |
| << '\n'; |
| boost::asio::post(self->io, [cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::bad_message), {}); |
| }); |
| return; |
| } |
| |
| if (!rae) |
| { |
| boost::asio::post( |
| self->io, [cb{std::move(cb)}, data{std::move(data)}]() mutable { |
| std::span<uint8_t> span{data.data(), data.size()}; |
| cb({}, span); |
| }); |
| return; |
| } |
| |
| offset += size; |
| boost::asio::post(self->io, |
| [self, ctrl, host, offset, data{std::move(data)}, |
| cb{std::move(cb)}]() mutable { |
| self->getTelemetryLogChunk(ctrl, host, offset, std::move(data), |
| std::move(cb)); |
| }); |
| }); |
| } |
| |
| void NVMeMi::adminGetLogPage( |
| nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid, |
| uint8_t /*lsp*/, uint16_t /*lsi*/, |
| std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| io.post( |
| [cb{std::move(cb)}, errc{degraded.value()}]() { cb(errc, {}); }); |
| return; |
| } |
| |
| try |
| { |
| post([ctrl, nsid, lid, self{shared_from_this()}, ep{endpoint}, |
| cb{std::move(cb)}]() { |
| std::vector<uint8_t> data; |
| std::function<void(void)> logHandler; |
| int rc = 0; |
| int logId = lid; |
| switch (logId) |
| { |
| case NVME_LOG_LID_ERROR: |
| { |
| data.resize(nvmeMiXferSize); |
| // The number of entries for most recent error logs. |
| // Currently we only do one nvme mi transfer for the |
| // error log to avoid blocking other tasks |
| static constexpr int num = nvmeMiXferSize / |
| sizeof(nvme_error_log_page); |
| nvme_error_log_page* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_error_log_page*>(data.data()); |
| |
| rc = nvme_mi_admin_get_log_error(ctrl, num, false, log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get error log" << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_SMART: |
| { |
| data.resize(sizeof(nvme_smart_log)); |
| nvme_smart_log* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_smart_log*>(data.data()); |
| rc = nvme_mi_admin_get_log_smart(ctrl, nsid, false, log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get smart log" << '\n'; |
| break; |
| } |
| } |
| break; |
| /* Begin of patch context |
| * |
| * |
| * |
| */ |
| /* |
| * |
| * |
| * End of patch context |
| */ |
| case NVME_LOG_LID_FW_SLOT: |
| { |
| data.resize(sizeof(nvme_firmware_slot)); |
| nvme_firmware_slot* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_firmware_slot*>(data.data()); |
| rc = nvme_mi_admin_get_log_fw_slot(ctrl, false, log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get firmware slot" << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_CMD_EFFECTS: |
| { |
| data.resize(sizeof(nvme_cmd_effects_log)); |
| nvme_cmd_effects_log* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_cmd_effects_log*>(data.data()); |
| |
| // nvme rev 1.3 doesn't support csi, |
| // set to default csi = NVME_CSI_NVM |
| rc = nvme_mi_admin_get_log_cmd_effects(ctrl, NVME_CSI_NVM, |
| log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get cmd supported and effects log" |
| << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_DEVICE_SELF_TEST: |
| { |
| data.resize(sizeof(nvme_self_test_log)); |
| nvme_self_test_log* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_self_test_log*>(data.data()); |
| rc = nvme_mi_admin_get_log_device_self_test(ctrl, log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get device self test log" << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_CHANGED_NS: |
| { |
| data.resize(sizeof(nvme_ns_list)); |
| nvme_ns_list* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_ns_list*>(data.data()); |
| rc = nvme_mi_admin_get_log_changed_ns_list(ctrl, false, |
| log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get changed namespace list" |
| << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_TELEMETRY_HOST: |
| // fall through to NVME_LOG_LID_TELEMETRY_CTRL |
| case NVME_LOG_LID_TELEMETRY_CTRL: |
| { |
| bool host = lid == NVME_LOG_LID_TELEMETRY_HOST; |
| |
| uint32_t size = 0; |
| rc = getTelemetryLogSize(ctrl, host, size); |
| if (rc == 0) |
| { |
| data.resize(size); |
| logHandler = [self, ctrl, host, data{data}, |
| cb{cb}]() mutable { |
| self->getTelemetryLogChunk( |
| ctrl, host, 0, std::move(data), std::move(cb)); |
| }; |
| } |
| } |
| break; |
| case NVME_LOG_LID_RESERVATION: |
| { |
| data.resize(sizeof(nvme_resv_notification_log)); |
| nvme_resv_notification_log* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_resv_notification_log*>( |
| data.data()); |
| |
| int rc = nvme_mi_admin_get_log_reservation(ctrl, false, |
| log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get reservation " |
| "notification log" |
| << '\n'; |
| break; |
| } |
| } |
| break; |
| case NVME_LOG_LID_SANITIZE: |
| { |
| data.resize(sizeof(nvme_sanitize_log_page)); |
| nvme_sanitize_log_page* log = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_sanitize_log_page*>(data.data()); |
| |
| int rc = nvme_mi_admin_get_log_sanitize(ctrl, false, log); |
| if (rc != 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get sanitize status log" << '\n'; |
| break; |
| } |
| } |
| break; |
| default: |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "unknown lid for GetLogPage" << '\n'; |
| rc = -1; |
| errno = EINVAL; |
| } |
| } |
| |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get log page: " << std::strerror(errno) |
| << '\n'; |
| logHandler = [cb{cb}, lastErrno{errno}]() mutable { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), |
| {}); |
| }; |
| } |
| else if (rc > 0) |
| { |
| std::string_view errMsg = |
| statusToString(static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to get log pag: " << errMsg << '\n'; |
| logHandler = [cb{cb}]() mutable { |
| cb(std::make_error_code(std::errc::bad_message), {}); |
| }; |
| } |
| |
| if (!logHandler) |
| { |
| logHandler = [cb{cb}, data{std::move(data)}]() mutable { |
| std::span<uint8_t> span{data.data(), data.size()}; |
| cb({}, span); |
| }; |
| } |
| boost::asio::post(self->io, logHandler); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" |
| << "NVMeMi adminGetLogPage throws: " << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), {}); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::adminXfer( |
| nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& adminReq, |
| std::span<uint8_t> data, unsigned int timeoutMs, |
| std::function<void(const std::error_code&, const nvme_mi_admin_resp_hdr&, |
| std::span<uint8_t>)>&& cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| io.post([cb{std::move(cb)}, errc{degraded.value()}]() { |
| cb(errc, {}, {}); |
| }); |
| return; |
| } |
| |
| try |
| { |
| std::vector<uint8_t> req(sizeof(nvme_mi_admin_req_hdr) + data.size()); |
| memcpy(req.data(), &adminReq, sizeof(nvme_mi_admin_req_hdr)); |
| memcpy(req.data() + sizeof(nvme_mi_admin_req_hdr), data.data(), |
| data.size()); |
| post([ctrl, req{std::move(req)}, self{shared_from_this()}, ep{endpoint}, |
| timeoutMs, cb{std::move(cb)}]() mutable { |
| int rc = 0; |
| |
| nvme_mi_admin_req_hdr* reqHeader = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_mi_admin_req_hdr*>(req.data()); |
| // set response data size == req DL. Only if data passed is empty |
| size_t respDataSize = 0; |
| if (req.size() == sizeof(nvme_mi_admin_req_hdr)) |
| { |
| respDataSize = |
| boost::endian::little_to_native<size_t>(reqHeader->dlen); |
| } |
| off_t respDataOffset = |
| boost::endian::little_to_native<off_t>(reqHeader->doff); |
| size_t bufSize = sizeof(nvme_mi_admin_resp_hdr) + respDataSize; |
| std::vector<uint8_t> buf(bufSize); |
| nvme_mi_admin_resp_hdr* respHeader = |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| reinterpret_cast<nvme_mi_admin_resp_hdr*>(buf.data()); |
| |
| // set timeout |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeoutMs); |
| |
| rc = nvme_mi_admin_xfer(ctrl, reqHeader, |
| req.size() - sizeof(nvme_mi_admin_req_hdr), |
| respHeader, respDataOffset, &respDataSize); |
| // revert to previous timeout |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "failed to nvme_mi_admin_xfer" << '\n'; |
| self->io.post([cb{std::move(cb)}, lastErrno{errno}]() { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), |
| {}, {}); |
| }); |
| return; |
| } |
| // the MI interface will only consume protocol/io errors |
| // The client will take the reponsibility to deal with nvme-mi |
| // status flag and nvme status field(cwd3). cmd specific return |
| // value (cdw0) is also client's job. |
| |
| buf.resize(sizeof(nvme_mi_admin_resp_hdr) + respDataSize); |
| self->io.post([cb{std::move(cb)}, data{std::move(buf)}]() mutable { |
| std::span<uint8_t> span( |
| data.begin() + sizeof(nvme_mi_admin_resp_hdr), data.end()); |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
| cb({}, *reinterpret_cast<nvme_mi_admin_resp_hdr*>(data.data()), |
| span); |
| }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), {}, {}); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::adminFwCommit( |
| nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| io.post([cb{std::move(cb)}, errc{degraded.value()}]() { |
| cb(errc, nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| try |
| { |
| nvme_fw_commit_args args{}; |
| memset(&args, 0, sizeof(args)); |
| args.args_size = sizeof(args); |
| args.action = action; |
| args.slot = slot; |
| args.bpid = bpid; |
| io.post([ctrl, args, cb{std::move(cb)}, ep{endpoint}, |
| self{shared_from_this()}]() mutable { |
| int rc = nvme_mi_admin_fw_commit(ctrl, &args); |
| if (rc < 0) |
| { |
| std::cerr << "[" << ep->describe() << "]" |
| << "fail to nvme_mi_admin_fw_commit: " |
| << std::strerror(errno) << '\n'; |
| self->io.post([cb{std::move(cb)}, lastErrno{errno}]() { |
| cb(std::make_error_code(static_cast<std::errc>(lastErrno)), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| if (rc >= 0) |
| { |
| switch (rc & 0x7ff) |
| { |
| case NVME_SC_SUCCESS: |
| case NVME_SC_FW_NEEDS_CONV_RESET: |
| case NVME_SC_FW_NEEDS_SUBSYS_RESET: |
| case NVME_SC_FW_NEEDS_RESET: |
| self->io.post([rc, cb{std::move(cb)}]() { |
| cb({}, static_cast<nvme_status_field>(rc)); |
| }); |
| break; |
| default: |
| std::string_view errMsg = statusToString( |
| static_cast<nvme_mi_resp_status>(rc)); |
| std::cerr |
| << "fail to nvme_mi_admin_fw_commit: " << errMsg |
| << '\n'; |
| self->io.post([rc, cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::bad_message), |
| static_cast<nvme_status_field>(rc)); |
| }); |
| } |
| return; |
| } |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << "[" << endpoint->describe() << "]" << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::adminFwDownloadChunk( |
| nvme_mi_ctrl_t ctrl, const std::string& firmwarefile, size_t size, |
| size_t offset, int attemptCount, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb) |
| { |
| if (auto degraded = isEndpointDegraded()) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << " MCTP connection is not established" << '\n'; |
| io.post([cb{std::move(cb)}, errc{degraded.value()}]() { |
| cb(errc, nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| try |
| { |
| post([ctrl, firmwarefile, size, offset, attemptCount, cb{std::move(cb)}, |
| self{shared_from_this()}]() mutable { |
| std::array<char, nvmeMiXferSize> data = {}; |
| std::ifstream fwFile(firmwarefile, std::ios::in | std::ios::binary); |
| if (fwFile.fail()) |
| { |
| std::cerr << "fail to open fw image file: " << firmwarefile |
| << strerror(errno) << '\n'; |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(static_cast<std::errc>(errno)), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| fwFile.seekg(static_cast<int64_t>(offset), std::ios::beg); |
| nvme_fw_download_args args = {}; |
| memset(&args, 0, sizeof(args)); |
| args.args_size = sizeof(args); |
| int dataLen = |
| static_cast<int>(std::min(size - offset, nvmeMiXferSize)); |
| fwFile.read((char*)data.data(), dataLen); |
| fwFile.close(); |
| args.offset = offset; |
| args.data_len = dataLen; |
| args.data = (char*)data.data(); |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, downloadDefaultTimeoutMS); |
| int rc = nvme_mi_admin_fw_download(ctrl, &args); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| if (rc < 0) |
| { |
| if (attemptCount > 0) |
| { |
| std::cout << "Retrying the firmware chunk. With Offset :" |
| << offset << " Total firmware Size :" << size |
| << '\n'; |
| attemptCount = attemptCount - 1; |
| } |
| else |
| { |
| std::cerr << "fail to nvme_mi_admin_fw_download: " |
| << std::strerror(errno) << '\n'; |
| self->io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(static_cast<std::errc>(errno)), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| } |
| else |
| { |
| attemptCount = 3; /* Reset the attempt count*/ |
| offset = offset + args.data_len; |
| } |
| if (offset >= size) |
| { |
| std::cout |
| << "Successfully transferred the firmware. Transfer Size : " |
| << offset << " Total Size :" << size << '\n'; |
| self->io.post([rc, cb{std::move(cb)}]() { |
| cb({}, static_cast<nvme_status_field>(rc)); |
| }); |
| return; |
| } |
| self->io.post([self, ctrl, firmwarefile, size, offset, attemptCount, |
| cb{std::move(cb)}]() mutable { |
| self->adminFwDownloadChunk(ctrl, firmwarefile, size, offset, |
| attemptCount, std::move(cb)); |
| }); |
| }); |
| } |
| catch (const std::runtime_error& e) |
| { |
| std::cerr << e.what() << '\n'; |
| io.post([cb{std::move(cb)}]() { |
| cb(std::make_error_code(std::errc::no_such_device), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| return; |
| } |
| } |
| |
| void NVMeMi::adminFwDownload( |
| nvme_mi_ctrl_t ctrl, std::string firmwarefile, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb) |
| { |
| size_t offset = 0; |
| int tryCount = 3; |
| std::ifstream imageFile(firmwarefile, |
| std::ios::in | std::ios::binary | std::ios::ate); |
| if (imageFile.fail()) |
| { |
| std::cerr << "Can't open the firmware file: " << std::strerror(errno) |
| << '\n'; |
| io.post([cb{cb}]() { |
| cb(std::make_error_code(std::errc::no_such_device), |
| nvme_status_field::NVME_SC_MASK); |
| }); |
| } |
| size_t size = imageFile.tellg(); |
| imageFile.close(); |
| adminFwDownloadChunk(ctrl, firmwarefile, size, offset, tryCount, |
| std::move(cb)); |
| } |
| |
| void NVMeMi::adminSecuritySend( |
| nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t protoSpecific, |
| std::span<uint8_t> data, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb) |
| { |
| std::error_code postErr = tryPost( |
| [self{shared_from_this()}, ctrl, proto, protoSpecific, data, cb{cb}]() { |
| struct nvme_security_send_args args = {}; |
| memset(&args, 0x0, sizeof(args)); |
| args.secp = proto; |
| args.spsp0 = protoSpecific & 0xff; |
| args.spsp1 = protoSpecific >> 8; |
| args.nssf = 0; |
| args.data = data.data(); |
| args.data_len = data.size_bytes(); |
| args.args_size = sizeof(struct nvme_security_send_args); |
| |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, tcgDefaultTimeoutMS); |
| int status = nvme_mi_admin_security_send(ctrl, &args); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| self->io.post([cb{cb}, nvmeErrno{errno}, status]() mutable { |
| auto err = std::make_error_code(static_cast<std::errc>(nvmeErrno)); |
| cb(err, status); |
| }); |
| }); |
| if (postErr) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << "adminSecuritySend post failed: " << postErr << '\n'; |
| io.post([cb{std::move(cb)}, postErr]() { cb(postErr, -1); }); |
| } |
| } |
| |
| void NVMeMi::adminSecurityReceive( |
| nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t protoSpecific, |
| uint32_t transferLength, |
| std::function<void(const std::error_code&, int nvmeStatus, |
| std::span<uint8_t> data)>&& cb) |
| { |
| if (transferLength > maxNVMeMILength) |
| { |
| cb(std::make_error_code(std::errc::invalid_argument), -1, {}); |
| return; |
| } |
| |
| std::error_code postErr = |
| tryPost([self{shared_from_this()}, ctrl, proto, protoSpecific, |
| transferLength, cb{cb}]() { |
| std::vector<uint8_t> data(transferLength); |
| |
| struct nvme_security_receive_args args = {}; |
| memset(&args, 0x0, sizeof(args)); |
| args.secp = proto; |
| args.spsp0 = protoSpecific & 0xff; |
| args.spsp1 = protoSpecific >> 8; |
| args.nssf = 0; |
| args.data = data.data(); |
| args.data_len = data.size(); |
| args.args_size = sizeof(struct nvme_security_receive_args); |
| |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, tcgDefaultTimeoutMS); |
| int status = nvme_mi_admin_security_recv(ctrl, &args); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| if (args.data_len > maxNVMeMILength) |
| { |
| std::cerr << "[" << self->device->describe() << "]" |
| << "nvme_mi_admin_security_send returned excess data, " |
| << args.data_len << '\n'; |
| self->io.post([cb]() { |
| cb(std::make_error_code(std::errc::protocol_error), -1, {}); |
| }); |
| return; |
| } |
| |
| data.resize(args.data_len); |
| self->io.post([cb{cb}, nvmeErrno{errno}, status, data]() mutable { |
| std::span<uint8_t> span{data.data(), data.size()}; |
| auto err = std::make_error_code(static_cast<std::errc>(nvmeErrno)); |
| cb(err, status, span); |
| }); |
| }); |
| if (postErr) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << "adminSecurityReceive post failed: " << postErr << '\n'; |
| io.post([cb{std::move(cb)}, postErr]() { cb(postErr, -1, {}); }); |
| } |
| } |
| |
| void NVMeMi::adminNonDataCmd( |
| nvme_mi_ctrl_t ctrl, uint8_t opcode, uint32_t cdw1, uint32_t cdw2, |
| uint32_t cdw3, uint32_t cdw10, uint32_t cdw11, uint32_t cdw12, |
| uint32_t cdw13, uint32_t cdw14, uint32_t cdw15, |
| std::function<void(const std::error_code&, int nvmeStatus, |
| uint32_t comptionDw0)>&& cb) |
| { |
| std::error_code postErr = |
| tryPost([self{shared_from_this()}, ctrl, opcode, cdw1, cdw2, cdw3, |
| cdw10, cdw11, cdw12, cdw13, cdw14, cdw15, cb{cb}]() { |
| uint32_t comptionDw0 = 0; |
| int nvmeStatus = nvme_mi_admin_admin_passthru( |
| ctrl, opcode, 0, 0, cdw1, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, |
| cdw14, cdw15, 0, nullptr, 0, nullptr, 10 * 1000, &comptionDw0); |
| self->io.post( |
| [cb{cb}, nvmeErrno{errno}, nvmeStatus, comptionDw0]() mutable { |
| auto err = std::make_error_code(static_cast<std::errc>(nvmeErrno)); |
| cb(err, nvmeStatus, comptionDw0); |
| }); |
| }); |
| if (postErr) |
| { |
| std::cerr << "[" << device->describe() << "]" |
| << "adminNonDataCmd post failed: " << postErr << '\n'; |
| io.post([cb{std::move(cb)}, postErr]() { cb(postErr, -1, 0); }); |
| } |
| } |
| |
| /* throws a nvme_ex_ptr on failure */ |
| size_t NVMeMi::getBlockSize(nvme_mi_ctrl_t ctrl, size_t lbaFormat) |
| { |
| struct nvme_id_ns id = {}; |
| std::cout << "getblocksize" << '\n'; |
| int status = nvme_mi_admin_identify_ns(ctrl, NVME_NSID_ALL, &id); |
| auto e = makeLibNVMeError(errno, status, "getBlockSize"); |
| if (e) |
| { |
| // TODO: (b/375054188) Exception handling to be refactored |
| // NOLINTNEXTLINE(cert-err09-cpp,cert-err60-cpp,cert-err61-cpp,misc-throw-by-value-catch-by-reference) |
| throw e; |
| } |
| |
| std::cout << "nlbaf " << (int)id.nlbaf << "lbaf " << (int)lbaFormat << '\n'; |
| |
| // Sanity check for the value from the drive |
| size_t maxLbaf = std::min(63, (int)id.nlbaf); |
| |
| // NLBAF is the maximum allowed index (not a count) |
| if (lbaFormat > maxLbaf) |
| { |
| throw makeLibNVMeError("LBA format out of range, maximum is " + |
| std::to_string(maxLbaf), |
| std::make_shared<CommonErr::InvalidArgument>()); |
| } |
| |
| return 1 << id.lbaf[lbaFormat].ds; |
| } |
| |
| /* |
| finished_cb will not be called if submitted_cb is called with a failure. |
| */ |
| void NVMeMi::createNamespace( |
| nvme_mi_ctrl_t ctrl, uint64_t size, size_t lbaFormat, bool metadataAtEnd, |
| std::function<void(nvme_ex_ptr ex)>&& submittedCb, |
| std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& finishedCb) |
| { |
| std::cout << "createns " << (int)gettid() << '\n'; |
| std::error_code postErr = tryPost( |
| [self{shared_from_this()}, ctrl, size, lbaFormat, metadataAtEnd, |
| submittedCb{submittedCb}, finishedCb{std::move(finishedCb)}]() { |
| size_t blockSize = 0; |
| |
| try |
| { |
| blockSize = self->getBlockSize(ctrl, lbaFormat); |
| } |
| // TODO: (b/375054188) Exception handling to be refactored |
| // NOLINTNEXTLINE(cert-err09-cpp,cert-err60-cpp,cert-err61-cpp,misc-throw-by-value-catch-by-reference) |
| catch (nvme_ex_ptr e) |
| { |
| submittedCb(e); |
| return; |
| } |
| |
| if (size % blockSize != 0) |
| { |
| auto msg = |
| std::string("Size must be a multiple of the block size ") + |
| std::to_string(blockSize); |
| submittedCb(makeLibNVMeError( |
| msg, std::make_shared<CommonErr::InvalidArgument>())); |
| return; |
| } |
| |
| uint64_t blocks = size / blockSize; |
| |
| // TODO: this will become nvme_ns_mgmt_host_sw_specified in a newer |
| // libnvme. |
| struct nvme_id_ns data = {}; |
| uint32_t newNsid = 0; |
| |
| uint8_t flbas = 0; |
| if (metadataAtEnd) |
| { |
| flbas |= (1 << 4); |
| } |
| // low 4 bits at 0:3 |
| flbas |= (lbaFormat & 0xf); |
| // high 2 bits at 5:6 |
| flbas |= ((lbaFormat & 0x30) << 1); |
| |
| memset(&data, 0x0, sizeof(data)); |
| data.nsze = ::htole64(blocks); |
| data.ncap = ::htole64(blocks); |
| data.flbas = flbas; |
| |
| std::cout << "verified " << (int)gettid() << '\n'; |
| |
| // submission has been verified. Handle the cb in main thread |
| // concurrently. |
| self->io.post([submittedCb{submittedCb}]() mutable { |
| submittedCb(nvme_ex_ptr()); |
| }); |
| std::cout << "after submitted_cb " << (int)gettid() << '\n'; |
| |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, namespaceDefaultTimeoutMS); |
| int status = nvme_mi_admin_ns_mgmt_create(ctrl, &data, 0, &newNsid); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume"); |
| |
| NVMeNSIdentify newns = { |
| .namespaceId = newNsid, |
| .size = size, |
| .capacity = size, |
| .blockSize = blockSize, |
| .lbaFormat = lbaFormat, |
| .metadataAtEnd = metadataAtEnd, |
| }; |
| |
| self->io.post([finishedCb{finishedCb}, e, newns]() mutable { |
| finishedCb(e, newns); |
| }); |
| |
| #if 0 |
| // TODO testing purposes |
| static uint32_t counter = 20; |
| |
| printf("createNamespace top, sleeping 5 seconds\n"); |
| sleep(5); |
| |
| uint32_t new_ns = counter++; |
| |
| printf("create complete. ns %d\n", new_ns); |
| |
| auto err = std::make_error_code(static_cast<std::errc>(0)); |
| cb(err, 0, new_ns); |
| #endif |
| }); |
| |
| std::cout << "submitted cb " << (int)gettid() << '\n'; |
| |
| if (postErr) |
| { |
| std::cerr << "adminAttachDetachNamespace post failed: " << postErr |
| << '\n'; |
| auto e = makeLibNVMeError(postErr, -1, "createVolume"); |
| io.post([submittedCb{std::move(submittedCb)}, e]() { submittedCb(e); }); |
| } |
| } |
| |
| // Deletes a namespace |
| void NVMeMi::adminDeleteNamespace( |
| nvme_mi_ctrl_t ctrl, uint32_t nsid, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb) |
| { |
| std::error_code postErr = |
| tryPost([self{shared_from_this()}, ctrl, nsid, cb{cb}]() { |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, namespaceDefaultTimeoutMS); |
| int status = nvme_mi_admin_ns_mgmt_delete(ctrl, nsid); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| |
| self->io.post([cb{cb}, nvmeErrno{errno}, status]() mutable { |
| auto err = std::make_error_code(static_cast<std::errc>(nvmeErrno)); |
| cb(err, status); |
| }); |
| }); |
| if (postErr) |
| { |
| std::cerr << "deleteNamespace post failed: " << postErr << '\n'; |
| io.post([cb{std::move(cb)}, postErr]() { cb(postErr, -1); }); |
| } |
| } |
| |
| void NVMeMi::adminListNamespaces( |
| nvme_mi_ctrl_t ctrl, |
| std::function<void(nvme_ex_ptr, std::vector<uint32_t> ns)>&& cb) |
| { |
| std::error_code postErr = |
| tryPost([self{shared_from_this()}, ctrl, cb{cb}]() { |
| int status = 0; |
| int nvmeErrno = 0; |
| std::vector<uint32_t> ns; |
| // sanity in case of bad drives, allows for >1million NSes |
| const int maxIter = 1000; |
| |
| for (int i = 0; i < maxIter; i++) |
| { |
| struct nvme_ns_list list = {}; |
| uint32_t start = NVME_NSID_NONE; |
| if (!ns.empty()) |
| { |
| start = ns.back() + 1; |
| } |
| status = nvme_mi_admin_identify_active_ns_list(ctrl, start, &list); |
| nvmeErrno = errno; |
| if (status != 0) |
| { |
| ns.clear(); |
| break; |
| } |
| |
| // NOLINTNEXTLINE(modernize-loop-convert) |
| for (size_t i = 0; i < NVME_ID_NS_LIST_MAX; i++) |
| { |
| if (list.ns[i] == 0U) |
| { |
| break; |
| } |
| ns.push_back(list.ns[i]); |
| } |
| if (list.ns[NVME_ID_NS_LIST_MAX - 1] == 0) |
| { |
| // all entries read |
| break; |
| } |
| } |
| |
| auto ex = makeLibNVMeError(nvmeErrno, status, "adminListNamespaces"); |
| self->io.post([cb{cb}, ex, ns]() mutable { cb(ex, ns); }); |
| }); |
| if (postErr) |
| { |
| auto ex = makeLibNVMeError("post failed"); |
| io.post([cb{std::move(cb)}, ex]() { cb(ex, std::vector<uint32_t>()); }); |
| } |
| } |
| |
| // Attaches or detaches a namespace from a controller |
| void NVMeMi::adminAttachDetachNamespace( |
| nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb) |
| { |
| std::error_code postErr = tryPost( |
| [self{shared_from_this()}, ctrl, nsid, attach, ctrlid, cb{cb}]() { |
| struct nvme_ctrl_list ctrlList = {}; |
| struct nvme_ns_attach_args args = {}; |
| memset(&args, 0x0, sizeof(args)); |
| |
| // TODO: add this to a newer libnvme |
| // uint16_t ctrl_id = nvme_mi_ctrl_id(ctrl); |
| uint16_t ctrlId = ctrlid; |
| nvme_init_ctrl_list(&ctrlList, 1, &ctrlId); |
| args.ctrlist = &ctrlList; |
| args.nsid = nsid; |
| if (attach) |
| { |
| args.sel = NVME_NS_ATTACH_SEL_CTRL_ATTACH; |
| } |
| else |
| { |
| args.sel = NVME_NS_ATTACH_SEL_CTRL_DEATTACH; |
| } |
| args.args_size = sizeof(args); |
| |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, namespaceDefaultTimeoutMS); |
| int status = nvme_mi_admin_ns_attach(ctrl, &args); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| self->io.post([cb{cb}, nvmeErrno{errno}, status]() mutable { |
| auto err = std::make_error_code(static_cast<std::errc>(nvmeErrno)); |
| cb(err, status); |
| }); |
| }); |
| if (postErr) |
| { |
| std::cerr << "adminAttachDetachNamespace post failed: " << postErr |
| << '\n'; |
| io.post([cb{std::move(cb)}, postErr]() { cb(postErr, -1); }); |
| } |
| } |
| |
| void NVMeMi::adminSanitize(nvme_mi_ctrl_t ctrl, |
| enum nvme_sanitize_sanact sanact, uint8_t passes, |
| uint32_t /*pattern*/, bool invertPattern, |
| std::function<void(nvme_ex_ptr ex)>&& cb) |
| { |
| std::error_code postErr = tryPost([self{shared_from_this()}, ctrl, sanact, |
| passes, invertPattern, cb{cb}]() { |
| struct nvme_sanitize_nvm_args args = {}; |
| memset(&args, 0x0, sizeof(args)); |
| args.args_size = sizeof(args); |
| args.sanact = sanact; |
| args.owpass = passes; |
| args.oipbp = invertPattern; |
| |
| unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP); |
| nvme_mi_ep_set_timeout(self->nvmeEP, sanitizeDefaultTimeoutMS); |
| int status = nvme_mi_admin_sanitize_nvm(ctrl, &args); |
| nvme_mi_ep_set_timeout(self->nvmeEP, timeout); |
| std::cout << "san status " << status << "errno " << errno << '\n'; |
| |
| auto ex = makeLibNVMeError(errno, status, "adminSanitize"); |
| self->io.post([cb{cb}, ex]() mutable { cb(ex); }); |
| }); |
| if (postErr) |
| { |
| auto ex = makeLibNVMeError("post failed"); |
| io.post([cb{std::move(cb)}, ex]() { cb(ex); }); |
| } |
| } |