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