xref: /openbmc/phosphor-power/tools/power-utils/aei_updater.cpp (revision e1a075956a0d16fa1df92d64e1700eb688d7555e)
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