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