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