1
2 #include "absl/flags/flag.h"
3 #include "absl/flags/parse.h"
4
5 #include "host_gpio_monitor_conf.hpp"
6
7 #include <systemd/sd-daemon.h>
8
9 #include <boost/asio/io_context.hpp>
10 #include <boost/asio/steady_timer.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/asio/connection.hpp>
13 #include <sdbusplus/asio/property.hpp>
14 #include <sdbusplus/bus.hpp>
15 #include <sdbusplus/bus/match.hpp>
16
17 #include <format>
18
19 ABSL_FLAG(std::string, host_label, "0",
20 "Label for the host in question. Usually this is an integer.");
21
22 const constexpr char* OperatingSystemStateInactive =
23 "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive";
24 const constexpr char* BareMetalActiveTargetTemplate =
25 "gbmc-bare-metal-active@{}.target";
26
27 const constexpr char* SystemdService = "org.freedesktop.systemd1";
28 const constexpr char* SystemdManagerObject = "/org/freedesktop/systemd1";
29 const constexpr char* SystemdManagerInterface =
30 "org.freedesktop.systemd1.Manager";
31
setUnitStatus(sdbusplus::asio::connection & bus,bool status,const std::string & host_instance)32 void setUnitStatus(sdbusplus::asio::connection& bus, bool status,
33 const std::string& host_instance)
34 {
35 auto method = bus.new_method_call(SystemdService, SystemdManagerObject,
36 SystemdManagerInterface,
37 status ? "StartUnit" : "StopUnit");
38 method.append(std::format(BareMetalActiveTargetTemplate, host_instance),
39 "replace");
40
41 bus.call(method);
42 }
43
checkPostComplete(sdbusplus::asio::connection & bus,const std::string & state,bool action,const std::string & host_instance)44 void checkPostComplete(sdbusplus::asio::connection& bus,
45 const std::string& state, bool action,
46 const std::string& host_instance)
47 {
48 sdbusplus::asio::getProperty<std::string>(
49 bus, std::format(DBUS_SERVICE_NAME, host_instance),
50 std::format(DBUS_OBJECT_PATH, host_instance), DBUS_INTERFACE,
51 DBUS_PROPERTY_NAME,
52 [&, state, action](const boost::system::error_code& ec,
53 const std::string& postCompleteState) {
54 if (ec)
55 {
56 lg2::error("Error when checking Post Complete GPIO state");
57 return;
58 }
59
60 lg2::info("Post Complete state is {STATE}", "STATE",
61 postCompleteState);
62
63 /*
64 * If the host OS is running (e.g. OperatingSystemState is Standby),
65 * enable the bare-metal-active systemd target.
66 * If the host CPU is in reset (e.g. OperatingSystemState is
67 * Inactive), no-op cause IPMI is enabled by default.
68 */
69 if (postCompleteState == state)
70 {
71 setUnitStatus(bus, action, host_instance);
72 }
73 });
74 }
75
76 /* This only gets called once on startup. */
checkPostCompleteStartup(sdbusplus::asio::connection & bus,const std::string & host_instance)77 void checkPostCompleteStartup(sdbusplus::asio::connection& bus,
78 const std::string& host_instance)
79 {
80 checkPostComplete(bus, DBUS_PROPERTY_HOST_RUNNING_VALUE, true,
81 host_instance);
82 }
83
84 /* Gets called when a GPIO state change is detected. */
checkPostCompleteEvent(sdbusplus::asio::connection & bus,const std::string & host_instance)85 void checkPostCompleteEvent(sdbusplus::asio::connection& bus,
86 const std::string& host_instance)
87 {
88 checkPostComplete(bus, DBUS_PROPERTY_HOST_IN_RESET_VALUE, false,
89 host_instance);
90 }
91
main(int argc,char ** argv)92 int main(int argc, char** argv)
93 {
94 absl::ParseCommandLine(argc, argv);
95 std::string host_label = absl::GetFlag(FLAGS_host_label);
96
97 try
98 {
99 /* Setup connection to dbus. */
100 boost::asio::io_context io;
101 auto conn = sdbusplus::asio::connection(io);
102
103 /* check IPMI status at startup */
104 checkPostCompleteStartup(conn, host_label);
105
106 /* Notify that the service is done starting up. */
107 sd_notify(0, "READY=1");
108
109 /*
110 * Set up an event handler to process Post Complete GPIO state changes.
111 */
112 boost::asio::steady_timer filterTimer(io);
113
114 /*
115 * Prepare object path we want to monitor, substituting in the host
116 * label, if needed.
117 */
118 std::string objectPath = std::format(DBUS_OBJECT_PATH, host_label);
119
120 auto match = std::make_unique<sdbusplus::bus::match_t>(
121 static_cast<sdbusplus::bus_t&>(conn),
122 std::format(
123 "type='signal',member='PropertiesChanged',path_namespace='"
124 "{}',arg0namespace='{}'",
125 objectPath, DBUS_INTERFACE),
126 [&](sdbusplus::message_t& message) {
127 if (message.is_method_error())
128 {
129 lg2::error("eventHandler callback method error");
130 return;
131 }
132
133 /*
134 * This implicitly cancels the timer, if it's already pending.
135 * If there's a burst of events within a short period, we want
136 * to handle them all at once. So, we will wait this long for no
137 * more events to occur, before processing them.
138 */
139 filterTimer.expires_from_now(std::chrono::milliseconds(100));
140
141 filterTimer.async_wait(
142 [&](const boost::system::error_code& ec) {
143 if (ec == boost::asio::error::operation_aborted)
144 {
145 /* we were canceled */
146 return;
147 }
148 if (ec)
149 {
150 lg2::error("timer error");
151 return;
152 }
153
154 /*
155 * Stop the bare metal active target if the post
156 * complete got deasserted.
157 */
158 checkPostCompleteEvent(conn, host_label);
159 });
160 });
161
162 io.run();
163 return 0;
164 }
165 catch (const std::exception& e)
166 {
167 lg2::error(e.what(), "REDFISH_MESSAGE_ID",
168 std::string("OpenBMC.1.0.ServiceException"));
169
170 return 2;
171 }
172 return 1;
173 }
174