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 
doUpdate()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 
writeIspKey()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 
writeIspMode()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 
writeIspStatusReset()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 
getFirmwarePath()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 
isFirmwareFileValid(const std::string & fspath)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>
openFirmwareFile(const std::string & fspath)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 
readFirmwareBlock(std::ifstream & file,const size_t & bytesToRead)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>
prepareCommandBlock(const std::vector<uint8_t> & dataBlockRead)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 
ispReboot()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 
ispReadRebootStatus()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