1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 3 4 #include "overlay.hpp" 5 6 #include "devices.hpp" 7 #include "utils.hpp" 8 9 #include <boost/algorithm/string/replace.hpp> 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 boost::replace_all(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 boost::replace_all(parameters, templateChar + keyPair.key(), 251 subsituteString); 252 boost::replace_all(busPath, templateChar + keyPair.key(), 253 subsituteString); 254 } 255 256 if (!bus || !address) 257 { 258 createDevice(busPath, parameters, constructor); 259 return; 260 } 261 262 buildDevice(name, busPath, parameters, *bus, *address, constructor, 263 destructor, hasHWMonDir, std::move(channels), io); 264 } 265 266 bool loadOverlays(const nlohmann::json& systemConfiguration, 267 boost::asio::io_context& io) 268 { 269 std::filesystem::create_directory(outputDir); 270 for (auto entity = systemConfiguration.begin(); 271 entity != systemConfiguration.end(); entity++) 272 { 273 auto findExposes = entity.value().find("Exposes"); 274 if (findExposes == entity.value().end() || 275 findExposes->type() != nlohmann::json::value_t::array) 276 { 277 continue; 278 } 279 280 for (const auto& configuration : *findExposes) 281 { 282 auto findStatus = configuration.find("Status"); 283 // status missing is assumed to be 'okay' 284 if (findStatus != configuration.end() && *findStatus == "disabled") 285 { 286 continue; 287 } 288 auto findType = configuration.find("Type"); 289 if (findType == configuration.end() || 290 findType->type() != nlohmann::json::value_t::string) 291 { 292 continue; 293 } 294 std::string type = findType.value().get<std::string>(); 295 auto device = devices::exportTemplates.find(type.c_str()); 296 if (device != devices::exportTemplates.end()) 297 { 298 exportDevice(type, device->second, configuration, io); 299 continue; 300 } 301 302 // Because many devices are intentionally not exportable, 303 // this error message is not printed in all situations. 304 // If wondering why your device not appearing, add your type to 305 // the exportTemplates array in the devices.hpp file. 306 lg2::debug("Device type {TYPE} not found in export map allowlist", 307 "TYPE", type); 308 } 309 } 310 311 return true; 312 } 313