boot-time-blob: exporting boot info to blob
This CL exports boot info via ipmi blob to external.
Including the checkpoint, durations and statistic infomation.
The format is proto 3.
Tested:
\# Read `/btm/host/0` blob from host and ensure the data can be encoded
correctly.
\# Blob raw data (proto 3 format)
```
00000000 12 19 0a 0b 52 65 62 6f 6f 74 53 74 61 72 74 10 |....RebootStart.|
00000010 80 e9 ef b0 ab 31 18 be bc ab 26 12 16 0a 08 53 |.....1....&....S|
00000020 68 75 74 64 6f 77 6e 10 dc 92 bf b7 ab 31 18 c4 |hutdown......1..|
00000030 c4 ab 26 12 1c 0a 0e 4b 65 72 6e 65 6c 53 68 75 |..&....KernelShu|
00000040 74 64 6f 77 6e 10 c5 a2 bf b7 ab 31 18 a8 d4 ab |tdown......1....|
00000050 26 12 10 0a 02 53 35 10 d9 a2 bf b7 ab 31 18 bc |&....S5......1..|
00000060 d4 ab 26 12 10 0a 02 53 30 10 b6 ba bf b7 ab 31 |..&....S0......1|
00000070 18 9c ec ab 26 12 19 0a 0b 54 6f 5f 46 69 72 6d |....&....To_Firm|
00000080 77 61 72 65 10 d2 c2 bf b7 ab 31 18 b6 f4 ab 26 |ware......1....&|
00000090 12 16 0a 08 46 69 72 6d 77 61 72 65 10 f2 e1 bf |....Firmware....|
000000a0 b7 ab 31 18 d6 93 ac 26 12 17 0a 09 54 6f 5f 4c |..1....&....To_L|
000000b0 6f 61 64 65 72 10 c5 f9 bf b7 ab 31 18 ac ab ac |oader......1....|
000000c0 26 12 14 0a 06 4c 6f 61 64 65 72 10 fd 90 c0 b7 |&....Loader.....|
000000d0 ab 31 18 e4 c2 ac 26 12 17 0a 09 54 6f 5f 4b 65 |.1....&....To_Ke|
000000e0 72 6e 65 6c 10 b0 b8 c0 b7 ab 31 18 94 ea ac 26 |rnel......1....&|
000000f0 12 14 0a 06 4b 65 72 6e 65 6c 10 80 c8 c0 b7 ab |....Kernel......|
00000100 31 18 e4 f9 ac 26 12 17 0a 09 54 6f 5f 49 6e 69 |1....&....To_Ini|
00000110 74 72 64 10 f8 fe c0 b7 ab 31 18 da b0 ad 26 12 |trd......1....&.|
00000120 14 0a 06 49 6e 69 74 72 64 10 e0 86 c1 b7 ab 31 |...Initrd......1|
00000130 18 c2 b8 ad 26 12 1a 0a 0c 54 6f 5f 55 73 65 72 |....&....To_User|
00000140 73 70 61 63 65 10 80 e3 f1 b0 ab 31 18 e0 b8 ad |space......1....|
00000150 26 12 17 0a 09 55 73 65 72 73 70 61 63 65 10 c8 |&....Userspace..|
00000160 c8 f2 b0 ab 31 18 a8 9e ae 26 1a 08 0a 04 55 45 |....1....&....UE|
00000170 46 49 10 64 1a 0d 0a 08 4e 45 52 46 44 68 63 70 |FI.d....NERFDhcp|
00000180 10 c8 01 1a 10 0a 0b 4e 45 52 46 4e 65 74 62 6f |.......NERFNetbo|
00000190 6f 74 10 ac 02 1a 11 0a 0c 4e 45 52 46 53 6d 61 |ot.......NERFSma|
000001a0 72 74 4e 49 43 10 90 03 1a 0c 0a 07 55 45 46 49 |rtNIC.......UEFI|
000001b0 53 65 63 10 f4 03 1a 0c 0a 07 55 45 46 49 50 65 |Sec.......UEFIPe|
000001c0 69 10 d8 04 1a 0c 0a 07 55 45 46 49 44 78 65 10 |i.......UEFIDxe.|
000001d0 bc 05 1a 10 0a 0b 42 4d 43 53 68 75 74 64 6f 77 |......BMCShutdow|
000001e0 6e 10 a0 06 1a 0e 0a 09 46 6c 61 73 68 42 49 4f |n.......FlashBIO|
000001f0 53 10 84 07 |S...|
```
Change-Id: Ifb0e381d517c147ca6d086c033b4a5d918dc391b
Signed-off-by: Michael Shen <gpgpgp@google.com>
diff --git a/boot-time-blob/blob_handler.cpp b/boot-time-blob/blob_handler.cpp
new file mode 100644
index 0000000..6fa8d62
--- /dev/null
+++ b/boot-time-blob/blob_handler.cpp
@@ -0,0 +1,265 @@
+#include "blob_handler.hpp"
+
+#include <boot_time.pb.h>
+#include <fmt/printf.h>
+
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <variant>
+#include <vector>
+
+namespace blobs
+{
+
+using CheckpointType = std::vector<std::tuple<std::string, int64_t, int64_t>>;
+using DurationType = std::vector<std::tuple<std::string, int64_t>>;
+
+constexpr static std::string_view kHostBlobPath = "/btm/host/0";
+constexpr static std::string_view kBMCBlobPath = "/btm/bmc";
+
+namespace BootTimeMonitor
+{
+constexpr std::string_view kService = "com.google.gbmc.boot_time_monitor";
+constexpr std::string_view kHostPath = "/xyz/openbmc_project/time/boot/host0";
+constexpr std::string_view kBMCPath = "/xyz/openbmc_project/time/boot/bmc";
+} // namespace BootTimeMonitor
+
+namespace Checkpoint
+{
+constexpr std::string_view kIface = "xyz.openbmc_project.Time.Boot.Checkpoint";
+constexpr std::string_view kMethod = "GetCheckpointList";
+} // namespace Checkpoint
+
+namespace Duration
+{
+constexpr std::string_view kIface = "xyz.openbmc_project.Time.Boot.Duration";
+constexpr std::string_view kMethod = "GetAdditionalDurations";
+} // namespace Duration
+
+namespace Statistic
+{
+constexpr std::string_view kIface = "org.freedesktop.DBus.Properties";
+constexpr std::string_view kMethod = "Get";
+constexpr std::string_view kParam1 = "xyz.openbmc_project.Time.Boot.Statistic";
+constexpr std::string_view kParam2 = "IsRebooting";
+} // namespace Statistic
+
+bool BlobHandler::canHandleBlob(const std::string& path)
+{
+ return (path == kHostBlobPath) || (path == kBMCBlobPath);
+}
+
+// A blob handler may have multiple Blobs.
+std::vector<std::string> BlobHandler::getBlobIds()
+{
+ return {kHostBlobPath.data(), kBMCBlobPath.data()};
+}
+
+// BmcBlobDelete (7) is not supported.
+bool BlobHandler::deleteBlob([[maybe_unused]] const std::string& path)
+{
+ return false;
+}
+
+// BmcBlobStat (8) (global stat) is not supported.
+bool BlobHandler::stat([[maybe_unused]] const std::string& path,
+ [[maybe_unused]] BlobMeta* meta)
+{
+ return false;
+}
+
+// BmcBlobOpen(2) handler.
+bool BlobHandler::open(uint16_t session, uint16_t flags,
+ const std::string& path)
+{
+ if (!isReadOnlyOpenFlags(flags))
+ {
+ fmt::print(stderr, "[{}] Flag is not read-only. flag={}\n",
+ __FUNCTION__, flags);
+ return false;
+ }
+ if (!canHandleBlob(path))
+ {
+ fmt::print(stderr, "[{}] Can't handle path={}\n", __FUNCTION__, path);
+ return false;
+ }
+
+ sdbusplus::bus_t bus = sdbusplus::bus::new_default();
+ boottimeproto::BootTime btProto;
+ std::string btmPath;
+ if (path == kBMCBlobPath)
+ {
+ btmPath = BootTimeMonitor::kBMCPath;
+ }
+ else
+ {
+ btmPath = BootTimeMonitor::kHostPath;
+ }
+
+ // Get checkpoints
+ try
+ {
+ auto method = bus.new_method_call(
+ BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Checkpoint::kIface.data(), Checkpoint::kMethod.data());
+ CheckpointType checkpoints;
+ bus.call(method).read(checkpoints);
+ for (auto checkpoint : checkpoints)
+ {
+ auto cpProto = btProto.add_checkpoints();
+ cpProto->set_name(std::get<0>(checkpoint));
+ cpProto->set_wall_time(std::get<1>(checkpoint));
+ cpProto->set_mono_time(std::get<2>(checkpoint));
+ }
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ fmt::print(
+ stderr,
+ "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, error={}\n",
+ __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Checkpoint::kIface.data(), Checkpoint::kMethod.data(), e.what());
+ return false;
+ }
+
+ // Get durations
+ try
+ {
+ auto method = bus.new_method_call(
+ BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Duration::kIface.data(), Duration::kMethod.data());
+ DurationType durations;
+ bus.call(method).read(durations);
+ for (auto duration : durations)
+ {
+ auto durProto = btProto.add_durations();
+ durProto->set_name(std::get<0>(duration));
+ durProto->set_duration(std::get<1>(duration));
+ }
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ fmt::print(
+ stderr,
+ "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, error={}\n",
+ __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Duration::kIface.data(), Duration::kMethod.data(), e.what());
+ return false;
+ }
+
+ // Get `IsRebooting`
+ try
+ {
+ auto method = bus.new_method_call(
+ BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Statistic::kIface.data(), Statistic::kMethod.data());
+ method.append(Statistic::kParam1.data(), Statistic::kParam2.data());
+
+ std::variant<bool> isRebooting;
+ bus.call(method).read(isRebooting);
+ btProto.set_is_rebooting(std::get<bool>(isRebooting));
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ fmt::print(
+ stderr,
+ "[{}] Failed in method call. service={}, path={}, Iface={}, method={}, param1={}, param2={}, error={}\n",
+ __FUNCTION__, BootTimeMonitor::kService.data(), btmPath.c_str(),
+ Statistic::kIface.data(), Statistic::kMethod.data(),
+ Statistic::kParam1.data(), Statistic::kParam2.data(), e.what());
+ return false;
+ }
+
+ std::vector<char> vec(btProto.ByteSizeLong());
+ if (!btProto.SerializeToArray(vec.data(), vec.size()))
+ {
+ fmt::print(stderr, "[{}]: Could not serialize protobuf to array\n",
+ __FUNCTION__);
+ return false;
+ }
+
+ sessions[session] = std::move(vec);
+ return true;
+}
+
+// BmcBlobRead(3) handler.
+std::vector<uint8_t> BlobHandler::read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize)
+{
+ auto it = sessions.find(session);
+ if (it == sessions.end())
+ {
+ return {};
+ }
+
+ if (offset + requestedSize > it->second.size())
+ {
+ return std::vector<uint8_t>(it->second.data() + offset,
+ it->second.data() + it->second.size());
+ }
+ return std::vector<uint8_t>(it->second.data() + offset,
+ it->second.data() + offset + requestedSize);
+}
+
+// BmcBlobWrite(4) is not supported.
+bool BlobHandler::write([[maybe_unused]] uint16_t session,
+ [[maybe_unused]] uint32_t offset,
+ [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+ return false;
+}
+
+// BmcBlobWriteMeta(10) is not supported.
+bool BlobHandler::writeMeta([[maybe_unused]] uint16_t session,
+ [[maybe_unused]] uint32_t offset,
+ [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+ return false;
+}
+
+// BmcBlobCommit(5) is not supported.
+bool BlobHandler::commit([[maybe_unused]] uint16_t session,
+ [[maybe_unused]] const std::vector<uint8_t>& data)
+{
+ return false;
+}
+
+// BmcBlobClose(6) handler.
+bool BlobHandler::close(uint16_t session)
+{
+ auto itr = sessions.find(session);
+ if (itr == sessions.end())
+ {
+ return false;
+ }
+ sessions.erase(itr);
+ return true;
+}
+
+// BmcBlobSessionStat(9) is not supported.
+bool BlobHandler::stat([[maybe_unused]] uint16_t session,
+ [[maybe_unused]] BlobMeta* meta)
+{
+ return false;
+}
+
+bool BlobHandler::expire(uint16_t session)
+{
+ return close(session);
+}
+
+// Checks for a read-only flag.
+bool BlobHandler::isReadOnlyOpenFlags(const uint16_t flags)
+{
+ if (((flags & blobs::OpenFlags::read) == blobs::OpenFlags::read) &&
+ ((flags & blobs::OpenFlags::write) == 0))
+ {
+ return true;
+ }
+ return false;
+}
+
+} // namespace blobs
diff --git a/boot-time-blob/blob_handler.hpp b/boot-time-blob/blob_handler.hpp
new file mode 100644
index 0000000..bce4bb2
--- /dev/null
+++ b/boot-time-blob/blob_handler.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace blobs
+{
+
+// This blob is a read-only blob can only handle single blob id.
+class BlobHandler : public GenericBlobInterface
+{
+ public:
+ BlobHandler() = default;
+
+ bool canHandleBlob(const std::string& path) override;
+ std::vector<std::string> getBlobIds() override;
+ bool deleteBlob(const std::string& path) override;
+ bool stat(const std::string& path, BlobMeta* meta) override;
+ bool open(uint16_t session, uint16_t flags,
+ const std::string& path) override;
+ std::vector<uint8_t> read(uint16_t session, uint32_t offset,
+ uint32_t requestedSize) override;
+ bool write(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool writeMeta(uint16_t session, uint32_t offset,
+ const std::vector<uint8_t>& data) override;
+ bool commit(uint16_t session, const std::vector<uint8_t>& data) override;
+ bool close(uint16_t session) override;
+ bool stat(uint16_t session, BlobMeta* meta) override;
+ bool expire(uint16_t session) override;
+
+ private:
+ bool isReadOnlyOpenFlags(const uint16_t flag);
+ std::unordered_map<uint16_t, std::vector<char>> sessions;
+};
+
+} // namespace blobs
diff --git a/boot-time-blob/blob_main.cpp b/boot-time-blob/blob_main.cpp
new file mode 100644
index 0000000..ba3a0c0
--- /dev/null
+++ b/boot-time-blob/blob_main.cpp
@@ -0,0 +1,21 @@
+#include "blob_handler.hpp"
+
+#include <blobs-ipmid/blobs.hpp>
+
+#include <memory>
+
+// Extern "C" is used due to the usage of dlopen() for loading IPMI handlers
+// and IPMI blob handlers. This happens in two steps:
+//
+// 1) ipmid loads all libraries in /usr/lib/ipmid-providers from its
+// loadLibraries() function using dlopen().
+// 2) The blobs library, libblobcmds.so, loads all libraries in
+// /usr/lib/blobs-ipmid from its loadLibraries() function. It uses dlsym()
+// to locate the createHandler function by its un-mangled name
+// "createHandler". Using extern "C" prevents its name from being mangled
+// into, for example, "_Z13createHandlerv".
+
+extern "C" std::unique_ptr<blobs::GenericBlobInterface> createHandler()
+{
+ return std::make_unique<blobs::BlobHandler>();
+}
diff --git a/boot-time-blob/boot_time.proto b/boot-time-blob/boot_time.proto
new file mode 100644
index 0000000..e6f0576
--- /dev/null
+++ b/boot-time-blob/boot_time.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+package boottimeproto;
+
+message Checkpoint {
+ string name = 1;
+ int64 wall_time = 2;
+ int64 mono_time = 3;
+}
+
+message Duration {
+ string name = 1;
+ int64 duration = 2;
+}
+
+message BootTime {
+ string name = 1;
+ repeated Checkpoint checkpoints = 2;
+ repeated Duration durations = 3;
+ bool is_rebooting = 4;
+}
diff --git a/boot-time-blob/meson.build b/boot-time-blob/meson.build
new file mode 100644
index 0000000..69b326b
--- /dev/null
+++ b/boot-time-blob/meson.build
@@ -0,0 +1,38 @@
+ipmi_blob_dep = dependency('phosphor-ipmi-blobs', required : false)
+if not ipmi_blob_dep.found()
+ ipmi_blob_proj = subproject('phosphor-ipmi-blobs')
+ ipmi_blob_dep = ipmi_blob_proj.get_variable('ipmi_blob_dep')
+endif
+protobuf_dep = dependency('protobuf')
+
+proto = custom_target(
+ 'boot_time_proto',
+ command: [
+ find_program('protoc', native: true),
+ '--proto_path=@CURRENT_SOURCE_DIR@',
+ '--cpp_out=@OUTDIR@',
+ '@INPUT@'
+ ],
+ output: [
+ 'boot_time.pb.cc',
+ 'boot_time.pb.h',
+ ],
+ input: 'boot_time.proto'
+)
+
+shared_module(
+ 'boottimeblob',
+ 'blob_main.cpp',
+ 'blob_handler.cpp',
+ proto,
+ dependencies: [
+ ipmi_blob_dep,
+ protobuf_dep,
+ boost_dep,
+ sdbusplus_dep,
+ fmt_dep,
+ ],
+ include_directories : ['.'],
+ install: true,
+ install_dir: get_option('libdir') / 'blob-ipmid'
+)
diff --git a/meson.build b/meson.build
index 09b79d7..be59a53 100644
--- a/meson.build
+++ b/meson.build
@@ -41,6 +41,7 @@
endif
subdir('src')
+subdir('boot-time-blob')
subdir('service_files')
if not get_option('tests').disabled()
diff --git a/subprojects/phosphor-ipmi-blobs.wrap b/subprojects/phosphor-ipmi-blobs.wrap
new file mode 100644
index 0000000..e4ab65a
--- /dev/null
+++ b/subprojects/phosphor-ipmi-blobs.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-ipmi-blobs.git
+revision = HEAD