10bf1b782SLei YU /** 20bf1b782SLei YU * Copyright © 2019 IBM Corporation 30bf1b782SLei YU * 40bf1b782SLei YU * Licensed under the Apache License, Version 2.0 (the "License"); 50bf1b782SLei YU * you may not use this file except in compliance with the License. 60bf1b782SLei YU * You may obtain a copy of the License at 70bf1b782SLei YU * 80bf1b782SLei YU * http://www.apache.org/licenses/LICENSE-2.0 90bf1b782SLei YU * 100bf1b782SLei YU * Unless required by applicable law or agreed to in writing, software 110bf1b782SLei YU * distributed under the License is distributed on an "AS IS" BASIS, 120bf1b782SLei YU * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130bf1b782SLei YU * See the License for the specific language governing permissions and 140bf1b782SLei YU * limitations under the License. 150bf1b782SLei YU */ 160bf1b782SLei YU #include "config.h" 170bf1b782SLei YU 180bf1b782SLei YU #include "version.hpp" 190bf1b782SLei YU 200bf1b782SLei YU #include "pmbus.hpp" 210bf1b782SLei YU #include "utility.hpp" 220bf1b782SLei YU 230bf1b782SLei YU #include <phosphor-logging/log.hpp> 24*5dce1a74SFaisal Awada #include <xyz/openbmc_project/Common/Device/error.hpp> 25d1bc4cecSBrandon Wyman 26*5dce1a74SFaisal Awada #include <exception> 27*5dce1a74SFaisal Awada #include <iostream> 28*5dce1a74SFaisal Awada #include <regex> 29*5dce1a74SFaisal Awada #include <stdexcept> 300bf1b782SLei YU #include <tuple> 310bf1b782SLei YU 320bf1b782SLei YU using json = nlohmann::json; 330bf1b782SLei YU 340bf1b782SLei YU using namespace phosphor::logging; 350bf1b782SLei YU 36*5dce1a74SFaisal Awada using namespace phosphor::power::util; 37*5dce1a74SFaisal Awada using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 380bf1b782SLei YU 39*5dce1a74SFaisal Awada namespace version 40*5dce1a74SFaisal Awada { 410bf1b782SLei YU namespace utils 420bf1b782SLei YU { 43*5dce1a74SFaisal Awada constexpr auto IBMCFFPSInterface = 44*5dce1a74SFaisal Awada "xyz.openbmc_project.Configuration.IBMCFFPSConnector"; 45*5dce1a74SFaisal Awada constexpr auto i2cBusProp = "I2CBus"; 46*5dce1a74SFaisal Awada constexpr auto i2cAddressProp = "I2CAddress"; 47*5dce1a74SFaisal Awada 480bf1b782SLei YU PsuVersionInfo getVersionInfo(const std::string& psuInventoryPath) 490bf1b782SLei YU { 500bf1b782SLei YU auto data = phosphor::power::util::loadJSONFromFile(PSU_JSON_PATH); 510bf1b782SLei YU 520bf1b782SLei YU if (data == nullptr) 530bf1b782SLei YU { 540bf1b782SLei YU return {}; 550bf1b782SLei YU } 560bf1b782SLei YU 570bf1b782SLei YU auto devices = data.find("psuDevices"); 580bf1b782SLei YU if (devices == data.end()) 590bf1b782SLei YU { 600bf1b782SLei YU log<level::WARNING>("Unable to find psuDevices"); 610bf1b782SLei YU return {}; 620bf1b782SLei YU } 630bf1b782SLei YU auto devicePath = devices->find(psuInventoryPath); 640bf1b782SLei YU if (devicePath == devices->end()) 650bf1b782SLei YU { 660bf1b782SLei YU log<level::WARNING>("Unable to find path for PSU", 670bf1b782SLei YU entry("PATH=%s", psuInventoryPath.c_str())); 680bf1b782SLei YU return {}; 690bf1b782SLei YU } 700bf1b782SLei YU 710bf1b782SLei YU auto type = phosphor::power::util::getPMBusAccessType(data); 720bf1b782SLei YU 730bf1b782SLei YU std::string versionStr; 740bf1b782SLei YU for (const auto& fru : data["fruConfigs"]) 750bf1b782SLei YU { 760bf1b782SLei YU if (fru["propertyName"] == "Version") 770bf1b782SLei YU { 78c0b9e9e4SMatt Spinler versionStr = fru["fileName"].get<std::string>(); 790bf1b782SLei YU break; 800bf1b782SLei YU } 810bf1b782SLei YU } 820bf1b782SLei YU if (versionStr.empty()) 830bf1b782SLei YU { 840bf1b782SLei YU log<level::WARNING>("Unable to find Version file"); 850bf1b782SLei YU return {}; 860bf1b782SLei YU } 870bf1b782SLei YU return std::make_tuple(*devicePath, type, versionStr); 880bf1b782SLei YU } 89093b5917SLei YU 90093b5917SLei YU // A default implemention compare the string itself 91093b5917SLei YU std::string getLatestDefault(const std::vector<std::string>& versions) 92093b5917SLei YU { 93093b5917SLei YU std::string latest; 94093b5917SLei YU for (const auto& version : versions) 95093b5917SLei YU { 96093b5917SLei YU if (latest < version) 97093b5917SLei YU { 98093b5917SLei YU latest = version; 99093b5917SLei YU } 100093b5917SLei YU } 101093b5917SLei YU return latest; 102093b5917SLei YU } 103093b5917SLei YU 104*5dce1a74SFaisal Awada PsuI2cInfo getPsuI2c(sdbusplus::bus_t& bus, const std::string& psuInventoryPath) 1050bf1b782SLei YU { 106*5dce1a74SFaisal Awada auto depth = 0; 107*5dce1a74SFaisal Awada auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 108*5dce1a74SFaisal Awada if (objects.empty()) 109*5dce1a74SFaisal Awada { 110*5dce1a74SFaisal Awada throw std::runtime_error("Supported Configuration Not Found"); 111*5dce1a74SFaisal Awada } 112*5dce1a74SFaisal Awada 113*5dce1a74SFaisal Awada std::optional<std::uint64_t> i2cbus; 114*5dce1a74SFaisal Awada std::optional<std::uint64_t> i2caddr; 115*5dce1a74SFaisal Awada 116*5dce1a74SFaisal Awada // GET a map of objects back. 117*5dce1a74SFaisal Awada // Each object will have a path, a service, and an interface. 118*5dce1a74SFaisal Awada for (const auto& [path, services] : objects) 119*5dce1a74SFaisal Awada { 120*5dce1a74SFaisal Awada auto service = services.begin()->first; 121*5dce1a74SFaisal Awada 122*5dce1a74SFaisal Awada if (path.empty() || service.empty()) 123*5dce1a74SFaisal Awada { 124*5dce1a74SFaisal Awada continue; 125*5dce1a74SFaisal Awada } 126*5dce1a74SFaisal Awada 127*5dce1a74SFaisal Awada // Match the PSU identifier in the path with the passed PSU inventory 128*5dce1a74SFaisal Awada // path. Compare the last character of both paths to find the PSU bus 129*5dce1a74SFaisal Awada // and address. example: PSU path: 130*5dce1a74SFaisal Awada // /xyz/openbmc_project/inventory/system/board/Nisqually_Backplane/Power_Supply_Slot_0 131*5dce1a74SFaisal Awada // PSU inventory path: 132*5dce1a74SFaisal Awada // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0 133*5dce1a74SFaisal Awada if (path.back() == psuInventoryPath.back()) 134*5dce1a74SFaisal Awada { 135*5dce1a74SFaisal Awada // Retrieve i2cBus and i2cAddress from array of properties. 136*5dce1a74SFaisal Awada auto properties = 137*5dce1a74SFaisal Awada getAllProperties(bus, path, IBMCFFPSInterface, service); 138*5dce1a74SFaisal Awada for (const auto& property : properties) 139*5dce1a74SFaisal Awada { 140*5dce1a74SFaisal Awada try 141*5dce1a74SFaisal Awada { 142*5dce1a74SFaisal Awada if (property.first == i2cBusProp) 143*5dce1a74SFaisal Awada { 144*5dce1a74SFaisal Awada i2cbus = std::get<uint64_t>(properties.at(i2cBusProp)); 145*5dce1a74SFaisal Awada } 146*5dce1a74SFaisal Awada else if (property.first == i2cAddressProp) 147*5dce1a74SFaisal Awada { 148*5dce1a74SFaisal Awada i2caddr = 149*5dce1a74SFaisal Awada std::get<uint64_t>(properties.at(i2cAddressProp)); 150*5dce1a74SFaisal Awada } 151*5dce1a74SFaisal Awada } 152*5dce1a74SFaisal Awada catch (const std::exception& e) 153*5dce1a74SFaisal Awada { 154*5dce1a74SFaisal Awada log<level::WARNING>( 155*5dce1a74SFaisal Awada std::format("Error reading property {}: {}", 156*5dce1a74SFaisal Awada property.first, e.what()) 157*5dce1a74SFaisal Awada .c_str()); 158*5dce1a74SFaisal Awada } 159*5dce1a74SFaisal Awada } 160*5dce1a74SFaisal Awada 161*5dce1a74SFaisal Awada if (i2cbus.has_value() && i2caddr.has_value()) 162*5dce1a74SFaisal Awada { 163*5dce1a74SFaisal Awada break; 164*5dce1a74SFaisal Awada } 165*5dce1a74SFaisal Awada } 166*5dce1a74SFaisal Awada } 167*5dce1a74SFaisal Awada 168*5dce1a74SFaisal Awada if (!i2cbus.has_value() || !i2caddr.has_value()) 169*5dce1a74SFaisal Awada { 170*5dce1a74SFaisal Awada throw std::runtime_error("Failed to get I2C bus or address"); 171*5dce1a74SFaisal Awada } 172*5dce1a74SFaisal Awada 173*5dce1a74SFaisal Awada return std::make_tuple(*i2cbus, *i2caddr); 174*5dce1a74SFaisal Awada } 175*5dce1a74SFaisal Awada 176*5dce1a74SFaisal Awada std::unique_ptr<phosphor::pmbus::PMBusBase> 177*5dce1a74SFaisal Awada getPmbusIntf(std::uint64_t i2cBus, std::uint64_t i2cAddr) 178*5dce1a74SFaisal Awada { 179*5dce1a74SFaisal Awada std::stringstream ss; 180*5dce1a74SFaisal Awada ss << std::hex << std::setw(4) << std::setfill('0') << i2cAddr; 181*5dce1a74SFaisal Awada return phosphor::pmbus::createPMBus(i2cBus, ss.str()); 182*5dce1a74SFaisal Awada } 183*5dce1a74SFaisal Awada 184*5dce1a74SFaisal Awada std::string readVPDValue(phosphor::pmbus::PMBusBase& pmbusIntf, 185*5dce1a74SFaisal Awada const std::string& vpdName, 186*5dce1a74SFaisal Awada const phosphor::pmbus::Type& type, 187*5dce1a74SFaisal Awada const std::size_t& vpdSize) 188*5dce1a74SFaisal Awada { 189*5dce1a74SFaisal Awada std::string vpdValue; 190*5dce1a74SFaisal Awada const std::regex illegalVPDRegex = 191*5dce1a74SFaisal Awada std::regex("[^[:alnum:]]", std::regex::basic); 192*5dce1a74SFaisal Awada 193*5dce1a74SFaisal Awada try 194*5dce1a74SFaisal Awada { 195*5dce1a74SFaisal Awada vpdValue = pmbusIntf.readString(vpdName, type); 196*5dce1a74SFaisal Awada } 197*5dce1a74SFaisal Awada catch (const ReadFailure& e) 198*5dce1a74SFaisal Awada { 199*5dce1a74SFaisal Awada // Ignore the read failure, let pmbus code indicate failure. 200*5dce1a74SFaisal Awada } 201*5dce1a74SFaisal Awada 202*5dce1a74SFaisal Awada if (vpdValue.size() != vpdSize) 203*5dce1a74SFaisal Awada { 204*5dce1a74SFaisal Awada log<level::INFO>( 205*5dce1a74SFaisal Awada std::format(" {} resize needed. size: {}", vpdName, vpdValue.size()) 206*5dce1a74SFaisal Awada .c_str()); 207*5dce1a74SFaisal Awada vpdValue.resize(vpdSize, ' '); 208*5dce1a74SFaisal Awada } 209*5dce1a74SFaisal Awada 210*5dce1a74SFaisal Awada // Replace any illegal values with space(s). 211*5dce1a74SFaisal Awada std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(), 212*5dce1a74SFaisal Awada illegalVPDRegex, " "); 213*5dce1a74SFaisal Awada 214*5dce1a74SFaisal Awada return vpdValue; 215*5dce1a74SFaisal Awada } 216*5dce1a74SFaisal Awada 217*5dce1a74SFaisal Awada bool checkFileExists(const std::string& filePath) 218*5dce1a74SFaisal Awada { 219*5dce1a74SFaisal Awada try 220*5dce1a74SFaisal Awada { 221*5dce1a74SFaisal Awada return std::filesystem::exists(filePath); 222*5dce1a74SFaisal Awada } 223*5dce1a74SFaisal Awada catch (const std::exception& e) 224*5dce1a74SFaisal Awada { 225*5dce1a74SFaisal Awada log<level::ERR>(std::format("Unable to check for existence of {}: {}", 226*5dce1a74SFaisal Awada filePath, e.what()) 227*5dce1a74SFaisal Awada .c_str()); 228*5dce1a74SFaisal Awada } 229*5dce1a74SFaisal Awada return false; 230*5dce1a74SFaisal Awada } 231*5dce1a74SFaisal Awada } // namespace utils 2320bf1b782SLei YU 2330bf1b782SLei YU std::string getVersion(const std::string& psuInventoryPath) 2340bf1b782SLei YU { 235f5402197SPatrick Williams const auto& [devicePath, type, versionStr] = 236f5402197SPatrick Williams utils::getVersionInfo(psuInventoryPath); 2370bf1b782SLei YU if (devicePath.empty() || versionStr.empty()) 2380bf1b782SLei YU { 239*5dce1a74SFaisal Awada return ""; 2400bf1b782SLei YU } 2410bf1b782SLei YU std::string version; 2420bf1b782SLei YU try 2430bf1b782SLei YU { 2440bf1b782SLei YU phosphor::pmbus::PMBus pmbus(devicePath); 2450bf1b782SLei YU version = pmbus.readString(versionStr, type); 2460bf1b782SLei YU } 2470bf1b782SLei YU catch (const std::exception& ex) 2480bf1b782SLei YU { 2490bf1b782SLei YU log<level::ERR>(ex.what()); 2500bf1b782SLei YU } 2510bf1b782SLei YU return version; 2520bf1b782SLei YU } 2530bf1b782SLei YU 254*5dce1a74SFaisal Awada std::string getVersion(sdbusplus::bus_t& bus, 255*5dce1a74SFaisal Awada const std::string& psuInventoryPath) 256*5dce1a74SFaisal Awada { 257*5dce1a74SFaisal Awada try 258*5dce1a74SFaisal Awada { 259*5dce1a74SFaisal Awada constexpr auto FW_VERSION = "fw_version"; 260*5dce1a74SFaisal Awada using namespace phosphor::pmbus; 261*5dce1a74SFaisal Awada const auto IBMCFFPS_FW_VERSION_SIZE = 12; 262*5dce1a74SFaisal Awada const auto& [i2cbus, i2caddr] = utils::getPsuI2c(bus, psuInventoryPath); 263*5dce1a74SFaisal Awada 264*5dce1a74SFaisal Awada auto pmbusIntf = utils::getPmbusIntf(i2cbus, i2caddr); 265*5dce1a74SFaisal Awada 266*5dce1a74SFaisal Awada if (!pmbusIntf) 267*5dce1a74SFaisal Awada { 268*5dce1a74SFaisal Awada log<level::WARNING>("Unable to get pointer PMBus Interface"); 269*5dce1a74SFaisal Awada return ""; 270*5dce1a74SFaisal Awada } 271*5dce1a74SFaisal Awada 272*5dce1a74SFaisal Awada std::string fwVersion = 273*5dce1a74SFaisal Awada utils::readVPDValue(*pmbusIntf, FW_VERSION, Type::HwmonDeviceDebug, 274*5dce1a74SFaisal Awada IBMCFFPS_FW_VERSION_SIZE); 275*5dce1a74SFaisal Awada return fwVersion; 276*5dce1a74SFaisal Awada } 277*5dce1a74SFaisal Awada catch (const std::exception& e) 278*5dce1a74SFaisal Awada { 279*5dce1a74SFaisal Awada log<level::ERR>(std::format("Error: {}", e.what()).c_str()); 280*5dce1a74SFaisal Awada return ""; 281*5dce1a74SFaisal Awada } 282*5dce1a74SFaisal Awada } 283*5dce1a74SFaisal Awada 284093b5917SLei YU std::string getLatest(const std::vector<std::string>& versions) 285093b5917SLei YU { 286093b5917SLei YU // TODO: when multiple PSU/Machines are supported, add configuration options 287093b5917SLei YU // to implement machine-specific logic. 288093b5917SLei YU // For now IBM AC Servers and Inspur FP5280G2 are supported. 289093b5917SLei YU // 290093b5917SLei YU // IBM AC servers' PSU version has two types: 291093b5917SLei YU // * XXXXYYYYZZZZ: XXXX is the primary version 292093b5917SLei YU // YYYY is the secondary version 293093b5917SLei YU // ZZZZ is the communication version 294093b5917SLei YU // 295093b5917SLei YU // * XXXXYYYY: XXXX is the primary version 296093b5917SLei YU // YYYY is the seconday version 297093b5917SLei YU // 298093b5917SLei YU // Inspur FP5280G2 PSU version is human readable text and a larger string 299093b5917SLei YU // means a newer version. 300093b5917SLei YU // 301093b5917SLei YU // So just compare by strings is OK for these cases 302093b5917SLei YU return utils::getLatestDefault(versions); 303093b5917SLei YU } 3040bf1b782SLei YU } // namespace version 305