blob: 9ebb26e74d939f07c8803fea21524c493e18e2ac [file] [log] [blame] [edit]
#include "pcie_bifurcation_utils.hpp"
#include "fru_device.hpp"
#include "fru_reader.hpp"
#include "fru_utils.hpp"
#include <iostream>
#include <numeric>
namespace google::pcie_bifurcation
{
namespace
{
inline bool
isSupportedLegacyBifurcation(const std::vector<uint8_t>& bifurcation)
{
return bifurcation.size() == 2 && bifurcation[0] == 8 &&
bifurcation[1] == 8;
}
} // namespace
constexpr std::string_view pcieBifurcationKeyPrefix = "PCIe-bifurcation:";
constexpr std::string_view badAmphenolPcieBifurcationKeyPrefix =
"\001PCIe-bifurcation:";
constexpr static const char* kEntityManagerServiceName =
"xyz.openbmc_project.EntityManager";
constexpr static const char* kPCIeDeviceInterfaceName =
"xyz.openbmc_project.Inventory.Item.PCIeDevice";
constexpr static const char* kPCIeSlotInterfaceName =
"xyz.openbmc_project.Inventory.Item.PCIeSlot";
constexpr static uint8_t kLegacyBifurcationMax = 16;
constexpr static uint8_t kBifurcationMax = 8;
// Takes a map with FRU data and get the PCIeBifurcation value from it in string
// format. For Example if there is a value PCIe-bifurcation:x8x8, then this
// function will return x8x8.
std::optional<PCIeBifurcationData>
getPcieBifurcationData(std::map<std::string, std::string>& fru_map)
{
for (const auto& fru_pair : fru_map)
{
if (fru_pair.second.substr(0, pcieBifurcationKeyPrefix.length()) ==
pcieBifurcationKeyPrefix ||
fru_pair.second.substr(
0, badAmphenolPcieBifurcationKeyPrefix.length()) ==
badAmphenolPcieBifurcationKeyPrefix)
{
const int prefixLength =
fru_pair.second.substr(0, pcieBifurcationKeyPrefix.length()) ==
pcieBifurcationKeyPrefix
? pcieBifurcationKeyPrefix.length()
: badAmphenolPcieBifurcationKeyPrefix.length();
std::string dataString = fru_pair.second.substr(prefixLength);
bool isCxl = false;
auto it = fru_map.find("cxl_device");
if (it != fru_map.end())
{
if (it->second == "true")
{
isCxl = true;
}
}
PCIeBifurcationData data = {dataString, isCxl};
return data;
}
}
return std::nullopt;
}
// Get the PCIe bifurcation data in the format of "x8x8" in string format and
// populates bifurcation data from it.
std::vector<uint8_t>
getBifurcationData(const PCIeBifurcationData& bifurcation_data)
{
uint8_t num = 0;
std::vector<uint8_t> data;
for (size_t i = 0; i < bifurcation_data.pcieBifurcationStr.size(); i++)
{
if (isdigit(bifurcation_data.pcieBifurcationStr[i]))
{
num *= 10;
num += bifurcation_data.pcieBifurcationStr[i] - '0';
}
else if (num != 0)
{
data.push_back(num);
num = 0;
}
}
if (num != 0)
{
data.push_back(num);
}
// If the device is CXL, set the CXL bit for the first port.
// By design, only the first port in a bifurcation supports CXL.
if (!data.empty() && bifurcation_data.cxlDevice)
{
data[0] |= 0x80;
}
return data;
}
// Reads the eeprom for bus at given address and populates PCIe data for the
// corresponding slot in the map.
std::optional<PCIeBifurcationData> getBifurcationFromFRU(uint32_t bus,
uint32_t addr)
{
FRUDevice device(bus, addr);
std::cout << "Reading FRU of " << bus << " and with address " << addr
<< '\n';
if (!device.isValidDevice())
{
std::cerr << "Device doesn't exist in bus: " << bus
<< " at address: " << addr << '\n';
return std::nullopt;
}
std::vector<uint8_t> contents = device.getFRUContents();
if (contents.empty())
{
std::cerr << "Device in bus '" << bus << "' and address '" << addr
<< "' has empty FRU contents." << std::endl;
return std::nullopt;
}
std::map<std::string, std::string> result;
resCodes res = formatIPMIFRU(contents, result);
if (res == resCodes::resErr)
{
std::cerr << "failed to parse FRU for device at bus " << bus
<< " address " << addr << "\n";
return std::nullopt;
}
// Get the PCIeBifurcation value and return it.
return getPcieBifurcationData(result);
}
std::vector<std::string> physicalAssociations(sdbusplus::bus::bus& sysBus,
const std::string& path)
{
const std::array<std::string_view, 2> interfaces = {
kPCIeDeviceInterfaceName, kPCIeSlotInterfaceName};
auto associations = sysBus.new_method_call(
"xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetAssociatedSubTreePaths");
associations.append(
sdbusplus::message::object_path(path + "/containing"),
sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0,
interfaces);
std::vector<std::string> mapperResponses;
try
{
auto responseMsg = sysBus.call(associations);
responseMsg.read(mapperResponses);
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << "Failed to get associations: " << e.what() << std::endl;
}
return mapperResponses;
}
std::optional<uint64_t> pcieDeviceMaxLanes(sdbusplus::bus::bus& sysBus,
const std::string& path)
{
auto getDeviceLanes =
sysBus.new_method_call(kEntityManagerServiceName, path.c_str(),
"org.freedesktop.DBus.Properties", "Get");
getDeviceLanes.append(kPCIeDeviceInterfaceName, "MaxLanes");
std::variant<uint64_t> deviceLanes;
try
{
auto responseMsg = sysBus.call(getDeviceLanes);
responseMsg.read(deviceLanes);
return std::get<std::uint64_t>(deviceLanes);
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << "Failed to get deviceLanes: " << e.what() << std::endl;
}
return std::nullopt;
}
std::optional<uint64_t> pcieSlotLanes(sdbusplus::bus::bus& sysBus,
const std::string& path)
{
auto getDeviceLanes =
sysBus.new_method_call(kEntityManagerServiceName, path.c_str(),
"org.freedesktop.DBus.Properties", "Get");
getDeviceLanes.append(kPCIeDeviceInterfaceName, "Lanes");
std::variant<uint64_t> slotLanes;
try
{
auto responseMsg = sysBus.call(getDeviceLanes);
responseMsg.read(slotLanes);
return std::get<std::uint64_t>(slotLanes);
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << "Failed to get slotLanes: " << e.what() << std::endl;
}
return std::nullopt;
}
std::vector<uint8_t> parseDevices(sdbusplus::bus::bus& sysBus,
const std::string& path)
{
std::optional<uint64_t> deviceLanes = pcieDeviceMaxLanes(sysBus, path);
if (deviceLanes != std::nullopt)
{
return std::vector<uint8_t>{static_cast<uint8_t>(deviceLanes.value())};
}
std::optional<uint64_t> slotProperties = pcieSlotLanes(sysBus, path);
if (slotProperties == std::nullopt)
{
return std::vector<uint8_t>();
}
auto maxLanes = slotProperties.value();
std::vector<std::string> associations = physicalAssociations(sysBus, path);
std::vector<uint8_t> bifurcation;
for (const std::string& childPath : associations)
{
std::vector<uint8_t> current = parseDevices(sysBus, childPath);
bifurcation.insert(bifurcation.end(), current.begin(), current.end());
}
uint64_t totalLanes =
std::accumulate(bifurcation.begin(), bifurcation.end(), 0);
if (totalLanes > maxLanes)
{
return std::vector<uint8_t>();
}
if (totalLanes < maxLanes)
{
bifurcation.emplace_back(maxLanes - totalLanes);
}
return bifurcation;
}
std::vector<uint8_t> parseBifurcationString(std::string_view bifurcationStr,
bool isUtilityCluster)
{
std::vector<uint8_t> result;
// An empty string is considered invalid for this format.
if (bifurcationStr.empty())
{
return {};
}
const char* current_pos = bifurcationStr.data();
const char* const end_pos = bifurcationStr.data() + bifurcationStr.size();
uint8_t sum = 0;
while (current_pos < end_pos)
{
// 1. Each segment must start with 'x'.
if (*current_pos != 'x')
{
return {}; // Invalid format: segment doesn't start with 'x'.
}
++current_pos;
// 2. A number must follow the 'x'.
if (current_pos == end_pos)
{
return {}; // Invalid format: string ends with 'x'.
}
// 3. Parse the number using std::from_chars.
unsigned int value = 0;
auto [ptr, ec] = std::from_chars(current_pos, end_pos, value);
if (ec != std::errc() || value > 255)
{
// This means no valid number could be parsed after 'x'.
// or value out of range [0, 255]
return {};
}
sum += value;
result.push_back(static_cast<uint8_t>(value));
// Move the pointer to the character after the parsed number.
current_pos = ptr;
}
if (sum == kLegacyBifurcationMax)
{
// From RP cables which is designed based on Server 2.0- PE slot (at
// most x16 for each). In Server 3.0, each A slot or B slot, at most it
// should only be x8 except for the x16 merge case.
if (isSupportedLegacyBifurcation(result))
{
result.pop_back();
}
else if (result.size() != 1)
{
std::cerr
<< "Err: For 16 bifurcation, only x16 or x8x8 is expected."
<< std::endl;
return {};
}
}
// Utility clusters will fall outside this category and aren't required
// to have a total of 8 or 16 lanes
else if (sum != kBifurcationMax && !isUtilityCluster)
{
std::cerr << "Err: Bifurcation sum is expected to be 8." << std::endl;
return {};
}
return result;
}
} // namespace google::pcie_bifurcation