| #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" |
| |
| #ifdef UNIT_TEST_BUILD |
| #include "test/g3/mock_managed_store.hpp" // NOLINT |
| #endif |
| |
| 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); |
| } |
| |
| 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); |
| |
| #ifdef ENABLE_CONFIGURE_CORE_COUNT |
| // Get SystemRegistry from ManagedObjectStore |
| std::shared_ptr<milotic_tlbmc::SystemRegistry> registry = |
| managedStore::GetManagedObjectStore()->GetSystemRegistry(); |
| |
| if (registry == nullptr) { |
| BMCWEB_LOG_ERROR << "System Registry is Null"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Call GetCoreCount with the systemName |
| absl::StatusOr<uint32_t> coreCount = registry->GetCoreCount(systemName); |
| if (!coreCount.ok()) { |
| if (absl::IsUnimplemented(coreCount.status())) { |
| BMCWEB_LOG_DEBUG << "CoreCount is not implemented."; |
| return; |
| } |
| BMCWEB_LOG_ERROR << "GetCoreCount failed: " << coreCount.status(); |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // Store the result in TotalEnabledCores |
| asyncResp->res.jsonValue["Oem"]["Google"]["TotalEnabledCores"] = *coreCount; |
| #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, |
| [[maybe_unused]] const std::string& systemName, int systemId = 0) { |
| asyncResp->res.result(boost::beast::http::status::no_content); |
| |
| if (!memTestBlob && !totalEnabledCores) { |
| BMCWEB_LOG_ERROR << "No valid patch parameter provided"; |
| redfish::messages::actionParameterMissing( |
| asyncResp->res, "handleBiosSettingPatch", |
| "MemTestBlob or TotalEnabledCores"); |
| 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 |
| |
| 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; |
| 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)) { |
| 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]( |
| 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, &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, 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_ |