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