xref: /openbmc/phosphor-power/tools/power-utils/updater.cpp (revision 4c94bc7c840d2bfda6ac7acec811f80fdf3f3b7a)
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 <chrono>
25 #include <fstream>
26 #include <phosphor-logging/log.hpp>
27 #include <thread>
28 
29 using namespace phosphor::logging;
30 namespace util = phosphor::power::util;
31 
32 namespace updater
33 {
34 
35 namespace internal
36 {
37 
38 /* Get the device name from the device path */
39 std::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 
48 std::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 
65 std::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 
76 } // namespace internal
77 
78 bool update(const std::string& psuInventoryPath, const std::string& imageDir)
79 {
80     auto devPath = internal::getDevicePath(psuInventoryPath);
81     if (devPath.empty())
82     {
83         return false;
84     }
85 
86     Updater updater(psuInventoryPath, devPath, imageDir);
87     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);
95     updater.createI2CDevice();
96     int ret = updater.doUpdate();
97     updater.bindUnbind(true);
98     return ret == 0;
99 }
100 
101 Updater::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     }
120 }
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.
127 void 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 
146 void 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 
162 bool Updater::isReadyToUpdate()
163 {
164     using namespace phosphor::pmbus;
165 
166     // Pre-condition for updating PSU:
167     // * Host is powered off
168     // * 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))
173     {
174         log<level::WARNING>("Unable to update PSU when host is on");
175         return false;
176     }
177 
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;
243 }
244 
245 int Updater::doUpdate()
246 {
247     using namespace std::chrono;
248 
249     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));
264 
265     i2c->read(0xf1, data);
266     printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
267     return 0;
268 }
269 
270 void Updater::createI2CDevice()
271 {
272     auto [id, addr] = internal::parseDeviceName(devName);
273     i2c = i2c::create(id, addr);
274 }
275 
276 } // namespace updater
277