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