blob: 5596ac375a8f0f380426f4de63e1b77eb581a080 [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
17#include <string>
18#include <iostream>
19#include <regex>
20#include <boost/process/child.hpp>
James Feist053a6642018-10-15 13:17:09 -070021#include <boost/algorithm/string/predicate.hpp>
Ed Tanous072e25d2018-12-16 21:45:20 -080022#include "filesystem.hpp"
James Feistc95cb142018-02-26 10:41:42 -080023#include <boost/container/flat_map.hpp>
24#include <boost/container/flat_set.hpp>
25#include <nlohmann/json.hpp>
James Feist053a6642018-10-15 13:17:09 -070026#include <devices.hpp>
James Feistc95cb142018-02-26 10:41:42 -080027#include <Overlay.hpp>
28#include <Utils.hpp>
29
30constexpr const char *DT_OVERLAY = "/usr/bin/dtoverlay";
31constexpr const char *DTC = "/usr/bin/dtc";
32constexpr const char *OUTPUT_DIR = "/tmp/overlays";
James Feistce4367c2018-10-16 09:19:57 -070033constexpr const char *TEMPLATE_DIR = PACKAGE_DIR "overlay_templates";
James Feistc95cb142018-02-26 10:41:42 -080034constexpr const char *TEMPLATE_CHAR = "$";
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -070035constexpr const char *HEX_FORMAT_STR = "0x";
James Feistc95cb142018-02-26 10:41:42 -080036constexpr const char *PLATFORM = "aspeed,ast2500";
37constexpr const char *I2C_DEVS_DIR = "/sys/class/i2c-dev";
38
39// some drivers need to be unbind / bind to load device tree changes
40static const boost::container::flat_map<std::string, std::string> FORCE_PROBES =
41 {{"IntelFanConnector", "/sys/bus/platform/drivers/aspeed_pwm_tacho"}};
42
43static const boost::container::flat_set<std::string> MUX_TYPES = {
44 {"PCA9543Mux"}, {"PCA9545Mux"}};
45
46std::regex ILLEGAL_NAME_REGEX("[^A-Za-z0-9_]");
47
48void createOverlay(const std::string &templatePath,
49 const nlohmann::json &configuration);
50
51void unloadAllOverlays(void)
52{
53 boost::process::child c(DT_OVERLAY, "-d", OUTPUT_DIR, "-R");
54 c.wait();
55}
56
57// this is hopefully temporary, but some drivers can't detect changes
58// without an unbind and bind so this function must exist for now
59void forceProbe(const std::string &driver)
60{
61 std::ofstream driverUnbind(driver + "/unbind");
62 std::ofstream driverBind(driver + "/bind");
63 if (!driverUnbind.is_open())
64 {
65 std::cerr << "force probe error opening " << driver << "\n";
66 return;
67 }
68 if (!driverBind.is_open())
69 {
70 driverUnbind.close();
71 std::cerr << "force probe error opening " << driver << "\n";
72 return;
73 }
74
Ed Tanous072e25d2018-12-16 21:45:20 -080075 std::filesystem::path pathObj(driver);
76 for (auto &p : std::filesystem::directory_iterator(pathObj))
James Feistc95cb142018-02-26 10:41:42 -080077 {
78 // symlinks are object names
79 if (is_symlink(p))
80 {
81 std::string driverName = p.path().filename();
82 driverUnbind << driverName;
83 driverBind << driverName;
84 break;
85 }
86 }
87 driverUnbind.close();
88 driverBind.close();
89}
90
91// helper function to make json types into string
92std::string jsonToString(const nlohmann::json &in)
93{
94 if (in.type() == nlohmann::json::value_t::string)
95 {
96 return in.get<std::string>();
97 }
98 else if (in.type() == nlohmann::json::value_t::array)
99 {
100 // remove brackets and comma from array
101 std::string array = in.dump();
102 array = array.substr(1, array.size() - 2);
103 boost::replace_all(array, ",", " ");
104 return array;
105 }
106 return in.dump();
107}
108
109// when muxes create new i2c devices, the symbols are not there to map the new
110// i2c devices to the mux address. this looks up the device tree path and adds
111// the new symbols so the new devices can be referenced via the phandle
Ed Tanous072e25d2018-12-16 21:45:20 -0800112void fixupSymbols(const std::vector<std::filesystem::path> &i2cDevsBefore)
James Feistc95cb142018-02-26 10:41:42 -0800113{
Ed Tanous072e25d2018-12-16 21:45:20 -0800114 std::vector<std::filesystem::path> i2cDevsAfter;
115 findFiles(std::filesystem::path(I2C_DEVS_DIR),
James Feista3c180a2018-08-09 16:06:04 -0700116 R"(i2c-\d+)", i2cDevsAfter);
James Feistc95cb142018-02-26 10:41:42 -0800117
118 for (const auto &dev : i2cDevsAfter)
119 {
120 if (std::find(i2cDevsBefore.begin(), i2cDevsBefore.end(), dev) !=
121 i2cDevsBefore.end())
122 {
123 continue;
124 }
125 // removes everything before and including the '-' in /path/i2c-##
126 std::string bus =
127 std::regex_replace(dev.string(), std::regex("^.*-"), "");
128 std::string devtreeRef = dev.string() + "/device/of_node";
Ed Tanous072e25d2018-12-16 21:45:20 -0800129 auto devtreePath = std::filesystem::path(devtreeRef);
130 std::string symbolPath = std::filesystem::canonical(devtreePath);
James Feistc95cb142018-02-26 10:41:42 -0800131 symbolPath =
132 symbolPath.substr(sizeof("/sys/firmware/devicetree/base") - 1);
James Feist1e3e6982018-08-03 16:09:28 -0700133 nlohmann::json configuration = {{"Path", symbolPath},
James Feistd63d18a2018-07-19 15:23:45 -0700134 {"Type", "Symbol"},
James Feist1e3e6982018-08-03 16:09:28 -0700135 {"Bus", std::stoul(bus)},
James Feistd63d18a2018-07-19 15:23:45 -0700136 {"Name", "i2c" + bus}};
James Feistc95cb142018-02-26 10:41:42 -0800137 createOverlay(TEMPLATE_DIR + std::string("/Symbol.template"),
138 configuration);
139 }
140}
141
James Feist053a6642018-10-15 13:17:09 -0700142void exportDevice(const devices::ExportTemplate &exportTemplate,
143 const nlohmann::json &configuration)
144{
145
146 std::string parameters = exportTemplate.parameters;
147 std::string device = exportTemplate.device;
148 std::string name = "unknown";
149 const uint64_t *bus = nullptr;
150 const uint64_t *address = nullptr;
151
152 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
153 keyPair++)
154 {
155 std::string subsituteString;
156
157 if (keyPair.key() == "Name" &&
158 keyPair.value().type() == nlohmann::json::value_t::string)
159 {
160 subsituteString = std::regex_replace(
161 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
162 name = subsituteString;
163 }
164 else
165 {
166 subsituteString = jsonToString(keyPair.value());
167 }
168
169 if (keyPair.key() == "Bus")
170 {
171 bus = keyPair.value().get_ptr<const uint64_t *>();
172 }
173 else if (keyPair.key() == "Address")
174 {
175 address = keyPair.value().get_ptr<const uint64_t *>();
176 }
177 boost::replace_all(parameters, TEMPLATE_CHAR + keyPair.key(),
178 subsituteString);
179 boost::replace_all(device, TEMPLATE_CHAR + keyPair.key(),
180 subsituteString);
181 }
182
183 // if we found bus and address we can attempt to prevent errors
184 if (bus != nullptr && address != nullptr)
185 {
186 std::ostringstream hex;
187 hex << std::hex << *address;
188 const std::string &addressHex = hex.str();
189 std::string busStr = std::to_string(*bus);
190
Ed Tanous072e25d2018-12-16 21:45:20 -0800191 std::filesystem::path devicePath(device);
James Feist053a6642018-10-15 13:17:09 -0700192 const std::string &dir = devicePath.parent_path().string();
Ed Tanous072e25d2018-12-16 21:45:20 -0800193 for (const auto &path : std::filesystem::directory_iterator(dir))
James Feist053a6642018-10-15 13:17:09 -0700194 {
Ed Tanous072e25d2018-12-16 21:45:20 -0800195 if (!std::filesystem::is_directory(path))
James Feist053a6642018-10-15 13:17:09 -0700196 {
197 continue;
198 }
199
200 const std::string &directoryName = path.path().filename();
201 if (boost::starts_with(directoryName, busStr) &&
202 boost::ends_with(directoryName, addressHex))
203 {
204 return; // already exported
205 }
206 }
207 }
208
209 std::ofstream deviceFile(device);
210 if (!deviceFile.good())
211 {
212 std::cerr << "Error writing " << device << "\n";
213 return;
214 }
215 deviceFile << parameters;
216 deviceFile.close();
217}
218
219// this is now deprecated
James Feistc95cb142018-02-26 10:41:42 -0800220void createOverlay(const std::string &templatePath,
221 const nlohmann::json &configuration)
222{
223 std::ifstream templateFile(templatePath);
224
225 if (!templateFile.is_open())
226 {
227 std::cerr << "createOverlay error opening " << templatePath << "\n";
228 return;
229 }
230 std::stringstream buff;
231 buff << templateFile.rdbuf();
232 std::string templateStr = buff.str();
233 std::string name = "unknown";
James Feistd63d18a2018-07-19 15:23:45 -0700234 std::string type = configuration["Type"];
James Feistc95cb142018-02-26 10:41:42 -0800235 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
236 keyPair++)
237 {
238 std::string subsituteString;
239
240 // device tree symbols are in decimal
James Feist1e3e6982018-08-03 16:09:28 -0700241 if (keyPair.key() == "Bus" &&
James Feistc95cb142018-02-26 10:41:42 -0800242 keyPair.value().type() == nlohmann::json::value_t::string)
243 {
244 unsigned int dec =
245 std::stoul(keyPair.value().get<std::string>(), nullptr, 16);
246 subsituteString = std::to_string(dec);
247 }
James Feistd63d18a2018-07-19 15:23:45 -0700248 else if (keyPair.key() == "Name" &&
James Feistc95cb142018-02-26 10:41:42 -0800249 keyPair.value().type() == nlohmann::json::value_t::string)
250 {
251 subsituteString = std::regex_replace(
252 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
253 name = subsituteString;
254 }
James Feist1e3e6982018-08-03 16:09:28 -0700255 else if (keyPair.key() == "Address")
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700256 {
257 if (keyPair.value().type() == nlohmann::json::value_t::string)
258 {
259 subsituteString = keyPair.value().get<std::string>();
260 subsituteString.erase(
261 0, subsituteString.find_first_not_of(HEX_FORMAT_STR));
262 }
263 else
264 {
265 std::ostringstream hex;
266 hex << std::hex << keyPair.value().get<unsigned int>();
267 subsituteString = hex.str();
268 }
269 }
James Feistc95cb142018-02-26 10:41:42 -0800270 else
271 {
272 subsituteString = jsonToString(keyPair.value());
273 }
274 boost::replace_all(templateStr, TEMPLATE_CHAR + keyPair.key(),
275 subsituteString);
276 }
277 // todo: this is a lame way to fill in platform, but we only
278 // care about ast2500 right now
James Feist1e3e6982018-08-03 16:09:28 -0700279 boost::replace_all(templateStr, TEMPLATE_CHAR + std::string("Platform"),
James Feistc95cb142018-02-26 10:41:42 -0800280 PLATFORM);
281 std::string dtsFilename =
282 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dts";
283 std::string dtboFilename =
284 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dtbo";
285
286 std::ofstream out(dtsFilename);
287 if (!out.is_open())
288 {
289 std::cerr << "createOverlay error opening " << dtsFilename << "\n";
290 return;
291 }
292 out << templateStr;
293 out.close();
294
James Feistc95cb142018-02-26 10:41:42 -0800295 // compile dtbo and load overlay
296 boost::process::child c1(DTC, "-@", "-q", "-I", "dts", "-O", "dtb", "-o",
297 dtboFilename, dtsFilename);
298 c1.wait();
299 if (c1.exit_code())
300 {
301 std::cerr << "DTC error with file " << dtsFilename << "\n";
302 return;
303 }
304 boost::process::child c2(DT_OVERLAY, "-d", OUTPUT_DIR, name + "_" + type);
305 c2.wait();
306 if (c2.exit_code())
307 {
308 std::cerr << "DTOverlay error with file " << dtboFilename << "\n";
309 return;
310 }
311 auto findForceProbe = FORCE_PROBES.find(type);
312 if (findForceProbe != FORCE_PROBES.end())
313 {
314 forceProbe(findForceProbe->second);
315 }
James Feistc95cb142018-02-26 10:41:42 -0800316}
317
318bool loadOverlays(const nlohmann::json &systemConfiguration)
319{
320
Ed Tanous072e25d2018-12-16 21:45:20 -0800321 std::vector<std::filesystem::path> paths;
322 if (!findFiles(std::filesystem::path(TEMPLATE_DIR),
James Feista3c180a2018-08-09 16:06:04 -0700323 R"(.*\.template)", paths))
James Feistc95cb142018-02-26 10:41:42 -0800324 {
325 std::cerr << "Unable to find any tempate files in " << TEMPLATE_DIR
326 << "\n";
327 return false;
328 }
329
Ed Tanous072e25d2018-12-16 21:45:20 -0800330 std::filesystem::create_directory(OUTPUT_DIR);
James Feistc95cb142018-02-26 10:41:42 -0800331 for (auto entity = systemConfiguration.begin();
332 entity != systemConfiguration.end(); entity++)
333 {
James Feist1e3e6982018-08-03 16:09:28 -0700334 auto findExposes = entity.value().find("Exposes");
James Feistc95cb142018-02-26 10:41:42 -0800335 if (findExposes == entity.value().end() ||
336 findExposes->type() != nlohmann::json::value_t::array)
337 {
338 continue;
339 }
340
341 for (auto &configuration : *findExposes)
342 {
James Feist1e3e6982018-08-03 16:09:28 -0700343 auto findStatus = configuration.find("Status");
James Feistc95cb142018-02-26 10:41:42 -0800344 // status missing is assumed to be 'okay'
345 if (findStatus != configuration.end() && *findStatus == "disabled")
346 {
347 continue;
348 }
James Feistd63d18a2018-07-19 15:23:45 -0700349 auto findType = configuration.find("Type");
James Feistc95cb142018-02-26 10:41:42 -0800350 if (findType == configuration.end() ||
351 findType->type() != nlohmann::json::value_t::string)
352 {
353 continue;
354 }
355 std::string type = findType.value().get<std::string>();
James Feistce4367c2018-10-16 09:19:57 -0700356#if OVERLAYS
357
James Feistc95cb142018-02-26 10:41:42 -0800358 std::string typeFile = type + std::string(".template");
James Feist053a6642018-10-15 13:17:09 -0700359 for (const auto &path : paths)
James Feistc95cb142018-02-26 10:41:42 -0800360 {
361 if (path.filename() != typeFile)
362 {
363 continue;
364 }
365 createOverlay(path.string(), configuration);
366 break;
367 }
James Feistce4367c2018-10-16 09:19:57 -0700368#endif
James Feist053a6642018-10-15 13:17:09 -0700369 auto device = devices::exportTemplates.find(type.c_str());
370 if (device != devices::exportTemplates.end())
371 {
372 exportDevice(device->second, configuration);
373 }
James Feistc95cb142018-02-26 10:41:42 -0800374 }
375 }
376
377 return true;
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700378}