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> 245dce1a74SFaisal Awada #include <xyz/openbmc_project/Common/Device/error.hpp> 25d1bc4cecSBrandon Wyman 265dce1a74SFaisal Awada #include <exception> 275dce1a74SFaisal Awada #include <iostream> 285dce1a74SFaisal Awada #include <regex> 295dce1a74SFaisal 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 365dce1a74SFaisal Awada using namespace phosphor::power::util; 375dce1a74SFaisal Awada using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 380bf1b782SLei YU 395dce1a74SFaisal Awada namespace version 405dce1a74SFaisal Awada { 410bf1b782SLei YU namespace utils 420bf1b782SLei YU { 435dce1a74SFaisal Awada constexpr auto IBMCFFPSInterface = 445dce1a74SFaisal Awada "xyz.openbmc_project.Configuration.IBMCFFPSConnector"; 455dce1a74SFaisal Awada constexpr auto i2cBusProp = "I2CBus"; 465dce1a74SFaisal Awada constexpr auto i2cAddressProp = "I2CAddress"; 475dce1a74SFaisal 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 1045dce1a74SFaisal Awada PsuI2cInfo getPsuI2c(sdbusplus::bus_t& bus, const std::string& psuInventoryPath) 1050bf1b782SLei YU { 1065dce1a74SFaisal Awada auto depth = 0; 1075dce1a74SFaisal Awada auto objects = getSubTree(bus, "/", IBMCFFPSInterface, depth); 1085dce1a74SFaisal Awada if (objects.empty()) 1095dce1a74SFaisal Awada { 1105dce1a74SFaisal Awada throw std::runtime_error("Supported Configuration Not Found"); 1115dce1a74SFaisal Awada } 1125dce1a74SFaisal Awada 1135dce1a74SFaisal Awada std::optional<std::uint64_t> i2cbus; 1145dce1a74SFaisal Awada std::optional<std::uint64_t> i2caddr; 1155dce1a74SFaisal Awada 1165dce1a74SFaisal Awada // GET a map of objects back. 1175dce1a74SFaisal Awada // Each object will have a path, a service, and an interface. 1185dce1a74SFaisal Awada for (const auto& [path, services] : objects) 1195dce1a74SFaisal Awada { 1205dce1a74SFaisal Awada auto service = services.begin()->first; 1215dce1a74SFaisal Awada 1225dce1a74SFaisal Awada if (path.empty() || service.empty()) 1235dce1a74SFaisal Awada { 1245dce1a74SFaisal Awada continue; 1255dce1a74SFaisal Awada } 1265dce1a74SFaisal Awada 1275dce1a74SFaisal Awada // Match the PSU identifier in the path with the passed PSU inventory 1285dce1a74SFaisal Awada // path. Compare the last character of both paths to find the PSU bus 1295dce1a74SFaisal Awada // and address. example: PSU path: 1305dce1a74SFaisal Awada // /xyz/openbmc_project/inventory/system/board/Nisqually_Backplane/Power_Supply_Slot_0 1315dce1a74SFaisal Awada // PSU inventory path: 1325dce1a74SFaisal Awada // /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0 1335dce1a74SFaisal Awada if (path.back() == psuInventoryPath.back()) 1345dce1a74SFaisal Awada { 1355dce1a74SFaisal Awada // Retrieve i2cBus and i2cAddress from array of properties. 1365dce1a74SFaisal Awada auto properties = 1375dce1a74SFaisal Awada getAllProperties(bus, path, IBMCFFPSInterface, service); 1385dce1a74SFaisal Awada for (const auto& property : properties) 1395dce1a74SFaisal Awada { 1405dce1a74SFaisal Awada try 1415dce1a74SFaisal Awada { 1425dce1a74SFaisal Awada if (property.first == i2cBusProp) 1435dce1a74SFaisal Awada { 1445dce1a74SFaisal Awada i2cbus = std::get<uint64_t>(properties.at(i2cBusProp)); 1455dce1a74SFaisal Awada } 1465dce1a74SFaisal Awada else if (property.first == i2cAddressProp) 1475dce1a74SFaisal Awada { 1485dce1a74SFaisal Awada i2caddr = 1495dce1a74SFaisal Awada std::get<uint64_t>(properties.at(i2cAddressProp)); 1505dce1a74SFaisal Awada } 1515dce1a74SFaisal Awada } 1525dce1a74SFaisal Awada catch (const std::exception& e) 1535dce1a74SFaisal Awada { 1545dce1a74SFaisal Awada log<level::WARNING>( 1555dce1a74SFaisal Awada std::format("Error reading property {}: {}", 1565dce1a74SFaisal Awada property.first, e.what()) 1575dce1a74SFaisal Awada .c_str()); 1585dce1a74SFaisal Awada } 1595dce1a74SFaisal Awada } 1605dce1a74SFaisal Awada 1615dce1a74SFaisal Awada if (i2cbus.has_value() && i2caddr.has_value()) 1625dce1a74SFaisal Awada { 1635dce1a74SFaisal Awada break; 1645dce1a74SFaisal Awada } 1655dce1a74SFaisal Awada } 1665dce1a74SFaisal Awada } 1675dce1a74SFaisal Awada 1685dce1a74SFaisal Awada if (!i2cbus.has_value() || !i2caddr.has_value()) 1695dce1a74SFaisal Awada { 1705dce1a74SFaisal Awada throw std::runtime_error("Failed to get I2C bus or address"); 1715dce1a74SFaisal Awada } 1725dce1a74SFaisal Awada 1735dce1a74SFaisal Awada return std::make_tuple(*i2cbus, *i2caddr); 1745dce1a74SFaisal Awada } 1755dce1a74SFaisal Awada 1765dce1a74SFaisal Awada std::unique_ptr<phosphor::pmbus::PMBusBase> 1775dce1a74SFaisal Awada getPmbusIntf(std::uint64_t i2cBus, std::uint64_t i2cAddr) 1785dce1a74SFaisal Awada { 1795dce1a74SFaisal Awada std::stringstream ss; 1805dce1a74SFaisal Awada ss << std::hex << std::setw(4) << std::setfill('0') << i2cAddr; 1815dce1a74SFaisal Awada return phosphor::pmbus::createPMBus(i2cBus, ss.str()); 1825dce1a74SFaisal Awada } 1835dce1a74SFaisal Awada 1845dce1a74SFaisal Awada std::string readVPDValue(phosphor::pmbus::PMBusBase& pmbusIntf, 1855dce1a74SFaisal Awada const std::string& vpdName, 1865dce1a74SFaisal Awada const phosphor::pmbus::Type& type, 1875dce1a74SFaisal Awada const std::size_t& vpdSize) 1885dce1a74SFaisal Awada { 1895dce1a74SFaisal Awada std::string vpdValue; 1905dce1a74SFaisal Awada const std::regex illegalVPDRegex = 1915dce1a74SFaisal Awada std::regex("[^[:alnum:]]", std::regex::basic); 1925dce1a74SFaisal Awada 1935dce1a74SFaisal Awada try 1945dce1a74SFaisal Awada { 1955dce1a74SFaisal Awada vpdValue = pmbusIntf.readString(vpdName, type); 1965dce1a74SFaisal Awada } 1975dce1a74SFaisal Awada catch (const ReadFailure& e) 1985dce1a74SFaisal Awada { 1995dce1a74SFaisal Awada // Ignore the read failure, let pmbus code indicate failure. 2005dce1a74SFaisal Awada } 2015dce1a74SFaisal Awada 2025dce1a74SFaisal Awada if (vpdValue.size() != vpdSize) 2035dce1a74SFaisal Awada { 2045dce1a74SFaisal Awada log<level::INFO>( 2055dce1a74SFaisal Awada std::format(" {} resize needed. size: {}", vpdName, vpdValue.size()) 2065dce1a74SFaisal Awada .c_str()); 2075dce1a74SFaisal Awada vpdValue.resize(vpdSize, ' '); 2085dce1a74SFaisal Awada } 2095dce1a74SFaisal Awada 2105dce1a74SFaisal Awada // Replace any illegal values with space(s). 2115dce1a74SFaisal Awada std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(), 2125dce1a74SFaisal Awada illegalVPDRegex, " "); 2135dce1a74SFaisal Awada 2145dce1a74SFaisal Awada return vpdValue; 2155dce1a74SFaisal Awada } 2165dce1a74SFaisal Awada 2175dce1a74SFaisal Awada bool checkFileExists(const std::string& filePath) 2185dce1a74SFaisal Awada { 2195dce1a74SFaisal Awada try 2205dce1a74SFaisal Awada { 2215dce1a74SFaisal Awada return std::filesystem::exists(filePath); 2225dce1a74SFaisal Awada } 2235dce1a74SFaisal Awada catch (const std::exception& e) 2245dce1a74SFaisal Awada { 2255dce1a74SFaisal Awada log<level::ERR>(std::format("Unable to check for existence of {}: {}", 2265dce1a74SFaisal Awada filePath, e.what()) 2275dce1a74SFaisal Awada .c_str()); 2285dce1a74SFaisal Awada } 2295dce1a74SFaisal Awada return false; 2305dce1a74SFaisal Awada } 2315dce1a74SFaisal 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 { 2395dce1a74SFaisal 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 2545dce1a74SFaisal Awada std::string getVersion(sdbusplus::bus_t& bus, 2555dce1a74SFaisal Awada const std::string& psuInventoryPath) 2565dce1a74SFaisal Awada { 257*37c2612bSShawn McCarney std::string version; 2585dce1a74SFaisal Awada try 2595dce1a74SFaisal Awada { 2605dce1a74SFaisal Awada const auto& [i2cbus, i2caddr] = utils::getPsuI2c(bus, psuInventoryPath); 261*37c2612bSShawn McCarney auto pmbus = utils::getPmbusIntf(i2cbus, i2caddr); 262*37c2612bSShawn McCarney std::string name = "fw_version"; 263*37c2612bSShawn McCarney auto type = phosphor::pmbus::Type::HwmonDeviceDebug; 264*37c2612bSShawn McCarney version = pmbus->readString(name, type); 2655dce1a74SFaisal Awada } 2665dce1a74SFaisal Awada catch (const std::exception& e) 2675dce1a74SFaisal Awada { 2685dce1a74SFaisal Awada log<level::ERR>(std::format("Error: {}", e.what()).c_str()); 2695dce1a74SFaisal Awada } 270*37c2612bSShawn McCarney return version; 2715dce1a74SFaisal Awada } 2725dce1a74SFaisal Awada 273093b5917SLei YU std::string getLatest(const std::vector<std::string>& versions) 274093b5917SLei YU { 275093b5917SLei YU // TODO: when multiple PSU/Machines are supported, add configuration options 276093b5917SLei YU // to implement machine-specific logic. 277093b5917SLei YU // For now IBM AC Servers and Inspur FP5280G2 are supported. 278093b5917SLei YU // 279093b5917SLei YU // IBM AC servers' PSU version has two types: 280093b5917SLei YU // * XXXXYYYYZZZZ: XXXX is the primary version 281093b5917SLei YU // YYYY is the secondary version 282093b5917SLei YU // ZZZZ is the communication version 283093b5917SLei YU // 284093b5917SLei YU // * XXXXYYYY: XXXX is the primary version 285093b5917SLei YU // YYYY is the seconday version 286093b5917SLei YU // 287093b5917SLei YU // Inspur FP5280G2 PSU version is human readable text and a larger string 288093b5917SLei YU // means a newer version. 289093b5917SLei YU // 290093b5917SLei YU // So just compare by strings is OK for these cases 291093b5917SLei YU return utils::getLatestDefault(versions); 292093b5917SLei YU } 2930bf1b782SLei YU } // namespace version 294