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