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