google-ipmi-sys: adding new commands
This patch is already in multiple vendor layer.
Hence moving this to shared layer.
Add new commands `sendRebootCheckpoint`, `sendRebootComplete` and
`sendRebootAdditionalDuration`.
These commands are for the `BootTimeMonitor` feature in
https://gbmc.googlesource.com/BootTimeMonitor/
Basically that daemon is for monitoring the reboot process for BMC and
hosts. It will be useful for profiling the reboot process and knowing
how to optimize it.
Tested: Not required, just moved a patch and not using in this layer.
Fusion-Link:
https://fusion2.corp.google.com/d8a61873-afaf-3f5e-9794-b19fd03f40b9
https://fusion2.corp.google.com/743dda24-c942-35f2-8d19-9803a71323d5
Google-Bug-Id: 352691500
Change-Id: Ie89e61044579a1c2c6f9f04af50a02315f42f5ce
Signed-off-by: Munawar Hussain <munawarhussain@google.com>
(cherry picked from commit 9d39811f7829d3e184b8c1c8ce71cdcb2a4e374e)
diff --git a/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch b/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch
new file mode 100644
index 0000000..99231a9
--- /dev/null
+++ b/recipes-google/ipmi/google-ipmi-sys/0002-boot-time-monitor-Add-commands.patch
@@ -0,0 +1,944 @@
+From 9a87ff5d07318ce7733ef7f6eb157eb491c4ad04 Mon Sep 17 00:00:00 2001
+From: Michael Shen <gpgpgp@google.com>
+Date: Mon, 29 Apr 2024 03:30:08 +0000
+Subject: [PATCH 2/2] boot-time-monitor: Add commands
+
+Add new commands `sendRebootCheckpoint`, `sendRebootComplete` and
+`sendRebootAdditionalDuration`.
+
+These commands are for the `BootTimeMonitor` feature in
+https://gbmc.googlesource.com/BootTimeMonitor/
+
+Basically that daemon is for monitoring the reboot process for BMC and
+hosts. It will be useful for profiling the reboot process and knowing
+how to optimize it.
+
+Tested:
+```
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x12 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x41 0x42 0x43
+ 79 2b 00 12
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x14 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x41 0x42 0x43
+ 79 2b 00 14
+bmc:~# ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x13
+ 79 2b 00 13
+```
+
+Patch Tracking Bug: b/310528119
+Upstream info / review: go/obmcl/66345
+Upstream-Status: Submitted
+Justification: [enable feature] Adding BootTimeMonitor needed IPMI
+commands.
+
+Signed-off-by: Michael Shen <gpgpgp@google.com>
+Change-Id: Ie740de503d110007820c0da77a6f1522fdf66a9f
+---
+ README.md | 43 ++++++
+ boot_time_monitor.cpp | 135 ++++++++++++++++++
+ boot_time_monitor.hpp | 50 +++++++
+ handler.cpp | 75 ++++++++++
+ handler.hpp | 29 ++++
+ handler_impl.hpp | 5 +
+ ipmi.cpp | 7 +
+ meson.build | 1 +
+ test/boot_time_monitor_unittest.cpp | 211 ++++++++++++++++++++++++++++
+ test/handler_mock.hpp | 6 +
+ test/handler_unittest.cpp | 196 +++++++++++++++++++++++++-
+ test/meson.build | 1 +
+ 12 files changed, 757 insertions(+), 2 deletions(-)
+ create mode 100644 boot_time_monitor.cpp
+ create mode 100644 boot_time_monitor.hpp
+ create mode 100644 test/boot_time_monitor_unittest.cpp
+
+diff --git a/README.md b/README.md
+index 71a213b..9e8097d 100644
+--- a/README.md
++++ b/README.md
+@@ -457,6 +457,49 @@ Response (if applicable)
+ | ------- | ----- | ---------- |
+ | 0x00 | 0x11 | Subcommand |
+
++### SendRebootCheckpoint - SubCommand 0x12
++
++Send checkpoints to BMC to monitor the current reboot process.
++User can put current wall lock time in the command or put `0` to let BMC fetch
++the wall clock time by itself.
++If user can somehow have the self measurement of a duration when they send
++checkpoint to BMC, BMC will help to calculate the transition time from previous
++checkpoint to current checkpoint. Then add a checkpoint with the name
++`To_${checkpoint}`. Or specify `0` to ignore it.
++
++Request
++
++| Byte(s) | Value | Data |
++| -------------- | --------------- | -------------------------------------- |
++| 0x00 | 0x12 | Subcommand |
++| 0x01..0x08 | Wall Time | Current epoch time in milliseconds |
++| 0x09..0x10 | Duration | Self measured duration in milliseconds |
++| 0x11 | Length (N) | Length of checkpoint name |
++| 0x12..0x12 + N | Checkpoint name | Checkpoint name ([0-9a-ZA-Z_] only) |
++
++### SendRebootComplete - SubCommand 0x13
++
++Informing BMC that the current reboot process is completed.
++
++Request
++
++| Byte(s) | Value | Data |
++| ------- | ----- | ---------- |
++| 0x00 | 0x13 | Subcommand |
++
++### SendRebootAdditionalDuration - SubCommand 0x14
++
++This command allows users to store any duration that they are interested in BMC.
++
++Request
++
++| Byte(s) | Value | Data |
++| -------------- | ------------- | -------------------------------------- |
++| 0x00 | 0x14 | Subcommand |
++| 0x01..0x08 | Duration | Self measured duration in milliseconds |
++| 0x09 | Length (N) | Length of duration name |
++| 0x0A..0x0A + N | Duration name | Duration name ([0-9a-ZA-Z_] only) |
++
+ ### SysGetAccelVrSettings - SubCommand 0x15
+
+ Get the accel's VR setting value for the given chip and settings ID
+diff --git a/boot_time_monitor.cpp b/boot_time_monitor.cpp
+new file mode 100644
+index 0000000..8c18162
+--- /dev/null
++++ b/boot_time_monitor.cpp
+@@ -0,0 +1,135 @@
++/*
++ * Copyright 2022 Google Inc.
++ *
++ * 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 "boot_time_monitor.hpp"
++
++#include "commands.hpp"
++#include "errors.hpp"
++#include "handler.hpp"
++
++#include <cstdint>
++#include <cstring>
++#include <ipmid/api-types.hpp>
++#include <optional>
++#include <regex>
++#include <span>
++#include <string>
++#include <vector>
++
++namespace google
++{
++namespace ipmi
++{
++Resp sendRebootCheckpoint(std::span<const uint8_t> data,
++ const HandlerInterface* handler)
++{
++ constexpr auto kNameOffset = sizeof(struct CheckpointReqHeader);
++ if (data.size() < kNameOffset ||
++ data.size() < kNameOffset + data[offsetof(CheckpointReqHeader, length)])
++ {
++ std::fprintf(stderr, "Invalid command length: %u\n",
++ static_cast<unsigned int>(data.size()));
++ return ::ipmi::responseReqDataLenInvalid();
++ }
++ const auto kNameLen = data[offsetof(CheckpointReqHeader, length)];
++
++ const std::string name{data.data() + kNameOffset,
++ data.data() + kNameOffset + kNameLen};
++ std::fprintf(stderr, "name = %s", name.c_str());
++ if (std::regex_search(name, std::regex("[^0-9a-zA-Z_]")))
++ {
++ std::fprintf(stderr,
++ "Invalid checkpoint name, only [0-9a-zA-Z_] are allowed");
++ return ::ipmi::responseParmOutOfRange();
++ }
++
++ int64_t wallTime = 0, duration = 0;
++ memcpy(&wallTime, data.data() + offsetof(CheckpointReqHeader, wallTime),
++ sizeof(wallTime));
++ memcpy(&duration, data.data() + offsetof(CheckpointReqHeader, duration),
++ sizeof(duration));
++
++ try
++ {
++ handler->sendRebootCheckpoint(name, wallTime, duration);
++ }
++ catch (const IpmiException& e)
++ {
++ return ::ipmi::response(e.getIpmiError());
++ }
++
++ return ::ipmi::responseSuccess(SysOEMCommands::SysSendRebootCheckpoint,
++ std::vector<uint8_t>{});
++}
++
++Resp sendRebootComplete([[maybe_unused]] std::span<const uint8_t> data,
++ const HandlerInterface* handler)
++{
++ try
++ {
++ handler->sendRebootComplete();
++ }
++ catch (const IpmiException& e)
++ {
++ return ::ipmi::response(e.getIpmiError());
++ }
++
++ return ::ipmi::responseSuccess(SysOEMCommands::SysSendRebootComplete,
++ std::vector<uint8_t>{});
++}
++
++Resp sendRebootAdditionalDuration(std::span<const uint8_t> data,
++ const HandlerInterface* handler)
++{
++ constexpr auto kNameOffset = sizeof(struct DurationReqHeader);
++ if (data.size() < kNameOffset ||
++ data.size() < kNameOffset + data[offsetof(DurationReqHeader, length)])
++ {
++ std::fprintf(stderr, "Invalid command length: %u\n",
++ static_cast<unsigned int>(data.size()));
++ return ::ipmi::responseReqDataLenInvalid();
++ }
++ const auto kNameLen = data[offsetof(DurationReqHeader, length)];
++
++ const std::string name{data.data() + kNameOffset,
++ data.data() + kNameOffset + kNameLen};
++ if (std::regex_search(name, std::regex("[^0-9a-zA-Z_]")))
++ {
++ std::fprintf(stderr,
++ "Invalid duration name, only [0-9a-zA-Z_] are allowed");
++ return ::ipmi::responseParmOutOfRange();
++ }
++
++ int64_t duration = 0;
++ memcpy(&duration, data.data() + offsetof(DurationReqHeader, duration),
++ sizeof(duration));
++
++ try
++ {
++ handler->sendRebootAdditionalDuration(name, duration);
++ }
++ catch (const IpmiException& e)
++ {
++ return ::ipmi::response(e.getIpmiError());
++ }
++
++ return ::ipmi::responseSuccess(
++ SysOEMCommands::SysSendRebootAdditionalDuration,
++ std::vector<uint8_t>{});
++}
++
++} // namespace ipmi
++} // namespace google
+diff --git a/boot_time_monitor.hpp b/boot_time_monitor.hpp
+new file mode 100644
+index 0000000..0bea4bd
+--- /dev/null
++++ b/boot_time_monitor.hpp
+@@ -0,0 +1,50 @@
++// Copyright 2021 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 <ipmid/api.h>
++
++#include <ipmid/api-types.hpp>
++#include <span>
++
++namespace google
++{
++namespace ipmi
++{
++
++struct CheckpointReqHeader
++{
++ int64_t wallTime;
++ int64_t duration;
++ uint8_t length;
++} __attribute__((packed));
++
++struct DurationReqHeader
++{
++ int64_t duration;
++ uint8_t length;
++} __attribute__((packed));
++
++Resp sendRebootCheckpoint(std::span<const uint8_t> data,
++ const HandlerInterface* handler);
++Resp sendRebootComplete(std::span<const uint8_t> data,
++ const HandlerInterface* handler);
++Resp sendRebootAdditionalDuration(std::span<const uint8_t> data,
++ const HandlerInterface* handler);
++
++} // namespace ipmi
++} // namespace google
+diff --git a/handler.cpp b/handler.cpp
+index 2bcb634..7b051f3 100644
+--- a/handler.cpp
++++ b/handler.cpp
+@@ -753,6 +753,81 @@ void Handler::linuxBootDone() const
+ }
+ }
+
++constexpr std::string_view BTMONITOR_SERVICE =
++ "com.google.gbmc.boot_time_monitor";
++constexpr std::string_view BTMONITOR_OBJECT =
++ "/xyz/openbmc_project/time/boot/host0";
++constexpr std::string_view BTMONITOR_CHECKPOINT_INTERFACE =
++ "xyz.openbmc_project.Time.Boot.Checkpoint";
++constexpr std::string_view BTMONITOR_SETCHECKPOINT = "SetCheckpoint";
++constexpr std::string_view BTMONITOR_COMPLETE = "RebootComplete";
++constexpr std::string_view BTMONITOR_DURATION_INTERFACE =
++ "xyz.openbmc_project.Time.Boot.Duration";
++constexpr std::string_view BTMONITOR_SETDURATION = "SetDuration";
++
++void Handler::sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++ int64_t duration) const
++{
++ try
++ {
++ auto bus = getDbus();
++ auto method = bus.new_method_call(BTMONITOR_SERVICE.data(),
++ BTMONITOR_OBJECT.data(),
++ BTMONITOR_CHECKPOINT_INTERFACE.data(),
++ BTMONITOR_SETCHECKPOINT.data());
++ method.append(name.data());
++ method.append(wallTime);
++ method.append(duration);
++ bus.call_noreply(method);
++ }
++ catch (const sdbusplus::exception::SdBusError& ex)
++ {
++ std::fprintf(stderr, "Failed to call btmanager `SetCheckpoint`: %s",
++ ex.what());
++ throw IpmiException(::ipmi::ccUnspecifiedError);
++ }
++}
++
++void Handler::sendRebootComplete() const
++{
++ try
++ {
++ auto bus = getDbus();
++ auto method = bus.new_method_call(
++ BTMONITOR_SERVICE.data(), BTMONITOR_OBJECT.data(),
++ BTMONITOR_CHECKPOINT_INTERFACE.data(), BTMONITOR_COMPLETE.data());
++
++ bus.call_noreply(method);
++ }
++ catch (const sdbusplus::exception::SdBusError& ex)
++ {
++ std::fprintf(stderr, "Failed to call btmanager `Complete`: %s",
++ ex.what());
++ throw IpmiException(::ipmi::ccUnspecifiedError);
++ }
++}
++
++void Handler::sendRebootAdditionalDuration(std::string_view name,
++ int64_t duration) const
++{
++ try
++ {
++ auto bus = getDbus();
++ auto method = bus.new_method_call(
++ BTMONITOR_SERVICE.data(), BTMONITOR_OBJECT.data(),
++ BTMONITOR_DURATION_INTERFACE.data(), BTMONITOR_SETDURATION.data());
++ method.append(name.data());
++ method.append(duration);
++ bus.call_noreply(method);
++ }
++ catch (const sdbusplus::exception::SdBusError& ex)
++ {
++ std::fprintf(stderr, "Failed to call btmanager `SetDuration`: %s",
++ ex.what());
++ throw IpmiException(::ipmi::ccUnspecifiedError);
++ }
++}
++
+ static constexpr char ACCEL_POWER_SERVICE[] = "xyz.openbmc_project.AccelPower";
+ static constexpr char ACCEL_POWER_PATH_PREFIX[] =
+ "/xyz/openbmc_project/control/accel_power_";
+diff --git a/handler.hpp b/handler.hpp
+index d805b56..5b3328e 100644
+--- a/handler.hpp
++++ b/handler.hpp
+@@ -216,6 +216,35 @@ class HandlerInterface
+ */
+ virtual void linuxBootDone() const = 0;
+
++ /**
++ * Send checkpoint to BMC to monitor reboot process.
++ *
++ * @param[in] name - Checkpoint name.
++ * @param[in] wallTime - Current epoch time in microseconds.
++ * @param[in] duration - Self measured duration in microsecond.
++ * @throw IpmiException on failure.
++ */
++ virtual void sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++ int64_t duration) const = 0;
++
++ /**
++ * Inform BMC that current reboot process is completed.
++ *
++ * @throw IpmiException on failure.
++ */
++ virtual void sendRebootComplete() const = 0;
++
++ /**
++ * Send additional durations to BMC to store any duration that user is
++ * interested.
++ *
++ * @param[in] name - Duration name.
++ * @param[in] duration - duration in millisecond.
++ * @throw IpmiException on failure.
++ */
++ virtual void sendRebootAdditionalDuration(std::string_view name,
++ int64_t duration) const = 0;
++
+ /**
+ * Update the VR settings for the given settings_id
+ *
+diff --git a/handler_impl.hpp b/handler_impl.hpp
+index 7fbae3e..10ba504 100644
+--- a/handler_impl.hpp
++++ b/handler_impl.hpp
+@@ -84,6 +84,11 @@ class Handler : public HandlerInterface
+ void accelOobWrite(std::string_view name, uint64_t address,
+ uint8_t num_bytes, uint64_t data) const override;
+ void linuxBootDone() const override;
++ void sendRebootComplete() const override;
++ void sendRebootCheckpoint(std::string_view name, int64_t wallTime,
++ int64_t duration) const override;
++ void sendRebootAdditionalDuration(std::string_view name,
++ int64_t duration) const override;
+ void accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+ uint8_t settings_id, uint16_t value) const override;
+ uint16_t accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id,
+diff --git a/ipmi.cpp b/ipmi.cpp
+index 170304d..836f57f 100644
+--- a/ipmi.cpp
++++ b/ipmi.cpp
+@@ -18,6 +18,7 @@
+
+ #include "bm_instance.hpp"
+ #include "bmc_mode.hpp"
++#include "boot_time_monitor.hpp"
+ #include "cable.hpp"
+ #include "commands.hpp"
+ #include "cpld.hpp"
+@@ -90,6 +91,12 @@ Resp handleSysCommand(HandlerInterface* handler, ::ipmi::Context::ptr ctx,
+ return pcieBifurcation(ctx, data, handler, DYNAMIC_BIFURCATION);
+ case SysLinuxBootDone:
+ return linuxBootDone(data, handler);
++ case SysSendRebootCheckpoint:
++ return sendRebootCheckpoint(data, handler);
++ case SysSendRebootComplete:
++ return sendRebootComplete(data, handler);
++ case SysSendRebootAdditionalDuration:
++ return sendRebootAdditionalDuration(data, handler);
+ case SysGetAccelVrSettings:
+ return accelGetVrSettings(ctx, data, handler);
+ case SysSetAccelVrSettings:
+diff --git a/meson.build b/meson.build
+index e1145d7..b7e3f44 100644
+--- a/meson.build
++++ b/meson.build
+@@ -60,6 +60,7 @@ sys_lib = static_library(
+ 'eth.cpp',
+ 'flash_size.cpp',
+ 'handler.cpp',
++ 'boot_time_monitor.cpp',
+ 'host_power_off.cpp',
+ 'ipmi.cpp',
+ 'linux_boot_done.cpp',
+diff --git a/test/boot_time_monitor_unittest.cpp b/test/boot_time_monitor_unittest.cpp
+new file mode 100644
+index 0000000..be597af
+--- /dev/null
++++ b/test/boot_time_monitor_unittest.cpp
+@@ -0,0 +1,211 @@
++// Copyright 2021 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 "boot_time_monitor.hpp"
++#include "commands.hpp"
++#include "errors.hpp"
++#include "handler_mock.hpp"
++#include "helper.hpp"
++
++#include <cstdint>
++#include <memory>
++#include <string>
++#include <vector>
++
++#include <gtest/gtest.h>
++
++namespace google
++{
++namespace ipmi
++{
++
++using ::testing::Return;
++using ::testing::Throw;
++
++constexpr uint32_t cpHeadLen = sizeof(struct CheckpointReqHeader);
++constexpr uint32_t durHeadLen = sizeof(struct DurationReqHeader);
++
++TEST(SendRebootCheckpointTest, RequestLenTest)
++{
++ HandlerMock hMock;
++ std::vector<uint8_t> request = {};
++
++ // Empty request test
++ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++ sendRebootCheckpoint(request, &hMock));
++
++ constexpr std::string_view kName = "GoodName";
++ constexpr uint32_t kExtraSize = 1;
++ CheckpointReqHeader r = {
++ .wallTime = 0,
++ .duration = 0,
++ .length = kName.size(),
++ };
++ request.resize(cpHeadLen + kName.size());
++
++ // Test if request len is correct.
++ // Expect OK
++ memcpy(request.data(), &r, cpHeadLen);
++ memcpy(request.data() + cpHeadLen, kName.data(), r.length);
++ EXPECT_NO_THROW(sendRebootCheckpoint(request, &hMock));
++
++ // Test if request is too long
++ // Expect OK
++ r.length = kName.size() - kExtraSize;
++ memcpy(request.data(), &r, cpHeadLen);
++ memcpy(request.data() + cpHeadLen, kName.data(), r.length);
++ EXPECT_NO_THROW(sendRebootCheckpoint(request, &hMock));
++
++ // Test if request is too short
++ // Expect Error
++ r.length = kName.size() + kExtraSize;
++ memcpy(request.data(), &r, cpHeadLen);
++ memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++ sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootCheckpointTest, InvalidName)
++{
++ HandlerMock hMock;
++ constexpr std::string_view kName = "+BadName-";
++ std::vector<uint8_t> request(cpHeadLen + kName.size());
++
++ CheckpointReqHeader r = {
++ .wallTime = 0,
++ .duration = 0,
++ .length = kName.size(),
++ };
++ memcpy(request.data(), &r, cpHeadLen + kName.size());
++ memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++ EXPECT_EQ(::ipmi::responseParmOutOfRange(),
++ sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootCheckpointTest, DBusCallThrow)
++{
++ HandlerMock hMock;
++ constexpr std::string_view kName = "GoodName";
++ std::vector<uint8_t> request(cpHeadLen + kName.size());
++
++ CheckpointReqHeader r = {
++ .wallTime = 0,
++ .duration = 0,
++ .length = kName.size(),
++ };
++ memcpy(request.data(), &r, cpHeadLen + kName.size());
++ memcpy(request.data() + cpHeadLen, kName.data(), kName.size());
++
++ EXPECT_CALL(hMock, sendRebootCheckpoint(kName, r.wallTime, r.duration))
++ .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++ EXPECT_EQ(::ipmi::response(
++ IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++ sendRebootCheckpoint(request, &hMock));
++}
++
++TEST(SendRebootComplete, Success)
++{
++ HandlerMock hMock;
++
++ EXPECT_NO_THROW(sendRebootComplete(std::vector<uint8_t>{}, &hMock));
++}
++
++TEST(SendRebootComplete, DBusCallThrow)
++{
++ HandlerMock hMock;
++
++ EXPECT_CALL(hMock, sendRebootComplete())
++ .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++ EXPECT_EQ(::ipmi::response(
++ IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++ sendRebootComplete(std::vector<uint8_t>{}, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, RequestLenTest)
++{
++ HandlerMock hMock;
++ std::vector<uint8_t> request = {};
++
++ // Empty request test
++ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++ sendRebootAdditionalDuration(request, &hMock));
++
++ constexpr std::string_view kName = "GoodName";
++ constexpr uint32_t kExtraSize = 1;
++ DurationReqHeader r = {
++ .duration = 0,
++ .length = kName.size(),
++ };
++ request.resize(durHeadLen + kName.size());
++
++ // Test if request len is correct.
++ // Expect OK
++ memcpy(request.data(), &r, durHeadLen);
++ memcpy(request.data() + durHeadLen, kName.data(), r.length);
++ EXPECT_NO_THROW(sendRebootAdditionalDuration(request, &hMock));
++
++ // Test if request is too long
++ // Expect OK
++ r.length = kName.size() - kExtraSize;
++ memcpy(request.data(), &r, durHeadLen);
++ memcpy(request.data() + durHeadLen, kName.data(), r.length);
++ EXPECT_NO_THROW(sendRebootAdditionalDuration(request, &hMock));
++
++ // Test if request is too short
++ // Expect Error
++ r.length = kName.size() + kExtraSize;
++ memcpy(request.data(), &r, durHeadLen);
++ memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++ EXPECT_EQ(::ipmi::responseReqDataLenInvalid(),
++ sendRebootAdditionalDuration(request, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, InvalidName)
++{
++ HandlerMock hMock;
++ constexpr std::string_view kName = "+BadName-";
++ std::vector<uint8_t> request(durHeadLen + kName.size());
++
++ DurationReqHeader r = {
++ .duration = 0,
++ .length = kName.size(),
++ };
++ memcpy(request.data(), &r, durHeadLen + kName.size());
++ memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++ EXPECT_EQ(::ipmi::responseParmOutOfRange(),
++ sendRebootAdditionalDuration(request, &hMock));
++}
++
++TEST(SendRebootAdditionalDurationTest, DBusCallThrow)
++{
++ HandlerMock hMock;
++ constexpr std::string_view kName = "GoodName";
++ std::vector<uint8_t> request(durHeadLen + kName.size());
++
++ DurationReqHeader r = {
++ .duration = 0,
++ .length = kName.size(),
++ };
++ memcpy(request.data(), &r, durHeadLen + kName.size());
++ memcpy(request.data() + durHeadLen, kName.data(), kName.size());
++
++ EXPECT_CALL(hMock, sendRebootAdditionalDuration(kName, r.duration))
++ .WillOnce(Throw(IpmiException(::ipmi::ccUnspecifiedError)));
++ EXPECT_EQ(::ipmi::response(
++ IpmiException(::ipmi::ccUnspecifiedError).getIpmiError()),
++ sendRebootAdditionalDuration(request, &hMock));
++}
++
++} // namespace ipmi
++} // namespace google
+diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
+index c5b2df0..7a3e96b 100644
+--- a/test/handler_mock.hpp
++++ b/test/handler_mock.hpp
+@@ -67,6 +67,12 @@ class HandlerMock : public HandlerInterface
+ MOCK_METHOD(std::vector<uint8_t>, pcieBifurcation,
+ (::ipmi::Context::ptr, uint8_t, bool), (override));
+ MOCK_METHOD(void, linuxBootDone, (), (const, override));
++ MOCK_METHOD((void), sendRebootCheckpoint,
++ (std::string_view name, int64_t wallTime, int64_t duration),
++ (const, override));
++ MOCK_METHOD((void), sendRebootComplete, (), (const, override));
++ MOCK_METHOD((void), sendRebootAdditionalDuration,
++ (std::string_view name, int64_t duration), (const, override));
+ MOCK_METHOD(void, accelSetVrSettings,
+ (::ipmi::Context::ptr, uint8_t, uint8_t, uint16_t),
+ (const, override));
+diff --git a/test/handler_unittest.cpp b/test/handler_unittest.cpp
+index aabec39..ec0fdfc 100644
+--- a/test/handler_unittest.cpp
++++ b/test/handler_unittest.cpp
+@@ -307,8 +307,8 @@ void ExpectGetManagedObjects(StrictMock<sdbusplus::SdBusMock>& mock,
+ }
+
+ void ExpectSdBusError(StrictMock<sdbusplus::SdBusMock>& mock,
+- const std::string& service, const std::string& objPath,
+- const std::string& interface, const std::string& function)
++ std::string_view service, std::string_view objPath,
++ std::string_view interface, std::string_view function)
+ {
+ EXPECT_CALL(
+ mock, sd_bus_message_new_method_call(_, // sd_bus *bus,
+@@ -866,6 +866,198 @@ TEST(HandlerTest, PcieBifurcationDynamic)
+ std::remove(testFilename);
+ }
+
++constexpr std::string_view BTMONITOR_SERVICE =
++ "com.google.gbmc.boot_time_monitor";
++constexpr std::string_view BTMONITOR_OBJECT =
++ "/xyz/openbmc_project/time/boot/host0";
++constexpr std::string_view BTMONITOR_CHECKPOINT_INTERFACE =
++ "xyz.openbmc_project.Time.Boot.Checkpoint";
++constexpr std::string_view BTMONITOR_SETCHECKPOINT = "SetCheckpoint";
++constexpr std::string_view BTMONITOR_COMPLETE = "RebootComplete";
++constexpr std::string_view BTMONITOR_DURATION_INTERFACE =
++ "xyz.openbmc_project.Time.Boot.Duration";
++constexpr std::string_view BTMONITOR_SETDURATION = "SetDuration";
++
++void expectSendCheckpoint(StrictMock<sdbusplus::SdBusMock>& mock,
++ std::string_view kName, int64_t kWallTime,
++ int64_t kDur)
++{
++ ::testing::InSequence s;
++
++ constexpr sd_bus_message* method = nullptr;
++
++ EXPECT_CALL(mock, sd_bus_message_new_method_call(
++ _, // sd_bus *bus,
++ NotNull(), // sd_bus_message **m
++ StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++ StrEq(BTMONITOR_CHECKPOINT_INTERFACE),
++ StrEq(BTMONITOR_SETCHECKPOINT)))
++ .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++ SetArgPointee<1>(method), Return(0)));
++
++ EXPECT_CALL(mock, sd_bus_message_append_basic(
++ method, SD_BUS_TYPE_STRING,
++ MatcherCast<const void*>(SafeMatcherCast<const char*>(
++ StrEq(kName.data())))))
++ .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic"), Return(1)));
++
++ EXPECT_CALL(
++ mock, sd_bus_message_append_basic(
++ method, SD_BUS_TYPE_INT64,
++ MatcherCast<const void*>(
++ SafeMatcherCast<const int64_t*>(Pointee(Eq(kWallTime))))))
++ .WillOnce(
++ DoAll(TraceDbus2("sd_bus_message_append_basic(kWallTime) -> 1"),
++ Return(1)));
++
++ EXPECT_CALL(mock,
++ sd_bus_message_append_basic(
++ method, SD_BUS_TYPE_INT64,
++ MatcherCast<const void*>(
++ SafeMatcherCast<const int64_t*>(Pointee(Eq(kDur))))))
++ .WillOnce(DoAll(TraceDbus2("sd_bus_message_append_basic(kDur) -> 1"),
++ Return(1)));
++
++ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
++ method, // sd_bus_message *m
++ _, // uint64_t timeout
++ NotNull(), // sd_bus_error *ret_error
++ IsNull())) // sd_bus_message **reply
++ .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++ SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++}
++
++void expectSendAdditionalDuration(StrictMock<sdbusplus::SdBusMock>& mock,
++ std::string_view kName, int64_t kDur)
++{
++ ::testing::InSequence s;
++
++ constexpr sd_bus_message* method = nullptr;
++
++ EXPECT_CALL(mock, sd_bus_message_new_method_call(
++ _, // sd_bus *bus,
++ NotNull(), // sd_bus_message **m
++ StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++ StrEq(BTMONITOR_DURATION_INTERFACE),
++ StrEq(BTMONITOR_SETDURATION)))
++ .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++ SetArgPointee<1>(method), Return(0)));
++
++ EXPECT_CALL(mock, sd_bus_message_append_basic(
++ method, SD_BUS_TYPE_STRING,
++ MatcherCast<const void*>(SafeMatcherCast<const char*>(
++ StrEq(kName.data())))))
++ .WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic"), Return(1)));
++
++ EXPECT_CALL(mock,
++ sd_bus_message_append_basic(
++ method, SD_BUS_TYPE_INT64,
++ MatcherCast<const void*>(
++ SafeMatcherCast<const int64_t*>(Pointee(Eq(kDur))))))
++ .WillOnce(DoAll(TraceDbus2("sd_bus_message_append_basic(kDur) -> 1"),
++ Return(1)));
++
++ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
++ method, // sd_bus_message *m
++ _, // uint64_t timeout
++ NotNull(), // sd_bus_error *ret_error
++ IsNull())) // sd_bus_message **reply
++ .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++ SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++}
++
++TEST(HandlerTest, SendRebootCheckpointTest_Success)
++{
++ // Parameters are not important.
++ constexpr std::string_view kName = "NotImportant";
++ constexpr int64_t kWallTime = 0;
++ constexpr int64_t kDur = 0;
++
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ expectSendCheckpoint(mock, kName, kWallTime, kDur);
++ EXPECT_NO_THROW(h.sendRebootCheckpoint(kName, kWallTime, kDur));
++}
++
++TEST(HandlerTest, SendRebootCheckpointTest_Fail)
++{
++ // Parameters are not important.
++ constexpr std::string_view kName = "NotImportant";
++ constexpr int64_t kWallTime = 0;
++ constexpr int64_t kDur = 0;
++
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++ BTMONITOR_CHECKPOINT_INTERFACE, BTMONITOR_SETCHECKPOINT);
++ EXPECT_THROW(h.sendRebootCheckpoint(kName, kWallTime, kDur), IpmiException);
++}
++
++TEST(HandlerTest, sendRebootCompleteTest_Success)
++{
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ constexpr sd_bus_message* method = nullptr;
++ EXPECT_CALL(mock, sd_bus_message_new_method_call(
++ _, // sd_bus *bus,
++ NotNull(), // sd_bus_message **m
++ StrEq(BTMONITOR_SERVICE), StrEq(BTMONITOR_OBJECT),
++ StrEq(BTMONITOR_CHECKPOINT_INTERFACE),
++ StrEq(BTMONITOR_COMPLETE)))
++ .WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
++ SetArgPointee<1>(method), Return(0)));
++
++ EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
++ method, // sd_bus_message *m
++ _, // uint64_t timeout
++ NotNull(), // sd_bus_error *ret_error
++ IsNull())) // sd_bus_message **reply
++ .WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
++ SetArgPointee<3>(SD_BUS_ERROR_NULL), Return(0)));
++
++ EXPECT_NO_THROW(h.sendRebootComplete());
++}
++
++TEST(HandlerTest, sendRebootCompleteTest_Fail)
++{
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++ BTMONITOR_CHECKPOINT_INTERFACE, BTMONITOR_COMPLETE);
++ EXPECT_THROW(h.sendRebootComplete(), IpmiException);
++}
++
++TEST(HandlerTest, sendRebootAdditionalDurationTest_Success)
++{
++ // Parameters are not important.
++ constexpr std::string_view kName = "NotImportant";
++ constexpr int64_t kDur = 0;
++
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ expectSendAdditionalDuration(mock, kName, kDur);
++ EXPECT_NO_THROW(h.sendRebootAdditionalDuration(kName, kDur));
++}
++
++TEST(HandlerTest, sendRebootAdditionalDurationTest_Fail)
++{
++ // Parameters are not important.
++ constexpr std::string_view kName = "NotImportant";
++ constexpr int64_t kDur = 0;
++
++ StrictMock<sdbusplus::SdBusMock> mock;
++ MockDbusHandler h(mock);
++
++ ExpectSdBusError(mock, BTMONITOR_SERVICE, BTMONITOR_OBJECT,
++ BTMONITOR_DURATION_INTERFACE, BTMONITOR_SETDURATION);
++ EXPECT_THROW(h.sendRebootAdditionalDuration(kName, kDur), IpmiException);
++}
++
+ // TODO: Add checks for other functions of handler.
+
+ } // namespace ipmi
+diff --git a/test/meson.build b/test/meson.build
+index f7e3bb9..d6f50bd 100644
+--- a/test/meson.build
++++ b/test/meson.build
+@@ -23,6 +23,7 @@ tests = [
+ 'flash',
+ 'google_accel_oob',
+ 'handler',
++ 'boot_time_monitor',
+ 'machine',
+ 'pcie',
+ 'poweroff',
+--
+2.45.1.288.g0e0cd299f1-goog
+