blob: 7df8908b0040813fb191f0774e4add38137d877e [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>
21#include <experimental/filesystem>
22#include <boost/container/flat_map.hpp>
23#include <boost/container/flat_set.hpp>
24#include <nlohmann/json.hpp>
25#include <Overlay.hpp>
26#include <Utils.hpp>
27
28constexpr const char *DT_OVERLAY = "/usr/bin/dtoverlay";
29constexpr const char *DTC = "/usr/bin/dtc";
30constexpr const char *OUTPUT_DIR = "/tmp/overlays";
31constexpr const char *TEMPLATE_DIR = "/usr/share/overlay_templates";
32constexpr const char *TEMPLATE_CHAR = "$";
33constexpr const char *PLATFORM = "aspeed,ast2500";
34constexpr const char *I2C_DEVS_DIR = "/sys/class/i2c-dev";
35
36// some drivers need to be unbind / bind to load device tree changes
37static const boost::container::flat_map<std::string, std::string> FORCE_PROBES =
38 {{"IntelFanConnector", "/sys/bus/platform/drivers/aspeed_pwm_tacho"}};
39
40static const boost::container::flat_set<std::string> MUX_TYPES = {
41 {"PCA9543Mux"}, {"PCA9545Mux"}};
42
43std::regex ILLEGAL_NAME_REGEX("[^A-Za-z0-9_]");
44
45void createOverlay(const std::string &templatePath,
46 const nlohmann::json &configuration);
47
48void unloadAllOverlays(void)
49{
50 boost::process::child c(DT_OVERLAY, "-d", OUTPUT_DIR, "-R");
51 c.wait();
52}
53
54// this is hopefully temporary, but some drivers can't detect changes
55// without an unbind and bind so this function must exist for now
56void forceProbe(const std::string &driver)
57{
58 std::ofstream driverUnbind(driver + "/unbind");
59 std::ofstream driverBind(driver + "/bind");
60 if (!driverUnbind.is_open())
61 {
62 std::cerr << "force probe error opening " << driver << "\n";
63 return;
64 }
65 if (!driverBind.is_open())
66 {
67 driverUnbind.close();
68 std::cerr << "force probe error opening " << driver << "\n";
69 return;
70 }
71
72 std::experimental::filesystem::path pathObj(driver);
73 for (auto &p : std::experimental::filesystem::directory_iterator(pathObj))
74 {
75 // symlinks are object names
76 if (is_symlink(p))
77 {
78 std::string driverName = p.path().filename();
79 driverUnbind << driverName;
80 driverBind << driverName;
81 break;
82 }
83 }
84 driverUnbind.close();
85 driverBind.close();
86}
87
88// helper function to make json types into string
89std::string jsonToString(const nlohmann::json &in)
90{
91 if (in.type() == nlohmann::json::value_t::string)
92 {
93 return in.get<std::string>();
94 }
95 else if (in.type() == nlohmann::json::value_t::array)
96 {
97 // remove brackets and comma from array
98 std::string array = in.dump();
99 array = array.substr(1, array.size() - 2);
100 boost::replace_all(array, ",", " ");
101 return array;
102 }
103 return in.dump();
104}
105
106// when muxes create new i2c devices, the symbols are not there to map the new
107// i2c devices to the mux address. this looks up the device tree path and adds
108// the new symbols so the new devices can be referenced via the phandle
109void fixupSymbols(
110 const std::vector<std::experimental::filesystem::path> &i2cDevsBefore)
111{
112 std::vector<std::experimental::filesystem::path> i2cDevsAfter;
113 find_files(std::experimental::filesystem::path(I2C_DEVS_DIR),
114 R"(i2c-\d+)", i2cDevsAfter, 0);
115
116 for (const auto &dev : i2cDevsAfter)
117 {
118 if (std::find(i2cDevsBefore.begin(), i2cDevsBefore.end(), dev) !=
119 i2cDevsBefore.end())
120 {
121 continue;
122 }
123 // removes everything before and including the '-' in /path/i2c-##
124 std::string bus =
125 std::regex_replace(dev.string(), std::regex("^.*-"), "");
126 std::string devtreeRef = dev.string() + "/device/of_node";
127 auto devtreePath = std::experimental::filesystem::path(devtreeRef);
128 std::string symbolPath =
129 std::experimental::filesystem::canonical(devtreePath);
130 symbolPath =
131 symbolPath.substr(sizeof("/sys/firmware/devicetree/base") - 1);
132 nlohmann::json configuration = {{"path", symbolPath},
133 {"type", "Symbol"},
134 {"bus", std::stoul(bus)},
135 {"name", "i2c" + bus}};
136 createOverlay(TEMPLATE_DIR + std::string("/Symbol.template"),
137 configuration);
138 }
139}
140
141void createOverlay(const std::string &templatePath,
142 const nlohmann::json &configuration)
143{
144 std::ifstream templateFile(templatePath);
145
146 if (!templateFile.is_open())
147 {
148 std::cerr << "createOverlay error opening " << templatePath << "\n";
149 return;
150 }
151 std::stringstream buff;
152 buff << templateFile.rdbuf();
153 std::string templateStr = buff.str();
154 std::string name = "unknown";
155 std::string type = configuration["type"];
156 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
157 keyPair++)
158 {
159 std::string subsituteString;
160
161 // device tree symbols are in decimal
162 if (keyPair.key() == "bus" &&
163 keyPair.value().type() == nlohmann::json::value_t::string)
164 {
165 unsigned int dec =
166 std::stoul(keyPair.value().get<std::string>(), nullptr, 16);
167 subsituteString = std::to_string(dec);
168 }
169 else if (keyPair.key() == "name" &&
170 keyPair.value().type() == nlohmann::json::value_t::string)
171 {
172 subsituteString = std::regex_replace(
173 keyPair.value().get<std::string>(), ILLEGAL_NAME_REGEX, "_");
174 name = subsituteString;
175 }
176 else
177 {
178 subsituteString = jsonToString(keyPair.value());
179 }
180 boost::replace_all(templateStr, TEMPLATE_CHAR + keyPair.key(),
181 subsituteString);
182 }
183 // todo: this is a lame way to fill in platform, but we only
184 // care about ast2500 right now
185 boost::replace_all(templateStr, TEMPLATE_CHAR + std::string("platform"),
186 PLATFORM);
187 std::string dtsFilename =
188 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dts";
189 std::string dtboFilename =
190 std::string(OUTPUT_DIR) + "/" + name + "_" + type + ".dtbo";
191
192 std::ofstream out(dtsFilename);
193 if (!out.is_open())
194 {
195 std::cerr << "createOverlay error opening " << dtsFilename << "\n";
196 return;
197 }
198 out << templateStr;
199 out.close();
200
201 // this is for muxes, we need to store the diff of i2c devices before
202 // and after scanning to load new symbols into device tree so that if we
203 // later add devices to the "virtual" i2c device, we can match the phandle
204 // to the correct mux
205 std::vector<std::experimental::filesystem::path> i2cDevsBefore;
206 auto findMux = MUX_TYPES.find(type);
207 if (findMux != MUX_TYPES.end())
208 {
209 find_files(std::experimental::filesystem::path(I2C_DEVS_DIR),
210 R"(i2c-\d+)", i2cDevsBefore, 0);
211 }
212
213 // compile dtbo and load overlay
214 boost::process::child c1(DTC, "-@", "-q", "-I", "dts", "-O", "dtb", "-o",
215 dtboFilename, dtsFilename);
216 c1.wait();
217 if (c1.exit_code())
218 {
219 std::cerr << "DTC error with file " << dtsFilename << "\n";
220 return;
221 }
222 boost::process::child c2(DT_OVERLAY, "-d", OUTPUT_DIR, name + "_" + type);
223 c2.wait();
224 if (c2.exit_code())
225 {
226 std::cerr << "DTOverlay error with file " << dtboFilename << "\n";
227 return;
228 }
229 auto findForceProbe = FORCE_PROBES.find(type);
230 if (findForceProbe != FORCE_PROBES.end())
231 {
232 forceProbe(findForceProbe->second);
233 }
234 if (findMux != MUX_TYPES.end())
235 {
236 fixupSymbols(i2cDevsBefore);
237 }
238}
239
240bool loadOverlays(const nlohmann::json &systemConfiguration)
241{
242
243 std::vector<std::experimental::filesystem::path> paths;
244 if (!find_files(std::experimental::filesystem::path(TEMPLATE_DIR),
245 R"(.*\.template)", paths, 0))
246 {
247 std::cerr << "Unable to find any tempate files in " << TEMPLATE_DIR
248 << "\n";
249 return false;
250 }
251
252 std::experimental::filesystem::create_directory(OUTPUT_DIR);
253 for (auto entity = systemConfiguration.begin();
254 entity != systemConfiguration.end(); entity++)
255 {
256 auto findExposes = entity.value().find("exposes");
257 if (findExposes == entity.value().end() ||
258 findExposes->type() != nlohmann::json::value_t::array)
259 {
260 continue;
261 }
262
263 for (auto &configuration : *findExposes)
264 {
265 auto findStatus = configuration.find("status");
266 // status missing is assumed to be 'okay'
267 if (findStatus != configuration.end() && *findStatus == "disabled")
268 {
269 continue;
270 }
271 auto findType = configuration.find("type");
272 if (findType == configuration.end() ||
273 findType->type() != nlohmann::json::value_t::string)
274 {
275 continue;
276 }
277 std::string type = findType.value().get<std::string>();
278 std::string typeFile = type + std::string(".template");
279 for (auto path : paths)
280 {
281 if (path.filename() != typeFile)
282 {
283 continue;
284 }
285 createOverlay(path.string(), configuration);
286 break;
287 }
288 }
289 }
290
291 return true;
292}