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"