xref: /openbmc/phosphor-power/tools/power-utils/updater.cpp (revision f8e8bc19fa720b6b348a3dddefd8eca7650b3e26)
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