// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "handler.hpp" #include "bm_config.h" #include "bm_instance.hpp" #include "bmc_mode_enum.hpp" #include "errors.hpp" #include "handler_impl.hpp" #include "util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NCSI_IF_NAME #define NCSI_IF_NAME eth0 #endif // To deal with receiving a string without quotes. #define QUOTE(name) #name #define STR(macro) QUOTE(macro) #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) namespace ipmi { std::uint8_t getChannelByName(const std::string& chName); } namespace google { namespace ipmi { using Json = nlohmann::json; using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using Value = std::variant; uint8_t isBmcInBareMetalMode(const std::unique_ptr& fs) { #if BARE_METAL return static_cast(BmcMode::BM_MODE); #else std::error_code ec; if (fs->exists(bmDriveCleaningDoneAckFlagPath, ec)) { stdplus::print( stderr, "{} exists so we acked cleaning done and must be in BM mode\n", bmDriveCleaningDoneAckFlagPath); return static_cast(BmcMode::BM_MODE); } if (fs->exists(bmDriveCleaningDoneFlagPath, ec)) { fs->rename(bmDriveCleaningDoneFlagPath, bmDriveCleaningDoneAckFlagPath, ec); stdplus::print( stderr, "{} exists so we just finished cleaning and must be in BM mode\n", bmDriveCleaningDoneFlagPath); return static_cast(BmcMode::BM_MODE); } if (fs->exists(BM_SIGNAL_PATH, ec)) { if (!fs->exists(bmDriveCleaningFlagPath, ec)) { fs->create(bmDriveCleaningFlagPath); } stdplus::print( stderr, "{} exists and no done/ack flag, we must be in BM cleaning mode\n", BM_SIGNAL_PATH); return static_cast(BmcMode::BM_CLEANING_MODE); } stdplus::print( stderr, "Unable to find any BM state files so we must not be in BM mode\n"); return static_cast(BmcMode::NON_BM_MODE); #endif } uint8_t Handler::getBmcMode() { // BM_CLEANING_MODE is not implemented yet return isBmcInBareMetalMode(this->getFs()); } std::tuple Handler::getEthDetails(std::string intf) const { if (intf.empty()) { intf = NCSI_IF_NAME_STR; } return std::make_tuple(::ipmi::getChannelByName(intf), std::move(intf)); } std::int64_t Handler::getRxPackets(const std::string& name) const { std::ostringstream opath; opath << "/sys/class/net/" << name << "/statistics/rx_packets"; std::string path = opath.str(); // Minor sanity & security check (of course, I'm less certain if unicode // comes into play here. // // Basically you can't easily inject ../ or /../ into the path below. if (name.find("/") != std::string::npos) { stdplus::print(stderr, "Invalid or illegal name: '{}'\n", name); throw IpmiException(::ipmi::ccInvalidFieldRequest); } std::error_code ec; if (!this->getFs()->exists(path, ec)) { stdplus::print(stderr, "Path: '{}' doesn't exist.\n", path); throw IpmiException(::ipmi::ccInvalidFieldRequest); } // We're uninterested in the state of ec. int64_t count = 0; std::ifstream ifs; ifs.exceptions(std::ifstream::failbit); try { ifs.open(path); ifs >> count; } catch (std::ios_base::failure& fail) { throw IpmiException(::ipmi::ccUnspecifiedError); } return count; } VersionTuple Handler::getCpldVersion(unsigned int id) const { std::ostringstream opath; opath << "/run/cpld" << id << ".version"; // Check for file std::error_code ec; if (!this->getFs()->exists(opath.str(), ec)) { stdplus::print(stderr, "Path: '{}' doesn't exist.\n", opath.str()); throw IpmiException(::ipmi::ccInvalidFieldRequest); } // We're uninterested in the state of ec. // If file exists, read. std::ifstream ifs; ifs.exceptions(std::ifstream::failbit); std::string value; try { ifs.open(opath.str()); ifs >> value; } catch (std::ios_base::failure& fail) { throw IpmiException(::ipmi::ccUnspecifiedError); } // If value parses as expected, return version. VersionTuple version = std::make_tuple(0, 0, 0, 0); int num_fields = std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, &std::get<0>(version), &std::get<1>(version), &std::get<2>(version), &std::get<3>(version)); if (num_fields == 0) { stdplus::print(stderr, "Invalid version.\n"); throw IpmiException(::ipmi::ccUnspecifiedError); } return version; } static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; void Handler::psuResetDelay(std::uint32_t delay) const { std::ofstream ofs; ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); if (!ofs.good()) { stdplus::print(stderr, "Unable to open file for output.\n"); throw IpmiException(::ipmi::ccUnspecifiedError); } ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; if (ofs.fail()) { stdplus::print(stderr, "Write failed\n"); ofs.close(); throw IpmiException(::ipmi::ccUnspecifiedError); } // Write succeeded, please continue. ofs.flush(); ofs.close(); auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, SYSTEMD_INTERFACE, "StartUnit"); method.append(PSU_HARDRESET_TARGET); method.append("replace"); try { bus.call_noreply(method); } catch (const sdbusplus::exception::SdBusError& ex) { log("Failed to call PSU hard reset"); throw IpmiException(::ipmi::ccUnspecifiedError); } } static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5"; void Handler::psuResetOnShutdown() const { std::ofstream ofs; ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out); if (!ofs.good()) { stdplus::print(stderr, "Unable to open file for output.\n"); throw IpmiException(::ipmi::ccUnspecifiedError); } ofs.close(); } uint32_t Handler::getFlashSize() { mtd_info_t info; int fd = open("/dev/mtd0", O_RDONLY); int err = ioctl(fd, MEMGETINFO, &info); close(fd); if (err) { throw IpmiException(::ipmi::ccUnspecifiedError); } return info.size; } std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) { // Check if we support this Entity ID. auto it = _entityIdToName.find(id); if (it == _entityIdToName.end()) { log("Unknown Entity ID", entry("ENTITY_ID=%d", id)); throw IpmiException(::ipmi::ccInvalidFieldRequest); } std::string entityName; try { // Parse the JSON config file. if (!_entityConfigParsed) { _entityConfig = parseConfig(_configFile); _entityConfigParsed = true; } // Find the "entity id:entity instance" mapping to entity name. entityName = readNameFromConfig(it->second, instance, _entityConfig); if (entityName.empty()) { throw IpmiException(::ipmi::ccInvalidFieldRequest); } } catch (InternalFailure& e) { throw IpmiException(::ipmi::ccUnspecifiedError); } return entityName; } std::string Handler::getMachineName() { const char* path = "/etc/os-release"; std::ifstream ifs(path); if (ifs.fail()) { stdplus::print(stderr, "Failed to open: {}\n", path); throw IpmiException(::ipmi::ccUnspecifiedError); } std::string line; while (true) { std::getline(ifs, line); if (ifs.eof()) { stdplus::print(stderr, "Failed to find OPENBMC_TARGET_MACHINE: {}\n", path); throw IpmiException(::ipmi::ccInvalidCommand); } if (ifs.fail()) { stdplus::print(stderr, "Failed to read: {}\n", path); throw IpmiException(::ipmi::ccUnspecifiedError); } std::string_view lineView(line); constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE="; if (lineView.substr(0, prefix.size()) != prefix) { continue; } lineView.remove_prefix(prefix.size()); lineView.remove_prefix( std::min(lineView.find_first_not_of('"'), lineView.size())); lineView.remove_suffix( lineView.size() - 1 - std::min(lineView.find_last_not_of('"'), lineView.size() - 1)); return std::string(lineView); } } static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay"; static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target"; void Handler::hostPowerOffDelay(std::uint32_t delay) const { // Set time delay std::ofstream ofs; ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out); if (!ofs.good()) { stdplus::print(stderr, "Unable to open file for output.\n"); throw IpmiException(::ipmi::ccUnspecifiedError); } ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl; ofs.close(); if (ofs.fail()) { stdplus::print(stderr, "Write failed\n"); throw IpmiException(::ipmi::ccUnspecifiedError); } // Write succeeded, please continue. auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, SYSTEMD_INTERFACE, "StartUnit"); method.append(HOST_POWEROFF_TARGET); method.append("replace"); try { bus.call_noreply(method); } catch (const sdbusplus::exception::SdBusError& ex) { log("Failed to call Power Off", entry("WHAT=%s", ex.what())); throw IpmiException(::ipmi::ccUnspecifiedError); } } std::string readNameFromConfig(const std::string& type, uint8_t instance, const Json& config) { static const std::vector empty{}; std::vector readings = config.value(type, empty); std::string name = ""; for (const auto& j : readings) { uint8_t instanceNum = j.value("instance", 0); // Not the instance we're interested in if (instanceNum != instance) { continue; } // Found the instance we're interested in name = j.value("name", ""); break; } return name; } void Handler::buildI2cPcieMapping() { _pcie_i2c_map = buildPcieMap(); } size_t Handler::getI2cPcieMappingSize() const { return _pcie_i2c_map.size(); } std::tuple Handler::getI2cEntry(unsigned int entry) const { return _pcie_i2c_map[entry]; } namespace { static constexpr std::string_view ACCEL_OOB_ROOT = "/com/google/customAccel/"; static constexpr char ACCEL_OOB_SERVICE[] = "com.google.custom_accel"; static constexpr char ACCEL_OOB_INTERFACE[] = "com.google.custom_accel.BAR"; // C type for "a{oa{sa{sv}}}" from DBus.ObjectManager::GetManagedObjects() using AnyType = std::variant; using AnyTypeList = std::vector>; using NamedArrayOfAnyTypeLists = std::vector>; using ArrayOfObjectPathsAndTieredAnyTypeLists = std::vector< std::pair>; } // namespace sdbusplus::bus_t Handler::getDbus() const { return sdbusplus::bus::new_default(); } const std::unique_ptr& Handler::getFs() const { return this->fsPtr; } uint32_t Handler::accelOobDeviceCount() const { ArrayOfObjectPathsAndTieredAnyTypeLists data; try { auto bus = getDbus(); auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); bus.call(method).read(data); } catch (const sdbusplus::exception::SdBusError& ex) { log( "Failed to call GetManagedObjects on com.google.custom_accel", entry("WHAT=%s", ex.what())); throw IpmiException(::ipmi::ccUnspecifiedError); } return data.size(); } std::string Handler::accelOobDeviceName(size_t index) const { ArrayOfObjectPathsAndTieredAnyTypeLists data; try { auto bus = getDbus(); auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); bus.call(method).read(data); } catch (const sdbusplus::exception::SdBusError& ex) { log( "Failed to call GetManagedObjects on com.google.custom_accel", entry("WHAT=%s", ex.what())); throw IpmiException(::ipmi::ccUnspecifiedError); } if (index >= data.size()) { log( "Requested index is larger than the number of entries.", entry("INDEX=%zu", index), entry("NUM_NAMES=%zu", data.size())); throw IpmiException(::ipmi::ccParmOutOfRange); } std::string_view name(data[index].first.str); if (!name.starts_with(ACCEL_OOB_ROOT)) { throw IpmiException(::ipmi::ccInvalidCommand); } name.remove_prefix(ACCEL_OOB_ROOT.length()); return std::string(name); } uint64_t Handler::accelOobRead(std::string_view name, uint64_t address, uint8_t num_bytes) const { static constexpr char ACCEL_OOB_METHOD[] = "Read"; std::string object_name(ACCEL_OOB_ROOT); object_name.append(name); auto bus = getDbus(); auto method = bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD); method.append(address, static_cast(num_bytes)); std::vector bytes; try { bus.call(method).read(bytes); } catch (const sdbusplus::exception::SdBusError& ex) { log("Failed to call Read on com.google.custom_accel", entry("WHAT=%s", ex.what()), entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), entry("DBUS_OBJECT=%s", object_name.c_str()), entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), entry("DBUS_ARG_ADDRESS=%016llx", address), entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes)); throw IpmiException(::ipmi::ccUnspecifiedError); } if (bytes.size() < num_bytes) { log( "Call to Read on com.google.custom_accel didn't return the expected" " number of bytes.", entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), entry("DBUS_OBJECT=%s", object_name.c_str()), entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), entry("DBUS_ARG_ADDRESS=%016llx", address), entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), entry("DBUS_RETURN_SIZE=%zu", bytes.size())); throw IpmiException(::ipmi::ccUnspecifiedError); } if (bytes.size() > sizeof(uint64_t)) { log( "Call to Read on com.google.custom_accel returned more than 8B.", entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), entry("DBUS_OBJECT=%s", object_name.c_str()), entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), entry("DBUS_ARG_ADDRESS=%016llx", address), entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), entry("DBUS_RETURN_SIZE=%zu", bytes.size())); throw IpmiException(::ipmi::ccReqDataTruncated); } uint64_t data = 0; for (size_t i = 0; i < num_bytes; ++i) { data = (data << 8) | bytes[i]; } return data; } void Handler::accelOobWrite(std::string_view name, uint64_t address, uint8_t num_bytes, uint64_t data) const { static constexpr std::string_view ACCEL_OOB_METHOD = "Write"; std::string object_name(ACCEL_OOB_ROOT); object_name.append(name); if (num_bytes > sizeof(data)) { log( "Call to Write on com.google.custom_accel requested more than 8B.", entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), entry("DBUS_OBJECT=%s", object_name.c_str()), entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), entry("DBUS_ARG_ADDRESS=%016llx", address), entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), entry("DBUS_ARG_DATA=%016llx", data)); throw IpmiException(::ipmi::ccParmOutOfRange); } std::vector bytes; bytes.reserve(num_bytes); for (size_t i = 0; i < num_bytes; ++i) { bytes.emplace_back(data & 0xff); data >>= 8; } try { auto bus = getDbus(); auto method = bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD.data()); method.append(address, bytes); bus.call_noreply(method); } catch (const sdbusplus::exception::SdBusError& ex) { log("Failed to call Write on com.google.custom_accel", entry("WHAT=%s", ex.what()), entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), entry("DBUS_OBJECT=%s", object_name.c_str()), entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), entry("DBUS_ARG_ADDRESS=%016llx", address), entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), entry("DBUS_ARG_DATA=%016llx", data)); throw IpmiException(::ipmi::ccUnspecifiedError); } } std::vector Handler::pcieBifurcation(uint8_t index) { return bifurcationHelper.get().getBifurcation(index).value_or( std::vector{}); } static constexpr auto BARE_METAL_TARGET = "gbmc-bare-metal-active.target"; void Handler::linuxBootDone() const { if (isBmcInBareMetalMode(this->fsPtr) != static_cast(BmcMode::BM_MODE)) { return; } log("LinuxBootDone: Disabling IPMI"); // Start the bare metal active systemd target. auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, SYSTEMD_INTERFACE, "StartUnit"); method.append(BARE_METAL_TARGET); method.append("replace"); try { bus.call_noreply(method); } catch (const sdbusplus::exception::SdBusError& ex) { log("Failed to start bare metal active systemd target", entry("WHAT=%s", ex.what())); throw IpmiException(::ipmi::ccUnspecifiedError); } } static constexpr char ACCEL_POWER_SERVICE[] = "xyz.openbmc_project.AccelPower"; static constexpr char ACCEL_POWER_PATH_PREFIX[] = "/xyz/openbmc_project/control/accel_power_"; static constexpr char POWER_MODE_IFC[] = "xyz.openbmc_project.Control.Power.Mode"; void Handler::accelSetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id, uint8_t settings_id, uint16_t value) const { int vrSettingsReq; boost::system::error_code ec; if (_vrSettingsMap.find(settings_id) == _vrSettingsMap.end()) { log("Settings ID is not supported", entry("settings_id=%d", settings_id)); throw IpmiException(::ipmi::ccParmOutOfRange); } vrSettingsReq = static_cast(settings_id | value << 8); std::string object_name( std::format("{}{}", ACCEL_POWER_PATH_PREFIX, chip_id)); std::variant val = vrSettingsReq; ctx->bus->yield_method_call(ctx->yield, ec, ACCEL_POWER_SERVICE, object_name.c_str(), "org.freedesktop.DBus.Properties", "Set", POWER_MODE_IFC, "PowerMode", val); if (ec) { log("Failed to set PowerMode property"); throw IpmiException(::ipmi::ccUnspecifiedError); } } static constexpr char EXTERNAL_SENSOR_SERVICE[] = "xyz.openbmc_project.ExternalSensor"; static constexpr char EXTERNAL_SENSOR_PATH_PREFIX[] = "/xyz/openbmc_project/sensors/power/"; static constexpr char SENSOR_VALUE_IFC[] = "xyz.openbmc_project.Sensor.Value"; uint16_t Handler::accelGetVrSettings(::ipmi::Context::ptr ctx, uint8_t chip_id, uint8_t settings_id) const { Value value; boost::system::error_code ec; std::string object_name(std::format("{}{}{}", EXTERNAL_SENSOR_PATH_PREFIX, _vrSettingsMap.at(settings_id), chip_id)); if (_vrSettingsMap.find(settings_id) == _vrSettingsMap.end()) { log("Settings ID is not supported", entry("settings_id=%d", settings_id)); throw IpmiException(::ipmi::ccParmOutOfRange); } value = ctx->bus->yield_method_call>( ctx->yield, ec, EXTERNAL_SENSOR_SERVICE, object_name.c_str(), "org.freedesktop.DBus.Properties", "Get", SENSOR_VALUE_IFC, "Value"); if (ec) { log("accelGetVrSettings: Failed to call GetObject "); throw IpmiException(::ipmi::ccUnspecifiedError); } return static_cast(std::get(value)); } std::string Handler::getBMInstanceProperty(uint8_t propertyType) const { std::string propertyTypeString; if (auto it = bmInstanceTypeStringMap.find(propertyType); it == bmInstanceTypeStringMap.end()) { stdplus::print(stderr, "PropertyType: '{}' is invalid.\n", propertyType); throw IpmiException(::ipmi::ccInvalidFieldRequest); } else { propertyTypeString = it->second; } std::string opath = std::format("/run/bm-instance/{}", propertyTypeString); // Check for file std::error_code ec; // TODO(brandonkim@google.com): Fix this to use stdplus::ManagedFd if (!this->getFs()->exists(opath, ec)) { stdplus::print(stderr, "Path: '{}' doesn't exist.\n", opath); throw IpmiException(::ipmi::ccInvalidFieldRequest); } std::ifstream ifs; ifs.exceptions(std::ifstream::failbit); std::string property; try { ifs.open(opath); std::getline(ifs, property); } catch (std::ios_base::failure& fail) { stdplus::print(stderr, "Failed to read: '{}'.\n", opath); throw IpmiException(::ipmi::ccUnspecifiedError); } return property; } } // namespace ipmi } // namespace google