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 */ 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 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 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 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 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. 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 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 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 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 271 void Updater::createI2CDevice() 272 { 273 auto [id, addr] = internal::parseDeviceName(devName); 274 i2c = i2c::create(id, addr); 275 } 276 277 } // namespace updater 278