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