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