xref: /openbmc/google-misc/subprojects/bare-metal-host-monitor/host_gpio_monitor.cpp (revision c9a43a3017d248aa821792899f29d408a7ca4c26)
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