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