| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 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 |  | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame] | 20 | #include "pmbus.hpp" | 
| Lei YU | cfc040c | 2019-10-29 17:10:26 +0800 | [diff] [blame] | 21 | #include "types.hpp" | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 22 | #include "utility.hpp" | 
|  | 23 |  | 
| Lei YU | 34fb8bd | 2019-11-07 14:24:20 +0800 | [diff] [blame] | 24 | #include <chrono> | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 25 | #include <fstream> | 
|  | 26 | #include <phosphor-logging/log.hpp> | 
| Lei YU | 34fb8bd | 2019-11-07 14:24:20 +0800 | [diff] [blame] | 27 | #include <thread> | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 28 |  | 
|  | 29 | using namespace phosphor::logging; | 
|  | 30 | namespace util = phosphor::power::util; | 
|  | 31 |  | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 32 | namespace updater | 
|  | 33 | { | 
|  | 34 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 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 |  | 
| Lei YU | 7c2fbbb | 2019-11-06 14:56:02 +0800 | [diff] [blame] | 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 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 76 | } // namespace internal | 
|  | 77 |  | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 78 | bool update(const std::string& psuInventoryPath, const std::string& imageDir) | 
|  | 79 | { | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 80 | auto devPath = internal::getDevicePath(psuInventoryPath); | 
|  | 81 | if (devPath.empty()) | 
|  | 82 | { | 
|  | 83 | return false; | 
|  | 84 | } | 
|  | 85 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 86 | Updater updater(psuInventoryPath, devPath, imageDir); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 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); | 
| Lei YU | 7c2fbbb | 2019-11-06 14:56:02 +0800 | [diff] [blame] | 95 | updater.createI2CDevice(); | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 96 | int ret = updater.doUpdate(); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 97 | updater.bindUnbind(true); | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 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 | } | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 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 |  | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 162 | bool Updater::isReadyToUpdate() | 
|  | 163 | { | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame] | 164 | using namespace phosphor::pmbus; | 
|  | 165 |  | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 166 | // Pre-condition for updating PSU: | 
|  | 167 | // * Host is powered off | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame] | 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)) | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 173 | { | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame] | 174 | log<level::WARNING>("Unable to update PSU when host is on"); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 175 | return false; | 
|  | 176 | } | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame] | 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; | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 243 | } | 
|  | 244 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 245 | int Updater::doUpdate() | 
|  | 246 | { | 
| Lei YU | 34fb8bd | 2019-11-07 14:24:20 +0800 | [diff] [blame] | 247 | using namespace std::chrono; | 
| Lei YU | 92e89eb | 2019-11-06 18:08:25 +0800 | [diff] [blame] | 248 |  | 
| Lei YU | 34fb8bd | 2019-11-07 14:24:20 +0800 | [diff] [blame] | 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)); | 
| Lei YU | 92e89eb | 2019-11-06 18:08:25 +0800 | [diff] [blame] | 264 |  | 
|  | 265 | i2c->read(0xf1, data); | 
| Lei YU | 34fb8bd | 2019-11-07 14:24:20 +0800 | [diff] [blame] | 266 | printf("Read of 0x%02x, 0x%02x\n", 0xf1, data); | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 267 | return 0; | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 268 | } | 
|  | 269 |  | 
| Lei YU | 7c2fbbb | 2019-11-06 14:56:02 +0800 | [diff] [blame] | 270 | void Updater::createI2CDevice() | 
|  | 271 | { | 
|  | 272 | auto [id, addr] = internal::parseDeviceName(devName); | 
|  | 273 | i2c = i2c::create(id, addr); | 
|  | 274 | } | 
|  | 275 |  | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 276 | } // namespace updater |