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