1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "updater.hpp" 18 19 #include "aei_updater.hpp" 20 #include "pmbus.hpp" 21 #include "types.hpp" 22 #include "utility.hpp" 23 #include "utils.hpp" 24 #include "validator.hpp" 25 #include "version.hpp" 26 27 #include <phosphor-logging/lg2.hpp> 28 #include <xyz/openbmc_project/Logging/Create/client.hpp> 29 30 #include <chrono> 31 #include <fstream> 32 #include <iostream> 33 #include <map> 34 #include <string> 35 36 namespace util = phosphor::power::util; 37 38 namespace updater 39 { 40 41 namespace internal 42 { 43 44 // Define the CRC-8 polynomial (CRC-8-CCITT) 45 constexpr uint8_t CRC8_POLYNOMIAL = 0x07; 46 constexpr uint8_t CRC8_INITIAL = 0x00; 47 48 // Get the appropriate Updater class instance based PSU model number 49 std::unique_ptr<updater::Updater> getClassInstance( 50 const std::string& model, const std::string& psuInventoryPath, 51 const std::string& devPath, const std::string& imageDir) 52 { 53 if (model == "51E9" || model == "51DA") 54 { 55 return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath, 56 devPath, imageDir); 57 } 58 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir); 59 } 60 61 // Function to locate FW file with model and extension bin or hex 62 const std::string getFWFilenamePath(const std::string& directory) 63 { 64 namespace fs = std::filesystem; 65 // Get the last part of the directory name (model number) 66 std::string model = fs::path(directory).filename().string(); 67 for (const auto& entry : fs::directory_iterator(directory)) 68 { 69 if (entry.is_regular_file()) 70 { 71 std::string filename = entry.path().filename().string(); 72 73 if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin"))) 74 { 75 return directory + "/" + filename; 76 } 77 } 78 } 79 return ""; 80 } 81 82 // Compute CRC-8 checksum for a vector of bytes 83 uint8_t calculateCRC8(const std::vector<uint8_t>& data) 84 { 85 uint8_t crc = CRC8_INITIAL; 86 87 for (const auto& byte : data) 88 { 89 crc ^= byte; 90 for (int i = 0; i < 8; ++i) 91 { 92 if (crc & 0x80) 93 crc = (crc << 1) ^ CRC8_POLYNOMIAL; 94 else 95 crc <<= 1; 96 } 97 } 98 return crc; 99 } 100 101 // Delay execution for a specified number of milliseconds 102 void delay(const int& milliseconds) 103 { 104 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); 105 } 106 107 // Convert big endian (32 bit integer) to a vector of little endian. 108 std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue) 109 { 110 std::vector<uint8_t> littleEndianBytes(4); 111 112 littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF; 113 littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF; 114 littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF; 115 littleEndianBytes[0] = bigEndianValue & 0xFF; 116 return littleEndianBytes; 117 } 118 119 // Validate the existence and size of a firmware file. 120 bool validateFWFile(const std::string& fileName) 121 { 122 // Ensure the file exists and get the file size. 123 if (!std::filesystem::exists(fileName)) 124 { 125 lg2::error("Firmware file not found: {FILE}", "FILE", fileName); 126 return false; 127 } 128 129 // Check the file size 130 auto fileSize = std::filesystem::file_size(fileName); 131 if (fileSize == 0) 132 { 133 lg2::error("Firmware {FILE} is empty", "FILE", fileName); 134 return false; 135 } 136 return true; 137 } 138 139 // Open a firmware file for reading in binary mode. 140 std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName) 141 { 142 if (fileName.empty()) 143 { 144 lg2::error("Firmware file path is not provided"); 145 return nullptr; 146 } 147 auto inputFile = 148 std::make_unique<std::ifstream>(fileName, std::ios::binary); 149 if (!inputFile->is_open()) 150 { 151 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName); 152 return nullptr; 153 } 154 return inputFile; 155 } 156 157 // Read firmware bytes from input stream. 158 std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile, 159 const size_t numberOfBytesToRead) 160 { 161 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF); 162 try 163 { 164 // Enable exceptions for failbit and badbit 165 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); 166 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()), 167 numberOfBytesToRead); 168 size_t bytesRead = inputFile.gcount(); 169 if (bytesRead != numberOfBytesToRead) 170 { 171 readDataBytes.resize(bytesRead); 172 } 173 } 174 catch (const std::ios_base::failure& e) 175 { 176 lg2::error("Error reading firmware: {ERROR}", "ERROR", e); 177 readDataBytes.clear(); 178 } 179 return readDataBytes; 180 } 181 182 } // namespace internal 183 184 bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath, 185 const std::string& imageDir) 186 { 187 auto devPath = utils::getDevicePath(bus, psuInventoryPath); 188 189 if (devPath.empty()) 190 { 191 return false; 192 } 193 194 std::filesystem::path fsPath(imageDir); 195 196 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance( 197 fsPath.filename().string(), psuInventoryPath, devPath, imageDir); 198 199 if (!updaterPtr->isReadyToUpdate()) 200 { 201 lg2::error("PSU not ready to update PSU = {PATH}", "PATH", 202 psuInventoryPath); 203 return false; 204 } 205 206 updaterPtr->bindUnbind(false); 207 updaterPtr->createI2CDevice(); 208 int ret = updaterPtr->doUpdate(); 209 updaterPtr->bindUnbind(true); 210 return ret == 0; 211 } 212 213 bool validateAndUpdate(sdbusplus::bus_t& bus, 214 const std::string& psuInventoryPath, 215 const std::string& imageDir) 216 { 217 auto poweredOn = phosphor::power::util::isPoweredOn(bus, true); 218 validator::PSUUpdateValidator psuValidator(bus, psuInventoryPath); 219 if (!poweredOn && psuValidator.validToUpdate()) 220 { 221 return updater::update(bus, psuInventoryPath, imageDir); 222 } 223 else 224 { 225 return false; 226 } 227 } 228 229 Updater::Updater(const std::string& psuInventoryPath, 230 const std::string& devPath, const std::string& imageDir) : 231 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath), 232 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir) 233 { 234 fs::path p = fs::path(devPath) / "driver"; 235 try 236 { 237 driverPath = 238 fs::canonical(p); // Get the path that points to the driver dir 239 } 240 catch (const fs::filesystem_error& e) 241 { 242 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}", 243 "PATH", devPath, "ERR", e); 244 } 245 } 246 247 // During PSU update, it needs to access the PSU i2c device directly, so it 248 // needs to unbind the driver during the update, and re-bind after it's done. 249 // After unbind, the hwmon sysfs will be gone, and the psu-monitor will report 250 // errors. So set the PSU inventory's Present property to false so that 251 // psu-monitor will not report any errors. 252 void Updater::bindUnbind(bool doBind) 253 { 254 if (!doBind) 255 { 256 // Set non-present before unbind the driver 257 setPresent(doBind); 258 } 259 auto p = driverPath; 260 p /= doBind ? "bind" : "unbind"; 261 std::ofstream out(p.string()); 262 out << devName; 263 if (doBind) 264 { 265 internal::delay(500); 266 } 267 out.close(); 268 269 if (doBind) 270 { 271 // Set to present after bind the driver 272 setPresent(doBind); 273 } 274 } 275 276 void Updater::setPresent(bool present) 277 { 278 try 279 { 280 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus); 281 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, 282 service, bus, present); 283 } 284 catch (const std::exception& e) 285 { 286 lg2::error( 287 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}", 288 "PATH", psuInventoryPath, "PRESENT", present); 289 } 290 } 291 292 bool Updater::isReadyToUpdate() 293 { 294 using namespace phosphor::pmbus; 295 296 // Pre-condition for updating PSU: 297 // * Host is powered off 298 // * At least one other PSU is present 299 // * All other PSUs that are present are having AC input and DC standby 300 // output 301 302 if (util::isPoweredOn(bus, true)) 303 { 304 lg2::warning("Unable to update PSU when host is on"); 305 return false; 306 } 307 308 bool hasOtherPresent = false; 309 auto paths = util::getPSUInventoryPaths(bus); 310 for (const auto& p : paths) 311 { 312 if (p == psuInventoryPath) 313 { 314 // Skip check for itself 315 continue; 316 } 317 318 // Check PSU present 319 bool present = false; 320 try 321 { 322 auto service = util::getService(p, INVENTORY_IFACE, bus); 323 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, 324 service, bus, present); 325 } 326 catch (const std::exception& e) 327 { 328 lg2::error("Failed to get present property PSU={PSU}", "PSU", p); 329 } 330 if (!present) 331 { 332 lg2::warning("PSU not present PSU={PSU}", "PSU", p); 333 continue; 334 } 335 hasOtherPresent = true; 336 337 // Typically the driver is still bound here, so it is possible to 338 // directly read the debugfs to get the status. 339 try 340 { 341 auto path = utils::getDevicePath(bus, p); 342 PMBus pmbus(path); 343 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug); 344 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0); 345 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug); 346 if ((statusWord & status_word::VOUT_FAULT) || 347 (statusWord & status_word::INPUT_FAULT_WARN) || 348 (statusWord & status_word::VIN_UV_FAULT) || 349 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX 350 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to 351 // UV_FAULT in vout status. 352 (voutStatus & status_vout::UV_FAULT) || 353 (voutStatus & status_vout::OV_FAULT)) 354 { 355 lg2::warning( 356 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}", 357 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex, 358 voutStatus); 359 return false; 360 } 361 } 362 catch (const std::exception& ex) 363 { 364 // If error occurs on accessing the debugfs, it means something went 365 // wrong, e.g. PSU is not present, and it's not ready to update. 366 lg2::error("{EX}", "EX", ex.what()); 367 return false; 368 } 369 } 370 return hasOtherPresent; 371 } 372 373 int Updater::doUpdate() 374 { 375 using namespace std::chrono; 376 377 uint8_t data; 378 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30, 379 0x33, 0x30, 0x30, 0x30, 0x34, 0x01}; 380 uint8_t bootFlag = 0x01; 381 static_assert(sizeof(unlockData) == 12); 382 383 i2c->write(0xf0, sizeof(unlockData), unlockData); 384 printf("Unlock PSU\n"); 385 386 std::this_thread::sleep_for(milliseconds(5)); 387 388 i2c->write(0xf1, bootFlag); 389 printf("Set boot flag ret\n"); 390 391 std::this_thread::sleep_for(seconds(3)); 392 393 i2c->read(0xf1, data); 394 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data); 395 return 0; 396 } 397 398 void Updater::createI2CDevice() 399 { 400 auto [id, addr] = utils::parseDeviceName(devName); 401 i2c = i2c::create(id, addr); 402 } 403 404 void Updater::createServiceableEventLog( 405 const std::string& errorName, const std::string& severity, 406 std::map<std::string, std::string>& additionalData) 407 { 408 if (!isEventLogEnabled() || isEventLoggedThisSession()) 409 { 410 return; 411 } 412 413 using namespace sdbusplus::xyz::openbmc_project; 414 using LoggingCreate = 415 sdbusplus::client::xyz::openbmc_project::logging::Create<>; 416 enableEventLoggedThisSession(); 417 try 418 { 419 additionalData["_PID"] = std::to_string(getpid()); 420 auto method = bus.new_method_call(LoggingCreate::default_service, 421 LoggingCreate::instance_path, 422 LoggingCreate::interface, "Create"); 423 method.append(errorName, severity, additionalData); 424 425 bus.call(method); 426 } 427 catch (const sdbusplus::exception::SdBusError& e) 428 { 429 lg2::error( 430 "Failed creating event log for fault {ERROR_NAME}, error {ERR}", 431 "ERROR_NAME", errorName, "ERR", e); 432 } 433 disableEventLogging(); 434 } 435 436 std::map<std::string, std::string> Updater::getI2CAdditionalData() 437 { 438 std::map<std::string, std::string> additionalData; 439 auto [id, addr] = utils::parseDeviceName(getDevName()); 440 std::string hexIdString = std::format("0x{:x}", id); 441 std::string hexAddrString = std::format("0x{:x}", addr); 442 443 additionalData["CALLOUT_IIC_BUS"] = hexIdString; 444 additionalData["CALLOUT_IIC_ADDR"] = hexAddrString; 445 return additionalData; 446 } 447 448 /* 449 * callOutI2CEventLog calls out FRUs in the following order: 450 * 1 - PSU high priority 451 * 2 - CALLOUT_IIC_BUS 452 */ 453 void Updater::callOutI2CEventLog( 454 std::map<std::string, std::string> extraAdditionalData, 455 const std::string& exceptionString, const int errorCode) 456 { 457 std::map<std::string, std::string> additionalData = { 458 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}}; 459 additionalData.merge(extraAdditionalData); 460 additionalData.merge(getI2CAdditionalData()); 461 additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode); 462 if (!exceptionString.empty()) 463 { 464 additionalData["I2C_EXCEPTION"] = exceptionString; 465 } 466 createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY, 467 additionalData); 468 } 469 470 /* 471 * callOutPsuEventLog calls out PSU and system planar 472 */ 473 void Updater::callOutPsuEventLog( 474 std::map<std::string, std::string> extraAdditionalData) 475 { 476 std::map<std::string, std::string> additionalData = { 477 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}}; 478 additionalData.merge(extraAdditionalData); 479 createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG, 480 updater::ERROR_SEVERITY, additionalData); 481 } 482 483 /* 484 * callOutSWEventLog calls out the BMC0001 procedure. 485 */ 486 void Updater::callOutSWEventLog( 487 std::map<std::string, std::string> additionalData) 488 { 489 createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG, 490 updater::ERROR_SEVERITY, additionalData); 491 } 492 493 /* 494 * callOutGoodEventLog calls out a successful firmware update. 495 */ 496 void Updater::callOutGoodEventLog() 497 { 498 std::map<std::string, std::string> additionalData = { 499 {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()}, 500 {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}}; 501 createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG, 502 updater::INFORMATIONAL_SEVERITY, additionalData); 503 } 504 } // namespace updater 505