blob: db6c2295c8a8589ddb6fd2cee927cbbac50fbe71 [file] [log] [blame] [edit]
#include "pcie_bifurcation.hpp"
#include "fru_device.hpp"
#include "fru_utils.hpp"
#include "pcie_bifurcation_utils.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/exception.hpp>
#include <fstream>
#include <future>
#include <set>
#include <string>
#include <variant>
#include <vector>
namespace google
{
namespace pcie_bifurcation
{
static constexpr char const* fruInterface =
"xyz.openbmc_project.Inventory.Item.I2CDevice";
static constexpr std::array<std::string_view, 1> fruInterfaces = {fruInterface};
PCIeBifurcation::PCIeBifurcation(std::string_view pcieConfigFilePath) :
pcieConfigFilePath(pcieConfigFilePath)
{}
std::optional<PCIeBifurcationData>
PCIeBifurcation::getPCIeBifurcationDataFromFRU(
uint32_t bus, const std::set<uint32_t>& addresses)
{
// Normally We have 1:1 mapping from bus->addr. However in some cases
// there are multiple address under a bus, but FRU is only under
// one address.
// Return the first address that contains a FRU.
for (uint32_t addr : addresses)
{
std::optional<PCIeBifurcationData> res =
getBifurcationFromFRU(bus, addr);
if (res != std::nullopt)
{
return res;
}
}
return std::nullopt;
}
std::optional<std::pair<uint32_t, std::set<uint32_t>>>
PCIeBifurcation::getSMBusAddrPair(const nlohmann::json& slotData)
{
if (slotData.is_null())
{
return std::nullopt;
}
auto smbus = slotData.find("smbus");
auto addr = slotData.find("addr");
if (smbus == slotData.end() || !smbus->is_number() ||
addr == slotData.end() || !addr->is_number())
{
std::cerr << "Invalid smbus/addr params in slotData\n";
return std::nullopt;
}
uint32_t addrValue = *addr;
fruBusAddrPair[*smbus].insert(addrValue);
std::string addressStr;
for (uint32_t addr : fruBusAddrPair[*smbus])
{
addressStr += std::format(" {:#x}", addr);
}
std::cout << "Bus " << *smbus << " has addresses: " << addressStr << "."
<< std::endl;
return std::make_pair(*smbus, fruBusAddrPair[*smbus]);
}
bool PCIeBifurcation::populatePCIeSlotDataMap(
const nlohmann::json& slotData,
std::map<uint8_t, PCIeBifurcationData>& slotDataMap,
std::mutex& slotDataMapMutex)
{
auto slot = slotData.find("slot");
if (slot == slotData.end() || !slot->is_number())
{
std::cerr << "Slot is not valid\n";
return false;
}
std::optional<PCIeBifurcationData> pcieBifurcationData = std::nullopt;
std::string pcieBifurcationDataStr;
bool cxlDevice = false;
std::optional<std::pair<uint32_t, std::set<uint32_t>>> smbusAddrPair =
getSMBusAddrPair(slotData);
if (smbusAddrPair != std::nullopt)
{
pcieBifurcationData = getPCIeBifurcationDataFromFRU(
smbusAddrPair->first, smbusAddrPair->second);
}
if (pcieBifurcationData != std::nullopt &&
!pcieBifurcationData->pcieBifurcationStr.empty())
{
pcieBifurcationDataStr = pcieBifurcationData->pcieBifurcationStr;
cxlDevice = pcieBifurcationData->cxlDevice;
}
// If reading pcie bifurcation data from eeprom failed. Then, populate the
// config using the "default_config" array in pcie bifurcation config file.
// That is, if the "default_config" is set to [8, 8], then bifurcation
// string will become x8x8
else if (slotData.find("default_config") != slotData.end() &&
slotData.find("default_config")->is_array())
{
for (const nlohmann::json& config : slotData["default_config"])
{
if (!config.is_number_unsigned())
{
std::cerr << "Default Config is not unsigned integer\n";
return false;
}
uint32_t config_integer = config.template get<std::uint32_t>();
pcieBifurcationDataStr += "x" + std::to_string(config_integer);
}
}
else
{
std::cerr << "default_config is either not available or invalid\n";
return false;
}
// Lock the mutex before updating the slotDataMap
std::lock_guard<std::mutex> guard(slotDataMapMutex);
slotDataMap[*slot].cxlDevice = cxlDevice;
slotDataMap[*slot].pcieBifurcationStr = pcieBifurcationDataStr;
return true;
}
bool PCIeBifurcation::writeBifurcationDataToFile(
const std::string& outputPath,
std::vector<uint8_t>& aggregatedBifurcationData)
{
std::remove(outputPath.c_str()); // Delete the file if already exists.
std::ofstream outputFile(outputPath, std::ios::binary);
if (outputFile.fail())
{
std::cerr << "Creating file(" << outputPath << ") failed" << '\n';
return false;
}
outputFile.write(reinterpret_cast<char*>(&aggregatedBifurcationData[0]),
static_cast<int64_t>(sizeof(uint8_t) *
aggregatedBifurcationData.size()));
outputFile.close();
return true;
}
bool PCIeBifurcation::populateCPUPCIeData(const nlohmann::json& cpuPCIeData)
{
if (cpuPCIeData.find("slot_data") == cpuPCIeData.end())
{
std::cerr << "slot_data not found\n";
return false;
}
if (!cpuPCIeData.find("slot_data")->is_array())
{
std::cerr << "slot_data is not array\n";
return false;
}
if (cpuPCIeData.find("output_path") == cpuPCIeData.end())
{
std::cerr << "output_path not found\n";
return false;
}
if (!cpuPCIeData.find("output_path")->is_string())
{
std::cerr << "output_path is not string\n";
return false;
}
std::map<uint8_t, PCIeBifurcationData> slotDataMap;
std::mutex slotDataMapMutex;
std::vector<std::future<bool>> slotPopulateTasks;
bool status = true;
for (const nlohmann::json& slotData : cpuPCIeData["slot_data"])
{
slotPopulateTasks.push_back(
std::async(std::launch::async,
[this, slotData, &slotDataMap, &slotDataMapMutex] {
return populatePCIeSlotDataMap(slotData, slotDataMap,
slotDataMapMutex);
}));
}
for (std::future<bool>& task : slotPopulateTasks)
{
status &= task.get();
}
if (!status)
{
std::cerr << "Populating PCIe SlotData Map failed\n";
return false;
}
std::vector<uint8_t> aggregatedBifurcationData;
aggregatedBifurcationData.push_back(slotDataMap.size());
for (const auto& slotData : slotDataMap)
{
std::vector<uint8_t> bifurcationData =
getBifurcationData(slotData.second);
aggregatedBifurcationData.push_back(bifurcationData.size());
for (uint8_t data : bifurcationData)
{
aggregatedBifurcationData.push_back(data);
}
}
std::string outputPath = cpuPCIeData["output_path"];
return writeBifurcationDataToFile(outputPath, aggregatedBifurcationData);
}
// Refer to pcie_bifurcation.hpp file for sample configuration
bool PCIeBifurcation::populatePCIeBifurcationData()
{
populateFruBusAddrPair();
std::ifstream pcieConfigFile(std::string{pcieConfigFilePath});
if (!pcieConfigFile.is_open())
{
std::cerr << "Unable to open file: " << pcieConfigFilePath << '\n';
return false;
}
nlohmann::json jsonData;
jsonData = nlohmann::json::parse(pcieConfigFile, nullptr, false);
if (jsonData.is_discarded())
{
std::cerr << "JsonData is not valid\n";
return false;
}
if (!jsonData.is_array())
{
std::cerr << "JsonData is not an array\n";
return false;
}
bool returnStatus = true;
std::vector<std::future<bool>> cpuPopulateTasks;
for (nlohmann::json::iterator it = jsonData.begin(); it != jsonData.end();
it++)
{
cpuPopulateTasks.push_back(
std::async(std::launch::async, [this, cpuPCIeData = *it] {
return populateCPUPCIeData(cpuPCIeData);
}));
}
for (std::future<bool>& task : cpuPopulateTasks)
{
returnStatus &= task.get();
}
if (!returnStatus)
{
std::cerr << "populating PCIe data of a CPU failed.\n";
}
// Return true only if writing PCIe data of all CPUs is successful.
return returnStatus;
}
// Popluate the FRU bus and address pair from FruDevice service.
void PCIeBifurcation::populateFruBusAddrPair()
{
auto sysBus = sdbusplus::bus::new_bus();
auto fruSubtree = sysBus.new_method_call(
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths");
fruSubtree.append("/xyz/openbmc_project/FruDevice", 2, fruInterfaces);
for (int retry = 0; retry < 10; retry++)
{
try
{
std::vector<std::string> mapperResponses;
auto responseMsg = sysBus.call(fruSubtree);
responseMsg.read(mapperResponses);
for (const auto& response : mapperResponses)
{
auto getProperty = sysBus.new_method_call(
"xyz.openbmc_project.FruDevice", response.c_str(),
"org.freedesktop.DBus.Properties", "GetAll");
getProperty.append(fruInterface);
std::map<std::string, std::variant<uint32_t>> properties;
auto resp = sysBus.call(getProperty);
resp.read(properties);
uint32_t bus = std::get<uint32_t>(properties["Bus"]);
uint32_t addr = std::get<uint32_t>(properties["Address"]);
fruBusAddrPair[bus].insert(addr);
}
break;
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << "Failed to get FRU devices." << e.what() << std::endl;
}
sleep(1);
}
}
} // namespace pcie_bifurcation
} // namespace google