blob: 1bf729c352a119f3cc25dad85d91ef9952e8d9ac [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
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060024#include <phosphor-logging/log.hpp>
25
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 {
121 log<level::ERR>(
122 std::format("Firmware file not found: {}", fileName).c_str());
123 return false;
124 }
125
126 // Check the file size
127 auto fileSize = std::filesystem::file_size(fileName);
128 if (fileSize == 0)
129 {
130 log<level::ERR>("Firmware file is empty");
131 return false;
132 }
133 return true;
134}
135
136// Open a firmware file for reading in binary mode.
137std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
138{
139 if (fileName.empty())
140 {
141 log<level::ERR>("Firmware file path is not provided");
142 return nullptr;
143 }
144 auto inputFile =
145 std::make_unique<std::ifstream>(fileName, std::ios::binary);
146 if (!inputFile->is_open())
147 {
148 log<level::ERR>(
149 std::format("Failed to open firmware file: {}", fileName).c_str());
150 return nullptr;
151 }
152 return inputFile;
153}
154
155// Read firmware bytes from input stream.
156std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
157 const size_t numberOfBytesToRead)
158{
159 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
160 try
161 {
162 // Enable exceptions for failbit and badbit
163 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
164 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
165 numberOfBytesToRead);
166 size_t bytesRead = inputFile.gcount();
167 if (bytesRead != numberOfBytesToRead)
168 {
169 readDataBytes.resize(bytesRead);
170 }
171 }
172 catch (const std::ios_base::failure& e)
173 {
174 log<level::ERR>(
175 std::format("Error reading firmware: {}", e.what()).c_str());
176 readDataBytes.clear();
177 }
178 return readDataBytes;
179}
180
Lei YU9ab6d752019-10-28 17:03:20 +0800181} // namespace internal
182
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600183bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
184 const std::string& imageDir)
Lei YUd19df252019-10-25 17:31:52 +0800185{
Shawn McCarney23dee382024-11-11 18:41:49 -0600186 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600187
Lei YU9ab6d752019-10-28 17:03:20 +0800188 if (devPath.empty())
189 {
190 return false;
191 }
192
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600193 std::filesystem::path fsPath(imageDir);
194
195 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
196 fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
197
198 if (!updaterPtr->isReadyToUpdate())
Lei YU575ed132019-10-29 17:22:16 +0800199 {
200 log<level::ERR>("PSU not ready to update",
201 entry("PSU=%s", psuInventoryPath.c_str()));
202 return false;
203 }
204
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600205 updaterPtr->bindUnbind(false);
206 updaterPtr->createI2CDevice();
207 int ret = updaterPtr->doUpdate();
208 updaterPtr->bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +0800209 return ret == 0;
210}
211
212Updater::Updater(const std::string& psuInventoryPath,
213 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400214 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600215 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800216{
217 fs::path p = fs::path(devPath) / "driver";
218 try
219 {
220 driverPath =
221 fs::canonical(p); // Get the path that points to the driver dir
222 }
223 catch (const fs::filesystem_error& e)
224 {
225 log<level::ERR>("Failed to get canonical path",
226 entry("DEVPATH=%s", devPath.c_str()),
227 entry("ERROR=%s", e.what()));
228 throw;
229 }
Lei YU9ab6d752019-10-28 17:03:20 +0800230}
231
232// During PSU update, it needs to access the PSU i2c device directly, so it
233// needs to unbind the driver during the update, and re-bind after it's done.
234// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
235// errors. So set the PSU inventory's Present property to false so that
236// psu-monitor will not report any errors.
237void Updater::bindUnbind(bool doBind)
238{
239 if (!doBind)
240 {
241 // Set non-present before unbind the driver
242 setPresent(doBind);
243 }
244 auto p = driverPath;
245 p /= doBind ? "bind" : "unbind";
246 std::ofstream out(p.string());
247 out << devName;
248
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 {
266 log<level::ERR>("Failed to set present property",
267 entry("PSU=%s", psuInventoryPath.c_str()),
268 entry("PRESENT=%d", present));
269 }
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 {
Lei YUe8c9cd62019-11-04 14:24:41 +0800284 log<level::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 {
308 log<level::ERR>("Failed to get present property",
309 entry("PSU=%s", p.c_str()));
310 }
311 if (!present)
312 {
313 log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
314 continue;
315 }
316 hasOtherPresent = true;
317
318 // Typically the driver is still bound here, so it is possible to
319 // directly read the debugfs to get the status.
320 try
321 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600322 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800323 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800324 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
325 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
326 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
327 if ((statusWord & status_word::VOUT_FAULT) ||
328 (statusWord & status_word::INPUT_FAULT_WARN) ||
329 (statusWord & status_word::VIN_UV_FAULT) ||
330 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
331 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
332 // UV_FAULT in vout status.
333 (voutStatus & status_vout::UV_FAULT) ||
334 (voutStatus & status_vout::OV_FAULT))
335 {
336 log<level::WARNING>(
337 "Unable to update PSU when other PSU has input/ouput fault",
338 entry("PSU=%s", p.c_str()),
339 entry("STATUS_WORD=0x%04x", statusWord),
340 entry("VOUT_BYTE=0x%02x", voutStatus));
341 return false;
342 }
343 }
344 catch (const std::exception& ex)
345 {
346 // If error occurs on accessing the debugfs, it means something went
347 // wrong, e.g. PSU is not present, and it's not ready to update.
348 log<level::ERR>(ex.what());
349 return false;
350 }
351 }
352 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800353}
354
Lei YU9ab6d752019-10-28 17:03:20 +0800355int Updater::doUpdate()
356{
Lei YU34fb8bd2019-11-07 14:24:20 +0800357 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800358
Lei YU34fb8bd2019-11-07 14:24:20 +0800359 uint8_t data;
360 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
361 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
362 uint8_t bootFlag = 0x01;
363 static_assert(sizeof(unlockData) == 12);
364
365 i2c->write(0xf0, sizeof(unlockData), unlockData);
366 printf("Unlock PSU\n");
367
368 std::this_thread::sleep_for(milliseconds(5));
369
370 i2c->write(0xf1, bootFlag);
371 printf("Set boot flag ret\n");
372
373 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800374
375 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800376 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800377 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800378}
379
Lei YU7c2fbbb2019-11-06 14:56:02 +0800380void Updater::createI2CDevice()
381{
Shawn McCarney23dee382024-11-11 18:41:49 -0600382 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800383 i2c = i2c::create(id, addr);
384}
Lei YUd19df252019-10-25 17:31:52 +0800385} // namespace updater