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