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 #include "config.h" 17 18 #include "updater.hpp" 19 20 #include "pmbus.hpp" 21 #include "types.hpp" 22 #include "utility.hpp" 23 #include "utils.hpp" 24 25 #include <sys/stat.h> 26 27 #include <phosphor-logging/log.hpp> 28 29 #include <chrono> 30 #include <fstream> 31 #include <thread> 32 #include <vector> 33 34 using namespace phosphor::logging; 35 namespace util = phosphor::power::util; 36 37 namespace updater 38 { 39 40 namespace internal 41 { 42 43 // Define the CRC-8 polynomial (CRC-8-CCITT) 44 constexpr uint8_t CRC8_POLYNOMIAL = 0x07; 45 constexpr uint8_t CRC8_INITIAL = 0x00; 46 47 /* Get the device name from the device path */ 48 std::string getDeviceName(std::string devPath) 49 { 50 if (devPath.back() == '/') 51 { 52 devPath.pop_back(); 53 } 54 return fs::path(devPath).stem().string(); 55 } 56 57 // Construct the device path using the I2C bus and address or read inventory 58 // path 59 std::string getDevicePath(sdbusplus::bus_t& bus, 60 const std::string& psuInventoryPath) 61 { 62 try 63 { 64 if (internal::usePsuJsonFile()) 65 { 66 auto data = util::loadJSONFromFile(PSU_JSON_PATH); 67 if (data == nullptr) 68 { 69 return {}; 70 } 71 auto devicePath = data["psuDevices"][psuInventoryPath]; 72 if (devicePath.empty()) 73 { 74 log<level::WARNING>("Unable to find psu devices or path"); 75 } 76 return devicePath; 77 } 78 else 79 { 80 const auto& [i2cbus, i2caddr] = 81 utils::getPsuI2c(bus, psuInventoryPath); 82 const auto DevicePath = "/sys/bus/i2c/devices/"; 83 std::ostringstream ss; 84 ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr; 85 std::string addrStr = ss.str(); 86 std::string busStr = std::to_string(i2cbus); 87 std::string devPath = DevicePath + busStr + "-" + addrStr; 88 return devPath; 89 } 90 } 91 catch (const std::exception& e) 92 { 93 log<level::ERR>( 94 std::format("Error in getDevicePath: {}", e.what()).c_str()); 95 return {}; 96 } 97 catch (...) 98 { 99 log<level::ERR>("Unknown error occurred in getDevicePath"); 100 return {}; 101 } 102 } 103 104 // Parse the device name to get I2C bus and address 105 std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName) 106 { 107 // Get I2C bus and device address, e.g. 3-0068 108 // is parsed to bus 3, device address 0x68 109 auto pos = devName.find('-'); 110 assert(pos != std::string::npos); 111 uint8_t busId = std::stoi(devName.substr(0, pos)); 112 uint8_t devAddr = std::stoi(devName.substr(pos + 1), nullptr, 16); 113 return {busId, devAddr}; 114 } 115 116 // Get the appropriate Updater class instance based PSU model number 117 std::unique_ptr<updater::Updater> getClassInstance( 118 const std::string& model, const std::string& psuInventoryPath, 119 const std::string& devPath, const std::string& imageDir) 120 { 121 if (model == "XXXX") 122 { 123 // XXXX updater class 124 } 125 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir); 126 } 127 128 // Function to locate FW file with model and extension bin or hex 129 const std::string getFWFilenamePath(const std::string& directory) 130 { 131 namespace fs = std::filesystem; 132 // Get the last part of the directory name (model number) 133 std::string model = fs::path(directory).filename().string(); 134 for (const auto& entry : fs::directory_iterator(directory)) 135 { 136 if (entry.is_regular_file()) 137 { 138 std::string filename = entry.path().filename().string(); 139 140 if ((filename.rfind(model, 0) == 0) && 141 (filename.ends_with(".bin") || filename.ends_with(".hex"))) 142 { 143 return directory + "/" + filename; 144 } 145 } 146 } 147 return ""; 148 } 149 150 // Compute CRC-8 checksum for a vector of bytes 151 uint8_t calculateCRC8(const std::vector<uint8_t>& data) 152 { 153 uint8_t crc = CRC8_INITIAL; 154 155 for (const auto& byte : data) 156 { 157 crc ^= byte; 158 for (int i = 0; i < 8; ++i) 159 { 160 if (crc & 0x80) 161 crc = (crc << 1) ^ CRC8_POLYNOMIAL; 162 else 163 crc <<= 1; 164 } 165 } 166 return crc; 167 } 168 169 // Delay execution for a specified number of milliseconds 170 void delay(const int& milliseconds) 171 { 172 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); 173 } 174 175 // Convert big endian (32 bit integer) to a vector of little endian. 176 std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue) 177 { 178 std::vector<uint8_t> littleEndianBytes(4); 179 180 littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF; 181 littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF; 182 littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF; 183 littleEndianBytes[0] = bigEndianValue & 0xFF; 184 return littleEndianBytes; 185 } 186 187 // Validate the existence and size of a firmware file. 188 bool validateFWFile(const std::string& fileName) 189 { 190 // Ensure the file exists and get the file size. 191 if (!std::filesystem::exists(fileName)) 192 { 193 log<level::ERR>( 194 std::format("Firmware file not found: {}", fileName).c_str()); 195 return false; 196 } 197 198 // Check the file size 199 auto fileSize = std::filesystem::file_size(fileName); 200 if (fileSize == 0) 201 { 202 log<level::ERR>("Firmware file is empty"); 203 return false; 204 } 205 return true; 206 } 207 208 // Open a firmware file for reading in binary mode. 209 std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName) 210 { 211 if (fileName.empty()) 212 { 213 log<level::ERR>("Firmware file path is not provided"); 214 return nullptr; 215 } 216 auto inputFile = 217 std::make_unique<std::ifstream>(fileName, std::ios::binary); 218 if (!inputFile->is_open()) 219 { 220 log<level::ERR>( 221 std::format("Failed to open firmware file: {}", fileName).c_str()); 222 return nullptr; 223 } 224 return inputFile; 225 } 226 227 // Read firmware bytes from input stream. 228 std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile, 229 const size_t numberOfBytesToRead) 230 { 231 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF); 232 try 233 { 234 // Enable exceptions for failbit and badbit 235 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); 236 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()), 237 numberOfBytesToRead); 238 size_t bytesRead = inputFile.gcount(); 239 if (bytesRead != numberOfBytesToRead) 240 { 241 readDataBytes.resize(bytesRead); 242 } 243 } 244 catch (const std::ios_base::failure& e) 245 { 246 log<level::ERR>( 247 std::format("Error reading firmware: {}", e.what()).c_str()); 248 readDataBytes.clear(); 249 } 250 return readDataBytes; 251 } 252 253 // Wrapper to check existence of PSU JSON file. 254 bool usePsuJsonFile() 255 { 256 return utils::checkFileExists(PSU_JSON_PATH); 257 } 258 } // namespace internal 259 260 bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath, 261 const std::string& imageDir) 262 { 263 auto devPath = internal::getDevicePath(bus, psuInventoryPath); 264 265 if (devPath.empty()) 266 { 267 return false; 268 } 269 270 std::filesystem::path fsPath(imageDir); 271 272 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance( 273 fsPath.filename().string(), psuInventoryPath, devPath, imageDir); 274 275 if (!updaterPtr->isReadyToUpdate()) 276 { 277 log<level::ERR>("PSU not ready to update", 278 entry("PSU=%s", psuInventoryPath.c_str())); 279 return false; 280 } 281 282 updaterPtr->bindUnbind(false); 283 updaterPtr->createI2CDevice(); 284 int ret = updaterPtr->doUpdate(); 285 updaterPtr->bindUnbind(true); 286 return ret == 0; 287 } 288 289 Updater::Updater(const std::string& psuInventoryPath, 290 const std::string& devPath, const std::string& imageDir) : 291 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath), 292 devPath(devPath), devName(internal::getDeviceName(devPath)), 293 imageDir(imageDir) 294 { 295 fs::path p = fs::path(devPath) / "driver"; 296 try 297 { 298 driverPath = 299 fs::canonical(p); // Get the path that points to the driver dir 300 } 301 catch (const fs::filesystem_error& e) 302 { 303 log<level::ERR>("Failed to get canonical path", 304 entry("DEVPATH=%s", devPath.c_str()), 305 entry("ERROR=%s", e.what())); 306 throw; 307 } 308 } 309 310 // During PSU update, it needs to access the PSU i2c device directly, so it 311 // needs to unbind the driver during the update, and re-bind after it's done. 312 // After unbind, the hwmon sysfs will be gone, and the psu-monitor will report 313 // errors. So set the PSU inventory's Present property to false so that 314 // psu-monitor will not report any errors. 315 void Updater::bindUnbind(bool doBind) 316 { 317 if (!doBind) 318 { 319 // Set non-present before unbind the driver 320 setPresent(doBind); 321 } 322 auto p = driverPath; 323 p /= doBind ? "bind" : "unbind"; 324 std::ofstream out(p.string()); 325 out << devName; 326 327 if (doBind) 328 { 329 // Set to present after bind the driver 330 setPresent(doBind); 331 } 332 } 333 334 void Updater::setPresent(bool present) 335 { 336 try 337 { 338 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus); 339 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, 340 service, bus, present); 341 } 342 catch (const std::exception& e) 343 { 344 log<level::ERR>("Failed to set present property", 345 entry("PSU=%s", psuInventoryPath.c_str()), 346 entry("PRESENT=%d", present)); 347 } 348 } 349 350 bool Updater::isReadyToUpdate() 351 { 352 using namespace phosphor::pmbus; 353 354 // Pre-condition for updating PSU: 355 // * Host is powered off 356 // * At least one other PSU is present 357 // * All other PSUs that are present are having AC input and DC standby 358 // output 359 360 if (util::isPoweredOn(bus, true)) 361 { 362 log<level::WARNING>("Unable to update PSU when host is on"); 363 return false; 364 } 365 366 bool hasOtherPresent = false; 367 auto paths = util::getPSUInventoryPaths(bus); 368 for (const auto& p : paths) 369 { 370 if (p == psuInventoryPath) 371 { 372 // Skip check for itself 373 continue; 374 } 375 376 // Check PSU present 377 bool present = false; 378 try 379 { 380 auto service = util::getService(p, INVENTORY_IFACE, bus); 381 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath, 382 service, bus, present); 383 } 384 catch (const std::exception& e) 385 { 386 log<level::ERR>("Failed to get present property", 387 entry("PSU=%s", p.c_str())); 388 } 389 if (!present) 390 { 391 log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str())); 392 continue; 393 } 394 hasOtherPresent = true; 395 396 // Typically the driver is still bound here, so it is possible to 397 // directly read the debugfs to get the status. 398 try 399 { 400 auto path = internal::getDevicePath(bus, p); 401 PMBus pmbus(path); 402 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug); 403 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0); 404 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug); 405 if ((statusWord & status_word::VOUT_FAULT) || 406 (statusWord & status_word::INPUT_FAULT_WARN) || 407 (statusWord & status_word::VIN_UV_FAULT) || 408 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX 409 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to 410 // UV_FAULT in vout status. 411 (voutStatus & status_vout::UV_FAULT) || 412 (voutStatus & status_vout::OV_FAULT)) 413 { 414 log<level::WARNING>( 415 "Unable to update PSU when other PSU has input/ouput fault", 416 entry("PSU=%s", p.c_str()), 417 entry("STATUS_WORD=0x%04x", statusWord), 418 entry("VOUT_BYTE=0x%02x", voutStatus)); 419 return false; 420 } 421 } 422 catch (const std::exception& ex) 423 { 424 // If error occurs on accessing the debugfs, it means something went 425 // wrong, e.g. PSU is not present, and it's not ready to update. 426 log<level::ERR>(ex.what()); 427 return false; 428 } 429 } 430 return hasOtherPresent; 431 } 432 433 int Updater::doUpdate() 434 { 435 using namespace std::chrono; 436 437 uint8_t data; 438 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30, 439 0x33, 0x30, 0x30, 0x30, 0x34, 0x01}; 440 uint8_t bootFlag = 0x01; 441 static_assert(sizeof(unlockData) == 12); 442 443 i2c->write(0xf0, sizeof(unlockData), unlockData); 444 printf("Unlock PSU\n"); 445 446 std::this_thread::sleep_for(milliseconds(5)); 447 448 i2c->write(0xf1, bootFlag); 449 printf("Set boot flag ret\n"); 450 451 std::this_thread::sleep_for(seconds(3)); 452 453 i2c->read(0xf1, data); 454 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data); 455 return 0; 456 } 457 458 void Updater::createI2CDevice() 459 { 460 auto [id, addr] = internal::parseDeviceName(devName); 461 i2c = i2c::create(id, addr); 462 } 463 } // namespace updater 464