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