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/predicate.hpp> 10 #include <boost/algorithm/string/replace.hpp> 11 #include <boost/asio/io_context.hpp> 12 #include <boost/asio/steady_timer.hpp> 13 #include <boost/container/flat_map.hpp> 14 #include <boost/container/flat_set.hpp> 15 #include <nlohmann/json.hpp> 16 #include <phosphor-logging/lg2.hpp> 17 18 #include <filesystem> 19 #include <fstream> 20 #include <iomanip> 21 #include <iostream> 22 #include <regex> 23 #include <string> 24 25 constexpr const char* outputDir = "/tmp/overlays"; 26 constexpr const char* templateChar = "$"; 27 constexpr const char* i2CDevsDir = "/sys/bus/i2c/devices"; 28 constexpr const char* muxSymlinkDir = "/dev/i2c-mux"; 29 30 const std::regex illegalNameRegex("[^A-Za-z0-9_]"); 31 32 // helper function to make json types into string 33 std::string jsonToString(const nlohmann::json& in) 34 { 35 if (in.type() == nlohmann::json::value_t::string) 36 { 37 return in.get<std::string>(); 38 } 39 if (in.type() == nlohmann::json::value_t::array) 40 { 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 50 static std::string deviceDirName(uint64_t bus, uint64_t address) 51 { 52 std::ostringstream name; 53 name << bus << "-" << std::hex << std::setw(4) << std::setfill('0') 54 << address; 55 return name.str(); 56 } 57 58 void linkMux(const std::string& muxName, uint64_t busIndex, uint64_t address, 59 const std::vector<std::string>& channelNames) 60 { 61 std::error_code ec; 62 std::filesystem::path muxSymlinkDirPath(muxSymlinkDir); 63 std::filesystem::create_directory(muxSymlinkDirPath, ec); 64 // ignore error codes here if the directory already exists 65 ec.clear(); 66 std::filesystem::path linkDir = muxSymlinkDirPath / muxName; 67 std::filesystem::create_directory(linkDir, ec); 68 69 std::filesystem::path devDir(i2CDevsDir); 70 devDir /= deviceDirName(busIndex, address); 71 72 for (std::size_t channelIndex = 0; channelIndex < channelNames.size(); 73 channelIndex++) 74 { 75 const std::string& channelName = channelNames[channelIndex]; 76 if (channelName.empty()) 77 { 78 continue; 79 } 80 81 std::filesystem::path channelPath = 82 devDir / ("channel-" + std::to_string(channelIndex)); 83 if (!is_symlink(channelPath)) 84 { 85 std::cerr << channelPath << " for mux channel " << channelName 86 << " 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()); 92 std::filesystem::path link(linkDir / channelName); 93 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 } 100 } 101 } 102 103 static int deleteDevice(const std::string& busPath, uint64_t address, 104 const std::string& destructor) 105 { 106 std::filesystem::path deviceDestructor(busPath); 107 deviceDestructor /= destructor; 108 std::ofstream deviceFile(deviceDestructor); 109 if (!deviceFile.good()) 110 { 111 std::cerr << "Error writing " << deviceDestructor << "\n"; 112 return -1; 113 } 114 deviceFile << std::to_string(address); 115 deviceFile.close(); 116 return 0; 117 } 118 119 static int createDevice(const std::string& busPath, 120 const std::string& parameters, 121 const std::string& constructor) 122 { 123 std::filesystem::path deviceConstructor(busPath); 124 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 137 static bool deviceIsCreated(const std::string& busPath, uint64_t bus, 138 uint64_t address, 139 const devices::createsHWMon hasHWMonDir) 140 { 141 std::filesystem::path dirPath = busPath; 142 dirPath /= deviceDirName(bus, address); 143 if (hasHWMonDir == devices::createsHWMon::hasHWMonDir) 144 { 145 dirPath /= "hwmon"; 146 } 147 148 std::error_code ec; 149 // Ignore errors; anything but a clean 'true' is just fine as 'false' 150 return std::filesystem::exists(dirPath, ec); 151 } 152 153 static 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, 158 std::vector<std::string> channelNames, boost::asio::io_context& io, 159 const size_t retries = 5) 160 { 161 if (retries == 0U) 162 { 163 return -1; 164 } 165 166 // If it's already instantiated, we don't need to create it again. 167 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 168 { 169 // Try to create the device 170 createDevice(busPath, parameters, constructor); 171 172 // 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, 183 channelNames(std::move(channelNames)), retries, 184 &io](const boost::system::error_code& ec) mutable { 185 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, 192 std::move(channelNames), io, 193 retries - 1); 194 }); 195 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); 203 } 204 205 return 0; 206 } 207 208 void exportDevice(const std::string& type, 209 const devices::ExportTemplate& exportTemplate, 210 const nlohmann::json& configuration, 211 boost::asio::io_context& io) 212 { 213 std::string parameters = exportTemplate.parameters; 214 std::string busPath = exportTemplate.busPath; 215 std::string constructor = exportTemplate.add; 216 std::string destructor = exportTemplate.remove; 217 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir; 218 std::string name = "unknown"; 219 std::optional<uint64_t> bus; 220 std::optional<uint64_t> address; 221 std::vector<std::string> channels; 222 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( 232 keyPair.value().get<std::string>(), illegalNameRegex, "_"); 233 name = subsituteString; 234 } 235 else 236 { 237 subsituteString = jsonToString(keyPair.value()); 238 } 239 240 if (keyPair.key() == "Bus") 241 { 242 bus = keyPair.value().get<uint64_t>(); 243 } 244 else if (keyPair.key() == "Address") 245 { 246 address = keyPair.value().get<uint64_t>(); 247 } 248 else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux")) 249 { 250 channels = keyPair.value().get<std::vector<std::string>>(); 251 } 252 boost::replace_all(parameters, templateChar + keyPair.key(), 253 subsituteString); 254 boost::replace_all(busPath, templateChar + keyPair.key(), 255 subsituteString); 256 } 257 258 if (!bus || !address) 259 { 260 createDevice(busPath, parameters, constructor); 261 return; 262 } 263 264 buildDevice(name, busPath, parameters, *bus, *address, constructor, 265 destructor, hasHWMonDir, std::move(channels), io); 266 } 267 268 bool loadOverlays(const nlohmann::json& systemConfiguration, 269 boost::asio::io_context& io) 270 { 271 std::filesystem::create_directory(outputDir); 272 for (auto entity = systemConfiguration.begin(); 273 entity != systemConfiguration.end(); entity++) 274 { 275 auto findExposes = entity.value().find("Exposes"); 276 if (findExposes == entity.value().end() || 277 findExposes->type() != nlohmann::json::value_t::array) 278 { 279 continue; 280 } 281 282 for (const auto& configuration : *findExposes) 283 { 284 auto findStatus = configuration.find("Status"); 285 // status missing is assumed to be 'okay' 286 if (findStatus != configuration.end() && *findStatus == "disabled") 287 { 288 continue; 289 } 290 auto findType = configuration.find("Type"); 291 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>(); 297 auto device = devices::exportTemplates.find(type.c_str()); 298 if (device != devices::exportTemplates.end()) 299 { 300 exportDevice(type, device->second, configuration, io); 301 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. 308 lg2::debug("Device type {TYPE} not found in export map allowlist", 309 "TYPE", type); 310 } 311 } 312 313 return true; 314 } 315