blob: 7da839f7670a526604d6545c73b2b6971889b212 [file] [log] [blame]
Lei YUd19df252019-10-25 17:31:52 +08001/**
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 */
Lei YUd19df252019-10-25 17:31:52 +080016#include "updater.hpp"
17
Faisal Awada5ace9fb2025-01-07 13:26:25 -060018#include "aei_updater.hpp"
Lei YUe8c9cd62019-11-04 14:24:41 +080019#include "pmbus.hpp"
Lei YUcfc040c2019-10-29 17:10:26 +080020#include "types.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080021#include "utility.hpp"
Shawn McCarney14572cf2024-11-06 12:17:57 -060022#include "utils.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080023
Faisal Awadaf9b426b2025-01-31 11:44:48 -060024#include <phosphor-logging/lg2.hpp>
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060025
Lei YU34fb8bd2019-11-07 14:24:20 +080026#include <chrono>
Lei YU9ab6d752019-10-28 17:03:20 +080027#include <fstream>
Lei YU34fb8bd2019-11-07 14:24:20 +080028#include <thread>
Faisal Awadaec61bbd2024-11-04 08:46:20 -060029#include <vector>
Lei YU9ab6d752019-10-28 17:03:20 +080030
31using namespace phosphor::logging;
32namespace util = phosphor::power::util;
33
Lei YUd19df252019-10-25 17:31:52 +080034namespace updater
35{
36
Lei YU9ab6d752019-10-28 17:03:20 +080037namespace internal
38{
39
Faisal Awadaec61bbd2024-11-04 08:46:20 -060040// Define the CRC-8 polynomial (CRC-8-CCITT)
41constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
42constexpr uint8_t CRC8_INITIAL = 0x00;
43
Faisal Awadaec61bbd2024-11-04 08:46:20 -060044// Get the appropriate Updater class instance based PSU model number
45std::unique_ptr<updater::Updater> getClassInstance(
46 const std::string& model, const std::string& psuInventoryPath,
47 const std::string& devPath, const std::string& imageDir)
48{
Faisal Awada5ace9fb2025-01-07 13:26:25 -060049 if (model == "51E9" || model == "51DA")
Faisal Awadaec61bbd2024-11-04 08:46:20 -060050 {
Faisal Awada5ace9fb2025-01-07 13:26:25 -060051 return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
52 devPath, imageDir);
Faisal Awadaec61bbd2024-11-04 08:46:20 -060053 }
54 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
55}
56
57// Function to locate FW file with model and extension bin or hex
58const std::string getFWFilenamePath(const std::string& directory)
59{
60 namespace fs = std::filesystem;
61 // Get the last part of the directory name (model number)
62 std::string model = fs::path(directory).filename().string();
63 for (const auto& entry : fs::directory_iterator(directory))
64 {
65 if (entry.is_regular_file())
66 {
67 std::string filename = entry.path().filename().string();
68
Faisal Awada5ace9fb2025-01-07 13:26:25 -060069 if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
Faisal Awadaec61bbd2024-11-04 08:46:20 -060070 {
71 return directory + "/" + filename;
72 }
73 }
74 }
75 return "";
76}
77
78// Compute CRC-8 checksum for a vector of bytes
79uint8_t calculateCRC8(const std::vector<uint8_t>& data)
80{
81 uint8_t crc = CRC8_INITIAL;
82
83 for (const auto& byte : data)
84 {
85 crc ^= byte;
86 for (int i = 0; i < 8; ++i)
87 {
88 if (crc & 0x80)
89 crc = (crc << 1) ^ CRC8_POLYNOMIAL;
90 else
91 crc <<= 1;
92 }
93 }
94 return crc;
95}
96
97// Delay execution for a specified number of milliseconds
98void delay(const int& milliseconds)
99{
100 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
101}
102
103// Convert big endian (32 bit integer) to a vector of little endian.
104std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue)
105{
106 std::vector<uint8_t> littleEndianBytes(4);
107
108 littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF;
109 littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF;
110 littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF;
111 littleEndianBytes[0] = bigEndianValue & 0xFF;
112 return littleEndianBytes;
113}
114
115// Validate the existence and size of a firmware file.
116bool validateFWFile(const std::string& fileName)
117{
118 // Ensure the file exists and get the file size.
119 if (!std::filesystem::exists(fileName))
120 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600121 lg2::error("Firmware file not found: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600122 return false;
123 }
124
125 // Check the file size
126 auto fileSize = std::filesystem::file_size(fileName);
127 if (fileSize == 0)
128 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600129 lg2::error("Firmware {FILE} is empty", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600130 return false;
131 }
132 return true;
133}
134
135// Open a firmware file for reading in binary mode.
136std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
137{
138 if (fileName.empty())
139 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600140 lg2::error("Firmware file path is not provided");
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600141 return nullptr;
142 }
143 auto inputFile =
144 std::make_unique<std::ifstream>(fileName, std::ios::binary);
145 if (!inputFile->is_open())
146 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600147 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600148 return nullptr;
149 }
150 return inputFile;
151}
152
153// Read firmware bytes from input stream.
154std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
155 const size_t numberOfBytesToRead)
156{
157 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
158 try
159 {
160 // Enable exceptions for failbit and badbit
161 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
162 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
163 numberOfBytesToRead);
164 size_t bytesRead = inputFile.gcount();
165 if (bytesRead != numberOfBytesToRead)
166 {
167 readDataBytes.resize(bytesRead);
168 }
169 }
170 catch (const std::ios_base::failure& e)
171 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600172 lg2::error("Error reading firmware: {ERROR}", "ERROR", e.what());
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600173 readDataBytes.clear();
174 }
175 return readDataBytes;
176}
177
Lei YU9ab6d752019-10-28 17:03:20 +0800178} // namespace internal
179
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600180bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
181 const std::string& imageDir)
Lei YUd19df252019-10-25 17:31:52 +0800182{
Shawn McCarney23dee382024-11-11 18:41:49 -0600183 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600184
Lei YU9ab6d752019-10-28 17:03:20 +0800185 if (devPath.empty())
186 {
187 return false;
188 }
189
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600190 std::filesystem::path fsPath(imageDir);
191
192 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
193 fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
194
195 if (!updaterPtr->isReadyToUpdate())
Lei YU575ed132019-10-29 17:22:16 +0800196 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600197 lg2::error("PSU not ready to update PSU = {PATH}", "PATH",
198 psuInventoryPath);
Lei YU575ed132019-10-29 17:22:16 +0800199 return false;
200 }
201
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600202 updaterPtr->bindUnbind(false);
203 updaterPtr->createI2CDevice();
204 int ret = updaterPtr->doUpdate();
205 updaterPtr->bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +0800206 return ret == 0;
207}
208
209Updater::Updater(const std::string& psuInventoryPath,
210 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400211 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600212 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800213{
214 fs::path p = fs::path(devPath) / "driver";
215 try
216 {
217 driverPath =
218 fs::canonical(p); // Get the path that points to the driver dir
219 }
220 catch (const fs::filesystem_error& e)
221 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600222 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
223 "PATH", devPath, "ERR", e.what());
Lei YU9ab6d752019-10-28 17:03:20 +0800224 }
Lei YU9ab6d752019-10-28 17:03:20 +0800225}
226
227// During PSU update, it needs to access the PSU i2c device directly, so it
228// needs to unbind the driver during the update, and re-bind after it's done.
229// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
230// errors. So set the PSU inventory's Present property to false so that
231// psu-monitor will not report any errors.
232void Updater::bindUnbind(bool doBind)
233{
234 if (!doBind)
235 {
236 // Set non-present before unbind the driver
237 setPresent(doBind);
238 }
239 auto p = driverPath;
240 p /= doBind ? "bind" : "unbind";
241 std::ofstream out(p.string());
242 out << devName;
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600243 if (doBind)
244 {
245 internal::delay(500);
246 }
247 out.close();
Lei YU9ab6d752019-10-28 17:03:20 +0800248
249 if (doBind)
250 {
251 // Set to present after bind the driver
252 setPresent(doBind);
253 }
254}
255
256void Updater::setPresent(bool present)
257{
258 try
259 {
260 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
261 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
262 service, bus, present);
263 }
264 catch (const std::exception& e)
265 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600266 lg2::error(
267 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
268 "PATH", psuInventoryPath, "PRESENT", present);
Lei YU9ab6d752019-10-28 17:03:20 +0800269 }
270}
271
Lei YU575ed132019-10-29 17:22:16 +0800272bool Updater::isReadyToUpdate()
273{
Lei YUe8c9cd62019-11-04 14:24:41 +0800274 using namespace phosphor::pmbus;
275
Lei YU575ed132019-10-29 17:22:16 +0800276 // Pre-condition for updating PSU:
277 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800278 // * At least one other PSU is present
279 // * All other PSUs that are present are having AC input and DC standby
280 // output
281
282 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800283 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600284 lg2::warning("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800285 return false;
286 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800287
288 bool hasOtherPresent = false;
289 auto paths = util::getPSUInventoryPaths(bus);
290 for (const auto& p : paths)
291 {
292 if (p == psuInventoryPath)
293 {
294 // Skip check for itself
295 continue;
296 }
297
298 // Check PSU present
299 bool present = false;
300 try
301 {
302 auto service = util::getService(p, INVENTORY_IFACE, bus);
303 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
304 service, bus, present);
305 }
306 catch (const std::exception& e)
307 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600308 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800309 }
310 if (!present)
311 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600312 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800313 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 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600321 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800322 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800323 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 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600335 lg2::warning(
336 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}",
337 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex,
338 voutStatus);
Lei YUe8c9cd62019-11-04 14:24:41 +0800339 return false;
340 }
341 }
342 catch (const std::exception& ex)
343 {
344 // If error occurs on accessing the debugfs, it means something went
345 // wrong, e.g. PSU is not present, and it's not ready to update.
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600346 lg2::error("{EX}", "EX", ex.what());
Lei YUe8c9cd62019-11-04 14:24:41 +0800347 return false;
348 }
349 }
350 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800351}
352
Lei YU9ab6d752019-10-28 17:03:20 +0800353int Updater::doUpdate()
354{
Lei YU34fb8bd2019-11-07 14:24:20 +0800355 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800356
Lei YU34fb8bd2019-11-07 14:24:20 +0800357 uint8_t data;
358 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
359 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
360 uint8_t bootFlag = 0x01;
361 static_assert(sizeof(unlockData) == 12);
362
363 i2c->write(0xf0, sizeof(unlockData), unlockData);
364 printf("Unlock PSU\n");
365
366 std::this_thread::sleep_for(milliseconds(5));
367
368 i2c->write(0xf1, bootFlag);
369 printf("Set boot flag ret\n");
370
371 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800372
373 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800374 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800375 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800376}
377
Lei YU7c2fbbb2019-11-06 14:56:02 +0800378void Updater::createI2CDevice()
379{
Shawn McCarney23dee382024-11-11 18:41:49 -0600380 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800381 i2c = i2c::create(id, addr);
382}
Lei YUd19df252019-10-25 17:31:52 +0800383} // namespace updater