blob: c48decb03744f548c22855d7aa1f8e95a69becb0 [file] [log] [blame]
Lei YUd19df252019-10-25 17:31:52 +08001/**
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 */
Lei YUd19df252019-10-25 17:31:52 +080016#include "updater.hpp"
17
Lei YUe8c9cd62019-11-04 14:24:41 +080018#include "pmbus.hpp"
Lei YUcfc040c2019-10-29 17:10:26 +080019#include "types.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080020#include "utility.hpp"
Shawn McCarney14572cf2024-11-06 12:17:57 -060021#include "utils.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080022
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060023#include <phosphor-logging/log.hpp>
24
Lei YU34fb8bd2019-11-07 14:24:20 +080025#include <chrono>
Lei YU9ab6d752019-10-28 17:03:20 +080026#include <fstream>
Lei YU34fb8bd2019-11-07 14:24:20 +080027#include <thread>
Faisal Awadaec61bbd2024-11-04 08:46:20 -060028#include <vector>
Lei YU9ab6d752019-10-28 17:03:20 +080029
30using namespace phosphor::logging;
31namespace util = phosphor::power::util;
32
Lei YUd19df252019-10-25 17:31:52 +080033namespace updater
34{
35
Lei YU9ab6d752019-10-28 17:03:20 +080036namespace internal
37{
38
Faisal Awadaec61bbd2024-11-04 08:46:20 -060039// Define the CRC-8 polynomial (CRC-8-CCITT)
40constexpr uint8_t CRC8_POLYNOMIAL = 0x07;
41constexpr uint8_t CRC8_INITIAL = 0x00;
42
Faisal Awadaec61bbd2024-11-04 08:46:20 -060043// Get the appropriate Updater class instance based PSU model number
44std::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
56const 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
78uint8_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
97void 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.
103std::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.
115bool 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.
136std::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.
155std::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
Lei YU9ab6d752019-10-28 17:03:20 +0800180} // namespace internal
181
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600182bool update(sdbusplus::bus_t& bus, const std::string& psuInventoryPath,
183 const std::string& imageDir)
Lei YUd19df252019-10-25 17:31:52 +0800184{
Shawn McCarney23dee382024-11-11 18:41:49 -0600185 auto devPath = utils::getDevicePath(bus, psuInventoryPath);
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600186
Lei YU9ab6d752019-10-28 17:03:20 +0800187 if (devPath.empty())
188 {
189 return false;
190 }
191
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600192 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())
Lei YU575ed132019-10-29 17:22:16 +0800198 {
199 log<level::ERR>("PSU not ready to update",
200 entry("PSU=%s", psuInventoryPath.c_str()));
201 return false;
202 }
203
Faisal Awadaec61bbd2024-11-04 08:46:20 -0600204 updaterPtr->bindUnbind(false);
205 updaterPtr->createI2CDevice();
206 int ret = updaterPtr->doUpdate();
207 updaterPtr->bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +0800208 return ret == 0;
209}
210
211Updater::Updater(const std::string& psuInventoryPath,
212 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400213 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
Shawn McCarney23dee382024-11-11 18:41:49 -0600214 devPath(devPath), devName(utils::getDeviceName(devPath)), imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800215{
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 }
Lei YU9ab6d752019-10-28 17:03:20 +0800229}
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.
236void 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
255void 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
Lei YU575ed132019-10-29 17:22:16 +0800271bool Updater::isReadyToUpdate()
272{
Lei YUe8c9cd62019-11-04 14:24:41 +0800273 using namespace phosphor::pmbus;
274
Lei YU575ed132019-10-29 17:22:16 +0800275 // Pre-condition for updating PSU:
276 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800277 // * 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))
Lei YU575ed132019-10-29 17:22:16 +0800282 {
Lei YUe8c9cd62019-11-04 14:24:41 +0800283 log<level::WARNING>("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800284 return false;
285 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800286
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 {
Shawn McCarney23dee382024-11-11 18:41:49 -0600321 auto path = utils::getDevicePath(bus, p);
George Liub9cf0d22023-02-28 10:32:42 +0800322 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800323 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;
Lei YU575ed132019-10-29 17:22:16 +0800352}
353
Lei YU9ab6d752019-10-28 17:03:20 +0800354int Updater::doUpdate()
355{
Lei YU34fb8bd2019-11-07 14:24:20 +0800356 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800357
Lei YU34fb8bd2019-11-07 14:24:20 +0800358 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));
Lei YU92e89eb2019-11-06 18:08:25 +0800373
374 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800375 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800376 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800377}
378
Lei YU7c2fbbb2019-11-06 14:56:02 +0800379void Updater::createI2CDevice()
380{
Shawn McCarney23dee382024-11-11 18:41:49 -0600381 auto [id, addr] = utils::parseDeviceName(devName);
Lei YU7c2fbbb2019-11-06 14:56:02 +0800382 i2c = i2c::create(id, addr);
383}
Lei YUd19df252019-10-25 17:31:52 +0800384} // namespace updater