blob: e26d1b0f729e6ec2e30d9a98d1611f154d55eb7c [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
63} // namespace internal
64
Lei YUd19df252019-10-25 17:31:52 +080065bool update(const std::string& psuInventoryPath, const std::string& imageDir)
66{
Lei YU9ab6d752019-10-28 17:03:20 +080067 auto devPath = internal::getDevicePath(psuInventoryPath);
68 if (devPath.empty())
69 {
70 return false;
71 }
72
Lei YU9ab6d752019-10-28 17:03:20 +080073 Updater updater(psuInventoryPath, devPath, imageDir);
Lei YU575ed132019-10-29 17:22:16 +080074 if (!updater.isReadyToUpdate())
75 {
76 log<level::ERR>("PSU not ready to update",
77 entry("PSU=%s", psuInventoryPath.c_str()));
78 return false;
79 }
80
81 updater.bindUnbind(false);
Lei YU9ab6d752019-10-28 17:03:20 +080082 int ret = updater.doUpdate();
Lei YU575ed132019-10-29 17:22:16 +080083 updater.bindUnbind(true);
Lei YU9ab6d752019-10-28 17:03:20 +080084 return ret == 0;
85}
86
87Updater::Updater(const std::string& psuInventoryPath,
88 const std::string& devPath, const std::string& imageDir) :
89 bus(sdbusplus::bus::new_default()),
90 psuInventoryPath(psuInventoryPath), devPath(devPath),
91 devName(internal::getDeviceName(devPath)), imageDir(imageDir)
92{
93 fs::path p = fs::path(devPath) / "driver";
94 try
95 {
96 driverPath =
97 fs::canonical(p); // Get the path that points to the driver dir
98 }
99 catch (const fs::filesystem_error& e)
100 {
101 log<level::ERR>("Failed to get canonical path",
102 entry("DEVPATH=%s", devPath.c_str()),
103 entry("ERROR=%s", e.what()));
104 throw;
105 }
Lei YU9ab6d752019-10-28 17:03:20 +0800106}
107
108// During PSU update, it needs to access the PSU i2c device directly, so it
109// needs to unbind the driver during the update, and re-bind after it's done.
110// After unbind, the hwmon sysfs will be gone, and the psu-monitor will report
111// errors. So set the PSU inventory's Present property to false so that
112// psu-monitor will not report any errors.
113void Updater::bindUnbind(bool doBind)
114{
115 if (!doBind)
116 {
117 // Set non-present before unbind the driver
118 setPresent(doBind);
119 }
120 auto p = driverPath;
121 p /= doBind ? "bind" : "unbind";
122 std::ofstream out(p.string());
123 out << devName;
124
125 if (doBind)
126 {
127 // Set to present after bind the driver
128 setPresent(doBind);
129 }
130}
131
132void Updater::setPresent(bool present)
133{
134 try
135 {
136 auto service = util::getService(psuInventoryPath, INVENTORY_IFACE, bus);
137 util::setProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
138 service, bus, present);
139 }
140 catch (const std::exception& e)
141 {
142 log<level::ERR>("Failed to set present property",
143 entry("PSU=%s", psuInventoryPath.c_str()),
144 entry("PRESENT=%d", present));
145 }
146}
147
Lei YU575ed132019-10-29 17:22:16 +0800148bool Updater::isReadyToUpdate()
149{
Lei YUe8c9cd62019-11-04 14:24:41 +0800150 using namespace phosphor::pmbus;
151
Lei YU575ed132019-10-29 17:22:16 +0800152 // Pre-condition for updating PSU:
153 // * Host is powered off
Lei YUe8c9cd62019-11-04 14:24:41 +0800154 // * At least one other PSU is present
155 // * All other PSUs that are present are having AC input and DC standby
156 // output
157
158 if (util::isPoweredOn(bus, true))
Lei YU575ed132019-10-29 17:22:16 +0800159 {
Lei YUe8c9cd62019-11-04 14:24:41 +0800160 log<level::WARNING>("Unable to update PSU when host is on");
Lei YU575ed132019-10-29 17:22:16 +0800161 return false;
162 }
Lei YUe8c9cd62019-11-04 14:24:41 +0800163
164 bool hasOtherPresent = false;
165 auto paths = util::getPSUInventoryPaths(bus);
166 for (const auto& p : paths)
167 {
168 if (p == psuInventoryPath)
169 {
170 // Skip check for itself
171 continue;
172 }
173
174 // Check PSU present
175 bool present = false;
176 try
177 {
178 auto service = util::getService(p, INVENTORY_IFACE, bus);
179 util::getProperty(INVENTORY_IFACE, PRESENT_PROP, psuInventoryPath,
180 service, bus, present);
181 }
182 catch (const std::exception& e)
183 {
184 log<level::ERR>("Failed to get present property",
185 entry("PSU=%s", p.c_str()));
186 }
187 if (!present)
188 {
189 log<level::WARNING>("PSU not present", entry("PSU=%s", p.c_str()));
190 continue;
191 }
192 hasOtherPresent = true;
193
194 // Typically the driver is still bound here, so it is possible to
195 // directly read the debugfs to get the status.
196 try
197 {
198 auto devPath = internal::getDevicePath(p);
199 PMBus pmbus(devPath);
200 uint16_t statusWord = pmbus.read(STATUS_WORD, Type::Debug);
201 auto status0Vout = pmbus.insertPageNum(STATUS_VOUT, 0);
202 uint8_t voutStatus = pmbus.read(status0Vout, Type::Debug);
203 if ((statusWord & status_word::VOUT_FAULT) ||
204 (statusWord & status_word::INPUT_FAULT_WARN) ||
205 (statusWord & status_word::VIN_UV_FAULT) ||
206 // For ibm-cffps PSUs, the MFR (0x80)'s OV (bit 2) and VAUX
207 // (bit 6) fault map to OV_FAULT, and UV (bit 3) fault maps to
208 // UV_FAULT in vout status.
209 (voutStatus & status_vout::UV_FAULT) ||
210 (voutStatus & status_vout::OV_FAULT))
211 {
212 log<level::WARNING>(
213 "Unable to update PSU when other PSU has input/ouput fault",
214 entry("PSU=%s", p.c_str()),
215 entry("STATUS_WORD=0x%04x", statusWord),
216 entry("VOUT_BYTE=0x%02x", voutStatus));
217 return false;
218 }
219 }
220 catch (const std::exception& ex)
221 {
222 // If error occurs on accessing the debugfs, it means something went
223 // wrong, e.g. PSU is not present, and it's not ready to update.
224 log<level::ERR>(ex.what());
225 return false;
226 }
227 }
228 return hasOtherPresent;
Lei YU575ed132019-10-29 17:22:16 +0800229}
230
Lei YU9ab6d752019-10-28 17:03:20 +0800231int Updater::doUpdate()
232{
Lei YUd19df252019-10-25 17:31:52 +0800233 // TODO
Lei YU9ab6d752019-10-28 17:03:20 +0800234 return 0;
Lei YUd19df252019-10-25 17:31:52 +0800235}
236
237} // namespace updater