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