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