blob: 19873423ff8c1b989eddeefa2e1594bfa3284eb1 [file] [log] [blame]
Lei YU0bf1b782019-08-29 16:02:30 +08001/**
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>
Faisal Awada5dce1a72024-08-19 15:51:44 -050024#include <xyz/openbmc_project/Common/Device/error.hpp>
Brandon Wymand1bc4ce2019-12-13 14:20:34 -060025
Faisal Awada5dce1a72024-08-19 15:51:44 -050026#include <exception>
27#include <iostream>
28#include <regex>
29#include <stdexcept>
Lei YU0bf1b782019-08-29 16:02:30 +080030#include <tuple>
31
32using json = nlohmann::json;
33
34using namespace phosphor::logging;
35
Faisal Awada5dce1a72024-08-19 15:51:44 -050036using namespace phosphor::power::util;
37using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
Lei YU0bf1b782019-08-29 16:02:30 +080038
Faisal Awada5dce1a72024-08-19 15:51:44 -050039namespace version
40{
Lei YU0bf1b782019-08-29 16:02:30 +080041namespace utils
42{
Faisal Awada5dce1a72024-08-19 15:51:44 -050043constexpr auto IBMCFFPSInterface =
44 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
45constexpr auto i2cBusProp = "I2CBus";
46constexpr auto i2cAddressProp = "I2CAddress";
47
Lei YU0bf1b782019-08-29 16:02:30 +080048PsuVersionInfo 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 {
Matt Spinlerc0b9e9e2023-07-17 14:09:58 -050078 versionStr = fru["fileName"].get<std::string>();
Lei YU0bf1b782019-08-29 16:02:30 +080079 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}
Lei YU093b5912019-10-22 15:28:51 +080089
90// A default implemention compare the string itself
91std::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
Faisal Awada5dce1a72024-08-19 15:51:44 -0500104PsuI2cInfo getPsuI2c(sdbusplus::bus_t& bus, const std::string& psuInventoryPath)
Lei YU0bf1b782019-08-29 16:02:30 +0800105{
Faisal Awada5dce1a72024-08-19 15:51:44 -0500106 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
176std::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
184std::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
217bool 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
Lei YU0bf1b782019-08-29 16:02:30 +0800232
233std::string getVersion(const std::string& psuInventoryPath)
234{
Patrick Williamsf5402192024-08-16 15:20:53 -0400235 const auto& [devicePath, type, versionStr] =
236 utils::getVersionInfo(psuInventoryPath);
Lei YU0bf1b782019-08-29 16:02:30 +0800237 if (devicePath.empty() || versionStr.empty())
238 {
Faisal Awada5dce1a72024-08-19 15:51:44 -0500239 return "";
Lei YU0bf1b782019-08-29 16:02:30 +0800240 }
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
Faisal Awada5dce1a72024-08-19 15:51:44 -0500254std::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
Lei YU093b5912019-10-22 15:28:51 +0800284std::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}
Lei YU0bf1b782019-08-29 16:02:30 +0800304} // namespace version