blob: 07e74e951d2c3d7d2457a160cc157c61ad5eb6bc [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 */
Faisal Awada57fb6642025-02-21 16:52:54 -060016
Lei YUd19df252019-10-25 17:31:52 +080017#include "updater.hpp"
18
Faisal Awada5ace9fb2025-01-07 13:26:25 -060019#include "aei_updater.hpp"
Lei YUe8c9cd62019-11-04 14:24:41 +080020#include "pmbus.hpp"
Lei YUcfc040c2019-10-29 17:10:26 +080021#include "types.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080022#include "utility.hpp"
Shawn McCarney14572cf2024-11-06 12:17:57 -060023#include "utils.hpp"
Faisal Awadafe5b5c62025-03-22 10:50:01 -050024#include "validator.hpp"
Faisal Awada57fb6642025-02-21 16:52:54 -060025#include "version.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080026
Faisal Awadaf9b426b2025-01-31 11:44:48 -060027#include <phosphor-logging/lg2.hpp>
Faisal Awada57fb6642025-02-21 16:52:54 -060028#include <xyz/openbmc_project/Logging/Create/client.hpp>
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060029
Lei YU34fb8bd2019-11-07 14:24:20 +080030#include <chrono>
Lei YU9ab6d752019-10-28 17:03:20 +080031#include <fstream>
Faisal Awada57fb6642025-02-21 16:52:54 -060032#include <iostream>
33#include <map>
34#include <string>
Lei YU9ab6d752019-10-28 17:03:20 +080035
36using namespace phosphor::logging;
37namespace util = phosphor::power::util;
38
Lei YUd19df252019-10-25 17:31:52 +080039namespace updater
40{
41
Lei YU9ab6d752019-10-28 17:03:20 +080042namespace internal
43{
44
Faisal Awadaec61bbd2024-11-04 08:46:20 -060045// Define the CRC-8 polynomial (CRC-8-CCITT)
46constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
47constexpr uint8_t CRC8_INITIAL = 0x00;
48
Faisal Awadaec61bbd2024-11-04 08:46:20 -060049// Get the appropriate Updater class instance based PSU model number
50std::unique_ptr<updater::Updater> getClassInstance(
51 const std::string& model, const std::string& psuInventoryPath,
52 const std::string& devPath, const std::string& imageDir)
53{
Faisal Awada5ace9fb2025-01-07 13:26:25 -060054 if (model == "51E9" || model == "51DA")
Faisal Awadaec61bbd2024-11-04 08:46:20 -060055 {
Faisal Awada5ace9fb2025-01-07 13:26:25 -060056 return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
57 devPath, imageDir);
Faisal Awadaec61bbd2024-11-04 08:46:20 -060058 }
59 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
60}
61
62// Function to locate FW file with model and extension bin or hex
63const std::string getFWFilenamePath(const std::string& directory)
64{
65 namespace fs = std::filesystem;
66 // Get the last part of the directory name (model number)
67 std::string model = fs::path(directory).filename().string();
68 for (const auto& entry : fs::directory_iterator(directory))
69 {
70 if (entry.is_regular_file())
71 {
72 std::string filename = entry.path().filename().string();
73
Faisal Awada5ace9fb2025-01-07 13:26:25 -060074 if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
Faisal Awadaec61bbd2024-11-04 08:46:20 -060075 {
76 return directory + "/" + filename;
77 }
78 }
79 }
80 return "";
81}
82
83// Compute CRC-8 checksum for a vector of bytes
84uint8_t calculateCRC8(const std::vector<uint8_t>& data)
85{
86 uint8_t crc = CRC8_INITIAL;
87
88 for (const auto& byte : data)
89 {
90 crc ^= byte;
91 for (int i = 0; i < 8; ++i)
92 {
93 if (crc & 0x80)
94 crc = (crc << 1) ^ CRC8_POLYNOMIAL;
95 else
96 crc <<= 1;
97 }
98 }
99 return crc;
100}
101
102// Delay execution for a specified number of milliseconds
103void delay(const int& milliseconds)
104{
105 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
106}
107
108// Convert big endian (32 bit integer) to a vector of little endian.
109std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue)
110{
111 std::vector<uint8_t> littleEndianBytes(4);
112
113 littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF;
114 littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF;
115 littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF;
116 littleEndianBytes[0] = bigEndianValue & 0xFF;
117 return littleEndianBytes;
118}
119
120// Validate the existence and size of a firmware file.
121bool validateFWFile(const std::string& fileName)
122{
123 // Ensure the file exists and get the file size.
124 if (!std::filesystem::exists(fileName))
125 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600126 lg2::error("Firmware file not found: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600127 return false;
128 }
129
130 // Check the file size
131 auto fileSize = std::filesystem::file_size(fileName);
132 if (fileSize == 0)
133 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600134 lg2::error("Firmware {FILE} is empty", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600135 return false;
136 }
137 return true;
138}
139
140// Open a firmware file for reading in binary mode.
141std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
142{
143 if (fileName.empty())
144 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600145 lg2::error("Firmware file path is not provided");
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600146 return nullptr;
147 }
148 auto inputFile =
149 std::make_unique<std::ifstream>(fileName, std::ios::binary);
150 if (!inputFile->is_open())
151 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600152 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600153 return nullptr;
154 }
155 return inputFile;
156}
157
158// Read firmware bytes from input stream.
159std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
160 const size_t numberOfBytesToRead)
161{
162 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
163 try
164 {
165 // Enable exceptions for failbit and badbit
166 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
167 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
168 numberOfBytesToRead);
169 size_t bytesRead = inputFile.gcount();
170 if (bytesRead != numberOfBytesToRead)
171 {
172 readDataBytes.resize(bytesRead);
173 }
174 }
175 catch (const std::ios_base::failure& e)
176 {
Faisal Awada57fb6642025-02-21 16:52:54 -0600177 lg2::error("Error reading firmware: {ERROR}", "ERROR", e);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600178 readDataBytes.clear();
179 }
180 return readDataBytes;
181}
182
Lei YU9ab6d752019-10-28 17:03:20 +0800183} // namespace internal
184
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600185bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
186 const std::string& imageDir)
Lei YUd19df252019-10-25 17:31:52 +0800187{
Shawn McCarney23dee382024-11-11 18:41:49 -0600188 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600189
Lei YU9ab6d752019-10-28 17:03:20 +0800190 if (devPath.empty())
191 {
192 return false;
193 }
194
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600195 std::filesystem::path fsPath(imageDir);
196
197 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
198 fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
199
200 if (!updaterPtr->isReadyToUpdate())
Lei YU575ed132019-10-29 17:22:16 +0800201 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600202 lg2::error("PSU not ready to update PSU = {PATH}", "PATH",
203 psuInventoryPath);
Lei YU575ed132019-10-29 17:22:16 +0800204 return false;
205 }
206
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600207 updaterPtr->bindUnbind(false);
208 updaterPtr->createI2CDevice();
209 int ret = updaterPtr->doUpdate();
210 updaterPtr->bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +0800211 return ret == 0;
212}
213
Faisal Awadafe5b5c62025-03-22 10:50:01 -0500214bool validateAndUpdate(sdbusplus::bus_t& bus,
215 const std::string& psuInventoryPath,
216 const std::string& imageDir)
217{
218 auto poweredOn = phosphor::power::util::isPoweredOn(bus, true);
219 validator::PSUUpdateValidator psuValidator(bus, psuInventoryPath);
220 if (!poweredOn && psuValidator.validToUpdate())
221 {
222 return updater::update(bus, psuInventoryPath, imageDir);
223 }
224 else
225 {
226 return false;
227 }
228}
229
Lei YU9ab6d752019-10-28 17:03:20 +0800230Updater::Updater(const std::string& psuInventoryPath,
231 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400232 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600233 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800234{
235 fs::path p = fs::path(devPath) / "driver";
236 try
237 {
238 driverPath =
239 fs::canonical(p); // Get the path that points to the driver dir
240 }
241 catch (const fs::filesystem_error& e)
242 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600243 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
Faisal Awada57fb6642025-02-21 16:52:54 -0600244 "PATH", devPath, "ERR", e);
Lei YU9ab6d752019-10-28 17:03:20 +0800245 }
Lei YU9ab6d752019-10-28 17:03:20 +0800246}
247
248// During PSU update, it needs to access the PSU i2c device directly, so it
249// needs to unbind the driver during the update, and re-bind after it's done.
250// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
251// errors. So set the PSU inventory's Present property to false so that
252// psu-monitor will not report any errors.
253void Updater::bindUnbind(bool doBind)
254{
255 if (!doBind)
256 {
257 // Set non-present before unbind the driver
258 setPresent(doBind);
259 }
260 auto p = driverPath;
261 p /= doBind ? "bind" : "unbind";
262 std::ofstream out(p.string());
263 out << devName;
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600264 if (doBind)
265 {
266 internal::delay(500);
267 }
268 out.close();
Lei YU9ab6d752019-10-28 17:03:20 +0800269
270 if (doBind)
271 {
272 // Set to present after bind the driver
273 setPresent(doBind);
274 }
275}
276
277void Updater::setPresent(bool present)
278{
279 try
280 {
281 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
282 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
283 service, bus, present);
284 }
285 catch (const std::exception& e)
286 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600287 lg2::error(
288 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
289 "PATH", psuInventoryPath, "PRESENT", present);
Lei YU9ab6d752019-10-28 17:03:20 +0800290 }
291}
292
Lei YU575ed132019-10-29 17:22:16 +0800293bool Updater::isReadyToUpdate()
294{
Lei YUe8c9cd62019-11-04 14:24:41 +0800295 using namespace phosphor::pmbus;
296
Lei YU575ed132019-10-29 17:22:16 +0800297 // Pre-condition for updating PSU:
298 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800299 // * At least one other PSU is present
300 // * All other PSUs that are present are having AC input and DC standby
301 // output
302
303 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800304 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600305 lg2::warning("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800306 return false;
307 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800308
309 bool hasOtherPresent = false;
310 auto paths = util::getPSUInventoryPaths(bus);
311 for (const auto& p : paths)
312 {
313 if (p == psuInventoryPath)
314 {
315 // Skip check for itself
316 continue;
317 }
318
319 // Check PSU present
320 bool present = false;
321 try
322 {
323 auto service = util::getService(p, INVENTORY_IFACE, bus);
324 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
325 service, bus, present);
326 }
327 catch (const std::exception& e)
328 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600329 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800330 }
331 if (!present)
332 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600333 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800334 continue;
335 }
336 hasOtherPresent = true;
337
338 // Typically the driver is still bound here, so it is possible to
339 // directly read the debugfs to get the status.
340 try
341 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600342 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800343 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800344 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
345 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
346 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
347 if ((statusWord & status_word::VOUT_FAULT) ||
348 (statusWord & status_word::INPUT_FAULT_WARN) ||
349 (statusWord & status_word::VIN_UV_FAULT) ||
350 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
351 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
352 // UV_FAULT in vout status.
353 (voutStatus & status_vout::UV_FAULT) ||
354 (voutStatus & status_vout::OV_FAULT))
355 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600356 lg2::warning(
357 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}",
358 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex,
359 voutStatus);
Lei YUe8c9cd62019-11-04 14:24:41 +0800360 return false;
361 }
362 }
363 catch (const std::exception& ex)
364 {
365 // If error occurs on accessing the debugfs, it means something went
366 // wrong, e.g. PSU is not present, and it's not ready to update.
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600367 lg2::error("{EX}", "EX", ex.what());
Lei YUe8c9cd62019-11-04 14:24:41 +0800368 return false;
369 }
370 }
371 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800372}
373
Lei YU9ab6d752019-10-28 17:03:20 +0800374int Updater::doUpdate()
375{
Lei YU34fb8bd2019-11-07 14:24:20 +0800376 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800377
Lei YU34fb8bd2019-11-07 14:24:20 +0800378 uint8_t data;
379 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
380 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
381 uint8_t bootFlag = 0x01;
382 static_assert(sizeof(unlockData) == 12);
383
384 i2c->write(0xf0, sizeof(unlockData), unlockData);
385 printf("Unlock PSU\n");
386
387 std::this_thread::sleep_for(milliseconds(5));
388
389 i2c->write(0xf1, bootFlag);
390 printf("Set boot flag ret\n");
391
392 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800393
394 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800395 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800396 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800397}
398
Lei YU7c2fbbb2019-11-06 14:56:02 +0800399void Updater::createI2CDevice()
400{
Shawn McCarney23dee382024-11-11 18:41:49 -0600401 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800402 i2c = i2c::create(id, addr);
403}
Faisal Awada57fb6642025-02-21 16:52:54 -0600404
405void Updater::createServiceableEventLog(
406 const std::string& errorName, const std::string& severity,
407 std::map<std::string, std::string>& additionalData)
408{
409 if (!isEventLogEnabled() || isEventLoggedThisSession())
410 {
411 return;
412 }
413
414 using namespace sdbusplus::xyz::openbmc_project;
415 using LoggingCreate =
416 sdbusplus::client::xyz::openbmc_project::logging::Create<>;
417 enableEventLoggedThisSession();
418 try
419 {
420 additionalData["_PID"] = std::to_string(getpid());
421 auto method = bus.new_method_call(LoggingCreate::default_service,
422 LoggingCreate::instance_path,
423 LoggingCreate::interface, "Create");
424 method.append(errorName, severity, additionalData);
425
426 bus.call(method);
427 }
428 catch (const sdbusplus::exception::SdBusError& e)
429 {
430 lg2::error(
431 "Failed creating event log for fault {ERROR_NAME}, error {ERR}",
432 "ERROR_NAME", errorName, "ERR", e);
433 }
434 disableEventLogging();
435}
436
437std::map<std::string, std::string> Updater::getI2CAdditionalData()
438{
439 std::map<std::string, std::string> additionalData;
440 auto [id, addr] = utils::parseDeviceName(getDevName());
441 std::string hexIdString = std::format("0x{:x}", id);
442 std::string hexAddrString = std::format("0x{:x}", addr);
443
444 additionalData["CALLOUT_IIC_BUS"] = hexIdString;
445 additionalData["CALLOUT_IIC_ADDR"] = hexAddrString;
446 return additionalData;
447}
448
449/*
450 * callOutI2CEventLog calls out FRUs in the following order:
451 * 1 - PSU high priority
452 * 2 - CALLOUT_IIC_BUS
453 */
454void Updater::callOutI2CEventLog(
455 std::map<std::string, std::string> extraAdditionalData,
456 const std::string& exceptionString, const int errorCode)
457{
458 std::map<std::string, std::string> additionalData = {
459 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
460 additionalData.merge(extraAdditionalData);
461 additionalData.merge(getI2CAdditionalData());
462 additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode);
463 if (!exceptionString.empty())
464 {
465 additionalData["I2C_EXCEPTION"] = exceptionString;
466 }
467 createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY,
468 additionalData);
469}
470
471/*
472 * callOutPsuEventLog calls out PSU and system planar
473 */
474void Updater::callOutPsuEventLog(
475 std::map<std::string, std::string> extraAdditionalData)
476{
477 std::map<std::string, std::string> additionalData = {
478 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
479 additionalData.merge(extraAdditionalData);
480 createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG,
481 updater::ERROR_SEVERITY, additionalData);
482}
483
484/*
485 * callOutSWEventLog calls out the BMC0001 procedure.
486 */
487void Updater::callOutSWEventLog(
488 std::map<std::string, std::string> additionalData)
489{
490 createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG,
491 updater::ERROR_SEVERITY, additionalData);
492}
493
494/*
495 * callOutGoodEventLog calls out a successful firmware update.
496 */
497void Updater::callOutGoodEventLog()
498{
499 std::map<std::string, std::string> additionalData = {
500 {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()},
501 {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}};
502 createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG,
503 updater::INFORMATIONAL_SEVERITY, additionalData);
504}
Lei YUd19df252019-10-25 17:31:52 +0800505} // namespace updater