xref: /openbmc/phosphor-power/tools/power-utils/updater.cpp (revision 5ace9fb733e0757f105c3f6d00128fd9f5dc7df3)
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 #include "updater.hpp"
17 
18 #include "aei_updater.hpp"
19 #include "pmbus.hpp"
20 #include "types.hpp"
21 #include "utility.hpp"
22 #include "utils.hpp"
23 
24 #include <phosphor-logging/log.hpp>
25 
26 #include <chrono>
27 #include <fstream>
28 #include <thread>
29 #include <vector>
30 
31 using namespace phosphor::logging;
32 namespace util = phosphor::power::util;
33 
34 namespace updater
35 {
36 
37 namespace internal
38 {
39 
40 // Define the CRC-8 polynomial (CRC-8-CCITT)
41 constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
42 constexpr uint8_t CRC8_INITIAL = 0x00;
43 
44 // 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)45 std::unique_ptr<updater::Updater> getClassInstance(
46     const std::string& model, const std::string& psuInventoryPath,
47     const std::string& devPath, const std::string& imageDir)
48 {
49     if (model == "51E9" || model == "51DA")
50     {
51         return std::make_unique<aeiUpdater::AeiUpdater>(psuInventoryPath,
52                                                         devPath, imageDir);
53     }
54     return std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
55 }
56 
57 // Function to locate FW file with model and extension bin or hex
getFWFilenamePath(const std::string & directory)58 const std::string getFWFilenamePath(const std::string& directory)
59 {
60     namespace fs = std::filesystem;
61     // Get the last part of the directory name (model number)
62     std::string model = fs::path(directory).filename().string();
63     for (const auto& entry : fs::directory_iterator(directory))
64     {
65         if (entry.is_regular_file())
66         {
67             std::string filename = entry.path().filename().string();
68 
69             if ((filename.rfind(model, 0) == 0) && (filename.ends_with(".bin")))
70             {
71                 return directory + "/" + filename;
72             }
73         }
74     }
75     return "";
76 }
77 
78 // Compute CRC-8 checksum for a vector of bytes
calculateCRC8(const std::vector<uint8_t> & data)79 uint8_t calculateCRC8(const std::vector<uint8_t>& data)
80 {
81     uint8_t crc = CRC8_INITIAL;
82 
83     for (const auto& byte : data)
84     {
85         crc ^= byte;
86         for (int i = 0; i < 8; ++i)
87         {
88             if (crc & 0x80)
89                 crc = (crc << 1) ^ CRC8_POLYNOMIAL;
90             else
91                 crc <<= 1;
92         }
93     }
94     return crc;
95 }
96 
97 // Delay execution for a specified number of milliseconds
delay(const int & milliseconds)98 void delay(const int& milliseconds)
99 {
100     std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
101 }
102 
103 // Convert big endian (32 bit integer) to a vector of little endian.
bigEndianToLittleEndian(const uint32_t bigEndianValue)104 std::vector<uint8_t> bigEndianToLittleEndian(const uint32_t bigEndianValue)
105 {
106     std::vector<uint8_t> littleEndianBytes(4);
107 
108     littleEndianBytes[3] = (bigEndianValue >> 24) & 0xFF;
109     littleEndianBytes[2] = (bigEndianValue >> 16) & 0xFF;
110     littleEndianBytes[1] = (bigEndianValue >> 8) & 0xFF;
111     littleEndianBytes[0] = bigEndianValue & 0xFF;
112     return littleEndianBytes;
113 }
114 
115 // Validate the existence and size of a firmware file.
validateFWFile(const std::string & fileName)116 bool validateFWFile(const std::string& fileName)
117 {
118     // Ensure the file exists and get the file size.
119     if (!std::filesystem::exists(fileName))
120     {
121         log<level::ERR>(
122             std::format("Firmware file not found: {}", fileName).c_str());
123         return false;
124     }
125 
126     // Check the file size
127     auto fileSize = std::filesystem::file_size(fileName);
128     if (fileSize == 0)
129     {
130         log<level::ERR>("Firmware file is empty");
131         return false;
132     }
133     return true;
134 }
135 
136 // Open a firmware file for reading in binary mode.
openFirmwareFile(const std::string & fileName)137 std::unique_ptr<std::ifstream> openFirmwareFile(const std::string& fileName)
138 {
139     if (fileName.empty())
140     {
141         log<level::ERR>("Firmware file path is not provided");
142         return nullptr;
143     }
144     auto inputFile =
145         std::make_unique<std::ifstream>(fileName, std::ios::binary);
146     if (!inputFile->is_open())
147     {
148         log<level::ERR>(
149             std::format("Failed to open firmware file: {}", fileName).c_str());
150         return nullptr;
151     }
152     return inputFile;
153 }
154 
155 // Read firmware bytes from input stream.
readFirmwareBytes(std::ifstream & inputFile,const size_t numberOfBytesToRead)156 std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
157                                        const size_t numberOfBytesToRead)
158 {
159     std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
160     try
161     {
162         // Enable exceptions for failbit and badbit
163         inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
164         inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
165                        numberOfBytesToRead);
166         size_t bytesRead = inputFile.gcount();
167         if (bytesRead != numberOfBytesToRead)
168         {
169             readDataBytes.resize(bytesRead);
170         }
171     }
172     catch (const std::ios_base::failure& e)
173     {
174         log<level::ERR>(
175             std::format("Error reading firmware: {}", e.what()).c_str());
176         readDataBytes.clear();
177     }
178     return readDataBytes;
179 }
180 
181 } // namespace internal
182 
update(sdbusplus::bus_t & bus,const std::string & psuInventoryPath,const std::string & imageDir)183 bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
184             const std::string& imageDir)
185 {
186     auto devPath = utils::getDevicePath(bus, psuInventoryPath);
187 
188     if (devPath.empty())
189     {
190         return false;
191     }
192 
193     std::filesystem::path fsPath(imageDir);
194 
195     std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
196         fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
197 
198     if (!updaterPtr->isReadyToUpdate())
199     {
200         log<level::ERR>("PSU not ready to update",
201                         entry("PSU=%s", psuInventoryPath.c_str()));
202         return false;
203     }
204 
205     updaterPtr->bindUnbind(false);
206     updaterPtr->createI2CDevice();
207     int ret = updaterPtr->doUpdate();
208     updaterPtr->bindUnbind(true);
209     return ret == 0;
210 }
211 
Updater(const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)212 Updater::Updater(const std::string& psuInventoryPath,
213                  const std::string& devPath, const std::string& imageDir) :
214     bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
215     devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
216 {
217     fs::path p = fs::path(devPath) / "driver";
218     try
219     {
220         driverPath =
221             fs::canonical(p); // Get the path that points to the driver dir
222     }
223     catch (const fs::filesystem_error& e)
224     {
225         log<level::ERR>("Failed to get canonical path",
226                         entry("DEVPATH=%s", devPath.c_str()),
227                         entry("ERROR=%s", e.what()));
228         throw;
229     }
230 }
231 
232 // During PSU update, it needs to access the PSU i2c device directly, so it
233 // needs to unbind the driver during the update, and re-bind after it's done.
234 // After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
235 // errors. So set the PSU inventory's Present property to false so that
236 // psu-monitor will not report any errors.
bindUnbind(bool doBind)237 void Updater::bindUnbind(bool doBind)
238 {
239     if (!doBind)
240     {
241         // Set non-present before unbind the driver
242         setPresent(doBind);
243     }
244     auto p = driverPath;
245     p /= doBind ? "bind" : "unbind";
246     std::ofstream out(p.string());
247     out << devName;
248 
249     if (doBind)
250     {
251         // Set to present after bind the driver
252         setPresent(doBind);
253     }
254 }
255 
setPresent(bool present)256 void Updater::setPresent(bool present)
257 {
258     try
259     {
260         auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
261         util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
262                           service, bus, present);
263     }
264     catch (const std::exception& e)
265     {
266         log<level::ERR>("Failed to set present property",
267                         entry("PSU=%s", psuInventoryPath.c_str()),
268                         entry("PRESENT=%d", present));
269     }
270 }
271 
isReadyToUpdate()272 bool Updater::isReadyToUpdate()
273 {
274     using namespace phosphor::pmbus;
275 
276     // Pre-condition for updating PSU:
277     // * Host is powered off
278     // * At least one other PSU is present
279     // * All other PSUs that are present are having AC input and DC standby
280     //   output
281 
282     if (util::isPoweredOn(bus, true))
283     {
284         log<level::WARNING>("Unable to update PSU when host is on");
285         return false;
286     }
287 
288     bool hasOtherPresent = false;
289     auto paths = util::getPSUInventoryPaths(bus);
290     for (const auto& p : paths)
291     {
292         if (p == psuInventoryPath)
293         {
294             // Skip check for itself
295             continue;
296         }
297 
298         // Check PSU present
299         bool present = false;
300         try
301         {
302             auto service = util::getService(p, INVENTORY_IFACE, bus);
303             util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
304                               service, bus, present);
305         }
306         catch (const std::exception& e)
307         {
308             log<level::ERR>("Failed to get present property",
309                             entry("PSU=%s", p.c_str()));
310         }
311         if (!present)
312         {
313             log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
314             continue;
315         }
316         hasOtherPresent = true;
317 
318         // Typically the driver is still bound here, so it is possible to
319         // directly read the debugfs to get the status.
320         try
321         {
322             auto path = utils::getDevicePath(bus, p);
323             PMBus pmbus(path);
324             uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
325             auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
326             uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
327             if ((statusWord & status_word::VOUT_FAULT) ||
328                 (statusWord & status_word::INPUT_FAULT_WARN) ||
329                 (statusWord & status_word::VIN_UV_FAULT) ||
330                 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
331                 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
332                 // UV_FAULT in vout status.
333                 (voutStatus & status_vout::UV_FAULT) ||
334                 (voutStatus & status_vout::OV_FAULT))
335             {
336                 log<level::WARNING>(
337                     "Unable to update PSU when other PSU has input/ouput fault",
338                     entry("PSU=%s", p.c_str()),
339                     entry("STATUS_WORD=0x%04x", statusWord),
340                     entry("VOUT_BYTE=0x%02x", voutStatus));
341                 return false;
342             }
343         }
344         catch (const std::exception& ex)
345         {
346             // If error occurs on accessing the debugfs, it means something went
347             // wrong, e.g. PSU is not present, and it's not ready to update.
348             log<level::ERR>(ex.what());
349             return false;
350         }
351     }
352     return hasOtherPresent;
353 }
354 
doUpdate()355 int Updater::doUpdate()
356 {
357     using namespace std::chrono;
358 
359     uint8_t data;
360     uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
361                               0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
362     uint8_t bootFlag = 0x01;
363     static_assert(sizeof(unlockData) == 12);
364 
365     i2c->write(0xf0, sizeof(unlockData), unlockData);
366     printf("Unlock PSU\n");
367 
368     std::this_thread::sleep_for(milliseconds(5));
369 
370     i2c->write(0xf1, bootFlag);
371     printf("Set boot flag ret\n");
372 
373     std::this_thread::sleep_for(seconds(3));
374 
375     i2c->read(0xf1, data);
376     printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
377     return 0;
378 }
379 
createI2CDevice()380 void Updater::createI2CDevice()
381 {
382     auto [id, addr] = utils::parseDeviceName(devName);
383     i2c = i2c::create(id, addr);
384 }
385 } // namespace updater
386