blob: 8a9b906efbbda189c8155f96a10d0b6f232d8349 [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*/
16
Patrick Venturea49dc332019-10-26 08:32:02 -070017#include "Overlay.hpp"
18
19#include "Utils.hpp"
20#include "devices.hpp"
21
James Feista465ccc2019-02-08 12:51:01 -080022#include <boost/algorithm/string/predicate.hpp>
23#include <boost/container/flat_map.hpp>
24#include <boost/container/flat_set.hpp>
25#include <boost/process/child.hpp>
James Feist637b3ef2019-04-15 16:35:30 -070026#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080027#include <iomanip>
28#include <iostream>
29#include <nlohmann/json.hpp>
30#include <regex>
31#include <string>
James Feistc95cb142018-02-26 10:41:42 -080032
James Feista465ccc2019-02-08 12:51:01 -080033constexpr const char* DT_OVERLAY = "/usr/bin/dtoverlay";
34constexpr const char* DTC = "/usr/bin/dtc";
35constexpr const char* OUTPUT_DIR = "/tmp/overlays";
36constexpr const char* TEMPLATE_DIR = PACKAGE_DIR "overlay_templates";
37constexpr const char* TEMPLATE_CHAR = "$";
38constexpr const char* HEX_FORMAT_STR = "0x";
39constexpr const char* PLATFORM = "aspeed,ast2500";
40constexpr 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 -080045// some drivers need to be unbind / bind to load device tree changes
46static const boost::container::flat_map<std::string, std::string> FORCE_PROBES =
47 {{"IntelFanConnector", "/sys/bus/platform/drivers/aspeed_pwm_tacho"}};
48
James Feistc95cb142018-02-26 10:41:42 -080049std::regex ILLEGAL_NAME_REGEX("[^A-Za-z0-9_]");
50
James Feista465ccc2019-02-08 12:51:01 -080051void createOverlay(const std::string& templatePath,
52 const nlohmann::json& configuration);
James Feistc95cb142018-02-26 10:41:42 -080053
54void unloadAllOverlays(void)
55{
56 boost::process::child c(DT_OVERLAY, "-d", OUTPUT_DIR, "-R");
57 c.wait();
58}
59
60// this is hopefully temporary, but some drivers can't detect changes
61// without an unbind and bind so this function must exist for now
James Feista465ccc2019-02-08 12:51:01 -080062void forceProbe(const std::string& driver)
James Feistc95cb142018-02-26 10:41:42 -080063{
64 std::ofstream driverUnbind(driver + "/unbind");
65 std::ofstream driverBind(driver + "/bind");
66 if (!driverUnbind.is_open())
67 {
68 std::cerr << "force probe error opening " << driver << "\n";
69 return;
70 }
71 if (!driverBind.is_open())
72 {
73 driverUnbind.close();
74 std::cerr << "force probe error opening " << driver << "\n";
75 return;
76 }
77
Ed Tanous072e25d2018-12-16 21:45:20 -080078 std::filesystem::path pathObj(driver);
James Feista465ccc2019-02-08 12:51:01 -080079 for (auto& p : std::filesystem::directory_iterator(pathObj))
James Feistc95cb142018-02-26 10:41:42 -080080 {
81 // symlinks are object names
82 if (is_symlink(p))
83 {
84 std::string driverName = p.path().filename();
85 driverUnbind << driverName;
86 driverBind << driverName;
87 break;
88 }
89 }
90 driverUnbind.close();
91 driverBind.close();
92}
93
94// helper function to make json types into string
James Feista465ccc2019-02-08 12:51:01 -080095std::string jsonToString(const nlohmann::json& in)
James Feistc95cb142018-02-26 10:41:42 -080096{
97 if (in.type() == nlohmann::json::value_t::string)
98 {
99 return in.get<std::string>();
100 }
101 else if (in.type() == nlohmann::json::value_t::array)
102 {
103 // remove brackets and comma from array
104 std::string array = in.dump();
105 array = array.substr(1, array.size() - 2);
106 boost::replace_all(array, ",", " ");
107 return array;
108 }
109 return in.dump();
110}
111
Ed Tanousee3357a2019-02-26 12:44:14 -0800112void linkMux(const std::string& muxName, size_t busIndex, size_t address,
James Feista465ccc2019-02-08 12:51:01 -0800113 const nlohmann::json::array_t& channelNames)
James Feistc95cb142018-02-26 10:41:42 -0800114{
James Feist286babc2019-02-07 16:48:28 -0800115 std::error_code ec;
Ed Tanousee3357a2019-02-26 12:44:14 -0800116 std::filesystem::path muxSymlinkDir(MUX_SYMLINK_DIR);
117 std::filesystem::create_directory(muxSymlinkDir, ec);
118 // ignore error codes here if the directory already exists
119 ec.clear();
120 std::filesystem::path linkDir = muxSymlinkDir / muxName;
121 std::filesystem::create_directory(linkDir, ec);
122
123 std::ostringstream hexAddress;
124 hexAddress << std::hex << std::setfill('0') << std::setw(4) << address;
James Feist286babc2019-02-07 16:48:28 -0800125
126 std::filesystem::path devDir(I2C_DEVS_DIR);
Ed Tanousee3357a2019-02-26 12:44:14 -0800127 devDir /= std::to_string(busIndex) + "-" + hexAddress.str();
James Feist286babc2019-02-07 16:48:28 -0800128
Ed Tanousee3357a2019-02-26 12:44:14 -0800129 for (std::size_t channelIndex = 0; channelIndex < channelNames.size();
130 channelIndex++)
James Feist286babc2019-02-07 16:48:28 -0800131 {
Ed Tanousee3357a2019-02-26 12:44:14 -0800132 const std::string* channelName =
133 channelNames[channelIndex].get_ptr<const std::string*>();
134 if (channelName == nullptr)
135 {
136 continue;
137 }
138 if (channelName->empty())
139 {
140 continue;
141 }
142
143 std::filesystem::path channelPath =
144 devDir / ("channel-" + std::to_string(channelIndex));
145 if (!is_symlink(channelPath))
146 {
147 std::cerr << channelPath << "for mux channel " << *channelName
148 << " doesn't exist!\n";
149 continue;
150 }
151 std::filesystem::path bus = std::filesystem::read_symlink(channelPath);
152
153 std::filesystem::path fp("/dev" / bus.filename());
154 std::filesystem::path link(linkDir / *channelName);
155
156 std::filesystem::create_symlink(fp, link, ec);
157 if (ec)
158 {
159 std::cerr << "Failure creating symlink for " << fp << " to " << link
160 << "\n";
161 }
James Feistc95cb142018-02-26 10:41:42 -0800162 }
163}
164
James Feista465ccc2019-02-08 12:51:01 -0800165void exportDevice(const std::string& type,
166 const devices::ExportTemplate& exportTemplate,
167 const nlohmann::json& configuration)
James Feist053a6642018-10-15 13:17:09 -0700168{
169
170 std::string parameters = exportTemplate.parameters;
171 std::string device = exportTemplate.device;
172 std::string name = "unknown";
James Feista465ccc2019-02-08 12:51:01 -0800173 const uint64_t* bus = nullptr;
174 const uint64_t* address = nullptr;
175 const nlohmann::json::array_t* channels = nullptr;
James Feist053a6642018-10-15 13:17:09 -0700176
177 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
178 keyPair++)
179 {
180 std::string subsituteString;
181
182 if (keyPair.key() == "Name" &&
183 keyPair.value().type() == nlohmann::json::value_t::string)
184 {
185 subsituteString = std::regex_replace(
186 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
187 name = subsituteString;
188 }
189 else
190 {
191 subsituteString = jsonToString(keyPair.value());
192 }
193
194 if (keyPair.key() == "Bus")
195 {
James Feista465ccc2019-02-08 12:51:01 -0800196 bus = keyPair.value().get_ptr<const uint64_t*>();
James Feist053a6642018-10-15 13:17:09 -0700197 }
198 else if (keyPair.key() == "Address")
199 {
James Feista465ccc2019-02-08 12:51:01 -0800200 address = keyPair.value().get_ptr<const uint64_t*>();
James Feist053a6642018-10-15 13:17:09 -0700201 }
James Feist286babc2019-02-07 16:48:28 -0800202 else if (keyPair.key() == "ChannelNames")
203 {
204 channels =
James Feista465ccc2019-02-08 12:51:01 -0800205 keyPair.value().get_ptr<const nlohmann::json::array_t*>();
James Feist286babc2019-02-07 16:48:28 -0800206 }
James Feist053a6642018-10-15 13:17:09 -0700207 boost::replace_all(parameters, TEMPLATE_CHAR + keyPair.key(),
208 subsituteString);
209 boost::replace_all(device, TEMPLATE_CHAR + keyPair.key(),
210 subsituteString);
211 }
212
213 // if we found bus and address we can attempt to prevent errors
214 if (bus != nullptr && address != nullptr)
215 {
216 std::ostringstream hex;
217 hex << std::hex << *address;
James Feista465ccc2019-02-08 12:51:01 -0800218 const std::string& addressHex = hex.str();
James Feist053a6642018-10-15 13:17:09 -0700219 std::string busStr = std::to_string(*bus);
220
Ed Tanous072e25d2018-12-16 21:45:20 -0800221 std::filesystem::path devicePath(device);
James Feist58b9c262019-02-12 13:38:45 -0800222 std::filesystem::path parentPath = devicePath.parent_path();
223 if (std::filesystem::is_directory(parentPath))
James Feist053a6642018-10-15 13:17:09 -0700224 {
James Feist58b9c262019-02-12 13:38:45 -0800225 for (const auto& path :
226 std::filesystem::directory_iterator(parentPath))
James Feist053a6642018-10-15 13:17:09 -0700227 {
James Feist58b9c262019-02-12 13:38:45 -0800228 if (!std::filesystem::is_directory(path))
229 {
230 continue;
231 }
James Feist053a6642018-10-15 13:17:09 -0700232
James Feist58b9c262019-02-12 13:38:45 -0800233 const std::string& directoryName = path.path().filename();
234 if (boost::starts_with(directoryName, busStr) &&
235 boost::ends_with(directoryName, addressHex))
236 {
237 return; // already exported
238 }
James Feist053a6642018-10-15 13:17:09 -0700239 }
240 }
241 }
242
243 std::ofstream deviceFile(device);
244 if (!deviceFile.good())
245 {
246 std::cerr << "Error writing " << device << "\n";
247 return;
248 }
249 deviceFile << parameters;
250 deviceFile.close();
James Feist286babc2019-02-07 16:48:28 -0800251 if (boost::ends_with(type, "Mux") && bus && address && channels)
252 {
James Feist98132792019-07-09 13:29:09 -0700253 linkMux(name, static_cast<size_t>(*bus), static_cast<size_t>(*address),
254 *channels);
James Feist286babc2019-02-07 16:48:28 -0800255 }
James Feist053a6642018-10-15 13:17:09 -0700256}
257
258// this is now deprecated
James Feista465ccc2019-02-08 12:51:01 -0800259void createOverlay(const std::string& templatePath,
260 const nlohmann::json& configuration)
James Feistc95cb142018-02-26 10:41:42 -0800261{
262 std::ifstream templateFile(templatePath);
263
264 if (!templateFile.is_open())
265 {
266 std::cerr << "createOverlay error opening " << templatePath << "\n";
267 return;
268 }
269 std::stringstream buff;
270 buff << templateFile.rdbuf();
271 std::string templateStr = buff.str();
272 std::string name = "unknown";
James Feistd63d18a2018-07-19 15:23:45 -0700273 std::string type = configuration["Type"];
James Feistc95cb142018-02-26 10:41:42 -0800274 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
275 keyPair++)
276 {
277 std::string subsituteString;
278
279 // device tree symbols are in decimal
James Feist1e3e6982018-08-03 16:09:28 -0700280 if (keyPair.key() == "Bus" &&
James Feistc95cb142018-02-26 10:41:42 -0800281 keyPair.value().type() == nlohmann::json::value_t::string)
282 {
James Feist98132792019-07-09 13:29:09 -0700283 long unsigned int dec =
James Feistc95cb142018-02-26 10:41:42 -0800284 std::stoul(keyPair.value().get<std::string>(), nullptr, 16);
285 subsituteString = std::to_string(dec);
286 }
James Feistd63d18a2018-07-19 15:23:45 -0700287 else if (keyPair.key() == "Name" &&
James Feistc95cb142018-02-26 10:41:42 -0800288 keyPair.value().type() == nlohmann::json::value_t::string)
289 {
290 subsituteString = std::regex_replace(
291 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
292 name = subsituteString;
293 }
James Feist1e3e6982018-08-03 16:09:28 -0700294 else if (keyPair.key() == "Address")
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700295 {
296 if (keyPair.value().type() == nlohmann::json::value_t::string)
297 {
298 subsituteString = keyPair.value().get<std::string>();
299 subsituteString.erase(
300 0, subsituteString.find_first_not_of(HEX_FORMAT_STR));
301 }
302 else
303 {
304 std::ostringstream hex;
305 hex << std::hex << keyPair.value().get<unsigned int>();
306 subsituteString = hex.str();
307 }
308 }
James Feistc95cb142018-02-26 10:41:42 -0800309 else
310 {
311 subsituteString = jsonToString(keyPair.value());
312 }
313 boost::replace_all(templateStr, TEMPLATE_CHAR + keyPair.key(),
314 subsituteString);
315 }
316 // todo: this is a lame way to fill in platform, but we only
317 // care about ast2500 right now
James Feist1e3e6982018-08-03 16:09:28 -0700318 boost::replace_all(templateStr, TEMPLATE_CHAR + std::string("Platform"),
James Feistc95cb142018-02-26 10:41:42 -0800319 PLATFORM);
320 std::string dtsFilename =
321 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dts";
322 std::string dtboFilename =
323 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dtbo";
324
325 std::ofstream out(dtsFilename);
326 if (!out.is_open())
327 {
328 std::cerr << "createOverlay error opening " << dtsFilename << "\n";
329 return;
330 }
331 out << templateStr;
332 out.close();
333
James Feistc95cb142018-02-26 10:41:42 -0800334 // compile dtbo and load overlay
335 boost::process::child c1(DTC, "-@", "-q", "-I", "dts", "-O", "dtb", "-o",
336 dtboFilename, dtsFilename);
337 c1.wait();
338 if (c1.exit_code())
339 {
340 std::cerr << "DTC error with file " << dtsFilename << "\n";
341 return;
342 }
343 boost::process::child c2(DT_OVERLAY, "-d", OUTPUT_DIR, name + "_" + type);
344 c2.wait();
345 if (c2.exit_code())
346 {
347 std::cerr << "DTOverlay error with file " << dtboFilename << "\n";
348 return;
349 }
350 auto findForceProbe = FORCE_PROBES.find(type);
351 if (findForceProbe != FORCE_PROBES.end())
352 {
353 forceProbe(findForceProbe->second);
354 }
James Feistc95cb142018-02-26 10:41:42 -0800355}
356
James Feista465ccc2019-02-08 12:51:01 -0800357bool loadOverlays(const nlohmann::json& systemConfiguration)
James Feistc95cb142018-02-26 10:41:42 -0800358{
359
Ed Tanous072e25d2018-12-16 21:45:20 -0800360 std::vector<std::filesystem::path> paths;
James Feistf861da82019-06-27 12:05:16 -0700361 if (!findFiles(std::filesystem::path(TEMPLATE_DIR), R"(.*\.template)",
362 paths))
James Feistc95cb142018-02-26 10:41:42 -0800363 {
Josh Lehane5cfe642019-10-09 14:21:35 -0700364 std::cerr << "Unable to find any template files in " << TEMPLATE_DIR
James Feistc95cb142018-02-26 10:41:42 -0800365 << "\n";
366 return false;
367 }
368
Ed Tanous072e25d2018-12-16 21:45:20 -0800369 std::filesystem::create_directory(OUTPUT_DIR);
James Feistc95cb142018-02-26 10:41:42 -0800370 for (auto entity = systemConfiguration.begin();
371 entity != systemConfiguration.end(); entity++)
372 {
James Feist1e3e6982018-08-03 16:09:28 -0700373 auto findExposes = entity.value().find("Exposes");
James Feistc95cb142018-02-26 10:41:42 -0800374 if (findExposes == entity.value().end() ||
375 findExposes->type() != nlohmann::json::value_t::array)
376 {
377 continue;
378 }
379
James Feista465ccc2019-02-08 12:51:01 -0800380 for (auto& configuration : *findExposes)
James Feistc95cb142018-02-26 10:41:42 -0800381 {
James Feist1e3e6982018-08-03 16:09:28 -0700382 auto findStatus = configuration.find("Status");
James Feistc95cb142018-02-26 10:41:42 -0800383 // status missing is assumed to be 'okay'
384 if (findStatus != configuration.end() && *findStatus == "disabled")
385 {
386 continue;
387 }
James Feistd63d18a2018-07-19 15:23:45 -0700388 auto findType = configuration.find("Type");
James Feistc95cb142018-02-26 10:41:42 -0800389 if (findType == configuration.end() ||
390 findType->type() != nlohmann::json::value_t::string)
391 {
392 continue;
393 }
394 std::string type = findType.value().get<std::string>();
James Feistce4367c2018-10-16 09:19:57 -0700395#if OVERLAYS
396
James Feistc95cb142018-02-26 10:41:42 -0800397 std::string typeFile = type + std::string(".template");
James Feista465ccc2019-02-08 12:51:01 -0800398 for (const auto& path : paths)
James Feistc95cb142018-02-26 10:41:42 -0800399 {
400 if (path.filename() != typeFile)
401 {
402 continue;
403 }
404 createOverlay(path.string(), configuration);
405 break;
406 }
James Feistce4367c2018-10-16 09:19:57 -0700407#endif
James Feist053a6642018-10-15 13:17:09 -0700408 auto device = devices::exportTemplates.find(type.c_str());
409 if (device != devices::exportTemplates.end())
410 {
James Feist286babc2019-02-07 16:48:28 -0800411 exportDevice(type, device->second, configuration);
Josh Lehan96b8a6e2019-10-09 14:39:49 -0700412 continue;
413 }
414
415 // Because many devices are intentionally not exportable,
416 // this error message is not printed in all situations.
417 // If wondering why your device not appearing, add your type to
418 // the exportTemplates array in the devices.hpp file.
419 if constexpr (DEBUG)
420 {
421 std::cerr << "Device type " << type
422 << " not found in export map whitelist\n";
James Feist053a6642018-10-15 13:17:09 -0700423 }
James Feistc95cb142018-02-26 10:41:42 -0800424 }
425 }
426
427 return true;
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700428}