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