blob: 16e225fa41e91e01c7b4aaadd4c240678b88721d [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 Awada57fb6642025-02-21 16:52:54 -060024#include "version.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080025
Faisal Awadaf9b426b2025-01-31 11:44:48 -060026#include <phosphor-logging/lg2.hpp>
Faisal Awada57fb6642025-02-21 16:52:54 -060027#include <xyz/openbmc_project/Logging/Create/client.hpp>
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060028
Lei YU34fb8bd2019-11-07 14:24:20 +080029#include <chrono>
Lei YU9ab6d752019-10-28 17:03:20 +080030#include <fstream>
Faisal Awada57fb6642025-02-21 16:52:54 -060031#include <iostream>
32#include <map>
33#include <string>
Lei YU9ab6d752019-10-28 17:03:20 +080034
35using namespace phosphor::logging;
36namespace util = phosphor::power::util;
37
Lei YUd19df252019-10-25 17:31:52 +080038namespace updater
39{
40
Lei YU9ab6d752019-10-28 17:03:20 +080041namespace internal
42{
43
Faisal Awadaec61bbd2024-11-04 08:46:20 -060044// Define the CRC-8 polynomial (CRC-8-CCITT)
45constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
46constexpr uint8_t CRC8_INITIAL = 0x00;
47
Faisal Awadaec61bbd2024-11-04 08:46:20 -060048// Get the appropriate Updater class instance based PSU model number
49std::unique_ptr<updater::Updater> getClassInstance(
50 const std::string& model, const std::string& psuInventoryPath,
51 const std::string& devPath, const std::string& imageDir)
52{
Faisal Awada5ace9fb2025-01-07 13:26:25 -060053 if (model == "51E9" || model == "51DA")
Faisal Awadaec61bbd2024-11-04 08:46:20 -060054 {
Faisal Awada5ace9fb2025-01-07 13:26:25 -060055 return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
56 devPath, imageDir);
Faisal Awadaec61bbd2024-11-04 08:46:20 -060057 }
58 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
59}
60
61// Function to locate FW file with model and extension bin or hex
62const std::string getFWFilenamePath(const std::string& directory)
63{
64 namespace fs = std::filesystem;
65 // Get the last part of the directory name (model number)
66 std::string model = fs::path(directory).filename().string();
67 for (const auto& entry : fs::directory_iterator(directory))
68 {
69 if (entry.is_regular_file())
70 {
71 std::string filename = entry.path().filename().string();
72
Faisal Awada5ace9fb2025-01-07 13:26:25 -060073 if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
Faisal Awadaec61bbd2024-11-04 08:46:20 -060074 {
75 return directory + "/" + filename;
76 }
77 }
78 }
79 return "";
80}
81
82// Compute CRC-8 checksum for a vector of bytes
83uint8_t calculateCRC8(const std::vector<uint8_t>& data)
84{
85 uint8_t crc = CRC8_INITIAL;
86
87 for (const auto& byte : data)
88 {
89 crc ^= byte;
90 for (int i = 0; i < 8; ++i)
91 {
92 if (crc & 0x80)
93 crc = (crc << 1) ^ CRC8_POLYNOMIAL;
94 else
95 crc <<= 1;
96 }
97 }
98 return crc;
99}
100
101// Delay execution for a specified number of milliseconds
102void delay(const int& milliseconds)
103{
104 std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
105}
106
107// Convert big endian (32 bit integer) to a vector of little endian.
108std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue)
109{
110 std::vector<uint8_t> littleEndianBytes(4);
111
112 littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF;
113 littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF;
114 littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF;
115 littleEndianBytes[0] = bigEndianValue & 0xFF;
116 return littleEndianBytes;
117}
118
119// Validate the existence and size of a firmware file.
120bool validateFWFile(const std::string& fileName)
121{
122 // Ensure the file exists and get the file size.
123 if (!std::filesystem::exists(fileName))
124 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600125 lg2::error("Firmware file not found: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600126 return false;
127 }
128
129 // Check the file size
130 auto fileSize = std::filesystem::file_size(fileName);
131 if (fileSize == 0)
132 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600133 lg2::error("Firmware {FILE} is empty", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600134 return false;
135 }
136 return true;
137}
138
139// Open a firmware file for reading in binary mode.
140std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
141{
142 if (fileName.empty())
143 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600144 lg2::error("Firmware file path is not provided");
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600145 return nullptr;
146 }
147 auto inputFile =
148 std::make_unique<std::ifstream>(fileName, std::ios::binary);
149 if (!inputFile->is_open())
150 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600151 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600152 return nullptr;
153 }
154 return inputFile;
155}
156
157// Read firmware bytes from input stream.
158std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
159 const size_t numberOfBytesToRead)
160{
161 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
162 try
163 {
164 // Enable exceptions for failbit and badbit
165 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
166 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
167 numberOfBytesToRead);
168 size_t bytesRead = inputFile.gcount();
169 if (bytesRead != numberOfBytesToRead)
170 {
171 readDataBytes.resize(bytesRead);
172 }
173 }
174 catch (const std::ios_base::failure& e)
175 {
Faisal Awada57fb6642025-02-21 16:52:54 -0600176 lg2::error("Error reading firmware: {ERROR}", "ERROR", e);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600177 readDataBytes.clear();
178 }
179 return readDataBytes;
180}
181
Lei YU9ab6d752019-10-28 17:03:20 +0800182} // namespace internal
183
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600184bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
185 const std::string& imageDir)
Lei YUd19df252019-10-25 17:31:52 +0800186{
Shawn McCarney23dee382024-11-11 18:41:49 -0600187 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600188
Lei YU9ab6d752019-10-28 17:03:20 +0800189 if (devPath.empty())
190 {
191 return false;
192 }
193
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600194 std::filesystem::path fsPath(imageDir);
195
196 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
197 fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
198
199 if (!updaterPtr->isReadyToUpdate())
Lei YU575ed132019-10-29 17:22:16 +0800200 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600201 lg2::error("PSU not ready to update PSU = {PATH}", "PATH",
202 psuInventoryPath);
Lei YU575ed132019-10-29 17:22:16 +0800203 return false;
204 }
205
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600206 updaterPtr->bindUnbind(false);
207 updaterPtr->createI2CDevice();
208 int ret = updaterPtr->doUpdate();
209 updaterPtr->bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +0800210 return ret == 0;
211}
212
213Updater::Updater(const std::string& psuInventoryPath,
214 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400215 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600216 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800217{
218 fs::path p = fs::path(devPath) / "driver";
219 try
220 {
221 driverPath =
222 fs::canonical(p); // Get the path that points to the driver dir
223 }
224 catch (const fs::filesystem_error& e)
225 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600226 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
Faisal Awada57fb6642025-02-21 16:52:54 -0600227 "PATH", devPath, "ERR", e);
Lei YU9ab6d752019-10-28 17:03:20 +0800228 }
Lei YU9ab6d752019-10-28 17:03:20 +0800229}
230
231// During PSU update, it needs to access the PSU i2c device directly, so it
232// needs to unbind the driver during the update, and re-bind after it's done.
233// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
234// errors. So set the PSU inventory's Present property to false so that
235// psu-monitor will not report any errors.
236void Updater::bindUnbind(bool doBind)
237{
238 if (!doBind)
239 {
240 // Set non-present before unbind the driver
241 setPresent(doBind);
242 }
243 auto p = driverPath;
244 p /= doBind ? "bind" : "unbind";
245 std::ofstream out(p.string());
246 out << devName;
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600247 if (doBind)
248 {
249 internal::delay(500);
250 }
251 out.close();
Lei YU9ab6d752019-10-28 17:03:20 +0800252
253 if (doBind)
254 {
255 // Set to present after bind the driver
256 setPresent(doBind);
257 }
258}
259
260void Updater::setPresent(bool present)
261{
262 try
263 {
264 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
265 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
266 service, bus, present);
267 }
268 catch (const std::exception& e)
269 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600270 lg2::error(
271 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
272 "PATH", psuInventoryPath, "PRESENT", present);
Lei YU9ab6d752019-10-28 17:03:20 +0800273 }
274}
275
Lei YU575ed132019-10-29 17:22:16 +0800276bool Updater::isReadyToUpdate()
277{
Lei YUe8c9cd62019-11-04 14:24:41 +0800278 using namespace phosphor::pmbus;
279
Lei YU575ed132019-10-29 17:22:16 +0800280 // Pre-condition for updating PSU:
281 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800282 // * At least one other PSU is present
283 // * All other PSUs that are present are having AC input and DC standby
284 // output
285
286 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800287 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600288 lg2::warning("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800289 return false;
290 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800291
292 bool hasOtherPresent = false;
293 auto paths = util::getPSUInventoryPaths(bus);
294 for (const auto& p : paths)
295 {
296 if (p == psuInventoryPath)
297 {
298 // Skip check for itself
299 continue;
300 }
301
302 // Check PSU present
303 bool present = false;
304 try
305 {
306 auto service = util::getService(p, INVENTORY_IFACE, bus);
307 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
308 service, bus, present);
309 }
310 catch (const std::exception& e)
311 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600312 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800313 }
314 if (!present)
315 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600316 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800317 continue;
318 }
319 hasOtherPresent = true;
320
321 // Typically the driver is still bound here, so it is possible to
322 // directly read the debugfs to get the status.
323 try
324 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600325 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800326 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800327 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
328 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
329 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
330 if ((statusWord & status_word::VOUT_FAULT) ||
331 (statusWord & status_word::INPUT_FAULT_WARN) ||
332 (statusWord & status_word::VIN_UV_FAULT) ||
333 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
334 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
335 // UV_FAULT in vout status.
336 (voutStatus & status_vout::UV_FAULT) ||
337 (voutStatus & status_vout::OV_FAULT))
338 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600339 lg2::warning(
340 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}",
341 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex,
342 voutStatus);
Lei YUe8c9cd62019-11-04 14:24:41 +0800343 return false;
344 }
345 }
346 catch (const std::exception& ex)
347 {
348 // If error occurs on accessing the debugfs, it means something went
349 // wrong, e.g. PSU is not present, and it's not ready to update.
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600350 lg2::error("{EX}", "EX", ex.what());
Lei YUe8c9cd62019-11-04 14:24:41 +0800351 return false;
352 }
353 }
354 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800355}
356
Lei YU9ab6d752019-10-28 17:03:20 +0800357int Updater::doUpdate()
358{
Lei YU34fb8bd2019-11-07 14:24:20 +0800359 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800360
Lei YU34fb8bd2019-11-07 14:24:20 +0800361 uint8_t data;
362 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
363 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
364 uint8_t bootFlag = 0x01;
365 static_assert(sizeof(unlockData) == 12);
366
367 i2c->write(0xf0, sizeof(unlockData), unlockData);
368 printf("Unlock PSU\n");
369
370 std::this_thread::sleep_for(milliseconds(5));
371
372 i2c->write(0xf1, bootFlag);
373 printf("Set boot flag ret\n");
374
375 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800376
377 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800378 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800379 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800380}
381
Lei YU7c2fbbb2019-11-06 14:56:02 +0800382void Updater::createI2CDevice()
383{
Shawn McCarney23dee382024-11-11 18:41:49 -0600384 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800385 i2c = i2c::create(id, addr);
386}
Faisal Awada57fb6642025-02-21 16:52:54 -0600387
388void Updater::createServiceableEventLog(
389 const std::string& errorName, const std::string& severity,
390 std::map<std::string, std::string>& additionalData)
391{
392 if (!isEventLogEnabled() || isEventLoggedThisSession())
393 {
394 return;
395 }
396
397 using namespace sdbusplus::xyz::openbmc_project;
398 using LoggingCreate =
399 sdbusplus::client::xyz::openbmc_project::logging::Create<>;
400 enableEventLoggedThisSession();
401 try
402 {
403 additionalData["_PID"] = std::to_string(getpid());
404 auto method = bus.new_method_call(LoggingCreate::default_service,
405 LoggingCreate::instance_path,
406 LoggingCreate::interface, "Create");
407 method.append(errorName, severity, additionalData);
408
409 bus.call(method);
410 }
411 catch (const sdbusplus::exception::SdBusError& e)
412 {
413 lg2::error(
414 "Failed creating event log for fault {ERROR_NAME}, error {ERR}",
415 "ERROR_NAME", errorName, "ERR", e);
416 }
417 disableEventLogging();
418}
419
420std::map<std::string, std::string> Updater::getI2CAdditionalData()
421{
422 std::map<std::string, std::string> additionalData;
423 auto [id, addr] = utils::parseDeviceName(getDevName());
424 std::string hexIdString = std::format("0x{:x}", id);
425 std::string hexAddrString = std::format("0x{:x}", addr);
426
427 additionalData["CALLOUT_IIC_BUS"] = hexIdString;
428 additionalData["CALLOUT_IIC_ADDR"] = hexAddrString;
429 return additionalData;
430}
431
432/*
433 * callOutI2CEventLog calls out FRUs in the following order:
434 * 1 - PSU high priority
435 * 2 - CALLOUT_IIC_BUS
436 */
437void Updater::callOutI2CEventLog(
438 std::map<std::string, std::string> extraAdditionalData,
439 const std::string& exceptionString, const int errorCode)
440{
441 std::map<std::string, std::string> additionalData = {
442 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
443 additionalData.merge(extraAdditionalData);
444 additionalData.merge(getI2CAdditionalData());
445 additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode);
446 if (!exceptionString.empty())
447 {
448 additionalData["I2C_EXCEPTION"] = exceptionString;
449 }
450 createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY,
451 additionalData);
452}
453
454/*
455 * callOutPsuEventLog calls out PSU and system planar
456 */
457void Updater::callOutPsuEventLog(
458 std::map<std::string, std::string> extraAdditionalData)
459{
460 std::map<std::string, std::string> additionalData = {
461 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
462 additionalData.merge(extraAdditionalData);
463 createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG,
464 updater::ERROR_SEVERITY, additionalData);
465}
466
467/*
468 * callOutSWEventLog calls out the BMC0001 procedure.
469 */
470void Updater::callOutSWEventLog(
471 std::map<std::string, std::string> additionalData)
472{
473 createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG,
474 updater::ERROR_SEVERITY, additionalData);
475}
476
477/*
478 * callOutGoodEventLog calls out a successful firmware update.
479 */
480void Updater::callOutGoodEventLog()
481{
482 std::map<std::string, std::string> additionalData = {
483 {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()},
484 {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}};
485 createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG,
486 updater::INFORMATIONAL_SEVERITY, additionalData);
487}
Lei YUd19df252019-10-25 17:31:52 +0800488} // namespace updater