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