1 #include "host_then_chassis_poweroff.hpp"
2 
3 #include "config.hpp"
4 #include "power_button_profile_factory.hpp"
5 
6 #include <phosphor-logging/lg2.hpp>
7 #include <xyz/openbmc_project/State/BMC/server.hpp>
8 #include <xyz/openbmc_project/State/Chassis/server.hpp>
9 
10 namespace phosphor::button
11 {
12 
13 // Register the profile with the factory.
14 static PowerButtonProfileRegister<HostThenChassisPowerOff> profileRegister;
15 
16 namespace service
17 {
18 constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
19 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis0";
20 constexpr auto hostState = "xyz.openbmc_project.State.Host";
21 } // namespace service
22 
23 namespace object_path
24 {
25 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0";
26 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0";
27 constexpr auto hostState = "/xyz/openbmc_project/state/host0";
28 } // namespace object_path
29 
30 namespace interface
31 {
32 constexpr auto property = "org.freedesktop.DBus.Properties";
33 constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
34 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis";
35 constexpr auto hostState = "xyz.openbmc_project.State.Host";
36 } // namespace interface
37 
38 using namespace sdbusplus::xyz::openbmc_project::State::server;
39 
pressed()40 void HostThenChassisPowerOff::pressed()
41 {
42     lg2::info("Power button pressed");
43 
44     try
45     {
46         // If power not on - power on
47         if (!isPoweredOn())
48         {
49             if (!isBmcReady())
50             {
51                 lg2::error("BMC not at ready state yet, cannot power on");
52                 return;
53             }
54 
55             state = PowerOpState::powerOnPress;
56             powerOn();
57             return;
58         }
59     }
60     catch (const sdbusplus::exception_t& e)
61     {
62         lg2::error(
63             "Exception while processing power button press. Cannot continue");
64         return;
65     }
66 
67     // Power is on ...
68 
69     if (state == PowerOpState::buttonNotPressed)
70     {
71         lg2::info("Starting countdown to power off");
72         state = PowerOpState::buttonPressed;
73         setHostOffTime();
74         timer.restart(pollInterval);
75     }
76 
77     // Button press during host off to chassis off window.
78     // Causes a chassis power off.
79     else if (state == PowerOpState::buttonReleasedHostToChassisOffWindow)
80     {
81         lg2::info("Starting chassis power off due to button press");
82         state = PowerOpState::chassisOffStarted;
83         timer.setEnabled(false);
84         chassisPowerOff();
85     }
86 }
87 
released(uint64_t)88 void HostThenChassisPowerOff::released(uint64_t /*pressTimeMS*/)
89 {
90     lg2::info("Power button released");
91 
92     // Button released in the host to chassis off window.
93     // Timer continues to run in case button is pressed again
94     // in the window.
95     if (state == PowerOpState::buttonPressedHostOffStarted)
96     {
97         state = PowerOpState::buttonReleasedHostToChassisOffWindow;
98         return;
99     }
100 
101     state = PowerOpState::buttonNotPressed;
102     timer.setEnabled(false);
103 }
104 
timerHandler()105 void HostThenChassisPowerOff::timerHandler()
106 {
107     const auto now = std::chrono::steady_clock::now();
108 
109     if ((state == PowerOpState::buttonPressed) && (now >= hostOffTime))
110     {
111         // Start the host power off and start the chassis
112         // power off countdown.
113         state = PowerOpState::buttonPressedHostOffStarted;
114         setChassisOffTime();
115         hostPowerOff();
116     }
117     else if ((state == PowerOpState::buttonPressedHostOffStarted) &&
118              (now >= chassisOffTime))
119     {
120         // Button still pressed and it passed the chassis off time.
121         // Issue the chassis power off.
122         state = PowerOpState::chassisOffStarted;
123         timer.setEnabled(false);
124         chassisPowerOff();
125     }
126 }
127 
hostTransition(Host::Transition transition)128 void HostThenChassisPowerOff::hostTransition(Host::Transition transition)
129 {
130     try
131     {
132         std::variant<std::string> state = convertForMessage(transition);
133 
134         lg2::info("Power button action requesting host transition of {TRANS}",
135                   "TRANS", std::get<std::string>(state));
136 
137         auto method =
138             bus.new_method_call(service::hostState, object_path::hostState,
139                                 interface::property, "Set");
140         method.append(interface::hostState, "RequestedHostTransition", state);
141 
142         bus.call(method);
143     }
144     catch (const sdbusplus::exception_t& e)
145     {
146         lg2::error("Failed requesting host transition {TRANS}: {ERROR}",
147                    "TRANS", convertForMessage(transition), "ERROR", e);
148     }
149 }
150 
powerOn()151 void HostThenChassisPowerOff::powerOn()
152 {
153     hostTransition(Host::Transition::On);
154 }
155 
hostPowerOff()156 void HostThenChassisPowerOff::hostPowerOff()
157 {
158     hostTransition(Host::Transition::Off);
159 }
160 
chassisPowerOff()161 void HostThenChassisPowerOff::chassisPowerOff()
162 {
163     lg2::info("Power button action requesting chassis power off");
164 
165     try
166     {
167         std::variant<std::string> state =
168             convertForMessage(Chassis::Transition::Off);
169 
170         auto method = bus.new_method_call(service::chassisState,
171                                           object_path::chassisState,
172                                           interface::property, "Set");
173         method.append(interface::chassisState, "RequestedPowerTransition",
174                       state);
175 
176         bus.call(method);
177     }
178     catch (const sdbusplus::exception_t& e)
179     {
180         lg2::error("Failed requesting chassis off: {ERROR}", "ERROR", e);
181     }
182 }
183 
isPoweredOn() const184 bool HostThenChassisPowerOff::isPoweredOn() const
185 {
186     Chassis::PowerState chassisState;
187 
188     try
189     {
190         auto method = bus.new_method_call(service::chassisState,
191                                           object_path::chassisState,
192                                           interface::property, "Get");
193         method.append(interface::chassisState, "CurrentPowerState");
194         auto result = bus.call(method);
195 
196         std::variant<std::string> state;
197         result.read(state);
198 
199         chassisState =
200             Chassis::convertPowerStateFromString(std::get<std::string>(state));
201     }
202     catch (const sdbusplus::exception_t& e)
203     {
204         lg2::error("Failed checking if chassis is on: {ERROR}", "ERROR", e);
205         throw;
206     }
207 
208     return chassisState == Chassis::PowerState::On;
209 }
210 
isBmcReady() const211 bool HostThenChassisPowerOff::isBmcReady() const
212 {
213     BMC::BMCState bmcState;
214 
215     try
216     {
217         auto method =
218             bus.new_method_call(service::bmcState, object_path::bmcState,
219                                 interface::property, "Get");
220         method.append(interface::bmcState, "CurrentBMCState");
221         auto result = bus.call(method);
222 
223         std::variant<std::string> state;
224         result.read(state);
225 
226         bmcState = BMC::convertBMCStateFromString(std::get<std::string>(state));
227     }
228     catch (const sdbusplus::exception_t& e)
229     {
230         lg2::error("Failed reading BMC state interface: {}", "ERROR", e);
231         throw;
232     }
233 
234     return bmcState == BMC::BMCState::Ready;
235 }
236 } // namespace phosphor::button
237