/* // Copyright (c) 2019 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include } /****************************************************************************/ /******************** Global Constants/Type Declarations ********************/ /****************************************************************************/ constexpr const char* hsbpCpldInft = "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD"; constexpr const char* hsbpConfigIntf = "xyz.openbmc_project.Configuration.HSBPConfiguration"; constexpr const char* nvmeIntf = "xyz.openbmc_project.Inventory.Item.NVMe"; constexpr const char* busName = "xyz.openbmc_project.HsbpManager"; constexpr size_t scanRateSeconds = 5; constexpr size_t maxDrives = 8; // only 1 byte alloted using NvmeMapping = std::vector; /***************************** End of Section *******************************/ /****************************************************************************/ /**************************** Enums Definitions *****************************/ /****************************************************************************/ enum class AppState : uint8_t { idle, loadingHsbpConfig, hsbpConfigLoaded, loadingComponents, componentsLoaded, loadingBackplanes, backplanesLoaded, loadingDrives, drivesLoaded }; enum class BlinkPattern : uint8_t { off = 0x0, error = 0x2, terminate = 0x3 }; /***************************** End of Section *******************************/ /****************************************************************************/ /************ HSBP Configuration related struct/class Definitions ***********/ /****************************************************************************/ struct HsbpConfig { size_t rootBus; std::vector supportedHsbps; std::unordered_map hsbpNvmeMap; std::vector clockBufferTypes; std::vector ioExpanderTypes; void clearConfig() { rootBus = -1; supportedHsbps.clear(); hsbpNvmeMap.clear(); clockBufferTypes.clear(); ioExpanderTypes.clear(); } }; class ClockBuffer { size_t bus; size_t address; std::string modeOfOperation; size_t outCtrlBaseAddr; size_t outCtrlByteCount; std::unordered_map> byteMap; std::string name; std::string type; int file = -1; bool initialized = false; void initialize() { /* Execute below operation only when mode of operation is SMBus. By * default the clock buffer is configured to follow OE pin output, so we * need to set the output value to 0 to disable the clock outputs. If * mode of operation is IO, then the IO value will determine the * disable/enable of clock output */ if (modeOfOperation == "SMBus") { if (file < 0) { file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR | O_CLOEXEC); if (file < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Unable to open bus : " << bus << "\n"; return; } } if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Unable to set address to " << address << "\n"; return; } for (uint8_t i = 0; i < outCtrlByteCount; i++) { std::string byteName = "Byte" + std::to_string(i); auto byte = byteMap.find(byteName); if (byte == byteMap.end()) { std::cerr << "ClockBuffer : \"" << name << "\" - Byte map error ! Unable to find " << byteName << "\n"; return; } /* Get current value of output control register */ int read = i2c_smbus_read_byte_data( file, static_cast(outCtrlBaseAddr + i)); if (read < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Error: Unable to read data from clock " "buffer register\n"; return; } std::bitset<8> currByte(read); /* Set zero only at bit position that we have a NVMe drive (i.e. * ignore where byteMap is "-"). We do not want to touch other * bits */ for (uint8_t bit = 0; bit < 8; bit++) { if (byte->second.at(bit) != "-") { currByte.reset(bit); } } int ret = i2c_smbus_write_byte_data( file, static_cast(outCtrlBaseAddr + i), static_cast(currByte.to_ulong())); if (ret < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Error: Unable to write data to clock " "buffer register\n"; return; } } } initialized = true; std::cerr << "ClockBuffer : \"" << name << "\" initialized\n"; } public: ClockBuffer( size_t busIn, size_t addressIn, std::string& modeOfOperationIn, size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn, std::unordered_map>& byteMapIn, std::string& nameIn, std::string& typeIn) : bus(busIn), address(addressIn), modeOfOperation(std::move(modeOfOperationIn)), outCtrlBaseAddr(outCtrlBaseAddrIn), outCtrlByteCount(outCtrlByteCountIn), byteMap(std::move(byteMapIn)), name(std::move(nameIn)), type(std::move(typeIn)) { initialize(); } bool isInitialized() { if (!initialized) { /* There was an issue with the initialization of this component. Try * to invoke initialization again */ initialize(); } return initialized; } std::string getName() { return name; } bool enableDisableClock(std::forward_list& nvmeDrivesInserted, std::forward_list& nvmeDrivesRemoved) { if (modeOfOperation != "SMBus") { /* The clock is enabled using IO expander. No action needed from * here */ return true; } if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty()) { /* There are no drives to update */ return true; } for (uint8_t i = 0; i < outCtrlByteCount; i++) { std::string byteName = "Byte" + std::to_string(i); auto byte = byteMap.find(byteName); if (byte == byteMap.end()) { std::cerr << "ClockBuffer : \"" << name << "\" - Byte map error ! Unable to find " << byteName << "\n"; return false; } /* Get current value of output control register */ int read = i2c_smbus_read_byte_data( file, static_cast(outCtrlBaseAddr + i)); if (read < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Error: Unable to read data from clock " "buffer register\n"; return false; } std::bitset<8> currByte(read); bool writeRequired = false; /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and * reset the bit if found in nvmeDrivesRemoved */ for (uint8_t bit = 0; bit < 8; bit++) { /* The remove function returns number of elements removed from * list indicating the presence of the drive and also removing * it form the list */ if (nvmeDrivesInserted.remove(byte->second.at(bit))) { writeRequired = true; currByte.set(bit); continue; } if (nvmeDrivesRemoved.remove(byte->second.at(bit))) { writeRequired = true; currByte.reset(bit); } } if (!writeRequired) { /* No Write is required as there are no changes */ continue; } int ret = i2c_smbus_write_byte_data( file, static_cast(outCtrlBaseAddr + i), static_cast(currByte.to_ulong())); if (ret < 0) { std::cerr << "ClockBuffer : \"" << name << "\" - Error: Unable to write data to clock " "buffer register\n"; return false; } } return true; } ~ClockBuffer() { if (file >= 0) { close(file); } } }; class IoExpander { size_t bus; size_t address; size_t confIORegAddr; size_t outCtrlBaseAddr; size_t outCtrlByteCount; std::unordered_map> ioMap; std::string name; std::string type; int file = -1; bool initialized = false; void initialize() { /* Initialize the IO expander Control register to configure the IO ports * as outputs and set the output to low by default */ if (file < 0) { file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR | O_CLOEXEC); if (file < 0) { std::cerr << "IoExpander : " << name << " - Unable to open bus : " << bus << "\n"; return; } } if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) { std::cerr << "IoExpander : \"" << name << "\" - Unable to set address to " << address << "\n"; return; } for (uint8_t i = 0; i < outCtrlByteCount; i++) { std::string ioName = "IO" + std::to_string(i); auto io = ioMap.find(ioName); if (io == ioMap.end()) { std::cerr << "IoExpander : \"" << name << "\" - IO map error ! Unable to find " << ioName << "\n"; return; } /* Get current value of IO configuration register */ int read1 = i2c_smbus_read_byte_data( file, static_cast(confIORegAddr + i)); if (read1 < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to read data from io expander " "IO control register\n"; return; } /* Get current value of IO Ouput register */ int read2 = i2c_smbus_read_byte_data( file, static_cast(confIORegAddr + i)); if (read2 < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to read data from io expander " "IO output register\n"; return; } std::bitset<8> currCtrlVal(read1); std::bitset<8> currOutVal(read2); /* Set zero only at bit position that we have a NVMe drive (i.e. * ignore where ioMap is "-"). We do not want to touch other * bits */ for (uint8_t bit = 0; bit < 8; bit++) { if (io->second.at(bit) != "-") { currCtrlVal.reset(bit); currOutVal.reset(bit); } } int ret1 = i2c_smbus_write_byte_data( file, static_cast(confIORegAddr + i), static_cast(currCtrlVal.to_ulong())); if (ret1 < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to write data to IO expander " "IO control register\n"; return; } int ret2 = i2c_smbus_write_byte_data( file, static_cast(outCtrlBaseAddr + i), static_cast(currOutVal.to_ulong())); if (ret2 < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to write data to IO expander " "IO output register\n"; return; } } initialized = true; std::cerr << "IoExpander : \"" << name << "\" initialized\n"; } public: IoExpander( size_t busIn, size_t addressIn, size_t confIORegAddrIn, size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn, std::unordered_map>& ioMapIn, std::string& nameIn, std::string& typeIn) : bus(busIn), address(addressIn), confIORegAddr(confIORegAddrIn), outCtrlBaseAddr(outCtrlBaseAddrIn), outCtrlByteCount(outCtrlByteCountIn), ioMap(std::move(ioMapIn)), name(std::move(nameIn)), type(std::move(typeIn)) { initialize(); } bool isInitialized() { if (!initialized) { /* There was an issue with the initialization of this component. Try * to invoke initialization again */ initialize(); } return initialized; } std::string getName() { return name; } bool enableDisableOuput(std::forward_list& nvmeDrivesInserted, std::forward_list& nvmeDrivesRemoved) { if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty()) { /* There are no drives to update */ return true; } for (uint8_t i = 0; i < outCtrlByteCount; i++) { std::string ioName = "IO" + std::to_string(i); auto io = ioMap.find(ioName); if (io == ioMap.end()) { std::cerr << "IoExpander : \"" << name << "\" - IO map error ! Unable to find " << ioName << "\n"; return false; } /* Get current value of IO output register */ int read = i2c_smbus_read_byte_data( file, static_cast(outCtrlBaseAddr + i)); if (read < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to read data from io expander " "register\n"; return false; } std::bitset<8> currVal(read); bool writeRequired = false; /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and * reset the bit if found in nvmeDrivesRemoved */ for (uint8_t bit = 0; bit < 8; bit++) { /* The remove function returns number of elements removed from * list indicating the presence of the drive and also removing * it form the list */ if (nvmeDrivesInserted.remove(io->second.at(bit))) { writeRequired = true; currVal.set(bit); continue; } if (nvmeDrivesRemoved.remove(io->second.at(bit))) { writeRequired = true; currVal.reset(bit); } } if (!writeRequired) { /* No Write is required as there are no changes */ continue; } int ret = i2c_smbus_write_byte_data( file, static_cast(outCtrlBaseAddr + i), static_cast(currVal.to_ulong())); if (ret < 0) { std::cerr << "IoExpander : \"" << name << "\" - Error: Unable to write data to IO expander " "register\n"; return false; } } return true; } ~IoExpander() { if (file >= 0) { close(file); } } }; /***************************** End of Section *******************************/ /****************************************************************************/ /*********************** Global Variables Declarations **********************/ /****************************************************************************/ /* State os Application */ static AppState appState = AppState::idle; /* Configuration and Components */ static HsbpConfig hsbpConfig; std::forward_list clockBuffers; std::forward_list ioExpanders; /* Boost IO context and Dbus variables */ boost::asio::io_context io; auto conn = std::make_shared(io); sdbusplus::asio::object_server objServer(conn); /* GPIO Lines and GPIO Event Descriptors */ static gpiod::line nvmeLvc3AlertLine; static boost::asio::posix::stream_descriptor nvmeLvc3AlertEvent(io); /***************************** End of Section *******************************/ /****************************************************************************/ /********** HSBP Backplane related struct and Global definitions ************/ /****************************************************************************/ struct Mux { Mux(size_t busIn, size_t addressIn, size_t channelsIn, size_t indexIn) : bus(busIn), address(addressIn), channels(channelsIn), index(indexIn) { } size_t bus; size_t address; size_t channels; size_t index; // to sort in the flat set bool operator<(const Mux& rhs) const { return index < rhs.index; } }; struct Led : std::enable_shared_from_this { // led pattern addresses start at 0x10 Led(const std::string& path, size_t index, int fd) : address(static_cast(index + 0x10)), file(fd), ledInterface(objServer.add_interface(path, ledGroup::interface)) { if (index >= maxDrives) { throw std::runtime_error("Invalid drive index"); } if (!set(BlinkPattern::off)) { std::cerr << "Cannot initialize LED " << path << "\n"; } } // this has to be called outside the constructor for shared_from_this to // work void createInterface(void) { ledInterface->register_property( ledGroup::asserted, false, [weakRef{weak_from_this()}](const bool req, bool& val) { auto self = weakRef.lock(); if (!self) { return 0; } if (req == val) { return 1; } if (!isPowerOn()) { std::cerr << "Can't change blink state when power is off\n"; throw std::runtime_error( "Can't change blink state when power is off"); } BlinkPattern pattern = req ? BlinkPattern::error : BlinkPattern::terminate; if (!self->set(pattern)) { std::cerr << "Can't change blink pattern\n"; throw std::runtime_error("Cannot set blink pattern"); } val = req; return 1; }); ledInterface->initialize(); } virtual ~Led() { objServer.remove_interface(ledInterface); } bool set(BlinkPattern pattern) { int ret = i2c_smbus_write_byte_data(file, address, static_cast(pattern)); return ret >= 0; } uint8_t address; int file; std::shared_ptr ledInterface; }; struct Drive { Drive(std::string driveName, bool present, bool isOperational, bool nvme, bool rebuilding) : isNvme(nvme), isPresent(present), name(driveName) { constexpr const char* basePath = "/xyz/openbmc_project/inventory/item/drive/"; itemIface = objServer.add_interface(basePath + driveName, inventory::interface); itemIface->register_property("Present", isPresent); itemIface->register_property("PrettyName", driveName); itemIface->initialize(); operationalIface = objServer.add_interface( itemIface->get_object_path(), "xyz.openbmc_project.State.Decorator.OperationalStatus"); operationalIface->register_property( "Functional", isOperational, [this](const bool req, bool& property) { if (!isPresent) { return 0; } if (property == req) { return 1; } property = req; if (req) { clearFailed(); return 1; } markFailed(); return 1; }); operationalIface->initialize(); rebuildingIface = objServer.add_interface( itemIface->get_object_path(), "xyz.openbmc_project.State.Drive"); rebuildingIface->register_property("Rebuilding", rebuilding); rebuildingIface->initialize(); driveIface = objServer.add_interface(itemIface->get_object_path(), "xyz.openbmc_project.Inventory.Item.Drive"); driveIface->initialize(); associations = objServer.add_interface(itemIface->get_object_path(), association::interface); associations->register_property("Associations", std::vector{}); associations->initialize(); if (isPresent && (!isOperational || rebuilding)) { markFailed(); } } virtual ~Drive() { objServer.remove_interface(itemIface); objServer.remove_interface(operationalIface); objServer.remove_interface(rebuildingIface); objServer.remove_interface(assetIface); objServer.remove_interface(driveIface); objServer.remove_interface(associations); } void removeAsset() { objServer.remove_interface(assetIface); assetIface = nullptr; } void createAsset( const boost::container::flat_map& data) { if (assetIface != nullptr) { return; } assetIface = objServer.add_interface( itemIface->get_object_path(), "xyz.openbmc_project.Inventory.Decorator.Asset"); for (const auto& [key, value] : data) { assetIface->register_property(key, value); if (key == "SerialNumber") { serialNumber = value; serialNumberInitialized = true; } } assetIface->initialize(); } void markFailed(void) { // todo: maybe look this up via mapper constexpr const char* globalInventoryPath = "/xyz/openbmc_project/CallbackManager"; if (!isPresent) { return; } operationalIface->set_property("Functional", false); std::vector warning = { {"", "warning", globalInventoryPath}}; associations->set_property("Associations", warning); logDriveError("Drive " + name); } void clearFailed(void) { operationalIface->set_property("Functional", true); associations->set_property("Associations", std::vector{}); } void setPresent(bool set) { // nvme drives get detected by their fru if (set == isPresent) { return; } itemIface->set_property("Present", set); isPresent = set; } void logPresent() { if (isNvme && !serialNumberInitialized) { // wait until NVMe asset is updated to include the serial number // from the NVMe drive return; } if (!isPresent && loggedPresent) { loggedPresent = false; logDeviceRemoved("Drive", name, serialNumber); serialNumber = "N/A"; serialNumberInitialized = false; removeAsset(); } else if (isPresent && !loggedPresent) { loggedPresent = true; logDeviceAdded("Drive", name, serialNumber); } } std::shared_ptr itemIface; std::shared_ptr operationalIface; std::shared_ptr rebuildingIface; std::shared_ptr assetIface; std::shared_ptr driveIface; std::shared_ptr associations; bool isNvme; bool isPresent; std::string name; std::string serialNumber = "N/A"; bool serialNumberInitialized = false; bool loggedPresent = false; }; struct Backplane : std::enable_shared_from_this { Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn, const std::string& nameIn) : bus(busIn), address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn), timer(boost::asio::steady_timer(io)), muxes(std::make_shared>()) { } void populateAsset(const std::string& rootPath, const std::string& busname) { conn->async_method_call( [assetIface{assetInterface}]( const boost::system::error_code ec, const boost::container::flat_map< std::string, std::variant>& values) mutable { if (ec) { std::cerr << "Error getting asset tag from HSBP configuration\n"; return; } for (const auto& [key, value] : values) { const std::string* ptr = std::get_if(&value); if (ptr == nullptr) { std::cerr << key << " Invalid type!\n"; continue; } assetIface->register_property(key, *ptr); } assetIface->initialize(); }, busname, rootPath, "org.freedesktop.DBus.Properties", "GetAll", assetTag); } static std::string zeroPad(const uint8_t val) { std::ostringstream version; version << std::setw(2) << std::setfill('0') << static_cast(val); return version.str(); } void run(const std::string& rootPath, const std::string& busname) { file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR | O_CLOEXEC); if (file < 0) { std::cerr << "unable to open bus " << bus << "\n"; return; } if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) { std::cerr << "unable to set address to " << address << "\n"; return; } if (!getPresent()) { std::cerr << "Cannot detect CPLD\n"; return; } getBootVer(bootVer); getFPGAVer(fpgaVer); getSecurityRev(securityRev); std::string dbusName = boost::replace_all_copy(name, " ", "_"); hsbpItemIface = objServer.add_interface( "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName, inventory::interface); hsbpItemIface->register_property("Present", true); hsbpItemIface->register_property("PrettyName", name); hsbpItemIface->initialize(); storageInterface = objServer.add_interface( hsbpItemIface->get_object_path(), "xyz.openbmc_project.Inventory.Item.StorageController"); storageInterface->initialize(); assetInterface = objServer.add_interface(hsbpItemIface->get_object_path(), assetTag); versionIface = objServer.add_interface("/xyz/openbmc_project/software/" + dbusName, "xyz.openbmc_project.Software.Version"); versionIface->register_property("Version", zeroPad(bootVer) + "." + zeroPad(fpgaVer) + "." + zeroPad(securityRev)); versionIface->register_property( "Purpose", std::string( "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP")); versionIface->initialize(); activationIface = objServer.add_interface("/xyz/openbmc_project/software/" + dbusName, "xyz.openbmc_project.Software.Activation"); activationIface->register_property( "Activation", std::string( "xyz.openbmc_project.Software.Activation.Activations.Active")); activationIface->register_property( "RequestedActivation", std::string("xyz.openbmc_project.Software.Activation." "RequestedActivations.None")); activationIface->initialize(); getPresence(presence); getIFDET(ifdet); populateAsset(rootPath, busname); createDrives(); runTimer(); } void runTimer() { timer.expires_after(std::chrono::seconds(scanRateSeconds)); timer.async_wait([weak{std::weak_ptr(shared_from_this())}]( boost::system::error_code ec) { auto self = weak.lock(); if (!self) { return; } if (ec == boost::asio::error::operation_aborted) { // we're being destroyed return; } else if (ec) { std::cerr << "timer error " << ec.message() << "\n"; return; } if (!isPowerOn()) { // can't access hsbp when power is off self->runTimer(); return; } self->getPresence(self->presence); self->getIFDET(self->ifdet); self->getFailed(self->failed); self->getRebuild(self->rebuilding); self->updateDrives(); self->runTimer(); }); } void createDrives() { for (size_t ii = 0; ii < maxDrives; ii++) { uint8_t driveSlot = (1 << ii); bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot)); bool isPresent = isNvme || (presence & driveSlot); bool isFailed = !isPresent || failed & driveSlot; bool isRebuilding = !isPresent && (rebuilding & driveSlot); // +1 to convert from 0 based to 1 based std::string driveName = boost::replace_all_copy(name, " ", "_") + "_Drive_" + std::to_string(ii + 1); Drive& drive = drives.emplace_back(driveName, isPresent, !isFailed, isNvme, isRebuilding); std::shared_ptr led = leds.emplace_back(std::make_shared( drive.itemIface->get_object_path(), ii, file)); led->createInterface(); } } void updateDrives() { size_t ii = 0; for (auto it = drives.begin(); it != drives.end(); it++, ii++) { uint8_t driveSlot = (1 << ii); bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot)); bool isPresent = isNvme || (presence & driveSlot); bool isFailed = !isPresent || (failed & driveSlot); bool isRebuilding = isPresent && (rebuilding & driveSlot); it->isNvme = isNvme; it->setPresent(isPresent); it->logPresent(); it->rebuildingIface->set_property("Rebuilding", isRebuilding); if (isFailed || isRebuilding) { it->markFailed(); } else { it->clearFailed(); } } } bool getPresent() { present = i2c_smbus_read_byte(file) >= 0; return present; } bool getTypeID(uint8_t& val) { constexpr uint8_t addr = 2; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } val = static_cast(ret); return true; } bool getBootVer(uint8_t& val) { constexpr uint8_t addr = 3; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } val = static_cast(ret); return true; } bool getFPGAVer(uint8_t& val) { constexpr uint8_t addr = 4; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } val = static_cast(ret); return true; } bool getSecurityRev(uint8_t& val) { constexpr uint8_t addr = 5; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } val = static_cast(ret); return true; } bool getPresence(uint8_t& val) { // NVMe drives do not assert PRSNTn, and as such do not get reported as // PRESENT in this register constexpr uint8_t addr = 8; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } // presence is inverted val = static_cast(~ret); return true; } bool getIFDET(uint8_t& val) { // This register is a bitmap of parallel GPIO pins connected to the // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert // IFDETn low when they are inserted into the HSBP.This register, in // combination with the PRESENCE register, are used by the BMC to detect // the presence of NVMe drives. constexpr uint8_t addr = 9; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } // ifdet is inverted val = static_cast(~ret); return true; } bool getFailed(uint8_t& val) { constexpr uint8_t addr = 0xC; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << "\n"; return false; } val = static_cast(ret); return true; } bool getRebuild(uint8_t& val) { constexpr uint8_t addr = 0xD; int ret = i2c_smbus_read_byte_data(file, addr); if (ret < 0) { std::cerr << "Error " << __FUNCTION__ << " " << strerror(ret) << "\n"; return false; } val = static_cast(ret); return true; } bool getInsertedAndRemovedNvmeDrives( std::forward_list& nvmeDrivesInserted, std::forward_list& nvmeDrivesRemoved) { /* Get the current drives status */ std::bitset<8> currDriveStatus; uint8_t nPresence; uint8_t nIfdet; if (!getPresence(nPresence) || !getIFDET(nIfdet)) { /* Error getting value. Return */ std::cerr << "Backplane " << name << " failed to get drive status\n"; return false; } std::string dbusHsbpName = boost::replace_all_copy(name, " ", "_"); auto nvmeMap = hsbpConfig.hsbpNvmeMap.find(dbusHsbpName); if (nvmeMap == hsbpConfig.hsbpNvmeMap.end()) { std::cerr << "Couldn't get the NVMe Map for the backplane : " << name << "\n"; return false; } /* NVMe drives do not assert PRSNTn, and as such do not get reported in * "presence" register, but assert ifdet low. This implies for a NVMe * drive to be present, corresponding precense bit has to be 0 and idfet * has to be 1 (as the values of these regosters are negated: check * getPresence() and getIfdet() functions) */ for (uint8_t bit = 0; bit < 8; bit++) { if ((nPresence & (1U << bit)) == 0) { if (nIfdet & (1U << bit)) { currDriveStatus.set(bit); } } } /* Determine Inserted and Removed Drives * Prev Bit | Curr Bit | Status * 0 | 0 | No Change * 0 | 1 | Inserted * 1 | 0 | Removed * 1 | 1 | No Change */ for (uint8_t index = 0; index < 8; index++) { /* Inserted */ if (!prevDriveStatus.test(index) && currDriveStatus.test(index)) { nvmeDrivesInserted.emplace_front(nvmeMap->second.at(index)); std::cerr << name << " : " << nvmeDrivesInserted.front() << " Inserted !\n"; } /* Removed */ else if (prevDriveStatus.test(index) && !currDriveStatus.test(index)) { nvmeDrivesRemoved.emplace_front(nvmeMap->second.at(index)); std::cerr << name << " : " << nvmeDrivesRemoved.front() << " Removed !\n"; } } prevDriveStatus = currDriveStatus; return true; } virtual ~Backplane() { timer.cancel(); objServer.remove_interface(hsbpItemIface); objServer.remove_interface(versionIface); objServer.remove_interface(storageInterface); objServer.remove_interface(assetInterface); objServer.remove_interface(activationIface); if (file >= 0) { close(file); } } size_t bus; size_t address; size_t backplaneIndex; std::string name; boost::asio::steady_timer timer; bool present = false; uint8_t typeId = 0; uint8_t bootVer = 0; uint8_t fpgaVer = 0; uint8_t securityRev = 0; uint8_t funSupported = 0; uint8_t presence = 0; uint8_t ifdet = 0; uint8_t failed = 0; uint8_t rebuilding = 0; std::bitset<8> prevDriveStatus; int file = -1; std::string type; std::shared_ptr hsbpItemIface; std::shared_ptr versionIface; std::shared_ptr storageInterface; std::shared_ptr assetInterface; std::shared_ptr activationIface; std::list drives; std::vector> leds; std::shared_ptr> muxes; }; /* Global HSBP backplanes and NVMe drives collection */ std::unordered_map> backplanes; std::list ownerlessDrives; // drives without a backplane /***************************** End of Section *******************************/ /****************************************************************************/ /***************** Miscellaneous Class/Function Definitions *****************/ /****************************************************************************/ /* The purpose of this class is to sync the code flow. Often times there could * be multiple dbus calls which are async, and upon completely finishing all * Dbus calls, we need to call next function, or handle the error. * When an object of this class goes out of scope, the respective handlers * will be called */ class AsyncCallbackHandler { bool errorOccurred = false; std::function onSuccess = nullptr; std::function onError = nullptr; public: explicit AsyncCallbackHandler(std::function onSuccessIn, std::function onErrorIn) : onSuccess(std::move(onSuccessIn)), onError(std::move(onErrorIn)) { } void setError() { errorOccurred = true; } ~AsyncCallbackHandler() { /* If error occurred flag was set, execute the error handler */ if (errorOccurred) { /* Check if Error Handler is defined */ if (onError) { onError(); } return; } /* If Success Handler is present, execute Success Handler */ if (onSuccess) { onSuccess(); } } }; void stopHsbpManager() { std::cerr << __FUNCTION__ << ": Stopping hsbp-manager\n"; appState = AppState::idle; hsbpConfig.clearConfig(); clockBuffers.clear(); ioExpanders.clear(); backplanes.clear(); io.stop(); } /***************************** End of Section *******************************/ /****************************************************************************/ /********* HSBP clock enable/disable related Function Definitions ***********/ /****************************************************************************/ void updateHsbpClocks(std::forward_list& nvmeDrivesInserted, std::forward_list& nvmeDrivesRemoved) { if (appState < AppState::backplanesLoaded) { std::cerr << "HSBP not initialized ! Cancelling Clock Update ! \n"; return; } std::cerr << "Updating HSBP drive clocks ...\n"; /* Loop through all clock buffers and try to update the clocks (this will be * done if the mode of operation of the clock buffer is SMBus) */ for (auto& clockBuffer : clockBuffers) { if (!clockBuffer.enableDisableClock(nvmeDrivesInserted, nvmeDrivesRemoved)) { std::cerr << "Error Occurred while setting the clock in \"" << clockBuffer.getName() << "\"\n"; } } /* If there are drives yet to be updated, check all the IO Expanders in case * they are mapped to the drives and enable the respective IO */ if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) { for (auto& ioExpander : ioExpanders) { if (!ioExpander.enableDisableOuput(nvmeDrivesInserted, nvmeDrivesRemoved)) { std::cerr << "Error Occurred while setting the IO in \"" << ioExpander.getName() << "\"\n"; } } } /* If there are drives still left, then one or more drives clock * enable/diable failed. There is a possibility of improper mapping or * current communication with the device failed */ if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) { std::cerr << "Critical Error !!!\nMapping issue detected !\n"; if (!nvmeDrivesInserted.empty()) { std::cerr << "The clock enable failed for : "; for (auto& nvme1 : nvmeDrivesInserted) { std::cerr << nvme1 << ", "; } std::cerr << "\n"; } if (!nvmeDrivesRemoved.empty()) { std::cerr << "The clock disable failed for : "; for (auto& nvme1 : nvmeDrivesRemoved) { std::cerr << nvme1 << ", "; } std::cerr << "\n"; } } } void scanHsbpDrives(bool& hsbpDriveScanInProgress) { std::cerr << __FUNCTION__ << ": Scanning HSBP drives status ...\n"; /* List variables to store the drives Inserted/Removed */ std::forward_list nvmeDrivesInserted; std::forward_list nvmeDrivesRemoved; /* Loop through each backplane present and get the list of inserted/removed * drives */ for (auto& [name, backplane] : backplanes) { backplane->getInsertedAndRemovedNvmeDrives(nvmeDrivesInserted, nvmeDrivesRemoved); } if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) { updateHsbpClocks(nvmeDrivesInserted, nvmeDrivesRemoved); } std::cerr << __FUNCTION__ << ": Scanning HSBP drives Completed\n"; hsbpDriveScanInProgress = false; } void checkHsbpDrivesStatus() { static bool hsbpDriveScanInProgress = false; static bool hsbpDriveRescanInQueue = false; if (appState < AppState::backplanesLoaded) { std::cerr << __FUNCTION__ << ": HSBP not initialized ! Cancelling scan of HSBP drives " "status ! \n"; return; } if (hsbpDriveScanInProgress) { /* Scan and Clock Update already in progress. Try again after sometime. * This event can occur due to the GPIO interrupt */ std::cerr << __FUNCTION__ << ": HSBP Drives Scan is already in progress\n"; if (hsbpDriveRescanInQueue) { /* There is already a Re-Scan in queue. No need to create multiple * rescans */ return; } hsbpDriveRescanInQueue = true; std::cerr << __FUNCTION__ << ": Queuing the Scan \n"; auto driveScanTimer = std::make_shared(io); driveScanTimer->expires_after(std::chrono::seconds(1)); driveScanTimer->async_wait( [driveScanTimer](const boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) { // Timer was Aborted return; } else if (ec) { std::cerr << "driveScanTimer: Timer error" << ec.message() << "\n"; return; } hsbpDriveRescanInQueue = false; checkHsbpDrivesStatus(); }); return; } hsbpDriveScanInProgress = true; /* Post the scan to IO queue and return from here. This enables capturing * next GPIO event if any */ boost::asio::post(io, []() { scanHsbpDrives(hsbpDriveScanInProgress); }); } /***************************** End of Section *******************************/ /****************************************************************************/ /********** Backplanes and NVMe drives related Function Definitions *********/ /****************************************************************************/ static size_t getDriveCount() { size_t count = 0; for (const auto& [key, backplane] : backplanes) { count += backplane->drives.size(); } return count + ownerlessDrives.size(); } void updateAssets() { appState = AppState::loadingDrives; /* Setup a callback to be called once the assets are populated completely or * fallback to error handler */ auto drivesLoadedCallback = std::make_shared( []() { appState = AppState::drivesLoaded; std::cerr << "Drives Updated !\n"; }, []() { // TODO: Handle this error if needed appState = AppState::backplanesLoaded; std::cerr << "An error occured ! Drives load failed \n"; }); conn->async_method_call( [drivesLoadedCallback](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; drivesLoadedCallback->setError(); return; } // drives may get an owner during this, or we might disover more // drives ownerlessDrives.clear(); for (const auto& [path, objDict] : subtree) { if (objDict.empty()) { continue; } const std::string& owner = objDict.begin()->first; // we export this interface too if (owner == busName) { continue; } if (std::find(objDict.begin()->second.begin(), objDict.begin()->second.end(), assetTag) == objDict.begin()->second.end()) { // no asset tag to associate to continue; } conn->async_method_call( [path, drivesLoadedCallback]( const boost::system::error_code ec2, const boost::container::flat_map< std::string, std::variant>& values) { if (ec2) { std::cerr << __FUNCTION__ << ": Error Getting Config " << ec2.message() << " " << "\n"; drivesLoadedCallback->setError(); return; } auto findBus = values.find("Bus"); if (findBus == values.end()) { std::cerr << __FUNCTION__ << ": Illegal interface at " << path << "\n"; drivesLoadedCallback->setError(); return; } // find the mux bus and addr size_t muxBus = static_cast( std::get(findBus->second)); std::filesystem::path muxPath = "/sys/bus/i2c/devices/i2c-" + std::to_string(muxBus) + "/mux_device"; if (!std::filesystem::is_symlink(muxPath)) { std::cerr << path << " mux does not exist\n"; drivesLoadedCallback->setError(); return; } // we should be getting something of the form 7-0052 // for bus 7 addr 52 std::string fname = std::filesystem::read_symlink(muxPath).filename(); auto findDash = fname.find('-'); if (findDash == std::string::npos || findDash + 1 >= fname.size()) { std::cerr << path << " mux path invalid\n"; drivesLoadedCallback->setError(); return; } std::string busStr = fname.substr(0, findDash); std::string muxStr = fname.substr(findDash + 1); size_t bus = static_cast(std::stoi(busStr)); size_t addr = static_cast(std::stoi(muxStr, nullptr, 16)); size_t muxIndex = 0; // find the channel of the mux the drive is on std::ifstream nameFile("/sys/bus/i2c/devices/i2c-" + std::to_string(muxBus) + "/name"); if (!nameFile) { std::cerr << __FUNCTION__ << ": Unable to open name file of bus " << muxBus << "\n"; drivesLoadedCallback->setError(); return; } std::string nameStr; std::getline(nameFile, nameStr); // file is of the form "i2c-4-mux (chan_id 1)", get chan // assume single digit chan const std::string prefix = "chan_id "; size_t findId = nameStr.find(prefix); if (findId == std::string::npos || findId + 1 >= nameStr.size()) { std::cerr << __FUNCTION__ << ": Illegal name file on bus " << muxBus << "\n"; } std::string indexStr = nameStr.substr(findId + prefix.size(), 1); size_t driveIndex = std::stoi(indexStr); boost::container::flat_map assetInventory; const std::array assetKeys = { "PartNumber", "SerialNumber", "Manufacturer", "Model"}; for (const auto& [key, value] : values) { if (std::find(assetKeys.begin(), assetKeys.end(), key) == assetKeys.end()) { continue; } assetInventory[key] = std::get(value); } Backplane* parent = nullptr; for (auto& [name, backplane] : backplanes) { muxIndex = 0; for (const Mux& mux : *(backplane->muxes)) { if (bus == mux.bus && addr == mux.address) { parent = backplane.get(); break; } muxIndex += mux.channels; } if (parent) { /* Found the backplane. No need to proceed * further */ break; } } // assume its a M.2 or something without a hsbp if (parent == nullptr) { std::string driveName = "Drive_" + std::to_string(getDriveCount() + 1); auto& drive = ownerlessDrives.emplace_back( driveName, true, true, true, false); drive.createAsset(assetInventory); return; } driveIndex += muxIndex; if (parent->drives.size() <= driveIndex) { std::cerr << __FUNCTION__ << ": Illegal drive index at " << path << " " << driveIndex << "\n"; drivesLoadedCallback->setError(); return; } auto it = parent->drives.begin(); std::advance(it, driveIndex); it->createAsset(assetInventory); }, owner, path, "org.freedesktop.DBus.Properties", "GetAll", "" /*all interface items*/); } }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 0, std::array{nvmeIntf}); } void populateMuxes(std::shared_ptr> muxes, std::string& rootPath) { const static std::array muxTypes = { "xyz.openbmc_project.Configuration.PCA9543Mux", "xyz.openbmc_project.Configuration.PCA9544Mux", "xyz.openbmc_project.Configuration.PCA9545Mux", "xyz.openbmc_project.Configuration.PCA9546Mux"}; conn->async_method_call( [muxes](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; return; } size_t index = 0; // as we use a flat map, these are sorted for (const auto& [path, objDict] : subtree) { if (objDict.empty() || objDict.begin()->second.empty()) { continue; } const std::string& owner = objDict.begin()->first; const std::vector& interfaces = objDict.begin()->second; const std::string* interface = nullptr; for (const std::string& iface : interfaces) { if (std::find(muxTypes.begin(), muxTypes.end(), iface) != muxTypes.end()) { interface = &iface; break; } } if (interface == nullptr) { std::cerr << __FUNCTION__ << ": Cannot get mux type\n"; continue; } conn->async_method_call( [path, muxes, index]( const boost::system::error_code ec2, const boost::container::flat_map< std::string, std::variant>>& values) { if (ec2) { std::cerr << __FUNCTION__ << ": Error Getting Config " << ec2.message() << "\n"; return; } auto findBus = values.find("Bus"); auto findAddress = values.find("Address"); auto findChannelNames = values.find("ChannelNames"); if (findBus == values.end() || findAddress == values.end()) { std::cerr << __FUNCTION__ << ": Illegal configuration at " << path << "\n"; return; } size_t bus = static_cast( std::get(findBus->second)); size_t address = static_cast( std::get(findAddress->second)); std::vector channels = std::get>( findChannelNames->second); muxes->emplace(bus, address, channels.size(), index); }, owner, path, "org.freedesktop.DBus.Properties", "GetAll", *interface); index++; } }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, rootPath, 1, muxTypes); } void populateHsbpBackplanes( const std::shared_ptr& backplanesLoadedCallback) { std::cerr << __FUNCTION__ << ": Scanning Backplanes ...\n"; appState = AppState::loadingBackplanes; backplanes.clear(); conn->async_method_call( [backplanesLoadedCallback](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; backplanesLoadedCallback->setError(); return; } if (subtree.empty()) { /* There wer no HSBP's detected. set teh state back to * componentsLoaded so that on backplane match event, the * process can start again */ appState = AppState::componentsLoaded; std::cerr << __FUNCTION__ << ": No HSBPs Detected....\n"; return; } for (const auto& [path, objDict] : subtree) { if (objDict.empty()) { std::cerr << __FUNCTION__ << ": Subtree data " "corrupted !\n"; backplanesLoadedCallback->setError(); return; } const std::string& owner = objDict.begin()->first; conn->async_method_call( [backplanesLoadedCallback, path, owner](const boost::system::error_code ec2, const boost::container::flat_map< std::string, BasicVariantType>& resp) { if (ec2) { std::cerr << __FUNCTION__ << ": Error Getting Config " << ec2.message() << "\n"; backplanesLoadedCallback->setError(); return; } std::optional bus; std::optional address; std::optional backplaneIndex; std::optional name; for (const auto& [key, value] : resp) { if (key == "Bus") { bus = std::get(value); } else if (key == "Address") { address = std::get(value); } else if (key == "Index") { backplaneIndex = std::get(value); } else if (key == "Name") { name = std::get(value); } } if (!bus || !address || !name || !backplaneIndex) { std::cerr << __FUNCTION__ << ": Illegal configuration at " << path << "\n"; backplanesLoadedCallback->setError(); return; } std::string parentPath = std::filesystem::path(path).parent_path(); const auto& [backplane, status] = backplanes.emplace( *name, std::make_shared( *bus, *address, *backplaneIndex, *name)); backplane->second->run(parentPath, owner); populateMuxes(backplane->second->muxes, parentPath); }, owner, path, "org.freedesktop.DBus.Properties", "GetAll", hsbpCpldInft); } }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 0, std::array{hsbpCpldInft}); } void setUpBackplanesAndDrives() { static bool backplanesScanInProgress = false; static bool backplanesRescanInQueue = false; if (appState < AppState::componentsLoaded) { std::cerr << __FUNCTION__ << ": Components are not initialized ! Cancelling scan of " "Backplanes ! \n"; return; } if (backplanesScanInProgress) { std::cerr << __FUNCTION__ << ": Backplanes Scan is already in progress\n"; if (backplanesRescanInQueue) { /* There is already a Re-Scan in queue. No need to create multiple * rescans */ return; } backplanesRescanInQueue = true; std::cerr << __FUNCTION__ << ": Queuing the Backplane Scan \n"; auto backplaneScanTimer = std::make_shared(io); backplaneScanTimer->expires_after(std::chrono::seconds(1)); backplaneScanTimer->async_wait( [backplaneScanTimer](const boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) { // Timer was Aborted return; } else if (ec) { std::cerr << "backplaneScanTimer: Timer error" << ec.message() << "\n"; return; } backplanesRescanInQueue = false; setUpBackplanesAndDrives(); }); return; } backplanesScanInProgress = true; /* Set Callback to be called once backplanes are populated to call * updateAssets() and checkHsbpDrivesStatus() or handle error scnenario */ auto backplanesLoadedCallback = std::make_shared( []() { /* If no HSBP's were detected, the state changes to * componentsLoaded. Proceed further only if state was * loadingBackplanes */ if (appState != AppState::loadingBackplanes) { backplanesScanInProgress = false; return; } /* If there is a ReScan in the Queue, dont proceed further. Load the * Backplanes again and then proceed further */ if (backplanesRescanInQueue) { backplanesScanInProgress = false; return; } appState = AppState::backplanesLoaded; std::cerr << __FUNCTION__ << ": Backplanes Loaded...\n"; checkHsbpDrivesStatus(); updateAssets(); backplanesScanInProgress = false; }, []() { /* Loading Backplanes is an important step. If the load failed due * to an error, stop the app so that restart cant be triggerred */ std::cerr << "Backplanes couldn't be loaded due to an error !...\n"; appState = AppState::idle; backplanesScanInProgress = false; stopHsbpManager(); }); populateHsbpBackplanes(backplanesLoadedCallback); } void setupBackplanesAndDrivesMatch() { static auto backplaneMatch = std::make_unique( *conn, "sender='xyz.openbmc_project.EntityManager', type='signal', " "member='PropertiesChanged', " "interface='org.freedesktop.DBus.Properties', " "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" + std::string(hsbpCpldInft) + "'", [](sdbusplus::message_t& msg) { std::string intfName; boost::container::flat_map values; msg.read(intfName, values); /* This match will be triggered for each of the property being set * under the hsbpCpldInft interface. Call the loader only on one * property say "name". This will avoid multiple calls to populate * function */ for (const auto& [key, value] : values) { if (key == "Name") { /* This match will be triggered when ever there is a * addition/removal of HSBP backplane. At this stage, all * the HSBP's need to be populated again and also assets * have to be re-discovered. So, setting state to * componentsLoaded and calling setUpBackplanesAndDrives() * only if configuration and components loading was * completed */ if (appState < AppState::componentsLoaded) { /* Configuration is not loaded yet. Backplanes will be * loaded * once configuration and components are loaded. */ std::cerr << __FUNCTION__ << ": Discarding Backplane match\n"; return; } appState = AppState::componentsLoaded; /* We will call the function after a small delay to let all * the properties to be intialized */ auto backplaneTimer = std::make_shared(io); backplaneTimer->expires_after(std::chrono::seconds(2)); backplaneTimer->async_wait( [backplaneTimer](const boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) { return; } else if (ec) { std::cerr << "backplaneTimer: Timer error" << ec.message() << "\n"; return; } setUpBackplanesAndDrives(); }); } } }); static auto drivesMatch = std::make_unique( *conn, "sender='xyz.openbmc_project.EntityManager', type='signal', " "member='PropertiesChanged', " "interface='org.freedesktop.DBus.Properties', arg0='" + std::string(nvmeIntf) + "'", [](sdbusplus::message_t& msg) { std::string intfName; boost::container::flat_map values; msg.read(intfName, values); /* This match will be triggered for each of the property being set * under the nvmeIntf interface. Call the loader only on one * property say "name". This will avoid multiple calls to populate * function */ for (const auto& [key, value] : values) { if (key == "Name") { /* This match will be triggered when ever there is a * addition/removal of drives. At this stage only assets * have to be re-discovered. So, setting state to * backplanesLoaded and calling updateAssets() only if all * previous states are completed */ if (appState < AppState::backplanesLoaded) { /* Configuration is not loaded yet. Drives will be * loaded once * configuration, components and backplanes are loaded. */ std::cerr << __FUNCTION__ << ": Discarding Drive match\n"; return; } appState = AppState::backplanesLoaded; /* We will call the function after a small delay to let all * the properties to be intialized */ auto driveTimer = std::make_shared(io); driveTimer->expires_after(std::chrono::seconds(2)); driveTimer->async_wait( [driveTimer](const boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) { return; } else if (ec) { std::cerr << "driveTimer: Timer error" << ec.message() << "\n"; return; } updateAssets(); }); } } }); } /***************************** End of Section *******************************/ /****************************************************************************/ /******************* Components related Function Definitions ****************/ /****************************************************************************/ bool verifyComponentsLoaded() { std::cerr << __FUNCTION__ << ": Verifying all Components...\n"; /* Loop through all clock buffers */ for (auto& clockBuffer : clockBuffers) { if (!clockBuffer.isInitialized()) { std::cerr << "Critical Error: Initializing \"" << clockBuffer.getName() << "\" failed\n"; return false; } } /* Loop through all IO Expanders */ for (auto& ioExpander : ioExpanders) { if (!ioExpander.isInitialized()) { std::cerr << "Critical Error: Initializing \"" << ioExpander.getName() << "\" failed\n"; return false; } } std::cerr << __FUNCTION__ << ": Verifying Components Complete\n"; return true; } /***************************** End of Section *******************************/ /****************************************************************************/ /****************** IO expander related Function Definitions ****************/ /****************************************************************************/ void loadIoExpanderInfo( const std::shared_ptr& componentsLoadedCallback) { appState = AppState::loadingComponents; /* Clear global ioExpanders to start off */ ioExpanders.clear(); conn->async_method_call( [componentsLoadedCallback](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; componentsLoadedCallback->setError(); return; } for (auto& [path, objDict] : subtree) { if (objDict.empty()) { std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; componentsLoadedCallback->setError(); return; } /* Ideally there would be only one element in objDict as only * one service exposes it and there would be only one interface * so it is safe to directly read them without loop */ const std::string& service = objDict.begin()->first; const std::string& intf = objDict.begin()->second.front(); conn->async_method_call( [componentsLoadedCallback]( const boost::system::error_code er, const boost::container::flat_map< std::string, BasicVariantType>& resp) { if (er) { std::cerr << __FUNCTION__ << ": Error Getting " "Config " << er.message() << "\n"; componentsLoadedCallback->setError(); return; } std::optional bus; std::optional address; std::optional confIORegAddr; std::optional outCtrlBaseAddr; std::optional outCtrlByteCount; std::unordered_map> ioMap; std::optional name; std::optional type; /* Loop through to get all IO Expander properties */ for (const auto& [key, value] : resp) { if (key == "Bus") { bus = std::get(value); } else if (key == "Address") { address = std::get(value); } else if (key == "ConfIORegAddr") { confIORegAddr = std::get(value); } else if (key == "OutCtrlBaseAddr") { outCtrlBaseAddr = std::get(value); } else if (key == "OutCtrlByteCount") { outCtrlByteCount = std::get(value); } else if (key == "Name") { name = std::get(value); } else if (key == "Type") { type = std::get(value); } else if (key.starts_with("IO")) { std::optional> outList; outList = std::get(value); if (!outList) { break; } ioMap.try_emplace(key, *outList); } } /* Verify if all properties were defined */ if (!bus || !address || !confIORegAddr || !outCtrlBaseAddr || !outCtrlByteCount || !name) { std::cerr << __FUNCTION__ << ": Incomplete " "Clock Buffer definition !! \n"; componentsLoadedCallback->setError(); return; } /* Check if we were able to get byteMap correctly */ if ((*outCtrlByteCount) != ioMap.size()) { std::cerr << "loadIoExpanderInfo(): Incomplete " "IO Map !! \n"; componentsLoadedCallback->setError(); return; } /* Create IO expander object and add it to global * ioExpanders vector */ ioExpanders.emplace_front( *bus, *address, *confIORegAddr, *outCtrlBaseAddr, *outCtrlByteCount, ioMap, *name, *type); }, service, path, "org.freedesktop.DBus.Properties", "GetAll", intf); } }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 0, hsbpConfig.ioExpanderTypes); } /***************************** End of Section *******************************/ /****************************************************************************/ /***************** Clock buffer related Function Definitions ****************/ /****************************************************************************/ void loadClockBufferInfo( const std::shared_ptr& componentsLoadedCallback) { appState = AppState::loadingComponents; /* Clear global clockBuffers to start off */ clockBuffers.clear(); conn->async_method_call( [componentsLoadedCallback](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; componentsLoadedCallback->setError(); return; } for (auto& [path, objDict] : subtree) { if (objDict.empty()) { std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; componentsLoadedCallback->setError(); return; } /* Ideally there would be only one element in objDict as only * one service exposes it and there would be only one interface * so it is safe to directly read them without loop */ const std::string& service = objDict.begin()->first; const std::string& intf = objDict.begin()->second.front(); conn->async_method_call( [componentsLoadedCallback]( const boost::system::error_code er, const boost::container::flat_map< std::string, BasicVariantType>& resp) { if (er) { std::cerr << __FUNCTION__ << ": Error Getting " "Config " << er.message() << "\n"; componentsLoadedCallback->setError(); return; } std::optional bus; std::optional address; std::optional mode; std::optional outCtrlBaseAddr; std::optional outCtrlByteCount; std::unordered_map> byteMap; std::optional name; std::optional type; /* Loop through to get all Clock Buffer properties */ for (const auto& [key, value] : resp) { if (key == "Bus") { bus = std::get(value); } else if (key == "Address") { address = std::get(value); } else if (key == "Mode") { mode = std::get(value); } else if (key == "OutCtrlBaseAddr") { outCtrlBaseAddr = std::get(value); } else if (key == "OutCtrlByteCount") { outCtrlByteCount = std::get(value); } else if (key == "Name") { name = std::get(value); } else if (key == "Type") { type = std::get(value); } else if (key.starts_with("Byte")) { std::optional> byteList; byteList = std::get(value); if (!byteList) { break; } byteMap.try_emplace(key, *byteList); } } /* Verify if all properties were defined */ if (!bus || !address || !mode || !outCtrlBaseAddr || !outCtrlByteCount || !name) { std::cerr << __FUNCTION__ << ": Incomplete " "Clock Buffer definition !! \n"; componentsLoadedCallback->setError(); return; } /* Check if we were able to get byteMap correctly */ if ((*outCtrlByteCount) != byteMap.size()) { std::cerr << __FUNCTION__ << ": Incomplete " "Byte Map !! \n"; componentsLoadedCallback->setError(); return; } /* Create clock buffer object and add it to global * clockBuffers vector */ clockBuffers.emplace_front( *bus, *address, *mode, *outCtrlBaseAddr, *outCtrlByteCount, byteMap, *name, *type); }, service, path, "org.freedesktop.DBus.Properties", "GetAll", intf); } }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 0, hsbpConfig.clockBufferTypes); } /***************************** End of Section *******************************/ /****************************************************************************/ /***************** HSBP Config related Function Definitions *****************/ /****************************************************************************/ void loadHsbpConfig() { appState = AppState::loadingHsbpConfig; conn->async_method_call( [](const boost::system::error_code ec, const GetSubTreeType& subtree) { if (ec) { std::cerr << __FUNCTION__ << ": Error contacting mapper " << ec.message() << "\n"; return; } if (subtree.empty()) { /* Entity manager is either still loading the configuration or * failed to load. In either way, return from here as the dbus * match will take care */ std::cerr << __FUNCTION__ << ": No configuration detected !!\n"; return; } /* There should be only one HSBP Configureation exposed */ if (subtree.size() != 1) { std::cerr << __FUNCTION__ << ": Multiple configurations " "detected !!\n"; /* Critical Error. Stop Application */ stopHsbpManager(); return; } auto& path = subtree.begin()->first; auto& objDict = subtree.begin()->second; if (objDict.empty()) { /* Critical Error. Stop Application */ std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; stopHsbpManager(); return; } const std::string& service = objDict.begin()->first; conn->async_method_call( [](const boost::system::error_code er, const boost::container::flat_map& resp) { if (er) { std::cerr << __FUNCTION__ << ": Error Getting Config " << er.message() << "\n"; /* Critical Error. Stop Application */ stopHsbpManager(); return; } std::optional rootI2cBus; std::optional> supportedHsbps; std::optional> clockBufferTypes; std::optional> ioExpanderTypes; /* Loop through to get root i2c bus and list of supported * HSBPs */ for (const auto& [key, value] : resp) { if (key == "HsbpSupported") { supportedHsbps = std::get>(value); } else if (key == "RootI2cBus") { rootI2cBus = std::get(value); } else if (key == "ClockBuffer") { clockBufferTypes = std::get>(value); } else if (key == "IoExpander") { ioExpanderTypes = std::get>(value); } } /* Verify if i2c bus, supported HSBP's and clock buffers * were defined (IO Expanders are optional) */ if (!rootI2cBus || !supportedHsbps || !clockBufferTypes) { std::cerr << __FUNCTION__ << ": Incomplete HSBP " "configuration !! \n"; /* Critical Error. Stop Application */ stopHsbpManager(); return; } /* Clear and Load all details to global hsbp configuration * variable */ hsbpConfig.clearConfig(); hsbpConfig.rootBus = *rootI2cBus; hsbpConfig.supportedHsbps = std::move(*supportedHsbps); for (auto& clkBuffType : *clockBufferTypes) { hsbpConfig.clockBufferTypes.emplace_back( "xyz.openbmc_project.Configuration." + clkBuffType); } if (ioExpanderTypes) { for (auto& ioCntrType : *ioExpanderTypes) { hsbpConfig.ioExpanderTypes.emplace_back( "xyz.openbmc_project.Configuration." + ioCntrType); } } /* Loop through to get HSBP-NVME map and Components map * details */ uint8_t hsbpMapCount = 0; for (const auto& [key, value] : resp) { if (std::find(hsbpConfig.supportedHsbps.begin(), hsbpConfig.supportedHsbps.end(), key) != hsbpConfig.supportedHsbps.end()) { std::optional> hsbpMap; hsbpMap = std::get(value); if (!hsbpMap) { break; } hsbpConfig.hsbpNvmeMap.try_emplace(key, *hsbpMap); hsbpMapCount++; } } /* Check if we were able to get all the HSBP-NVMe maps */ if (hsbpConfig.supportedHsbps.size() != hsbpMapCount) { std::cerr << __FUNCTION__ << ": Incomplete HSBP Map " "details !! \n"; /* Critical Error. Stop Application */ stopHsbpManager(); return; } /* HSBP configuration is loaded */ appState = AppState::hsbpConfigLoaded; std::cerr << "HSBP Config loaded !\n"; /* Get Clock buffers and IO expander details. Create shared * object of AsyncCallbackHandler with success and error * callback */ auto componentsLoadedCallback = std::make_shared< AsyncCallbackHandler>( []() { /* Verify if all components were initialized without * errors */ if (!verifyComponentsLoaded()) { /* The application cannot proceed further as * components initialization failed. App needs * Restart */ appState = AppState::idle; std::cerr << "One or more Componenets initialization " "failed !! Restart Required !\n"; stopHsbpManager(); } appState = AppState::componentsLoaded; setUpBackplanesAndDrives(); }, []() { /* The application cannot proceed further as * components load failed. App needs Restart */ appState = AppState::idle; std::cerr << "Loading Componenets failed !! " "Restart Required !\n"; stopHsbpManager(); }); loadClockBufferInfo(componentsLoadedCallback); if (ioExpanderTypes) { loadIoExpanderInfo(componentsLoadedCallback); } }, service, path, "org.freedesktop.DBus.Properties", "GetAll", hsbpConfigIntf); }, mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 0, std::array{hsbpConfigIntf}); } void setupHsbpConfigMatch() { static auto hsbpConfigMatch = std::make_unique( *conn, "sender='xyz.openbmc_project.EntityManager', type='signal', " "member='PropertiesChanged', " "interface='org.freedesktop.DBus.Properties', " "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" + std::string(hsbpConfigIntf) + "'", [](sdbusplus::message_t& msg) { std::string intfName; boost::container::flat_map values; msg.read(intfName, values); /* This match will be triggered for each of the property being set * under the hsbpConfig interface. "HsbpSupported" is one of the * important property which will enable us to read other properties. * So, when the match event occurs for "HsbpSupported" property * being set, we will call "loadHsbpConfig()" If the control has * come here, its either the first initialization or entity-manager * reload. So, we will reset the state to uninitialized */ for (const auto& [key, value] : values) { if (key == "HsbpSupported") { /* Configuration change detected, change the state to stop * other processing */ appState = AppState::idle; /* We will call the function after a small delay to let all * the properties to be intialized */ auto loadTimer = std::make_shared(io); loadTimer->expires_after(std::chrono::seconds(1)); loadTimer->async_wait( [loadTimer](const boost::system::error_code ec) { if (ec == boost::asio::error::operation_aborted) { return; } else if (ec) { std::cerr << __FUNCTION__ << ": Timer error" << ec.message() << "\n"; if (hsbpConfig.supportedHsbps.empty()) { /* Critical Error as none of the * configuration was loaded and timer * failed. Stop the application */ stopHsbpManager(); } return; } loadHsbpConfig(); }); } } }); } /***************************** End of Section *******************************/ /****************************************************************************/ /***************** GPIO Events related Function Definitions *****************/ /****************************************************************************/ static void nvmeLvc3AlertHandler() { /* If the state is not backplanesLoaded, we ignore the GPIO event as we * cannot communicate to the backplanes yet */ if (appState < AppState::backplanesLoaded) { std::cerr << __FUNCTION__ << ": HSBP not initialized ! Dropping the interrupt ! \n"; return; } /* This GPIO event only indicates the addition or removal of drive to either * of CPU. The backplanes detected need to be scanned and detect which drive * has been added/removed and enable/diable clock accordingly */ gpiod::line_event gpioLineEvent = nvmeLvc3AlertLine.event_read(); if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) { /* Check for HSBP Drives status to determine if any new drive has been * added/removed and update clocks accordingly */ checkHsbpDrivesStatus(); } nvmeLvc3AlertEvent.async_wait( boost::asio::posix::stream_descriptor::wait_read, [](const boost::system::error_code ec) { if (ec) { std::cerr << __FUNCTION__ << ": nvmealert event error: " << ec.message() << "\n"; } nvmeLvc3AlertHandler(); }); } static bool hsbpRequestAlertGpioEvents( const std::string& name, const std::function& handler, gpiod::line& gpioLine, boost::asio::posix::stream_descriptor& gpioEventDescriptor) { // Find the GPIO line gpioLine = gpiod::find_line(name); if (!gpioLine) { std::cerr << __FUNCTION__ << ": Failed to find the " << name << " line\n"; return false; } try { gpioLine.request( {"hsbp-manager", gpiod::line_request::EVENT_BOTH_EDGES, 0}); } catch (std::exception&) { std::cerr << __FUNCTION__ << ": Failed to request events for " << name << "\n"; return false; } int gpioLineFd = gpioLine.event_get_fd(); if (gpioLineFd < 0) { std::cerr << __FUNCTION__ << ": Failed to get " << name << " fd\n"; return false; } gpioEventDescriptor.assign(gpioLineFd); gpioEventDescriptor.async_wait( boost::asio::posix::stream_descriptor::wait_read, [&name, handler](const boost::system::error_code ec) { if (ec) { std::cerr << __FUNCTION__ << ": " << name << " fd handler error: " << ec.message() << "\n"; return; } handler(); }); return true; } /***************************** End of Section *******************************/ int main() { std::cerr << "******* Starting hsbp-manager *******\n"; /* Set the Dbus name */ conn->request_name(busName); /* Add interface for storage inventory */ objServer.add_interface("/xyz/openbmc_project/inventory/item/storage", "xyz.openbmc_project.inventory.item.storage"); /* HSBP initializtion flow: * 1. Register GPIO event callback on FM_SMB_BMC_NVME_LVC3_ALERT_N line * 2. Set up Dbus match for power - determine if host is up and running * or powered off * 3. Set up Dbus match for HSBP backplanes and Drives * 4. Load HSBP config exposed by entity manager * - Also setup a match to capture HSBP configuation in case * entity-manager restarts * 5. Load Clock buffer and IO expander (and other peripherals if any * related to HSBP functionality) * - Reload the info each time HSBP configuration is changed * 6. Populate all Backpanes (HSBP's) * 7. Load all NVMe drive's and associate with HSBP Backpane */ /* Register GPIO Events on FM_SMB_BMC_NVME_LVC3_ALERT_N */ if (!hsbpRequestAlertGpioEvents("FM_SMB_BMC_NVME_LVC3_ALERT_N", nvmeLvc3AlertHandler, nvmeLvc3AlertLine, nvmeLvc3AlertEvent)) { std::cerr << __FUNCTION__ << ": error: Unable to monitor events on HSBP " "Alert line\n"; return -1; } /* Setup Dbus-match for power */ setupPowerMatch(conn); /* Setup Dbus-match for HSBP backplanes and Drives */ setupBackplanesAndDrivesMatch(); /* Setup HSBP Config match and load config * In the event of entity-manager reboot, the match will help catch new * configuration. * In the event of hsbp-manager reboot, loadHsbpConfig will get all * config details and will take care of remaining config's to be * loaded */ setupHsbpConfigMatch(); loadHsbpConfig(); io.run(); std::cerr << __FUNCTION__ << ": Aborting hsbp-manager !\n"; return -1; }