blob: 26680306ecee2dd3481a0fee4ff6016465f537b1 [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
24#include <fstream>
25#include <phosphor-logging/log.hpp>
26
27using namespace phosphor::logging;
28namespace util = phosphor::power::util;
29
Lei YUd19df252019-10-25 17:31:52 +080030namespace updater
31{
32
Lei YU9ab6d752019-10-28 17:03:20 +080033namespace internal
34{
35
36/* Get the device name from the device path */
37std::string getDeviceName(std::string devPath)
38{
39 if (devPath.back() == '/')
40 {
41 devPath.pop_back();
42 }
43 return fs::path(devPath).stem().string();
44}
45
46std::string getDevicePath(const std::string& psuInventoryPath)
47{
48 auto data = util::loadJSONFromFile(PSU_JSON_PATH);
49
50 if (data == nullptr)
51 {
52 return {};
53 }
54
55 auto devicePath = data["psuDevices"][psuInventoryPath];
56 if (devicePath.empty())
57 {
58 log<level::WARNING>("Unable to find psu devices or path");
59 }
60 return devicePath;
61}
62
Lei YU7c2fbbb2019-11-06 14:56:02 +080063std::pair<uint8_t, uint8_t> parseDeviceName(const std::string& devName)
64{
65 // Get I2C bus id and device address, e.g. 3-0068
66 // is parsed to bus id 3, device address 0x68
67 auto pos = devName.find('-');
68 assert(pos != std::string::npos);
69 uint8_t busId = std::stoi(devName.substr(0, pos));
70 uint8_t devAddr = std::stoi(devName.substr(pos + 1), nullptr, 16);
71 return {busId, devAddr};
72}
73
Lei YU9ab6d752019-10-28 17:03:20 +080074} // namespace internal
75
Lei YUd19df252019-10-25 17:31:52 +080076bool update(const std::string& psuInventoryPath, const std::string& imageDir)
77{
Lei YU9ab6d752019-10-28 17:03:20 +080078 auto devPath = internal::getDevicePath(psuInventoryPath);
79 if (devPath.empty())
80 {
81 return false;
82 }
83
Lei YU9ab6d752019-10-28 17:03:20 +080084 Updater updater(psuInventoryPath, devPath, imageDir);
Lei YU575ed132019-10-29 17:22:16 +080085 if (!updater.isReadyToUpdate())
86 {
87 log<level::ERR>("PSU not ready to update",
88 entry("PSU=%s", psuInventoryPath.c_str()));
89 return false;
90 }
91
92 updater.bindUnbind(false);
Lei YU7c2fbbb2019-11-06 14:56:02 +080093 updater.createI2CDevice();
Lei YU9ab6d752019-10-28 17:03:20 +080094 int ret = updater.doUpdate();
Lei YU575ed132019-10-29 17:22:16 +080095 updater.bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +080096 return ret == 0;
97}
98
99Updater::Updater(const std::string& psuInventoryPath,
100 const std::string& devPath, const std::string& imageDir) :
101 bus(sdbusplus::bus::new_default()),
102 psuInventoryPath(psuInventoryPath), devPath(devPath),
103 devName(internal::getDeviceName(devPath)), imageDir(imageDir)
104{
105 fs::path p = fs::path(devPath) / "driver";
106 try
107 {
108 driverPath =
109 fs::canonical(p); // Get the path that points to the driver dir
110 }
111 catch (const fs::filesystem_error& e)
112 {
113 log<level::ERR>("Failed to get canonical path",
114 entry("DEVPATH=%s", devPath.c_str()),
115 entry("ERROR=%s", e.what()));
116 throw;
117 }
Lei YU9ab6d752019-10-28 17:03:20 +0800118}
119
120// During PSU update, it needs to access the PSU i2c device directly, so it
121// needs to unbind the driver during the update, and re-bind after it's done.
122// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
123// errors. So set the PSU inventory's Present property to false so that
124// psu-monitor will not report any errors.
125void Updater::bindUnbind(bool doBind)
126{
127 if (!doBind)
128 {
129 // Set non-present before unbind the driver
130 setPresent(doBind);
131 }
132 auto p = driverPath;
133 p /= doBind ? "bind" : "unbind";
134 std::ofstream out(p.string());
135 out << devName;
136
137 if (doBind)
138 {
139 // Set to present after bind the driver
140 setPresent(doBind);
141 }
142}
143
144void Updater::setPresent(bool present)
145{
146 try
147 {
148 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
149 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
150 service, bus, present);
151 }
152 catch (const std::exception& e)
153 {
154 log<level::ERR>("Failed to set present property",
155 entry("PSU=%s", psuInventoryPath.c_str()),
156 entry("PRESENT=%d", present));
157 }
158}
159
Lei YU575ed132019-10-29 17:22:16 +0800160bool Updater::isReadyToUpdate()
161{
Lei YUe8c9cd62019-11-04 14:24:41 +0800162 using namespace phosphor::pmbus;
163
Lei YU575ed132019-10-29 17:22:16 +0800164 // Pre-condition for updating PSU:
165 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800166 // * At least one other PSU is present
167 // * All other PSUs that are present are having AC input and DC standby
168 // output
169
170 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800171 {
Lei YUe8c9cd62019-11-04 14:24:41 +0800172 log<level::WARNING>("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800173 return false;
174 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800175
176 bool hasOtherPresent = false;
177 auto paths = util::getPSUInventoryPaths(bus);
178 for (const auto& p : paths)
179 {
180 if (p == psuInventoryPath)
181 {
182 // Skip check for itself
183 continue;
184 }
185
186 // Check PSU present
187 bool present = false;
188 try
189 {
190 auto service = util::getService(p, INVENTORY_IFACE, bus);
191 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
192 service, bus, present);
193 }
194 catch (const std::exception& e)
195 {
196 log<level::ERR>("Failed to get present property",
197 entry("PSU=%s", p.c_str()));
198 }
199 if (!present)
200 {
201 log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
202 continue;
203 }
204 hasOtherPresent = true;
205
206 // Typically the driver is still bound here, so it is possible to
207 // directly read the debugfs to get the status.
208 try
209 {
210 auto devPath = internal::getDevicePath(p);
211 PMBus pmbus(devPath);
212 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
213 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
214 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
215 if ((statusWord & status_word::VOUT_FAULT) ||
216 (statusWord & status_word::INPUT_FAULT_WARN) ||
217 (statusWord & status_word::VIN_UV_FAULT) ||
218 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
219 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
220 // UV_FAULT in vout status.
221 (voutStatus & status_vout::UV_FAULT) ||
222 (voutStatus & status_vout::OV_FAULT))
223 {
224 log<level::WARNING>(
225 "Unable to update PSU when other PSU has input/ouput fault",
226 entry("PSU=%s", p.c_str()),
227 entry("STATUS_WORD=0x%04x", statusWord),
228 entry("VOUT_BYTE=0x%02x", voutStatus));
229 return false;
230 }
231 }
232 catch (const std::exception& ex)
233 {
234 // If error occurs on accessing the debugfs, it means something went
235 // wrong, e.g. PSU is not present, and it's not ready to update.
236 log<level::ERR>(ex.what());
237 return false;
238 }
239 }
240 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800241}
242
Lei YU9ab6d752019-10-28 17:03:20 +0800243int Updater::doUpdate()
244{
Lei YUd19df252019-10-25 17:31:52 +0800245 // TODO
Lei YU7c2fbbb2019-11-06 14:56:02 +0800246 uint8_t data;
Lei YU92e89eb2019-11-06 18:08:25 +0800247 uint8_t size;
248 uint16_t word;
249 std::vector<uint8_t> blockData(32);
250
251 i2c->read(data);
252 printf("Read byte 0x%02x\n", data);
253
254 i2c->read(0xf1, data);
255 printf("First read of 0x%02x, 0x%02x\n", 0xf1, data);
256
257 i2c->read(0xbd, word);
258 printf("Read word of 0x%02x, 0x%04x\n", 0xbd, word);
259
260 i2c->read(0x00, size, blockData.data()); // This throws on the device
261 printf("Read block data, size: %d\n", size);
Lei YU9ab6d752019-10-28 17:03:20 +0800262 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800263}
264
Lei YU7c2fbbb2019-11-06 14:56:02 +0800265void Updater::createI2CDevice()
266{
267 auto [id, addr] = internal::parseDeviceName(devName);
268 i2c = i2c::create(id, addr);
269}
270
Lei YUd19df252019-10-25 17:31:52 +0800271} // namespace updater