blob: 2587ae90371c4eeda2321cd1da8be1aac893b3cb [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
Lei YU9ab6d752019-10-28 17:03:20 +080036namespace 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
Faisal Awadafe5b5c62025-03-22 10:50:01 -0500213bool validateAndUpdate(sdbusplus::bus_t& bus,
214 const std::string& psuInventoryPath,
215 const std::string& imageDir)
216{
217 auto poweredOn = phosphor::power::util::isPoweredOn(bus, true);
218 validator::PSUUpdateValidator psuValidator(bus, psuInventoryPath);
219 if (!poweredOn && psuValidator.validToUpdate())
220 {
221 return updater::update(bus, psuInventoryPath, imageDir);
222 }
223 else
224 {
225 return false;
226 }
227}
228
Lei YU9ab6d752019-10-28 17:03:20 +0800229Updater::Updater(const std::string& psuInventoryPath,
230 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400231 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600232 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800233{
234 fs::path p = fs::path(devPath) / "driver";
235 try
236 {
237 driverPath =
238 fs::canonical(p); // Get the path that points to the driver dir
239 }
240 catch (const fs::filesystem_error& e)
241 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600242 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
Faisal Awada57fb6642025-02-21 16:52:54 -0600243 "PATH", devPath, "ERR", e);
Lei YU9ab6d752019-10-28 17:03:20 +0800244 }
Lei YU9ab6d752019-10-28 17:03:20 +0800245}
246
247// During PSU update, it needs to access the PSU i2c device directly, so it
248// needs to unbind the driver during the update, and re-bind after it's done.
249// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
250// errors. So set the PSU inventory's Present property to false so that
251// psu-monitor will not report any errors.
252void Updater::bindUnbind(bool doBind)
253{
254 if (!doBind)
255 {
256 // Set non-present before unbind the driver
257 setPresent(doBind);
258 }
259 auto p = driverPath;
260 p /= doBind ? "bind" : "unbind";
261 std::ofstream out(p.string());
262 out << devName;
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600263 if (doBind)
264 {
265 internal::delay(500);
266 }
267 out.close();
Lei YU9ab6d752019-10-28 17:03:20 +0800268
269 if (doBind)
270 {
271 // Set to present after bind the driver
272 setPresent(doBind);
273 }
274}
275
276void Updater::setPresent(bool present)
277{
278 try
279 {
280 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
281 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
282 service, bus, present);
283 }
284 catch (const std::exception& e)
285 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600286 lg2::error(
287 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
288 "PATH", psuInventoryPath, "PRESENT", present);
Lei YU9ab6d752019-10-28 17:03:20 +0800289 }
290}
291
Lei YU575ed132019-10-29 17:22:16 +0800292bool Updater::isReadyToUpdate()
293{
Lei YUe8c9cd62019-11-04 14:24:41 +0800294 using namespace phosphor::pmbus;
295
Lei YU575ed132019-10-29 17:22:16 +0800296 // Pre-condition for updating PSU:
297 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800298 // * At least one other PSU is present
299 // * All other PSUs that are present are having AC input and DC standby
300 // output
301
302 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800303 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600304 lg2::warning("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800305 return false;
306 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800307
308 bool hasOtherPresent = false;
309 auto paths = util::getPSUInventoryPaths(bus);
310 for (const auto& p : paths)
311 {
312 if (p == psuInventoryPath)
313 {
314 // Skip check for itself
315 continue;
316 }
317
318 // Check PSU present
319 bool present = false;
320 try
321 {
322 auto service = util::getService(p, INVENTORY_IFACE, bus);
323 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
324 service, bus, present);
325 }
326 catch (const std::exception& e)
327 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600328 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800329 }
330 if (!present)
331 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600332 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
Lei YUe8c9cd62019-11-04 14:24:41 +0800333 continue;
334 }
335 hasOtherPresent = true;
336
337 // Typically the driver is still bound here, so it is possible to
338 // directly read the debugfs to get the status.
339 try
340 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600341 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800342 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800343 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
344 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
345 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
346 if ((statusWord & status_word::VOUT_FAULT) ||
347 (statusWord & status_word::INPUT_FAULT_WARN) ||
348 (statusWord & status_word::VIN_UV_FAULT) ||
349 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
350 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
351 // UV_FAULT in vout status.
352 (voutStatus & status_vout::UV_FAULT) ||
353 (voutStatus & status_vout::OV_FAULT))
354 {
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600355 lg2::warning(
356 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}",
357 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex,
358 voutStatus);
Lei YUe8c9cd62019-11-04 14:24:41 +0800359 return false;
360 }
361 }
362 catch (const std::exception& ex)
363 {
364 // If error occurs on accessing the debugfs, it means something went
365 // wrong, e.g. PSU is not present, and it's not ready to update.
Faisal Awadaf9b426b2025-01-31 11:44:48 -0600366 lg2::error("{EX}", "EX", ex.what());
Lei YUe8c9cd62019-11-04 14:24:41 +0800367 return false;
368 }
369 }
370 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800371}
372
Lei YU9ab6d752019-10-28 17:03:20 +0800373int Updater::doUpdate()
374{
Lei YU34fb8bd2019-11-07 14:24:20 +0800375 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800376
Lei YU34fb8bd2019-11-07 14:24:20 +0800377 uint8_t data;
378 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
379 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
380 uint8_t bootFlag = 0x01;
381 static_assert(sizeof(unlockData) == 12);
382
383 i2c->write(0xf0, sizeof(unlockData), unlockData);
384 printf("Unlock PSU\n");
385
386 std::this_thread::sleep_for(milliseconds(5));
387
388 i2c->write(0xf1, bootFlag);
389 printf("Set boot flag ret\n");
390
391 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800392
393 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800394 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800395 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800396}
397
Lei YU7c2fbbb2019-11-06 14:56:02 +0800398void Updater::createI2CDevice()
399{
Shawn McCarney23dee382024-11-11 18:41:49 -0600400 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800401 i2c = i2c::create(id, addr);
402}
Faisal Awada57fb6642025-02-21 16:52:54 -0600403
404void Updater::createServiceableEventLog(
405 const std::string& errorName, const std::string& severity,
406 std::map<std::string, std::string>& additionalData)
407{
408 if (!isEventLogEnabled() || isEventLoggedThisSession())
409 {
410 return;
411 }
412
413 using namespace sdbusplus::xyz::openbmc_project;
414 using LoggingCreate =
415 sdbusplus::client::xyz::openbmc_project::logging::Create<>;
416 enableEventLoggedThisSession();
417 try
418 {
419 additionalData["_PID"] = std::to_string(getpid());
420 auto method = bus.new_method_call(LoggingCreate::default_service,
421 LoggingCreate::instance_path,
422 LoggingCreate::interface, "Create");
423 method.append(errorName, severity, additionalData);
424
425 bus.call(method);
426 }
427 catch (const sdbusplus::exception::SdBusError& e)
428 {
429 lg2::error(
430 "Failed creating event log for fault {ERROR_NAME}, error {ERR}",
431 "ERROR_NAME", errorName, "ERR", e);
432 }
433 disableEventLogging();
434}
435
436std::map<std::string, std::string> Updater::getI2CAdditionalData()
437{
438 std::map<std::string, std::string> additionalData;
439 auto [id, addr] = utils::parseDeviceName(getDevName());
440 std::string hexIdString = std::format("0x{:x}", id);
441 std::string hexAddrString = std::format("0x{:x}", addr);
442
443 additionalData["CALLOUT_IIC_BUS"] = hexIdString;
444 additionalData["CALLOUT_IIC_ADDR"] = hexAddrString;
445 return additionalData;
446}
447
448/*
449 * callOutI2CEventLog calls out FRUs in the following order:
450 * 1 - PSU high priority
451 * 2 - CALLOUT_IIC_BUS
452 */
453void Updater::callOutI2CEventLog(
454 std::map<std::string, std::string> extraAdditionalData,
455 const std::string& exceptionString, const int errorCode)
456{
457 std::map<std::string, std::string> additionalData = {
458 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
459 additionalData.merge(extraAdditionalData);
460 additionalData.merge(getI2CAdditionalData());
461 additionalData["CALLOUT_ERRNO"] = std::to_string(errorCode);
462 if (!exceptionString.empty())
463 {
464 additionalData["I2C_EXCEPTION"] = exceptionString;
465 }
466 createServiceableEventLog(FW_UPDATE_FAILED_MSG, ERROR_SEVERITY,
467 additionalData);
468}
469
470/*
471 * callOutPsuEventLog calls out PSU and system planar
472 */
473void Updater::callOutPsuEventLog(
474 std::map<std::string, std::string> extraAdditionalData)
475{
476 std::map<std::string, std::string> additionalData = {
477 {"CALLOUT_INVENTORY_PATH", getPsuInventoryPath()}};
478 additionalData.merge(extraAdditionalData);
479 createServiceableEventLog(updater::FW_UPDATE_FAILED_MSG,
480 updater::ERROR_SEVERITY, additionalData);
481}
482
483/*
484 * callOutSWEventLog calls out the BMC0001 procedure.
485 */
486void Updater::callOutSWEventLog(
487 std::map<std::string, std::string> additionalData)
488{
489 createServiceableEventLog(updater::PSU_FW_FILE_ISSUE_MSG,
490 updater::ERROR_SEVERITY, additionalData);
491}
492
493/*
494 * callOutGoodEventLog calls out a successful firmware update.
495 */
496void Updater::callOutGoodEventLog()
497{
498 std::map<std::string, std::string> additionalData = {
499 {"SUCCESSFUL_PSU_UPDATE", getPsuInventoryPath()},
500 {"FIRMWARE_VERSION", version::getVersion(bus, getPsuInventoryPath())}};
501 createServiceableEventLog(updater::FW_UPDATE_SUCCESS_MSG,
502 updater::INFORMATIONAL_SEVERITY, additionalData);
503}
Lei YUd19df252019-10-25 17:31:52 +0800504} // namespace updater