Add new google ipmi sys command: SysHostPowerOff
New google ipmi sys command to let the BMC knows host shutdown,
allow host to gracefully shutdown and disable the watchdog with given
time delay.
Signed-off-by: Yunyun Lin <linyuny@google.com>
Change-Id: I02171c9cfed57ae5d10d66b515e4ab7ee8856466
diff --git a/Makefile.am b/Makefile.am
index 5016449..ec1dfaf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,8 @@
if HAVE_SYSTEMD
systemdsystemunit_DATA = \
- gbmc-psu-hardreset.target
+ gbmc-psu-hardreset.target \
+ gbmc-host-poweroff.target
endif
# Ignore system headers
@@ -51,7 +52,8 @@
machine_name.cpp \
handler.cpp \
util.cpp \
- ipmi.cpp
+ ipmi.cpp \
+ host_power_off.cpp
libsyscmds_common_la_CXXFLAGS = \
$(SDBUSPLUS_CFLAGS) \
$(PHOSPHOR_LOGGING_CFLAGS) \
diff --git a/README.md b/README.md
index 25ae8e0..853eebe 100644
--- a/README.md
+++ b/README.md
@@ -215,3 +215,21 @@
|--------|------|----
|0x00|0x09|Subcommand
|0x01...0x04|Flash size|Flash size
+
+### HostPowerOff - SubCommand 0x0A
+Sys command needs to be able to let the BMC knows host attempt S5 shutdown,
+it need power-off the Host gracefully and disable the watchdog with given time
+delay.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0A|Subcommand
+|0x01..0x04| |Seconds to delay (uint32)
+
+Response
+
+|Byte(s) |Value |Data
+|--------|-------|----
+|0x00|0x0A|Subcommand
diff --git a/commands.hpp b/commands.hpp
index d9efb4f..08ab21b 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -27,6 +27,8 @@
SysPsuHardResetOnShutdown = 8,
// The Sys get flash size command
SysGetFlashSize = 9,
+ // The Sys Host Power Off with disabled fallback watchdog
+ SysHostPowerOff = 10,
};
} // namespace ipmi
diff --git a/gbmc-host-poweroff.target b/gbmc-host-poweroff.target
new file mode 100644
index 0000000..521e840
--- /dev/null
+++ b/gbmc-host-poweroff.target
@@ -0,0 +1,2 @@
+[Unit]
+Description=Sys Host Power Off
diff --git a/handler.cpp b/handler.cpp
index ef48842..e99bfff 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -308,6 +308,48 @@
}
}
+static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay";
+static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target";
+
+void Handler::hostPowerOffDelay(std::uint32_t delay) const
+{
+ // Set time delay
+ std::ofstream ofs;
+ ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out);
+ if (!ofs.good())
+ {
+ std::fprintf(stderr, "Unable to open file for output.\n");
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl;
+ ofs.close();
+ if (ofs.fail())
+ {
+ std::fprintf(stderr, "Write failed\n");
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+
+ // Write succeeded, please continue.
+ auto bus = sdbusplus::bus::new_default();
+ auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
+ SYSTEMD_INTERFACE, "StartUnit");
+
+ method.append(HOST_POWEROFF_TARGET);
+ method.append("replace");
+
+ try
+ {
+ bus.call_noreply(method);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>("Failed to call Power Off",
+ entry("WHAT=%s", ex.what()));
+ throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+ }
+}
+
std::string readNameFromConfig(const std::string& type, uint8_t instance,
const Json& config)
{
diff --git a/handler.hpp b/handler.hpp
index 1a2cbae..f4b1bbd 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -109,6 +109,14 @@
*/
virtual std::tuple<std::uint32_t, std::string>
getI2cEntry(unsigned int entry) const = 0;
+
+ /**
+ * Set the Host Power Off delay.
+ *
+ * @param[in] delay - delay in seconds.
+ * @throw IpmiException on failure.
+ */
+ virtual void hostPowerOffDelay(std::uint32_t delay) const = 0;
};
} // namespace ipmi
diff --git a/handler_impl.hpp b/handler_impl.hpp
index 677efa2..807114e 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -34,6 +34,7 @@
std::string getMachineName() override;
void buildI2cPcieMapping() override;
size_t getI2cPcieMappingSize() const override;
+ void hostPowerOffDelay(std::uint32_t delay) const override;
std::tuple<std::uint32_t, std::string>
getI2cEntry(unsigned int entry) const override;
diff --git a/host_power_off.cpp b/host_power_off.cpp
new file mode 100644
index 0000000..57e45a1
--- /dev/null
+++ b/host_power_off.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 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 "host_power_off.hpp"
+
+#include "commands.hpp"
+#include "errors.hpp"
+#include "handler.hpp"
+
+#include <cstdint>
+#include <cstring>
+
+namespace google
+{
+namespace ipmi
+{
+
+ipmi_ret_t hostPowerOff(const uint8_t* reqBuf, uint8_t* replyBuf,
+ size_t* dataLen, const HandlerInterface* handler)
+{
+ struct HostPowerOffRequest request;
+
+ if ((*dataLen) < sizeof(request))
+ {
+ std::fprintf(stderr, "Invalid command length: %u\n",
+ static_cast<uint32_t>(*dataLen));
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ std::memcpy(&request, &reqBuf[0], sizeof(struct HostPowerOffRequest));
+ try
+ {
+ handler->hostPowerOffDelay(request.delay);
+ }
+ catch (const IpmiException& e)
+ {
+ return e.getIpmiError();
+ }
+
+ replyBuf[0] = SysHostPowerOff;
+ (*dataLen) = sizeof(uint8_t);
+
+ return IPMI_CC_OK;
+}
+} // namespace ipmi
+} // namespace google
diff --git a/host_power_off.hpp b/host_power_off.hpp
new file mode 100644
index 0000000..e399007
--- /dev/null
+++ b/host_power_off.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+struct HostPowerOffRequest
+{
+ uint8_t subcommand;
+ // Delay in seconds.
+ uint32_t delay;
+} __attribute__((packed));
+
+// Disable the fallback watchdog with given time delay and Power Off Host
+ipmi_ret_t hostPowerOff(const uint8_t* reqBuf, uint8_t* replyBuf,
+ size_t* dataLen, const HandlerInterface* handler);
+
+} // namespace ipmi
+} // namespace google
diff --git a/ipmi.cpp b/ipmi.cpp
index 2b71315..bcd2b4d 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -23,6 +23,7 @@
#include "eth.hpp"
#include "flash_size.hpp"
#include "handler.hpp"
+#include "host_power_off.hpp"
#include "machine_name.hpp"
#include "pcie_i2c.hpp"
#include "psu.hpp"
@@ -72,6 +73,8 @@
handler);
case SysGetFlashSize:
return getFlashSize(reqBuf, replyCmdBuf, dataLen, handler);
+ case SysHostPowerOff:
+ return hostPowerOff(reqBuf, replyCmdBuf, dataLen, handler);
default:
std::fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
return IPMI_CC_INVALID;
diff --git a/test/Makefile.am b/test/Makefile.am
index c97f480..a502e77 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -52,3 +52,7 @@
check_PROGRAMS += flash_unittest
flash_unittest_SOURCES = flash_unittest.cpp
flash_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
+
+check_PROGRAMS += poweroff_unittest
+poweroff_unittest_SOURCES = poweroff_unittest.cpp
+poweroff_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index d539f7b..edd2aec 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -34,6 +34,7 @@
MOCK_CONST_METHOD0(getI2cPcieMappingSize, size_t());
MOCK_CONST_METHOD1(getI2cEntry,
std::tuple<std::uint32_t, std::string>(unsigned int));
+ MOCK_CONST_METHOD1(hostPowerOffDelay, void(std::uint32_t));
};
} // namespace ipmi
diff --git a/test/poweroff_unittest.cpp b/test/poweroff_unittest.cpp
new file mode 100644
index 0000000..ce9501b
--- /dev/null
+++ b/test/poweroff_unittest.cpp
@@ -0,0 +1,49 @@
+#include "commands.hpp"
+#include "handler_mock.hpp"
+#include "host_power_off.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define MAX_IPMI_BUFFER 64
+
+namespace google
+{
+namespace ipmi
+{
+
+TEST(PowerOffCommandTest, InvalidRequestLength)
+{
+ std::vector<std::uint8_t> request = {SysOEMCommands::SysHostPowerOff};
+ size_t dataLen = request.size();
+ std::uint8_t reply[MAX_IPMI_BUFFER];
+
+ HandlerMock hMock;
+ EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+ hostPowerOff(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(PowerOffCommandTest, ValidRequest)
+{
+ // Set the dealy to 15 mins
+ std::uint32_t delayValue = 0x384;
+ struct HostPowerOffRequest requestContents;
+ requestContents.subcommand = SysOEMCommands::SysHostPowerOff;
+ requestContents.delay = delayValue;
+
+ std::vector<std::uint8_t> request(sizeof(requestContents));
+ std::memcpy(request.data(), &requestContents, sizeof(requestContents));
+ size_t dataLen = request.size();
+ std::uint8_t reply[MAX_IPMI_BUFFER];
+
+ HandlerMock hMock;
+ EXPECT_CALL(hMock, hostPowerOffDelay(delayValue));
+ EXPECT_EQ(IPMI_CC_OK,
+ hostPowerOff(request.data(), reply, &dataLen, &hMock));
+}
+
+} // namespace ipmi
+} // namespace google