blob: 51f733d09ed840f85691becc90cdf9d93674cb40 [file] [log] [blame]
/*
// 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 <algorithm>
#include <bitset>
#include <boost/algorithm/string/replace.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/container/flat_set.hpp>
#include <filesystem>
#include <forward_list>
#include <fstream>
#include <gpiod.hpp>
#include <iostream>
#include <list>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/bus/match.hpp>
#include <string>
#include <utility>
extern "C" {
#include <i2c/smbus.h>
#include <linux/i2c-dev.h>
}
/****************************************************************************/
/******************** 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<std::string>;
/***************************** 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<std::string> supportedHsbps;
std::unordered_map<std::string, NvmeMapping> hsbpNvmeMap;
std::vector<std::string> clockBufferTypes;
std::vector<std::string> 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<std::string, std::vector<std::string>> 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<uint8_t>(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<uint8_t>(outCtrlBaseAddr + i),
static_cast<uint8_t>(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<std::string, std::vector<std::string>>& 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<std::string>& nvmeDrivesInserted,
std::forward_list<std::string>& 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<uint8_t>(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<uint8_t>(outCtrlBaseAddr + i),
static_cast<uint8_t>(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<std::string, std::vector<std::string>> 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<uint8_t>(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<uint8_t>(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<uint8_t>(confIORegAddr + i),
static_cast<uint8_t>(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<uint8_t>(outCtrlBaseAddr + i),
static_cast<uint8_t>(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<std::string, std::vector<std::string>>& 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<std::string>& nvmeDrivesInserted,
std::forward_list<std::string>& 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<uint8_t>(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<uint8_t>(outCtrlBaseAddr + i),
static_cast<uint8_t>(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<ClockBuffer> clockBuffers;
std::forward_list<IoExpander> ioExpanders;
/* Boost IO context and Dbus variables */
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(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>
{
// led pattern addresses start at 0x10
Led(const std::string& path, size_t index, int fd) :
address(static_cast<uint8_t>(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)
{
std::shared_ptr<Led> self = shared_from_this();
ledInterface->register_property(
ledGroup::asserted, false, [self](const bool req, bool& val) {
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<uint8_t>(pattern));
return ret >= 0;
}
uint8_t address;
int file;
std::shared_ptr<sdbusplus::asio::dbus_interface> 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<Association>{});
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<std::string, std::string>& 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<Association> 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<Association>{});
}
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<sdbusplus::asio::dbus_interface> itemIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> assetIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> 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>
{
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<boost::container::flat_set<Mux>>())
{
}
void populateAsset(const std::string& rootPath, const std::string& busname)
{
conn->async_method_call(
[assetIface{assetInterface}, hsbpIface{hsbpItemIface}](
const boost::system::error_code ec,
const boost::container::flat_map<
std::string, std::variant<std::string>>& values) mutable {
if (ec)
{
std::cerr
<< "Error getting asset tag from HSBP configuration\n";
return;
}
assetIface = objServer.add_interface(
hsbpIface->get_object_path(), assetTag);
for (const auto& [key, value] : values)
{
const std::string* ptr = std::get_if<std::string>(&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<size_t>(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();
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();
auto 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<Backplane>(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> led = leds.emplace_back(std::make_shared<Led>(
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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(~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<uint8_t>(~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<uint8_t>(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<uint8_t>(ret);
return true;
}
bool getInsertedAndRemovedNvmeDrives(
std::forward_list<std::string>& nvmeDrivesInserted,
std::forward_list<std::string>& 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);
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<sdbusplus::asio::dbus_interface> hsbpItemIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
std::shared_ptr<sdbusplus::asio::dbus_interface> storageInterface;
std::shared_ptr<sdbusplus::asio::dbus_interface> assetInterface;
std::list<Drive> drives;
std::vector<std::shared_ptr<Led>> leds;
std::shared_ptr<boost::container::flat_set<Mux>> muxes;
};
/* Global HSBP backplanes and NVMe drives collection */
std::unordered_map<std::string, std::shared_ptr<Backplane>> backplanes;
std::list<Drive> 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<void()> onSuccess = nullptr;
std::function<void()> onError = nullptr;
public:
explicit AsyncCallbackHandler(std::function<void()> onSuccessIn,
std::function<void()> 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<std::string>& nvmeDrivesInserted,
std::forward_list<std::string>& 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<std::string> nvmeDrivesInserted;
std::forward_list<std::string> 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<boost::asio::steady_timer>(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<AsyncCallbackHandler>(
[]() {
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<uint64_t, std::string>>&
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<size_t>(
std::get<uint64_t>(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<size_t>(std::stoi(busStr));
size_t addr =
static_cast<size_t>(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<std::string, std::string>
assetInventory;
const std::array<const char*, 4> 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<std::string>(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<const char*, 1>{nvmeIntf});
}
void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes,
std::string& rootPath)
{
const static std::array<const std::string, 4> 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<std::string>& 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<uint64_t, std::vector<std::string>>>&
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<size_t>(
std::get<uint64_t>(findBus->second));
size_t address = static_cast<size_t>(
std::get<uint64_t>(findAddress->second));
std::vector<std::string> channels =
std::get<std::vector<std::string>>(
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<AsyncCallbackHandler>& 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<size_t> bus;
std::optional<size_t> address;
std::optional<size_t> backplaneIndex;
std::optional<std::string> name;
for (const auto& [key, value] : resp)
{
if (key == "Bus")
{
bus = std::get<uint64_t>(value);
}
else if (key == "Address")
{
address = std::get<uint64_t>(value);
}
else if (key == "Index")
{
backplaneIndex = std::get<uint64_t>(value);
}
else if (key == "Name")
{
name = std::get<std::string>(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<Backplane>(
*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<const char*, 1>{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<boost::asio::steady_timer>(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<AsyncCallbackHandler>(
[]() {
/* 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<sdbusplus::bus::match_t>(
*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<std::string, BasicVariantType> 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<boost::asio::steady_timer>(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<sdbusplus::bus::match_t>(
*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<std::string, BasicVariantType> 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<boost::asio::steady_timer>(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<AsyncCallbackHandler>& 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<uint64_t> bus;
std::optional<uint64_t> address;
std::optional<uint64_t> confIORegAddr;
std::optional<uint64_t> outCtrlBaseAddr;
std::optional<uint64_t> outCtrlByteCount;
std::unordered_map<std::string,
std::vector<std::string>>
ioMap;
std::optional<std::string> name;
std::optional<std::string> type;
/* Loop through to get all IO Expander properties */
for (const auto& [key, value] : resp)
{
if (key == "Bus")
{
bus = std::get<uint64_t>(value);
}
else if (key == "Address")
{
address = std::get<uint64_t>(value);
}
else if (key == "ConfIORegAddr")
{
confIORegAddr = std::get<uint64_t>(value);
}
else if (key == "OutCtrlBaseAddr")
{
outCtrlBaseAddr = std::get<uint64_t>(value);
}
else if (key == "OutCtrlByteCount")
{
outCtrlByteCount = std::get<uint64_t>(value);
}
else if (key == "Name")
{
name = std::get<std::string>(value);
}
else if (key == "Type")
{
type = std::get<std::string>(value);
}
else if (key.starts_with("IO"))
{
std::optional<std::vector<std::string>> outList;
outList = std::get<NvmeMapping>(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<AsyncCallbackHandler>& 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<uint64_t> bus;
std::optional<uint64_t> address;
std::optional<std::string> mode;
std::optional<uint64_t> outCtrlBaseAddr;
std::optional<uint64_t> outCtrlByteCount;
std::unordered_map<std::string,
std::vector<std::string>>
byteMap;
std::optional<std::string> name;
std::optional<std::string> type;
/* Loop through to get all Clock Buffer properties */
for (const auto& [key, value] : resp)
{
if (key == "Bus")
{
bus = std::get<uint64_t>(value);
}
else if (key == "Address")
{
address = std::get<uint64_t>(value);
}
else if (key == "Mode")
{
mode = std::get<std::string>(value);
}
else if (key == "OutCtrlBaseAddr")
{
outCtrlBaseAddr = std::get<uint64_t>(value);
}
else if (key == "OutCtrlByteCount")
{
outCtrlByteCount = std::get<uint64_t>(value);
}
else if (key == "Name")
{
name = std::get<std::string>(value);
}
else if (key == "Type")
{
type = std::get<std::string>(value);
}
else if (key.starts_with("Byte"))
{
std::optional<std::vector<std::string>>
byteList;
byteList = std::get<NvmeMapping>(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<std::string,
BasicVariantType>& resp) {
if (er)
{
std::cerr << __FUNCTION__ << ": Error Getting Config "
<< er.message() << "\n";
/* Critical Error. Stop Application */
stopHsbpManager();
return;
}
std::optional<uint64_t> rootI2cBus;
std::optional<std::vector<std::string>> supportedHsbps;
std::optional<std::vector<std::string>> clockBufferTypes;
std::optional<std::vector<std::string>> 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<std::vector<std::string>>(value);
}
else if (key == "RootI2cBus")
{
rootI2cBus = std::get<uint64_t>(value);
}
else if (key == "ClockBuffer")
{
clockBufferTypes =
std::get<std::vector<std::string>>(value);
}
else if (key == "IoExpander")
{
ioExpanderTypes =
std::get<std::vector<std::string>>(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<std::vector<std::string>> hsbpMap;
hsbpMap = std::get<NvmeMapping>(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<const char*, 1>{hsbpConfigIntf});
}
void setupHsbpConfigMatch()
{
static auto hsbpConfigMatch = std::make_unique<sdbusplus::bus::match_t>(
*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<std::string, BasicVariantType> 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<boost::asio::steady_timer>(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<void()>& 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;
}