1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16
17 #include "types.hpp"
18
19 #include <boost/algorithm/string/predicate.hpp>
20 #include <boost/algorithm/string/replace.hpp>
21 #include <boost/asio/post.hpp>
22 #include <boost/container/flat_set.hpp>
23 #include <cold_redundancy.hpp>
24 #include <phosphor-logging/elog-errors.hpp>
25 #include <sdbusplus/asio/connection.hpp>
26 #include <sdbusplus/asio/object_server.hpp>
27 #include <sdbusplus/bus/match.hpp>
28
29 #include <array>
30 #include <filesystem>
31 #include <fstream>
32 #include <iostream>
33 #include <regex>
34
35 namespace
36 {
37 constexpr const std::array<const char*, 1> psuInterfaceTypes = {
38 "xyz.openbmc_project.Configuration.pmbus"};
39 std::string inventoryPath = std::string(INVENTORY_OBJ_PATH) + "/system";
40 const constexpr char* eventPath = "/xyz/openbmc_project/State/Decorator";
41 std::vector<std::unique_ptr<PowerSupply>> powerSupplies;
42 } // namespace
43
ColdRedundancy(boost::asio::io_context & io,std::shared_ptr<sdbusplus::asio::connection> & systemBus)44 ColdRedundancy::ColdRedundancy(
45 boost::asio::io_context& io,
46 std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
47 filterTimer(io), systemBus(systemBus)
48 {
49 post(io, [this, &systemBus]() { createPSU(systemBus); });
50 std::function<void(sdbusplus::message_t&)> eventHandler =
51 [this, &systemBus](sdbusplus::message_t&) {
52 filterTimer.expires_after(std::chrono::seconds(1));
53 filterTimer.async_wait(
54 [this, &systemBus](const boost::system::error_code& ec) {
55 if (ec == boost::asio::error::operation_aborted)
56 {
57 return;
58 }
59 else if (ec)
60 {
61 std::cerr << "timer error\n";
62 }
63 createPSU(systemBus);
64 });
65 };
66
67 std::function<void(sdbusplus::message_t&)> eventCollect =
68 [&](sdbusplus::message_t& message) {
69 std::string objectName;
70 boost::container::flat_map<std::string, std::variant<bool>> values;
71 std::string path = message.get_path();
72 std::size_t slantingPos = path.find_last_of("/\\");
73 if ((slantingPos == std::string::npos) ||
74 ((slantingPos + 1) >= path.size()))
75 {
76 std::cerr << "Unable to get PSU state name from path\n";
77 return;
78 }
79 std::string statePSUName = path.substr(slantingPos + 1);
80
81 std::size_t hypenPos = statePSUName.find("_");
82 if (hypenPos == std::string::npos)
83 {
84 std::cerr << "Unable to get PSU name from PSU path\n";
85 return;
86 }
87 std::string psuName = statePSUName.substr(0, hypenPos);
88
89 try
90 {
91 message.read(objectName, values);
92 }
93 catch (const sdbusplus::exception_t& e)
94 {
95 std::cerr << "Failed to read message from PSU Event\n";
96 return;
97 }
98
99 for (auto& psu : powerSupplies)
100 {
101 if (psu->name != psuName)
102 {
103 continue;
104 }
105
106 std::string psuEventName = "functional";
107 auto findEvent = values.find(psuEventName);
108 if (findEvent != values.end())
109 {
110 bool* functional = std::get_if<bool>(&(findEvent->second));
111 if (functional == nullptr)
112 {
113 std::cerr << "Unable to get valid functional status\n";
114 continue;
115 }
116 if (*functional)
117 {
118 psu->state = CR::PSUState::normal;
119 }
120 else
121 {
122 psu->state = CR::PSUState::acLost;
123 }
124 }
125 }
126 };
127
128 using namespace sdbusplus::bus::match::rules;
129 for (const char* type : psuInterfaceTypes)
130 {
131 auto match = std::make_unique<sdbusplus::bus::match_t>(
132 static_cast<sdbusplus::bus_t&>(*systemBus),
133 type::signal() + member("PropertiesChanged") +
134 path_namespace(inventoryPath) + arg0namespace(type),
135 eventHandler);
136 matches.emplace_back(std::move(match));
137 }
138
139 for (const char* eventType : psuEventInterface)
140 {
141 auto eventMatch = std::make_unique<sdbusplus::bus::match_t>(
142 static_cast<sdbusplus::bus_t&>(*systemBus),
143 type::signal() + member("PropertiesChanged") +
144 path_namespace(eventPath) + arg0namespace(eventType),
145 eventCollect);
146 matches.emplace_back(std::move(eventMatch));
147 }
148 }
149
150 static const constexpr int psuDepth = 3;
151 // Check PSU information from entity-manager D-Bus interface and use the bus
152 // address to create PSU Class for cold redundancy.
createPSU(std::shared_ptr<sdbusplus::asio::connection> & conn)153 void ColdRedundancy::createPSU(
154 std::shared_ptr<sdbusplus::asio::connection>& conn)
155 {
156 numberOfPSU = 0;
157 powerSupplies.clear();
158
159 // call mapper to get matched obj paths
160 conn->async_method_call(
161 [this, &conn](const boost::system::error_code ec,
162 CR::GetSubTreeType subtree) {
163 if (ec)
164 {
165 std::cerr << "Exception happened when communicating to "
166 "ObjectMapper\n";
167 return;
168 }
169 for (const auto& object : subtree)
170 {
171 std::string pathName = object.first;
172 for (const auto& serviceIface : object.second)
173 {
174 std::string serviceName = serviceIface.first;
175 for (const auto& interface : serviceIface.second)
176 {
177 // only get property of matched interface
178 bool isIfaceMatched = false;
179 for (const auto& type : psuInterfaceTypes)
180 {
181 if (type == interface)
182 {
183 isIfaceMatched = true;
184 break;
185 }
186 }
187 if (!isIfaceMatched)
188 continue;
189
190 conn->async_method_call(
191 [this, &conn,
192 interface](const boost::system::error_code ec,
193 CR::PropertyMapType propMap) {
194 if (ec)
195 {
196 std::cerr
197 << "Exception happened when get all "
198 "properties\n";
199 return;
200 }
201
202 auto configName =
203 std::get_if<std::string>(&propMap["Name"]);
204 if (configName == nullptr)
205 {
206 std::cerr << "error finding necessary "
207 "entry in configuration\n";
208 return;
209 }
210
211 auto configBus =
212 std::get_if<uint64_t>(&propMap["Bus"]);
213 auto configAddress =
214 std::get_if<uint64_t>(&propMap["Address"]);
215
216 if (configBus == nullptr ||
217 configAddress == nullptr)
218 {
219 std::cerr << "error finding necessary "
220 "entry in configuration\n";
221 return;
222 }
223 for (auto& psu : powerSupplies)
224 {
225 if ((static_cast<uint8_t>(*configBus) ==
226 psu->bus) &&
227 (static_cast<uint8_t>(*configAddress) ==
228 psu->address))
229 {
230 return;
231 }
232 }
233
234 uint8_t order = 0;
235
236 powerSupplies.emplace_back(
237 std::make_unique<PowerSupply>(
238 *configName,
239 static_cast<uint8_t>(*configBus),
240 static_cast<uint8_t>(*configAddress),
241 order, conn));
242
243 numberOfPSU++;
244 std::vector<uint8_t> orders = {};
245 for (auto& psu : powerSupplies)
246 {
247 orders.push_back(psu->order);
248 }
249 },
250 serviceName.c_str(), pathName.c_str(),
251 "org.freedesktop.DBus.Properties", "GetAll",
252 interface);
253 }
254 }
255 }
256 },
257 "xyz.openbmc_project.ObjectMapper",
258 "/xyz/openbmc_project/object_mapper",
259 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
260 "/xyz/openbmc_project/inventory/system", psuDepth, psuInterfaceTypes);
261 }
262
PowerSupply(std::string & name,uint8_t bus,uint8_t address,uint8_t order,const std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)263 PowerSupply::PowerSupply(
264 std::string& name, uint8_t bus, uint8_t address, uint8_t order,
265 const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) :
266 name(name), bus(bus), address(address), order(order)
267 {
268 CR::getPSUEvent(dbusConnection, name, state);
269 }
270