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