xref: /openbmc/phosphor-power/tools/power-utils/version.cpp (revision 37c2612b4497f4065a810187e3f99cfaccca368c)
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