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 #include "utils.hpp"
23
24 #include <nlohmann/json.hpp>
25 #include <phosphor-logging/lg2.hpp>
26
27 #include <exception>
28 #include <tuple>
29
30 using json = nlohmann::json;
31
32 using namespace utils;
33 using namespace phosphor::power::util;
34
35 namespace version
36 {
37
38 namespace internal
39 {
40
41 // PsuInfo contains the device path, PMBus access type, and sysfs file name
42 using PsuVersionInfo =
43 std::tuple<std::string, phosphor::pmbus::Type, std::string>;
44
45 /**
46 * @brief Get PSU version information
47 *
48 * @param[in] psuInventoryPath - The PSU inventory path.
49 *
50 * @return tuple - device path, PMBus access type, and sysfs file name
51 */
getVersionInfo(const std::string & psuInventoryPath)52 PsuVersionInfo getVersionInfo(const std::string& psuInventoryPath)
53 {
54 auto data = loadJSONFromFile(PSU_JSON_PATH);
55
56 if (data == nullptr)
57 {
58 return {};
59 }
60
61 auto devices = data.find("psuDevices");
62 if (devices == data.end())
63 {
64 lg2::warning("Unable to find psuDevices");
65 return {};
66 }
67 auto devicePath = devices->find(psuInventoryPath);
68 if (devicePath == devices->end())
69 {
70 lg2::warning("Unable to find path for PSU PATH={PATH}", "PATH",
71 psuInventoryPath);
72 return {};
73 }
74
75 auto type = getPMBusAccessType(data);
76
77 std::string fileName;
78 for (const auto& fru : data["fruConfigs"])
79 {
80 if (fru.contains("propertyName") &&
81 (fru["propertyName"] == "Version") && fru.contains("fileName"))
82 {
83 fileName = fru["fileName"];
84 break;
85 }
86 }
87 if (fileName.empty())
88 {
89 lg2::warning("Unable to find Version file");
90 return {};
91 }
92 return std::make_tuple(*devicePath, type, fileName);
93 }
94
95 /**
96 * @brief Get the PSU version from sysfs.
97 *
98 * Obtain PSU information from the PSU JSON file.
99 *
100 * Throws an exception if an error occurs.
101 *
102 * @param[in] psuInventoryPath - PSU D-Bus inventory path
103 *
104 * @return PSU version, or "" if none found
105 */
getVersionJson(const std::string & psuInventoryPath)106 std::string getVersionJson(const std::string& psuInventoryPath)
107 {
108 // Get PSU device path, PMBus access type, and sysfs file name from JSON
109 const auto [devicePath, type, fileName] = getVersionInfo(psuInventoryPath);
110
111 // Read version from sysfs file
112 std::string version;
113 if (!devicePath.empty() && !fileName.empty())
114 {
115 phosphor::pmbus::PMBus pmbus(devicePath);
116 version = pmbus.readString(fileName, type);
117 }
118 return version;
119 }
120
121 /**
122 * @brief Get the PSU version from sysfs.
123 *
124 * Obtain PSU information from D-Bus.
125 *
126 * Throws an exception if an error occurs.
127 *
128 * @param[in] bus - D-Bus connection
129 * @param[in] psuInventoryPath - PSU D-Bus inventory path
130 *
131 * @return PSU version, or "" if none found
132 */
getVersionDbus(sdbusplus::bus_t & bus,const std::string & psuInventoryPath)133 std::string getVersionDbus(sdbusplus::bus_t& bus,
134 const std::string& psuInventoryPath)
135 {
136 // Get PSU I2C bus/address and create PMBus interface
137 const auto [i2cbus, i2caddr] = getPsuI2c(bus, psuInventoryPath);
138 auto pmbus = getPmbusIntf(i2cbus, i2caddr);
139
140 // Read version from sysfs file
141 std::string name = "fw_version";
142 auto type = phosphor::pmbus::Type::HwmonDeviceDebug;
143 std::string version = pmbus->readString(name, type);
144 return version;
145 }
146
147 /**
148 * @brief Get firmware latest version
149 *
150 * @param[in] versions - String of versions
151 *
152 * @return version - latest firmware level
153 */
getLatestDefault(const std::vector<std::string> & versions)154 std::string getLatestDefault(const std::vector<std::string>& versions)
155 {
156 std::string latest;
157 for (const auto& version : versions)
158 {
159 if (latest < version)
160 {
161 latest = version;
162 }
163 }
164 return latest;
165 }
166
167 } // namespace internal
168
getVersion(sdbusplus::bus_t & bus,const std::string & psuInventoryPath)169 std::string getVersion(sdbusplus::bus_t& bus,
170 const std::string& psuInventoryPath)
171 {
172 std::string version;
173 try
174 {
175 if (usePsuJsonFile())
176 {
177 // Obtain PSU information from JSON file
178 version = internal::getVersionJson(psuInventoryPath);
179 }
180 else
181 {
182 // Obtain PSU information from D-Bus
183 version = internal::getVersionDbus(bus, psuInventoryPath);
184 }
185 }
186 catch (const std::exception& e)
187 {
188 lg2::error("Error in getVersion: {ERROR}", "ERROR", e);
189 }
190 return version;
191 }
192
getLatest(const std::vector<std::string> & versions)193 std::string getLatest(const std::vector<std::string>& versions)
194 {
195 // TODO: when multiple PSU/Machines are supported, add configuration options
196 // to implement machine-specific logic.
197 // For now IBM AC Servers and Inspur FP5280G2 are supported.
198 //
199 // IBM AC servers' PSU version has two types:
200 // * XXXXYYYYZZZZ: XXXX is the primary version
201 // YYYY is the secondary version
202 // ZZZZ is the communication version
203 //
204 // * XXXXYYYY: XXXX is the primary version
205 // YYYY is the seconday version
206 //
207 // Inspur FP5280G2 PSU version is human readable text and a larger string
208 // means a newer version.
209 //
210 // So just compare by strings is OK for these cases
211 return internal::getLatestDefault(versions);
212 }
213
214 } // namespace version
215