| #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 |