1 #pragma once
2 
3 #include "sdbusplus.hpp"
4 
5 #include <phosphor-logging/log.hpp>
6 #include <xyz/openbmc_project/State/Host/server.hpp>
7 
8 #include <functional>
9 
10 using HostState =
11     sdbusplus::xyz::openbmc_project::State::server::Host::HostState;
12 
13 namespace phosphor::fan
14 {
15 
16 /**
17  * @class PowerState
18  *
19  * This class provides an interface to check the current power state,
20  * and to register a function that gets called when there is a power
21  * state change.  A callback can be passed in using the constructor,
22  * or can be added later using addCallback().
23  *
24  * Different architectures may have different ways of considering
25  * power to be on, such as a pgood property on the
26  * org.openbmc.Control.Power interface, or the CurrentPowerState
27  * property on the State.Chassis interface, so those details will
28  * be in a derived class.
29  */
30 class PowerState
31 {
32   public:
33     using StateChangeFunc = std::function<void(bool)>;
34 
35     virtual ~PowerState() = default;
36     PowerState(const PowerState&) = delete;
37     PowerState& operator=(const PowerState&) = delete;
38     PowerState(PowerState&&) = delete;
39     PowerState& operator=(PowerState&&) = delete;
40 
41     /**
42      * @brief Constructor
43      *
44      * @param[in] bus - The D-Bus bus connection object
45      * @param[in] callback - The function that should be run when
46      *                       the power state changes
47      */
PowerState(sdbusplus::bus_t & bus,StateChangeFunc callback)48     PowerState(sdbusplus::bus_t& bus, StateChangeFunc callback) : _bus(bus)
49     {
50         _callbacks.emplace("default", std::move(callback));
51     }
52 
53     /**
54      * @brief Constructor
55      *
56      * Callbacks can be added with addCallback().
57      */
PowerState()58     PowerState() : _bus(util::SDBusPlus::getBus()) {}
59 
60     /**
61      * @brief Adds a function to call when the power state changes
62      *
63      * @param[in] - Any unique name, so the callback can be removed later
64      *              if desired.
65      * @param[in] callback - The function that should be run when
66      *                       the power state changes
67      */
addCallback(const std::string & name,StateChangeFunc callback)68     void addCallback(const std::string& name, StateChangeFunc callback)
69     {
70         _callbacks.emplace(name, std::move(callback));
71     }
72 
73     /**
74      * @brief Remove the callback so it is no longer called
75      *
76      * @param[in] name - The name used when it was added.
77      */
deleteCallback(const std::string & name)78     void deleteCallback(const std::string& name)
79     {
80         _callbacks.erase(name);
81     }
82 
83     /**
84      * @brief Says if power is on
85      *
86      * @return bool - The power state
87      */
isPowerOn() const88     bool isPowerOn() const
89     {
90         return _powerState;
91     }
92 
93   protected:
94     /**
95      * @brief Called by derived classes to set the power state value
96      *
97      * Will call the callback functions if the state changed.
98      *
99      * @param[in] state - The new power state
100      */
setPowerState(bool state)101     void setPowerState(bool state)
102     {
103         if (state != _powerState)
104         {
105             _powerState = state;
106             for (const auto& [name, callback] : _callbacks)
107             {
108                 callback(_powerState);
109             }
110         }
111     }
112 
113     /**
114      * @brief Reference to the D-Bus connection object.
115      */
116     sdbusplus::bus_t& _bus;
117 
118     /**
119      * @brief The power state value
120      */
121     bool _powerState = false;
122 
123   private:
124     /**
125      * @brief The callback functions to run when the power state changes
126      */
127     std::map<std::string, StateChangeFunc> _callbacks;
128 };
129 
130 /**
131  * @class PGoodState
132  *
133  * This class implements the PowerState API by looking at the 'pgood'
134  * property on the org.openbmc.Control.Power interface.
135  */
136 class PGoodState : public PowerState
137 {
138   public:
139     virtual ~PGoodState() = default;
140     PGoodState(const PGoodState&) = delete;
141     PGoodState& operator=(const PGoodState&) = delete;
142     PGoodState(PGoodState&&) = delete;
143     PGoodState& operator=(PGoodState&&) = delete;
144 
PGoodState()145     PGoodState() :
146         PowerState(),
147         _match(_bus,
148                sdbusplus::bus::match::rules::propertiesChanged(_pgoodPath,
149                                                                _pgoodInterface),
150                [this](auto& msg) { this->pgoodChanged(msg); })
151     {
152         readPGood();
153     }
154 
155     /**
156      * @brief Constructor
157      *
158      * @param[in] bus - The D-Bus bus connection object
159      * @param[in] callback - The function that should be run when
160      *                       the power state changes
161      */
PGoodState(sdbusplus::bus_t & bus,StateChangeFunc func)162     PGoodState(sdbusplus::bus_t& bus, StateChangeFunc func) :
163         PowerState(bus, func),
164         _match(_bus,
165                sdbusplus::bus::match::rules::propertiesChanged(_pgoodPath,
166                                                                _pgoodInterface),
167                [this](auto& msg) { this->pgoodChanged(msg); })
168     {
169         readPGood();
170     }
171 
172     /**
173      * @brief PropertiesChanged callback for the PGOOD property.
174      *
175      * Will call the registered callback function if necessary.
176      *
177      * @param[in] msg - The payload of the propertiesChanged signal
178      */
pgoodChanged(sdbusplus::message_t & msg)179     void pgoodChanged(sdbusplus::message_t& msg)
180     {
181         std::string interface;
182         std::map<std::string, std::variant<int32_t>> properties;
183 
184         msg.read(interface, properties);
185 
186         auto pgoodProp = properties.find(_pgoodProperty);
187         if (pgoodProp != properties.end())
188         {
189             auto pgood = std::get<int32_t>(pgoodProp->second);
190             setPowerState(pgood);
191         }
192     }
193 
194   private:
195     /**
196      * @brief Reads the PGOOD property from D-Bus and saves it.
197      */
readPGood()198     void readPGood()
199     {
200         try
201         {
202             auto pgood = util::SDBusPlus::getProperty<int32_t>(
203                 _bus, _pgoodPath, _pgoodInterface, _pgoodProperty);
204 
205             _powerState = static_cast<bool>(pgood);
206         }
207         catch (const util::DBusServiceError& e)
208         {
209             // Wait for propertiesChanged signal when service starts
210         }
211     }
212 
213     /** @brief D-Bus path constant */
214     const std::string _pgoodPath{"/org/openbmc/control/power0"};
215 
216     /** @brief D-Bus interface constant */
217     const std::string _pgoodInterface{"org.openbmc.control.Power"};
218 
219     /** @brief D-Bus property constant */
220     const std::string _pgoodProperty{"pgood"};
221 
222     /** @brief The propertiesChanged match */
223     sdbusplus::bus::match_t _match;
224 };
225 
226 /**
227  * @class HostPowerState
228  *
229  * This class implements the PowerState API by looking at the 'powerState'
230  * property on the phosphor virtual sensor interface.
231  */
232 class HostPowerState : public PowerState
233 {
234   public:
235     virtual ~HostPowerState() = default;
236     HostPowerState(const HostPowerState&) = delete;
237     HostPowerState& operator=(const HostPowerState&) = delete;
238     HostPowerState(HostPowerState&&) = delete;
239     HostPowerState& operator=(HostPowerState&&) = delete;
240 
HostPowerState()241     HostPowerState() :
242         PowerState(),
243         _match(_bus,
244                sdbusplus::bus::match::rules::propertiesChangedNamespace(
245                    _hostStatePath, _hostStateInterface),
246                [this](auto& msg) { this->hostStateChanged(msg); })
247     {
248         readHostState();
249     }
250 
251     /**
252      * @brief Constructor
253      *
254      * @param[in] bus - The D-Bus bus connection object
255      * @param[in] callback - The function that should be run when
256      *                       the power state changes
257      */
HostPowerState(sdbusplus::bus_t & bus,StateChangeFunc func)258     HostPowerState(sdbusplus::bus_t& bus, StateChangeFunc func) :
259         PowerState(bus, func),
260         _match(_bus,
261                sdbusplus::bus::match::rules::propertiesChangedNamespace(
262                    _hostStatePath, _hostStateInterface),
263                [this](auto& msg) { this->hostStateChanged(msg); })
264     {
265         readHostState();
266     }
267 
268     /**
269      * @brief PropertiesChanged callback for the CurrentHostState property.
270      *
271      * Will call the registered callback function if necessary.
272      *
273      * @param[in] msg - The payload of the propertiesChanged signal
274      */
hostStateChanged(sdbusplus::message_t & msg)275     void hostStateChanged(sdbusplus::message_t& msg)
276     {
277         std::string interface;
278         std::map<std::string, std::variant<std::string>> properties;
279         std::vector<HostState> hostPowerStates;
280 
281         msg.read(interface, properties);
282 
283         auto hostStateProp = properties.find(_hostStateProperty);
284         if (hostStateProp != properties.end())
285         {
286             auto currentHostState =
287                 sdbusplus::message::convert_from_string<HostState>(
288                     std::get<std::string>(hostStateProp->second));
289 
290             if (!currentHostState)
291             {
292                 throw sdbusplus::exception::InvalidEnumString();
293             }
294             HostState hostState = *currentHostState;
295 
296             hostPowerStates.emplace_back(hostState);
297             setHostPowerState(hostPowerStates, true);
298         }
299     }
300 
301   private:
setHostPowerState(std::vector<HostState> & hostPowerStates,bool callFromStateChange)302     void setHostPowerState(std::vector<HostState>& hostPowerStates,
303                            bool callFromStateChange)
304     {
305         bool powerStateflag = false;
306         for (const auto& powerState : hostPowerStates)
307         {
308             if (powerState == HostState::Standby ||
309                 powerState == HostState::Running ||
310                 powerState == HostState::TransitioningToRunning ||
311                 powerState == HostState::Quiesced ||
312                 powerState == HostState::DiagnosticMode)
313             {
314                 powerStateflag = true;
315                 break;
316             }
317         }
318         if (callFromStateChange)
319         {
320             setPowerState(powerStateflag);
321         }
322         else
323         {
324             // This won't call callbacks (if exists) during the constructor of
325             // the class when the first read for this flag is true which is
326             // different from the init value of _powerState.
327             // Daemon that wants to do anything when the initial power state
328             // is true can check isPowerOn() and do it.
329             _powerState = powerStateflag;
330         }
331     }
332 
333     /**
334      * @brief Reads the CurrentHostState property from D-Bus and saves it.
335      */
readHostState()336     void readHostState()
337     {
338         std::string hostStatePath;
339         std::string hostStateService;
340         std::string hostService = "xyz.openbmc_project.State.Host";
341         std::vector<HostState> hostPowerStates;
342 
343         int32_t depth = 0;
344         const std::string path = "/";
345 
346         auto mapperResponse = util::SDBusPlus::getSubTreeRaw(
347             _bus, path, _hostStateInterface, depth);
348 
349         for (const auto& path : mapperResponse)
350         {
351             for (const auto& service : path.second)
352             {
353                 hostStateService = service.first;
354 
355                 if (hostStateService.find(hostService) != std::string::npos)
356                 {
357                     hostStatePath = path.first;
358 
359                     auto currentHostState =
360                         util::SDBusPlus::getProperty<HostState>(
361                             hostStateService, hostStatePath,
362                             _hostStateInterface, _hostStateProperty);
363 
364                     hostPowerStates.emplace_back(currentHostState);
365                 }
366             }
367         }
368         setHostPowerState(hostPowerStates, false);
369     }
370 
371     const std::string _hostStatePath{"/xyz/openbmc_project/state"};
372 
373     /** @brief D-Bus interface constant */
374     const std::string _hostStateInterface{"xyz.openbmc_project.State.Host"};
375 
376     /** @brief D-Bus property constant */
377     const std::string _hostStateProperty{"CurrentHostState"};
378 
379     /** @brief The propertiesChanged match */
380     sdbusplus::bus::match_t _match;
381 };
382 
383 } // namespace phosphor::fan
384