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()),
105     psuInventoryPath(psuInventoryPath), devPath(devPath),
106     devName(internal::getDeviceName(devPath)), 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