1 /**
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>
24 #include <xyz/openbmc_project/Common/Device/error.hpp>
25
26 #include <exception>
27 #include <iostream>
28 #include <regex>
29 #include <stdexcept>
30 #include <tuple>
31
32 using json = nlohmann::json;
33
34 using namespace phosphor::logging;
35
36 using namespace phosphor::power::util;
37 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
38
39 namespace version
40 {
41 namespace utils
42 {
43 constexpr auto IBMCFFPSInterface =
44 "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
45 constexpr auto i2cBusProp = "I2CBus";
46 constexpr auto i2cAddressProp = "I2CAddress";
47
getVersionInfo(const std::string & psuInventoryPath)48 PsuVersionInfo 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 {
78 versionStr = fru["fileName"].get<std::string>();
79 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 }
89
90 // A default implemention compare the string itself
getLatestDefault(const std::vector<std::string> & versions)91 std::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
getPsuI2c(sdbusplus::bus_t & bus,const std::string & psuInventoryPath)104 PsuI2cInfo getPsuI2c(sdbusplus::bus_t& bus, const std::string& psuInventoryPath)
105 {
106 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
176 std::unique_ptr<phosphor::pmbus::PMBusBase>
getPmbusIntf(std::uint64_t i2cBus,std::uint64_t i2cAddr)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
readVPDValue(phosphor::pmbus::PMBusBase & pmbusIntf,const std::string & vpdName,const phosphor::pmbus::Type & type,const std::size_t & vpdSize)184 std::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
checkFileExists(const std::string & filePath)217 bool 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
232
getVersion(const std::string & psuInventoryPath)233 std::string getVersion(const std::string& psuInventoryPath)
234 {
235 const auto& [devicePath, type, versionStr] =
236 utils::getVersionInfo(psuInventoryPath);
237 if (devicePath.empty() || versionStr.empty())
238 {
239 return "";
240 }
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
getVersion(sdbusplus::bus_t & bus,const std::string & psuInventoryPath)254 std::string getVersion(sdbusplus::bus_t& bus,
255 const std::string& psuInventoryPath)
256 {
257 std::string version;
258 try
259 {
260 const auto& [i2cbus, i2caddr] = utils::getPsuI2c(bus, psuInventoryPath);
261 auto pmbus = utils::getPmbusIntf(i2cbus, i2caddr);
262 std::string name = "fw_version";
263 auto type = phosphor::pmbus::Type::HwmonDeviceDebug;
264 version = pmbus->readString(name, type);
265 }
266 catch (const std::exception& e)
267 {
268 log<level::ERR>(std::format("Error: {}", e.what()).c_str());
269 }
270 return version;
271 }
272
getLatest(const std::vector<std::string> & versions)273 std::string getLatest(const std::vector<std::string>& versions)
274 {
275 // TODO: when multiple PSU/Machines are supported, add configuration options
276 // to implement machine-specific logic.
277 // For now IBM AC Servers and Inspur FP5280G2 are supported.
278 //
279 // IBM AC servers' PSU version has two types:
280 // * XXXXYYYYZZZZ: XXXX is the primary version
281 // YYYY is the secondary version
282 // ZZZZ is the communication version
283 //
284 // * XXXXYYYY: XXXX is the primary version
285 // YYYY is the seconday version
286 //
287 // Inspur FP5280G2 PSU version is human readable text and a larger string
288 // means a newer version.
289 //
290 // So just compare by strings is OK for these cases
291 return utils::getLatestDefault(versions);
292 }
293 } // namespace version
294