1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 3 4 #include "overlay.hpp" 5 6 #include "../utils.hpp" 7 #include "devices.hpp" 8 #include "utils.hpp" 9 10 #include <boost/asio/io_context.hpp> 11 #include <boost/asio/steady_timer.hpp> 12 #include <boost/container/flat_map.hpp> 13 #include <boost/container/flat_set.hpp> 14 #include <nlohmann/json.hpp> 15 #include <phosphor-logging/lg2.hpp> 16 17 #include <filesystem> 18 #include <fstream> 19 #include <iomanip> 20 #include <regex> 21 #include <string> 22 23 constexpr const char* outputDir = "/tmp/overlays"; 24 constexpr const char* templateChar = "$"; 25 constexpr const char* i2CDevsDir = "/sys/bus/i2c/devices"; 26 constexpr const char* muxSymlinkDir = "/dev/i2c-mux"; 27 28 const std::regex illegalNameRegex("[^A-Za-z0-9_]"); 29 30 // helper function to make json types into string 31 std::string jsonToString(const nlohmann::json& in) 32 { 33 if (in.type() == nlohmann::json::value_t::string) 34 { 35 return in.get<std::string>(); 36 } 37 if (in.type() == nlohmann::json::value_t::array) 38 { 39 // remove brackets and comma from array 40 std::string array = in.dump(); 41 array = array.substr(1, array.size() - 2); 42 std::ranges::replace(array, ',', ' '); 43 return array; 44 } 45 return in.dump(); 46 } 47 48 static std::string deviceDirName(uint64_t bus, uint64_t address) 49 { 50 std::ostringstream name; 51 name << bus << "-" << std::hex << std::setw(4) << std::setfill('0') 52 << address; 53 return name.str(); 54 } 55 56 void linkMux(const std::string& muxName, uint64_t busIndex, uint64_t address, 57 const std::vector<std::string>& channelNames) 58 { 59 std::error_code ec; 60 std::filesystem::path muxSymlinkDirPath(muxSymlinkDir); 61 std::filesystem::create_directory(muxSymlinkDirPath, ec); 62 // ignore error codes here if the directory already exists 63 ec.clear(); 64 std::filesystem::path linkDir = muxSymlinkDirPath / muxName; 65 std::filesystem::create_directory(linkDir, ec); 66 67 std::filesystem::path devDir(i2CDevsDir); 68 devDir /= deviceDirName(busIndex, address); 69 70 for (std::size_t channelIndex = 0; channelIndex < channelNames.size(); 71 channelIndex++) 72 { 73 const std::string& channelName = channelNames[channelIndex]; 74 if (channelName.empty()) 75 { 76 continue; 77 } 78 79 std::filesystem::path channelPath = 80 devDir / ("channel-" + std::to_string(channelIndex)); 81 if (!is_symlink(channelPath)) 82 { 83 lg2::error("{PATH} for mux channel {CHANNEL} doesn't exist!", 84 "PATH", channelPath.string(), "CHANNEL", channelName); 85 continue; 86 } 87 std::filesystem::path bus = std::filesystem::read_symlink(channelPath); 88 89 std::filesystem::path fp("/dev" / bus.filename()); 90 std::filesystem::path link(linkDir / channelName); 91 92 std::filesystem::create_symlink(fp, link, ec); 93 if (ec) 94 { 95 lg2::error("Failure creating symlink for {PATH} to {LINK}", "PATH", 96 fp.string(), "LINK", link.string()); 97 } 98 } 99 } 100 101 static int deleteDevice(const std::string& busPath, uint64_t address, 102 const std::string& destructor) 103 { 104 std::filesystem::path deviceDestructor(busPath); 105 deviceDestructor /= destructor; 106 std::ofstream deviceFile(deviceDestructor); 107 if (!deviceFile.good()) 108 { 109 lg2::error("Error writing {PATH}", "PATH", deviceDestructor.string()); 110 return -1; 111 } 112 deviceFile << std::to_string(address); 113 deviceFile.close(); 114 return 0; 115 } 116 117 static int createDevice(const std::string& busPath, 118 const std::string& parameters, 119 const std::string& constructor) 120 { 121 std::filesystem::path deviceConstructor(busPath); 122 deviceConstructor /= constructor; 123 std::ofstream deviceFile(deviceConstructor); 124 if (!deviceFile.good()) 125 { 126 lg2::error("Error writing {PATH}", "PATH", deviceConstructor.string()); 127 return -1; 128 } 129 deviceFile << parameters; 130 deviceFile.close(); 131 132 return 0; 133 } 134 135 static bool deviceIsCreated(const std::string& busPath, uint64_t bus, 136 uint64_t address, 137 const devices::createsHWMon hasHWMonDir) 138 { 139 std::filesystem::path dirPath = busPath; 140 dirPath /= deviceDirName(bus, address); 141 if (hasHWMonDir == devices::createsHWMon::hasHWMonDir) 142 { 143 dirPath /= "hwmon"; 144 } 145 146 std::error_code ec; 147 // Ignore errors; anything but a clean 'true' is just fine as 'false' 148 return std::filesystem::exists(dirPath, ec); 149 } 150 151 static int buildDevice( 152 const std::string& name, const std::string& busPath, 153 const std::string& parameters, uint64_t bus, uint64_t address, 154 const std::string& constructor, const std::string& destructor, 155 const devices::createsHWMon hasHWMonDir, 156 std::vector<std::string> channelNames, boost::asio::io_context& io, 157 const size_t retries = 5) 158 { 159 if (retries == 0U) 160 { 161 return -1; 162 } 163 164 // If it's already instantiated, we don't need to create it again. 165 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 166 { 167 // Try to create the device 168 createDevice(busPath, parameters, constructor); 169 170 // If it didn't work, delete it and try again in 500ms 171 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 172 { 173 deleteDevice(busPath, address, destructor); 174 175 std::shared_ptr<boost::asio::steady_timer> createTimer = 176 std::make_shared<boost::asio::steady_timer>(io); 177 createTimer->expires_after(std::chrono::milliseconds(500)); 178 createTimer->async_wait( 179 [createTimer, name, busPath, parameters, bus, address, 180 constructor, destructor, hasHWMonDir, 181 channelNames(std::move(channelNames)), retries, 182 &io](const boost::system::error_code& ec) mutable { 183 if (ec) 184 { 185 lg2::error("Timer error: {ERR}", "ERR", ec.message()); 186 return -2; 187 } 188 return buildDevice(name, busPath, parameters, bus, address, 189 constructor, destructor, hasHWMonDir, 190 std::move(channelNames), io, 191 retries - 1); 192 }); 193 return -1; 194 } 195 } 196 197 // Link the mux channels if needed once the device is created. 198 if (!channelNames.empty()) 199 { 200 linkMux(name, bus, address, channelNames); 201 } 202 203 return 0; 204 } 205 206 void exportDevice(const std::string& type, 207 const devices::ExportTemplate& exportTemplate, 208 const nlohmann::json& configuration, 209 boost::asio::io_context& io) 210 { 211 std::string parameters = exportTemplate.parameters; 212 std::string busPath = exportTemplate.busPath; 213 std::string constructor = exportTemplate.add; 214 std::string destructor = exportTemplate.remove; 215 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir; 216 std::string name = "unknown"; 217 std::optional<uint64_t> bus; 218 std::optional<uint64_t> address; 219 std::vector<std::string> channels; 220 221 for (auto keyPair = configuration.begin(); keyPair != configuration.end(); 222 keyPair++) 223 { 224 std::string subsituteString; 225 226 if (keyPair.key() == "Name" && 227 keyPair.value().type() == nlohmann::json::value_t::string) 228 { 229 subsituteString = std::regex_replace( 230 keyPair.value().get<std::string>(), illegalNameRegex, "_"); 231 name = subsituteString; 232 } 233 else 234 { 235 subsituteString = jsonToString(keyPair.value()); 236 } 237 238 if (keyPair.key() == "Bus") 239 { 240 bus = keyPair.value().get<uint64_t>(); 241 } 242 else if (keyPair.key() == "Address") 243 { 244 address = keyPair.value().get<uint64_t>(); 245 } 246 else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux")) 247 { 248 channels = keyPair.value().get<std::vector<std::string>>(); 249 } 250 replaceAll(parameters, templateChar + keyPair.key(), subsituteString); 251 replaceAll(busPath, templateChar + keyPair.key(), subsituteString); 252 } 253 254 if (!bus || !address) 255 { 256 createDevice(busPath, parameters, constructor); 257 return; 258 } 259 260 buildDevice(name, busPath, parameters, *bus, *address, constructor, 261 destructor, hasHWMonDir, std::move(channels), io); 262 } 263 264 bool loadOverlays(const nlohmann::json& systemConfiguration, 265 boost::asio::io_context& io) 266 { 267 std::filesystem::create_directory(outputDir); 268 for (auto entity = systemConfiguration.begin(); 269 entity != systemConfiguration.end(); entity++) 270 { 271 auto findExposes = entity.value().find("Exposes"); 272 if (findExposes == entity.value().end() || 273 findExposes->type() != nlohmann::json::value_t::array) 274 { 275 continue; 276 } 277 278 for (const auto& configuration : *findExposes) 279 { 280 auto findStatus = configuration.find("Status"); 281 // status missing is assumed to be 'okay' 282 if (findStatus != configuration.end() && *findStatus == "disabled") 283 { 284 continue; 285 } 286 auto findType = configuration.find("Type"); 287 if (findType == configuration.end() || 288 findType->type() != nlohmann::json::value_t::string) 289 { 290 continue; 291 } 292 std::string type = findType.value().get<std::string>(); 293 auto device = devices::exportTemplates.find(type.c_str()); 294 if (device != devices::exportTemplates.end()) 295 { 296 exportDevice(type, device->second, configuration, io); 297 continue; 298 } 299 300 // Because many devices are intentionally not exportable, 301 // this error message is not printed in all situations. 302 // If wondering why your device not appearing, add your type to 303 // the exportTemplates array in the devices.hpp file. 304 lg2::debug("Device type {TYPE} not found in export map allowlist", 305 "TYPE", type); 306 } 307 } 308 309 return true; 310 } 311