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