blob: e2ae09ce49d6f790242fd04d660d7d89106564b3 [file] [log] [blame]
#include "NVMeSubsys.hpp"
#include "AsioHelper.hpp"
#include "NVMeCache.hpp"
#include "NVMeDrive.hpp"
#include "NVMeError.hpp"
#include "NVMePlugin.hpp"
#include "NVMeUtil.hpp"
#include "Thresholds.hpp"
#include "xyz/openbmc_project/Common/error.hpp"
#include <dlfcn.h>
#include <boost/asio/spawn.hpp>
#include <algorithm>
#include <charconv>
#include <filesystem>
#include <stdexcept>
#include <unordered_map>
using SchedulerClockType = std::chrono::steady_clock;
std::unordered_map<std::string, std::shared_ptr<Scheduler<SchedulerClockType>>>
schedulers;
template <>
std::shared_ptr<Scheduler<SchedulerClockType>>
Scheduler<SchedulerClockType>::getScheduler(const std::string& path)
{
auto itr = std::find_if(
schedulers.begin(), schedulers.end(),
[&path](const std::pair<std::string,
std::shared_ptr<Scheduler<SchedulerClockType>>>&
pair) {
// check if the given path is subdir of the subsystem path
const std::string& parent = pair.first;
if (parent.empty() || path.empty())
{
return false;
}
if (parent.size() > path.size())
{
return false;
}
if (!path.starts_with(parent))
{
return false;
}
if (parent.back() != '/' && path.size() > parent.size() &&
path.at(parent.size()) != '/')
{
return false;
}
return true;
});
if (itr == schedulers.end())
{
return {};
}
return itr->second;
}
void NVMeSubsystem::createAssociation()
{
assocIntf = objServer.add_interface(path, association::interface);
assocIntf->register_property("Associations", makeAssociation());
assocIntf->initialize();
}
void NVMeSubsystem::updateAssociation()
{
assocIntf->set_property("Associations", makeAssociation());
}
std::vector<Association> NVMeSubsystem::makeAssociation() const
{
std::vector<Association> associations;
std::filesystem::path p(path);
associations.emplace_back("chassis", "storage", p.parent_path().string());
associations.emplace_back("chassis", "drive", p.parent_path().string());
associations.emplace_back("drive", "storage", path);
for (const auto& [_, prog] : createProgress)
{
associations.emplace_back("awaiting", "awaited", prog->path);
}
for (const auto& [_, vol] : volumes)
{
associations.emplace_back("containing", "contained", vol->path);
}
return associations;
}
// get temporature from a NVMe Basic reading.
static double getTemperatureReading(int8_t reading)
{
if (reading == static_cast<int8_t>(0x80) ||
reading == static_cast<int8_t>(0x81))
{
// 0x80 = No temperature data or temperature data is more the 5 s
// old 0x81 = Temperature sensor failure
return std::numeric_limits<double>::quiet_NaN();
}
return reading;
}
std::shared_ptr<NVMeSubsystem> NVMeSubsystem::create(
boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
const std::shared_ptr<sdbusplus::asio::connection>& conn,
const std::string& path, const std::string& name,
const SensorData& configData, NVMeIntf intf)
{
auto self = std::make_shared<NVMeSubsystem>(io, objServer, conn, path, name,
configData, std::move(intf));
self->init();
return self;
}
NVMeSubsystem::NVMeSubsystem(
boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
const std::shared_ptr<sdbusplus::asio::connection>& conn,
const std::string& path, const std::string& name,
const SensorData& configData, NVMeIntf intf) :
NVMeStorage(objServer, *dynamic_cast<sdbusplus::bus_t*>(conn.get()),
path.c_str()),
path(path), io(io), objServer(objServer), conn(conn), name(name),
config(configData), nvmeIntf(std::move(intf)), status(Status::Stop)
{}
// Performs initialisation after shared_from_this() has been set up.
void NVMeSubsystem::init()
{
NVMeIntf::Protocol protocol{NVMeIntf::Protocol::NVMeBasic};
try
{
protocol = nvmeIntf.getProtocol();
}
catch (const std::runtime_error&)
{
throw std::runtime_error("NVMe interface is null");
}
// initiate the common interfaces (thermal sensor, Drive and Storage)
if (protocol != NVMeIntf::Protocol::NVMeBasic &&
protocol != NVMeIntf::Protocol::NVMeMI)
{
throw std::runtime_error("Unsupported NVMe interface");
}
/* xyz.openbmc_project.Inventory.Item.Storage */
NVMeStorage::init(
std::static_pointer_cast<NVMeStorage>(shared_from_this()));
/* xyz.openbmc_project.Inventory.Item.Drive */
drive = std::make_shared<NVMeDrive>(io, conn, path, weak_from_this());
drive->protocol(NVMeDrive::DriveProtocol::NVMe);
drive->type(NVMeDrive::DriveType::SSD);
// TODO: update capacity
// make association for Drive/Storage/Chassis
createAssociation();
}
NVMeSubsystem::~NVMeSubsystem()
{
objServer.remove_interface(assocIntf);
}
void NVMeSubsystem::processSecondaryControllerList(
nvme_secondary_ctrl_list* secCntlrList)
{
auto findPrimary = controllers.begin();
int secCntlrCount = 0;
if (secCntlrList != nullptr)
{
// all sc_entry pointing to a single pcid, so we only check
// the first entry.
findPrimary = controllers.find(secCntlrList->sc_entry[0].pcid);
if (findPrimary == controllers.end())
{
std::cerr << "fail to match primary controller from "
"identify sencondary cntrl list"
<< '\n';
status = Status::Aborting;
markFunctional(false);
markAvailable(false);
return;
}
secCntlrCount = secCntlrList->num;
}
// Enable primary controller since they are required to work
auto& pc = findPrimary->second.first;
primaryController = NVMeControllerEnabled::create(std::move(*pc));
// replace with the new controller object
pc = primaryController;
std::vector<std::shared_ptr<NVMeController>> secCntrls;
for (int i = 0; i < secCntlrCount; i++)
{
auto findSecondary = controllers.find(secCntlrList->sc_entry[i].scid);
if (findSecondary == controllers.end())
{
std::cerr << "fail to match secondary controller from "
"identify sencondary cntrl list"
<< '\n';
break;
}
auto& secondaryController = findSecondary->second.first;
// Check Secondary Controller State
if (secCntlrList->sc_entry[i].scs != 0)
{
secondaryController =
NVMeControllerEnabled::create(std::move(*secondaryController));
}
secondaryController->setSecondary();
secCntrls.push_back(secondaryController);
}
primaryController->setPrimary(secCntrls);
boost::asio::spawn(io, [self{shared_from_this()}](
const boost::asio::yield_context& yield) {
try
{
self->fillDrive(yield);
self->updateVolumes(yield);
self->querySupportedFormats(yield);
std::cerr << "finished NS enum" << '\n';
}
catch (const std::exception& e)
{
std::cerr << std::format("[{}] failed starting the subsystem: {}",
self->name, e.what())
<< '\n';
self->status = Status::Aborting;
self->markFunctional(false);
self->markAvailable(false);
return;
}
// start controller
for (auto& [_, pair] : self->controllers)
{
// create controller plugin
if (self->plugin)
{
pair.second = self->plugin->createControllerPlugin(
*pair.first, self->config);
}
pair.first->start(pair.second);
}
// start plugin
if (self->plugin)
{
self->plugin->start();
}
// start Metric Scheduler
auto scheduler =
Scheduler<SchedulerClockType>::getScheduler(self->path);
assert(
scheduler &&
"scheduler should be created at the beginning of Initializing Status");
scheduler->start();
scheduler->dequeue();
self->status = Status::Start;
});
}
void NVMeSubsystem::markFunctional(bool toggle)
{
if (ctemp)
{
ctemp->markFunctional(toggle);
}
if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeBasic)
{
return;
}
// disable the subsystem
if (!toggle)
{
if (status == Status::Intiatilzing)
{
throw std::runtime_error(
"cannot stop: the subsystem is intiatilzing");
}
if (status == Status::Terminating || status == Status::Stop)
{
return;
}
assert(status == Status::Start || status == Status::Aborting);
status = Status::Terminating;
// erase the scheduler to stop furthur Metric scheduling
schedulers.erase(path);
if (plugin)
{
plugin->stop();
}
// TODO: the controller should be stopped after controller level polling
// is enabled
// Tell any progress objects we're aborting the operation
for (auto& [_, v] : createProgress)
{
v->abort();
}
// Tell the controllers they mustn't post further jobs
if (primaryController)
{
primaryController->stop();
}
for (auto& [_, v] : controllers)
{
v.first->stop();
}
// Avoid triggering C*V D-Bus updates by clearing internal state
// directly. The controller and volume objects and interfaces will be
// removed which will update the mapper.
attached.clear();
volumes.clear();
primaryController.reset();
controllers.clear();
// plugin.reset();
updateAssociation();
if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeMI)
{
auto nvme =
std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
bool posted =
nvme->flushOperations([self{shared_from_this()}]() mutable {
self->status = Status::Stop;
});
if (!posted)
{
std::cerr
<< "Failed to flush operations, subsystem has stalled!"
<< '\n';
}
}
return;
}
if (status == Status::Intiatilzing)
{
throw std::runtime_error("cannot start: the subsystem is intiatilzing");
}
if (status == Status::Aborting)
{
throw std::runtime_error(
"cannot start: subsystem initialisation has aborted and must transition to stopped");
}
if (status == Status::Start || status == Status::Terminating)
{
// Prevent consecutive calls to NVMeMiIntf::miScanCtrl()
//
// NVMeMiIntf::miScanCtrl() calls nvme_mi_scan_ep(..., true), which
// forces a rescan and invalidates any nvme_mi_ctrl objects created on a
// previous scan.
//
// We require a transition through Status::Stop (via
// `markFunctional(false)`) so that the lifetime of the NVMeController
// instances in this->controllers do not exceed the lifetime of their
// associated nvme_mi_ctrl object.
return;
}
assert(status == Status::Stop);
status = Status::Intiatilzing;
// create scheduler
assert(schedulers.find(path) == schedulers.end() &&
"scheduler should exsit only between [Intiatilzing, Terminating)");
schedulers.emplace(path, new Scheduler<SchedulerClockType>(io));
markAvailable(toggle);
// add controllers for the subsystem
if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeMI)
{
auto nvme =
std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
nvme->miScanCtrl(
[self{shared_from_this()},
nvme](const std::error_code& ec,
const std::vector<nvme_mi_ctrl_t>& ctrlList) mutable {
if (ec || ctrlList.empty())
{
// TODO: mark the subsystem invalid and reschedule refresh
std::cerr << "fail to scan controllers for the nvme subsystem"
<< (ec ? ": " + ec.message() : "") << '\n';
self->status = Status::Aborting;
self->markFunctional(false);
self->markAvailable(false);
return;
}
// TODO: manually open nvme_mi_ctrl_t from cntrl id, instead hacking
// into structure of nvme_mi_ctrl
for (auto* c : ctrlList)
{
/* calucate the cntrl id from nvme_mi_ctrl:
struct nvme_mi_ctrl
{
struct nvme_mi_ep* ep;
__u16 id;
struct list_node ep_entry;
};
*/
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
uint16_t* index = reinterpret_cast<uint16_t*>(
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
(reinterpret_cast<uint8_t*>(c) +
std::max(sizeof(uint16_t), sizeof(void*))));
std::filesystem::path path = std::filesystem::path(self->path) /
"controllers" /
std::to_string(*index);
try
{
auto nvmeController = std::make_shared<NVMeController>(
self->io, self->objServer, self->conn, path.string(),
self->config, nvme, c, self->weak_from_this());
self->controllers.insert({*index, {nvmeController, {}}});
}
catch (const std::exception& e)
{
std::cerr << "failed to create controller: "
<< std::to_string(*index)
<< ", reason: " << e.what() << '\n';
}
index++;
}
// self->createStorageAssociation();
/* Begin of patch context
*
*
*
*/
/*
*
*
* End of patch context
*/
/*
find primary controller and make association
The controller is SR-IOV, meaning all controllers (within a
subsystem) are pointing to a single primary controller. So we
only need to do identify on an arbatary controller.
If the controller list contains a single controller. Skip
identifying the secondary controller list. It will be the primary
controller.
*/
if (ctrlList.size() == 1)
{
self->processSecondaryControllerList(nullptr);
return;
}
auto* ctrl = ctrlList.back();
nvme->adminIdentify(
ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST,
0, 0,
[self{self->shared_from_this()}](const nvme_ex_ptr& ex,
std::span<uint8_t> data) {
if (ex || data.size() < sizeof(nvme_secondary_ctrl_list))
{
std::cerr << "fail to identify secondary controller list"
<< '\n';
self->status = Status::Aborting;
self->markFunctional(false);
self->markAvailable(false);
return;
}
nvme_secondary_ctrl_list* listHdr =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<nvme_secondary_ctrl_list*>(data.data());
if (listHdr->num == 0)
{
std::cerr << "empty identify secondary controller list"
<< '\n';
self->status = Status::Aborting;
self->markFunctional(false);
self->markAvailable(false);
return;
}
self->processSecondaryControllerList(listHdr);
});
});
}
}
void NVMeSubsystem::markAvailable(bool toggle)
{
if (ctemp)
{
ctemp->markAvailable(toggle);
}
if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeBasic)
{
return;
}
if (toggle)
{
// TODO: make the Available interface true
unavailableCount = 0;
return;
}
// TODO: make the Available interface false
unavailableCount = unavailableMaxCount;
}
std::shared_ptr<NVMeControllerEnabled>
NVMeSubsystem::getPrimaryController() const
{
if (!primaryController)
{
std::cerr << "dbus call for inactive NVMe subsystem " << name
<< ". Returning Unavailable\n";
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
return primaryController;
}
/* Begin of patch context
*
*
*
*/
/*
*
*
* End of patch context
*/
void NVMeSubsystem::start()
{
for (const auto& [_, lib] : pluginLibMap)
{
createplugin_t pluginFunc =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<createplugin_t>(::dlsym(lib, "createPlugin"));
auto p = pluginFunc(shared_from_this(), config);
if (p)
{
plugin = p;
break;
}
}
// add thermal sensor for the subsystem
std::optional<std::string> sensorName = createSensorNameFromPath(path);
if (!sensorName)
{
// fail to parse sensor name from path, using name instead.
sensorName.emplace(name);
}
std::vector<thresholds::Threshold> sensorThresholds;
if (!parseThresholdsFromConfig(config, sensorThresholds))
{
std::cerr << "error populating thresholds for " << *sensorName << "\n";
throw std::runtime_error("error populating thresholds for " +
*sensorName);
}
if (ctemp || ctempTimer)
{
throw std::logic_error(
"NVMeSubsystem::start() called from invalid state");
}
assert(!ctemp && !ctempTimer);
PowerState powerState = PowerState::always;
auto sensorBase = config.find(configInterfaceName(nvme::sensorType));
if (sensorBase == config.end())
{
std::cerr << "Warning: " << name
<< ": cannot find sensor config " +
configInterfaceName(nvme::sensorType)
<< '\n';
}
else
{
const SensorBaseConfigMap& sensorConfig = sensorBase->second;
powerState = getPowerState(sensorConfig);
}
/* Begin of patch context
*
*
*
*/
/*
*
*
* End of patch context
*/
ctemp = std::make_shared<NVMeSensor>(objServer, io, conn, *sensorName,
std::move(sensorThresholds), path,
powerState);
ctempTimer = std::make_shared<boost::asio::steady_timer>(io);
// start to poll value for CTEMP sensor.
if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeBasic)
{
auto intf =
std::get<std::shared_ptr<NVMeBasicIntf>>(nvmeIntf.getInferface());
ctemp_fetch_t<NVMeBasicIntf::DriveStatus*> dataFetcher =
[intf, self{shared_from_this()},
timer = std::weak_ptr<boost::asio::steady_timer>(ctempTimer)](
std::function<void(const std::error_code&,
NVMeBasicIntf::DriveStatus*)>&& cb) {
/* Potentially defer sampling the sensor sensor if it is in error */
if (!self->ctemp->sample())
{
cb(std::make_error_code(std::errc::operation_canceled),
nullptr);
return;
}
intf->getStatus(std::move(cb));
};
ctemp_process_t<NVMeBasicIntf::DriveStatus*> dataProcessor =
[self{shared_from_this()},
timer = std::weak_ptr<boost::asio::steady_timer>(ctempTimer)](
const std::error_code& error,
NVMeBasicIntf::DriveStatus* status) {
// deferred sampling
if (error == std::errc::operation_canceled)
{
return;
}
// The device is physically absent
if (error == std::errc::no_such_device)
{
std::cerr << "error reading ctemp from subsystem"
<< ", reason:" << error.message() << "\n";
self->markFunctional(false);
self->markAvailable(false);
return;
}
// other communication errors
if (error)
{
std::cerr << "error reading ctemp from subsystem"
<< ", reason:" << error.message() << "\n";
self->ctemp->incrementError();
return;
}
if (status == nullptr)
{
std::cerr << "empty data returned by data fetcher" << '\n';
self->markFunctional(false);
return;
}
uint8_t flags = status->Status;
if (((flags & NVMeBasicIntf::StatusFlags::
NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) ||
((flags & NVMeBasicIntf::StatusFlags::
NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0))
{
std::cerr
<< self->name
<< ": health poll returns drive not ready or drive not functional"
<< '\n';
self->markFunctional(false);
return;
}
self->ctemp->updateValue(
getTemperatureReading(static_cast<int8_t>(status->Temp)));
};
pollCtemp(ctempTimer, pollingInterval, dataFetcher, dataProcessor);
}
else if (nvmeIntf.getProtocol() == NVMeIntf::Protocol::NVMeMI)
{
auto intf =
std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
ctemp_fetch_t<nvme_mi_nvm_ss_health_status*> dataFetcher =
[intf, self{shared_from_this()},
timer = std::weak_ptr<boost::asio::steady_timer>(ctempTimer)](
std::function<void(const std::error_code&,
nvme_mi_nvm_ss_health_status*)>&& cb) {
// do not poll the health status if subsystem is in cooldown
if (self->unavailableCount > 0)
{
cb(std::make_error_code(std::errc::operation_canceled),
nullptr);
return;
}
// do not poll the health status if the subsystem is intiatilzing
if (self->status == Status::Intiatilzing)
{
std::cerr << "subsystem is intiatilzing, cancel the health poll"
<< '\n';
cb(std::make_error_code(std::errc::operation_canceled),
nullptr);
return;
}
intf->miSubsystemHealthStatusPoll(std::move(cb));
};
ctemp_process_t<nvme_mi_nvm_ss_health_status*> dataProcessor =
[self{shared_from_this()},
timer = std::weak_ptr<boost::asio::steady_timer>(ctempTimer)](
const std::error_code& error,
nvme_mi_nvm_ss_health_status* status) {
if (self->unavailableCount > 0)
{
self->unavailableCount--;
return;
}
if (error == std::errc::operation_canceled)
{
std::cerr << "processing health data has been cancelled"
<< '\n';
return;
}
if (self->status == Status::Intiatilzing)
{
// on initialization, the subsystem will not update the status.
std::cerr
<< "subsystem is intiatilzing, do not process the status"
<< '\n';
return;
}
if (error == std::errc::no_such_device)
{
std::cerr << "error reading ctemp "
"from subsystem"
<< ", reason:" << error.message() << "\n";
// stop the subsystem
self->markFunctional(false);
self->markAvailable(false);
return;
}
if (error)
{
std::cerr << "error reading ctemp "
"from subsystem"
<< ", reason:" << error.message() << "\n";
self->ctemp->incrementError();
if (self->ctemp->inError())
{
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(
self->nvmeIntf.getInferface());
intf->recover();
// stop the subsystem
self->markFunctional(false);
self->markAvailable(false);
}
return;
}
// Drive Functional
bool df = (status->nss & 0x20) != 0;
if (!df)
{
// stop the subsystem
std::cerr << self->name << ": health poll returns df status 0"
<< '\n';
self->markFunctional(false);
return;
}
self->markFunctional(true);
// TODO: update the drive interface
self->ctemp->updateValue(
getTemperatureReading(static_cast<int8_t>(status->ctemp)));
return;
};
pollCtemp(ctempTimer, pollingInterval, dataFetcher, dataProcessor);
}
}
void NVMeSubsystem::stop()
{
if (ctempTimer)
{
ctempTimer->cancel();
ctempTimer.reset();
ctemp.reset();
}
if (status == Status::Intiatilzing)
{
std::cerr << "status init" << '\n';
auto timer = std::make_shared<boost::asio::steady_timer>(
io, std::chrono::milliseconds(100));
timer->async_wait(
[self{shared_from_this()}, timer](boost::system::error_code ec) {
if (ec)
{
return;
}
self->stop();
});
}
else
{
std::cerr << "status else" << '\n';
markFunctional(false);
// There's been an explicit request to stop the subsystem. If it has
// entered an unavailable state, reset that too. If the subsystem
// continues to be unavailable beyond a subsequent invocation of start()
// this will be detected in the usual fashion. Put another way: Don't
// unnecessarily impede the progress of a subsequent start().
unavailableCount = 0;
}
if (plugin)
{
plugin.reset();
}
}
sdbusplus::message::object_path
NVMeSubsystem::createVolume(boost::asio::yield_context yield, uint64_t size,
size_t lbaFormat, bool metadataAtEnd)
{
if (status != Status::Start)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
// #0 (sequence of runtime/callbacks)
auto progId = getRandomId();
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using submit_callback_t = void(std::tuple<nvme_ex_ptr>);
auto [ex] = boost::asio::async_initiate<boost::asio::yield_context,
submit_callback_t>(
[weak{weak_from_this()}, progId, intf, ctrl, size, lbaFormat,
metadataAtEnd](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
// #1
intf->createNamespace(ctrl, size, lbaFormat, metadataAtEnd,
// submitted_cb
[h](const nvme_ex_ptr& ex) mutable {
// #2
// Async completion of the createNamespace call.
// The actual nvme_mi_admin_ns_mgmt_create() call is still running
// in a separate thread. Pass the error status back out.
h(std::make_tuple(ex));
},
// finished_cb
[weak, progId](const nvme_ex_ptr& ex,
NVMeNSIdentify newns) mutable {
// #5. This will only be called once #4 completes.
// It will not be called if the submit failed.
auto self = weak.lock();
if (!self)
{
std::cerr << "createNamespace completed while nvmesensor was "
"exiting\n";
return;
}
// The NS create has completed (either successfully or not)
self->createVolumeFinished(progId, ex, newns);
});
},
yield);
// #3
// Exception must be thrown outside of the async block
if (ex)
{
// 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 *ex;
}
// Progress endpoint for clients to poll, if the submit was successful.
std::string progPath = path + "/CreateProgress/" + progId;
auto prog = std::make_shared<NVMeCreateVolumeProgress>(conn, progPath);
if (!createProgress.insert({progId, prog}).second)
{
throw std::logic_error("duplicate progress id");
}
updateAssociation();
// #4
return progPath;
}
void NVMeSubsystem::createVolumeFinished(const std::string& progId,
const nvme_ex_ptr& ex,
NVMeNSIdentify ns)
{
try
{
auto p = createProgress.find(progId);
if (p == createProgress.end())
{
throw std::logic_error("Missing progress entry");
}
auto prog = p->second;
if (prog->status() == OperationStatus::Aborted)
{
return;
}
assert(status == Status::Start);
if (ex)
{
prog->createFailure(ex);
return;
}
std::shared_ptr<NVMeVolume> vol;
try
{
vol = addVolume(ns);
}
// 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)
{
prog->createFailure(e);
return;
}
prog->createSuccess(vol);
updateAssociation();
}
catch (const std::exception& e)
{
std::cerr << "Unhandled error in createVolumeFinished: " << e.what()
<< "\n";
}
}
std::string NVMeSubsystem::volumePath(uint32_t nsid) const
{
return path + "/volumes/" + std::to_string(nsid);
}
void NVMeSubsystem::addIdentifyNamespace(boost::asio::yield_context yield,
uint32_t nsid)
{
assert((status == Status::Start || status == Status::Intiatilzing) &&
std::format("Subsystem not in Start state, have {}",
static_cast<int>(status))
.c_str());
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = getPrimaryController()->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using admin_identify_t = void(std::tuple<nvme_ex_ptr, std::span<uint8_t>>);
auto [ex, data] = boost::asio::async_initiate<boost::asio::yield_context,
admin_identify_t>(
[intf, ctrl, nsid](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminIdentify(
ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_ALLOCATED_NS, nsid,
NVME_CNTLID_NONE,
[h](const nvme_ex_ptr& ex, std::span<uint8_t> data) mutable {
h(std::make_tuple(ex, data));
});
}, yield);
if (ex)
{
// 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 *ex;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
nvme_id_ns& id = *reinterpret_cast<nvme_id_ns*>(data.data());
// msb 6:5 and lsb 3:0
size_t lbafIndex = ((id.flbas >> 1) & 0x30) | (id.flbas & 0x0f);
size_t blockSize = 1UL << id.lbaf[lbafIndex].ds;
bool metadataAtEnd = (id.flbas & (1 << 4)) != 0;
NVMeNSIdentify ns = {
.namespaceId = nsid,
.size = ::le64toh(id.nsze * blockSize),
.capacity = ::le64toh(id.ncap * blockSize),
.blockSize = blockSize,
.lbaFormat = lbafIndex,
.metadataAtEnd = metadataAtEnd,
};
addVolume(ns);
// determine attached controllers
std::tie(ex, data) = boost::asio::async_initiate<boost::asio::yield_context,
admin_identify_t>(
[intf, ctrl, nsid](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminIdentify(
ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_NS_CTRL_LIST, nsid,
NVME_CNTLID_NONE,
[h](const nvme_ex_ptr& ex, std::span<uint8_t> data) mutable {
h(std::make_tuple(ex, data));
});
}, yield);
if (ex)
{
// 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 *ex;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
nvme_ctrl_list& list = *reinterpret_cast<nvme_ctrl_list*>(data.data());
uint16_t num = ::le16toh(list.num);
if (num == NVME_ID_CTRL_LIST_MAX)
{
std::cerr << "Warning: full ctrl list returned\n";
}
for (auto i = 0; i < num; i++)
{
uint16_t c = ::le16toh(list.identifier[i]);
attachCtrlVolume(c, nsid);
}
}
void NVMeSubsystem::updateVolumes(boost::asio::yield_context yield)
{
assert((status == Status::Start || status == Status::Intiatilzing) &&
std::format("Subsystem not in Start state, have {}",
static_cast<int>(status))
.c_str());
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = getPrimaryController()->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using admin_list_ns_t =
void(std::tuple<nvme_ex_ptr, std::vector<uint32_t>>);
auto [ex, ns] = boost::asio::async_initiate<boost::asio::yield_context,
admin_list_ns_t>(
[intf, ctrl](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminListNamespaces(ctrl,
[h](const nvme_ex_ptr& ex,
const std::vector<uint32_t>& ns) mutable {
h(std::make_tuple(ex, ns));
});
}, yield);
if (ex)
{
// 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 *ex;
}
std::vector<uint32_t> existing;
existing.reserve(volumes.size());
for (auto& [n, _] : volumes)
{
existing.push_back(n);
}
std::vector<uint32_t> additions;
std::vector<uint32_t> deletions;
// namespace lists are ordered
std::set_difference(ns.begin(), ns.end(), existing.begin(), existing.end(),
std::back_inserter(additions));
std::set_difference(existing.begin(), existing.end(), ns.begin(), ns.end(),
std::back_inserter(deletions));
std::cerr << std::format(
"[{}] subsystem enum {} NS, {} will be added, {} will be deleted\n",
name, ns.size(), additions.size(), deletions.size());
for (auto n : deletions)
{
forgetVolume(volumes.find(n)->second);
}
for (auto n : additions)
{
addIdentifyNamespace(yield, n);
}
}
void NVMeSubsystem::fillDrive(boost::asio::yield_context yield)
{
assert(status == Status::Intiatilzing);
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using admin_identify_t = void(std::tuple<nvme_ex_ptr, std::span<uint8_t>>);
auto [ex, data] = boost::asio::async_initiate<boost::asio::yield_context,
admin_identify_t>(
[intf, ctrl](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminIdentify(
ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_CTRL, NVME_NSID_NONE,
NVME_CNTLID_NONE,
[h](const nvme_ex_ptr& ex, std::span<uint8_t> data) mutable {
h(std::make_tuple(ex, data));
});
}, yield);
if (ex)
{
throw std::runtime_error(
std::format("{}: Error for controller identify", name));
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
nvme_id_ctrl& id = *reinterpret_cast<nvme_id_ctrl*>(data.data());
drive->serialNumber(nvmeString((const char*)id.sn, sizeof(id.sn)));
drive->model(nvmeString((const char*)id.mn, sizeof(id.mn)));
drive->capacity(char128ToUint64((const unsigned char*)id.tnvmcap));
auto fwVer = nvmeString((const char*)id.fr, sizeof(id.fr));
if (!fwVer.empty())
{
// Formatting per
// https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/43458/2/xyz/openbmc_project/Software/Version.interface.yaml#47
// TODO find/write a better reference
std::string v("xyz.openbmc_project.NVMe.ControllerFirmwareVersion");
auto pc = getPrimaryController();
pc->version(v);
pc->purpose(SoftwareVersion::VersionPurpose::Other);
pc->extendedVersion(v + ":" + fwVer);
}
}
std::shared_ptr<NVMeVolume> NVMeSubsystem::getVolume(
const sdbusplus::message::object_path& volPath) const
{
if (volPath.parent_path() != path + "/volumes")
{
std::cerr << "getVolume path '" << volPath.str
<< "' doesn't match parent " << path << "\n";
return nullptr;
}
std::string id = volPath.filename();
uint32_t nsid = 0;
auto e = std::from_chars(id.data(), id.data() + id.size(), nsid);
if (e.ptr != id.data() + id.size() || e.ec != std::errc())
{
std::cerr << "getVolume path '" << volPath.str << "' bad nsid\n";
return nullptr;
}
auto v = volumes.find(nsid);
if (v == volumes.end())
{
std::cerr << "getVolume nsid " << nsid << " not found\n";
return nullptr;
}
return v->second;
}
std::vector<uint32_t> NVMeSubsystem::attachedVolumes(uint16_t ctrlId) const
{
std::vector<uint32_t> vols;
if (!controllers.contains(ctrlId))
{
std::cerr << "attachedVolumes bad controller " << ctrlId << '\n';
return vols;
}
try
{
std::ranges::copy(attached.at(ctrlId), std::back_inserter(vols));
}
catch (std::out_of_range&)
{
// no volumes attached
}
return vols;
}
void NVMeSubsystem::attachCtrlVolume(uint16_t c, uint32_t ns)
{
assert((status == Status::Start || status == Status::Intiatilzing) &&
std::format("Subsystem not in Start state, have {}",
static_cast<int>(status))
.c_str());
if (!controllers.contains(c))
{
throw std::runtime_error(
std::format("attachCtrlVolume bad controller {}", c));
}
if (!volumes.contains(ns))
{
throw std::runtime_error(std::format("attachCtrlVolume bad ns {}", ns));
}
attached[c].insert(ns);
std::cout << name << " attached insert " << c << " " << ns << "\n";
controllers[c].first->updateAssociation();
}
void NVMeSubsystem::detachCtrlVolume(uint16_t c, uint32_t ns)
{
assert(status == Status::Start &&
std::format("Subsystem is not in Start state: {}",
static_cast<int>(status))
.c_str());
if (!controllers.contains(c))
{
throw std::runtime_error(
std::format("detachCtrlVolume bad controller {}", c));
}
if (!volumes.contains(ns))
{
throw std::runtime_error(std::format("detachCtrlVolume bad ns {}", ns));
}
attached[c].erase(ns);
std::cout << name << " attached erase " << c << " " << ns << "\n";
controllers[c].first->updateAssociation();
}
void NVMeSubsystem::detachAllCtrlVolume(uint32_t ns)
{
assert(status == Status::Start &&
std::format("Subsystem is not in Start state: {}",
static_cast<int>(status))
.c_str());
if (!volumes.contains(ns))
{
throw std::runtime_error(std::format("detachCtrlVolume bad ns {}", ns));
}
// remove from attached controllers list
for (auto& [c, attach_vols] : attached)
{
if (attach_vols.erase(ns) == 1)
{
controllers[c].first->updateAssociation();
}
}
}
// Will throw a nvme_ex_ptr if the NS already exists */
std::shared_ptr<NVMeVolume> NVMeSubsystem::addVolume(const NVMeNSIdentify& ns)
{
assert((status == Status::Start || status == Status::Intiatilzing) &&
std::format("Subsystem not in Start state, have {}",
static_cast<int>(status))
.c_str());
if (volumes.contains(ns.namespaceId))
{
std::string errMsg = std::string("Internal error, NSID exists " +
std::to_string(ns.namespaceId));
std::cerr << errMsg << "\n";
throw makeLibNVMeError(errMsg);
}
auto vol = NVMeVolume::create(objServer, conn, shared_from_this(), ns);
volumes.insert({ns.namespaceId, vol});
updateAssociation();
return vol;
}
void NVMeSubsystem::forgetVolume(const std::shared_ptr<NVMeVolume>& volume)
{
// remove any progress references
for (const auto& [progId, prog] : createProgress)
{
std::string s = prog->volumePath();
if (prog->volumePath() == volume->path)
{
createProgress.erase(progId);
break;
}
}
// remove from attached controllers list
detachAllCtrlVolume(volume->namespaceId());
if (volumes.erase(volume->namespaceId()) != 1)
{
throw std::runtime_error(std::format(
"volume {} disappeared unexpectedly", volume->namespaceId()));
}
updateAssociation();
}
void NVMeSubsystem::querySupportedFormats(boost::asio::yield_context yield)
{
assert((status == Status::Start || status == Status::Intiatilzing) &&
std::format("Subsystem not in Start state, have {}",
static_cast<int>(status))
.c_str());
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using admin_identify_t = void(std::tuple<nvme_ex_ptr, std::span<uint8_t>>);
auto [ex, data] = boost::asio::async_initiate<boost::asio::yield_context,
admin_identify_t>(
[intf, ctrl](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminIdentify(
ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_NS, NVME_NSID_ALL,
NVME_CNTLID_NONE,
[h](const nvme_ex_ptr& ex, std::span<uint8_t> data) mutable {
h(std::make_tuple(ex, data));
});
}, yield);
if (ex)
{
throw std::runtime_error(
std::format("{}: Error getting LBA formats :{}", name, ex->what()));
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
nvme_id_ns& id = *reinterpret_cast<nvme_id_ns*>(data.data());
// nlbaf is 0’s based
size_t nlbaf = id.nlbaf + 1;
if (nlbaf > 64)
{
throw std::runtime_error(std::format("{}: Bad nlbaf {}", name, nlbaf));
}
std::cerr << name << ": Got nlbaf " << nlbaf << "\n";
std::vector<LBAFormat> formats;
for (size_t i = 0; i < nlbaf; i++)
{
size_t blockSize = 1UL << id.lbaf[i].ds;
size_t metadataSize = id.lbaf[i].ms;
RelPerf rp = relativePerformanceFromRP(id.lbaf[i].rp);
std::cerr << name << ": lbaf " << i << " blocksize " << blockSize
<< "\n";
formats.push_back({.index = i,
.blockSize = blockSize,
.metadataSize = metadataSize,
.relativePerformance = rp});
}
setSupportedFormats(formats);
}
void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
const std::shared_ptr<NVMeVolume>& volume)
{
if (status != Status::Start)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
using callback_t = void(std::tuple<std::error_code, int>);
auto [err, nvmeStatus] =
boost::asio::async_initiate<boost::asio::yield_context, callback_t>(
[intf, ctrl, nsid{volume->namespaceId()}](auto&& handler) {
auto h = asio_helper::CopyableCallback(
std::forward<decltype(handler)>(handler));
intf->adminDeleteNamespace(
ctrl, nsid,
[h](const std::error_code& err, int nvmeStatus) mutable {
h(std::make_tuple(err, nvmeStatus));
});
}, yield);
// exception must be thrown outside of the async block
checkLibNVMeError(err, nvmeStatus, "Delete");
forgetVolume(volume);
}
// submitCb is called once the sanitize has been submitted
void NVMeSubsystem::startSanitize(
const NVMeSanitizeParams& params,
std::function<void(nvme_ex_ptr ex)>&& submitCb)
{
if (status != Status::Start)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
intf->adminSanitize(
ctrl, params.nvmeAction(), params.passes, params.pattern,
params.patternInvert,
[submitCb](nvme_ex_ptr ex) { submitCb(std::move(ex)); });
}
void NVMeSubsystem::sanitizeStatus(
std::function<void(nvme_ex_ptr ex, bool inProgress, bool failed,
bool completed, uint16_t sstat, uint16_t sprog,
uint32_t scdw10)>&& cb)
{
if (status != Status::Start)
{
std::cerr << "Subsystem not in Start state, have "
<< static_cast<int>(status) << '\n';
return;
}
auto pc = getPrimaryController();
nvme_mi_ctrl_t ctrl = pc->getMiCtrl();
auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
intf->adminGetLogPage(
ctrl, NVME_LOG_LID_SANITIZE, NVME_NSID_NONE, 0, 0,
[self{shared_from_this()}, cb](const std::error_code& ec,
std::span<uint8_t> data) {
if (ec)
{
std::string msg = "GetLogPage failed: " + ec.message();
auto ex = makeLibNVMeError(msg);
cb(ex, false, false, false, 0, 0, 0);
return;
}
nvme_sanitize_log_page* log =
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
reinterpret_cast<nvme_sanitize_log_page*>(data.data());
uint8_t sanStatus = log->sstat & NVME_SANITIZE_SSTAT_STATUS_MASK;
cb(nvme_ex_ptr(), sanStatus == NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS,
sanStatus == NVME_SANITIZE_SSTAT_STATUS_COMPLETED_FAILED,
sanStatus == NVME_SANITIZE_SSTAT_STATUS_COMPLETE_SUCCESS, log->sstat,
log->sprog, log->scdw10);
});
}