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