1 #pragma once
2 
3 #include <cstring>
4 #include <vector>
5 #include <experimental/filesystem>
6 #include <functional>
7 #include <sdbusplus/bus.hpp>
8 #include <phosphor-logging/log.hpp>
9 #include <phosphor-logging/elog.hpp>
10 #include <powercap.hpp>
11 #include "occ_pass_through.hpp"
12 #include "occ_status.hpp"
13 #include "occ_finder.hpp"
14 #include "config.h"
15 #include "i2c_occ.hpp"
16 
17 namespace sdbusRule = sdbusplus::bus::match::rules;
18 namespace open_power
19 {
20 namespace occ
21 {
22 
23 /** @class Manager
24  *  @brief Builds and manages OCC objects
25  */
26 struct Manager
27 {
28     public:
29         Manager() = delete;
30         Manager(const Manager&) = delete;
31         Manager& operator=(const Manager&) = delete;
32         Manager(Manager&&) = default;
33         Manager& operator=(Manager&&) = default;
34         ~Manager() = default;
35 
36         /** @brief Adds OCC pass-through and status objects on the bus
37          *         when corresponding CPU inventory is created.
38          *
39          *  @param[in] bus   - handle to the bus
40          *  @param[in] event - Unique ptr reference to sd_event
41          */
42         Manager(sdbusplus::bus::bus& bus,
43                 EventPtr& event) :
44             bus(bus),
45             event(event)
46         {
47 #ifdef I2C_OCC
48             // I2C OCC status objects are initialized directly
49             initStatusObjects();
50 #else
51             // Check if CPU inventory exists already.
52             auto occs = open_power::occ::finder::get(bus);
53             if (occs.empty())
54             {
55                 // Need to watch for CPU inventory creation.
56                 for (auto id = 0; id < MAX_CPUS; ++id)
57                 {
58                     auto path = std::string(CPU_PATH) + std::to_string(id);
59                     cpuMatches.emplace_back(
60                         bus,
61                         sdbusRule::interfacesAdded() +
62                         sdbusRule::argNpath(0, path),
63                         std::bind(std::mem_fn(&Manager::cpuCreated),
64                                   this, std::placeholders::_1));
65                 }
66             }
67             else
68             {
69                 for (const auto& occ : occs)
70                 {
71                     // CPU inventory exists already, OCC objects can be created.
72                     createObjects(occ);
73                 }
74             }
75 #endif
76         }
77 
78     private:
79         /** @brief Callback that responds to cpu creation in the inventory -
80          *         by creating the needed objects.
81          *
82          *  @param[in] msg - bus message
83          *
84          *  @returns 0 to indicate success
85          */
86         int cpuCreated(sdbusplus::message::message& msg)
87         {
88             namespace fs = std::experimental::filesystem;
89 
90             sdbusplus::message::object_path o;
91             msg.read(o);
92             fs::path cpuPath(std::string(std::move(o)));
93 
94             auto name = cpuPath.filename().string();
95             auto index = name.find(CPU_NAME);
96             name.replace(index, std::strlen(CPU_NAME), OCC_NAME);
97 
98             createObjects(name);
99 
100             return 0;
101         }
102 
103         /** @brief Create child OCC objects.
104          *
105          *  @param[in] occ - the occ name, such as occ0.
106          */
107         void createObjects(const std::string& occ)
108         {
109             auto path = fs::path(OCC_CONTROL_ROOT) / occ;
110 
111             passThroughObjects.emplace_back(
112                 std::make_unique<PassThrough>(
113                     bus,
114                     path.c_str()));
115 
116             statusObjects.emplace_back(
117                 std::make_unique<Status>(
118                     bus,
119                     event,
120                     path.c_str(),
121                     std::bind(std::mem_fn(&Manager::statusCallBack),
122                                           this, std::placeholders::_1)));
123 
124             // Create the power cap monitor object for master occ (0)
125             if (!pcap)
126             {
127                 pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
128                                                         bus,
129                                                         *statusObjects.front());
130             }
131         }
132 
133         /** @brief Callback handler invoked by Status object when the OccActive
134          *         property is changed. This is needed to make sure that the
135          *         error detection is started only after all the OCCs are bound.
136          *         Similarly, when one of the OCC gets its OccActive property
137          *         un-set, then the OCC error detection needs to be stopped on
138          *         all the OCCs
139          *
140          *  @param[in] status - OccActive status
141          */
142         void statusCallBack(bool status)
143         {
144             using namespace phosphor::logging;
145             using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
146                                         Error::InternalFailure;
147 
148             // At this time, it won't happen but keeping it
149             // here just in case something changes in the future
150             if ((activeCount == 0) && (!status))
151             {
152                 log<level::ERR>("Invalid update on OCCActive");
153                 elog<InternalFailure>();
154             }
155 
156             activeCount += status ? 1 : -1;
157 
158             // If all the OCCs are bound, then start error detection
159             if (activeCount == statusObjects.size())
160             {
161                 for (const auto& occ: statusObjects)
162                 {
163                     occ->addErrorWatch();
164                 }
165             }
166             else if (!status)
167             {
168                 // If some OCCs are not bound yet, those will be a NO-OP
169                 for (const auto& occ: statusObjects)
170                 {
171                     occ->removeErrorWatch();
172                 }
173             }
174         }
175 
176         /** @brief reference to the bus */
177         sdbusplus::bus::bus& bus;
178 
179         /** @brief reference to sd_event wrapped in unique_ptr */
180         EventPtr& event;
181 
182         /** @brief OCC pass-through objects */
183         std::vector<std::unique_ptr<PassThrough>> passThroughObjects;
184 
185         /** @brief OCC Status objects */
186         std::vector<std::unique_ptr<Status>> statusObjects;
187 
188         /** @brief Power cap monitor and occ notification object */
189         std::unique_ptr<open_power::occ::powercap::PowerCap> pcap;
190 
191         /** @brief sbdbusplus match objects */
192         std::vector<sdbusplus::bus::match_t> cpuMatches;
193 
194         /** @brief Number of OCCs that are bound */
195         uint8_t activeCount = 0;
196 
197 #ifdef I2C_OCC
198         /** @brief Init Status objects for I2C OCC devices
199          *
200          * It iterates in /sys/bus/i2c/devices, finds all occ hwmon devices
201          * and creates status objects.
202          */
203         void initStatusObjects()
204         {
205             // Make sure we have a valid path string
206             static_assert(sizeof(DEV_PATH) != 0);
207 
208             auto deviceNames = i2c_occ::getOccHwmonDevices(DEV_PATH);
209             for (auto& name : deviceNames)
210             {
211                 i2c_occ::i2cToDbus(name);
212                 auto path = fs::path(OCC_CONTROL_ROOT) / name;
213                 statusObjects.emplace_back(
214                     std::make_unique<Status>(
215                         bus,
216                         event,
217                         path.c_str()));
218             }
219         }
220 #endif
221 };
222 
223 } // namespace occ
224 } // namespace open_power
225