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