1 /** 2 * Copyright © 2024 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 "config.h" 18 19 #include "aei_updater.hpp" 20 21 #include "pmbus.hpp" 22 #include "types.hpp" 23 #include "updater.hpp" 24 #include "utility.hpp" 25 26 #include <phosphor-logging/lg2.hpp> 27 28 #include <fstream> 29 30 namespace aeiUpdater 31 { 32 33 // Suppress clang-tidy errors for unused variables that are intended 34 // for future use 35 #ifdef __clang__ 36 #pragma clang diagnostic push 37 #pragma clang diagnostic ignored "-Wunused-variable" 38 #endif 39 40 constexpr uint8_t MAX_RETRIES = 0x02; // Constants for retry limits 41 42 constexpr int ISP_STATUS_DELAY = 1200; // Delay for ISP status check (1.2s) 43 constexpr int MEM_WRITE_DELAY = 5000; // Memory write delay (5s) 44 constexpr int MEM_STRETCH_DELAY = 10; // Delay between writes (10ms) 45 constexpr int MEM_COMPLETE_DELAY = 2000; // Delay before completion (2s) 46 constexpr int REBOOT_DELAY = 8000; // Delay for reboot (8s) 47 48 constexpr uint8_t I2C_SMBUS_BLOCK_MAX = 0x20; // Max Read bytes from PSU 49 constexpr uint8_t FW_READ_BLOCK_SIZE = 0x20; // Read bytes from FW file 50 constexpr uint8_t BLOCK_WRITE_SIZE = 0x25; // I2C block write size 51 constexpr uint8_t READ_SEQ_ST_CML_SIZE = 0x6; // Read sequence and status CML 52 // size 53 constexpr uint8_t START_SEQUENCE_INDEX = 0x1; // Starting sequence index 54 constexpr uint8_t STATUS_CML_INDEX = 0x5; // Status CML read index 55 56 // Register addresses for commands. 57 constexpr uint8_t KEY_REGISTER = 0xF6; // Key register 58 constexpr uint8_t STATUS_REGISTER = 0xF7; // Status register 59 constexpr uint8_t ISP_MEMORY_REGISTER = 0xF9; // ISP memory register 60 61 // Define AEI ISP status register commands 62 constexpr uint8_t CMD_CLEAR_STATUS = 0x0; // Clear the status register 63 constexpr uint8_t CMD_RESET_SEQ = 0x01; // This command will reset ISP OS for 64 // another attempt of a sequential 65 // programming operation. 66 constexpr uint8_t CMD_BOOT_ISP = 0x02; // Boot the In-System Programming System. 67 constexpr uint8_t CMD_BOOT_PWR = 0x03; // Attempt to boot the Power Management 68 // OS. 69 70 // Define AEI ISP response status bit 71 constexpr uint8_t B_CHKSUM_ERR = 0x0; // The checksum verification 72 // unsuccessful 73 constexpr uint8_t B_CHKSUM_SUCCESS = 0x1; // The checksum 74 // verification successful. 75 constexpr uint8_t B_MEM_ERR = 0x2; // Memory boundry error indication. 76 constexpr uint8_t B_ALIGN_ERR = 0x4; // Address error indication. 77 constexpr uint8_t B_KEY_ERR = 0x8; // Invalid Key 78 constexpr uint8_t B_START_ERR = 0x10; // Error indicator set at startup. 79 constexpr uint8_t B_IMG_MISSMATCH_ERR = 0x20; // Firmware image does not match 80 // PSU 81 constexpr uint8_t B_ISP_MODE = 0x40; // ISP mode 82 constexpr uint8_t B_ISP_MODE_CHKSUM_GOOD = 0x41; // ISP mode & good checksum. 83 constexpr uint8_t B_PRGM_BUSY = 0x80; // Write operation in progress. 84 constexpr uint8_t SUCCESSFUL_ISP_REBOOT_STATUS = 0x0; // Successful ISP reboot 85 // status 86 87 #ifdef __clang__ 88 #pragma clang diagnostic pop 89 #endif 90 91 using namespace phosphor::logging; 92 namespace util = phosphor::power::util; 93 94 int AeiUpdater::doUpdate() 95 { 96 i2cInterface = Updater::getI2C(); 97 if (i2cInterface == nullptr) 98 { 99 throw std::runtime_error("I2C interface error"); 100 } 101 bool cleanFailedIspMode = false; // Flag to prevent download and continue to 102 // restore the PSU to it's original state. 103 104 // Set ISP mode by writing necessary keys and resetting ISP status 105 if (!writeIspKey() || !writeIspMode() || !writeIspStatusReset()) 106 { 107 lg2::error("Failed to set ISP key or mode or reset ISP status"); 108 cleanFailedIspMode = true; 109 } 110 111 if (cleanFailedIspMode) 112 { 113 return 1; 114 } 115 return 0; // Update successful. 116 } 117 118 bool AeiUpdater::writeIspKey() 119 { 120 // ISP Key to unlock programming mode ( ASCII for "artY"). 121 constexpr std::array<uint8_t, 4> unlockData = {0x61, 0x72, 0x74, 122 0x59}; // ISP Key "artY" 123 try 124 { 125 // Send ISP Key to unlock device for firmware update 126 i2cInterface->write(KEY_REGISTER, unlockData.size(), unlockData.data()); 127 return true; 128 } 129 catch (const std::exception& e) 130 { 131 // Log failure if I2C write fails. 132 lg2::error("I2C write failed: {ERROR}", "ERROR", e); 133 return false; 134 } 135 } 136 137 bool AeiUpdater::writeIspMode() 138 { 139 // Attempt to set device in ISP mode with retries. 140 uint8_t ispStatus = 0x0; 141 for (int retry = 0; retry < MAX_RETRIES; ++retry) 142 { 143 try 144 { 145 // Write command to enter ISP mode. 146 i2cInterface->write(STATUS_REGISTER, CMD_BOOT_ISP); 147 // Delay to allow status register update. 148 updater::internal::delay(ISP_STATUS_DELAY); 149 // Read back status register to confirm ISP mode is active. 150 i2cInterface->read(STATUS_REGISTER, ispStatus); 151 152 if (ispStatus & B_ISP_MODE) 153 { 154 return true; 155 } 156 } 157 catch (const std::exception& e) 158 { 159 // Log I2C error with each retry attempt. 160 lg2::error("I2C error during ISP mode write/read: {ERROR}", "ERROR", 161 e); 162 } 163 } 164 lg2::error("Failed to set ISP Mode"); 165 return false; // Failed to set ISP Mode after retries 166 } 167 168 bool AeiUpdater::writeIspStatusReset() 169 { 170 // Reset ISP status register before firmware download. 171 uint8_t ispStatus = 0; 172 try 173 { 174 i2cInterface->write(STATUS_REGISTER, 175 CMD_RESET_SEQ); // Start reset sequence. 176 for (int retry = 0; retry < MAX_RETRIES; ++retry) 177 { 178 i2cInterface->read(STATUS_REGISTER, ispStatus); 179 if (ispStatus == B_ISP_MODE) 180 { 181 return true; // ISP status reset successfully. 182 } 183 i2cInterface->write(STATUS_REGISTER, 184 CMD_CLEAR_STATUS); // Clear status if 185 // not reset. 186 } 187 } 188 catch (const std::exception& e) 189 { 190 // Log any errors encountered during reset sequence. 191 lg2::error("I2C Read/Write error during ISP reset: {ERROR}", "ERROR", 192 e); 193 } 194 lg2::error("Failed to reset ISP Status"); 195 return false; 196 } 197 198 std::string AeiUpdater::getFirmwarePath() 199 { 200 const std::string fspath = 201 updater::internal::getFWFilenamePath(getImageDir()); 202 if (fspath.empty()) 203 { 204 lg2::error("Firmware file path not found"); 205 } 206 return fspath; 207 } 208 209 bool AeiUpdater::isFirmwareFileValid(const std::string& fspath) 210 { 211 if (!updater::internal::validateFWFile(fspath)) 212 { 213 lg2::error("Firmware validation failed"); 214 return false; 215 } 216 return true; 217 } 218 219 std::unique_ptr<std::ifstream> 220 AeiUpdater::openFirmwareFile(const std::string& fspath) 221 { 222 auto inputFile = updater::internal::openFirmwareFile(fspath); 223 if (!inputFile) 224 { 225 lg2::error("Failed to open firmware file"); 226 } 227 return inputFile; 228 } 229 230 std::vector<uint8_t> AeiUpdater::readFirmwareBlock(std::ifstream& file, 231 const size_t& bytesToRead) 232 { 233 auto block = updater::internal::readFirmwareBytes(file, bytesToRead); 234 return block; 235 } 236 237 std::vector<uint8_t> 238 AeiUpdater::prepareCommandBlock(const std::vector<uint8_t>& dataBlockRead) 239 { 240 std::vector<uint8_t> cmdBlockWrite = {ISP_MEMORY_REGISTER, 241 BLOCK_WRITE_SIZE}; 242 243 cmdBlockWrite.insert(cmdBlockWrite.end(), byteSwappedIndex.begin(), 244 byteSwappedIndex.end()); 245 cmdBlockWrite.insert(cmdBlockWrite.end(), dataBlockRead.begin(), 246 dataBlockRead.end()); 247 248 // Resize to ensure it matches BLOCK_WRITE_SIZE + 1 and append CRC 249 if (cmdBlockWrite.size() != BLOCK_WRITE_SIZE + 1) 250 { 251 cmdBlockWrite.resize(BLOCK_WRITE_SIZE + 1, 0xFF); 252 } 253 cmdBlockWrite.push_back(updater::internal::calculateCRC8(cmdBlockWrite)); 254 // Remove the F9 and byte count 255 cmdBlockWrite.erase(cmdBlockWrite.begin(), cmdBlockWrite.begin() + 2); 256 257 return cmdBlockWrite; 258 } 259 260 void AeiUpdater::ispReboot() 261 { 262 updater::internal::delay( 263 MEM_COMPLETE_DELAY); // Delay before starting the reboot process 264 265 try 266 { 267 // Write reboot command to the status register 268 i2cInterface->write(STATUS_REGISTER, CMD_BOOT_PWR); 269 270 updater::internal::delay( 271 REBOOT_DELAY); // Add delay after writing reboot command 272 } 273 catch (const std::exception& e) 274 { 275 lg2::error("I2C write error during reboot: {ERROR}", "ERROR", e); 276 } 277 } 278 279 bool AeiUpdater::ispReadRebootStatus() 280 { 281 try 282 { 283 // Read from the status register to verify reboot 284 uint8_t data = 1; // Initialize data to a non-zero value 285 i2cInterface->read(STATUS_REGISTER, data); 286 287 uint8_t status = SUCCESSFUL_ISP_REBOOT_STATUS; 288 // If the reboot was successful, the read data should be 0 289 if (data == status) 290 { 291 lg2::info("ISP Status Reboot successful."); 292 return true; 293 } 294 } 295 catch (const std::exception& e) 296 { 297 lg2::error("I2C read error during reboot attempt: {ERROR}", "ERROR", e); 298 } 299 return false; 300 } 301 302 } // namespace aeiUpdater 303