1 /**
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 */
16
17 #include "updater.hpp"
18
19 #include "aei_updater.hpp"
20 #include "pmbus.hpp"
21 #include "types.hpp"
22 #include "utility.hpp"
23 #include "utils.hpp"
24 #include "validator.hpp"
25 #include "version.hpp"
26
27 #include <phosphor-logging/lg2.hpp>
28 #include <xyz/openbmc_project/Logging/Create/client.hpp>
29
30 #include <chrono>
31 #include <fstream>
32 #include <iostream>
33 #include <map>
34 #include <string>
35
36 using namespace phosphor::logging;
37 namespace util = phosphor::power::util;
38
39 namespace updater
40 {
41
42 namespace internal
43 {
44
45 // Define the CRC-8 polynomial (CRC-8-CCITT)
46 constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
47 constexpr uint8_t CRC8_INITIAL = 0x00;
48
49 // Get the appropriate Updater class instance based PSU model number
getClassInstance(const std::string & model,const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)50 std::unique_ptr<updater::Updater> getClassInstance(
51 const std::string& model, const std::string& psuInventoryPath,
52 const std::string& devPath, const std::string& imageDir)
53 {
54 if (model == "51E9" || model == "51DA")
55 {
56 return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
57 devPath, imageDir);
58 }
59 return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
60 }
61
62 // Function to locate FW file with model and extension bin or hex
getFWFilenamePath(const std::string & directory)63 const 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
74 if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
75 {
76 return directory + "/" + filename;
77 }
78 }
79 }
80 return "";
81 }
82
83 // Compute CRC-8 checksum for a vector of bytes
calculateCRC8(const std::vector<uint8_t> & data)84 uint8_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
delay(const int & milliseconds)103 void 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.
bigEndianToLittleEndian(const uint32_t bigEndianValue)109 std::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.
validateFWFile(const std::string & fileName)121 bool validateFWFile(const std::string& fileName)
122 {
123 // Ensure the file exists and get the file size.
124 if (!std::filesystem::exists(fileName))
125 {
126 lg2::error("Firmware file not found: {FILE}", "FILE", fileName);
127 return false;
128 }
129
130 // Check the file size
131 auto fileSize = std::filesystem::file_size(fileName);
132 if (fileSize == 0)
133 {
134 lg2::error("Firmware {FILE} is empty", "FILE", fileName);
135 return false;
136 }
137 return true;
138 }
139
140 // Open a firmware file for reading in binary mode.
openFirmwareFile(const std::string & fileName)141 std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
142 {
143 if (fileName.empty())
144 {
145 lg2::error("Firmware file path is not provided");
146 return nullptr;
147 }
148 auto inputFile =
149 std::make_unique<std::ifstream>(fileName, std::ios::binary);
150 if (!inputFile->is_open())
151 {
152 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName);
153 return nullptr;
154 }
155 return inputFile;
156 }
157
158 // Read firmware bytes from input stream.
readFirmwareBytes(std::ifstream & inputFile,const size_t numberOfBytesToRead)159 std::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 {
177 lg2::error("Error reading firmware: {ERROR}", "ERROR", e);
178 readDataBytes.clear();
179 }
180 return readDataBytes;
181 }
182
183 } // namespace internal
184
update(sdbusplus::bus_t & bus,const std::string & psuInventoryPath,const std::string & imageDir)185 bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
186 const std::string& imageDir)
187 {
188 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
189
190 if (devPath.empty())
191 {
192 return false;
193 }
194
195 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())
201 {
202 lg2::error("PSU not ready to update PSU = {PATH}", "PATH",
203 psuInventoryPath);
204 return false;
205 }
206
207 updaterPtr->bindUnbind(false);
208 updaterPtr->createI2CDevice();
209 int ret = updaterPtr->doUpdate();
210 updaterPtr->bindUnbind(true);
211 return ret == 0;
212 }
213
validateAndUpdate(sdbusplus::bus_t & bus,const std::string & psuInventoryPath,const std::string & imageDir)214 bool 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
Updater(const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)230 Updater::Updater(const std::string& psuInventoryPath,
231 const std::string& devPath, const std::string& imageDir) :
232 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
233 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
234 {
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 {
243 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
244 "PATH", devPath, "ERR", e);
245 }
246 }
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.
bindUnbind(bool doBind)253 void 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;
264 if (doBind)
265 {
266 internal::delay(500);
267 }
268 out.close();
269
270 if (doBind)
271 {
272 // Set to present after bind the driver
273 setPresent(doBind);
274 }
275 }
276
setPresent(bool present)277 void 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 {
287 lg2::error(
288 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
289 "PATH", psuInventoryPath, "PRESENT", present);
290 }
291 }
292
isReadyToUpdate()293 bool Updater::isReadyToUpdate()
294 {
295 using namespace phosphor::pmbus;
296
297 // Pre-condition for updating PSU:
298 // * Host is powered off
299 // * 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))
304 {
305 lg2::warning("Unable to update PSU when host is on");
306 return false;
307 }
308
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 {
329 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
330 }
331 if (!present)
332 {
333 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
334 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 {
342 auto path = utils::getDevicePath(bus, p);
343 PMBus pmbus(path);
344 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 {
356 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);
360 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.
367 lg2::error("{EX}", "EX", ex.what());
368 return false;
369 }
370 }
371 return hasOtherPresent;
372 }
373
doUpdate()374 int Updater::doUpdate()
375 {
376 using namespace std::chrono;
377
378 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));
393
394 i2c->read(0xf1, data);
395 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
396 return 0;
397 }
398
createI2CDevice()399 void Updater::createI2CDevice()
400 {
401 auto [id, addr] = utils::parseDeviceName(devName);
402 i2c = i2c::create(id, addr);
403 }
404
createServiceableEventLog(const std::string & errorName,const std::string & severity,std::map<std::string,std::string> & additionalData)405 void 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
getI2CAdditionalData()437 std::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 */
callOutI2CEventLog(std::map<std::string,std::string> extraAdditionalData,const std::string & exceptionString,const int errorCode)454 void 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 */
callOutPsuEventLog(std::map<std::string,std::string> extraAdditionalData)474 void 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 */
callOutSWEventLog(std::map<std::string,std::string> additionalData)487 void 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 */
callOutGoodEventLog()497 void 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 }
505 } // namespace updater
506