1 #pragma once
2 #include "util_base.hpp"
3 #include "utility.hpp"
4 #include "xyz/openbmc_project/Common/error.hpp"
5 
6 #include <gpiod.hpp>
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <phosphor-logging/elog.hpp>
9 #include <phosphor-logging/log.hpp>
10 
11 #include <bitset>
12 #include <chrono>
13 #include <format>
14 
15 namespace phosphor::power::psu
16 {
17 
18 using Property = std::string;
19 using Value = std::variant<bool, std::string>;
20 using PropertyMap = std::map<Property, Value>;
21 using Interface = std::string;
22 using InterfaceMap = std::map<Interface, PropertyMap>;
23 using Object = sdbusplus::message::object_path;
24 using ObjectMap = std::map<Object, InterfaceMap>;
25 
26 class Util : public UtilBase
27 {
28   public:
29     bool getPresence(sdbusplus::bus_t& bus,
30                      const std::string& invpath) const override
31     {
32         bool present = false;
33 
34         // Use getProperty utility function to get presence status.
35         util::getProperty(INVENTORY_IFACE, PRESENT_PROP, invpath,
36                           INVENTORY_MGR_IFACE, bus, present);
37 
38         return present;
39     }
40 
41     void setPresence(sdbusplus::bus_t& bus, const std::string& invpath,
42                      bool present, const std::string& name) const override
43     {
44         using namespace phosphor::logging;
45         log<level::INFO>(std::format("Updating inventory present property. "
46                                      "present:{} invpath:{} name:{}",
47                                      present, invpath, name)
48                              .c_str());
49 
50         using InternalFailure =
51             sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
52         PropertyMap invProp;
53 
54         invProp.emplace("Present", present);
55         invProp.emplace("PrettyName", name);
56 
57         InterfaceMap invIntf;
58         invIntf.emplace("xyz.openbmc_project.Inventory.Item",
59                         std::move(invProp));
60 
61         Interface extraIface = "xyz.openbmc_project.Inventory.Item.PowerSupply";
62 
63         invIntf.emplace(extraIface, PropertyMap());
64 
65         ObjectMap invObj;
66         invObj.emplace(std::move(invpath), std::move(invIntf));
67 
68         try
69         {
70             auto invService = phosphor::power::util::getService(
71                 INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
72 
73             // Update inventory
74             auto invMsg = bus.new_method_call(invService.c_str(),
75                                               INVENTORY_OBJ_PATH,
76                                               INVENTORY_MGR_IFACE, "Notify");
77             invMsg.append(std::move(invObj));
78             auto invMgrResponseMsg = bus.call(invMsg);
79         }
80         catch (const std::exception& e)
81         {
82             log<level::ERR>(
83                 std::format(
84                     "Error in inventory manager call to update inventory: {}",
85                     e.what())
86                     .c_str());
87             elog<InternalFailure>();
88         }
89     }
90 
91     void setAvailable(sdbusplus::bus_t& bus, const std::string& invpath,
92                       bool available) const override
93     {
94         PropertyMap invProp;
95         InterfaceMap invIntf;
96         ObjectMap invObj;
97 
98         invProp.emplace(AVAILABLE_PROP, available);
99         invIntf.emplace(AVAILABILITY_IFACE, std::move(invProp));
100 
101         invObj.emplace(std::move(invpath), std::move(invIntf));
102 
103         try
104         {
105             auto invService = phosphor::power::util::getService(
106                 INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
107 
108             auto invMsg = bus.new_method_call(invService.c_str(),
109                                               INVENTORY_OBJ_PATH,
110                                               INVENTORY_MGR_IFACE, "Notify");
111             invMsg.append(std::move(invObj));
112             auto invMgrResponseMsg = bus.call(invMsg);
113         }
114         catch (const sdbusplus::exception_t& e)
115         {
116             using namespace phosphor::logging;
117             log<level::ERR>(
118                 std::format("Error in inventory manager call to update "
119                             "availability interface: {}",
120                             e.what())
121                     .c_str());
122             throw;
123         }
124     }
125 
126     void handleChassisHealthRollup(sdbusplus::bus_t& bus,
127                                    const std::string& invpath,
128                                    bool addRollup) const override
129     {
130         using AssociationTuple =
131             std::tuple<std::string, std::string, std::string>;
132         using AssociationsProperty = std::vector<AssociationTuple>;
133         try
134         {
135             auto chassisPath = getChassis(bus, invpath);
136 
137             auto service = phosphor::power::util::getService(
138                 invpath, ASSOC_DEF_IFACE, bus);
139 
140             AssociationsProperty associations;
141             phosphor::power::util::getProperty<AssociationsProperty>(
142                 ASSOC_DEF_IFACE, ASSOC_PROP, invpath, service, bus,
143                 associations);
144 
145             AssociationTuple critAssociation{"health_rollup", "critical",
146                                              chassisPath};
147 
148             auto assocIt = std::find(associations.begin(), associations.end(),
149                                      critAssociation);
150             if (addRollup)
151             {
152                 if (assocIt != associations.end())
153                 {
154                     // It's already there
155                     return;
156                 }
157 
158                 associations.push_back(critAssociation);
159             }
160             else
161             {
162                 if (assocIt == associations.end())
163                 {
164                     // It's already been removed.
165                     return;
166                 }
167 
168                 // If the object still isn't functional, then don't clear
169                 // the association.
170                 bool functional = false;
171                 phosphor::power::util::getProperty<bool>(
172                     OPERATIONAL_STATE_IFACE, FUNCTIONAL_PROP, invpath, service,
173                     bus, functional);
174 
175                 if (!functional)
176                 {
177                     return;
178                 }
179 
180                 associations.erase(assocIt);
181             }
182 
183             phosphor::power::util::setProperty(ASSOC_DEF_IFACE, ASSOC_PROP,
184                                                invpath, service, bus,
185                                                associations);
186         }
187         catch (const sdbusplus::exception_t& e)
188         {
189             using namespace phosphor::logging;
190             log<level::INFO>(std::format("Error trying to handle health rollup "
191                                          "associations for {}: {}",
192                                          invpath, e.what())
193                                  .c_str());
194         }
195     }
196 
197     std::string getChassis(sdbusplus::bus_t& bus,
198                            const std::string& invpath) const override
199     {
200         sdbusplus::message::object_path assocPath = invpath + "/powering";
201         sdbusplus::message::object_path basePath{"/"};
202         std::vector<std::string> interfaces{CHASSIS_IFACE};
203 
204         // Find the object path that implements the chassis interface
205         // and also shows up in the endpoints list of the powering assoc.
206         auto chassisPaths = phosphor::power::util::getAssociatedSubTreePaths(
207             bus, assocPath, basePath, interfaces, 0);
208 
209         if (chassisPaths.empty())
210         {
211             throw std::runtime_error(std::format(
212                 "No association to a chassis found for {}", invpath));
213         }
214 
215         return chassisPaths[0];
216     }
217 };
218 
219 std::unique_ptr<GPIOInterfaceBase> createGPIO(const std::string& namedGpio);
220 
221 class GPIOInterface : public GPIOInterfaceBase
222 {
223   public:
224     GPIOInterface() = delete;
225     virtual ~GPIOInterface() = default;
226     GPIOInterface(const GPIOInterface&) = default;
227     GPIOInterface& operator=(const GPIOInterface&) = default;
228     GPIOInterface(GPIOInterface&&) = default;
229     GPIOInterface& operator=(GPIOInterface&&) = default;
230 
231     /**
232      * Constructor
233      *
234      * @param[in] namedGpio - The string for the gpio-line-name
235      */
236     GPIOInterface(const std::string& namedGpio);
237 
238     static std::unique_ptr<GPIOInterfaceBase>
239         createGPIO(const std::string& namedGpio);
240 
241     /**
242      * @brief Attempts to read the state of the GPIO line.
243      *
244      * Throws an exception if line not found, request line fails, or get_value
245      * from line fails.
246      *
247      * @return 1 for active (low/present), 0 for not active (high/not present).
248      */
249     int read() override;
250 
251     /**
252      * @brief Attempts to set the state of the GPIO line to the specified value.
253      *
254      * Throws an exception if line not found, request line fails, or set_value
255      * to line fails.
256      *
257      * @param[in] value - The value to set the state of the GPIO line, 1 or 0.
258      * @param[in] flags - Additional line request flags as defined in gpiod.hpp.
259      */
260     void write(int value, std::bitset<32> flags) override;
261 
262     /**
263      * @brief Attempts to toggle (write) a GPIO low then high.
264      *
265      * Relies on write, so throws exception if line not found, etc.
266      *
267      * @param[in] delay - Milliseconds to delay betwen low/high toggle.
268      */
269     void toggleLowHigh(const std::chrono::milliseconds& delay) override;
270 
271     /**
272      * @brief Returns the name of the GPIO, if not empty.
273      */
274     std::string getName() const override;
275 
276   private:
277     gpiod::line line;
278 };
279 
280 } // namespace phosphor::power::psu
281