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 "version.hpp" 19 20 #include "pmbus.hpp" 21 #include "utility.hpp" 22 23 #include <phosphor-logging/log.hpp> 24 #include <xyz/openbmc_project/Common/Device/error.hpp> 25 26 #include <exception> 27 #include <iostream> 28 #include <regex> 29 #include <stdexcept> 30 #include <tuple> 31 32 using json = nlohmann::json; 33 34 using namespace phosphor::logging; 35 36 using namespace phosphor::power::util; 37 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 38 39 namespace version 40 { 41 namespace utils 42 { 43 constexpr auto IBMCFFPSInterface = 44 "xyz.openbmc_project.Configuration.IBMCFFPSConnector"; 45 constexpr auto i2cBusProp = "I2CBus"; 46 constexpr auto i2cAddressProp = "I2CAddress"; 47 48 PsuVersionInfo getVersionInfo(const std::string& psuInventoryPath) 49 { 50 auto data = phosphor::power::util::loadJSONFromFile(PSU_JSON_PATH); 51 52 if (data == nullptr) 53 { 54 return {}; 55 } 56 57 auto devices = data.find("psuDevices"); 58 if (devices == data.end()) 59 { 60 log<level::WARNING>("Unable to find psuDevices"); 61 return {}; 62 } 63 auto devicePath = devices->find(psuInventoryPath); 64 if (devicePath == devices->end()) 65 { 66 log<level::WARNING>("Unable to find path for PSU", 67 entry("PATH=%s", psuInventoryPath.c_str())); 68 return {}; 69 } 70 71 auto type = phosphor::power::util::getPMBusAccessType(data); 72 73 std::string versionStr; 74 for (const auto& fru : data["fruConfigs"]) 75 { 76 if (fru["propertyName"] == "Version") 77 { 78 versionStr = fru["fileName"].get<std::string>(); 79 break; 80 } 81 } 82 if (versionStr.empty()) 83 { 84 log<level::WARNING>("Unable to find Version file"); 85 return {}; 86 } 87 return std::make_tuple(*devicePath, type, versionStr); 88 } 89 90 // A default implemention compare the string itself 91 std::string getLatestDefault(const std::vector<std::string>& versions) 92 { 93 std::string latest; 94 for (const auto& version : versions) 95 { 96 if (latest < version) 97 { 98 latest = version; 99 } 100 } 101 return latest; 102 } 103 104 PsuI2cInfo getPsuI2c(sdbusplus::bus_t& bus, const std::string& psuInventoryPath) 105 { 106 auto depth = 0; 107 auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 108 if (objects.empty()) 109 { 110 throw std::runtime_error("Supported Configuration Not Found"); 111 } 112 113 std::optional<std::uint64_t> i2cbus; 114 std::optional<std::uint64_t> i2caddr; 115 116 // GET a map of objects back. 117 // Each object will have a path, a service, and an interface. 118 for (const auto& [path, services] : objects) 119 { 120 auto service = services.begin()->first; 121 122 if (path.empty() || service.empty()) 123 { 124 continue; 125 } 126 127 // Match the PSU identifier in the path with the passed PSU inventory 128 // path. Compare the last character of both paths to find the PSU bus 129 // and address. example: PSU path: 130 // /xyz/openbmc_project/inventory/system/board/Nisqually_Backplane/Power_Supply_Slot_0 131 // PSU inventory path: 132 // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0 133 if (path.back() == psuInventoryPath.back()) 134 { 135 // Retrieve i2cBus and i2cAddress from array of properties. 136 auto properties = 137 getAllProperties(bus, path, IBMCFFPSInterface, service); 138 for (const auto& property : properties) 139 { 140 try 141 { 142 if (property.first == i2cBusProp) 143 { 144 i2cbus = std::get<uint64_t>(properties.at(i2cBusProp)); 145 } 146 else if (property.first == i2cAddressProp) 147 { 148 i2caddr = 149 std::get<uint64_t>(properties.at(i2cAddressProp)); 150 } 151 } 152 catch (const std::exception& e) 153 { 154 log<level::WARNING>( 155 std::format("Error reading property {}: {}", 156 property.first, e.what()) 157 .c_str()); 158 } 159 } 160 161 if (i2cbus.has_value() && i2caddr.has_value()) 162 { 163 break; 164 } 165 } 166 } 167 168 if (!i2cbus.has_value() || !i2caddr.has_value()) 169 { 170 throw std::runtime_error("Failed to get I2C bus or address"); 171 } 172 173 return std::make_tuple(*i2cbus, *i2caddr); 174 } 175 176 std::unique_ptr<phosphor::pmbus::PMBusBase> 177 getPmbusIntf(std::uint64_t i2cBus, std::uint64_t i2cAddr) 178 { 179 std::stringstream ss; 180 ss << std::hex << std::setw(4) << std::setfill('0') << i2cAddr; 181 return phosphor::pmbus::createPMBus(i2cBus, ss.str()); 182 } 183 184 std::string readVPDValue(phosphor::pmbus::PMBusBase& pmbusIntf, 185 const std::string& vpdName, 186 const phosphor::pmbus::Type& type, 187 const std::size_t& vpdSize) 188 { 189 std::string vpdValue; 190 const std::regex illegalVPDRegex = 191 std::regex("[^[:alnum:]]", std::regex::basic); 192 193 try 194 { 195 vpdValue = pmbusIntf.readString(vpdName, type); 196 } 197 catch (const ReadFailure& e) 198 { 199 // Ignore the read failure, let pmbus code indicate failure. 200 } 201 202 if (vpdValue.size() != vpdSize) 203 { 204 log<level::INFO>( 205 std::format(" {} resize needed. size: {}", vpdName, vpdValue.size()) 206 .c_str()); 207 vpdValue.resize(vpdSize, ' '); 208 } 209 210 // Replace any illegal values with space(s). 211 std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(), 212 illegalVPDRegex, " "); 213 214 return vpdValue; 215 } 216 217 bool checkFileExists(const std::string& filePath) 218 { 219 try 220 { 221 return std::filesystem::exists(filePath); 222 } 223 catch (const std::exception& e) 224 { 225 log<level::ERR>(std::format("Unable to check for existence of {}: {}", 226 filePath, e.what()) 227 .c_str()); 228 } 229 return false; 230 } 231 } // namespace utils 232 233 std::string getVersion(const std::string& psuInventoryPath) 234 { 235 const auto& [devicePath, type, versionStr] = 236 utils::getVersionInfo(psuInventoryPath); 237 if (devicePath.empty() || versionStr.empty()) 238 { 239 return ""; 240 } 241 std::string version; 242 try 243 { 244 phosphor::pmbus::PMBus pmbus(devicePath); 245 version = pmbus.readString(versionStr, type); 246 } 247 catch (const std::exception& ex) 248 { 249 log<level::ERR>(ex.what()); 250 } 251 return version; 252 } 253 254 std::string getVersion(sdbusplus::bus_t& bus, 255 const std::string& psuInventoryPath) 256 { 257 try 258 { 259 constexpr auto FW_VERSION = "fw_version"; 260 using namespace phosphor::pmbus; 261 const auto IBMCFFPS_FW_VERSION_SIZE = 12; 262 const auto& [i2cbus, i2caddr] = utils::getPsuI2c(bus, psuInventoryPath); 263 264 auto pmbusIntf = utils::getPmbusIntf(i2cbus, i2caddr); 265 266 if (!pmbusIntf) 267 { 268 log<level::WARNING>("Unable to get pointer PMBus Interface"); 269 return ""; 270 } 271 272 std::string fwVersion = 273 utils::readVPDValue(*pmbusIntf, FW_VERSION, Type::HwmonDeviceDebug, 274 IBMCFFPS_FW_VERSION_SIZE); 275 return fwVersion; 276 } 277 catch (const std::exception& e) 278 { 279 log<level::ERR>(std::format("Error: {}", e.what()).c_str()); 280 return ""; 281 } 282 } 283 284 std::string getLatest(const std::vector<std::string>& versions) 285 { 286 // TODO: when multiple PSU/Machines are supported, add configuration options 287 // to implement machine-specific logic. 288 // For now IBM AC Servers and Inspur FP5280G2 are supported. 289 // 290 // IBM AC servers' PSU version has two types: 291 // * XXXXYYYYZZZZ: XXXX is the primary version 292 // YYYY is the secondary version 293 // ZZZZ is the communication version 294 // 295 // * XXXXYYYY: XXXX is the primary version 296 // YYYY is the seconday version 297 // 298 // Inspur FP5280G2 PSU version is human readable text and a larger string 299 // means a newer version. 300 // 301 // So just compare by strings is OK for these cases 302 return utils::getLatestDefault(versions); 303 } 304 } // namespace version 305