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 "config.h"
17
18 #include "updater.hpp"
19
20 #include "pmbus.hpp"
21 #include "types.hpp"
22 #include "utility.hpp"
23
24 #include <phosphor-logging/log.hpp>
25
26 #include <chrono>
27 #include <fstream>
28 #include <thread>
29
30 using namespace phosphor::logging;
31 namespace util = phosphor::power::util;
32
33 namespace updater
34 {
35
36 namespace internal
37 {
38
39 /* Get the device name from the device path */
getDeviceName(std::string devPath)40 std::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
getDevicePath(const std::string & psuInventoryPath)49 std::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
parseDeviceName(const std::string & devName)66 std::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
77 } // namespace internal
78
update(const std::string & psuInventoryPath,const std::string & imageDir)79 bool update(const std::string& psuInventoryPath, const std::string& imageDir)
80 {
81 auto devPath = internal::getDevicePath(psuInventoryPath);
82 if (devPath.empty())
83 {
84 return false;
85 }
86
87 Updater updater(psuInventoryPath, devPath, imageDir);
88 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);
96 updater.createI2CDevice();
97 int ret = updater.doUpdate();
98 updater.bindUnbind(true);
99 return ret == 0;
100 }
101
Updater(const std::string & psuInventoryPath,const std::string & devPath,const std::string & imageDir)102 Updater::Updater(const std::string& psuInventoryPath,
103 const std::string& devPath, const std::string& imageDir) :
104 bus(sdbusplus::bus::new_default()), psuInventoryPath(psuInventoryPath),
105 devPath(devPath), devName(internal::getDeviceName(devPath)),
106 imageDir(imageDir)
107 {
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 }
121 }
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.
bindUnbind(bool doBind)128 void 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
setPresent(bool present)147 void 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
isReadyToUpdate()163 bool Updater::isReadyToUpdate()
164 {
165 using namespace phosphor::pmbus;
166
167 // Pre-condition for updating PSU:
168 // * Host is powered off
169 // * 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))
174 {
175 log<level::WARNING>("Unable to update PSU when host is on");
176 return false;
177 }
178
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 {
213 auto path = internal::getDevicePath(p);
214 PMBus pmbus(path);
215 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;
244 }
245
doUpdate()246 int Updater::doUpdate()
247 {
248 using namespace std::chrono;
249
250 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));
265
266 i2c->read(0xf1, data);
267 printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
268 return 0;
269 }
270
createI2CDevice()271 void Updater::createI2CDevice()
272 {
273 auto [id, addr] = internal::parseDeviceName(devName);
274 i2c = i2c::create(id, addr);
275 }
276
277 } // namespace updater
278