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