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/lg2.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 lg2::error("Firmware file not found: {FILE}", "FILE", fileName);
122 return false;
123 }
124
125 // Check the file size
126 auto fileSize = std::filesystem::file_size(fileName);
127 if (fileSize == 0)
128 {
129 lg2::error("Firmware {FILE} is empty", "FILE", fileName);
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 lg2::error("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 lg2::error("Failed to open firmware file: {FILE}", "FILE", fileName);
148 return nullptr;
149 }
150 return inputFile;
151 }
152
153 // Read firmware bytes from input stream.
readFirmwareBytes(std::ifstream & inputFile,const size_t numberOfBytesToRead)154 std::vector<uint8_t> readFirmwareBytes(std::ifstream& inputFile,
155 const size_t numberOfBytesToRead)
156 {
157 std::vector<uint8_t> readDataBytes(numberOfBytesToRead, 0xFF);
158 try
159 {
160 // Enable exceptions for failbit and badbit
161 inputFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
162 inputFile.read(reinterpret_cast<char*>(readDataBytes.data()),
163 numberOfBytesToRead);
164 size_t bytesRead = inputFile.gcount();
165 if (bytesRead != numberOfBytesToRead)
166 {
167 readDataBytes.resize(bytesRead);
168 }
169 }
170 catch (const std::ios_base::failure& e)
171 {
172 lg2::error("Error reading firmware: {ERROR}", "ERROR", e.what());
173 readDataBytes.clear();
174 }
175 return readDataBytes;
176 }
177
178 } // namespace internal
179
update(sdbusplus::bus_t & bus,const std::string & psuInventoryPath,const std::string & imageDir)180 bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
181 const std::string& imageDir)
182 {
183 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
184
185 if (devPath.empty())
186 {
187 return false;
188 }
189
190 std::filesystem::path fsPath(imageDir);
191
192 std::unique_ptr<updater::Updater> updaterPtr = internal::getClassInstance(
193 fsPath.filename().string(), psuInventoryPath, devPath, imageDir);
194
195 if (!updaterPtr->isReadyToUpdate())
196 {
197 lg2::error("PSU not ready to update PSU = {PATH}", "PATH",
198 psuInventoryPath);
199 return false;
200 }
201
202 updaterPtr->bindUnbind(false);
203 updaterPtr->createI2CDevice();
204 int ret = updaterPtr->doUpdate();
205 updaterPtr->bindUnbind(true);
206 return ret == 0;
207 }
208
Updater(const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)209 Updater::Updater(const std::string& psuInventoryPath,
210 const std::string& devPath, const std::string& imageDir) :
211 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
212 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
213 {
214 fs::path p = fs::path(devPath) / "driver";
215 try
216 {
217 driverPath =
218 fs::canonical(p); // Get the path that points to the driver dir
219 }
220 catch (const fs::filesystem_error& e)
221 {
222 lg2::error("Failed to get canonical path DEVPATH= {PATH}, ERROR= {ERR}",
223 "PATH", devPath, "ERR", e.what());
224 }
225 }
226
227 // During PSU update, it needs to access the PSU i2c device directly, so it
228 // needs to unbind the driver during the update, and re-bind after it's done.
229 // After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
230 // errors. So set the PSU inventory's Present property to false so that
231 // psu-monitor will not report any errors.
bindUnbind(bool doBind)232 void Updater::bindUnbind(bool doBind)
233 {
234 if (!doBind)
235 {
236 // Set non-present before unbind the driver
237 setPresent(doBind);
238 }
239 auto p = driverPath;
240 p /= doBind ? "bind" : "unbind";
241 std::ofstream out(p.string());
242 out << devName;
243 if (doBind)
244 {
245 internal::delay(500);
246 }
247 out.close();
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 lg2::error(
267 "Failed to set present property PSU= {PATH}, PRESENT= {PRESENT}",
268 "PATH", psuInventoryPath, "PRESENT", 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 lg2::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 lg2::error("Failed to get present property PSU={PSU}", "PSU", p);
309 }
310 if (!present)
311 {
312 lg2::warning("PSU not present PSU={PSU}", "PSU", p);
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 lg2::warning(
336 "Unable to update PSU when other PSU has input/ouput fault PSU={PSU}, STATUS_WORD={STATUS}, VOUT_BYTE={VOUT}",
337 "PSU", p, "STATUS", lg2::hex, statusWord, "VOUT", lg2::hex,
338 voutStatus);
339 return false;
340 }
341 }
342 catch (const std::exception& ex)
343 {
344 // If error occurs on accessing the debugfs, it means something went
345 // wrong, e.g. PSU is not present, and it's not ready to update.
346 lg2::error("{EX}", "EX", ex.what());
347 return false;
348 }
349 }
350 return hasOtherPresent;
351 }
352
doUpdate()353 int Updater::doUpdate()
354 {
355 using namespace std::chrono;
356
357 uint8_t data;
358 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
359 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
360 uint8_t bootFlag = 0x01;
361 static_assert(sizeof(unlockData) == 12);
362
363 i2c->write(0xf0, sizeof(unlockData), unlockData);
364 printf("Unlock PSU\n");
365
366 std::this_thread::sleep_for(milliseconds(5));
367
368 i2c->write(0xf1, bootFlag);
369 printf("Set boot flag ret\n");
370
371 std::this_thread::sleep_for(seconds(3));
372
373 i2c->read(0xf1, data);
374 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
375 return 0;
376 }
377
createI2CDevice()378 void Updater::createI2CDevice()
379 {
380 auto [id, addr] = utils::parseDeviceName(devName);
381 i2c = i2c::create(id, addr);
382 }
383 } // namespace updater
384