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_set.hpp> 13 #include <nlohmann/json.hpp> 14 #include <phosphor-logging/lg2.hpp> 15 16 #include <filesystem> 17 #include <flat_map> 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(std::string_view 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(std::string_view busPath, uint64_t address, 102 std::string_view 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(std::string_view busPath, std::string_view parameters, 118 std::string_view constructor) 119 { 120 std::filesystem::path deviceConstructor(busPath); 121 deviceConstructor /= constructor; 122 std::ofstream deviceFile(deviceConstructor); 123 if (!deviceFile.good()) 124 { 125 lg2::error("Error writing {PATH}", "PATH", deviceConstructor.string()); 126 return -1; 127 } 128 deviceFile << parameters; 129 deviceFile.close(); 130 131 return 0; 132 } 133 134 static bool deviceIsCreated(std::string_view busPath, uint64_t bus, 135 uint64_t address, 136 const devices::createsHWMon hasHWMonDir) 137 { 138 std::filesystem::path dirPath = busPath; 139 dirPath /= deviceDirName(bus, address); 140 if (hasHWMonDir == devices::createsHWMon::hasHWMonDir) 141 { 142 dirPath /= "hwmon"; 143 } 144 145 std::error_code ec; 146 // Ignore errors; anything but a clean 'true' is just fine as 'false' 147 return std::filesystem::exists(dirPath, ec); 148 } 149 150 static int buildDevice( 151 std::string_view name, std::string_view busPath, 152 std::string_view parameters, uint64_t bus, uint64_t address, 153 std::string_view constructor, std::string_view destructor, 154 const devices::createsHWMon hasHWMonDir, 155 std::vector<std::string> channelNames, boost::asio::io_context& io, 156 const size_t retries = 5) 157 { 158 if (retries == 0U) 159 { 160 return -1; 161 } 162 163 // If it's already instantiated, we don't need to create it again. 164 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 165 { 166 // Try to create the device 167 createDevice(busPath, parameters, constructor); 168 169 // If it didn't work, delete it and try again in 500ms 170 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 171 { 172 deleteDevice(busPath, address, destructor); 173 174 std::shared_ptr<boost::asio::steady_timer> createTimer = 175 std::make_shared<boost::asio::steady_timer>(io); 176 createTimer->expires_after(std::chrono::milliseconds(500)); 177 createTimer->async_wait( 178 [createTimer, name, busPath, parameters, bus, address, 179 constructor, destructor, hasHWMonDir, 180 channelNames(std::move(channelNames)), retries, 181 &io](const boost::system::error_code& ec) mutable { 182 if (ec) 183 { 184 lg2::error("Timer error: {ERR}", "ERR", ec.message()); 185 return -2; 186 } 187 return buildDevice(name, busPath, parameters, bus, address, 188 constructor, destructor, hasHWMonDir, 189 std::move(channelNames), io, 190 retries - 1); 191 }); 192 return -1; 193 } 194 } 195 196 // Link the mux channels if needed once the device is created. 197 if (!channelNames.empty()) 198 { 199 linkMux(name, bus, address, channelNames); 200 } 201 202 return 0; 203 } 204 205 void exportDevice(const devices::ExportTemplate& exportTemplate, 206 const nlohmann::json& configuration, 207 boost::asio::io_context& io) 208 { 209 std::string_view type = exportTemplate.type; 210 std::string parameters(exportTemplate.parameters); 211 std::string busPath(exportTemplate.busPath); 212 std::string_view constructor = exportTemplate.add; 213 std::string_view destructor = exportTemplate.remove; 214 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir; 215 std::string name = "unknown"; 216 std::optional<uint64_t> bus; 217 std::optional<uint64_t> address; 218 std::vector<std::string> channels; 219 220 for (auto keyPair = configuration.begin(); keyPair != configuration.end(); 221 keyPair++) 222 { 223 std::string subsituteString; 224 225 if (keyPair.key() == "Name" && 226 keyPair.value().type() == nlohmann::json::value_t::string) 227 { 228 subsituteString = std::regex_replace( 229 keyPair.value().get<std::string>(), illegalNameRegex, "_"); 230 name = subsituteString; 231 } 232 else 233 { 234 subsituteString = jsonToString(keyPair.value()); 235 } 236 237 if (keyPair.key() == "Bus") 238 { 239 bus = keyPair.value().get<uint64_t>(); 240 } 241 else if (keyPair.key() == "Address") 242 { 243 address = keyPair.value().get<uint64_t>(); 244 } 245 else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux")) 246 { 247 channels = keyPair.value().get<std::vector<std::string>>(); 248 } 249 replaceAll(parameters, templateChar + keyPair.key(), subsituteString); 250 replaceAll(busPath, templateChar + keyPair.key(), subsituteString); 251 } 252 253 if (!bus || !address) 254 { 255 createDevice(busPath, parameters, constructor); 256 return; 257 } 258 259 buildDevice(name, busPath, parameters, *bus, *address, constructor, 260 destructor, hasHWMonDir, std::move(channels), io); 261 } 262 263 bool loadOverlays(const nlohmann::json& systemConfiguration, 264 boost::asio::io_context& io) 265 { 266 std::filesystem::create_directory(outputDir); 267 for (auto entity = systemConfiguration.begin(); 268 entity != systemConfiguration.end(); entity++) 269 { 270 auto findExposes = entity.value().find("Exposes"); 271 if (findExposes == entity.value().end() || 272 findExposes->type() != nlohmann::json::value_t::array) 273 { 274 continue; 275 } 276 277 for (const auto& configuration : *findExposes) 278 { 279 auto findStatus = configuration.find("Status"); 280 // status missing is assumed to be 'okay' 281 if (findStatus != configuration.end() && *findStatus == "disabled") 282 { 283 continue; 284 } 285 auto findType = configuration.find("Type"); 286 if (findType == configuration.end() || 287 findType->type() != nlohmann::json::value_t::string) 288 { 289 continue; 290 } 291 const std::string& type = findType.value().get<std::string>(); 292 const auto* device = std::ranges::find_if( 293 devices::exportTemplates, 294 [&type](const auto& tmp) { return tmp.type == type; }); 295 if (device != devices::exportTemplates.end()) 296 { 297 exportDevice(*device, configuration, io); 298 continue; 299 } 300 301 // Because many devices are intentionally not exportable, 302 // this error message is not printed in all situations. 303 // If wondering why your device not appearing, add your type to 304 // the exportTemplates array in the devices.hpp file. 305 lg2::debug("Device type {TYPE} not found in export map allowlist", 306 "TYPE", type); 307 } 308 } 309 310 return true; 311 } 312