xref: /openbmc/entity-manager/src/entity_manager/overlay.cpp (revision 6f4c6b4e7cbc7401d47f2bd15f6bbaf2b85ac083)
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/algorithm/string/replace.hpp>
25 #include <boost/asio/io_context.hpp>
26 #include <boost/asio/steady_timer.hpp>
27 #include <boost/container/flat_map.hpp>
28 #include <boost/container/flat_set.hpp>
29 #include <nlohmann/json.hpp>
30 #include <phosphor-logging/lg2.hpp>
31 
32 #include <filesystem>
33 #include <fstream>
34 #include <iomanip>
35 #include <iostream>
36 #include <regex>
37 #include <string>
38 
39 constexpr const char* outputDir = "/tmp/overlays";
40 constexpr const char* templateChar = "$";
41 constexpr const char* i2CDevsDir = "/sys/bus/i2c/devices";
42 constexpr const char* muxSymlinkDir = "/dev/i2c-mux";
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(
168     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, boost::asio::io_context& io,
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)), retries,
198                  &io](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), io,
207                                        retries - 1);
208                 });
209             return -1;
210         }
211     }
212 
213     // Link the mux channels if needed once the device is created.
214     if (!channelNames.empty())
215     {
216         linkMux(name, bus, address, channelNames);
217     }
218 
219     return 0;
220 }
221 
222 void exportDevice(const std::string& type,
223                   const devices::ExportTemplate& exportTemplate,
224                   const nlohmann::json& configuration,
225                   boost::asio::io_context& io)
226 {
227     std::string parameters = exportTemplate.parameters;
228     std::string busPath = exportTemplate.busPath;
229     std::string constructor = exportTemplate.add;
230     std::string destructor = exportTemplate.remove;
231     devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir;
232     std::string name = "unknown";
233     std::optional<uint64_t> bus;
234     std::optional<uint64_t> address;
235     std::vector<std::string> channels;
236 
237     for (auto keyPair = configuration.begin(); keyPair != configuration.end();
238          keyPair++)
239     {
240         std::string subsituteString;
241 
242         if (keyPair.key() == "Name" &&
243             keyPair.value().type() == nlohmann::json::value_t::string)
244         {
245             subsituteString = std::regex_replace(
246                 keyPair.value().get<std::string>(), illegalNameRegex, "_");
247             name = subsituteString;
248         }
249         else
250         {
251             subsituteString = jsonToString(keyPair.value());
252         }
253 
254         if (keyPair.key() == "Bus")
255         {
256             bus = keyPair.value().get<uint64_t>();
257         }
258         else if (keyPair.key() == "Address")
259         {
260             address = keyPair.value().get<uint64_t>();
261         }
262         else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux"))
263         {
264             channels = keyPair.value().get<std::vector<std::string>>();
265         }
266         boost::replace_all(parameters, templateChar + keyPair.key(),
267                            subsituteString);
268         boost::replace_all(busPath, templateChar + keyPair.key(),
269                            subsituteString);
270     }
271 
272     if (!bus || !address)
273     {
274         createDevice(busPath, parameters, constructor);
275         return;
276     }
277 
278     buildDevice(name, busPath, parameters, *bus, *address, constructor,
279                 destructor, hasHWMonDir, std::move(channels), io);
280 }
281 
282 bool loadOverlays(const nlohmann::json& systemConfiguration,
283                   boost::asio::io_context& io)
284 {
285     std::filesystem::create_directory(outputDir);
286     for (auto entity = systemConfiguration.begin();
287          entity != systemConfiguration.end(); entity++)
288     {
289         auto findExposes = entity.value().find("Exposes");
290         if (findExposes == entity.value().end() ||
291             findExposes->type() != nlohmann::json::value_t::array)
292         {
293             continue;
294         }
295 
296         for (const auto& configuration : *findExposes)
297         {
298             auto findStatus = configuration.find("Status");
299             // status missing is assumed to be 'okay'
300             if (findStatus != configuration.end() && *findStatus == "disabled")
301             {
302                 continue;
303             }
304             auto findType = configuration.find("Type");
305             if (findType == configuration.end() ||
306                 findType->type() != nlohmann::json::value_t::string)
307             {
308                 continue;
309             }
310             std::string type = findType.value().get<std::string>();
311             auto device = devices::exportTemplates.find(type.c_str());
312             if (device != devices::exportTemplates.end())
313             {
314                 exportDevice(type, device->second, configuration, io);
315                 continue;
316             }
317 
318             // Because many devices are intentionally not exportable,
319             // this error message is not printed in all situations.
320             // If wondering why your device not appearing, add your type to
321             // the exportTemplates array in the devices.hpp file.
322             lg2::debug("Device type {TYPE} not found in export map allowlist",
323                        "TYPE", type);
324         }
325     }
326 
327     return true;
328 }
329