blob: 864c3dc1e1d9c55fa00f32c11b0d87c5b1859d80 [file] [log] [blame] [edit]
#include "NVMeMi.hpp"
#include "NVMeError.hpp"
#include "NVMeUtil.hpp"
#include <endian.h>
#include <boost/endian.hpp>
#include <cerrno>
#include <fstream>
#include <iostream>
std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
// 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 initCmdTimeoutMS = 1000;
NVMeMi::NVMeMi(boost::asio::io_context& io,
std::shared_ptr<sdbusplus::asio::connection> conn, int bus,
int addr, bool singleThreadMode, PowerState readState) :
io(io), conn(conn), dbus(*conn.get()), bus(bus), addr(addr),
readState(readState), nvmeEP(nullptr), nid(-1), eid(0), mtu(64),
restart(false), optimizeTimer(nullptr), mctpStatus(Status::Reset)
{
// set update the worker thread
if (!nvmeRoot)
{
throw std::runtime_error("invalid NVMe root");
}
if (singleThreadMode)
{
auto root = deriveRootBus(bus);
if (!root || *root < 0)
{
throw std::runtime_error("invalid root bus number");
}
auto res = workerMap.find(*root);
if (res == workerMap.end() || res->second.expired())
{
worker = std::make_shared<Worker>();
workerMap[*root] = worker;
}
else
{
worker = res->second.lock();
}
}
else
{
worker = std::make_shared<Worker>();
}
// 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))
{
start();
}
else
{
stop();
}
});
}
}
void NVMeMi::start()
{
if (mctpStatus == Status::Terminating)
{
restart = true;
return;
}
if (mctpStatus == Status::Reset)
{
// init mctp ep via mctpd
std::cerr << "[bus: " << bus << ", addr: " << addr << "]"
<< "start MCTP initialization" << std::endl;
int i = 0;
for (;; i++)
{
try
{
auto msg = dbus.new_method_call(
"xyz.openbmc_project.MCTP", "/xyz/openbmc_project/mctp",
"au.com.CodeConstruct.MCTP", "SetupEndpoint");
msg.append("mctpi2c" + std::to_string(bus));
msg.append(std::vector<uint8_t>{static_cast<uint8_t>(addr)});
auto reply = msg.call(); // throw SdBusError
reply.read(eid);
reply.read(nid);
reply.read(mctpPath);
break;
}
catch (const std::exception& e)
{
if (i < 5)
{
std::cerr << "[bus: " << bus << ", addr: " << addr << "]"
<< "retry to SetupEndpoint: " << e.what()
<< std::endl;
}
else
{
mctpStatus = Status::Reset;
nid = -1;
eid = 0;
mtu = 64;
mctpPath.erase();
nvmeEP = nullptr;
std::cerr << "[bus: " << bus << ", addr: " << addr << "]"
<< "fail to init MCTP endpoint: " << e.what()
<< std::endl;
return;
}
}
}
// open mctp endpoint
nvmeEP = nvme_mi_open_mctp(nvmeRoot, nid, eid);
if (nvmeEP == nullptr)
{
mctpStatus = Status::Reset;
nid = -1;
eid = 0;
mtu = 64;
// MCTPd won't expect to delete the ep object, just to erase the
// record here.
mctpPath.erase();
nvmeEP = nullptr;
std::cerr << "[bus: " << bus << ", addr: " << addr << "]"
<< "can't open MCTP endpoint "
<< std::to_string(nid) + ":" + std::to_string(eid)
<< std::endl;
return;
}
// TODO: make a flag to indicate the next health poll should return
// no_such_device error. This is to inform the subsystem that the
// connected has been reset or hot-swapped.
mctpStatus = Status::Initiated;
}
if (mctpStatus == Status::Initiated)
{
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
<< std::endl;
return;
}
miSetMCTPConfiguration(
[self{shared_from_this()}](const std::error_code& ec) {
self->optimizeTimer = nullptr;
if (ec)
{
std::cerr << "[bus: " << self->bus
<< ", addr: " << self->addr << "]"
<< "Failed setting up MTU for the MCTP endpoint. "
<< std::to_string(self->nid) + ":" +
std::to_string(self->eid)
<< std::endl;
self->start();
return;
}
auto rc = self->configureLocalRouteMtu();
if (rc)
{
self->start();
return;
}
self->mctpStatus = Status::Connected;
});
});
}
}
void NVMeMi::stop()
{
restart = false;
if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating)
{
return;
}
if (optimizeTimer)
{
std::cerr << "[ bus: " << bus << ", addr: " << addr << "]"
<< "Cancel the optimization Timer for the endpoint"
<< std::endl;
optimizeTimer->cancel();
optimizeTimer = nullptr;
}
mctpStatus = Status::Terminating;
std::cerr << "[bus: " << bus << ", addr: " << addr << "]"
<< "start MCTP closure" << std::endl;
// 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->nid = -1;
self->eid = 0;
self->mctpPath.erase();
self->nvmeEP = nullptr;
self->mctpStatus = Status::Reset;
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr << "]"
<< "end MCTP closure" << std::endl;
if (self->restart)
{
// If restart is true then we've received update Power
// signal while in terminating loop.
self->restart = false;
self->start();
}
});
}
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");
}
NVMeMi::Worker::Worker()
{ // start worker thread
workerStop = false;
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 (1)
{
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;
}
}
}
});
}
NVMeMi::Worker::~Worker()
{
// close worker
workerStop = true;
{
std::unique_lock<std::mutex> lock(workerMtx);
workerIsNotified = true;
workerCv.notify_all();
}
thread.join();
}
NVMeMi::~NVMeMi()
{
stop();
}
void NVMeMi::Worker::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::try_post(std::function<void(void)>&& func)
{
try
{
post([self{shared_from_this()}, func{std::move(func)}]() { func(); });
}
catch (const std::runtime_error& e)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
return std::make_error_code(std::errc::no_such_device);
}
return std::error_code();
}
void NVMeMi::miConfigureSMBusFrequency(
uint8_t port_id, uint8_t max_supported_freq,
std::function<void(const std::error_code&)>&& cb)
{
if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "nvme endpoint is invalid" << std::endl;
io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::no_such_device));
});
return;
}
try
{
post([port_id, max_supported_freq, self{shared_from_this()},
cb{std::move(cb)}]() mutable {
enum nvme_mi_config_smbus_freq smbusFreq;
unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
nvme_mi_ep_set_timeout(self->nvmeEP, initCmdTimeoutMS);
auto rc = nvme_mi_mi_config_get_smbus_freq(self->nvmeEP, port_id,
&smbusFreq);
nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
if (rc)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid)
<< "] failed to get the SMBus frequency "
<< std::endl;
}
else if (smbusFreq == NVME_MI_CONFIG_SMBUS_FREQ_100kHz)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid)
<< "] Setting the SMBus frequency to 400kHz\n";
rc = nvme_mi_mi_config_set_smbus_freq(
self->nvmeEP, port_id, NVME_MI_CONFIG_SMBUS_FREQ_400kHz);
if (rc)
{
std::cerr << "[bus: " << self->bus
<< ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid)
<< "] failed to set the SMBus frequency\n";
}
}
if (rc)
{
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
return;
}
}
void NVMeMi::miConfigureRemoteMCTP(
uint8_t port, uint16_t mtu, uint8_t max_supported_freq,
std::function<void(const std::error_code&)>&& cb)
{
if (mctpStatus == Status::Reset || mctpStatus == Status::Terminating)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "nvme endpoint is invalid" << std::endl;
io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::no_such_device));
});
return;
}
try
{
post([port, mtu, max_supported_freq, 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)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< " failed to set remote MCTP MTU for port :"
<< unsigned(port) << std::endl;
self->io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::bad_message));
});
return;
}
self->mtu = mtu;
if (max_supported_freq >= 2)
{
self->io.post([self, port, max_supported_freq,
cb{std::move(cb)}]() mutable {
self->miConfigureSMBusFrequency(port, max_supported_freq,
std::move(cb));
});
return;
}
self->io.post([cb{std::move(cb)}]() { cb({}); });
});
}
catch (const std::runtime_error& e)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "nvme endpoint is invalid" << std::endl;
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)
{
std::cerr << "Failed reading subsystem info failing: "
<< std::strerror(errno) << std::endl;
self->io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::bad_message));
});
return;
}
for (uint8_t port_id = 0; port_id <= ssInfo.nump; port_id++)
{
struct nvme_mi_read_port_info portInfo;
auto rc = nvme_mi_mi_read_mi_data_port(self->nvmeEP, port_id,
&portInfo);
if (rc)
{
/* PCIe port might not be ready right after AC/DC cycle. */
std::cerr << "[bus: " << self->bus
<< ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid)
<< "] failed reading port info for port_id: "
<< unsigned(port_id) << std::endl;
}
else if (portInfo.portt == 0x2)
{
// SMBus ports = 0x2
uint16_t supportedMtu = portInfo.mmctptus;
uint8_t supportedFreq = portInfo.smb.mme_freq;
self->io.post([self, port_id, supportedMtu, supportedFreq,
cb{std::move(cb)}]() mutable {
self->miConfigureRemoteMCTP(port_id, 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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::no_such_device));
});
return;
}
}
int NVMeMi::configureLocalRouteMtu()
{
int i = 0;
for (;; i++)
{
try
{
auto path = std::string("/xyz/openbmc_project/mctp/") +
std::to_string(nid) + "/" + std::to_string(eid);
auto msg = dbus.new_method_call(
"xyz.openbmc_project.MCTP", path.c_str(),
"au.com.CodeConstruct.MCTP.Endpoint", "SetMTU");
// 4 bytes for the MCTP header
uint32_t mctpMtu = mtu + 4;
msg.append(mctpMtu);
auto reply = msg.call();
break;
}
catch (const std::exception& e)
{
if (i < 5)
{
std::cerr << "[bus: " << bus << ", addr: " << addr << ", eid: "
<< "] retry to set MCTP route MTU : " << e.what()
<< std::endl;
}
else
{
std::cerr << "[bus: " << bus << ", addr: " << addr << ", eid: "
<< "] failed to set MCTP route MTU : " << e.what()
<< std::endl;
return -1;
}
}
}
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << std::to_string(eid)
<< "] finished MCTP initialization. MTU: " << mtu << std::endl;
return 0;
}
void NVMeMi::miSubsystemHealthStatusPoll(
std::function<void(const std::error_code&, nvme_mi_nvm_ss_health_status*)>&&
cb)
{
if (auto degraded = isEndpointDegraded())
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
io.post([cb{std::move(cb)}, errc{degraded.value()}]() {
cb(errc, nullptr);
});
return;
}
try
{
post([self{shared_from_this()}, cb{std::move(cb)}]() {
nvme_mi_nvm_ss_health_status ss_health;
auto rc = nvme_mi_mi_subsystem_health_status_poll(self->nvmeEP,
true, &ss_health);
if (rc < 0)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< " fail to subsystem_health_status_poll: "
<< std::strerror(errno) << std::endl;
self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
nullptr);
});
return;
}
else if (rc > 0)
{
std::string_view errMsg =
statusToString(static_cast<nvme_mi_resp_status>(rc));
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< " fail to subsystem_health_status_poll: " << errMsg
<< std::endl;
self->io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::bad_message), nullptr);
});
return;
}
self->io.post(
[cb{std::move(cb)}, ss_health{std::move(ss_health)}]() mutable {
cb({}, &ss_health);
});
});
}
catch (const std::runtime_error& e)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
io.post(
[cb{std::move(cb)}, errc{degraded.value()}]() { cb(errc, {}); });
return;
}
try
{
post([self{shared_from_this()}, cb{std::move(cb)}]() {
int rc = nvme_mi_scan_ep(self->nvmeEP, true);
if (rc < 0)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to scan controllers: "
<< std::strerror(errno) << std::endl;
self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
{});
});
return;
}
else if (rc > 0)
{
std::string_view errMsg =
statusToString(static_cast<nvme_mi_resp_status>(rc));
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to scan controllers: " << errMsg
<< std::endl;
self->io.post([cb{std::move(cb)}]() {
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{std::move(cb)}, list{std::move(list)}]() { cb({}, list); });
});
}
catch (const std::runtime_error& e)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
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() << std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
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()},
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 << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to do nvme identify: "
<< std::strerror(errno) << std::endl;
}
else if (rc > 0)
{
std::string_view errMsg =
statusToString(static_cast<nvme_mi_resp_status>(rc));
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to do nvme identify: " << errMsg
<< std::endl;
}
auto ex = makeLibNVMeError(errno, rc, "adminIdentify");
if (ex)
{
std::cerr << "fail to do nvme identify: " << ex->description()
<< std::endl;
}
self->io.post(
[cb{std::move(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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
auto msg = std::string("Runtime error: ") + e.what();
std::cerr << msg << std::endl;
io.post([cb{std::move(cb)}, msg]() { cb(makeLibNVMeError(msg), {}); });
return;
}
}
static int nvme_mi_admin_get_log_telemetry_host_rae(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 ? nvme_mi_admin_get_log_telemetry_host_rae
: 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)
{
std::cerr << "failed to create telemetry host log" << std::endl;
return rc;
}
}
rc = func(ctrl, false, 0, sizeof(log), &log);
if (rc)
{
std::cerr << "failed to retain telemetry log for "
<< (host ? "host" : "ctrl") << std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "get telemetry log: offset exceed the log size. "
<< "offset: " << offset << ", size: " << data.size()
<< std::endl;
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 = 1;
auto func = host ? nvme_mi_admin_get_log_telemetry_host_rae
: nvme_mi_admin_get_log_telemetry_ctrl;
uint32_t size = 0;
// final transaction
if (offset + nvme_mi_xfer_size >= data.size())
{
rae = 0;
}
size = std::min(static_cast<uint32_t>(nvme_mi_xfer_size),
static_cast<uint32_t>(data.size() - offset));
rc = func(ctrl, rae, offset, size, data.data() + offset);
if (rc < 0)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get chunk for telemetry log: "
<< std::strerror(errno) << std::endl;
boost::asio::post(self->io,
[cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
{});
});
return;
}
else if (rc > 0)
{
std::string_view errMsg =
statusToString(static_cast<nvme_mi_resp_status>(rc));
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get chunk for telemetry log: " << errMsg
<< std::endl;
boost::asio::post(self->io, [cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::bad_message), {});
});
return;
}
if (rae == 0)
{
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
io.post(
[cb{std::move(cb)}, errc{degraded.value()}]() { cb(errc, {}); });
return;
}
try
{
post([ctrl, nsid, lid, lsp, lsi, self{shared_from_this()},
cb{std::move(cb)}]() {
std::vector<uint8_t> data;
std::function<void(void)> logHandler;
int rc = 0;
switch (lid)
{
case NVME_LOG_LID_ERROR:
{
data.resize(nvme_mi_xfer_size);
// 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 = nvme_mi_xfer_size /
sizeof(nvme_error_log_page);
nvme_error_log_page* log =
reinterpret_cast<nvme_error_log_page*>(data.data());
rc = nvme_mi_admin_get_log_error(ctrl, num, false, log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get error log" << std::endl;
break;
}
}
break;
case NVME_LOG_LID_SMART:
{
data.resize(sizeof(nvme_smart_log));
nvme_smart_log* log =
reinterpret_cast<nvme_smart_log*>(data.data());
rc = nvme_mi_admin_get_log_smart(ctrl, nsid, false, log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get smart log" << std::endl;
break;
}
}
break;
case NVME_LOG_LID_FW_SLOT:
{
data.resize(sizeof(nvme_firmware_slot));
nvme_firmware_slot* log =
reinterpret_cast<nvme_firmware_slot*>(data.data());
rc = nvme_mi_admin_get_log_fw_slot(ctrl, false, log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get firmware slot" << std::endl;
break;
}
}
break;
case NVME_LOG_LID_CMD_EFFECTS:
{
data.resize(sizeof(nvme_cmd_effects_log));
nvme_cmd_effects_log* log =
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)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get cmd supported and effects log"
<< std::endl;
break;
}
}
break;
case NVME_LOG_LID_DEVICE_SELF_TEST:
{
data.resize(sizeof(nvme_self_test_log));
nvme_self_test_log* log =
reinterpret_cast<nvme_self_test_log*>(data.data());
rc = nvme_mi_admin_get_log_device_self_test(ctrl, log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get device self test log" << std::endl;
break;
}
}
break;
case NVME_LOG_LID_CHANGED_NS:
{
data.resize(sizeof(nvme_ns_list));
nvme_ns_list* log =
reinterpret_cast<nvme_ns_list*>(data.data());
rc = nvme_mi_admin_get_log_changed_ns_list(ctrl, false,
log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get changed namespace list"
<< std::endl;
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) ? true
: false;
uint32_t size = 0;
rc = getTelemetryLogSize(ctrl, host, size);
if (rc == 0)
{
data.resize(size);
logHandler = [self, ctrl, host, data{std::move(data)},
cb{std::move(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 =
reinterpret_cast<nvme_resv_notification_log*>(
data.data());
int rc = nvme_mi_admin_get_log_reservation(ctrl, false,
log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get reservation "
"notification log"
<< std::endl;
break;
}
}
break;
case NVME_LOG_LID_SANITIZE:
{
data.resize(sizeof(nvme_sanitize_log_page));
nvme_sanitize_log_page* log =
reinterpret_cast<nvme_sanitize_log_page*>(data.data());
int rc = nvme_mi_admin_get_log_sanitize(ctrl, false, log);
if (rc)
{
std::cerr
<< "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get sanitize status log" << std::endl;
break;
}
}
break;
default:
{
std::cerr << "[bus: " << self->bus
<< ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "unknown lid for GetLogPage" << std::endl;
rc = -1;
errno = EINVAL;
}
}
if (rc < 0)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get log page: " << std::strerror(errno)
<< std::endl;
logHandler = [cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
{});
};
}
else if (rc > 0)
{
std::string_view errMsg =
statusToString(static_cast<nvme_mi_resp_status>(rc));
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to get log pag: " << errMsg << std::endl;
logHandler = [cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::bad_message), {});
};
}
if (!logHandler)
{
logHandler = [cb{std::move(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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "NVMeMi adminGetLogPage throws: " << e.what() << std::endl;
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& admin_req,
std::span<uint8_t> data, unsigned int timeout_ms,
std::function<void(const std::error_code&, const nvme_mi_admin_resp_hdr&,
std::span<uint8_t>)>&& cb)
{
if (auto degraded = isEndpointDegraded())
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
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(), &admin_req, 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()}, timeout_ms,
cb{std::move(cb)}]() mutable {
int rc = 0;
nvme_mi_admin_req_hdr* reqHeader =
reinterpret_cast<nvme_mi_admin_req_hdr*>(req.data());
size_t 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 =
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, timeout_ms);
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 << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "failed to nvme_mi_admin_xfer" << std::endl;
self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
{}, {});
});
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());
cb({}, *reinterpret_cast<nvme_mi_admin_resp_hdr*>(data.data()),
span);
});
});
}
catch (const std::runtime_error& e)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
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)},
self{shared_from_this()}]() mutable {
int rc = nvme_mi_admin_fw_commit(ctrl, &args);
if (rc < 0)
{
std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "fail to nvme_mi_admin_fw_commit: "
<< std::strerror(errno) << std::endl;
self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
cb(std::make_error_code(static_cast<std::errc>(last_errno)),
nvme_status_field::NVME_SC_MASK);
});
return;
}
else 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
<< std::endl;
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 << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]" << e.what()
<< std::endl;
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, std::string firmwarefile, size_t size, size_t offset,
int attempt_count,
std::function<void(const std::error_code&, nvme_status_field)>&& cb)
{
if (auto degraded = isEndpointDegraded())
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< " MCTP connection is not established" << std::endl;
io.post([cb{std::move(cb)}, errc{degraded.value()}]() {
cb(errc, nvme_status_field::NVME_SC_MASK);
});
return;
}
try
{
post([ctrl, firmwarefile, size, offset, attempt_count,
cb{std::move(cb)}, self{shared_from_this()}]() mutable {
char data[nvme_mi_xfer_size];
std::ifstream fwFile(firmwarefile, std::ios::in | std::ios::binary);
if (fwFile.fail())
{
std::cerr << "fail to open fw image file: " << firmwarefile
<< strerror(errno) << std::endl;
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(offset, std::ios::beg);
nvme_fw_download_args args;
memset(&args, 0, sizeof(args));
args.args_size = sizeof(args);
int data_len = std::min(size - offset, nvme_mi_xfer_size);
fwFile.read(data, data_len);
fwFile.close();
args.offset = offset;
args.data_len = data_len;
args.data = data;
int rc = nvme_mi_admin_fw_download(ctrl, &args);
if (rc < 0)
{
if (attempt_count > 0)
{
std::cout << "Retrying the firmware chunk. With Offset :"
<< offset << " Total firmware Size :" << size
<< std::endl;
attempt_count = attempt_count - 1;
}
else
{
std::cerr << "fail to nvme_mi_admin_fw_download: "
<< std::strerror(errno) << std::endl;
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
{
attempt_count = 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 << std::endl;
self->io.post([rc, cb{std::move(cb)}]() {
cb({}, static_cast<nvme_status_field>(rc));
});
return;
}
self->adminFwDownloadChunk(ctrl, firmwarefile, size, offset,
attempt_count, std::move(cb));
});
}
catch (const std::runtime_error& e)
{
std::cerr << e.what() << std::endl;
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)
<< std::endl;
io.post([cb{std::move(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 proto_specific,
std::span<uint8_t> data,
std::function<void(const std::error_code&, int nvme_status)>&& cb)
{
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, proto, proto_specific, data,
cb{std::move(cb)}]() {
struct nvme_security_send_args args;
memset(&args, 0x0, sizeof(args));
args.secp = proto;
args.spsp0 = proto_specific & 0xff;
args.spsp1 = proto_specific >> 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{std::move(cb)}, nvme_errno{errno}, status]() {
auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
cb(err, status);
});
});
if (post_err)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "adminSecuritySend post failed: " << post_err << std::endl;
io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
}
}
void NVMeMi::adminSecurityReceive(
nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
uint32_t transfer_length,
std::function<void(const std::error_code&, int nvme_status,
std::span<uint8_t> data)>&& cb)
{
if (transfer_length > maxNVMeMILength)
{
cb(std::make_error_code(std::errc::invalid_argument), -1, {});
return;
}
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, proto, proto_specific,
transfer_length, cb{std::move(cb)}]() {
std::vector<uint8_t> data(transfer_length);
struct nvme_security_receive_args args;
memset(&args, 0x0, sizeof(args));
args.secp = proto;
args.spsp0 = proto_specific & 0xff;
args.spsp1 = proto_specific >> 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 << "[bus: " << self->bus << ", addr: " << self->addr
<< ", eid: " << static_cast<int>(self->eid) << "]"
<< "nvme_mi_admin_security_send returned excess data, "
<< args.data_len << std::endl;
self->io.post([cb]() {
cb(std::make_error_code(std::errc::protocol_error), -1, {});
});
return;
}
data.resize(args.data_len);
self->io.post(
[cb{std::move(cb)}, nvme_errno{errno}, status, data]() mutable {
std::span<uint8_t> span{data.data(), data.size()};
auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
cb(err, status, span);
});
});
if (post_err)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "adminSecurityReceive post failed: " << post_err
<< std::endl;
io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -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 nvme_status,
uint32_t comption_dw0)>&& cb)
{
std::error_code post_err = try_post(
[self{shared_from_this()}, ctrl, opcode, cdw1, cdw2, cdw3, cdw10, cdw11,
cdw12, cdw13, cdw14, cdw15, cb{std::move(cb)}]() {
uint32_t comption_dw0 = 0;
int nvme_status = nvme_mi_admin_admin_passthru(
ctrl, opcode, 0, 0, cdw1, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13,
cdw14, cdw15, 0, nullptr, 0, nullptr, 10 * 1000, &comption_dw0);
self->io.post([cb{std::move(cb)}, nvme_errno{errno}, nvme_status,
comption_dw0]() mutable {
auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
cb(err, nvme_status, comption_dw0);
});
});
if (post_err)
{
std::cerr << "[bus: " << bus << ", addr: " << addr
<< ", eid: " << static_cast<int>(eid) << "]"
<< "adminNonDataCmd post failed: " << post_err << std::endl;
io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1, 0); });
}
}
/* throws a nvme_ex_ptr on failure */
size_t NVMeMi::getBlockSize(nvme_mi_ctrl_t ctrl, size_t lba_format)
{
struct nvme_id_ns id;
printf("getblocksize\n");
int status = nvme_mi_admin_identify_ns(ctrl, NVME_NSID_ALL, &id);
auto e = makeLibNVMeError(errno, status, "getBlockSize");
if (e)
{
throw e;
}
printf("nlbaf %d, lbaf %d\n", (int)id.nlbaf, (int)lba_format);
// Sanity check for the value from the drive
size_t max_lbaf = std::min(63, (int)id.nlbaf);
// NLBAF is the maximum allowed index (not a count)
if (lba_format > max_lbaf)
{
throw makeLibNVMeError("LBA format out of range, maximum is " +
std::to_string(max_lbaf),
std::make_shared<CommonErr::InvalidArgument>());
}
return 1 << id.lbaf[lba_format].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 lba_format, bool metadata_at_end,
std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& finished_cb)
{
printf("createns %d\n", (int)gettid());
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, size, lba_format,
metadata_at_end, submitted_cb{std::move(submitted_cb)},
finished_cb{std::move(finished_cb)}]() {
size_t block_size;
try
{
block_size = self->getBlockSize(ctrl, lba_format);
}
catch (nvme_ex_ptr e)
{
submitted_cb(e);
return;
}
if (size % block_size != 0)
{
auto msg =
std::string("Size must be a multiple of the block size ") +
std::to_string(block_size);
submitted_cb(makeLibNVMeError(
msg, std::make_shared<CommonErr::InvalidArgument>()));
return;
}
uint64_t blocks = size / block_size;
// TODO: this will become nvme_ns_mgmt_host_sw_specified in a newer
// libnvme.
struct nvme_id_ns data;
uint32_t new_nsid = 0;
uint8_t flbas = 0;
if (metadata_at_end)
{
flbas |= (1 << 4);
}
// low 4 bits at 0:3
flbas |= (lba_format & 0xf);
// high 2 bits at 5:6
flbas |= ((lba_format & 0x30) << 1);
memset(&data, 0x0, sizeof(data));
data.nsze = ::htole64(blocks);
data.ncap = ::htole64(blocks);
data.flbas = flbas;
printf("verified %d\n", (int)gettid());
// submission has been verified. Handle the cb in main thread
// concurrently.
self->io.post([submitted_cb{std::move(submitted_cb)}]() {
submitted_cb(nvme_ex_ptr());
});
printf("after submitted_cb %d\n", (int)gettid());
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, &new_nsid);
nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume");
NVMeNSIdentify newns = {
.namespaceId = new_nsid,
.size = size,
.capacity = size,
.blockSize = block_size,
.lbaFormat = lba_format,
.metadataAtEnd = metadata_at_end,
};
self->io.post([finished_cb{std::move(finished_cb)}, e, newns]() {
finished_cb(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
});
printf("submitted cb %d\n", (int)gettid());
if (post_err)
{
std::cerr << "adminAttachDetachNamespace post failed: " << post_err
<< std::endl;
auto e = makeLibNVMeError(post_err, -1, "createVolume");
io.post(
[submitted_cb{std::move(submitted_cb)}, e]() { submitted_cb(e); });
}
}
// Deletes a namespace
void NVMeMi::adminDeleteNamespace(
nvme_mi_ctrl_t ctrl, uint32_t nsid,
std::function<void(const std::error_code&, int nvme_status)>&& cb)
{
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, nsid, cb{std::move(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{std::move(cb)}, nvme_errno{errno}, status]() {
auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
cb(err, status);
});
});
if (post_err)
{
std::cerr << "deleteNamespace post failed: " << post_err << std::endl;
io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
}
}
void NVMeMi::adminListNamespaces(
nvme_mi_ctrl_t ctrl,
std::function<void(nvme_ex_ptr, std::vector<uint32_t> ns)>&& cb)
{
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, cb{std::move(cb)}]() {
int status, nvme_errno;
std::vector<uint32_t> ns;
// sanity in case of bad drives, allows for >1million NSes
const int MAX_ITER = 1000;
for (int i = 0; i < MAX_ITER; i++)
{
struct nvme_ns_list list;
uint32_t start = NVME_NSID_NONE;
if (!ns.empty())
{
start = ns.back() + 1;
}
status = nvme_mi_admin_identify_allocated_ns_list(ctrl, start,
&list);
nvme_errno = errno;
if (status != 0)
{
ns.clear();
break;
}
for (size_t i = 0; i < NVME_ID_NS_LIST_MAX; i++)
{
if (!list.ns[i])
{
break;
}
ns.push_back(list.ns[i]);
}
if (list.ns[NVME_ID_NS_LIST_MAX - 1] == 0)
{
// all entries read
break;
}
}
auto ex = makeLibNVMeError(nvme_errno, status, "adminListNamespaces");
self->io.post([cb{std::move(cb)}, ex, ns]() { cb(ex, ns); });
});
if (post_err)
{
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 nvme_status)>&& cb)
{
std::error_code post_err = try_post([self{shared_from_this()}, ctrl, nsid,
attach, ctrlid, cb{std::move(cb)}]() {
struct nvme_ctrl_list ctrl_list;
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 ctrl_id = ctrlid;
nvme_init_ctrl_list(&ctrl_list, 1, &ctrl_id);
args.ctrlist = &ctrl_list;
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{std::move(cb)}, nvme_errno{errno}, status]() {
auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
cb(err, status);
});
});
if (post_err)
{
std::cerr << "adminAttachDetachNamespace post failed: " << post_err
<< std::endl;
io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
}
}
void NVMeMi::adminSanitize(nvme_mi_ctrl_t ctrl,
enum nvme_sanitize_sanact sanact, uint8_t passes,
uint32_t pattern, bool invert_pattern,
std::function<void(nvme_ex_ptr ex)>&& cb)
{
std::error_code post_err =
try_post([self{shared_from_this()}, ctrl, sanact, passes, pattern,
invert_pattern, cb{std::move(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 = invert_pattern;
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);
printf("san status %d errno %d\n", status, errno);
auto ex = makeLibNVMeError(errno, status, "adminSanitize");
self->io.post([cb{std::move(cb)}, ex]() { cb(ex); });
});
if (post_err)
{
auto ex = makeLibNVMeError("post failed");
io.post([cb{std::move(cb)}, ex]() { cb(ex); });
}
}