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 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, size_t busIndex, size_t address, 73 const nlohmann::json::array_t& 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 = 90 channelNames[channelIndex].get_ptr<const std::string*>(); 91 if (channelName == nullptr) 92 { 93 continue; 94 } 95 if (channelName->empty()) 96 { 97 continue; 98 } 99 100 std::filesystem::path channelPath = 101 devDir / ("channel-" + std::to_string(channelIndex)); 102 if (!is_symlink(channelPath)) 103 { 104 std::cerr << channelPath << " for mux channel " << *channelName 105 << " doesn't exist!\n"; 106 continue; 107 } 108 std::filesystem::path bus = std::filesystem::read_symlink(channelPath); 109 110 std::filesystem::path fp("/dev" / bus.filename()); 111 std::filesystem::path link(linkDir / *channelName); 112 113 std::filesystem::create_symlink(fp, link, ec); 114 if (ec) 115 { 116 std::cerr << "Failure creating symlink for " << fp << " to " << link 117 << "\n"; 118 } 119 } 120 } 121 122 static int deleteDevice(const std::string& busPath, 123 const std::shared_ptr<uint64_t>& address, 124 const std::string& destructor) 125 { 126 if (!address) 127 { 128 return -1; 129 } 130 std::filesystem::path deviceDestructor(busPath); 131 deviceDestructor /= destructor; 132 std::ofstream deviceFile(deviceDestructor); 133 if (!deviceFile.good()) 134 { 135 std::cerr << "Error writing " << deviceDestructor << "\n"; 136 return -1; 137 } 138 deviceFile << std::to_string(*address); 139 deviceFile.close(); 140 return 0; 141 } 142 143 static int createDevice(const std::string& busPath, 144 const std::string& parameters, 145 const std::string& constructor) 146 { 147 std::filesystem::path deviceConstructor(busPath); 148 deviceConstructor /= constructor; 149 std::ofstream deviceFile(deviceConstructor); 150 if (!deviceFile.good()) 151 { 152 std::cerr << "Error writing " << deviceConstructor << "\n"; 153 return -1; 154 } 155 deviceFile << parameters; 156 deviceFile.close(); 157 158 return 0; 159 } 160 161 static bool deviceIsCreated(const std::string& busPath, 162 const std::shared_ptr<uint64_t>& bus, 163 const std::shared_ptr<uint64_t>& address, 164 const devices::createsHWMon hasHWMonDir) 165 { 166 if (!bus || !address) 167 { 168 return false; 169 } 170 171 std::filesystem::path dirPath = busPath; 172 dirPath /= deviceDirName(*bus, *address); 173 if (hasHWMonDir == devices::createsHWMon::hasHWMonDir) 174 { 175 dirPath /= "hwmon"; 176 } 177 178 std::error_code ec; 179 // Ignore errors; anything but a clean 'true' is just fine as 'false' 180 return std::filesystem::exists(dirPath, ec); 181 } 182 183 static int buildDevice(const std::string& busPath, 184 const std::string& parameters, 185 const std::shared_ptr<uint64_t>& bus, 186 const std::shared_ptr<uint64_t>& address, 187 const std::string& constructor, 188 const std::string& destructor, 189 const devices::createsHWMon hasHWMonDir, 190 const size_t retries = 5) 191 { 192 if (retries == 0U) 193 { 194 return -1; 195 } 196 197 // If it's already instantiated, there's nothing we need to do. 198 if (deviceIsCreated(busPath, bus, address, hasHWMonDir)) 199 { 200 return 0; 201 } 202 203 // Try to create the device 204 createDevice(busPath, parameters, constructor); 205 206 // If it didn't work, delete it and try again in 500ms 207 if (!deviceIsCreated(busPath, bus, address, hasHWMonDir)) 208 { 209 deleteDevice(busPath, address, destructor); 210 211 std::shared_ptr<boost::asio::steady_timer> createTimer = 212 std::make_shared<boost::asio::steady_timer>(io); 213 createTimer->expires_after(std::chrono::milliseconds(500)); 214 createTimer->async_wait([createTimer, busPath, parameters, bus, address, 215 constructor, destructor, hasHWMonDir, 216 retries](const boost::system::error_code& ec) { 217 if (ec) 218 { 219 std::cerr << "Timer error: " << ec << "\n"; 220 return -2; 221 } 222 return buildDevice(busPath, parameters, bus, address, constructor, 223 destructor, hasHWMonDir, retries - 1); 224 }); 225 } 226 227 return 0; 228 } 229 230 void exportDevice(const std::string& type, 231 const devices::ExportTemplate& exportTemplate, 232 const nlohmann::json& configuration) 233 { 234 235 std::string parameters = exportTemplate.parameters; 236 std::string busPath = exportTemplate.busPath; 237 std::string constructor = exportTemplate.add; 238 std::string destructor = exportTemplate.remove; 239 devices::createsHWMon hasHWMonDir = exportTemplate.hasHWMonDir; 240 std::string name = "unknown"; 241 std::shared_ptr<uint64_t> bus = nullptr; 242 std::shared_ptr<uint64_t> address = nullptr; 243 const nlohmann::json::array_t* channels = nullptr; 244 245 for (auto keyPair = configuration.begin(); keyPair != configuration.end(); 246 keyPair++) 247 { 248 std::string subsituteString; 249 250 if (keyPair.key() == "Name" && 251 keyPair.value().type() == nlohmann::json::value_t::string) 252 { 253 subsituteString = std::regex_replace( 254 keyPair.value().get<std::string>(), illegalNameRegex, "_"); 255 name = subsituteString; 256 } 257 else 258 { 259 subsituteString = jsonToString(keyPair.value()); 260 } 261 262 if (keyPair.key() == "Bus") 263 { 264 bus = std::make_shared<uint64_t>( 265 *keyPair.value().get_ptr<const uint64_t*>()); 266 } 267 else if (keyPair.key() == "Address") 268 { 269 address = std::make_shared<uint64_t>( 270 *keyPair.value().get_ptr<const uint64_t*>()); 271 } 272 else if (keyPair.key() == "ChannelNames") 273 { 274 channels = 275 keyPair.value().get_ptr<const nlohmann::json::array_t*>(); 276 } 277 boost::replace_all(parameters, templateChar + keyPair.key(), 278 subsituteString); 279 boost::replace_all(busPath, templateChar + keyPair.key(), 280 subsituteString); 281 } 282 283 int err = buildDevice(busPath, parameters, bus, address, constructor, 284 destructor, hasHWMonDir); 285 286 if ((err == 0) && boost::ends_with(type, "Mux") && bus && address && 287 (channels != nullptr)) 288 { 289 linkMux(name, static_cast<size_t>(*bus), static_cast<size_t>(*address), 290 *channels); 291 } 292 } 293 294 bool loadOverlays(const nlohmann::json& systemConfiguration) 295 { 296 std::filesystem::create_directory(outputDir); 297 for (auto entity = systemConfiguration.begin(); 298 entity != systemConfiguration.end(); entity++) 299 { 300 auto findExposes = entity.value().find("Exposes"); 301 if (findExposes == entity.value().end() || 302 findExposes->type() != nlohmann::json::value_t::array) 303 { 304 continue; 305 } 306 307 for (const auto& configuration : *findExposes) 308 { 309 auto findStatus = configuration.find("Status"); 310 // status missing is assumed to be 'okay' 311 if (findStatus != configuration.end() && *findStatus == "disabled") 312 { 313 continue; 314 } 315 auto findType = configuration.find("Type"); 316 if (findType == configuration.end() || 317 findType->type() != nlohmann::json::value_t::string) 318 { 319 continue; 320 } 321 std::string type = findType.value().get<std::string>(); 322 auto device = devices::exportTemplates.find(type.c_str()); 323 if (device != devices::exportTemplates.end()) 324 { 325 exportDevice(type, device->second, configuration); 326 continue; 327 } 328 329 // Because many devices are intentionally not exportable, 330 // this error message is not printed in all situations. 331 // If wondering why your device not appearing, add your type to 332 // the exportTemplates array in the devices.hpp file. 333 if constexpr (debug) 334 { 335 std::cerr << "Device type " << type 336 << " not found in export map whitelist\n"; 337 } 338 } 339 } 340 341 return true; 342 } 343