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