blob: bd135043a89ff2a98295c1805bc566ad6007e61b [file] [log] [blame] [edit]
#include "NVMeIntf.hpp"
#include <boost/asio.hpp>
#include <boost/endian.hpp>
#include <iostream>
#include <thread>
struct ListNode
{
ListNode *next, *prev;
};
struct nvme_mi_ctrl
{
void* ep;
__u16 id;
ListNode ep_entry;
};
class NVMeMiFake :
public NVMeMiIntf,
public std::enable_shared_from_this<NVMeMiFake>
{
public:
NVMeMiFake(boost::asio::io_context& io, std::chrono::milliseconds delay) :
io(io), valid(true) /*, worker(workerIO.get_executor())*/
{
// start worker thread
thread = std::thread([&io = workerIO, &stop = workerStop,
&mtx = workerMtx, &cv = workerCv,
&isNotified = workerIsNotified, delay]() {
std::cerr << "NVMeMiFake worker thread started: " << io.stopped()
<< '\n';
// 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.
io.stop();
io.restart();
while (true)
{
// mimik the communication delay.
std::this_thread::sleep_for(delay);
io.run();
io.restart();
std::cerr << "job done\n";
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]() { return isNotified; });
isNotified = false;
if (stop)
{
// exhaust all tasks and exit
io.run();
break;
}
}
}
std::cerr << "NVMeMi worker this line should not be reached\n";
});
ctrls.resize(3);
std::cerr << "NVMeMiFake constructor\n";
}
~NVMeMiFake() override
{
if (valid)
{
std::cerr << "NVMeMiFake destroyer\n";
}
else
{
std::cerr << "NVMeMiFake default destroyer\n";
}
// close worker
workerStop = true;
{
std::unique_lock<std::mutex> lock(workerMtx);
workerIsNotified = true;
workerCv.notify_all();
}
thread.join();
}
void start(const std::shared_ptr<MctpEndpoint>& endpoint
[[maybe_unused]]) override
{}
void stop() override {}
void recover() override {}
void miSubsystemHealthStatusPoll(
std::function<void(const std::error_code&,
nvme_mi_nvm_ss_health_status*)>&& cb) override
{
io.post([cb{std::move(cb)}]() {
nvme_mi_nvm_ss_health_status status{};
status.nss = 1 << 5;
status.ctemp = 24;
cb({}, &status);
});
}
void miScanCtrl(std::function<void(const std::error_code&,
const std::vector<nvme_mi_ctrl_t>&)>
cb) override
{
if (workerStop)
{
std::cerr << "worker thread for nvme endpoint is stopped\n";
io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(std::errc::no_such_device), {});
});
return;
}
post([self{shared_from_this()}, cb = std::move(cb)] {
std::cerr << "libnvme: scan\n";
self->io.post([self{self->shared_from_this()}, cb{cb}]() mutable {
auto& ctrl1 = self->ctrls[0];
auto& ctrl2 = self->ctrls[1];
auto& ctrl3 = self->ctrls[2];
ctrl1.id = 0;
ctrl2.id = 1;
ctrl3.id = 2;
std::vector<nvme_mi_ctrl_t> list{&ctrl1, &ctrl2, &ctrl3};
cb({}, list);
});
});
}
bool flushOperations(std::function<void()>&& cb) override
{
post([self{shared_from_this()}, cb{std::move(cb)}]() {
self->io.post(cb);
});
return true;
}
void adminIdentify(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, nvme_identify_cns cns,
[[maybe_unused]] uint32_t nsid, [[maybe_unused]] uint16_t cntid,
std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) override
{
std::cerr << "identify\n";
post([self{shared_from_this()}, cb = std::move(cb), cns, nsid]() {
std::cerr << "libnvme: identify\n";
self->io.post([cb{cb}, cns, nsid]() mutable {
std::vector<uint8_t> data;
switch (cns)
{
case NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST:
{
nvme_secondary_ctrl_list list{};
list.num = 2;
list.sc_entry[0].pcid = 0;
list.sc_entry[0].scid = 1;
list.sc_entry[0].scs = 1;
list.sc_entry[1].pcid = 0;
list.sc_entry[1].scid = 2;
list.sc_entry[1].scs = 0;
data.resize(sizeof(nvme_secondary_ctrl_list));
memcpy(data.data(), &list, data.size());
break;
}
case NVME_IDENTIFY_CNS_NS_CTRL_LIST:
{
nvme_ctrl_list ctrlList{};
ctrlList.num = 1;
ctrlList.identifier[0] = 0;
if (nsid == 1)
{
ctrlList.num++;
ctrlList.identifier[1] = 1;
}
data.resize(sizeof(ctrlList));
memcpy(data.data(), &ctrlList, data.size());
break;
}
default:
data.resize(NVME_IDENTIFY_DATA_SIZE);
}
cb({}, std::span<uint8_t>(data.begin(), data.size()));
});
});
}
void 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) override
{
try
{
post([lid, lsp, self{shared_from_this()}, cb{std::move(cb)}]() {
int rc = 0;
std::vector<uint8_t> data;
try
{
switch (lid)
{
case NVME_LOG_LID_TELEMETRY_HOST:
{
data.resize(sizeof(nvme_telemetry_log));
nvme_telemetry_log& log =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
*reinterpret_cast<nvme_telemetry_log*>(
data.data());
if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
{
log.lpi = 0x07;
if (rc != 0)
{
std::cerr
<< "failed to create telemetry host log\n";
throw std::system_error(
errno, std::generic_category());
}
}
else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
{
// nvme rev 1.3 only applies upto Area 3
log.dalb1 = 512;
log.dalb2 = 512;
log.dalb3 = 512;
data.resize(sizeof(nvme_telemetry_log) + 512);
std::string str = "hello world";
for (std::size_t i = 0; i < str.size(); i++)
{
data[sizeof(nvme_telemetry_log) + i] =
static_cast<uint8_t>(str[i]);
}
if (rc != 0)
{
std::cerr
<< "failed to retain telemetry host "
"log full log\n";
throw std::system_error(
errno, std::generic_category());
}
}
else
{
throw std::system_error(std::make_error_code(
std::errc::invalid_argument));
}
}
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());
uint8_t temp = 40;
log.temperature[0] = temp;
log.temperature[1] = 1;
break;
}
default:
{
std::cerr << "unknown lid for GetLogPage\n";
throw std::system_error(std::make_error_code(
std::errc::invalid_argument));
}
}
}
catch (const ::std::system_error& e)
{
self->io.post(
[cb{cb}, ec = e.code()]() mutable { cb(ec, {}); });
return;
}
self->io.post([cb{cb}, data{std::move(data)}]() mutable {
std::span<uint8_t> span{data.data(), data.size()};
cb({}, span);
});
});
}
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), {});
});
return;
}
}
void 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) override
{
try
{
nvme_fw_commit_args args{};
memset(&args, 0, sizeof(args));
args.action = action;
args.slot = slot;
args.bpid = bpid;
io.post([cb{std::move(cb)}, self{shared_from_this()}]() mutable {
// int rc = nvme_mi_admin_fw_commit(ctrl, &args);
int rc = 1;
if (rc < 0)
{
std::cerr << "fail to subsystem_health_status_poll: "
<< 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;
}
if (rc > 0)
{
switch (rc & 0x7ff)
{
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 subsystem_health_status_poll: "
<< 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 << 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 adminXfer(nvme_mi_ctrl_t /*ctrl*/,
const nvme_mi_admin_req_hdr& adminReq,
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) override
{
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([req{std::move(req)}, self{shared_from_this()},
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());
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 =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<nvme_mi_admin_resp_hdr*>(buf.data());
(void)respHeader;
// rc = nvme_mi_admin_xfer(
// ctrl, reqHeader, req.size() -
// sizeof(nvme_mi_admin_req_hdr), respHeader,
// respDataOffset, &respDataSize);
if (rc < 0)
{
std::cerr << "failed to nvme_mi_admin_xfer\n";
self->io.post([cb{std::move(cb)}]() {
cb(std::make_error_code(static_cast<std::errc>(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());
nvme_smart_log& log =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
*reinterpret_cast<nvme_smart_log*>(span.data());
uint8_t temp = 40;
log.temperature[0] = temp;
log.temperature[1] = 1;
cb({},
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
*reinterpret_cast<nvme_mi_admin_resp_hdr*>(data.data()),
span);
});
});
}
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), {}, {});
});
return;
}
}
void adminSecuritySend(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint8_t proto,
[[maybe_unused]] uint16_t protoSpecific,
[[maybe_unused]] std::span<uint8_t> data,
std::function<void(const std::error_code&, int nvmeStatus)>&& cb)
override
{
cb(std::make_error_code(std::errc::not_supported), 0);
}
void adminSecurityReceive(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint8_t proto,
[[maybe_unused]] uint16_t protoSpecific,
[[maybe_unused]] uint32_t transferLength,
std::function<void(const std::error_code&, int nvmeStatus,
const std::span<uint8_t> data)>&& cb) override
{
cb(std::make_error_code(std::errc::not_supported), 0, {});
}
void adminFwDownload(
[[maybe_unused]] nvme_mi_ctrl_t ctrl,
[[maybe_unused]] std::string firmwarefile,
[[maybe_unused]] std::function<void(const std::error_code&,
nvme_status_field)>&& cb) override
{
cb(std::make_error_code(std::errc::not_supported),
nvme_status_field::NVME_SC_SUCCESS);
}
void adminNonDataCmd(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint8_t opcode,
[[maybe_unused]] uint32_t cdw1, [[maybe_unused]] uint32_t cdw2,
[[maybe_unused]] uint32_t cdw3, [[maybe_unused]] uint32_t cdw10,
[[maybe_unused]] uint32_t cdw11, [[maybe_unused]] uint32_t cdw12,
[[maybe_unused]] uint32_t cdw13, [[maybe_unused]] uint32_t cdw14,
[[maybe_unused]] uint32_t cdw15,
[[maybe_unused]] std::function<void(
const std::error_code&, int nvmeStatus, uint32_t comptionDw0)>&& cb)
override
{
cb(std::make_error_code(std::errc::not_supported), 0, 0);
}
void createNamespace(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint64_t size,
[[maybe_unused]] size_t lbaFormat, [[maybe_unused]] bool metadataAtEnd,
[[maybe_unused]] std::function<void(nvme_ex_ptr ex)>&& submittedCb,
[[maybe_unused]] std::function<
void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& finishedCb) override
{
/* TODO: return not support to the cb/s */
}
void adminDeleteNamespace(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint32_t nsid,
[[maybe_unused]] std::function<void(const std::error_code&,
int nvmeStatus)>&& cb) override
{
cb(std::make_error_code(std::errc::not_supported), 0);
}
void adminListNamespaces(
[[maybe_unused]] nvme_mi_ctrl_t ctrl,
[[maybe_unused]] std::function<
void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& cb) override
{
// return fake NS
return cb({}, {1, 2});
}
void adminAttachDetachNamespace(
[[maybe_unused]] nvme_mi_ctrl_t ctrl, [[maybe_unused]] uint16_t ctrlid,
[[maybe_unused]] uint32_t nsid, [[maybe_unused]] bool attach,
[[maybe_unused]] std::function<void(const std::error_code&,
int nvmeStatus)>&& cb) override
{
cb(std::make_error_code(std::errc::not_supported), 0);
}
void adminSanitize(
[[maybe_unused]] nvme_mi_ctrl_t ctrl,
[[maybe_unused]] enum nvme_sanitize_sanact sanact,
[[maybe_unused]] uint8_t passes, [[maybe_unused]] uint32_t pattern,
[[maybe_unused]] bool invertPattern,
[[maybe_unused]] std::function<void(nvme_ex_ptr ex)>&& cb) override
{
/* TODO: return not support to the cb/s */
}
private:
boost::asio::io_context& io;
bool valid = false;
bool workerStop = false;
std::mutex workerMtx;
std::condition_variable workerCv;
boost::asio::io_context workerIO;
bool workerIsNotified = false;
std::thread thread;
void post(std::function<void(void)>&& func);
std::vector<nvme_mi_ctrl> ctrls;
};
inline void NVMeMiFake::post(std::function<void(void)>&& func)
{
if (!workerStop)
{
std::unique_lock<std::mutex> lock(workerMtx);
if (!workerStop)
{
std::cerr << "do post\n";
workerIsNotified = true;
workerIO.post(std::move(func));
workerCv.notify_all();
return;
}
}
throw std::runtime_error("NVMeMi has been stopped");
}