xref: /openbmc/entity-manager/src/entity_manager/overlay.cpp (revision 4e1142d6f418f48ea260132ebb5a4995b2310c90)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3 
4 #include "overlay.hpp"
5 
6 #include "devices.hpp"
7 #include "utils.hpp"
8 
9 #include <boost/algorithm/string/predicate.hpp>
10 #include <boost/algorithm/string/replace.hpp>
11 #include <boost/asio/io_context.hpp>
12 #include <boost/asio/steady_timer.hpp>
13 #include <boost/container/flat_map.hpp>
14 #include <boost/container/flat_set.hpp>
15 #include <nlohmann/json.hpp>
16 #include <phosphor-logging/lg2.hpp>
17 
18 #include <filesystem>
19 #include <fstream>
20 #include <iomanip>
21 #include <iostream>
22 #include <regex>
23 #include <string>
24 
25 constexpr const char* outputDir = "/tmp/overlays";
26 constexpr const char* templateChar = "$";
27 constexpr const char* i2CDevsDir = "/sys/bus/i2c/devices";
28 constexpr const char* muxSymlinkDir = "/dev/i2c-mux";
29 
30 const std::regex illegalNameRegex("[^A-Za-z0-9_]");
31 
32 // helper function to make json types into string
33 std::string jsonToString(const nlohmann::json& in)
34 {
35     if (in.type() == nlohmann::json::value_t::string)
36     {
37         return in.get<std::string>();
38     }
39     if (in.type() == nlohmann::json::value_t::array)
40     {
41         // remove brackets and comma from array
42         std::string array = in.dump();
43         array = array.substr(1, array.size() - 2);
44         boost::replace_all(array, ",", " ");
45         return array;
46     }
47     return in.dump();
48 }
49 
50 static std::string deviceDirName(uint64_t bus, uint64_t address)
51 {
52     std::ostringstream name;
53     name << bus << "-" << std::hex << std::setw(4) << std::setfill('0')
54          << address;
55     return name.str();
56 }
57 
58 void linkMux(const std::string& muxName, uint64_t busIndex, uint64_t address,
59              const std::vector<std::string>& channelNames)
60 {
61     std::error_code ec;
62     std::filesystem::path muxSymlinkDirPath(muxSymlinkDir);
63     std::filesystem::create_directory(muxSymlinkDirPath, ec);
64     // ignore error codes here if the directory already exists
65     ec.clear();
66     std::filesystem::path linkDir = muxSymlinkDirPath / muxName;
67     std::filesystem::create_directory(linkDir, ec);
68 
69     std::filesystem::path devDir(i2CDevsDir);
70     devDir /= deviceDirName(busIndex, address);
71 
72     for (std::size_t channelIndex = 0; channelIndex < channelNames.size();
73          channelIndex++)
74     {
75         const std::string& channelName = channelNames[channelIndex];
76         if (channelName.empty())
77         {
78             continue;
79         }
80 
81         std::filesystem::path channelPath =
82             devDir / ("channel-" + std::to_string(channelIndex));
83         if (!is_symlink(channelPath))
84         {
85             std::cerr << channelPath << " for mux channel " << channelName
86                       << " doesn't exist!\n";
87             continue;
88         }
89         std::filesystem::path bus = std::filesystem::read_symlink(channelPath);
90 
91         std::filesystem::path fp("/dev" / bus.filename());
92         std::filesystem::path link(linkDir / channelName);
93 
94         std::filesystem::create_symlink(fp, link, ec);
95         if (ec)
96         {
97             std::cerr << "Failure creating symlink for " << fp << " to " << link
98                       << "\n";
99         }
100     }
101 }
102 
103 static int deleteDevice(const std::string& busPath, uint64_t address,
104                         const std::string& destructor)
105 {
106     std::filesystem::path deviceDestructor(busPath);
107     deviceDestructor /= destructor;
108     std::ofstream deviceFile(deviceDestructor);
109     if (!deviceFile.good())
110     {
111         std::cerr << "Error writing " << deviceDestructor << "\n";
112         return -1;
113     }
114     deviceFile << std::to_string(address);
115     deviceFile.close();
116     return 0;
117 }
118 
119 static int createDevice(const std::string& busPath,
120                         const std::string& parameters,
121                         const std::string& constructor)
122 {
123     std::filesystem::path deviceConstructor(busPath);
124     deviceConstructor /= constructor;
125     std::ofstream deviceFile(deviceConstructor);
126     if (!deviceFile.good())
127     {
128         std::cerr << "Error writing " << deviceConstructor << "\n";
129         return -1;
130     }
131     deviceFile << parameters;
132     deviceFile.close();
133 
134     return 0;
135 }
136 
137 static bool deviceIsCreated(const std::string& busPath, uint64_t bus,
138                             uint64_t address,
139                             const devices::createsHWMon hasHWMonDir)
140 {
141     std::filesystem::path dirPath = busPath;
142     dirPath /= deviceDirName(bus, address);
143     if (hasHWMonDir == devices::createsHWMon::hasHWMonDir)
144     {
145         dirPath /= "hwmon";
146     }
147 
148     std::error_code ec;
149     // Ignore errors; anything but a clean 'true' is just fine as 'false'
150     return std::filesystem::exists(dirPath, ec);
151 }
152 
153 static int buildDevice(
154     const std::string& name, const std::string& busPath,
155     const std::string& parameters, uint64_t bus, uint64_t address,
156     const std::string& constructor, const std::string& destructor,
157     const devices::createsHWMon hasHWMonDir,
158     std::vector<std::string> channelNames, boost::asio::io_context& io,
159     const size_t retries = 5)
160 {
161     if (retries == 0U)
162     {
163         return -1;
164     }
165 
166     // If it's already instantiated, we don't need to create it again.
167     if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
168     {
169         // Try to create the device
170         createDevice(busPath, parameters, constructor);
171 
172         // If it didn't work, delete it and try again in 500ms
173         if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
174         {
175             deleteDevice(busPath, address, destructor);
176 
177             std::shared_ptr<boost::asio::steady_timer> createTimer =
178                 std::make_shared<boost::asio::steady_timer>(io);
179             createTimer->expires_after(std::chrono::milliseconds(500));
180             createTimer->async_wait(
181                 [createTimer, name, busPath, parameters, bus, address,
182                  constructor, destructor, hasHWMonDir,
183                  channelNames(std::move(channelNames)), retries,
184                  &io](const boost::system::error_code& ec) mutable {
185                     if (ec)
186                     {
187                         std::cerr << "Timer error: " << ec << "\n";
188                         return -2;
189                     }
190                     return buildDevice(name, busPath, parameters, bus, address,
191                                        constructor, destructor, hasHWMonDir,
192                                        std::move(channelNames), io,
193                                        retries - 1);
194                 });
195             return -1;
196         }
197     }
198 
199     // Link the mux channels if needed once the device is created.
200     if (!channelNames.empty())
201     {
202         linkMux(name, bus, address, channelNames);
203     }
204 
205     return 0;
206 }
207 
208 void exportDevice(const std::string& type,
209                   const devices::ExportTemplate& exportTemplate,
210                   const nlohmann::json& configuration,
211                   boost::asio::io_context& io)
212 {
213     std::string parameters = exportTemplate.parameters;
214     std::string busPath = exportTemplate.busPath;
215     std::string constructor = exportTemplate.add;
216     std::string destructor = exportTemplate.remove;
217     devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir;
218     std::string name = "unknown";
219     std::optional<uint64_t> bus;
220     std::optional<uint64_t> address;
221     std::vector<std::string> channels;
222 
223     for (auto keyPair = configuration.begin(); keyPair != configuration.end();
224          keyPair++)
225     {
226         std::string subsituteString;
227 
228         if (keyPair.key() == "Name" &&
229             keyPair.value().type() == nlohmann::json::value_t::string)
230         {
231             subsituteString = std::regex_replace(
232                 keyPair.value().get<std::string>(), illegalNameRegex, "_");
233             name = subsituteString;
234         }
235         else
236         {
237             subsituteString = jsonToString(keyPair.value());
238         }
239 
240         if (keyPair.key() == "Bus")
241         {
242             bus = keyPair.value().get<uint64_t>();
243         }
244         else if (keyPair.key() == "Address")
245         {
246             address = keyPair.value().get<uint64_t>();
247         }
248         else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux"))
249         {
250             channels = keyPair.value().get<std::vector<std::string>>();
251         }
252         boost::replace_all(parameters, templateChar + keyPair.key(),
253                            subsituteString);
254         boost::replace_all(busPath, templateChar + keyPair.key(),
255                            subsituteString);
256     }
257 
258     if (!bus || !address)
259     {
260         createDevice(busPath, parameters, constructor);
261         return;
262     }
263 
264     buildDevice(name, busPath, parameters, *bus, *address, constructor,
265                 destructor, hasHWMonDir, std::move(channels), io);
266 }
267 
268 bool loadOverlays(const nlohmann::json& systemConfiguration,
269                   boost::asio::io_context& io)
270 {
271     std::filesystem::create_directory(outputDir);
272     for (auto entity = systemConfiguration.begin();
273          entity != systemConfiguration.end(); entity++)
274     {
275         auto findExposes = entity.value().find("Exposes");
276         if (findExposes == entity.value().end() ||
277             findExposes->type() != nlohmann::json::value_t::array)
278         {
279             continue;
280         }
281 
282         for (const auto& configuration : *findExposes)
283         {
284             auto findStatus = configuration.find("Status");
285             // status missing is assumed to be 'okay'
286             if (findStatus != configuration.end() && *findStatus == "disabled")
287             {
288                 continue;
289             }
290             auto findType = configuration.find("Type");
291             if (findType == configuration.end() ||
292                 findType->type() != nlohmann::json::value_t::string)
293             {
294                 continue;
295             }
296             std::string type = findType.value().get<std::string>();
297             auto device = devices::exportTemplates.find(type.c_str());
298             if (device != devices::exportTemplates.end())
299             {
300                 exportDevice(type, device->second, configuration, io);
301                 continue;
302             }
303 
304             // Because many devices are intentionally not exportable,
305             // this error message is not printed in all situations.
306             // If wondering why your device not appearing, add your type to
307             // the exportTemplates array in the devices.hpp file.
308             lg2::debug("Device type {TYPE} not found in export map allowlist",
309                        "TYPE", type);
310         }
311     }
312 
313     return true;
314 }
315