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