|  | /** | 
|  | * Copyright © 2019 IBM Corporation | 
|  | * | 
|  | * 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 "updater.hpp" | 
|  |  | 
|  | #include "aei_updater.hpp" | 
|  | #include "pmbus.hpp" | 
|  | #include "types.hpp" | 
|  | #include "utility.hpp" | 
|  | #include "utils.hpp" | 
|  | #include "validator.hpp" | 
|  | #include "version.hpp" | 
|  |  | 
|  | #include <phosphor-logging/lg2.hpp> | 
|  | #include <xyz/openbmc_project/Logging/Create/client.hpp> | 
|  |  | 
|  | #include <chrono> | 
|  | #include <fstream> | 
|  | #include <iostream> | 
|  | #include <map> | 
|  | #include <string> | 
|  |  | 
|  | namespace util = phosphor::power::util; | 
|  |  | 
|  | namespace updater | 
|  | { | 
|  |  | 
|  | namespace internal | 
|  | { | 
|  |  | 
|  | // Define the CRC-8 polynomial (CRC-8-CCITT) | 
|  | constexpr uint8_t CRC8_POLYNOMIAL = 0x07; | 
|  | constexpr uint8_t CRC8_INITIAL = 0x00; | 
|  |  | 
|  | // Get the appropriate Updater class instance based PSU model number | 
|  | std::unique_ptr<updater::Updater> getClassInstance( | 
|  | const std::string& model, const std::string& psuInventoryPath, | 
|  | const std::string& devPath, const std::string& imageDir) | 
|  | { | 
|  | if (model == "51E9" || model == "51DA") | 
|  | { | 
|  | return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath, | 
|  | devPath, imageDir); | 
|  | } | 
|  | return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir); | 
|  | } | 
|  |  | 
|  | // Function to locate FW file with model and extension bin or hex | 
|  | const std::string getFWFilenamePath(const std::string& directory) | 
|  | { | 
|  | namespace fs = std::filesystem; | 
|  | // Get the last part of the directory name (model number) | 
|  | std::string model = fs::path(directory).filename().string(); | 
|  | for (const auto& entry : fs::directory_iterator(directory)) | 
|  | { | 
|  | if (entry.is_regular_file()) | 
|  | { | 
|  | std::string filename = entry.path().filename().string(); | 
|  |  | 
|  | if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin"))) | 
|  | { | 
|  | return directory + "/" + filename; | 
|  | } | 
|  | } | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | // Compute CRC-8 checksum for a vector of bytes | 
|  | uint8_t calculateCRC8(const std::vector<uint8_t>& data) | 
|  | { | 
|  | uint8_t crc = CRC8_INITIAL; | 
|  |  | 
|  | for (const auto& byte : data) | 
|  | { | 
|  | crc ^= byte; | 
|  | for (int i = 0; i < 8; ++i) | 
|  | { | 
|  | if (crc & 0x80) | 
|  | crc = (crc << 1) ^ CRC8_POLYNOMIAL; | 
|  | else | 
|  | crc <<= 1; | 
|  | } | 
|  | } | 
|  | return crc; | 
|  | } | 
|  |  | 
|  | // Delay execution for a specified number of milliseconds | 
|  | void delay(const int& milliseconds) | 
|  | { | 
|  | std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); | 
|  | } | 
|  |  | 
|  | // Convert big endian (32 bit integer) to a vector of little endian. | 
|  | std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue) | 
|  | { | 
|  | std::vector<uint8_t> littleEndianBytes(4); | 
|  |  | 
|  | littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF; | 
|  | littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF; | 
|  | littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF; | 
|  | littleEndianBytes[0] = bigEndianValue & 0xFF; | 
|  | return littleEndianBytes; | 
|  | } | 
|  |  | 
|  | // Validate the existence and size of a firmware file. | 
|  | bool validateFWFile(const std::string& fileName) | 
|  | { | 
|  | // Ensure the file exists and get the file size. | 
|  | if (!std::filesystem::exists(fileName)) | 
|  | { | 
|  | lg2::error("Firmware file not found: {FILE}", "FILE", fileName); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Check the file size | 
|  | auto fileSize = std::filesystem::file_size(fileName); | 
|  | if (fileSize == 0) | 
|  | { | 
|  | lg2::error("Firmware {FILE} is empty", "FILE", fileName); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Open a firmware file for reading in binary mode. | 
|  | std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName) | 
|  | { | 
|  | if (fileName.empty()) | 
|  | { | 
|  | lg2::error("Firmware file path is not provided"); | 
|  | return nullptr; | 
|  | } | 
|  | auto inputFile = | 
|  | std::make_unique<std::ifstream>(fileName, std::ios::binary); | 
|  | if (!inputFile->is_open()) | 
|  | { | 
|  | lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName); | 
|  | return nullptr; | 
|  | } | 
|  | return inputFile; | 
|  | } | 
|  |  | 
|  | // Read firmware bytes from input stream. | 
|  | std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile, | 
|  | const size_t numberOfBytesToRead) | 
|  | { | 
|  | std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF); | 
|  | try | 
|  | { | 
|  | // Enable exceptions for failbit and badbit | 
|  | inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); | 
|  | inputFile.read(reinterpret_cast<char*>(readDataBytes.data()), | 
|  | numberOfBytesToRead); | 
|  | size_t bytesRead = inputFile.gcount(); | 
|  | if (bytesRead != numberOfBytesToRead) | 
|  | { | 
|  | readDataBytes.resize(bytesRead); | 
|  | } | 
|  | } | 
|  | catch (const std::ios_base::failure& e) | 
|  | { | 
|  | lg2::error("Error reading firmware: {ERROR}", "ERROR", e); | 
|  | readDataBytes.clear(); | 
|  | } | 
|  | return readDataBytes; | 
|  | } | 
|  |  | 
|  | } // namespace internal | 
|  |  | 
|  | bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath, | 
|  | const std::string& imageDir) | 
|  | { | 
|  | auto devPath = utils::getDevicePath(bus, psuInventoryPath); | 
|  |  | 
|  | if (devPath.empty()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::filesystem::path fsPath(imageDir); | 
|  |  | 
|  | std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance( | 
|  | fsPath.filename().string(), psuInventoryPath, devPath, imageDir); | 
|  |  | 
|  | if (!updaterPtr->isReadyToUpdate()) | 
|  | { | 
|  | lg2::error("PSU not ready to update PSU = {PATH}", "PATH", | 
|  | psuInventoryPath); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | updaterPtr->bindUnbind(false); | 
|  | updaterPtr->createI2CDevice(); | 
|  | int ret = updaterPtr->doUpdate(); | 
|  | updaterPtr->bindUnbind(true); | 
|  | return ret == 0; | 
|  | } | 
|  |  | 
|  | bool validateAndUpdate(sdbusplus::bus_t& bus, | 
|  | const std::string& psuInventoryPath, | 
|  | const std::string& imageDir) | 
|  | { | 
|  | auto poweredOn = phosphor::power::util::isPoweredOn(bus, true); | 
|  | validator::PSUUpdateValidator psuValidator(bus, psuInventoryPath); | 
|  | if (!poweredOn && psuValidator.validToUpdate()) | 
|  | { | 
|  | return updater::update(bus, psuInventoryPath, imageDir); | 
|  | } | 
|  | else | 
|  | { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | Updater::Updater(const std::string& psuInventoryPath, | 
|  | const std::string& devPath, const std::string& imageDir) : | 
|  | bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath), | 
|  | devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir) | 
|  | { | 
|  | fs::path p = fs::path(devPath) / "driver"; | 
|  | try | 
|  | { | 
|  | driverPath = | 
|  | fs::canonical(p); // Get the path that points to the driver dir | 
|  | } | 
|  | catch (const fs::filesystem_error& e) | 
|  | { | 
|  | lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}", | 
|  | "PATH", devPath, "ERR", e); | 
|  | } | 
|  | } | 
|  |  | 
|  | // During PSU update, it needs to access the PSU i2c device directly, so it | 
|  | // needs to unbind the driver during the update, and re-bind after it's done. | 
|  | // After unbind, the hwmon sysfs will be gone, and the psu-monitor will report | 
|  | // errors. So set the PSU inventory's Present property to false so that | 
|  | // psu-monitor will not report any errors. | 
|  | void Updater::bindUnbind(bool doBind) | 
|  | { | 
|  | if (!doBind) | 
|  | { | 
|  | // Set non-present before unbind the driver | 
|  | setPresent(doBind); | 
|  | } | 
|  | auto p = driverPath; | 
|  | p /= doBind ? "bind" : "unbind"; | 
|  | std::ofstream out(p.string()); | 
|  | out << devName; | 
|  | if (doBind) | 
|  | { | 
|  | internal::delay(500); | 
|  | } | 
|  | out.close(); | 
|  |  | 
|  | if (doBind) | 
|  | { | 
|  | // Set to present after bind the driver | 
|  | setPresent(doBind); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Updater::setPresent(bool present) | 
|  | { | 
|  | try | 
|  | { | 
|  | auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus); | 
|  | util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, | 
|  | service, bus, present); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | lg2::error( | 
|  | "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}", | 
|  | "PATH", psuInventoryPath, "PRESENT", present); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Updater::isReadyToUpdate() | 
|  | { | 
|  | using namespace phosphor::pmbus; | 
|  |  | 
|  | // Pre-condition for updating PSU: | 
|  | // * Host is powered off | 
|  | // * At least one other PSU is present | 
|  | // * All other PSUs that are present are having AC input and DC standby | 
|  | //   output | 
|  |  | 
|  | if (util::isPoweredOn(bus, true)) | 
|  | { | 
|  | lg2::warning("Unable to update PSU when host is on"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool hasOtherPresent = false; | 
|  | auto paths = util::getPSUInventoryPaths(bus); | 
|  | for (const auto& p : paths) | 
|  | { | 
|  | if (p == psuInventoryPath) | 
|  | { | 
|  | // Skip check for itself | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Check PSU present | 
|  | bool present = false; | 
|  | try | 
|  | { | 
|  | auto service = util::getService(p, INVENTORY_IFACE, bus); | 
|  | util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, | 
|  | service, bus, present); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | lg2::error("Failed to get present property PSU={PSU}", "PSU", p); | 
|  | } | 
|  | if (!present) | 
|  | { | 
|  | lg2::warning("PSU not present PSU={PSU}", "PSU", p); | 
|  | continue; | 
|  | } | 
|  | hasOtherPresent = true; | 
|  |  | 
|  | // Typically the driver is still bound here, so it is possible to | 
|  | // directly read the debugfs to get the status. | 
|  | try | 
|  | { | 
|  | auto path = utils::getDevicePath(bus, p); | 
|  | PMBus pmbus(path); | 
|  | uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug); | 
|  | auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0); | 
|  | uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug); | 
|  | if ((statusWord & status_word::VOUT_FAULT) || | 
|  | (statusWord & status_word::INPUT_FAULT_WARN) || | 
|  | (statusWord & status_word::VIN_UV_FAULT) || | 
|  | // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX | 
|  | // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to | 
|  | // UV_FAULT in vout status. | 
|  | (voutStatus & status_vout::UV_FAULT) || | 
|  | (voutStatus & status_vout::OV_FAULT)) | 
|  | { | 
|  | lg2::warning( | 
|  | "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}", | 
|  | "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex, | 
|  | voutStatus); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | catch (const std::exception& ex) | 
|  | { | 
|  | // If error occurs on accessing the debugfs, it means something went | 
|  | // wrong, e.g. PSU is not present, and it's not ready to update. | 
|  | lg2::error("{EX}", "EX", ex.what()); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return hasOtherPresent; | 
|  | } | 
|  |  | 
|  | int Updater::doUpdate() | 
|  | { | 
|  | using namespace std::chrono; | 
|  |  | 
|  | uint8_t data; | 
|  | uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30, | 
|  | 0x33, 0x30, 0x30, 0x30, 0x34, 0x01}; | 
|  | uint8_t bootFlag = 0x01; | 
|  | static_assert(sizeof(unlockData) == 12); | 
|  |  | 
|  | i2c->write(0xf0, sizeof(unlockData), unlockData); | 
|  | printf("Unlock PSU\n"); | 
|  |  | 
|  | std::this_thread::sleep_for(milliseconds(5)); | 
|  |  | 
|  | i2c->write(0xf1, bootFlag); | 
|  | printf("Set boot flag ret\n"); | 
|  |  | 
|  | std::this_thread::sleep_for(seconds(3)); | 
|  |  | 
|  | i2c->read(0xf1, data); | 
|  | printf("Read of 0x%02x, 0x%02x\n", 0xf1, data); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void Updater::createI2CDevice() | 
|  | { | 
|  | auto [id, addr] = utils::parseDeviceName(devName); | 
|  | i2c = i2c::create(id, addr); | 
|  | } | 
|  |  | 
|  | void Updater::createServiceableEventLog( | 
|  | const std::string& errorName, const std::string& severity, | 
|  | std::map<std::string, std::string>& additionalData) | 
|  | { | 
|  | if (!isEventLogEnabled() || isEventLoggedThisSession()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | using namespace sdbusplus::xyz::openbmc_project; | 
|  | using LoggingCreate = | 
|  | sdbusplus::client::xyz::openbmc_project::logging::Create<>; | 
|  | enableEventLoggedThisSession(); | 
|  | try | 
|  | { | 
|  | additionalData["_PID"] = std::to_string(getpid()); | 
|  | auto method = bus.new_method_call(LoggingCreate::default_service, | 
|  | LoggingCreate::instance_path, | 
|  | LoggingCreate::interface, "Create"); | 
|  | method.append(errorName, severity, additionalData); | 
|  |  | 
|  | bus.call(method); | 
|  | } | 
|  | catch (const sdbusplus::exception::SdBusError& e) | 
|  | { | 
|  | lg2::error( | 
|  | "Failed creating event log for fault {ERROR_NAME}, error {ERR}", | 
|  | "ERROR_NAME", errorName, "ERR", e); | 
|  | } | 
|  | disableEventLogging(); | 
|  | } | 
|  |  | 
|  | std::map<std::string, std::string> Updater::getI2CAdditionalData() | 
|  | { | 
|  | std::map<std::string, std::string> additionalData; | 
|  | auto [id, addr] = utils::parseDeviceName(getDevName()); | 
|  | std::string hexIdString = std::format("0x{:x}", id); | 
|  | std::string hexAddrString = std::format("0x{:x}", addr); | 
|  |  | 
|  | additionalData["CALLOUT_IIC_BUS"] = hexIdString; | 
|  | additionalData["CALLOUT_IIC_ADDR"] = hexAddrString; | 
|  | return additionalData; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * callOutI2CEventLog calls out FRUs in the following order: | 
|  | * 1 - PSU  high priority | 
|  | * 2 - CALLOUT_IIC_BUS | 
|  | */ | 
|  | void Updater::callOutI2CEventLog( | 
|  | std::map<std::string, std::string> extraAdditionalData, | 
|  | const std::string& exceptionString, const int errorCode) | 
|  | { | 
|  | std::map<std::string, std::string> additionalData = { | 
|  | {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}}; | 
|  | additionalData.merge(extraAdditionalData); | 
|  | additionalData.merge(getI2CAdditionalData()); | 
|  | additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode); | 
|  | if (!exceptionString.empty()) | 
|  | { | 
|  | additionalData["I2C_EXCEPTION"] = exceptionString; | 
|  | } | 
|  | createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY, | 
|  | additionalData); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * callOutPsuEventLog calls out PSU and system planar | 
|  | */ | 
|  | void Updater::callOutPsuEventLog( | 
|  | std::map<std::string, std::string> extraAdditionalData) | 
|  | { | 
|  | std::map<std::string, std::string> additionalData = { | 
|  | {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}}; | 
|  | additionalData.merge(extraAdditionalData); | 
|  | createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG, | 
|  | updater::ERROR_SEVERITY, additionalData); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * callOutSWEventLog calls out the BMC0001 procedure. | 
|  | */ | 
|  | void Updater::callOutSWEventLog( | 
|  | std::map<std::string, std::string> additionalData) | 
|  | { | 
|  | createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG, | 
|  | updater::ERROR_SEVERITY, additionalData); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * callOutGoodEventLog calls out a successful firmware update. | 
|  | */ | 
|  | void Updater::callOutGoodEventLog() | 
|  | { | 
|  | std::map<std::string, std::string> additionalData = { | 
|  | {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()}, | 
|  | {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}}; | 
|  | createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG, | 
|  | updater::INFORMATIONAL_SEVERITY, additionalData); | 
|  | } | 
|  | } // namespace updater |