xref: /openbmc/intel-ipmi-oem/src/appcommands.cpp (revision 49fbe6faeda465f4140ecd3af0046c89bbaaeac5)
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