1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "handler.hpp" 16 17 #include "errors.hpp" 18 #include "handler_impl.hpp" 19 #include "util.hpp" 20 21 #include <fcntl.h> 22 #include <ipmid/api.h> 23 #include <mtd/mtd-abi.h> 24 #include <mtd/mtd-user.h> 25 #include <sys/ioctl.h> 26 #include <unistd.h> 27 28 #include <cinttypes> 29 #include <cstdio> 30 #include <filesystem> 31 #include <fstream> 32 #include <map> 33 #include <nlohmann/json.hpp> 34 #include <phosphor-logging/elog-errors.hpp> 35 #include <phosphor-logging/log.hpp> 36 #include <sdbusplus/bus.hpp> 37 #include <sstream> 38 #include <string> 39 #include <string_view> 40 #include <tuple> 41 #include <xyz/openbmc_project/Common/error.hpp> 42 43 #ifndef NCSI_IF_NAME 44 #define NCSI_IF_NAME eth0 45 #endif 46 47 // To deal with receiving a string without quotes. 48 #define QUOTE(name) #name 49 #define STR(macro) QUOTE(macro) 50 #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) 51 52 namespace ipmi 53 { 54 std::uint8_t getChannelByName(const std::string& chName); 55 } 56 57 namespace google 58 { 59 namespace ipmi 60 { 61 namespace fs = std::filesystem; 62 using Json = nlohmann::json; 63 using namespace phosphor::logging; 64 using InternalFailure = 65 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 66 67 std::tuple<std::uint8_t, std::string> 68 Handler::getEthDetails(std::string intf) const 69 { 70 if (intf.empty()) 71 { 72 intf = NCSI_IF_NAME_STR; 73 } 74 return std::make_tuple(::ipmi::getChannelByName(intf), std::move(intf)); 75 } 76 77 std::int64_t Handler::getRxPackets(const std::string& name) const 78 { 79 std::ostringstream opath; 80 opath << "/sys/class/net/" << name << "/statistics/rx_packets"; 81 std::string path = opath.str(); 82 83 // Minor sanity & security check (of course, I'm less certain if unicode 84 // comes into play here. 85 // 86 // Basically you can't easily inject ../ or /../ into the path below. 87 if (name.find("/") != std::string::npos) 88 { 89 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); 90 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 91 } 92 93 std::error_code ec; 94 if (!fs::exists(path, ec)) 95 { 96 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str()); 97 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 98 } 99 // We're uninterested in the state of ec. 100 101 int64_t count = 0; 102 std::ifstream ifs; 103 ifs.exceptions(std::ifstream::failbit); 104 try 105 { 106 ifs.open(path); 107 ifs >> count; 108 } 109 catch (std::ios_base::failure& fail) 110 { 111 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 112 } 113 114 return count; 115 } 116 117 VersionTuple Handler::getCpldVersion(unsigned int id) const 118 { 119 std::ostringstream opath; 120 opath << "/run/cpld" << id << ".version"; 121 // Check for file 122 123 std::error_code ec; 124 if (!fs::exists(opath.str(), ec)) 125 { 126 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", 127 opath.str().c_str()); 128 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 129 } 130 // We're uninterested in the state of ec. 131 132 // If file exists, read. 133 std::ifstream ifs; 134 ifs.exceptions(std::ifstream::failbit); 135 std::string value; 136 try 137 { 138 ifs.open(opath.str()); 139 ifs >> value; 140 } 141 catch (std::ios_base::failure& fail) 142 { 143 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 144 } 145 146 // If value parses as expected, return version. 147 VersionTuple version = std::make_tuple(0, 0, 0, 0); 148 149 int num_fields = 150 std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, 151 &std::get<0>(version), &std::get<1>(version), 152 &std::get<2>(version), &std::get<3>(version)); 153 if (num_fields == 0) 154 { 155 std::fprintf(stderr, "Invalid version.\n"); 156 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 157 } 158 159 return version; 160 } 161 162 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; 163 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 164 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 165 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 166 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; 167 168 void Handler::psuResetDelay(std::uint32_t delay) const 169 { 170 std::ofstream ofs; 171 ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); 172 if (!ofs.good()) 173 { 174 std::fprintf(stderr, "Unable to open file for output.\n"); 175 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 176 } 177 178 ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; 179 if (ofs.fail()) 180 { 181 std::fprintf(stderr, "Write failed\n"); 182 ofs.close(); 183 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 184 } 185 186 // Write succeeded, please continue. 187 ofs.flush(); 188 ofs.close(); 189 190 auto bus = sdbusplus::bus::new_default(); 191 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 192 SYSTEMD_INTERFACE, "StartUnit"); 193 194 method.append(PSU_HARDRESET_TARGET); 195 method.append("replace"); 196 197 try 198 { 199 bus.call_noreply(method); 200 } 201 catch (const sdbusplus::exception::SdBusError& ex) 202 { 203 log<level::ERR>("Failed to call PSU hard reset"); 204 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 205 } 206 } 207 208 static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5"; 209 210 void Handler::psuResetOnShutdown() const 211 { 212 std::ofstream ofs; 213 ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out); 214 if (!ofs.good()) 215 { 216 std::fprintf(stderr, "Unable to open file for output.\n"); 217 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 218 } 219 ofs.close(); 220 } 221 222 uint32_t Handler::getFlashSize() 223 { 224 mtd_info_t info; 225 int fd = open("/dev/mtd0", O_RDONLY); 226 int err = ioctl(fd, MEMGETINFO, &info); 227 close(fd); 228 229 if (err) 230 { 231 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 232 } 233 return info.size; 234 } 235 236 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 237 { 238 // Check if we support this Entity ID. 239 auto it = _entityIdToName.find(id); 240 if (it == _entityIdToName.end()) 241 { 242 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 243 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 244 } 245 246 std::string entityName; 247 try 248 { 249 // Parse the JSON config file. 250 if (!_entityConfigParsed) 251 { 252 _entityConfig = parseConfig(_configFile); 253 _entityConfigParsed = true; 254 } 255 256 // Find the "entity id:entity instance" mapping to entity name. 257 entityName = readNameFromConfig(it->second, instance, _entityConfig); 258 if (entityName.empty()) 259 { 260 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 261 } 262 } 263 catch (InternalFailure& e) 264 { 265 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 266 } 267 268 return entityName; 269 } 270 271 std::string Handler::getMachineName() 272 { 273 const char* path = "/etc/os-release"; 274 std::ifstream ifs(path); 275 if (ifs.fail()) 276 { 277 std::fprintf(stderr, "Failed to open: %s\n", path); 278 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 279 } 280 281 std::string line; 282 while (true) 283 { 284 std::getline(ifs, line); 285 if (ifs.eof()) 286 { 287 std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n", 288 path); 289 throw IpmiException(IPMI_CC_INVALID); 290 } 291 if (ifs.fail()) 292 { 293 std::fprintf(stderr, "Failed to read: %s\n", path); 294 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 295 } 296 std::string_view lineView(line); 297 constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE="; 298 if (lineView.substr(0, prefix.size()) != prefix) 299 { 300 continue; 301 } 302 lineView.remove_prefix(prefix.size()); 303 lineView.remove_prefix( 304 std::min(lineView.find_first_not_of('"'), lineView.size())); 305 lineView.remove_suffix( 306 lineView.size() - 1 - 307 std::min(lineView.find_last_not_of('"'), lineView.size() - 1)); 308 return std::string(lineView); 309 } 310 } 311 312 static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay"; 313 static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target"; 314 315 void Handler::hostPowerOffDelay(std::uint32_t delay) const 316 { 317 // Set time delay 318 std::ofstream ofs; 319 ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out); 320 if (!ofs.good()) 321 { 322 std::fprintf(stderr, "Unable to open file for output.\n"); 323 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 324 } 325 326 ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl; 327 ofs.close(); 328 if (ofs.fail()) 329 { 330 std::fprintf(stderr, "Write failed\n"); 331 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 332 } 333 334 // Write succeeded, please continue. 335 auto bus = sdbusplus::bus::new_default(); 336 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 337 SYSTEMD_INTERFACE, "StartUnit"); 338 339 method.append(HOST_POWEROFF_TARGET); 340 method.append("replace"); 341 342 try 343 { 344 bus.call_noreply(method); 345 } 346 catch (const sdbusplus::exception::SdBusError& ex) 347 { 348 log<level::ERR>("Failed to call Power Off", 349 entry("WHAT=%s", ex.what())); 350 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 351 } 352 } 353 354 std::string readNameFromConfig(const std::string& type, uint8_t instance, 355 const Json& config) 356 { 357 static const std::vector<Json> empty{}; 358 std::vector<Json> readings = config.value(type, empty); 359 std::string name = ""; 360 361 for (const auto& j : readings) 362 { 363 uint8_t instanceNum = j.value("instance", 0); 364 // Not the instance we're interested in 365 if (instanceNum != instance) 366 { 367 continue; 368 } 369 370 // Found the instance we're interested in 371 name = j.value("name", ""); 372 373 break; 374 } 375 376 return name; 377 } 378 379 void Handler::buildI2cPcieMapping() 380 { 381 _pcie_i2c_map = buildPcieMap(); 382 } 383 384 size_t Handler::getI2cPcieMappingSize() const 385 { 386 return _pcie_i2c_map.size(); 387 } 388 389 std::tuple<std::uint32_t, std::string> 390 Handler::getI2cEntry(unsigned int entry) const 391 { 392 return _pcie_i2c_map[entry]; 393 } 394 395 } // namespace ipmi 396 } // namespace google 397