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