blob: 64c11ed986795e2668e518854415a45436c6b2d2 [file] [log] [blame]
Alexander Hansen4e1142d2025-07-25 17:07:27 +02001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
James Feistc95cb142018-02-26 10:41:42 -08003
Brad Bishope45d8c72022-05-25 15:12:53 -04004#include "overlay.hpp"
Patrick Venturea49dc332019-10-26 08:32:02 -07005
Patrick Venturea49dc332019-10-26 08:32:02 -07006#include "devices.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +02007#include "utils.hpp"
Patrick Venturea49dc332019-10-26 08:32:02 -07008
James Feista465ccc2019-02-08 12:51:01 -08009#include <boost/algorithm/string/predicate.hpp>
Patrick Williamscc34fd72025-06-13 15:04:03 -040010#include <boost/algorithm/string/replace.hpp>
Johnathan Mantey9b867872020-10-13 15:00:51 -070011#include <boost/asio/io_context.hpp>
12#include <boost/asio/steady_timer.hpp>
James Feista465ccc2019-02-08 12:51:01 -080013#include <boost/container/flat_map.hpp>
14#include <boost/container/flat_set.hpp>
James Feist8c505da2020-05-28 10:06:33 -070015#include <nlohmann/json.hpp>
Alexander Hansenc3db2c32024-08-20 15:01:38 +020016#include <phosphor-logging/lg2.hpp>
James Feist8c505da2020-05-28 10:06:33 -070017
James Feist637b3ef2019-04-15 16:35:30 -070018#include <filesystem>
Christopher Meis59ef1e72025-04-16 08:53:25 +020019#include <fstream>
James Feista465ccc2019-02-08 12:51:01 -080020#include <iomanip>
21#include <iostream>
James Feista465ccc2019-02-08 12:51:01 -080022#include <regex>
23#include <string>
James Feistc95cb142018-02-26 10:41:42 -080024
Ed Tanous07d467b2021-02-23 14:48:37 -080025constexpr const char* outputDir = "/tmp/overlays";
26constexpr const char* templateChar = "$";
27constexpr const char* i2CDevsDir = "/sys/bus/i2c/devices";
28constexpr const char* muxSymlinkDir = "/dev/i2c-mux";
James Feistc95cb142018-02-26 10:41:42 -080029
Ed Tanousfc171422024-04-04 17:18:16 -070030const std::regex illegalNameRegex("[^A-Za-z0-9_]");
James Feistc95cb142018-02-26 10:41:42 -080031
James Feistc95cb142018-02-26 10:41:42 -080032// helper function to make json types into string
James Feista465ccc2019-02-08 12:51:01 -080033std::string jsonToString(const nlohmann::json& in)
James Feistc95cb142018-02-26 10:41:42 -080034{
35 if (in.type() == nlohmann::json::value_t::string)
36 {
37 return in.get<std::string>();
38 }
Ed Tanous07d467b2021-02-23 14:48:37 -080039 if (in.type() == nlohmann::json::value_t::array)
James Feistc95cb142018-02-26 10:41:42 -080040 {
41 // remove brackets and comma from array
42 std::string array = in.dump();
43 array = array.substr(1, array.size() - 2);
44 boost::replace_all(array, ",", " ");
45 return array;
46 }
47 return in.dump();
48}
49
Zev Weiss9afef3a2022-07-13 16:38:23 -070050static std::string deviceDirName(uint64_t bus, uint64_t address)
51{
52 std::ostringstream name;
Johnathan Mantey7b21ef22022-08-08 12:57:55 -070053 name << bus << "-" << std::hex << std::setw(4) << std::setfill('0')
54 << address;
Zev Weiss9afef3a2022-07-13 16:38:23 -070055 return name.str();
56}
57
Jonathan Domand0eb1292023-04-19 11:21:56 -070058void linkMux(const std::string& muxName, uint64_t busIndex, uint64_t address,
Jonathan Doman6af72c92023-04-19 11:55:39 -070059 const std::vector<std::string>& channelNames)
James Feistc95cb142018-02-26 10:41:42 -080060{
James Feist286babc2019-02-07 16:48:28 -080061 std::error_code ec;
Ed Tanous07d467b2021-02-23 14:48:37 -080062 std::filesystem::path muxSymlinkDirPath(muxSymlinkDir);
63 std::filesystem::create_directory(muxSymlinkDirPath, ec);
Ed Tanousee3357a2019-02-26 12:44:14 -080064 // ignore error codes here if the directory already exists
65 ec.clear();
Ed Tanous07d467b2021-02-23 14:48:37 -080066 std::filesystem::path linkDir = muxSymlinkDirPath / muxName;
Ed Tanousee3357a2019-02-26 12:44:14 -080067 std::filesystem::create_directory(linkDir, ec);
68
Ed Tanous07d467b2021-02-23 14:48:37 -080069 std::filesystem::path devDir(i2CDevsDir);
Zev Weiss9afef3a2022-07-13 16:38:23 -070070 devDir /= deviceDirName(busIndex, address);
James Feist286babc2019-02-07 16:48:28 -080071
Ed Tanousee3357a2019-02-26 12:44:14 -080072 for (std::size_t channelIndex = 0; channelIndex < channelNames.size();
73 channelIndex++)
James Feist286babc2019-02-07 16:48:28 -080074 {
Jonathan Doman6af72c92023-04-19 11:55:39 -070075 const std::string& channelName = channelNames[channelIndex];
76 if (channelName.empty())
Ed Tanousee3357a2019-02-26 12:44:14 -080077 {
78 continue;
79 }
80
81 std::filesystem::path channelPath =
82 devDir / ("channel-" + std::to_string(channelIndex));
83 if (!is_symlink(channelPath))
84 {
Jonathan Doman6af72c92023-04-19 11:55:39 -070085 std::cerr << channelPath << " for mux channel " << channelName
Ed Tanousee3357a2019-02-26 12:44:14 -080086 << " doesn't exist!\n";
87 continue;
88 }
89 std::filesystem::path bus = std::filesystem::read_symlink(channelPath);
90
91 std::filesystem::path fp("/dev" / bus.filename());
Jonathan Doman6af72c92023-04-19 11:55:39 -070092 std::filesystem::path link(linkDir / channelName);
Ed Tanousee3357a2019-02-26 12:44:14 -080093
94 std::filesystem::create_symlink(fp, link, ec);
95 if (ec)
96 {
97 std::cerr << "Failure creating symlink for " << fp << " to " << link
98 << "\n";
99 }
James Feistc95cb142018-02-26 10:41:42 -0800100 }
101}
102
Jonathan Domand0eb1292023-04-19 11:21:56 -0700103static int deleteDevice(const std::string& busPath, uint64_t address,
Johnathan Mantey9b867872020-10-13 15:00:51 -0700104 const std::string& destructor)
105{
Zev Weissc11b5da2022-07-12 16:31:37 -0700106 std::filesystem::path deviceDestructor(busPath);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700107 deviceDestructor /= destructor;
108 std::ofstream deviceFile(deviceDestructor);
109 if (!deviceFile.good())
110 {
111 std::cerr << "Error writing " << deviceDestructor << "\n";
112 return -1;
113 }
Jonathan Domand0eb1292023-04-19 11:21:56 -0700114 deviceFile << std::to_string(address);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700115 deviceFile.close();
116 return 0;
117}
118
Zev Weissc11b5da2022-07-12 16:31:37 -0700119static int createDevice(const std::string& busPath,
Johnathan Mantey9b867872020-10-13 15:00:51 -0700120 const std::string& parameters,
121 const std::string& constructor)
122{
Zev Weissc11b5da2022-07-12 16:31:37 -0700123 std::filesystem::path deviceConstructor(busPath);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700124 deviceConstructor /= constructor;
125 std::ofstream deviceFile(deviceConstructor);
126 if (!deviceFile.good())
127 {
128 std::cerr << "Error writing " << deviceConstructor << "\n";
129 return -1;
130 }
131 deviceFile << parameters;
132 deviceFile.close();
133
134 return 0;
135}
136
Jonathan Domand0eb1292023-04-19 11:21:56 -0700137static bool deviceIsCreated(const std::string& busPath, uint64_t bus,
138 uint64_t address,
Johnathan Mantey7b21ef22022-08-08 12:57:55 -0700139 const devices::createsHWMon hasHWMonDir)
Johnathan Mantey9b867872020-10-13 15:00:51 -0700140{
Zev Weiss67003d62022-07-15 16:38:19 -0700141 std::filesystem::path dirPath = busPath;
Jonathan Domand0eb1292023-04-19 11:21:56 -0700142 dirPath /= deviceDirName(bus, address);
Johnathan Mantey7b21ef22022-08-08 12:57:55 -0700143 if (hasHWMonDir == devices::createsHWMon::hasHWMonDir)
Zev Weiss67003d62022-07-15 16:38:19 -0700144 {
145 dirPath /= "hwmon";
146 }
Johnathan Mantey9b867872020-10-13 15:00:51 -0700147
Ed Tanous37e142b2021-04-30 12:19:26 -0700148 std::error_code ec;
Zev Weiss67003d62022-07-15 16:38:19 -0700149 // Ignore errors; anything but a clean 'true' is just fine as 'false'
150 return std::filesystem::exists(dirPath, ec);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700151}
152
Patrick Williams5a807032025-03-03 11:20:39 -0500153static int buildDevice(
154 const std::string& name, const std::string& busPath,
155 const std::string& parameters, uint64_t bus, uint64_t address,
156 const std::string& constructor, const std::string& destructor,
157 const devices::createsHWMon hasHWMonDir,
Alexander Hansena555acf2025-06-27 11:59:10 +0200158 std::vector<std::string> channelNames, boost::asio::io_context& io,
159 const size_t retries = 5)
Johnathan Mantey9b867872020-10-13 15:00:51 -0700160{
Ed Tanous3013fb42022-07-09 08:27:06 -0700161 if (retries == 0U)
Johnathan Mantey9b867872020-10-13 15:00:51 -0700162 {
163 return -1;
164 }
165
Jonathan Doman6af72c92023-04-19 11:55:39 -0700166 // If it's already instantiated, we don't need to create it again.
Johnathan Mantey7b21ef22022-08-08 12:57:55 -0700167 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
Johnathan Mantey9b867872020-10-13 15:00:51 -0700168 {
Jonathan Doman6af72c92023-04-19 11:55:39 -0700169 // Try to create the device
170 createDevice(busPath, parameters, constructor);
Zev Weisscbcd17f2022-07-15 15:39:21 -0700171
Jonathan Doman6af72c92023-04-19 11:55:39 -0700172 // If it didn't work, delete it and try again in 500ms
173 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir))
174 {
175 deleteDevice(busPath, address, destructor);
176
177 std::shared_ptr<boost::asio::steady_timer> createTimer =
178 std::make_shared<boost::asio::steady_timer>(io);
179 createTimer->expires_after(std::chrono::milliseconds(500));
180 createTimer->async_wait(
181 [createTimer, name, busPath, parameters, bus, address,
182 constructor, destructor, hasHWMonDir,
Alexander Hansena555acf2025-06-27 11:59:10 +0200183 channelNames(std::move(channelNames)), retries,
184 &io](const boost::system::error_code& ec) mutable {
Patrick Williamsb7077432024-08-16 15:22:21 -0400185 if (ec)
186 {
187 std::cerr << "Timer error: " << ec << "\n";
188 return -2;
189 }
190 return buildDevice(name, busPath, parameters, bus, address,
191 constructor, destructor, hasHWMonDir,
Alexander Hansena555acf2025-06-27 11:59:10 +0200192 std::move(channelNames), io,
193 retries - 1);
Patrick Williamsb7077432024-08-16 15:22:21 -0400194 });
Jonathan Doman6af72c92023-04-19 11:55:39 -0700195 return -1;
196 }
197 }
198
199 // Link the mux channels if needed once the device is created.
200 if (!channelNames.empty())
201 {
202 linkMux(name, bus, address, channelNames);
Johnathan Mantey9b867872020-10-13 15:00:51 -0700203 }
Zev Weisscbcd17f2022-07-15 15:39:21 -0700204
Johnathan Mantey9b867872020-10-13 15:00:51 -0700205 return 0;
206}
207
James Feista465ccc2019-02-08 12:51:01 -0800208void exportDevice(const std::string& type,
209 const devices::ExportTemplate& exportTemplate,
Alexander Hansena555acf2025-06-27 11:59:10 +0200210 const nlohmann::json& configuration,
211 boost::asio::io_context& io)
James Feist053a6642018-10-15 13:17:09 -0700212{
James Feist053a6642018-10-15 13:17:09 -0700213 std::string parameters = exportTemplate.parameters;
Zev Weissc11b5da2022-07-12 16:31:37 -0700214 std::string busPath = exportTemplate.busPath;
Johnathan Mantey9b867872020-10-13 15:00:51 -0700215 std::string constructor = exportTemplate.add;
216 std::string destructor = exportTemplate.remove;
Johnathan Mantey7b21ef22022-08-08 12:57:55 -0700217 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir;
James Feist053a6642018-10-15 13:17:09 -0700218 std::string name = "unknown";
Jonathan Domand0eb1292023-04-19 11:21:56 -0700219 std::optional<uint64_t> bus;
220 std::optional<uint64_t> address;
Jonathan Doman6af72c92023-04-19 11:55:39 -0700221 std::vector<std::string> channels;
James Feist053a6642018-10-15 13:17:09 -0700222
223 for (auto keyPair = configuration.begin(); keyPair != configuration.end();
224 keyPair++)
225 {
226 std::string subsituteString;
227
228 if (keyPair.key() == "Name" &&
229 keyPair.value().type() == nlohmann::json::value_t::string)
230 {
231 subsituteString = std::regex_replace(
Ed Tanous07d467b2021-02-23 14:48:37 -0800232 keyPair.value().get<std::string>(), illegalNameRegex, "_");
James Feist053a6642018-10-15 13:17:09 -0700233 name = subsituteString;
234 }
235 else
236 {
237 subsituteString = jsonToString(keyPair.value());
238 }
239
240 if (keyPair.key() == "Bus")
241 {
Jonathan Domand0eb1292023-04-19 11:21:56 -0700242 bus = keyPair.value().get<uint64_t>();
James Feist053a6642018-10-15 13:17:09 -0700243 }
244 else if (keyPair.key() == "Address")
245 {
Jonathan Domand0eb1292023-04-19 11:21:56 -0700246 address = keyPair.value().get<uint64_t>();
James Feist053a6642018-10-15 13:17:09 -0700247 }
Jonathan Doman6af72c92023-04-19 11:55:39 -0700248 else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux"))
James Feist286babc2019-02-07 16:48:28 -0800249 {
Jonathan Doman6af72c92023-04-19 11:55:39 -0700250 channels = keyPair.value().get<std::vector<std::string>>();
James Feist286babc2019-02-07 16:48:28 -0800251 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800252 boost::replace_all(parameters, templateChar + keyPair.key(),
James Feist053a6642018-10-15 13:17:09 -0700253 subsituteString);
Zev Weissc11b5da2022-07-12 16:31:37 -0700254 boost::replace_all(busPath, templateChar + keyPair.key(),
James Feist053a6642018-10-15 13:17:09 -0700255 subsituteString);
256 }
257
Jonathan Domand0eb1292023-04-19 11:21:56 -0700258 if (!bus || !address)
259 {
260 createDevice(busPath, parameters, constructor);
261 return;
262 }
263
Jonathan Doman6af72c92023-04-19 11:55:39 -0700264 buildDevice(name, busPath, parameters, *bus, *address, constructor,
Alexander Hansena555acf2025-06-27 11:59:10 +0200265 destructor, hasHWMonDir, std::move(channels), io);
James Feist053a6642018-10-15 13:17:09 -0700266}
267
Alexander Hansena555acf2025-06-27 11:59:10 +0200268bool loadOverlays(const nlohmann::json& systemConfiguration,
269 boost::asio::io_context& io)
James Feistc95cb142018-02-26 10:41:42 -0800270{
Ed Tanous07d467b2021-02-23 14:48:37 -0800271 std::filesystem::create_directory(outputDir);
James Feistc95cb142018-02-26 10:41:42 -0800272 for (auto entity = systemConfiguration.begin();
273 entity != systemConfiguration.end(); entity++)
274 {
James Feist1e3e6982018-08-03 16:09:28 -0700275 auto findExposes = entity.value().find("Exposes");
James Feistc95cb142018-02-26 10:41:42 -0800276 if (findExposes == entity.value().end() ||
277 findExposes->type() != nlohmann::json::value_t::array)
278 {
279 continue;
280 }
281
Ed Tanous3013fb42022-07-09 08:27:06 -0700282 for (const auto& configuration : *findExposes)
James Feistc95cb142018-02-26 10:41:42 -0800283 {
James Feist1e3e6982018-08-03 16:09:28 -0700284 auto findStatus = configuration.find("Status");
James Feistc95cb142018-02-26 10:41:42 -0800285 // status missing is assumed to be 'okay'
286 if (findStatus != configuration.end() && *findStatus == "disabled")
287 {
288 continue;
289 }
James Feistd63d18a2018-07-19 15:23:45 -0700290 auto findType = configuration.find("Type");
James Feistc95cb142018-02-26 10:41:42 -0800291 if (findType == configuration.end() ||
292 findType->type() != nlohmann::json::value_t::string)
293 {
294 continue;
295 }
296 std::string type = findType.value().get<std::string>();
James Feist053a6642018-10-15 13:17:09 -0700297 auto device = devices::exportTemplates.find(type.c_str());
298 if (device != devices::exportTemplates.end())
299 {
Alexander Hansena555acf2025-06-27 11:59:10 +0200300 exportDevice(type, device->second, configuration, io);
Josh Lehan96b8a6e2019-10-09 14:39:49 -0700301 continue;
302 }
303
304 // Because many devices are intentionally not exportable,
305 // this error message is not printed in all situations.
306 // If wondering why your device not appearing, add your type to
307 // the exportTemplates array in the devices.hpp file.
Alexander Hansenc3db2c32024-08-20 15:01:38 +0200308 lg2::debug("Device type {TYPE} not found in export map allowlist",
309 "TYPE", type);
James Feistc95cb142018-02-26 10:41:42 -0800310 }
311 }
312
313 return true;
Jae Hyun Yoo6d1d0142018-07-25 10:07:43 -0700314}