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