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 168 buildDevice(const std::string& name, const std::string& busPath, 169 const std::string& parameters, uint64_t bus, uint64_t address, 170 const std::string& constructor, const std::string& destructor, 171 const devices::createsHWMon hasHWMonDir, 172 std::vector<std::string> channelNames, const size_t retries = 5) 173 { 174 if (retries == 0U) 175 { 176 return -1; 177 } 178 179 // If it's already instantiated, we don't need to create it again. 180 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 181 { 182 // Try to create the device 183 createDevice(busPath, parameters, constructor); 184 185 // If it didn't work, delete it and try again in 500ms 186 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 187 { 188 deleteDevice(busPath, address, destructor); 189 190 std::shared_ptr<boost::asio::steady_timer> createTimer = 191 std::make_shared<boost::asio::steady_timer>(io); 192 createTimer->expires_after(std::chrono::milliseconds(500)); 193 createTimer->async_wait( 194 [createTimer, name, busPath, parameters, bus, address, 195 constructor, destructor, hasHWMonDir, 196 channelNames(std::move(channelNames)), 197 retries](const boost::system::error_code& ec) mutable { 198 if (ec) 199 { 200 std::cerr << "Timer error: " << ec << "\n"; 201 return -2; 202 } 203 return buildDevice(name, busPath, parameters, bus, address, 204 constructor, destructor, hasHWMonDir, 205 std::move(channelNames), retries - 1); 206 }); 207 return -1; 208 } 209 } 210 211 // Link the mux channels if needed once the device is created. 212 if (!channelNames.empty()) 213 { 214 linkMux(name, bus, address, channelNames); 215 } 216 217 return 0; 218 } 219 220 void exportDevice(const std::string& type, 221 const devices::ExportTemplate& exportTemplate, 222 const nlohmann::json& configuration) 223 { 224 std::string parameters = exportTemplate.parameters; 225 std::string busPath = exportTemplate.busPath; 226 std::string constructor = exportTemplate.add; 227 std::string destructor = exportTemplate.remove; 228 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir; 229 std::string name = "unknown"; 230 std::optional<uint64_t> bus; 231 std::optional<uint64_t> address; 232 std::vector<std::string> channels; 233 234 for (auto keyPair = configuration.begin(); keyPair != configuration.end(); 235 keyPair++) 236 { 237 std::string subsituteString; 238 239 if (keyPair.key() == "Name" && 240 keyPair.value().type() == nlohmann::json::value_t::string) 241 { 242 subsituteString = std::regex_replace( 243 keyPair.value().get<std::string>(), illegalNameRegex, "_"); 244 name = subsituteString; 245 } 246 else 247 { 248 subsituteString = jsonToString(keyPair.value()); 249 } 250 251 if (keyPair.key() == "Bus") 252 { 253 bus = keyPair.value().get<uint64_t>(); 254 } 255 else if (keyPair.key() == "Address") 256 { 257 address = keyPair.value().get<uint64_t>(); 258 } 259 else if (keyPair.key() == "ChannelNames" && type.ends_with("Mux")) 260 { 261 channels = keyPair.value().get<std::vector<std::string>>(); 262 } 263 boost::replace_all(parameters, templateChar + keyPair.key(), 264 subsituteString); 265 boost::replace_all(busPath, templateChar + keyPair.key(), 266 subsituteString); 267 } 268 269 if (!bus || !address) 270 { 271 createDevice(busPath, parameters, constructor); 272 return; 273 } 274 275 buildDevice(name, busPath, parameters, *bus, *address, constructor, 276 destructor, hasHWMonDir, std::move(channels)); 277 } 278 279 bool loadOverlays(const nlohmann::json& systemConfiguration) 280 { 281 std::filesystem::create_directory(outputDir); 282 for (auto entity = systemConfiguration.begin(); 283 entity != systemConfiguration.end(); entity++) 284 { 285 auto findExposes = entity.value().find("Exposes"); 286 if (findExposes == entity.value().end() || 287 findExposes->type() != nlohmann::json::value_t::array) 288 { 289 continue; 290 } 291 292 for (const auto& configuration : *findExposes) 293 { 294 auto findStatus = configuration.find("Status"); 295 // status missing is assumed to be 'okay' 296 if (findStatus != configuration.end() && *findStatus == "disabled") 297 { 298 continue; 299 } 300 auto findType = configuration.find("Type"); 301 if (findType == configuration.end() || 302 findType->type() != nlohmann::json::value_t::string) 303 { 304 continue; 305 } 306 std::string type = findType.value().get<std::string>(); 307 auto device = devices::exportTemplates.find(type.c_str()); 308 if (device != devices::exportTemplates.end()) 309 { 310 exportDevice(type, device->second, configuration); 311 continue; 312 } 313 314 // Because many devices are intentionally not exportable, 315 // this error message is not printed in all situations. 316 // If wondering why your device not appearing, add your type to 317 // the exportTemplates array in the devices.hpp file. 318 if constexpr (debug) 319 { 320 std::cerr << "Device type " << type 321 << " not found in export map allowlist\n"; 322 } 323 } 324 } 325 326 return true; 327 } 328