blob: d18cf9d4ce2adb418ed72e58acc28bded1fc613d [file] [log] [blame]
James Feistc95cb142018-02-26 10:41:42 -08001/*
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*/
Brad Bishop1fb9f3f2020-08-28 08:15:13 -040016/// \file Overlay.cpp
James Feistc95cb142018-02-26 10:41:42 -080017
Patrick Venturea49dc332019-10-26 08:32:02 -070018#include "Overlay.hpp"
19
20#include "Utils.hpp"
21#include "devices.hpp"
22
James Feista465ccc2019-02-08 12:51:01 -080023#include <boost/algorithm/string/predicate.hpp>
Johnathan Mantey9b867872020-10-13 15:00:51 -070024#include <boost/asio/io_context.hpp>
25#include <boost/asio/steady_timer.hpp>
James Feista465ccc2019-02-08 12:51:01 -080026#include <boost/container/flat_map.hpp>
27#include <boost/container/flat_set.hpp>
28#include <boost/process/child.hpp>
James Feist8c505da2020-05-28 10:06:33 -070029#include <nlohmann/json.hpp>
30
James Feist637b3ef2019-04-15 16:35:30 -070031#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080032#include <iomanip>
33#include <iostream>
James Feista465ccc2019-02-08 12:51:01 -080034#include <regex>
35#include <string>
James Feistc95cb142018-02-26 10:41:42 -080036
James Feista465ccc2019-02-08 12:51:01 -080037constexpr const char* OUTPUT_DIR = "/tmp/overlays";
James Feista465ccc2019-02-08 12:51:01 -080038constexpr const char* TEMPLATE_CHAR = "$";
39constexpr const char* HEX_FORMAT_STR = "0x";
James Feista465ccc2019-02-08 12:51:01 -080040constexpr const char* I2C_DEVS_DIR = "/sys/bus/i2c/devices";
41constexpr const char* MUX_SYMLINK_DIR = "/dev/i2c-mux";
James Feistc95cb142018-02-26 10:41:42 -080042
Josh Lehan96b8a6e2019-10-09 14:39:49 -070043constexpr const bool DEBUG = false;
44
James Feistc95cb142018-02-26 10:41:42 -080045std::regex ILLEGAL_NAME_REGEX("[^A-Za-z0-9_]");
46
James Feistc95cb142018-02-26 10:41:42 -080047// helper function to make json types into string
James Feista465ccc2019-02-08 12:51:01 -080048std::string jsonToString(const nlohmann::json& in)
James Feistc95cb142018-02-26 10:41:42 -080049{
50 if (in.type() == nlohmann::json::value_t::string)
51 {
52 return in.get<std::string>();
53 }
54 else if (in.type() == nlohmann::json::value_t::array)
55 {
56 // remove brackets and comma from array
57 std::string array = in.dump();
58 array = array.substr(1, array.size() - 2);
59 boost::replace_all(array, ",", " ");
60 return array;
61 }
62 return in.dump();
63}
64
Ed Tanousee3357a2019-02-26 12:44:14 -080065void linkMux(const std::string& muxName, size_t busIndex, size_t address,
James Feista465ccc2019-02-08 12:51:01 -080066 const nlohmann::json::array_t& channelNames)
James Feistc95cb142018-02-26 10:41:42 -080067{
James Feist286babc2019-02-07 16:48:28 -080068 std::error_code ec;
Ed Tanousee3357a2019-02-26 12:44:14 -080069 std::filesystem::path muxSymlinkDir(MUX_SYMLINK_DIR);
70 std::filesystem::create_directory(muxSymlinkDir, ec);
71 // ignore error codes here if the directory already exists
72 ec.clear();
73 std::filesystem::path linkDir = muxSymlinkDir / muxName;
74 std::filesystem::create_directory(linkDir, ec);
75
76 std::ostringstream hexAddress;
77 hexAddress << std::hex << std::setfill('0') << std::setw(4) << address;
James Feist286babc2019-02-07 16:48:28 -080078
79 std::filesystem::path devDir(I2C_DEVS_DIR);
Ed Tanousee3357a2019-02-26 12:44:14 -080080 devDir /= std::to_string(busIndex) + "-" + hexAddress.str();
James Feist286babc2019-02-07 16:48:28 -080081
Ed Tanousee3357a2019-02-26 12:44:14 -080082 for (std::size_t channelIndex = 0; channelIndex < channelNames.size();
83 channelIndex++)
James Feist286babc2019-02-07 16:48:28 -080084 {
Ed Tanousee3357a2019-02-26 12:44:14 -080085 const std::string* channelName =
86 channelNames[channelIndex].get_ptr<const std::string*>();
87 if (channelName == nullptr)
88 {
89 continue;
90 }
91 if (channelName->empty())
92 {
93 continue;
94 }
95
96 std::filesystem::path channelPath =
97 devDir / ("channel-" + std::to_string(channelIndex));
98 if (!is_symlink(channelPath))
99 {
100 std::cerr << channelPath << "for mux channel " << *channelName
101 << " doesn't exist!\n";
102 continue;
103 }
104 std::filesystem::path bus = std::filesystem::read_symlink(channelPath);
105
106 std::filesystem::path fp("/dev" / bus.filename());
107 std::filesystem::path link(linkDir / *channelName);
108
109 std::filesystem::create_symlink(fp, link, ec);
110 if (ec)
111 {
112 std::cerr << "Failure creating symlink for " << fp << " to " << link
113 << "\n";
114 }
James Feistc95cb142018-02-26 10:41:42 -0800115 }
116}
117
Johnathan Mantey9b867872020-10-13 15:00:51 -0700118static int deleteDevice(const std::string& devicePath,
119 std::shared_ptr<uint64_t> address,
120 const std::string& destructor)
121{
122 if (!address)
123 {
124 return -1;
125 }
126 std::filesystem::path deviceDestructor(devicePath);
127 deviceDestructor /= destructor;
128 std::ofstream deviceFile(deviceDestructor);
129 if (!deviceFile.good())
130 {
131 std::cerr << "Error writing " << deviceDestructor << "\n";
132 return -1;
133 }
134 deviceFile << std::to_string(*address);
135 deviceFile.close();
136 return 0;
137}
138
139static int createDevice(const std::string& devicePath,
140 const std::string& parameters,
141 const std::string& constructor)
142{
143 std::filesystem::path deviceConstructor(devicePath);
144 deviceConstructor /= constructor;
145 std::ofstream deviceFile(deviceConstructor);
146 if (!deviceFile.good())
147 {
148 std::cerr << "Error writing " << deviceConstructor << "\n";
149 return -1;
150 }
151 deviceFile << parameters;
152 deviceFile.close();
153
154 return 0;
155}
156
157static bool deviceIsCreated(const std::string& devicePath,
158 std::shared_ptr<uint64_t> bus,
159 std::shared_ptr<uint64_t> address,
160 const bool retrying)
161{
162 if (!bus || !address)
163 {
164 return false;
165 }
166
167 std::ostringstream hex;
168 hex << std::hex << std::setw(4) << std::setfill('0') << *address;
169 std::string addressHex = hex.str();
170 std::string busStr = std::to_string(*bus);
171
Ed Tanous37e142b2021-04-30 12:19:26 -0700172 std::error_code ec;
173 auto path = std::filesystem::recursive_directory_iterator(devicePath, ec);
174 if (ec)
175 {
176 std::cerr << "Unable to open path " << devicePath << "\n";
177 return false;
178 }
179 for (; path != std::filesystem::recursive_directory_iterator(); path++)
Johnathan Mantey9b867872020-10-13 15:00:51 -0700180 {
181 if (!std::filesystem::is_directory(*path))
182 {
183 continue;
184 }
185
186 const std::string directoryName = path->path().filename();
187 if (directoryName == busStr + "-" + addressHex)
188 {
189 // The first time the BMC boots the kernel has creates a
190 // filesystem enumerating the I2C devices. The I2C device has not
191 // been initialized for use. This requires a call to a device
192 // node, such as "new_device". The first pass through this
193 // function is only confirming the filesystem contains the device
194 // entry of interest (i.e. i2c4-0050).
195 //
196 // An upper level function performs the device creation
197 // action. This action may fail. The device driver (dd) used to
198 // create the I2C filesystem substructure eats any error codes,
199 // and always returns 0. This is by design. It is also possible
200 // for the new_device action to fail because the device is not
201 // actually in the system, i.e. optional equipment.
202 //
203 // The 'retrying' pass of this function is used to confirm the
204 // 'dd' device driver succeeded. Success is measured by finding
205 // the 'hwmon' subdirectory in the filesystem. The first attempt
206 // is delayed by an arbitrary amount, in order to permit the
207 // kernel time to create the filesystem entries. The upper level
208 // function determines the number of times to retry calling this
209 // function.
210 if (retrying)
211 {
212 std::error_code ec;
213 std::filesystem::path hwmonDir(devicePath);
214 hwmonDir /= directoryName;
215 hwmonDir /= "hwmon";
216 return std::filesystem::is_directory(hwmonDir, ec);
217 }
218 return true;
219 }
220 else
221 {
222 path.disable_recursion_pending();
223 }
224 }
225 return false;
226}
227
228static int buildDevice(const std::string& devicePath,
229 const std::string& parameters,
230 std::shared_ptr<uint64_t> bus,
231 std::shared_ptr<uint64_t> address,
232 const std::string& constructor,
233 const std::string& destructor, const bool createsHWMon,
234 const size_t retries = 5)
235{
236 bool tryAgain = false;
237 if (!retries)
238 {
239 return -1;
240 }
241
242 if (!deviceIsCreated(devicePath, bus, address, false))
243 {
244 createDevice(devicePath, parameters, constructor);
245 tryAgain = true;
246 }
247 else if (createsHWMon && !deviceIsCreated(devicePath, bus, address, true))
248 {
249 // device is present, hwmon subdir missing
250 deleteDevice(devicePath, address, destructor);
251 tryAgain = true;
252 }
253
254 if (tryAgain)
255 {
256 std::shared_ptr<boost::asio::steady_timer> createTimer =
257 std::make_shared<boost::asio::steady_timer>(io);
258 createTimer->expires_after(std::chrono::milliseconds(500));
259 createTimer->async_wait([createTimer, devicePath, parameters, bus,
260 address, constructor, destructor, createsHWMon,
261 retries](const boost::system::error_code& ec) {
262 if (ec)
263 {
264 std::cerr << "Timer error: " << ec << "\n";
265 return -2;
266 }
267 return buildDevice(devicePath, parameters, bus, address,
268 constructor, destructor, createsHWMon,
269 retries - 1);
270 });
271 }
272 return 0;
273}
274
James Feista465ccc2019-02-08 12:51:01 -0800275void exportDevice(const std::string& type,
276 const devices::ExportTemplate& exportTemplate,
277 const nlohmann::json& configuration)
James Feist053a6642018-10-15 13:17:09 -0700278{
279
280 std::string parameters = exportTemplate.parameters;
Johnathan Mantey9b867872020-10-13 15:00:51 -0700281 std::string devicePath = exportTemplate.devicePath;
282 std::string constructor = exportTemplate.add;
283 std::string destructor = exportTemplate.remove;
284 bool createsHWMon = exportTemplate.createsHWMon;
James Feist053a6642018-10-15 13:17:09 -0700285 std::string name = "unknown";
Johnathan Mantey9b867872020-10-13 15:00:51 -0700286 std::shared_ptr<uint64_t> bus = nullptr;
287 std::shared_ptr<uint64_t> address = nullptr;
James Feista465ccc2019-02-08 12:51:01 -0800288 const nlohmann::json::array_t* channels = nullptr;
James Feist053a6642018-10-15 13:17:09 -0700289
290 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
291 keyPair++)
292 {
293 std::string subsituteString;
294
295 if (keyPair.key() == "Name" &&
296 keyPair.value().type() == nlohmann::json::value_t::string)
297 {
298 subsituteString = std::regex_replace(
299 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
300 name = subsituteString;
301 }
302 else
303 {
304 subsituteString = jsonToString(keyPair.value());
305 }
306
307 if (keyPair.key() == "Bus")
308 {
Johnathan Mantey9b867872020-10-13 15:00:51 -0700309 bus = std::make_shared<uint64_t>(
310 *keyPair.value().get_ptr<const uint64_t*>());
James Feist053a6642018-10-15 13:17:09 -0700311 }
312 else if (keyPair.key() == "Address")
313 {
Johnathan Mantey9b867872020-10-13 15:00:51 -0700314 address = std::make_shared<uint64_t>(
315 *keyPair.value().get_ptr<const uint64_t*>());
James Feist053a6642018-10-15 13:17:09 -0700316 }
James Feist286babc2019-02-07 16:48:28 -0800317 else if (keyPair.key() == "ChannelNames")
318 {
319 channels =
James Feista465ccc2019-02-08 12:51:01 -0800320 keyPair.value().get_ptr<const nlohmann::json::array_t*>();
James Feist286babc2019-02-07 16:48:28 -0800321 }
James Feist053a6642018-10-15 13:17:09 -0700322 boost::replace_all(parameters, TEMPLATE_CHAR + keyPair.key(),
323 subsituteString);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700324 boost::replace_all(devicePath, TEMPLATE_CHAR + keyPair.key(),
James Feist053a6642018-10-15 13:17:09 -0700325 subsituteString);
326 }
327
Johnathan Mantey9b867872020-10-13 15:00:51 -0700328 int err = buildDevice(devicePath, parameters, bus, address, constructor,
329 destructor, createsHWMon);
James Feist053a6642018-10-15 13:17:09 -0700330
Johnathan Mantey9b867872020-10-13 15:00:51 -0700331 if (!err && boost::ends_with(type, "Mux") && bus && address && channels)
James Feist286babc2019-02-07 16:48:28 -0800332 {
James Feist98132792019-07-09 13:29:09 -0700333 linkMux(name, static_cast<size_t>(*bus), static_cast<size_t>(*address),
334 *channels);
James Feist286babc2019-02-07 16:48:28 -0800335 }
James Feist053a6642018-10-15 13:17:09 -0700336}
337
James Feista465ccc2019-02-08 12:51:01 -0800338bool loadOverlays(const nlohmann::json& systemConfiguration)
James Feistc95cb142018-02-26 10:41:42 -0800339{
Ed Tanous072e25d2018-12-16 21:45:20 -0800340 std::filesystem::create_directory(OUTPUT_DIR);
James Feistc95cb142018-02-26 10:41:42 -0800341 for (auto entity = systemConfiguration.begin();
342 entity != systemConfiguration.end(); entity++)
343 {
James Feist1e3e6982018-08-03 16:09:28 -0700344 auto findExposes = entity.value().find("Exposes");
James Feistc95cb142018-02-26 10:41:42 -0800345 if (findExposes == entity.value().end() ||
346 findExposes->type() != nlohmann::json::value_t::array)
347 {
348 continue;
349 }
350
James Feista465ccc2019-02-08 12:51:01 -0800351 for (auto& configuration : *findExposes)
James Feistc95cb142018-02-26 10:41:42 -0800352 {
James Feist1e3e6982018-08-03 16:09:28 -0700353 auto findStatus = configuration.find("Status");
James Feistc95cb142018-02-26 10:41:42 -0800354 // status missing is assumed to be 'okay'
355 if (findStatus != configuration.end() && *findStatus == "disabled")
356 {
357 continue;
358 }
James Feistd63d18a2018-07-19 15:23:45 -0700359 auto findType = configuration.find("Type");
James Feistc95cb142018-02-26 10:41:42 -0800360 if (findType == configuration.end() ||
361 findType->type() != nlohmann::json::value_t::string)
362 {
363 continue;
364 }
365 std::string type = findType.value().get<std::string>();
James Feist053a6642018-10-15 13:17:09 -0700366 auto device = devices::exportTemplates.find(type.c_str());
367 if (device != devices::exportTemplates.end())
368 {
James Feist286babc2019-02-07 16:48:28 -0800369 exportDevice(type, device->second, configuration);
Josh Lehan96b8a6e2019-10-09 14:39:49 -0700370 continue;
371 }
372
373 // Because many devices are intentionally not exportable,
374 // this error message is not printed in all situations.
375 // If wondering why your device not appearing, add your type to
376 // the exportTemplates array in the devices.hpp file.
377 if constexpr (DEBUG)
378 {
379 std::cerr << "Device type " << type
380 << " not found in export map whitelist\n";
James Feist053a6642018-10-15 13:17:09 -0700381 }
James Feistc95cb142018-02-26 10:41:42 -0800382 }
383 }
384
385 return true;
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700386}