xref: /openbmc/entity-manager/src/overlay.cpp (revision 2f2f4b87)
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
168     buildDevice(const std::string& name, const std::string& busPath,
169                 const std::string& parameters, uint64_t bus, uint64_t address,
170                 const std::string& constructor, const std::string& destructor,
171                 const devices::createsHWMon hasHWMonDir,
172                 std::vector<std::string> channelNames, const size_t retries = 5)
173 {
174     if (retries == 0U)
175     {
176         return -1;
177     }
178 
179     // If it's already instantiated, we don't need to create it again.
180     if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
181     {
182         // Try to create the device
183         createDevice(busPath, parameters, constructor);
184 
185         // If it didn't work, delete it and try again in 500ms
186         if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
187         {
188             deleteDevice(busPath, address, destructor);
189 
190             std::shared_ptr<boost::asio::steady_timer> createTimer =
191                 std::make_shared<boost::asio::steady_timer>(io);
192             createTimer->expires_after(std::chrono::milliseconds(500));
193             createTimer->async_wait(
194                 [createTimer, name, busPath, parameters, bus, address,
195                  constructor, destructor, hasHWMonDir,
196                  channelNames(std::move(channelNames)),
197                  retries](const boost::system::error_code& ec) mutable {
198                     if (ec)
199                     {
200                         std::cerr << "Timer error: " << ec << "\n";
201                         return -2;
202                     }
203                     return buildDevice(name, busPath, parameters, bus, address,
204                                        constructor, destructor, hasHWMonDir,
205                                        std::move(channelNames), retries - 1);
206                 });
207             return -1;
208         }
209     }
210 
211     // Link the mux channels if needed once the device is created.
212     if (!channelNames.empty())
213     {
214         linkMux(name, bus, address, channelNames);
215     }
216 
217     return 0;
218 }
219 
220 void exportDevice(const std::string& type,
221                   const devices::ExportTemplate& exportTemplate,
222                   const nlohmann::json& configuration)
223 {
224     std::string parameters = exportTemplate.parameters;
225     std::string busPath = exportTemplate.busPath;
226     std::string constructor = exportTemplate.add;
227     std::string destructor = exportTemplate.remove;
228     devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir;
229     std::string name = "unknown";
230     std::optional<uint64_t> bus;
231     std::optional<uint64_t> address;
232     std::vector<std::string> channels;
233 
234     for (auto keyPair = configuration.begin(); keyPair != configuration.end();
235          keyPair++)
236     {
237         std::string subsituteString;
238 
239         if (keyPair.key() == "Name" &&
240             keyPair.value().type() == nlohmann::json::value_t::string)
241         {
242             subsituteString = std::regex_replace(
243                 keyPair.value().get<std::string>(), illegalNameRegex, "_");
244             name = subsituteString;
245         }
246         else
247         {
248             subsituteString = jsonToString(keyPair.value());
249         }
250 
251         if (keyPair.key() == "Bus")
252         {
253             bus = keyPair.value().get<uint64_t>();
254         }
255         else if (keyPair.key() == "Address")
256         {
257             address = keyPair.value().get<uint64_t>();
258         }
259         else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux"))
260         {
261             channels = keyPair.value().get<std::vector<std::string>>();
262         }
263         boost::replace_all(parameters, templateChar + keyPair.key(),
264                            subsituteString);
265         boost::replace_all(busPath, templateChar + keyPair.key(),
266                            subsituteString);
267     }
268 
269     if (!bus || !address)
270     {
271         createDevice(busPath, parameters, constructor);
272         return;
273     }
274 
275     buildDevice(name, busPath, parameters, *bus, *address, constructor,
276                 destructor, hasHWMonDir, std::move(channels));
277 }
278 
279 bool loadOverlays(const nlohmann::json& systemConfiguration)
280 {
281     std::filesystem::create_directory(outputDir);
282     for (auto entity = systemConfiguration.begin();
283          entity != systemConfiguration.end(); entity++)
284     {
285         auto findExposes = entity.value().find("Exposes");
286         if (findExposes == entity.value().end() ||
287             findExposes->type() != nlohmann::json::value_t::array)
288         {
289             continue;
290         }
291 
292         for (const auto& configuration : *findExposes)
293         {
294             auto findStatus = configuration.find("Status");
295             // status missing is assumed to be 'okay'
296             if (findStatus != configuration.end() && *findStatus == "disabled")
297             {
298                 continue;
299             }
300             auto findType = configuration.find("Type");
301             if (findType == configuration.end() ||
302                 findType->type() != nlohmann::json::value_t::string)
303             {
304                 continue;
305             }
306             std::string type = findType.value().get<std::string>();
307             auto device = devices::exportTemplates.find(type.c_str());
308             if (device != devices::exportTemplates.end())
309             {
310                 exportDevice(type, device->second, configuration);
311                 continue;
312             }
313 
314             // Because many devices are intentionally not exportable,
315             // this error message is not printed in all situations.
316             // If wondering why your device not appearing, add your type to
317             // the exportTemplates array in the devices.hpp file.
318             if constexpr (debug)
319             {
320                 std::cerr << "Device type " << type
321                           << " not found in export map allowlist\n";
322             }
323         }
324     }
325 
326     return true;
327 }
328