xref: /openbmc/intel-ipmi-oem/src/appcommands.cpp (revision 3c8af65773238144b8f04fab529e154e7d0fbbee)
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 <appcommands.hpp>
19 #include <fstream>
20 #include <ipmid/api.hpp>
21 #include <ipmid/utils.hpp>
22 #include <nlohmann/json.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <regex>
25 #include <xyz/openbmc_project/Software/Activation/server.hpp>
26 #include <xyz/openbmc_project/Software/Version/server.hpp>
27 #include <xyz/openbmc_project/State/BMC/server.hpp>
28 
29 namespace ipmi
30 {
31 
32 static void registerAPPFunctions() __attribute__((constructor));
33 
34 namespace Log = phosphor::logging;
35 namespace Error = sdbusplus::xyz::openbmc_project::Common::Error;
36 using Version = sdbusplus::xyz::openbmc_project::Software::server::Version;
37 using Activation =
38     sdbusplus::xyz::openbmc_project::Software::server::Activation;
39 using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC;
40 
41 constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
42 constexpr auto bmc_state_property = "CurrentBMCState";
43 
44 static constexpr auto redundancyIntf =
45     "xyz.openbmc_project.Software.RedundancyPriority";
46 static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
47 static constexpr auto activationIntf =
48     "xyz.openbmc_project.Software.Activation";
49 static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
50 
51 bool getCurrentBmcState()
52 {
53     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
54 
55     // Get the Inventory object implementing the BMC interface
56     ipmi::DbusObjectInfo bmcObject =
57         ipmi::getDbusObject(bus, bmc_state_interface);
58     auto variant =
59         ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
60                               bmc_state_interface, bmc_state_property);
61 
62     return std::holds_alternative<std::string>(variant) &&
63            BMC::convertBMCStateFromString(std::get<std::string>(variant)) ==
64                BMC::BMCState::Ready;
65 }
66 
67 bool getCurrentBmcStateWithFallback(const bool fallbackAvailability)
68 {
69     try
70     {
71         return getCurrentBmcState();
72     }
73     catch (...)
74     {
75         // Nothing provided the BMC interface, therefore return whatever was
76         // configured as the default.
77         return fallbackAvailability;
78     }
79 }
80 
81 /**
82  * @brief Returns the Version info from primary software object
83  *
84  * Get the Version info from the active s/w object which is having high
85  * "Priority" value(a smaller number is a higher priority) and "Purpose"
86  * is "BMC" from the list of all s/w objects those are implementing
87  * RedundancyPriority interface from the given softwareRoot path.
88  *
89  * @return On success returns the Version info from primary software object.
90  *
91  */
92 std::string getActiveSoftwareVersionInfo()
93 {
94     auto busp = getSdBus();
95 
96     std::string revision{};
97     ipmi::ObjectTree objectTree;
98     try
99     {
100         objectTree =
101             ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf);
102     }
103     catch (sdbusplus::exception::SdBusError& e)
104     {
105         Log::log<Log::level::ERR>("Failed to fetch redundancy object from dbus",
106                                   Log::entry("INTERFACE=%s", redundancyIntf),
107                                   Log::entry("ERRMSG=%s", e.what()));
108     }
109 
110     auto objectFound = false;
111     for (auto& softObject : objectTree)
112     {
113         auto service =
114             ipmi::getService(*busp, redundancyIntf, softObject.first);
115         auto objValueTree =
116             ipmi::getManagedObjects(*busp, service, softwareRoot);
117 
118         auto minPriority = 0xFF;
119         for (const auto& objIter : objValueTree)
120         {
121             try
122             {
123                 auto& intfMap = objIter.second;
124                 auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
125                 auto& versionProps = intfMap.at(versionIntf);
126                 auto& activationProps = intfMap.at(activationIntf);
127                 auto priority =
128                     std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
129                 auto purpose =
130                     std::get<std::string>(versionProps.at("Purpose"));
131                 auto activation =
132                     std::get<std::string>(activationProps.at("Activation"));
133                 auto version =
134                     std::get<std::string>(versionProps.at("Version"));
135                 if ((Version::convertVersionPurposeFromString(purpose) ==
136                      Version::VersionPurpose::BMC) &&
137                     (Activation::convertActivationsFromString(activation) ==
138                      Activation::Activations::Active))
139                 {
140                     if (priority < minPriority)
141                     {
142                         minPriority = priority;
143                         objectFound = true;
144                         revision = std::move(version);
145                     }
146                 }
147             }
148             catch (const std::exception& e)
149             {
150                 Log::log<Log::level::ERR>(e.what());
151             }
152         }
153     }
154 
155     if (!objectFound)
156     {
157         Log::log<Log::level::ERR>("Could not find an BMC software object");
158     }
159 
160     return revision;
161 }
162 
163 // Support both 2 solutions:
164 // 1.Current solution  2.7.0-dev-533-g14dc00e79-5e7d997
165 //   openbmcTag  2.7.0-dev
166 //   BuildNo     533
167 //   openbmcHash 14dc00e79
168 //   MetaHasg    5e7d997
169 //
170 // 2.New solution  wht-0.2-3-gab3500-38384ac
171 //   IdStr        wht
172 //   Major        0
173 //   Minor        2
174 //   buildNo      3
175 //   MetaHash     ab3500
176 //   openbmcHash  38384ac
177 std::optional<MetaRevision> convertIntelVersion(std::string& s)
178 {
179     std::smatch results;
180     MetaRevision rev;
181     std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)");
182     constexpr size_t matchedPhosphor = 6;
183     if (std::regex_match(s, results, pattern1))
184     {
185         if (results.size() == matchedPhosphor)
186         {
187             rev.platform = "whtref";
188             rev.major = static_cast<uint8_t>(std::stoi(results[1]));
189             rev.minor = static_cast<uint8_t>(std::stoi(results[2]));
190             rev.buildNo = static_cast<uint32_t>(std::stoi(results[3]));
191             rev.openbmcHash = results[4];
192             rev.metaHash = results[5];
193             std::string versionString =
194                 rev.platform + ":" + std::to_string(rev.major) + ":" +
195                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
196                 ":" + rev.openbmcHash + ":" + rev.metaHash;
197             Log::log<Log::level::INFO>(
198                 "Get BMC version",
199                 Log::entry("VERSION=%s", versionString.c_str()));
200             return rev;
201         }
202     }
203     constexpr size_t matchedIntel = 7;
204     std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)-(\\d+?)-g(\\w+?)-(\\w+?)");
205     if (std::regex_match(s, results, pattern2))
206     {
207         if (results.size() == matchedIntel)
208         {
209             rev.platform = results[1];
210             rev.major = static_cast<uint8_t>(std::stoi(results[2]));
211             rev.minor = static_cast<uint8_t>(std::stoi(results[3]));
212             rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));
213             rev.openbmcHash = results[6];
214             rev.metaHash = results[5];
215             std::string versionString =
216                 rev.platform + ":" + std::to_string(rev.major) + ":" +
217                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
218                 ":" + rev.openbmcHash + ":" + rev.metaHash;
219             Log::log<Log::level::INFO>(
220                 "Get BMC version",
221                 Log::entry("VERSION=%s", versionString.c_str()));
222             return rev;
223         }
224     }
225 
226     return std::nullopt;
227 }
228 
229 RspType<uint8_t,  // Device ID
230         uint8_t,  // Device Revision
231         uint8_t,  // Firmware Revision Major
232         uint8_t,  // Firmware Revision minor
233         uint8_t,  // IPMI version
234         uint8_t,  // Additional device support
235         uint24_t, // MFG ID
236         uint16_t, // Product ID
237         uint32_t  // AUX info
238         >
239     ipmiAppGetDeviceId()
240 {
241     static struct
242     {
243         uint8_t id;
244         uint8_t revision;
245         uint8_t fw[2];
246         uint8_t ipmiVer = 2;
247         uint8_t addnDevSupport;
248         uint24_t manufId;
249         uint16_t prodId;
250         uint32_t aux;
251     } devId;
252     static bool dev_id_initialized = false;
253     static bool defaultActivationSetting = false;
254     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
255     const char* prodIdFilename = "/var/cache/private/prodID";
256     constexpr auto ipmiDevIdStateShift = 7;
257     constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
258     constexpr auto ipmiDevIdBusy = (1 << ipmiDevIdStateShift);
259 
260     if (!dev_id_initialized)
261     {
262         std::optional<MetaRevision> rev;
263         try
264         {
265             auto version = getActiveSoftwareVersionInfo();
266             rev = convertIntelVersion(version);
267         }
268         catch (const std::exception& e)
269         {
270             Log::log<Log::level::ERR>("Failed to get active version info",
271                                       Log::entry("ERROR=%s", e.what()));
272         }
273 
274         if (rev.has_value())
275         {
276             // bit7 identifies if the device is available
277             // 0=normal operation
278             // 1=device firmware, SDR update,
279             // or self-initialization in progress.
280             // The availability may change in run time, so mask here
281             // and initialize later.
282             MetaRevision revision = rev.value();
283             devId.fw[0] = revision.major & ipmiDevIdFw1Mask;
284 
285             revision.minor = (revision.minor > 99 ? 99 : revision.minor);
286             devId.fw[1] = revision.minor % 10 + (revision.minor / 10) * 16;
287             try
288             {
289                 uint32_t hash = std::stoul(revision.metaHash, 0, 16);
290                 hash = ((hash & 0xff000000) >> 24) |
291                        ((hash & 0x00FF0000) >> 8) | ((hash & 0x0000FF00) << 8) |
292                        ((hash & 0xFF) << 24);
293                 devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
294             }
295             catch (const std::exception& e)
296             {
297                 Log::log<Log::level::ERR>("Failed to convert git hash",
298                                           Log::entry("ERROR=%s", e.what()));
299             }
300         }
301 
302         std::ifstream devIdFile(filename);
303         if (devIdFile.is_open())
304         {
305             auto data = nlohmann::json::parse(devIdFile, nullptr, false);
306             if (!data.is_discarded())
307             {
308                 devId.id = data.value("id", 0);
309                 devId.revision = data.value("revision", 0);
310                 devId.addnDevSupport = data.value("addn_dev_support", 0);
311                 devId.manufId = data.value("manuf_id", 0);
312             }
313             else
314             {
315                 Log::log<Log::level::ERR>("Device ID JSON parser failure");
316                 return ipmi::responseUnspecifiedError();
317             }
318         }
319         else
320         {
321             Log::log<Log::level::ERR>("Device ID file not found");
322             return ipmi::responseUnspecifiedError();
323         }
324 
325         // Determine the Product ID. Using the DBus system is painfully slow at
326         // boot time. Avoid using DBus to get the Product ID. The Product ID is
327         // stored in a non-volatile file now. The /usr/bin/checkFru.sh script,
328         // run during bootup, will populate the productIdFile.
329         std::fstream prodIdFile(prodIdFilename);
330         if (prodIdFile.is_open())
331         {
332             std::string id = "0x00";
333             char* end;
334             prodIdFile.getline(&id[0], id.size() + 1);
335             devId.prodId = std::strtol(&id[0], &end, 0);
336             dev_id_initialized = true;
337         }
338         else
339         {
340             // For any exception send out platform id as 0,
341             // and make sure to re-query the device id.
342             dev_id_initialized = false;
343             devId.prodId = 0;
344         }
345     }
346 
347     // Set availability to the actual current BMC state
348     devId.fw[0] &= ipmiDevIdFw1Mask;
349     if (!getCurrentBmcStateWithFallback(defaultActivationSetting))
350     {
351         devId.fw[0] |= ipmiDevIdBusy;
352     }
353 
354     return ipmi::responseSuccess(
355         devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
356         devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
357 }
358 
359 static void registerAPPFunctions(void)
360 {
361     Log::log<Log::level::INFO>("Registering App commands");
362     // <Get Device ID>
363     registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
364                     ipmiAppGetDeviceId);
365 }
366 
367 } // namespace ipmi
368