blob: 1ced2d80e0ba6c72fd4a0b72bcd1ececf197d0f7 [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
172 for (auto path = std::filesystem::recursive_directory_iterator(devicePath);
173 path != std::filesystem::recursive_directory_iterator(); path++)
174 {
175 if (!std::filesystem::is_directory(*path))
176 {
177 continue;
178 }
179
180 const std::string directoryName = path->path().filename();
181 if (directoryName == busStr + "-" + addressHex)
182 {
183 // The first time the BMC boots the kernel has creates a
184 // filesystem enumerating the I2C devices. The I2C device has not
185 // been initialized for use. This requires a call to a device
186 // node, such as "new_device". The first pass through this
187 // function is only confirming the filesystem contains the device
188 // entry of interest (i.e. i2c4-0050).
189 //
190 // An upper level function performs the device creation
191 // action. This action may fail. The device driver (dd) used to
192 // create the I2C filesystem substructure eats any error codes,
193 // and always returns 0. This is by design. It is also possible
194 // for the new_device action to fail because the device is not
195 // actually in the system, i.e. optional equipment.
196 //
197 // The 'retrying' pass of this function is used to confirm the
198 // 'dd' device driver succeeded. Success is measured by finding
199 // the 'hwmon' subdirectory in the filesystem. The first attempt
200 // is delayed by an arbitrary amount, in order to permit the
201 // kernel time to create the filesystem entries. The upper level
202 // function determines the number of times to retry calling this
203 // function.
204 if (retrying)
205 {
206 std::error_code ec;
207 std::filesystem::path hwmonDir(devicePath);
208 hwmonDir /= directoryName;
209 hwmonDir /= "hwmon";
210 return std::filesystem::is_directory(hwmonDir, ec);
211 }
212 return true;
213 }
214 else
215 {
216 path.disable_recursion_pending();
217 }
218 }
219 return false;
220}
221
222static int buildDevice(const std::string& devicePath,
223 const std::string& parameters,
224 std::shared_ptr<uint64_t> bus,
225 std::shared_ptr<uint64_t> address,
226 const std::string& constructor,
227 const std::string& destructor, const bool createsHWMon,
228 const size_t retries = 5)
229{
230 bool tryAgain = false;
231 if (!retries)
232 {
233 return -1;
234 }
235
236 if (!deviceIsCreated(devicePath, bus, address, false))
237 {
238 createDevice(devicePath, parameters, constructor);
239 tryAgain = true;
240 }
241 else if (createsHWMon && !deviceIsCreated(devicePath, bus, address, true))
242 {
243 // device is present, hwmon subdir missing
244 deleteDevice(devicePath, address, destructor);
245 tryAgain = true;
246 }
247
248 if (tryAgain)
249 {
250 std::shared_ptr<boost::asio::steady_timer> createTimer =
251 std::make_shared<boost::asio::steady_timer>(io);
252 createTimer->expires_after(std::chrono::milliseconds(500));
253 createTimer->async_wait([createTimer, devicePath, parameters, bus,
254 address, constructor, destructor, createsHWMon,
255 retries](const boost::system::error_code& ec) {
256 if (ec)
257 {
258 std::cerr << "Timer error: " << ec << "\n";
259 return -2;
260 }
261 return buildDevice(devicePath, parameters, bus, address,
262 constructor, destructor, createsHWMon,
263 retries - 1);
264 });
265 }
266 return 0;
267}
268
James Feista465ccc2019-02-08 12:51:01 -0800269void exportDevice(const std::string& type,
270 const devices::ExportTemplate& exportTemplate,
271 const nlohmann::json& configuration)
James Feist053a6642018-10-15 13:17:09 -0700272{
273
274 std::string parameters = exportTemplate.parameters;
Johnathan Mantey9b867872020-10-13 15:00:51 -0700275 std::string devicePath = exportTemplate.devicePath;
276 std::string constructor = exportTemplate.add;
277 std::string destructor = exportTemplate.remove;
278 bool createsHWMon = exportTemplate.createsHWMon;
James Feist053a6642018-10-15 13:17:09 -0700279 std::string name = "unknown";
Johnathan Mantey9b867872020-10-13 15:00:51 -0700280 std::shared_ptr<uint64_t> bus = nullptr;
281 std::shared_ptr<uint64_t> address = nullptr;
James Feista465ccc2019-02-08 12:51:01 -0800282 const nlohmann::json::array_t* channels = nullptr;
James Feist053a6642018-10-15 13:17:09 -0700283
284 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
285 keyPair++)
286 {
287 std::string subsituteString;
288
289 if (keyPair.key() == "Name" &&
290 keyPair.value().type() == nlohmann::json::value_t::string)
291 {
292 subsituteString = std::regex_replace(
293 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
294 name = subsituteString;
295 }
296 else
297 {
298 subsituteString = jsonToString(keyPair.value());
299 }
300
301 if (keyPair.key() == "Bus")
302 {
Johnathan Mantey9b867872020-10-13 15:00:51 -0700303 bus = std::make_shared<uint64_t>(
304 *keyPair.value().get_ptr<const uint64_t*>());
James Feist053a6642018-10-15 13:17:09 -0700305 }
306 else if (keyPair.key() == "Address")
307 {
Johnathan Mantey9b867872020-10-13 15:00:51 -0700308 address = std::make_shared<uint64_t>(
309 *keyPair.value().get_ptr<const uint64_t*>());
James Feist053a6642018-10-15 13:17:09 -0700310 }
James Feist286babc2019-02-07 16:48:28 -0800311 else if (keyPair.key() == "ChannelNames")
312 {
313 channels =
James Feista465ccc2019-02-08 12:51:01 -0800314 keyPair.value().get_ptr<const nlohmann::json::array_t*>();
James Feist286babc2019-02-07 16:48:28 -0800315 }
James Feist053a6642018-10-15 13:17:09 -0700316 boost::replace_all(parameters, TEMPLATE_CHAR + keyPair.key(),
317 subsituteString);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700318 boost::replace_all(devicePath, TEMPLATE_CHAR + keyPair.key(),
James Feist053a6642018-10-15 13:17:09 -0700319 subsituteString);
320 }
321
Johnathan Mantey9b867872020-10-13 15:00:51 -0700322 int err = buildDevice(devicePath, parameters, bus, address, constructor,
323 destructor, createsHWMon);
James Feist053a6642018-10-15 13:17:09 -0700324
Johnathan Mantey9b867872020-10-13 15:00:51 -0700325 if (!err && boost::ends_with(type, "Mux") && bus && address && channels)
James Feist286babc2019-02-07 16:48:28 -0800326 {
James Feist98132792019-07-09 13:29:09 -0700327 linkMux(name, static_cast<size_t>(*bus), static_cast<size_t>(*address),
328 *channels);
James Feist286babc2019-02-07 16:48:28 -0800329 }
James Feist053a6642018-10-15 13:17:09 -0700330}
331
James Feista465ccc2019-02-08 12:51:01 -0800332bool loadOverlays(const nlohmann::json& systemConfiguration)
James Feistc95cb142018-02-26 10:41:42 -0800333{
Ed Tanous072e25d2018-12-16 21:45:20 -0800334 std::filesystem::create_directory(OUTPUT_DIR);
James Feistc95cb142018-02-26 10:41:42 -0800335 for (auto entity = systemConfiguration.begin();
336 entity != systemConfiguration.end(); entity++)
337 {
James Feist1e3e6982018-08-03 16:09:28 -0700338 auto findExposes = entity.value().find("Exposes");
James Feistc95cb142018-02-26 10:41:42 -0800339 if (findExposes == entity.value().end() ||
340 findExposes->type() != nlohmann::json::value_t::array)
341 {
342 continue;
343 }
344
James Feista465ccc2019-02-08 12:51:01 -0800345 for (auto& configuration : *findExposes)
James Feistc95cb142018-02-26 10:41:42 -0800346 {
James Feist1e3e6982018-08-03 16:09:28 -0700347 auto findStatus = configuration.find("Status");
James Feistc95cb142018-02-26 10:41:42 -0800348 // status missing is assumed to be 'okay'
349 if (findStatus != configuration.end() && *findStatus == "disabled")
350 {
351 continue;
352 }
James Feistd63d18a2018-07-19 15:23:45 -0700353 auto findType = configuration.find("Type");
James Feistc95cb142018-02-26 10:41:42 -0800354 if (findType == configuration.end() ||
355 findType->type() != nlohmann::json::value_t::string)
356 {
357 continue;
358 }
359 std::string type = findType.value().get<std::string>();
James Feist053a6642018-10-15 13:17:09 -0700360 auto device = devices::exportTemplates.find(type.c_str());
361 if (device != devices::exportTemplates.end())
362 {
James Feist286babc2019-02-07 16:48:28 -0800363 exportDevice(type, device->second, configuration);
Josh Lehan96b8a6e2019-10-09 14:39:49 -0700364 continue;
365 }
366
367 // Because many devices are intentionally not exportable,
368 // this error message is not printed in all situations.
369 // If wondering why your device not appearing, add your type to
370 // the exportTemplates array in the devices.hpp file.
371 if constexpr (DEBUG)
372 {
373 std::cerr << "Device type " << type
374 << " not found in export map whitelist\n";
James Feist053a6642018-10-15 13:17:09 -0700375 }
James Feistc95cb142018-02-26 10:41:42 -0800376 }
377 }
378
379 return true;
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700380}