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 "version.hpp"
25
26 #include <phosphor-logging/lg2.hpp>
27 #include <xyz/openbmc_project/Logging/Create/client.hpp>
28
29 #include <chrono>
30 #include <fstream>
31 #include <iostream>
32 #include <map>
33 #include <string>
34
35 using namespace phosphor::logging;
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
Updater(const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)213 Updater::Updater(const std::string& psuInventoryPath,
214 const std::string& devPath, const std::string& imageDir) :
215 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
216 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
217 {
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 {
226 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
227 "PATH", devPath, "ERR", e);
228 }
229 }
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.
bindUnbind(bool doBind)236 void 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;
247 if (doBind)
248 {
249 internal::delay(500);
250 }
251 out.close();
252
253 if (doBind)
254 {
255 // Set to present after bind the driver
256 setPresent(doBind);
257 }
258 }
259
setPresent(bool present)260 void 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 {
270 lg2::error(
271 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
272 "PATH", psuInventoryPath, "PRESENT", present);
273 }
274 }
275
isReadyToUpdate()276 bool Updater::isReadyToUpdate()
277 {
278 using namespace phosphor::pmbus;
279
280 // Pre-condition for updating PSU:
281 // * Host is powered off
282 // * 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))
287 {
288 lg2::warning("Unable to update PSU when host is on");
289 return false;
290 }
291
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 {
312 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
313 }
314 if (!present)
315 {
316 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
317 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 {
325 auto path = utils::getDevicePath(bus, p);
326 PMBus pmbus(path);
327 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 {
339 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);
343 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.
350 lg2::error("{EX}", "EX", ex.what());
351 return false;
352 }
353 }
354 return hasOtherPresent;
355 }
356
doUpdate()357 int Updater::doUpdate()
358 {
359 using namespace std::chrono;
360
361 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));
376
377 i2c->read(0xf1, data);
378 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
379 return 0;
380 }
381
createI2CDevice()382 void Updater::createI2CDevice()
383 {
384 auto [id, addr] = utils::parseDeviceName(devName);
385 i2c = i2c::create(id, addr);
386 }
387
createServiceableEventLog(const std::string & errorName,const std::string & severity,std::map<std::string,std::string> & additionalData)388 void 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
getI2CAdditionalData()420 std::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 */
callOutI2CEventLog(std::map<std::string,std::string> extraAdditionalData,const std::string & exceptionString,const int errorCode)437 void 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 */
callOutPsuEventLog(std::map<std::string,std::string> extraAdditionalData)457 void 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 */
callOutSWEventLog(std::map<std::string,std::string> additionalData)470 void 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 */
callOutGoodEventLog()480 void 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 }
488 } // namespace updater
489