bm_instance: Create a new handler
This OEM handler will get properties from tmpfs to serve over IPMI.
Tested:
~# cat /run/bm-instance/asset-tag
12345
~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x17 0x00
79 2b 00 17 05 31 32 33 34 35
...
...
(Verified this works for all 7 sub commands)
Signed-off-by: Brandon Kim <brandonkim@google.com>
Change-Id: I191b6f4994d91ada49a3332a8e93a3a305561904
diff --git a/README.md b/README.md
index 39c8866..71a213b 100644
--- a/README.md
+++ b/README.md
@@ -509,3 +509,25 @@
| Byte(s) | Value | Data |
| ------- | ----- | ---------- |
| 0x00 | 0x16 | Subcommand |
+
+### GetBMInstanceProperty - SubCommand 0x17
+
+Read a BM instance property, specify the property to read following the enum.
+
+The response contains the length of the property string to read in bytes
+followed by the property string which does **NOT** have a NULL terminator.
+
+Request
+
+| Byte(s) | Value | Data |
+| ------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| 0x00 | 0x17 | Subcommand |
+| 0x01 | | 0x0: AssetTag <br>0x1: BoardSerialNumber <br>0x2: Family <br>0x3: ProductName <br>0x4: SKU <br>0x5: SystemSerialNumber <br>0x6: UUID |
+
+Response
+
+| Byte(s) | Value | Data |
+| ----------------- | ------------------ | ---------------------------------------------------------------- |
+| 0x00 | 0x17 | Subcommand |
+| 0x01 | String Length (N) | Number of bytes to read for property string - Size limited to 64 |
+| 0x2..0x02 + N - 1 | String of property | String, not null-terminated |
diff --git a/bm_instance.cpp b/bm_instance.cpp
new file mode 100644
index 0000000..d3fbd8e
--- /dev/null
+++ b/bm_instance.cpp
@@ -0,0 +1,76 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "bm_instance.hpp"
+
+#include "commands.hpp"
+#include "errors.hpp"
+#include "handler.hpp"
+
+#include <ipmid/api-types.hpp>
+#include <stdplus/print.hpp>
+
+#include <span>
+#include <vector>
+
+namespace google
+{
+namespace ipmi
+{
+
+namespace
+{
+#ifndef MAX_IPMI_BUFFER
+#define MAX_IPMI_BUFFER 64
+#endif
+} // namespace
+
+struct BMInstancePropertyRequest
+{
+ std::uint8_t bmInstancePropertyType;
+} __attribute__((packed));
+
+Resp getBMInstanceProperty(std::span<const uint8_t> data,
+ HandlerInterface* handler)
+{
+ if (data.size() < sizeof(struct BMInstancePropertyRequest))
+ {
+ stdplus::print(stderr, "Invalid command length: {}\n",
+ static_cast<uint32_t>(data.size()));
+ return ::ipmi::responseReqDataLenInvalid();
+ }
+
+ std::string bmInstanceProperty =
+ handler->getBMInstanceProperty(/*type=*/data[0]);
+
+ const size_t length = sizeof(struct BMInstancePropertyReply) +
+ bmInstanceProperty.size();
+
+ if (length > MAX_IPMI_BUFFER)
+ {
+ stdplus::print(stderr, "Response would overflow response buffer\n");
+ return ::ipmi::responseInvalidCommand();
+ }
+
+ std::vector<std::uint8_t> reply;
+ reply.reserve(length);
+ reply.emplace_back(bmInstanceProperty.size());
+ reply.insert(reply.end(), bmInstanceProperty.begin(),
+ bmInstanceProperty.end());
+
+ return ::ipmi::responseSuccess(SysOEMCommands::SysGetBMInstanceProperty,
+ reply);
+}
+} // namespace ipmi
+} // namespace google
diff --git a/bm_instance.hpp b/bm_instance.hpp
new file mode 100644
index 0000000..9852a78
--- /dev/null
+++ b/bm_instance.hpp
@@ -0,0 +1,35 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "handler.hpp"
+
+#include <span>
+
+namespace google
+{
+namespace ipmi
+{
+
+struct BMInstancePropertyReply
+{
+ uint8_t bmInstancePropertyLength;
+} __attribute__((packed));
+
+Resp getBMInstanceProperty(std::span<const uint8_t> data,
+ HandlerInterface* handler);
+
+} // namespace ipmi
+} // namespace google
diff --git a/commands.hpp b/commands.hpp
index cc22a23..0dc80e6 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -67,6 +67,8 @@
SysGetAccelVrSettings = 21,
// Google CustomAccel Set VR Settings
SysSetAccelVrSettings = 22,
+ // Get BM instance property info
+ SysGetBMInstanceProperty = 23,
};
} // namespace ipmi
diff --git a/handler.cpp b/handler.cpp
index d4a8897..bc6323d 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -16,6 +16,7 @@
#include "bm_config.h"
+#include "bm_instance.hpp"
#include "bmc_mode_enum.hpp"
#include "errors.hpp"
#include "handler_impl.hpp"
@@ -39,6 +40,7 @@
#include <cstdio>
#include <filesystem>
#include <fstream>
+#include <iomanip>
#include <map>
#include <sstream>
#include <string>
@@ -767,5 +769,48 @@
return static_cast<uint16_t>(std::get<double>(value));
}
+
+std::string Handler::getBMInstanceProperty(uint8_t propertyType) const
+{
+ std::string propertyTypeString;
+ if (auto it = bmInstanceTypeStringMap.find(propertyType);
+ it == bmInstanceTypeStringMap.end())
+ {
+ stdplus::print(stderr, "PropertyType: '{}' is invalid.\n",
+ propertyType);
+ throw IpmiException(::ipmi::ccInvalidFieldRequest);
+ }
+ else
+ {
+ propertyTypeString = it->second;
+ }
+ std::string opath = std::format("/run/bm-instance/{}", propertyTypeString);
+ // Check for file
+
+ std::error_code ec;
+ // TODO(brandonkim@google.com): Fix this to use stdplus::ManagedFd
+ if (!this->getFs()->exists(opath, ec))
+ {
+ stdplus::print(stderr, "Path: '{}' doesn't exist.\n", opath);
+ throw IpmiException(::ipmi::ccInvalidFieldRequest);
+ }
+
+ // If file exists, read up to 64 bytes (normally shouldn't be more than 32)
+ std::ifstream ifs;
+ ifs.exceptions(std::ifstream::failbit);
+ std::string property;
+ try
+ {
+ ifs.open(opath);
+ ifs >> std::setw(64) >> property;
+ }
+ catch (std::ios_base::failure& fail)
+ {
+ stdplus::print(stderr, "Failed to read: '{}'.\n", opath);
+ throw IpmiException(::ipmi::ccUnspecifiedError);
+ }
+ return property;
+}
+
} // namespace ipmi
} // namespace google
diff --git a/handler.hpp b/handler.hpp
index b06d986..50bfb02 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -233,6 +233,14 @@
virtual uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx,
uint8_t chip_id,
uint8_t settings_id) const = 0;
+
+ /**
+ * Get the BM instance property from /run/<propertyType>
+ *
+ * @param[in] propertyType - BM instance property type
+ * @return - string of the requested BM instance property
+ */
+ virtual std::string getBMInstanceProperty(uint8_t propertyType) const = 0;
};
} // namespace ipmi
diff --git a/handler_impl.hpp b/handler_impl.hpp
index b1f48bc..dc00051 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -78,6 +78,7 @@
uint8_t settings_id, uint16_t value) const override;
uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
uint8_t settings_id) const override;
+ std::string getBMInstanceProperty(uint8_t propertyType) const override;
protected:
// Exposed for dependency injection
@@ -109,6 +110,12 @@
const std::unordered_map<uint8_t, std::string> _vrSettingsMap{
{0, "idle_mode_"}, {1, "power_brake_"}, {2, "loadline_"}};
+ const std::unordered_map<uint8_t, std::string> bmInstanceTypeStringMap = {
+ {0x00, "asset-tag"}, {0x01, "board-serial-number"},
+ {0x02, "family"}, {0x03, "product-name"},
+ {0x04, "sku"}, {0x05, "system-serial-number"},
+ {0x06, "uuid"}};
+
nlohmann::json _entityConfig{};
std::vector<std::tuple<uint32_t, std::string>> _pcie_i2c_map;
diff --git a/ipmi.cpp b/ipmi.cpp
index d8d94e9..5c0b916 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -14,6 +14,7 @@
#include "ipmi.hpp"
+#include "bm_instance.hpp"
#include "bmc_mode.hpp"
#include "cable.hpp"
#include "commands.hpp"
@@ -91,6 +92,8 @@
return accelGetVrSettings(ctx, data, handler);
case SysSetAccelVrSettings:
return accelSetVrSettings(ctx, data, handler);
+ case SysGetBMInstanceProperty:
+ return getBMInstanceProperty(data, handler);
default:
stdplus::print(stderr, "Invalid subcommand: {:#x}\n", cmd);
return ::ipmi::responseInvalidCommand();
diff --git a/meson.build b/meson.build
index fcbfedd..b101757 100644
--- a/meson.build
+++ b/meson.build
@@ -44,6 +44,7 @@
sys_lib = static_library(
'sys',
+ 'bm_instance.cpp',
'bmc_mode.cpp',
'cable.cpp',
'cpld.cpp',
diff --git a/test/bm_instance_unittest.cpp b/test/bm_instance_unittest.cpp
new file mode 100644
index 0000000..2c603c4
--- /dev/null
+++ b/test/bm_instance_unittest.cpp
@@ -0,0 +1,76 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "bm_instance.hpp"
+#include "commands.hpp"
+#include "handler_mock.hpp"
+#include "helper.hpp"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+using testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+TEST(GetBMInstancePropertyTest, InvalidRequestSize)
+{
+ std::vector<uint8_t> request = {};
+
+ HandlerMock hMock;
+ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
+ getBMInstanceProperty(request, &hMock));
+}
+
+TEST(GetBMInstancePropertyTest, InvalidCommand)
+{
+ std::vector<uint8_t> request = {1};
+ std::string expectedOutput(64, 'a');
+
+ HandlerMock hMock;
+ EXPECT_CALL(hMock, getBMInstanceProperty(1))
+ .WillOnce(Return(expectedOutput));
+ EXPECT_EQ(::ipmi::responseInvalidCommand(),
+ getBMInstanceProperty(request, &hMock));
+}
+
+TEST(GetBMInstancePropertyTest, ValidRequest)
+{
+ std::vector<uint8_t> request = {2};
+ std::string expectedOutput = "asdf";
+
+ HandlerMock hMock;
+ EXPECT_CALL(hMock, getBMInstanceProperty(2))
+ .WillOnce(Return(expectedOutput));
+
+ auto reply = getBMInstanceProperty(request, &hMock);
+ auto result = ValidateReply(reply);
+ auto& data = result.second;
+
+ EXPECT_EQ(sizeof(struct BMInstancePropertyReply) + expectedOutput.size(),
+ data.size());
+ EXPECT_EQ(SysOEMCommands::SysGetBMInstanceProperty, result.first);
+ EXPECT_THAT(std::string(data.begin() + 1, data.end()),
+ StrEq(expectedOutput));
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index 579dfc6..b08cd5e 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -71,6 +71,8 @@
(const, override));
MOCK_METHOD(uint16_t, accelGetVrSettings,
(::ipmi::Context::ptr, uint8_t, uint8_t), (const, override));
+ MOCK_METHOD(std::string, getBMInstanceProperty, (uint8_t),
+ (const, override));
};
} // namespace ipmi
diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
index 549f1d0..2fb470e 100644
--- a/test/handler_unittest.cpp
+++ b/test/handler_unittest.cpp
@@ -642,6 +642,18 @@
}
}
+TEST(HandlerTest, BmInstanceFailCase)
+{
+ StrictMock<sdbusplus::SdBusMock> mock;
+ MockDbusHandler h(mock);
+
+ // Invalid enum
+ EXPECT_THROW(h.getBMInstanceProperty(0x07), IpmiException);
+
+ // Valid enum but no path exists
+ EXPECT_THROW(h.getBMInstanceProperty(0x00), IpmiException);
+}
+
// TODO: Add checks for other functions of handler.
} // namespace ipmi
diff --git a/test/meson.build b/test/meson.build
index c3387dd..f7e3bb9 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -31,6 +31,7 @@
'bmc_mode',
'linux_boot_done',
'bm_mode_transition',
+ 'bm_instance',
]
foreach t : tests