| #ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_ | |
| #define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <exception> | |
| #include <filesystem> // NOLINT | |
| #include <fstream> | |
| #include <functional> | |
| #include <ios> | |
| #include <iterator> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/substitute.h" | |
| #include "bm_config.h" | |
| #include "bmcweb_config.h" // NOLINT(misc-include-cleaner) | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "registries/privilege_registry.hpp" | |
| #include "json_utils.hpp" | |
| #include "sw_utils.hpp" | |
| #include "system_utils.hpp" | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| namespace redfish { | |
| constexpr absl::string_view kPldmServiceName = "xyz.openbmc_project.pldm"; | |
| constexpr absl::string_view kPldmObjectPath = "/xyz/openbmc_project/pldm"; | |
| constexpr absl::string_view kPldmControlInterface = | |
| "xyz.openbmc_project.PLDM.control"; | |
| constexpr absl::string_view kPldmLoadSingleFileMethod = | |
| "loadSingleFileAndSyncCache"; | |
| inline std::string biosSettingFilepathFromConfig = BIOS_SETTING_FILE_PATH; | |
| inline std::string readBiosSettingsIntoBase64(std::string biosSettingBlobPath) { | |
| // If the path doesn't exist, set the properties to an empty string | |
| if (!std::filesystem::exists(biosSettingBlobPath)) { | |
| BMCWEB_LOG_ERROR << "the BIOS setting file doesn't exist, path:" | |
| << biosSettingBlobPath; | |
| return {}; | |
| } | |
| const auto fileSize = std::filesystem::file_size(biosSettingBlobPath); | |
| if (fileSize == 0) { | |
| BMCWEB_LOG_DEBUG << "the BIOS setting file is empty, path:" | |
| << biosSettingBlobPath; | |
| return {}; | |
| } | |
| // ManagedFd would be nice but stdplus is not included as DEPENDS in gbmcweb | |
| std::ifstream ifs(biosSettingBlobPath, std::ios::binary); | |
| ifs.exceptions(std::ifstream::failbit); | |
| std::vector<char> fileData(fileSize); | |
| try { | |
| std::copy(std::istreambuf_iterator<char>(ifs), | |
| std::istreambuf_iterator<char>(), fileData.begin()); | |
| } catch (std::ios_base::failure& fail) { | |
| BMCWEB_LOG_ERROR << "Failed to read the BIOS setting file"; | |
| return {}; | |
| } | |
| return crow::utility::base64encode( | |
| std::string_view(fileData.data(), fileData.size())); | |
| } | |
| inline std::string resourceIdFromNameString(const std::string& name) { | |
| // Find out the index of last non digit character(0,1,2,3,4,5,6,7,8,9) | |
| // assuming the inout has at least one digit so that we can get resource | |
| // index. For example, system1->1. | |
| return name.substr(name.find_last_not_of("0123456789") + 1); | |
| } | |
| #ifdef ENABLE_CONFIGURE_CORE_COUNT | |
| inline void populateCoreCount( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::shared_ptr<milotic_tlbmc::SystemRegistry>& registry, | |
| const std::string& systemName) { | |
| if (registry == nullptr) { | |
| BMCWEB_LOG_ERROR << "System Registry is Null"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| absl::StatusOr<uint32_t> coreCount = registry->GetCoreCount(systemName); | |
| if (coreCount.ok()) { | |
| asyncResp->res.jsonValue["Oem"]["Google"]["TotalEnabledCores"] = *coreCount; | |
| return; | |
| } | |
| if (absl::IsUnimplemented(coreCount.status())) { | |
| BMCWEB_LOG_DEBUG << "CoreCount is not implemented."; | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "GetCoreCount failed: " << coreCount.status(); | |
| messages::internalError(asyncResp->res); | |
| } | |
| #endif | |
| #ifdef ENABLE_CONFIGURE_MEMORY_CAPACITY | |
| inline void populateMemoryCapacity( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::shared_ptr<milotic_tlbmc::SystemRegistry>& registry, | |
| const std::string& systemName) { | |
| if (registry == nullptr) { | |
| BMCWEB_LOG_ERROR << "System Registry is Null"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| absl::StatusOr<uint32_t> memoryCapacity = | |
| registry->GetMemoryCapacityGib(systemName); | |
| if (memoryCapacity.ok()) { | |
| asyncResp->res.jsonValue["Oem"]["Google"]["MemoryCapacityGib"] = | |
| *memoryCapacity; | |
| return; | |
| } | |
| if (absl::IsUnimplemented(memoryCapacity.status())) { | |
| BMCWEB_LOG_DEBUG << "MemoryCapacityGib is not implemented."; | |
| return; | |
| } | |
| BMCWEB_LOG_ERROR << "GetMemoryCapacityGib failed: " | |
| << memoryCapacity.status(); | |
| messages::internalError(asyncResp->res); | |
| } | |
| #endif | |
| inline void handleBios(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName, bool multiHost) { | |
| // systemId should be empty for single host system. | |
| std::string systemId = multiHost ? resourceIdFromNameString(systemName) : ""; | |
| asyncResp->res.jsonValue["@odata.id"] = | |
| absl::Substitute("/redfish/v1/Systems/$0/Bios", systemName); | |
| asyncResp->res.jsonValue["@odata.type"] = "#Bios.v1_1_0.Bios"; | |
| asyncResp->res.jsonValue["Name"] = "BIOS Configuration"; | |
| asyncResp->res.jsonValue["Description"] = "BIOS Configuration Service"; | |
| asyncResp->res.jsonValue["Id"] = "BIOS"; | |
| asyncResp->res.jsonValue["Actions"]["#Bios.ResetBios"] = { | |
| {"target", | |
| absl::Substitute("/redfish/v1/Systems/$0/Bios/Actions/Bios.ResetBios", | |
| systemName)}}; | |
| // Get the ActiveSoftwareImage and SoftwareImages | |
| sw_util::populateSoftwareInformation(asyncResp, sw_util::biosPurpose, "", | |
| true, sw_util::SwAssociation::System, | |
| systemName); | |
| // Populated BIOS setting blob | |
| std::string biosSettingFilepath = biosSettingFilepathFromConfig; | |
| if (multiHost) { | |
| size_t posHost = biosSettingFilepath.find("host"); | |
| if (posHost == std::string::npos) { | |
| BMCWEB_LOG_DEBUG << "The BIOS setting file path template is ill-formated" | |
| << biosSettingFilepath; | |
| return; | |
| } | |
| // Get index | |
| int systemIdInt = 0; | |
| try { | |
| systemIdInt = std::stoi(systemId); | |
| // system1 -> /run/soc_firmware/host0/vmtppr.cfg | |
| // system2 -> /run/soc_firmware/host1/vmtppr.cfg | |
| systemIdInt -= 1; | |
| } catch (const std::exception& ex) { | |
| BMCWEB_LOG_DEBUG << "file to parse systemId " << systemName | |
| << ":from string to int, exception:" << ex.what(); | |
| return; | |
| } | |
| // insert the index right after "host" i.e. pos + 4 | |
| biosSettingFilepath.insert(posHost + 4, std::to_string(systemIdInt)); | |
| } | |
| asyncResp->res.jsonValue["Oem"]["Google"]["MemTestBlob"] = | |
| readBiosSettingsIntoBase64(biosSettingFilepath); | |
| std::shared_ptr<milotic_tlbmc::SystemRegistry> registry = | |
| managedStore::GetManagedObjectStore()->GetSystemRegistry(); | |
| #ifdef ENABLE_CONFIGURE_CORE_COUNT | |
| populateCoreCount(asyncResp, registry, systemName); | |
| #endif | |
| #ifdef ENABLE_CONFIGURE_MEMORY_CAPACITY | |
| populateMemoryCapacity(asyncResp, registry, systemName); | |
| #endif | |
| } | |
| /** | |
| * BiosService class supports handle get method for bios. | |
| */ | |
| inline void handleBiosServiceGet( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| constexpr std::array<std::string_view, 1> systemInterfaces{ | |
| {"xyz.openbmc_project.Inventory.Item.System"}}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/", 0, systemInterfaces, requestContext, | |
| [asyncResp, systemName](const boost::system::error_code& ec, | |
| const std::vector<std::string>& objects) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| system_utils::findSystemPathAndTriggerCallback( | |
| asyncResp, systemName, objects, | |
| [&systemName](const std::shared_ptr<bmcweb::AsyncResp>& resp, | |
| const std::string& objectPath) { | |
| const bool multiHost = !objectPath.empty(); | |
| handleBios(resp, systemName, multiHost); | |
| }); | |
| }); | |
| } | |
| inline void handleBiosSettingPatch( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& biosSettingFilepath, bool multiHost, | |
| std::optional<std::string> memTestBlob, | |
| std::optional<uint32_t> totalEnabledCores, | |
| std::optional<uint32_t> memoryCapacityGib, | |
| [[maybe_unused]] const std::string& systemName, int systemId = 0) { | |
| asyncResp->res.result(boost::beast::http::status::no_content); | |
| if (!memTestBlob && !totalEnabledCores && !memoryCapacityGib) { | |
| BMCWEB_LOG_ERROR << "No valid patch parameter provided"; | |
| redfish::messages::actionParameterMissing( | |
| asyncResp->res, "handleBiosSettingPatch", | |
| "MemTestBlob or TotalEnabledCores or MemoryCapacityGib"); | |
| return; | |
| } | |
| #ifdef ENABLE_CONFIGURE_CORE_COUNT | |
| if (totalEnabledCores) { | |
| std::shared_ptr<milotic_tlbmc::SystemRegistry> registry = | |
| managedStore::GetManagedObjectStore()->GetSystemRegistry(); | |
| if (registry == nullptr) { | |
| BMCWEB_LOG_ERROR << "SystemRegistry is null"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| absl::Status status = | |
| registry->SetCoreCount(systemName, *totalEnabledCores); | |
| if (!status.ok()) { | |
| BMCWEB_LOG_ERROR << "SetCoreCount failed: " << status; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| } | |
| #else | |
| if (totalEnabledCores) { | |
| BMCWEB_LOG_ERROR << "TotalEnabledCores is not supported"; | |
| messages::actionParameterNotSupported(asyncResp->res, "TotalEnabledCores", | |
| "Configuring Core Count"); | |
| return; | |
| } | |
| #endif | |
| #ifdef ENABLE_CONFIGURE_MEMORY_CAPACITY | |
| if (memoryCapacityGib) { | |
| std::shared_ptr<milotic_tlbmc::SystemRegistry> dimm_registry = | |
| managedStore::GetManagedObjectStore()->GetSystemRegistry(); | |
| if (dimm_registry == nullptr) { | |
| BMCWEB_LOG_ERROR << "SystemRegistry is null"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| absl::Status status = | |
| dimm_registry->SetMemoryCapacityGib(systemName, *memoryCapacityGib); | |
| if (!status.ok()) { | |
| BMCWEB_LOG_ERROR << "SetMemoryCapacityGib failed: " << status; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| } | |
| #else | |
| if (memoryCapacityGib) { | |
| BMCWEB_LOG_ERROR << "MemoryCapacityGib is not supported"; | |
| messages::actionParameterNotSupported(asyncResp->res, "MemoryCapacityGib", | |
| "Configuring Memory Capacity"); | |
| return; | |
| } | |
| #endif | |
| if (memTestBlob) { | |
| std::filesystem::path filePath(biosSettingFilepath); | |
| if (!std::filesystem::exists(filePath.parent_path())) { | |
| BMCWEB_LOG_ERROR | |
| << "The directory of the BIOS setting file doesn't exist"; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| std::string memTestBlobBase64; | |
| if (crow::utility::base64Decode(memTestBlob.value_or(""), | |
| memTestBlobBase64) == false) { | |
| BMCWEB_LOG_ERROR << "Base64 Decoding unsuccessful"; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| try { | |
| std::ofstream ofs; | |
| ofs.open(biosSettingFilepath, std::ios::trunc | std::ios::binary); | |
| ofs.write(reinterpret_cast<char*>(memTestBlobBase64.data()), | |
| // Get warning if no cast | |
| // " error: conversion to | |
| // 'std::streamsize' {aka 'int'} from | |
| // 'std::__cxx11::basic_string<char>::size_type' {aka | |
| // 'unsigned int'} may change the sign of the result | |
| // [-Werror=sign-conversion]" | |
| static_cast<int>(memTestBlobBase64.size())); | |
| ofs.close(); | |
| } catch (std::ios_base::failure& fail) { | |
| BMCWEB_LOG_ERROR << "Failed to write the BIOS setting file"; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| // We are using PLDM to transfer the BIOS file out in multi-host machine. | |
| // Reload the file content is necessary to guarantee the file content in | |
| // PLDMd cache is up-to-date. | |
| if (multiHost) { | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [asyncResp](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "DBUS response error: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| }), | |
| std::string(kPldmServiceName) + std::to_string(systemId), | |
| std::string(kPldmObjectPath), std::string(kPldmControlInterface), | |
| std::string(kPldmLoadSingleFileMethod), | |
| static_cast<uint16_t>(BIOS_SETTING_FILE_HANDLE)); | |
| } | |
| } | |
| redfish::messages::success(asyncResp->res); | |
| } | |
| inline void patchBiosSettings( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::optional<std::string> memTestBlob; | |
| std::optional<uint32_t> totalEnabledCores; | |
| std::optional<uint32_t> memoryCapacityGib; | |
| if (!ENABLE_BIOS_PEER_AUTH_BYPASS && !req.peer_authenticated) { | |
| redfish::messages::accessDenied(asyncResp->res, "Bios"); | |
| return; | |
| } | |
| if (!redfish::json_util::readJsonPatch( | |
| req, asyncResp->res, "Oem/Google/MemTestBlob", memTestBlob, | |
| "Oem/Google/TotalEnabledCores", totalEnabledCores, | |
| "Oem/Google/MemoryCapacityGib", memoryCapacityGib)) { | |
| BMCWEB_LOG_ERROR << "There was no valid patch payload"; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| constexpr std::array<std::string_view, 1> systemInterfaces{ | |
| {"xyz.openbmc_project.Inventory.Item.System"}}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTreePaths( | |
| "/", 0, systemInterfaces, requestContext, | |
| [asyncResp, systemName, memTestBlob, totalEnabledCores, | |
| memoryCapacityGib](const boost::system::error_code& ec, | |
| const std::vector<std::string>& objects) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error"; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| system_utils::findSystemPathAndTriggerCallback( | |
| asyncResp, systemName, objects, | |
| [&memTestBlob, &totalEnabledCores, &memoryCapacityGib, &systemName]( | |
| const std::shared_ptr<bmcweb::AsyncResp>& resp, | |
| const std::string& objectPath) { | |
| std::string biosSettingFilepath = biosSettingFilepathFromConfig; | |
| bool multiHost = !objectPath.empty(); | |
| int systemIdInt = 0; | |
| if (multiHost) { | |
| std::string systemId = resourceIdFromNameString(systemName); | |
| size_t posHost = biosSettingFilepath.find("host"); | |
| if (posHost == std::string::npos) { | |
| BMCWEB_LOG_DEBUG | |
| << "The BIOS setting file path template is invalid" | |
| << biosSettingFilepath; | |
| messages::internalError(resp->res); | |
| return; | |
| } | |
| try { | |
| systemIdInt = std::stoi(systemId); | |
| // system1 -> /run/soc_firmware/host0/vmtppr.cfg | |
| // system2 -> /run/soc_firmware/host1/vmtppr.cfg | |
| systemIdInt -= 1; | |
| } catch (const std::exception& ex) { | |
| BMCWEB_LOG_DEBUG | |
| << "file to parse systemId " << systemId | |
| << ":from string to int, exception:" << ex.what(); | |
| messages::internalError(resp->res); | |
| return; | |
| } | |
| biosSettingFilepath.insert(posHost + 4, | |
| std::to_string(systemIdInt)); | |
| } | |
| handleBiosSettingPatch(resp, biosSettingFilepath, multiHost, | |
| memTestBlob, totalEnabledCores, | |
| memoryCapacityGib, systemName, | |
| systemIdInt); | |
| }); | |
| }); | |
| } | |
| inline void requestRoutesBiosService(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/") | |
| .privileges(redfish::privileges::getBios) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleBiosServiceGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/") | |
| .privileges(redfish::privileges::patchBios) | |
| .methods(boost::beast::http::verb::patch)( | |
| std::bind_front(patchBiosSettings, std::ref(app))); | |
| } | |
| /** | |
| * BiosReset class supports handle POST method for Reset bios. | |
| * The class retrieves and sends data directly to D-Bus. | |
| * | |
| * Function handles POST method request. | |
| * Analyzes POST body message before sends Reset request data to D-Bus. | |
| */ | |
| inline void handleBiosResetPost( | |
| crow::App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& systemName) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| if (systemName != "system") { | |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", systemName); | |
| return; | |
| } | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&)>( | |
| [asyncResp](const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Failed to reset bios: " << ec; | |
| messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| }), | |
| "org.open_power.Software.Host.Updater", "/xyz/openbmc_project/software", | |
| "xyz.openbmc_project.Common.FactoryReset", "Reset"); | |
| } | |
| inline void requestRoutesBiosReset(App& app) { | |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/Actions/Bios.ResetBios/") | |
| .privileges(redfish::privileges::postBios) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handleBiosResetPost, std::ref(app))); | |
| } | |
| } // namespace redfish | |
| #endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_ |