smbios: support gRPC BiosSettingsService
Implement the BiosSettingsService according to design
go/bios-settings-service
Tested:
https://paste.googleplex.com/5440790091530240
Google-Bug-Id: 358609968
Change-Id: Ica2a676b7094393814e706fb22e6d679a61d1141
Signed-off-by: Jinliang Wang <jinliangw@google.com>
diff --git a/recipes-phosphor/smbios/smbios-mdr/0001-Add-gRPC-BiosSettingsService.patch b/recipes-phosphor/smbios/smbios-mdr/0001-Add-gRPC-BiosSettingsService.patch
new file mode 100644
index 0000000..7c79cd0
--- /dev/null
+++ b/recipes-phosphor/smbios/smbios-mdr/0001-Add-gRPC-BiosSettingsService.patch
@@ -0,0 +1,302 @@
+From 4873c8b7bd814f08bd9ddbc585a35a771cff6950 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Thu, 17 Oct 2024 15:15:24 -0700
+Subject: [PATCH] Add gRPC BiosSettingsService
+
+Support gRPC service as below so NERF can get and report bios settings
+with BMC:
+
+service BiosSettingsService {
+ rpc GetBiosSettings(google.protobuf.Empty) returns (phosphor.bios.BiosSettings) {}
+ rpc ReportBiosSettingsStatus(phosphor.bios.BiosSettingsStatus) returns (google.protobuf.Empty) {}
+}
+
+For GetBiosSettings, we read and parse text format protobuf message from
+/var/google/bios-settings.txtpb file. This file path is persistent
+across power cycle.
+
+For ReportBiosSettingsStatus, we serialize the BiosSettingsStatus
+message into /var/run/bios-settings-status.txtpb file. This file path is
+located within tmpfs-mounted file system, its content will get loss
+after BMC reboot or power cycle. Host needs to call
+ReportBiosSettingsStatus periodically to update bios-settings-status in
+case BMC reboot.
+
+Tested:
+https://paste.googleplex.com/5440790091530240
+
+Google-Bug-Id: 358609968
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/smbios-grpc-blobs/grpc_callback.cpp | 116 ++++++++++++++++++
+ src/smbios-grpc-blobs/grpc_callback.hpp | 24 ++++
+ src/smbios-grpc-blobs/grpc_instance.cpp | 1 +
+ src/smbios-grpc-blobs/grpc_instance.hpp | 6 +-
+ .../proto/BiosSettings.proto | 31 +++++
+ src/smbios-grpc-blobs/proto/meson.build | 2 +-
+ 6 files changed, 178 insertions(+), 2 deletions(-)
+ create mode 100644 src/smbios-grpc-blobs/proto/BiosSettings.proto
+
+diff --git a/src/smbios-grpc-blobs/grpc_callback.cpp b/src/smbios-grpc-blobs/grpc_callback.cpp
+index 67f3db7..3d75992 100644
+--- a/src/smbios-grpc-blobs/grpc_callback.cpp
++++ b/src/smbios-grpc-blobs/grpc_callback.cpp
+@@ -3,11 +3,16 @@
+ #include "smbios_file.hpp"
+
+ #include <grpcpp/grpcpp.h>
++#include <google/protobuf/text_format.h>
+
+ #include <boost/asio/io_context.hpp>
+ #include <boost/asio/steady_timer.hpp>
+ #include <phosphor-logging/lg2.hpp>
+
++#include <sys/sysinfo.h>
++#include <sstream>
++#include <fstream>
++
+ namespace blobs
+ {
+
+@@ -108,4 +113,115 @@ bool SmbiosTransferServiceImpl::acceptEntry(const SmbiosEntry& entry,
+ return true;
+ }
+
++// Non-volatile path
++constexpr const char* biosSettingsConfigFile =
++ "/var/google/bios-settings.txtpb";
++// Volatile path
++constexpr const char* biosSettingsStatusFile =
++ "/var/run/bios-settings-status.txtpb";
++
++static uint64_t getSystemUptimeInSeconds()
++{
++ struct sysinfo s_info;
++ int error = sysinfo(&s_info);
++ if (error != 0)
++ {
++ // Unlikely to fail
++ lg2::error("Failed to call sysinfo, error = {ERR}", "ERR", error);
++ return 0;
++ }
++ return s_info.uptime;
++}
++
++// Read and Write the simple message should be quick enough, we can use sync
++// implementation.
++grpc::Status BiosSettingsServiceImpl::GetBiosSettings(
++ [[maybe_unused]] grpc::ServerContext* context,
++ [[maybe_unused]] const google::protobuf::Empty* request,
++ phosphor::bios::BiosSettings* reply)
++{
++ std::ifstream ifs(biosSettingsConfigFile);
++ std::string errorMessage;
++
++ if (!ifs.is_open())
++ {
++ errorMessage = "Failed to open bios-settings.txtpb";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::NOT_FOUND, errorMessage};
++ }
++
++ std::stringstream buffer;
++ buffer << ifs.rdbuf();
++ std::string content = buffer.str();
++ if (content.empty())
++ {
++ errorMessage = "Empty bios-settings.txtpb";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::INTERNAL, errorMessage};
++ }
++
++ google::protobuf::TextFormat::Parser parser;
++ if (!parser.ParseFromString(content, reply))
++ {
++ errorMessage = "Failed to parse bios-settings.txtpb";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::INTERNAL, errorMessage};
++ }
++
++ lg2::info("Received GetBiosSettings gRPC");
++ return grpc::Status::OK;
++}
++
++grpc::Status BiosSettingsServiceImpl::ReportBiosSettingsStatus(
++ [[maybe_unused]] grpc::ServerContext* context,
++ const phosphor::bios::BiosSettingsStatus* request,
++ [[maybe_unused]] google::protobuf::Empty* reply)
++{
++ phosphor::bios::BiosSettingsStatus status;
++ status.CopyFrom(*request);
++ status.set_updated_timestamp(getSystemUptimeInSeconds());
++
++ std::string errorMessage;
++ std::string text;
++ google::protobuf::TextFormat::Printer printer;
++ if (!printer.PrintToString(status, &text))
++ {
++ errorMessage =
++ "Failed to transform BiosSettingsStatus message into text format";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::INVALID_ARGUMENT, errorMessage};
++ }
++
++ std::string statusFile;
++ if ((instance + 1) == numInstances)
++ {
++ // Last instance, assume we are single host system
++ statusFile = biosSettingsStatusFile;
++ }
++ else
++ {
++ // Not last instance, assume we are multi-host system
++ statusFile = "/var/run/bios-settings-status-" +
++ std::to_string(instance) + ".txtpb";
++ }
++ std::ofstream out(statusFile);
++ if (!out)
++ {
++ errorMessage = "Failed to open bios-settings-status.txtpb";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::INTERNAL, errorMessage};
++ }
++ out << text;
++ out.close();
++ if (!out)
++ {
++ errorMessage = "Failed to write into bios-settings-status.txtpb";
++ lg2::error("{TXT}", "TXT", errorMessage);
++ return {grpc::StatusCode::INTERNAL, errorMessage};
++ }
++
++ lg2::info("Received ReportBiosSettingsStatus gRPC");
++ return grpc::Status::OK;
++}
++
+ } // namespace blobs
+diff --git a/src/smbios-grpc-blobs/grpc_callback.hpp b/src/smbios-grpc-blobs/grpc_callback.hpp
+index 579693b..aae9f73 100644
+--- a/src/smbios-grpc-blobs/grpc_callback.hpp
++++ b/src/smbios-grpc-blobs/grpc_callback.hpp
+@@ -1,6 +1,7 @@
+ #pragma once
+
+ #include "SmbiosTransfer.grpc.pb.h"
++#include "BiosSettings.grpc.pb.h"
+
+ #include "smbios_file.hpp"
+ #include "smbios_link.hpp"
+@@ -42,4 +43,27 @@ class SmbiosTransferServiceImpl final :
+ std::shared_ptr<boost::asio::io_context> mainIo;
+ };
+
++class BiosSettingsServiceImpl final :
++ public phosphor::bios::BiosSettingsService::Service
++{
++ public:
++ explicit BiosSettingsServiceImpl(int instanceNumber) :
++ phosphor::bios::BiosSettingsService::Service(),
++ instance(instanceNumber)
++ {}
++
++ grpc::Status
++ GetBiosSettings([[maybe_unused]] grpc::ServerContext* context,
++ [[maybe_unused]] const google::protobuf::Empty* request,
++ phosphor::bios::BiosSettings* reply) override;
++
++ grpc::Status ReportBiosSettingsStatus(
++ [[maybe_unused]] grpc::ServerContext* context,
++ const phosphor::bios::BiosSettingsStatus* request,
++ [[maybe_unused]] google::protobuf::Empty* reply) override;
++
++ private:
++ int instance;
++};
++
+ } // namespace blobs
+diff --git a/src/smbios-grpc-blobs/grpc_instance.cpp b/src/smbios-grpc-blobs/grpc_instance.cpp
+index c261a80..87540fc 100644
+--- a/src/smbios-grpc-blobs/grpc_instance.cpp
++++ b/src/smbios-grpc-blobs/grpc_instance.cpp
+@@ -91,6 +91,7 @@ void SmbiosGrpcInstance::start()
+
+ grpcBuilder.AddListeningPort(address, grpcCredentials);
+ grpcBuilder.RegisterService(grpcServiceCallback.get());
++ grpcBuilder.RegisterService(biosSettingGrpcService.get());
+
+ lg2::info("Claimed gRPC address: {ADDRESS} for {I}", "ADDRESS", address,
+ "I", instance);
+diff --git a/src/smbios-grpc-blobs/grpc_instance.hpp b/src/smbios-grpc-blobs/grpc_instance.hpp
+index f0ba731..3637fcb 100644
+--- a/src/smbios-grpc-blobs/grpc_instance.hpp
++++ b/src/smbios-grpc-blobs/grpc_instance.hpp
+@@ -27,7 +27,9 @@ class SmbiosGrpcInstance
+ dbusLink(std::make_shared<SmbiosAppLink>(mainIo, mainConn,
+ mainObjServer, instance)),
+ grpcServiceCallback(std::make_unique<SmbiosTransferServiceImpl>(
+- instance, dbusLink, mainIo))
++ instance, dbusLink, mainIo)),
++ biosSettingGrpcService(
++ std::make_unique<BiosSettingsServiceImpl>(instance))
+ {}
+
+ void start();
+@@ -56,6 +58,8 @@ class SmbiosGrpcInstance
+ // This is a pointer because gRPC RegisterService() requires it
+ std::unique_ptr<SmbiosTransferServiceImpl> grpcServiceCallback;
+
++ std::unique_ptr<BiosSettingsServiceImpl> biosSettingGrpcService;
++
+ // Here, and below, do not need parameters in constructor
+ // This is a pointer because gRPC BuildAndStart() requires it
+ std::unique_ptr<grpc::Server> grpcServer;
+diff --git a/src/smbios-grpc-blobs/proto/BiosSettings.proto b/src/smbios-grpc-blobs/proto/BiosSettings.proto
+new file mode 100644
+index 0000000..fd5f1b5
+--- /dev/null
++++ b/src/smbios-grpc-blobs/proto/BiosSettings.proto
+@@ -0,0 +1,31 @@
++syntax = "proto3";
++
++package phosphor.bios;
++
++import "google/protobuf/empty.proto";
++
++message BiosSettings {
++ // Enable EGM mode, required by Grace CPU
++ optional bool grace_egm_enabled = 1;
++ // Memory in MB to reserve for hypervisor, required by Grace CPU
++ optional uint64 grace_egm_hypervisor_reserved_memory_mb = 2;
++}
++
++message BiosSettingsStatus {
++ // Success/Failure.
++ optional bool success = 1;
++ // Provide error information - which setting failed and why
++ optional string error_message = 2;
++ // Timestamp (uptime) in seconds, recorded by BMC when it receives the BiosSettingsStatus
++ optional uint64 updated_timestamp = 3;
++ // Active BIOS settings reported from host
++ optional BiosSettings biosSettings = 4;
++}
++
++// Service to provide BIOS settings
++service BiosSettingsService {
++ // Get BIOS settings
++ rpc GetBiosSettings(google.protobuf.Empty) returns (BiosSettings) {}
++ // Report status of applied BIOS settings, periodically updated by host
++ rpc ReportBiosSettingsStatus(BiosSettingsStatus) returns (google.protobuf.Empty) {}
++}
+diff --git a/src/smbios-grpc-blobs/proto/meson.build b/src/smbios-grpc-blobs/proto/meson.build
+index bab0fb5..46e1fd1 100644
+--- a/src/smbios-grpc-blobs/proto/meson.build
++++ b/src/smbios-grpc-blobs/proto/meson.build
+@@ -1,4 +1,4 @@
+-proto_file = 'SmbiosTransfer.proto'
++proto_file = ['SmbiosTransfer.proto', 'BiosSettings.proto']
+
+ # The remainder of this file can be used as boilerplate
+
+--
+2.47.0.163.g1226f6d8fa-goog
+
diff --git a/recipes-phosphor/smbios/smbios-mdr_%.bbappend b/recipes-phosphor/smbios/smbios-mdr_%.bbappend
index cf876ec..24b0085 100644
--- a/recipes-phosphor/smbios/smbios-mdr_%.bbappend
+++ b/recipes-phosphor/smbios/smbios-mdr_%.bbappend
@@ -13,6 +13,7 @@
file://0001-Add-support-for-google-oem-cpu-link-structure.patch\
file://0001-Use-Device-Locator-as-DIMM-Object-Path-if-Enabled.patch\
file://0001-fix-gRPC-blobs-meson.patch \
+ file://0001-Add-gRPC-BiosSettingsService.patch \
"
EXTRA_OEMESON:append:gbmc = " -Ddimm-only-locator=enabled"