xref: /openbmc/phosphor-host-ipmid/oem/example/apphandler.cpp (revision d0b99b1137ee811ca1c4a2d6f24f7caab8f2f4c0)
1 #include "config.h"
2 
3 #include <ipmid/api.hpp>
4 #include <ipmid/types.hpp>
5 #include <ipmid/utils.hpp>
6 #include <nlohmann/json.hpp>
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <phosphor-logging/lg2.hpp>
9 #include <sdbusplus/message/types.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 #include <xyz/openbmc_project/Software/Activation/server.hpp>
12 #include <xyz/openbmc_project/Software/Version/server.hpp>
13 #include <xyz/openbmc_project/State/BMC/server.hpp>
14 
15 #include <algorithm>
16 #include <array>
17 #include <charconv>
18 #include <cstddef>
19 #include <cstdint>
20 #include <filesystem>
21 #include <fstream>
22 #include <memory>
23 #include <regex>
24 #include <string>
25 #include <string_view>
26 #include <tuple>
27 #include <vector>
28 
29 constexpr auto bmcStateInterface = "xyz.openbmc_project.State.BMC";
30 constexpr auto bmcStateProperty = "CurrentBMCState";
31 
32 static constexpr auto redundancyIntf =
33     "xyz.openbmc_project.Software.RedundancyPriority";
34 static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
35 static constexpr auto activationIntf =
36     "xyz.openbmc_project.Software.Activation";
37 static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
38 
39 void registerNetFnAppFunctions() __attribute__((constructor));
40 
41 using namespace phosphor::logging;
42 using namespace sdbusplus::error::xyz::openbmc_project::common;
43 using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
44 using Activation =
45     sdbusplus::server::xyz::openbmc_project::software::Activation;
46 using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
47 namespace fs = std::filesystem;
48 
49 /**
50  * @brief Returns the Version info from primary s/w object
51  *
52  * Get the Version info from the active s/w object which is having high
53  * "Priority" value(a smaller number is a higher priority) and "Purpose"
54  * is "BMC" from the list of all s/w objects those are implementing
55  * RedundancyPriority interface from the given softwareRoot path.
56  *
57  * @return On success returns the Version info from primary s/w object.
58  *
59  */
getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)60 std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)
61 {
62     std::string revision{};
63     ipmi::ObjectTree objectTree;
64     try
65     {
66         objectTree =
67             ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf);
68     }
69     catch (const sdbusplus::exception_t& e)
70     {
71         lg2::error("Failed to fetch redundancy object from dbus, "
72                    "interface: {INTERFACE},  error: {ERROR}",
73                    "INTERFACE", redundancyIntf, "ERROR", e);
74         elog<InternalFailure>();
75     }
76 
77     auto objectFound = false;
78     for (auto& softObject : objectTree)
79     {
80         auto service =
81             ipmi::getService(*ctx->bus, redundancyIntf, softObject.first);
82         auto objValueTree =
83             ipmi::getManagedObjects(*ctx->bus, service, softwareRoot);
84 
85         auto minPriority = 0xFF;
86         for (const auto& objIter : objValueTree)
87         {
88             try
89             {
90                 auto& intfMap = objIter.second;
91                 auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
92                 auto& versionProps = intfMap.at(versionIntf);
93                 auto& activationProps = intfMap.at(activationIntf);
94                 auto priority =
95                     std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
96                 auto purpose =
97                     std::get<std::string>(versionProps.at("Purpose"));
98                 auto activation =
99                     std::get<std::string>(activationProps.at("Activation"));
100                 auto version =
101                     std::get<std::string>(versionProps.at("Version"));
102                 if ((Version::convertVersionPurposeFromString(purpose) ==
103                      Version::VersionPurpose::BMC) &&
104                     (Activation::convertActivationsFromString(activation) ==
105                      Activation::Activations::Active))
106                 {
107                     if (priority < minPriority)
108                     {
109                         minPriority = priority;
110                         objectFound = true;
111                         revision = std::move(version);
112                     }
113                 }
114             }
115             catch (const std::exception& e)
116             {
117                 lg2::error("error message: {ERROR}", "ERROR", e);
118             }
119         }
120     }
121 
122     if (!objectFound)
123     {
124         lg2::error("Could not found an BMC software Object");
125         elog<InternalFailure>();
126     }
127 
128     return revision;
129 }
130 
getCurrentBmcStateWithFallback(ipmi::Context::ptr & ctx,const bool fallbackAvailability)131 bool getCurrentBmcStateWithFallback(ipmi::Context::ptr& ctx,
132                                     const bool fallbackAvailability)
133 {
134     // Get the Inventory object implementing the BMC interface
135     ipmi::DbusObjectInfo bmcObject{};
136     boost::system::error_code ec =
137         ipmi::getDbusObject(ctx, bmcStateInterface, bmcObject);
138     std::string bmcState{};
139     if (ec.value())
140     {
141         return fallbackAvailability;
142     }
143     ec = ipmi::getDbusProperty(ctx, bmcObject.second, bmcObject.first,
144                                bmcStateInterface, bmcStateProperty, bmcState);
145     if (!ec.value())
146     {
147         return fallbackAvailability;
148     }
149     return BMC::convertBMCStateFromString(bmcState) == BMC::BMCState::Ready;
150 }
151 
152 typedef struct
153 {
154     char major;
155     char minor;
156     uint8_t aux[4];
157 } Revision;
158 
159 /* Use regular expression searching matched pattern X.Y, and convert it to  */
160 /* Major (X) and Minor (Y) version.                                         */
161 /* Example:                                                                 */
162 /* version = 2.14.0-dev                                                     */
163 /*           ^ ^                                                            */
164 /*           | |---------------- Minor                                      */
165 /*           |------------------ Major                                      */
166 /*                                                                          */
167 /* Default regex string only tries to match Major and Minor version.        */
168 /*                                                                          */
169 /* To match more firmware version info, platforms need to define it own     */
170 /* regex string to match more strings, and assign correct mapping index in  */
171 /* matches array.                                                           */
172 /*                                                                          */
173 /* matches[0]: matched index for major ver                                  */
174 /* matches[1]: matched index for minor ver                                  */
175 /* matches[2]: matched index for aux[0] (set 0 to skip)                     */
176 /* matches[3]: matched index for aux[1] (set 0 to skip)                     */
177 /* matches[4]: matched index for aux[2] (set 0 to skip)                     */
178 /* matches[5]: matched index for aux[3] (set 0 to skip)                     */
179 /* Example:                                                                 */
180 /* regex = "([\d]+).([\d]+).([\d]+)-dev-([\d]+)-g([0-9a-fA-F]{2})           */
181 /*          ([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})"               */
182 /* matches = {1,2,5,6,7,8}                                                  */
183 /* version = 2.14.0-dev-750-g37a7c5ad1-dirty                                */
184 /*           ^ ^  ^     ^    ^ ^ ^ ^                                        */
185 /*           | |  |     |    | | | |                                        */
186 /*           | |  |     |    | | | |-- Aux byte 3 (0xAD), index 8           */
187 /*           | |  |     |    | | |---- Aux byte 2 (0xC5), index 7           */
188 /*           | |  |     |    | |------ Aux byte 1 (0xA7), index 6           */
189 /*           | |  |     |    |-------- Aux byte 0 (0x37), index 5           */
190 /*           | |  |     |------------- Not used, index 4                    */
191 /*           | |  |------------------- Not used, index 3                    */
192 /*           | |---------------------- Minor (14), index 2                  */
193 /*           |------------------------ Major (2), index 1                   */
convertVersion(std::string s,Revision & rev)194 int convertVersion(std::string s, Revision& rev)
195 {
196     static const std::vector<size_t> matches = {
197         MAJOR_MATCH_INDEX, MINOR_MATCH_INDEX, AUX_0_MATCH_INDEX,
198         AUX_1_MATCH_INDEX, AUX_2_MATCH_INDEX, AUX_3_MATCH_INDEX};
199     std::regex fw_regex(FW_VER_REGEX);
200     std::smatch m;
201     Revision r = {0};
202     size_t val;
203 
204     if (std::regex_search(s, m, fw_regex))
205     {
206         if (m.size() < *std::max_element(matches.begin(), matches.end()))
207         { // max index higher than match count
208             return -1;
209         }
210 
211         // convert major
212         {
213             std::string str = m[matches[0]].str();
214             const auto& [ptr, ec] =
215                 std::from_chars(str.data(), str.data() + str.size(), val);
216             if (ec != std::errc() || ptr != str.data() + str.size())
217             { // failed to convert major string
218                 return -1;
219             }
220 
221             if (val >= 2000)
222             { // For the platforms use year as major version, it would expect to
223               // have major version between 0 - 99. If the major version is
224               // greater than or equal to 2000, it is treated as a year and
225               // converted to 0 - 99.
226                 r.major = val % 100;
227             }
228             else
229             {
230                 r.major = val & 0x7F;
231             }
232         }
233 
234         // convert minor
235         {
236             std::string str = m[matches[1]].str();
237             const auto& [ptr, ec] =
238                 std::from_chars(str.data(), str.data() + str.size(), val);
239             if (ec != std::errc() || ptr != str.data() + str.size())
240             { // failed to convert minor string
241                 return -1;
242             }
243             r.minor = val & 0xFF;
244         }
245 
246         // convert aux bytes
247         {
248             size_t i;
249             for (i = 0; i < 4; i++)
250             {
251                 if (matches[i + 2] == 0)
252                 {
253                     continue;
254                 }
255 
256                 std::string str = m[matches[i + 2]].str();
257                 const char* cstr = str.c_str();
258                 auto [ptr,
259                       ec] = std::from_chars(cstr, cstr + str.size(), val, 16);
260                 if (ec != std::errc() || ptr != cstr + str.size())
261                 { // failed to convert aux byte string
262                     break;
263                 }
264 
265                 r.aux[i] = val & 0xFF;
266             }
267 
268             if (i != 4)
269             { // something wrong durign converting aux bytes
270                 return -1;
271             }
272         }
273 
274         // all matched
275         rev = r;
276         return 0;
277     }
278 
279     return -1;
280 }
281 
282 /* @brief: Implement the Get Device ID IPMI command per the IPMI spec
283  *  @param[in] ctx - shared_ptr to an IPMI context struct
284  *
285  *  @returns IPMI completion code plus response data
286  *   - Device ID (manufacturer defined)
287  *   - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit]
288  *   - FW revision major[7 bits] (binary encoded); available[1 bit]
289  *   - FW Revision minor (BCD encoded)
290  *   - IPMI version (0x02 for IPMI 2.0)
291  *   - device support (bitfield of supported options)
292  *   - MFG IANA ID (3 bytes)
293  *   - product ID (2 bytes)
294  *   - AUX info (4 bytes)
295  */
296 ipmi::RspType<uint8_t,  // Device ID
297               uint8_t,  // Device Revision
298               uint8_t,  // Firmware Revision Major
299               uint8_t,  // Firmware Revision minor
300               uint8_t,  // IPMI version
301               uint8_t,  // Additional device support
302               uint24_t, // MFG ID
303               uint16_t, // Product ID
304               uint32_t  // AUX info
305               >
ipmiAppGetDeviceId(ipmi::Context::ptr ctx)306     ipmiAppGetDeviceId([[maybe_unused]] ipmi::Context::ptr ctx)
307 {
308     static struct
309     {
310         uint8_t id;
311         uint8_t revision;
312         uint8_t fw[2];
313         uint8_t ipmiVer;
314         uint8_t addnDevSupport;
315         uint24_t manufId;
316         uint16_t prodId;
317         uint32_t aux;
318     } devId;
319     static bool dev_id_initialized = false;
320     static bool defaultActivationSetting = true;
321     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
322     constexpr auto ipmiDevIdStateShift = 7;
323     constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
324 
325     static bool haveBMCVersion = false;
326     if (!haveBMCVersion || !dev_id_initialized)
327     {
328         int r = -1;
329         Revision rev = {0, 0, {0, 0, 0, 0}};
330         try
331         {
332             auto version = getActiveSoftwareVersionInfo(ctx);
333             r = convertVersion(version, rev);
334         }
335         catch (const std::exception& e)
336         {
337             lg2::error("error message: {ERROR}", "ERROR", e);
338         }
339 
340         if (r >= 0)
341         {
342             // bit7 identifies if the device is available
343             // 0=normal operation
344             // 1=device firmware, SDR update,
345             // or self-initialization in progress.
346             // The availability may change in run time, so mask here
347             // and initialize later.
348             devId.fw[0] = rev.major & ipmiDevIdFw1Mask;
349 
350             rev.minor = (rev.minor > 99 ? 99 : rev.minor);
351             devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
352             std::memcpy(&devId.aux, rev.aux, sizeof(rev.aux));
353             haveBMCVersion = true;
354         }
355     }
356     if (!dev_id_initialized)
357     {
358         // IPMI Spec version 2.0
359         devId.ipmiVer = 2;
360 
361         std::ifstream devIdFile(filename);
362         if (devIdFile.is_open())
363         {
364             auto data = nlohmann::json::parse(devIdFile, nullptr, false);
365             if (!data.is_discarded())
366             {
367                 devId.id = data.value("id", 0);
368                 devId.revision = data.value("revision", 0);
369                 devId.addnDevSupport = data.value("addn_dev_support", 0);
370                 devId.manufId = data.value("manuf_id", 0);
371                 devId.prodId = data.value("prod_id", 0);
372                 if (!(AUX_0_MATCH_INDEX || AUX_1_MATCH_INDEX ||
373                       AUX_2_MATCH_INDEX || AUX_3_MATCH_INDEX))
374                 {
375                     devId.aux = data.value("aux", 0);
376                 }
377 
378                 if (data.contains("firmware_revision"))
379                 {
380                     const auto& firmwareRevision = data.at("firmware_revision");
381                     if (firmwareRevision.contains("major"))
382                     {
383                         firmwareRevision.at("major").get_to(devId.fw[0]);
384                     }
385                     if (firmwareRevision.contains("minor"))
386                     {
387                         firmwareRevision.at("minor").get_to(devId.fw[1]);
388                     }
389                 }
390 
391                 // Set the availablitity of the BMC.
392                 defaultActivationSetting = data.value("availability", true);
393 
394                 // Don't read the file every time if successful
395                 dev_id_initialized = true;
396             }
397             else
398             {
399                 lg2::error("Device ID JSON parser failure");
400                 return ipmi::responseUnspecifiedError();
401             }
402         }
403         else
404         {
405             lg2::error("Device ID file not found");
406             return ipmi::responseUnspecifiedError();
407         }
408     }
409 
410     // Set availability to the actual current BMC state
411     devId.fw[0] &= ipmiDevIdFw1Mask;
412     if (!getCurrentBmcStateWithFallback(ctx, defaultActivationSetting))
413     {
414         devId.fw[0] |= (1 << ipmiDevIdStateShift);
415     }
416 
417     return ipmi::responseSuccess(
418         devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
419         devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
420 }
421 
registerNetFnAppFunctions()422 void registerNetFnAppFunctions()
423 {
424     // OEM libraries should use ipmi::prioOemBase to override default
425     // implementation of IPMI commands that use ipmi::prioOpenBmcBase
426 
427     // <Get Device ID>
428     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,
429                           ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,
430                           ipmiAppGetDeviceId);
431 }
432