1 /*
2 // Copyright (c) 2019 Intel 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 <byteswap.h>
17
18 #include <appcommands.hpp>
19 #include <ipmid/api.hpp>
20 #include <ipmid/utils.hpp>
21 #include <nlohmann/json.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <types.hpp>
24
25 #include <fstream>
26 #include <regex>
27
28 namespace ipmi
29 {
30
31 static void registerAPPFunctions() __attribute__((constructor));
32
33 static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC";
34 static constexpr const char* softwareVerIntf =
35 "xyz.openbmc_project.Software.Version";
36 static constexpr const char* softwareActivationIntf =
37 "xyz.openbmc_project.Software.Activation";
38 static constexpr const char* associationIntf =
39 "xyz.openbmc_project.Association";
40 static constexpr const char* softwareFunctionalPath =
41 "/xyz/openbmc_project/software/functional";
42
43 static constexpr const char* currentBmcStateProp = "CurrentBMCState";
44 static constexpr const char* bmcStateReadyStr =
45 "xyz.openbmc_project.State.BMC.BMCState.Ready";
46
47 static std::unique_ptr<sdbusplus::bus::match_t> bmcStateChangedSignal;
48 static uint8_t bmcDeviceBusy = true;
49
initBMCDeviceState(ipmi::Context::ptr ctx)50 int initBMCDeviceState(ipmi::Context::ptr ctx)
51 {
52 DbusObjectInfo objInfo;
53 boost::system::error_code ec =
54 ipmi::getDbusObject(ctx, bmcStateIntf, "/", "bmc0", objInfo);
55 if (ec)
56 {
57 phosphor::logging::log<phosphor::logging::level::ERR>(
58 "initBMCDeviceState: Failed to perform GetSubTree action",
59 phosphor::logging::entry("ERROR=%s", ec.message().c_str()),
60 phosphor::logging::entry("INTERFACE=%s", bmcStateIntf));
61 return -1;
62 }
63
64 std::string bmcState;
65 ec = ipmi::getDbusProperty(ctx, objInfo.second, objInfo.first, bmcStateIntf,
66 currentBmcStateProp, bmcState);
67 if (ec)
68 {
69 phosphor::logging::log<phosphor::logging::level::ERR>(
70 "initBMCDeviceState: Failed to get CurrentBMCState property",
71 phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
72 return -1;
73 }
74
75 bmcDeviceBusy = (bmcState != bmcStateReadyStr);
76
77 phosphor::logging::log<phosphor::logging::level::INFO>(
78 "BMC device state updated");
79
80 // BMC state may change runtime while doing firmware udpate.
81 // Register for property change signal to update state.
82 bmcStateChangedSignal = std::make_unique<sdbusplus::bus::match_t>(
83 *(ctx->bus),
84 sdbusplus::bus::match::rules::propertiesChanged(objInfo.first,
85 bmcStateIntf),
86 [](sdbusplus::message_t& msg) {
87 std::map<std::string, ipmi::DbusVariant> props;
88 std::vector<std::string> inVal;
89 std::string iface;
90 try
91 {
92 msg.read(iface, props, inVal);
93 }
94 catch (const std::exception& e)
95 {
96 phosphor::logging::log<phosphor::logging::level::ERR>(
97 "Exception caught in Get CurrentBMCState");
98 return;
99 }
100
101 auto it = props.find(currentBmcStateProp);
102 if (it != props.end())
103 {
104 std::string* state = std::get_if<std::string>(&it->second);
105 if (state)
106 {
107 bmcDeviceBusy = (*state != bmcStateReadyStr);
108 phosphor::logging::log<phosphor::logging::level::INFO>(
109 "BMC device state updated");
110 }
111 }
112 });
113
114 return 0;
115 }
116
117 /**
118 * @brief Returns the functional firmware version information.
119 *
120 * It reads the active firmware versions by checking functional
121 * endpoints association and matching the input version purpose string.
122 * ctx[in] - ipmi context.
123 * reqVersionPurpose[in] - Version purpose which need to be read.
124 * version[out] - Output Version string.
125 *
126 * @return Returns '0' on success and '-1' on failure.
127 *
128 */
getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,const std::string & reqVersionPurpose,std::string & version)129 int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,
130 const std::string& reqVersionPurpose,
131 std::string& version)
132 {
133 std::vector<std::string> activeEndPoints;
134 boost::system::error_code ec = ipmi::getDbusProperty(
135 ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf,
136 "endpoints", activeEndPoints);
137 if (ec)
138 {
139 phosphor::logging::log<phosphor::logging::level::ERR>(
140 "Failed to get Active firmware version endpoints.");
141 return -1;
142 }
143
144 for (auto& activeEndPoint : activeEndPoints)
145 {
146 std::string serviceName;
147 ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint,
148 serviceName);
149 if (ec)
150 {
151 phosphor::logging::log<phosphor::logging::level::ERR>(
152 "Failed to perform getService.",
153 phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str()));
154 continue;
155 }
156
157 PropertyMap propMap;
158 ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint,
159 softwareVerIntf, propMap);
160 if (ec)
161 {
162 phosphor::logging::log<phosphor::logging::level::ERR>(
163 "Failed to perform GetAll on Version interface.",
164 phosphor::logging::entry("SERVICE=%s", serviceName.c_str()),
165 phosphor::logging::entry("PATH=%s", activeEndPoint.c_str()));
166 continue;
167 }
168
169 std::string* purposeProp =
170 std::get_if<std::string>(&propMap["Purpose"]);
171 std::string* versionProp =
172 std::get_if<std::string>(&propMap["Version"]);
173 if (!purposeProp || !versionProp)
174 {
175 phosphor::logging::log<phosphor::logging::level::ERR>(
176 "Failed to get version or purpose property");
177 continue;
178 }
179
180 // Check for requested version information and return if found.
181 if (*purposeProp == reqVersionPurpose)
182 {
183 version = *versionProp;
184 return 0;
185 }
186 }
187
188 phosphor::logging::log<phosphor::logging::level::INFO>(
189 "Failed to find version information.",
190 phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str()));
191 return -1;
192 }
193
194 // Support both 2 solutions:
195 // 1.Current solution 2.7.0-dev-533-g14dc00e79-5e7d997
196 // openbmcTag 2.7.0-dev
197 // BuildNo 533
198 // openbmcHash 14dc00e79
199 // MetaHasg 5e7d997
200 //
201 // 2.New solution wht-0.2-3-gab3500-38384ac or wht-2000.2.3-gab3500-38384ac
202 // IdStr wht
203 // Major 0
204 // Minor 2
205 // buildNo 3
206 // MetaHash ab3500
207 // openbmcHash 38384ac
convertIntelVersion(std::string & s)208 std::optional<MetaRevision> convertIntelVersion(std::string& s)
209 {
210 std::smatch results;
211 MetaRevision rev;
212 std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)");
213 constexpr size_t matchedPhosphor = 6;
214 if (std::regex_match(s, results, pattern1))
215 {
216 if (results.size() == matchedPhosphor)
217 {
218 rev.platform = "whtref";
219 rev.major = static_cast<uint8_t>(std::stoi(results[1]));
220 rev.minor = static_cast<uint8_t>(std::stoi(results[2]));
221 rev.buildNo = static_cast<uint32_t>(std::stoi(results[3]));
222 rev.openbmcHash = results[4];
223 rev.metaHash = results[5];
224 std::string versionString =
225 rev.platform + ":" + std::to_string(rev.major) + ":" +
226 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
227 ":" + rev.openbmcHash + ":" + rev.metaHash;
228 phosphor::logging::log<phosphor::logging::level::INFO>(
229 "Get BMC version",
230 phosphor::logging::entry("VERSION=%s", versionString.c_str()));
231 return rev;
232 }
233 }
234 constexpr size_t matchedIntel = 7;
235 std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)[-.](\\d+?)-g(\\w+?)-(\\w+?)");
236 if (std::regex_match(s, results, pattern2))
237 {
238 if (results.size() == matchedIntel)
239 {
240 rev.platform = results[1];
241 std::string majorVer = results[2].str();
242 // Take only the last two digits of the major version
243 rev.major = static_cast<uint8_t>(
244 std::stoi(majorVer.substr(majorVer.size() - 2)));
245 rev.minor = static_cast<uint8_t>(std::stoi(results[3]));
246 rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));
247 rev.openbmcHash = results[6];
248 rev.metaHash = results[5];
249 std::string versionString =
250 rev.platform + ":" + std::to_string(rev.major) + ":" +
251 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
252 ":" + rev.openbmcHash + ":" + rev.metaHash;
253 phosphor::logging::log<phosphor::logging::level::INFO>(
254 "Get BMC version",
255 phosphor::logging::entry("VERSION=%s", versionString.c_str()));
256 return rev;
257 }
258 }
259
260 return std::nullopt;
261 }
262
263 RspType<uint8_t, // Device ID
264 uint8_t, // Device Revision
265 uint7_t, // Firmware Revision Major
266 bool, // Device available(0=NormalMode,1=DeviceFirmware)
267 uint8_t, // Firmware Revision minor
268 uint8_t, // IPMI version
269 uint8_t, // Additional device support
270 uint24_t, // MFG ID
271 uint16_t, // Product ID
272 uint32_t // AUX info
273 >
ipmiAppGetDeviceId(ipmi::Context::ptr ctx)274 ipmiAppGetDeviceId(ipmi::Context::ptr ctx)
275 {
276 static struct
277 {
278 uint8_t id;
279 uint8_t revision;
280 uint7_t fwMajor;
281 bool devBusy;
282 uint8_t fwMinor;
283 uint8_t ipmiVer = 2;
284 uint8_t addnDevSupport;
285 uint24_t manufId;
286 uint16_t prodId;
287 uint32_t aux;
288 } devId;
289 static bool fwVerInitialized = false;
290 static bool devIdInitialized = false;
291 static bool bmcStateInitialized = false;
292 const char* filename = "/usr/share/ipmi-providers/dev_id.json";
293 const char* prodIdFilename = "/var/cache/private/prodID";
294 if (!fwVerInitialized)
295 {
296 std::string versionString;
297 if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC,
298 versionString))
299 {
300 std::optional<MetaRevision> rev =
301 convertIntelVersion(versionString);
302 if (rev.has_value())
303 {
304 MetaRevision revision = rev.value();
305 devId.fwMajor = static_cast<uint7_t>(revision.major);
306
307 revision.minor = (revision.minor > 99 ? 99 : revision.minor);
308 devId.fwMinor = revision.minor % 10 +
309 (revision.minor / 10) * 16;
310 try
311 {
312 uint32_t hash = std::stoul(revision.metaHash, 0, 16);
313 hash = bswap_32(hash);
314 devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
315 fwVerInitialized = true;
316 }
317 catch (const std::exception& e)
318 {
319 phosphor::logging::log<phosphor::logging::level::ERR>(
320 "Failed to convert git hash",
321 phosphor::logging::entry("ERROR=%s", e.what()));
322 }
323 }
324 }
325 }
326
327 if (!devIdInitialized)
328 {
329 std::ifstream devIdFile(filename);
330 if (devIdFile.is_open())
331 {
332 auto data = nlohmann::json::parse(devIdFile, nullptr, false);
333 if (!data.is_discarded())
334 {
335 devId.id = data.value("id", 0);
336 devId.revision = data.value("revision", 0);
337 devId.addnDevSupport = data.value("addn_dev_support", 0);
338 devId.manufId = data.value("manuf_id", 0);
339 }
340 else
341 {
342 phosphor::logging::log<phosphor::logging::level::ERR>(
343 "Device ID JSON parser failure");
344 return ipmi::responseUnspecifiedError();
345 }
346 }
347 else
348 {
349 phosphor::logging::log<phosphor::logging::level::ERR>(
350 "Device ID file not found");
351 return ipmi::responseUnspecifiedError();
352 }
353
354 // Determine the Product ID. Using the DBus system is painfully slow at
355 // boot time. Avoid using DBus to get the Product ID. The Product ID is
356 // stored in a non-volatile file now. The /usr/bin/checkFru.sh script,
357 // run during bootup, will populate the productIdFile.
358 std::fstream prodIdFile(prodIdFilename);
359 if (prodIdFile.is_open())
360 {
361 std::string id = "0x00";
362 char* end;
363 prodIdFile.getline(&id[0], id.size() + 1);
364 devId.prodId = std::strtol(&id[0], &end, 0);
365 devIdInitialized = true;
366 }
367 else
368 {
369 // For any exception send out platform id as 0,
370 // and make sure to re-query the device id.
371 devIdInitialized = false;
372 devId.prodId = 0;
373 }
374 }
375
376 if (!bmcStateInitialized)
377 {
378 if (!initBMCDeviceState(ctx))
379 {
380 bmcStateInitialized = true;
381 }
382 }
383
384 return ipmi::responseSuccess(
385 devId.id, devId.revision, devId.fwMajor, bmcDeviceBusy, devId.fwMinor,
386 devId.ipmiVer, devId.addnDevSupport, devId.manufId, devId.prodId,
387 devId.aux);
388 }
389
registerAPPFunctions(void)390 static void registerAPPFunctions(void)
391 {
392 // <Get Device ID>
393 registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
394 ipmiAppGetDeviceId);
395 }
396
397 } // namespace ipmi
398