| #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"); |
| } |