1 // Copyright 2022 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 <variant> 42 #include <xyz/openbmc_project/Common/error.hpp> 43 44 #ifndef NCSI_IF_NAME 45 #define NCSI_IF_NAME eth0 46 #endif 47 48 // To deal with receiving a string without quotes. 49 #define QUOTE(name) #name 50 #define STR(macro) QUOTE(macro) 51 #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) 52 53 namespace ipmi 54 { 55 std::uint8_t getChannelByName(const std::string& chName); 56 } 57 58 namespace google 59 { 60 namespace ipmi 61 { 62 namespace fs = std::filesystem; 63 using Json = nlohmann::json; 64 using namespace phosphor::logging; 65 using InternalFailure = 66 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 67 68 std::tuple<std::uint8_t, std::string> 69 Handler::getEthDetails(std::string intf) const 70 { 71 if (intf.empty()) 72 { 73 intf = NCSI_IF_NAME_STR; 74 } 75 return std::make_tuple(::ipmi::getChannelByName(intf), std::move(intf)); 76 } 77 78 std::int64_t Handler::getRxPackets(const std::string& name) const 79 { 80 std::ostringstream opath; 81 opath << "/sys/class/net/" << name << "/statistics/rx_packets"; 82 std::string path = opath.str(); 83 84 // Minor sanity & security check (of course, I'm less certain if unicode 85 // comes into play here. 86 // 87 // Basically you can't easily inject ../ or /../ into the path below. 88 if (name.find("/") != std::string::npos) 89 { 90 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); 91 throw IpmiException(::ipmi::ccInvalidFieldRequest); 92 } 93 94 std::error_code ec; 95 if (!fs::exists(path, ec)) 96 { 97 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str()); 98 throw IpmiException(::ipmi::ccInvalidFieldRequest); 99 } 100 // We're uninterested in the state of ec. 101 102 int64_t count = 0; 103 std::ifstream ifs; 104 ifs.exceptions(std::ifstream::failbit); 105 try 106 { 107 ifs.open(path); 108 ifs >> count; 109 } 110 catch (std::ios_base::failure& fail) 111 { 112 throw IpmiException(::ipmi::ccUnspecifiedError); 113 } 114 115 return count; 116 } 117 118 VersionTuple Handler::getCpldVersion(unsigned int id) const 119 { 120 std::ostringstream opath; 121 opath << "/run/cpld" << id << ".version"; 122 // Check for file 123 124 std::error_code ec; 125 if (!fs::exists(opath.str(), ec)) 126 { 127 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", 128 opath.str().c_str()); 129 throw IpmiException(::ipmi::ccInvalidFieldRequest); 130 } 131 // We're uninterested in the state of ec. 132 133 // If file exists, read. 134 std::ifstream ifs; 135 ifs.exceptions(std::ifstream::failbit); 136 std::string value; 137 try 138 { 139 ifs.open(opath.str()); 140 ifs >> value; 141 } 142 catch (std::ios_base::failure& fail) 143 { 144 throw IpmiException(::ipmi::ccUnspecifiedError); 145 } 146 147 // If value parses as expected, return version. 148 VersionTuple version = std::make_tuple(0, 0, 0, 0); 149 150 int num_fields = 151 std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, 152 &std::get<0>(version), &std::get<1>(version), 153 &std::get<2>(version), &std::get<3>(version)); 154 if (num_fields == 0) 155 { 156 std::fprintf(stderr, "Invalid version.\n"); 157 throw IpmiException(::ipmi::ccUnspecifiedError); 158 } 159 160 return version; 161 } 162 163 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; 164 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 165 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 166 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 167 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; 168 169 void Handler::psuResetDelay(std::uint32_t delay) const 170 { 171 std::ofstream ofs; 172 ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); 173 if (!ofs.good()) 174 { 175 std::fprintf(stderr, "Unable to open file for output.\n"); 176 throw IpmiException(::ipmi::ccUnspecifiedError); 177 } 178 179 ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; 180 if (ofs.fail()) 181 { 182 std::fprintf(stderr, "Write failed\n"); 183 ofs.close(); 184 throw IpmiException(::ipmi::ccUnspecifiedError); 185 } 186 187 // Write succeeded, please continue. 188 ofs.flush(); 189 ofs.close(); 190 191 auto bus = sdbusplus::bus::new_default(); 192 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 193 SYSTEMD_INTERFACE, "StartUnit"); 194 195 method.append(PSU_HARDRESET_TARGET); 196 method.append("replace"); 197 198 try 199 { 200 bus.call_noreply(method); 201 } 202 catch (const sdbusplus::exception::SdBusError& ex) 203 { 204 log<level::ERR>("Failed to call PSU hard reset"); 205 throw IpmiException(::ipmi::ccUnspecifiedError); 206 } 207 } 208 209 static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5"; 210 211 void Handler::psuResetOnShutdown() const 212 { 213 std::ofstream ofs; 214 ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out); 215 if (!ofs.good()) 216 { 217 std::fprintf(stderr, "Unable to open file for output.\n"); 218 throw IpmiException(::ipmi::ccUnspecifiedError); 219 } 220 ofs.close(); 221 } 222 223 uint32_t Handler::getFlashSize() 224 { 225 mtd_info_t info; 226 int fd = open("/dev/mtd0", O_RDONLY); 227 int err = ioctl(fd, MEMGETINFO, &info); 228 close(fd); 229 230 if (err) 231 { 232 throw IpmiException(::ipmi::ccUnspecifiedError); 233 } 234 return info.size; 235 } 236 237 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 238 { 239 // Check if we support this Entity ID. 240 auto it = _entityIdToName.find(id); 241 if (it == _entityIdToName.end()) 242 { 243 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 244 throw IpmiException(::ipmi::ccInvalidFieldRequest); 245 } 246 247 std::string entityName; 248 try 249 { 250 // Parse the JSON config file. 251 if (!_entityConfigParsed) 252 { 253 _entityConfig = parseConfig(_configFile); 254 _entityConfigParsed = true; 255 } 256 257 // Find the "entity id:entity instance" mapping to entity name. 258 entityName = readNameFromConfig(it->second, instance, _entityConfig); 259 if (entityName.empty()) 260 { 261 throw IpmiException(::ipmi::ccInvalidFieldRequest); 262 } 263 } 264 catch (InternalFailure& e) 265 { 266 throw IpmiException(::ipmi::ccUnspecifiedError); 267 } 268 269 return entityName; 270 } 271 272 std::string Handler::getMachineName() 273 { 274 const char* path = "/etc/os-release"; 275 std::ifstream ifs(path); 276 if (ifs.fail()) 277 { 278 std::fprintf(stderr, "Failed to open: %s\n", path); 279 throw IpmiException(::ipmi::ccUnspecifiedError); 280 } 281 282 std::string line; 283 while (true) 284 { 285 std::getline(ifs, line); 286 if (ifs.eof()) 287 { 288 std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n", 289 path); 290 throw IpmiException(::ipmi::ccInvalidCommand); 291 } 292 if (ifs.fail()) 293 { 294 std::fprintf(stderr, "Failed to read: %s\n", path); 295 throw IpmiException(::ipmi::ccUnspecifiedError); 296 } 297 std::string_view lineView(line); 298 constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE="; 299 if (lineView.substr(0, prefix.size()) != prefix) 300 { 301 continue; 302 } 303 lineView.remove_prefix(prefix.size()); 304 lineView.remove_prefix( 305 std::min(lineView.find_first_not_of('"'), lineView.size())); 306 lineView.remove_suffix( 307 lineView.size() - 1 - 308 std::min(lineView.find_last_not_of('"'), lineView.size() - 1)); 309 return std::string(lineView); 310 } 311 } 312 313 static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay"; 314 static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target"; 315 316 void Handler::hostPowerOffDelay(std::uint32_t delay) const 317 { 318 // Set time delay 319 std::ofstream ofs; 320 ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out); 321 if (!ofs.good()) 322 { 323 std::fprintf(stderr, "Unable to open file for output.\n"); 324 throw IpmiException(::ipmi::ccUnspecifiedError); 325 } 326 327 ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl; 328 ofs.close(); 329 if (ofs.fail()) 330 { 331 std::fprintf(stderr, "Write failed\n"); 332 throw IpmiException(::ipmi::ccUnspecifiedError); 333 } 334 335 // Write succeeded, please continue. 336 auto bus = sdbusplus::bus::new_default(); 337 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 338 SYSTEMD_INTERFACE, "StartUnit"); 339 340 method.append(HOST_POWEROFF_TARGET); 341 method.append("replace"); 342 343 try 344 { 345 bus.call_noreply(method); 346 } 347 catch (const sdbusplus::exception::SdBusError& ex) 348 { 349 log<level::ERR>("Failed to call Power Off", 350 entry("WHAT=%s", ex.what())); 351 throw IpmiException(::ipmi::ccUnspecifiedError); 352 } 353 } 354 355 std::string readNameFromConfig(const std::string& type, uint8_t instance, 356 const Json& config) 357 { 358 static const std::vector<Json> empty{}; 359 std::vector<Json> readings = config.value(type, empty); 360 std::string name = ""; 361 362 for (const auto& j : readings) 363 { 364 uint8_t instanceNum = j.value("instance", 0); 365 // Not the instance we're interested in 366 if (instanceNum != instance) 367 { 368 continue; 369 } 370 371 // Found the instance we're interested in 372 name = j.value("name", ""); 373 374 break; 375 } 376 377 return name; 378 } 379 380 void Handler::buildI2cPcieMapping() 381 { 382 _pcie_i2c_map = buildPcieMap(); 383 } 384 385 size_t Handler::getI2cPcieMappingSize() const 386 { 387 return _pcie_i2c_map.size(); 388 } 389 390 std::tuple<std::uint32_t, std::string> 391 Handler::getI2cEntry(unsigned int entry) const 392 { 393 return _pcie_i2c_map[entry]; 394 } 395 396 namespace 397 { 398 399 static constexpr std::string_view ACCEL_OOB_ROOT = "/com/google/customAccel/"; 400 static constexpr char ACCEL_OOB_SERVICE[] = "com.google.custom_accel"; 401 static constexpr char ACCEL_OOB_INTERFACE[] = "com.google.custom_accel.BAR"; 402 403 // C type for "a{oa{sa{sv}}}" from DBus.ObjectManager::GetManagedObjects() 404 using AnyType = std::variant<std::string, uint8_t, uint32_t, uint64_t>; 405 using AnyTypeList = std::vector<std::pair<std::string, AnyType>>; 406 using NamedArrayOfAnyTypeLists = 407 std::vector<std::pair<std::string, AnyTypeList>>; 408 using ArrayOfObjectPathsAndTieredAnyTypeLists = std::vector< 409 std::pair<sdbusplus::message::object_path, NamedArrayOfAnyTypeLists>>; 410 411 } // namespace 412 413 sdbusplus::bus::bus Handler::accelOobGetDbus() const 414 { 415 return sdbusplus::bus::new_default(); 416 } 417 418 uint32_t Handler::accelOobDeviceCount() const 419 { 420 ArrayOfObjectPathsAndTieredAnyTypeLists data; 421 422 try 423 { 424 auto bus = accelOobGetDbus(); 425 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", 426 "org.freedesktop.DBus.ObjectManager", 427 "GetManagedObjects"); 428 bus.call(method).read(data); 429 } 430 catch (const sdbusplus::exception::SdBusError& ex) 431 { 432 log<level::ERR>( 433 "Failed to call GetManagedObjects on com.google.custom_accel", 434 entry("WHAT=%s", ex.what())); 435 throw IpmiException(::ipmi::ccUnspecifiedError); 436 } 437 438 return data.size(); 439 } 440 441 std::string Handler::accelOobDeviceName(size_t index) const 442 { 443 ArrayOfObjectPathsAndTieredAnyTypeLists data; 444 445 try 446 { 447 auto bus = accelOobGetDbus(); 448 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", 449 "org.freedesktop.DBus.ObjectManager", 450 "GetManagedObjects"); 451 bus.call(method).read(data); 452 } 453 catch (const sdbusplus::exception::SdBusError& ex) 454 { 455 log<level::ERR>( 456 "Failed to call GetManagedObjects on com.google.custom_accel", 457 entry("WHAT=%s", ex.what())); 458 throw IpmiException(::ipmi::ccUnspecifiedError); 459 } 460 461 if (index >= data.size()) 462 { 463 log<level::WARNING>( 464 "Requested index is larger than the number of entries.", 465 entry("INDEX=%zu", index), entry("NUM_NAMES=%zu", data.size())); 466 throw IpmiException(::ipmi::ccParmOutOfRange); 467 } 468 469 std::string_view name(data[index].first.str); 470 if (!name.starts_with(ACCEL_OOB_ROOT)) 471 { 472 throw IpmiException(::ipmi::ccInvalidCommand); 473 } 474 name.remove_prefix(ACCEL_OOB_ROOT.length()); 475 return std::string(name); 476 } 477 478 uint64_t Handler::accelOobRead(std::string_view name, uint64_t address, 479 uint8_t num_bytes) const 480 { 481 static constexpr char ACCEL_OOB_METHOD[] = "Read"; 482 483 std::string object_name(ACCEL_OOB_ROOT); 484 object_name.append(name); 485 486 auto bus = accelOobGetDbus(); 487 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), 488 ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD); 489 method.append(address, static_cast<uint64_t>(num_bytes)); 490 491 std::vector<uint8_t> bytes; 492 493 try 494 { 495 bus.call(method).read(bytes); 496 } 497 catch (const sdbusplus::exception::SdBusError& ex) 498 { 499 log<level::ERR>("Failed to call Read on com.google.custom_accel", 500 entry("WHAT=%s", ex.what()), 501 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 502 entry("DBUS_OBJECT=%s", object_name.c_str()), 503 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 504 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 505 entry("DBUS_ARG_ADDRESS=%016llx", address), 506 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes)); 507 throw IpmiException(::ipmi::ccUnspecifiedError); 508 } 509 510 if (bytes.size() < num_bytes) 511 { 512 log<level::ERR>( 513 "Call to Read on com.google.custom_accel didn't return the expected" 514 " number of bytes.", 515 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 516 entry("DBUS_OBJECT=%s", object_name.c_str()), 517 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 518 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 519 entry("DBUS_ARG_ADDRESS=%016llx", address), 520 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 521 entry("DBUS_RETURN_SIZE=%zu", bytes.size())); 522 throw IpmiException(::ipmi::ccUnspecifiedError); 523 } 524 525 if (bytes.size() > sizeof(uint64_t)) 526 { 527 log<level::ERR>( 528 "Call to Read on com.google.custom_accel returned more than 8B.", 529 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 530 entry("DBUS_OBJECT=%s", object_name.c_str()), 531 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 532 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 533 entry("DBUS_ARG_ADDRESS=%016llx", address), 534 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 535 entry("DBUS_RETURN_SIZE=%zu", bytes.size())); 536 throw IpmiException(::ipmi::ccReqDataTruncated); 537 } 538 539 uint64_t data = 0; 540 for (size_t i = 0; i < num_bytes; ++i) 541 { 542 data = (data << 8) | bytes[i]; 543 } 544 545 return data; 546 } 547 548 void Handler::accelOobWrite(std::string_view name, uint64_t address, 549 uint8_t num_bytes, uint64_t data) const 550 { 551 static constexpr std::string_view ACCEL_OOB_METHOD = "Write"; 552 553 std::string object_name(ACCEL_OOB_ROOT); 554 object_name.append(name); 555 556 if (num_bytes > sizeof(data)) 557 { 558 log<level::ERR>( 559 "Call to Write on com.google.custom_accel requested more than 8B.", 560 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 561 entry("DBUS_OBJECT=%s", object_name.c_str()), 562 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 563 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), 564 entry("DBUS_ARG_ADDRESS=%016llx", address), 565 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 566 entry("DBUS_ARG_DATA=%016llx", data)); 567 throw IpmiException(::ipmi::ccParmOutOfRange); 568 } 569 570 std::vector<uint8_t> bytes; 571 bytes.reserve(num_bytes); 572 for (size_t i = 0; i < num_bytes; ++i) 573 { 574 bytes.emplace_back(data & 0xff); 575 data >>= 8; 576 } 577 578 try 579 { 580 auto bus = accelOobGetDbus(); 581 auto method = 582 bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), 583 ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD.data()); 584 method.append(address, bytes); 585 bus.call_noreply(method); 586 } 587 catch (const sdbusplus::exception::SdBusError& ex) 588 { 589 log<level::ERR>("Failed to call Write on com.google.custom_accel", 590 entry("WHAT=%s", ex.what()), 591 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 592 entry("DBUS_OBJECT=%s", object_name.c_str()), 593 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 594 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), 595 entry("DBUS_ARG_ADDRESS=%016llx", address), 596 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 597 entry("DBUS_ARG_DATA=%016llx", data)); 598 throw IpmiException(::ipmi::ccUnspecifiedError); 599 } 600 } 601 602 std::vector<uint8_t> Handler::pcieBifurcation(uint8_t index) 603 { 604 return bifurcationHelper.get().getBifurcation(index).value_or( 605 std::vector<uint8_t>{}); 606 } 607 608 } // namespace ipmi 609 } // namespace google 610