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