blob: 5c4f7f5c6c96ae112607e662022d5386457a2e2e [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 */
16#include "config.h"
17
18#include "updater.hpp"
19
Lei YUe8c9cd62019-11-04 14:24:41 +080020#include "pmbus.hpp"
Lei YUcfc040c2019-10-29 17:10:26 +080021#include "types.hpp"
Lei YU9ab6d752019-10-28 17:03:20 +080022#include "utility.hpp"
23
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060024#include <phosphor-logging/log.hpp>
25
Lei YU34fb8bd2019-11-07 14:24:20 +080026#include <chrono>
Lei YU9ab6d752019-10-28 17:03:20 +080027#include <fstream>
Lei YU34fb8bd2019-11-07 14:24:20 +080028#include <thread>
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
39/* Get the device name from the device path */
40std::string getDeviceName(std::string devPath)
41{
42 if (devPath.back() == '/')
43 {
44 devPath.pop_back();
45 }
46 return fs::path(devPath).stem().string();
47}
48
49std::string getDevicePath(const std::string& psuInventoryPath)
50{
51 auto data = util::loadJSONFromFile(PSU_JSON_PATH);
52
53 if (data == nullptr)
54 {
55 return {};
56 }
57
58 auto devicePath = data["psuDevices"][psuInventoryPath];
59 if (devicePath.empty())
60 {
61 log<level::WARNING>("Unable to find psu devices or path");
62 }
63 return devicePath;
64}
65
Lei YU7c2fbbb2019-11-06 14:56:02 +080066std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName)
67{
68 // Get I2C bus id and device address, e.g. 3-0068
69 // is parsed to bus id 3, device address 0x68
70 auto pos = devName.find('-');
71 assert(pos != std::string::npos);
72 uint8_t busId = std::stoi(devName.substr(0, pos));
73 uint8_t devAddr = std::stoi(devName.substr(pos + 1), nullptr, 16);
74 return {busId, devAddr};
75}
76
Lei YU9ab6d752019-10-28 17:03:20 +080077} // namespace internal
78
Lei YUd19df252019-10-25 17:31:52 +080079bool update(const std::string& psuInventoryPath, const std::string& imageDir)
80{
Lei YU9ab6d752019-10-28 17:03:20 +080081 auto devPath = internal::getDevicePath(psuInventoryPath);
82 if (devPath.empty())
83 {
84 return false;
85 }
86
Lei YU9ab6d752019-10-28 17:03:20 +080087 Updater updater(psuInventoryPath, devPath, imageDir);
Lei YU575ed132019-10-29 17:22:16 +080088 if (!updater.isReadyToUpdate())
89 {
90 log<level::ERR>("PSU not ready to update",
91 entry("PSU=%s", psuInventoryPath.c_str()));
92 return false;
93 }
94
95 updater.bindUnbind(false);
Lei YU7c2fbbb2019-11-06 14:56:02 +080096 updater.createI2CDevice();
Lei YU9ab6d752019-10-28 17:03:20 +080097 int ret = updater.doUpdate();
Lei YU575ed132019-10-29 17:22:16 +080098 updater.bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +080099 return ret == 0;
100}
101
102Updater::Updater(const std::string& psuInventoryPath,
103 const std::string& devPath, const std::string& imageDir) :
Patrick Williamsf5402192024-08-16 15:20:53 -0400104 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
105 devPath(devPath), devName(internal::getDeviceName(devPath)),
106 imageDir(imageDir)
Lei YU9ab6d752019-10-28 17:03:20 +0800107{
108 fs::path p = fs::path(devPath) / "driver";
109 try
110 {
111 driverPath =
112 fs::canonical(p); // Get the path that points to the driver dir
113 }
114 catch (const fs::filesystem_error& e)
115 {
116 log<level::ERR>("Failed to get canonical path",
117 entry("DEVPATH=%s", devPath.c_str()),
118 entry("ERROR=%s", e.what()));
119 throw;
120 }
Lei YU9ab6d752019-10-28 17:03:20 +0800121}
122
123// During PSU update, it needs to access the PSU i2c device directly, so it
124// needs to unbind the driver during the update, and re-bind after it's done.
125// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
126// errors. So set the PSU inventory's Present property to false so that
127// psu-monitor will not report any errors.
128void Updater::bindUnbind(bool doBind)
129{
130 if (!doBind)
131 {
132 // Set non-present before unbind the driver
133 setPresent(doBind);
134 }
135 auto p = driverPath;
136 p /= doBind ? "bind" : "unbind";
137 std::ofstream out(p.string());
138 out << devName;
139
140 if (doBind)
141 {
142 // Set to present after bind the driver
143 setPresent(doBind);
144 }
145}
146
147void Updater::setPresent(bool present)
148{
149 try
150 {
151 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
152 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
153 service, bus, present);
154 }
155 catch (const std::exception& e)
156 {
157 log<level::ERR>("Failed to set present property",
158 entry("PSU=%s", psuInventoryPath.c_str()),
159 entry("PRESENT=%d", present));
160 }
161}
162
Lei YU575ed132019-10-29 17:22:16 +0800163bool Updater::isReadyToUpdate()
164{
Lei YUe8c9cd62019-11-04 14:24:41 +0800165 using namespace phosphor::pmbus;
166
Lei YU575ed132019-10-29 17:22:16 +0800167 // Pre-condition for updating PSU:
168 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800169 // * At least one other PSU is present
170 // * All other PSUs that are present are having AC input and DC standby
171 // output
172
173 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800174 {
Lei YUe8c9cd62019-11-04 14:24:41 +0800175 log<level::WARNING>("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800176 return false;
177 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800178
179 bool hasOtherPresent = false;
180 auto paths = util::getPSUInventoryPaths(bus);
181 for (const auto& p : paths)
182 {
183 if (p == psuInventoryPath)
184 {
185 // Skip check for itself
186 continue;
187 }
188
189 // Check PSU present
190 bool present = false;
191 try
192 {
193 auto service = util::getService(p, INVENTORY_IFACE, bus);
194 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
195 service, bus, present);
196 }
197 catch (const std::exception& e)
198 {
199 log<level::ERR>("Failed to get present property",
200 entry("PSU=%s", p.c_str()));
201 }
202 if (!present)
203 {
204 log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
205 continue;
206 }
207 hasOtherPresent = true;
208
209 // Typically the driver is still bound here, so it is possible to
210 // directly read the debugfs to get the status.
211 try
212 {
George Liub9cf0d22023-02-28 10:32:42 +0800213 auto path = internal::getDevicePath(p);
214 PMBus pmbus(path);
Lei YUe8c9cd62019-11-04 14:24:41 +0800215 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
216 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
217 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
218 if ((statusWord & status_word::VOUT_FAULT) ||
219 (statusWord & status_word::INPUT_FAULT_WARN) ||
220 (statusWord & status_word::VIN_UV_FAULT) ||
221 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
222 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
223 // UV_FAULT in vout status.
224 (voutStatus & status_vout::UV_FAULT) ||
225 (voutStatus & status_vout::OV_FAULT))
226 {
227 log<level::WARNING>(
228 "Unable to update PSU when other PSU has input/ouput fault",
229 entry("PSU=%s", p.c_str()),
230 entry("STATUS_WORD=0x%04x", statusWord),
231 entry("VOUT_BYTE=0x%02x", voutStatus));
232 return false;
233 }
234 }
235 catch (const std::exception& ex)
236 {
237 // If error occurs on accessing the debugfs, it means something went
238 // wrong, e.g. PSU is not present, and it's not ready to update.
239 log<level::ERR>(ex.what());
240 return false;
241 }
242 }
243 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800244}
245
Lei YU9ab6d752019-10-28 17:03:20 +0800246int Updater::doUpdate()
247{
Lei YU34fb8bd2019-11-07 14:24:20 +0800248 using namespace std::chrono;
Lei YU92e89eb2019-11-06 18:08:25 +0800249
Lei YU34fb8bd2019-11-07 14:24:20 +0800250 uint8_t data;
251 uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
252 0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
253 uint8_t bootFlag = 0x01;
254 static_assert(sizeof(unlockData) == 12);
255
256 i2c->write(0xf0, sizeof(unlockData), unlockData);
257 printf("Unlock PSU\n");
258
259 std::this_thread::sleep_for(milliseconds(5));
260
261 i2c->write(0xf1, bootFlag);
262 printf("Set boot flag ret\n");
263
264 std::this_thread::sleep_for(seconds(3));
Lei YU92e89eb2019-11-06 18:08:25 +0800265
266 i2c->read(0xf1, data);
Lei YU34fb8bd2019-11-07 14:24:20 +0800267 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
Lei YU9ab6d752019-10-28 17:03:20 +0800268 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800269}
270
Lei YU7c2fbbb2019-11-06 14:56:02 +0800271void Updater::createI2CDevice()
272{
273 auto [id, addr] = internal::parseDeviceName(devName);
274 i2c = i2c::create(id, addr);
275}
276
Lei YUd19df252019-10-25 17:31:52 +0800277} // namespace updater