blob: fad24d22f0775ab30812bd7338e15a1d9193a818 [file] [log] [blame]
#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); });
}
}