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