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