| 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 |  | 
 | 24 | #include <fstream> | 
 | 25 | #include <phosphor-logging/log.hpp> | 
 | 26 |  | 
 | 27 | using namespace phosphor::logging; | 
 | 28 | namespace util = phosphor::power::util; | 
 | 29 |  | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 30 | namespace updater | 
 | 31 | { | 
 | 32 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 33 | namespace internal | 
 | 34 | { | 
 | 35 |  | 
 | 36 | /* Get the device name from the device path */ | 
 | 37 | std::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 |  | 
 | 46 | std::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 YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 65 | bool update(const std::string& psuInventoryPath, const std::string& imageDir) | 
 | 66 | { | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 67 |     auto devPath = internal::getDevicePath(psuInventoryPath); | 
 | 68 |     if (devPath.empty()) | 
 | 69 |     { | 
 | 70 |         return false; | 
 | 71 |     } | 
 | 72 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 73 |     Updater updater(psuInventoryPath, devPath, imageDir); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 74 |     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 YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 82 |     int ret = updater.doUpdate(); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 83 |     updater.bindUnbind(true); | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 84 |     return ret == 0; | 
 | 85 | } | 
 | 86 |  | 
 | 87 | Updater::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 YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 106 | } | 
 | 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. | 
 | 113 | void 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 |  | 
 | 132 | void 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 YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 148 | bool Updater::isReadyToUpdate() | 
 | 149 | { | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame^] | 150 |     using namespace phosphor::pmbus; | 
 | 151 |  | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 152 |     // Pre-condition for updating PSU: | 
 | 153 |     // * Host is powered off | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame^] | 154 |     // * 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 YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 159 |     { | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame^] | 160 |         log<level::WARNING>("Unable to update PSU when host is on"); | 
| Lei YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 161 |         return false; | 
 | 162 |     } | 
| Lei YU | e8c9cd6 | 2019-11-04 14:24:41 +0800 | [diff] [blame^] | 163 |  | 
 | 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 YU | 575ed13 | 2019-10-29 17:22:16 +0800 | [diff] [blame] | 229 | } | 
 | 230 |  | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 231 | int Updater::doUpdate() | 
 | 232 | { | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 233 |     // TODO | 
| Lei YU | 9ab6d75 | 2019-10-28 17:03:20 +0800 | [diff] [blame] | 234 |     return 0; | 
| Lei YU | d19df25 | 2019-10-25 17:31:52 +0800 | [diff] [blame] | 235 | } | 
 | 236 |  | 
 | 237 | } // namespace updater |