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 <byteswap.h>
17 
18 #include <appcommands.hpp>
19 #include <ipmid/api.hpp>
20 #include <ipmid/utils.hpp>
21 #include <nlohmann/json.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <types.hpp>
24 
25 #include <fstream>
26 #include <regex>
27 
28 namespace ipmi
29 {
30 
31 static void registerAPPFunctions() __attribute__((constructor));
32 
33 static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC";
34 static constexpr const char* softwareVerIntf =
35     "xyz.openbmc_project.Software.Version";
36 static constexpr const char* softwareActivationIntf =
37     "xyz.openbmc_project.Software.Activation";
38 static constexpr const char* associationIntf =
39     "xyz.openbmc_project.Association";
40 static constexpr const char* softwareFunctionalPath =
41     "/xyz/openbmc_project/software/functional";
42 
43 static constexpr const char* currentBmcStateProp = "CurrentBMCState";
44 static constexpr const char* bmcStateReadyStr =
45     "xyz.openbmc_project.State.BMC.BMCState.Ready";
46 
47 static std::unique_ptr<sdbusplus::bus::match_t> bmcStateChangedSignal;
48 static uint8_t bmcDeviceBusy = true;
49 
50 int initBMCDeviceState(ipmi::Context::ptr ctx)
51 {
52     DbusObjectInfo objInfo;
53     boost::system::error_code ec = ipmi::getDbusObject(ctx, bmcStateIntf, "/",
54                                                        "bmc0", objInfo);
55     if (ec)
56     {
57         phosphor::logging::log<phosphor::logging::level::ERR>(
58             "initBMCDeviceState: Failed to perform GetSubTree action",
59             phosphor::logging::entry("ERROR=%s", ec.message().c_str()),
60             phosphor::logging::entry("INTERFACE=%s", bmcStateIntf));
61         return -1;
62     }
63 
64     std::string bmcState;
65     ec = ipmi::getDbusProperty(ctx, objInfo.second, objInfo.first, bmcStateIntf,
66                                currentBmcStateProp, bmcState);
67     if (ec)
68     {
69         phosphor::logging::log<phosphor::logging::level::ERR>(
70             "initBMCDeviceState: Failed to get CurrentBMCState property",
71             phosphor::logging::entry("ERROR=%s", ec.message().c_str()));
72         return -1;
73     }
74 
75     bmcDeviceBusy = (bmcState != bmcStateReadyStr);
76 
77     phosphor::logging::log<phosphor::logging::level::INFO>(
78         "BMC device state updated");
79 
80     // BMC state may change runtime while doing firmware udpate.
81     // Register for property change signal to update state.
82     bmcStateChangedSignal = std::make_unique<sdbusplus::bus::match_t>(
83         *(ctx->bus),
84         sdbusplus::bus::match::rules::propertiesChanged(objInfo.first,
85                                                         bmcStateIntf),
86         [](sdbusplus::message_t& msg) {
87         std::map<std::string, ipmi::DbusVariant> props;
88         std::vector<std::string> inVal;
89         std::string iface;
90         try
91         {
92             msg.read(iface, props, inVal);
93         }
94         catch (const std::exception& e)
95         {
96             phosphor::logging::log<phosphor::logging::level::ERR>(
97                 "Exception caught in Get CurrentBMCState");
98             return;
99         }
100 
101         auto it = props.find(currentBmcStateProp);
102         if (it != props.end())
103         {
104             std::string* state = std::get_if<std::string>(&it->second);
105             if (state)
106             {
107                 bmcDeviceBusy = (*state != bmcStateReadyStr);
108                 phosphor::logging::log<phosphor::logging::level::INFO>(
109                     "BMC device state updated");
110             }
111         }
112         });
113 
114     return 0;
115 }
116 
117 /**
118  * @brief Returns the functional firmware version information.
119  *
120  * It reads the active firmware versions by checking functional
121  * endpoints association and matching the input version purpose string.
122  * ctx[in]                - ipmi context.
123  * reqVersionPurpose[in]  - Version purpose which need to be read.
124  * version[out]           - Output Version string.
125  *
126  * @return Returns '0' on success and '-1' on failure.
127  *
128  */
129 int getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx,
130                                  const std::string& reqVersionPurpose,
131                                  std::string& version)
132 {
133     std::vector<std::string> activeEndPoints;
134     boost::system::error_code ec = ipmi::getDbusProperty(
135         ctx, ipmi::MAPPER_BUS_NAME, softwareFunctionalPath, associationIntf,
136         "endpoints", activeEndPoints);
137     if (ec)
138     {
139         phosphor::logging::log<phosphor::logging::level::ERR>(
140             "Failed to get Active firmware version endpoints.");
141         return -1;
142     }
143 
144     for (auto& activeEndPoint : activeEndPoints)
145     {
146         std::string serviceName;
147         ec = ipmi::getService(ctx, softwareActivationIntf, activeEndPoint,
148                               serviceName);
149         if (ec)
150         {
151             phosphor::logging::log<phosphor::logging::level::ERR>(
152                 "Failed to perform getService.",
153                 phosphor::logging::entry("OBJPATH=%s", activeEndPoint.c_str()));
154             continue;
155         }
156 
157         PropertyMap propMap;
158         ec = ipmi::getAllDbusProperties(ctx, serviceName, activeEndPoint,
159                                         softwareVerIntf, propMap);
160         if (ec)
161         {
162             phosphor::logging::log<phosphor::logging::level::ERR>(
163                 "Failed to perform GetAll on Version interface.",
164                 phosphor::logging::entry("SERVICE=%s", serviceName.c_str()),
165                 phosphor::logging::entry("PATH=%s", activeEndPoint.c_str()));
166             continue;
167         }
168 
169         std::string* purposeProp =
170             std::get_if<std::string>(&propMap["Purpose"]);
171         std::string* versionProp =
172             std::get_if<std::string>(&propMap["Version"]);
173         if (!purposeProp || !versionProp)
174         {
175             phosphor::logging::log<phosphor::logging::level::ERR>(
176                 "Failed to get version or purpose property");
177             continue;
178         }
179 
180         // Check for requested version information and return if found.
181         if (*purposeProp == reqVersionPurpose)
182         {
183             version = *versionProp;
184             return 0;
185         }
186     }
187 
188     phosphor::logging::log<phosphor::logging::level::INFO>(
189         "Failed to find version information.",
190         phosphor::logging::entry("PURPOSE=%s", reqVersionPurpose.c_str()));
191     return -1;
192 }
193 
194 // Support both 2 solutions:
195 // 1.Current solution  2.7.0-dev-533-g14dc00e79-5e7d997
196 //   openbmcTag  2.7.0-dev
197 //   BuildNo     533
198 //   openbmcHash 14dc00e79
199 //   MetaHasg    5e7d997
200 //
201 // 2.New solution  wht-0.2-3-gab3500-38384ac
202 //   IdStr        wht
203 //   Major        0
204 //   Minor        2
205 //   buildNo      3
206 //   MetaHash     ab3500
207 //   openbmcHash  38384ac
208 std::optional<MetaRevision> convertIntelVersion(std::string& s)
209 {
210     std::smatch results;
211     MetaRevision rev;
212     std::regex pattern1("(\\d+?).(\\d+?).\\d+?-\\w*?-(\\d+?)-g(\\w+?)-(\\w+?)");
213     constexpr size_t matchedPhosphor = 6;
214     if (std::regex_match(s, results, pattern1))
215     {
216         if (results.size() == matchedPhosphor)
217         {
218             rev.platform = "whtref";
219             rev.major = static_cast<uint8_t>(std::stoi(results[1]));
220             rev.minor = static_cast<uint8_t>(std::stoi(results[2]));
221             rev.buildNo = static_cast<uint32_t>(std::stoi(results[3]));
222             rev.openbmcHash = results[4];
223             rev.metaHash = results[5];
224             std::string versionString =
225                 rev.platform + ":" + std::to_string(rev.major) + ":" +
226                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
227                 ":" + rev.openbmcHash + ":" + rev.metaHash;
228             phosphor::logging::log<phosphor::logging::level::INFO>(
229                 "Get BMC version",
230                 phosphor::logging::entry("VERSION=%s", versionString.c_str()));
231             return rev;
232         }
233     }
234     constexpr size_t matchedIntel = 7;
235     std::regex pattern2("(\\w+?)-(\\d+?).(\\d+?)-(\\d+?)-g(\\w+?)-(\\w+?)");
236     if (std::regex_match(s, results, pattern2))
237     {
238         if (results.size() == matchedIntel)
239         {
240             rev.platform = results[1];
241             rev.major = static_cast<uint8_t>(std::stoi(results[2]));
242             rev.minor = static_cast<uint8_t>(std::stoi(results[3]));
243             rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));
244             rev.openbmcHash = results[6];
245             rev.metaHash = results[5];
246             std::string versionString =
247                 rev.platform + ":" + std::to_string(rev.major) + ":" +
248                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
249                 ":" + rev.openbmcHash + ":" + rev.metaHash;
250             phosphor::logging::log<phosphor::logging::level::INFO>(
251                 "Get BMC version",
252                 phosphor::logging::entry("VERSION=%s", versionString.c_str()));
253             return rev;
254         }
255     }
256 
257     return std::nullopt;
258 }
259 
260 RspType<uint8_t,  // Device ID
261         uint8_t,  // Device Revision
262         uint7_t,  // Firmware Revision Major
263         bool,     // Device available(0=NormalMode,1=DeviceFirmware)
264         uint8_t,  // Firmware Revision minor
265         uint8_t,  // IPMI version
266         uint8_t,  // Additional device support
267         uint24_t, // MFG ID
268         uint16_t, // Product ID
269         uint32_t  // AUX info
270         >
271     ipmiAppGetDeviceId(ipmi::Context::ptr ctx)
272 {
273     static struct
274     {
275         uint8_t id;
276         uint8_t revision;
277         uint7_t fwMajor;
278         bool devBusy;
279         uint8_t fwMinor;
280         uint8_t ipmiVer = 2;
281         uint8_t addnDevSupport;
282         uint24_t manufId;
283         uint16_t prodId;
284         uint32_t aux;
285     } devId;
286     static bool fwVerInitialized = false;
287     static bool devIdInitialized = false;
288     static bool bmcStateInitialized = false;
289     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
290     const char* prodIdFilename = "/var/cache/private/prodID";
291     if (!fwVerInitialized)
292     {
293         std::string versionString;
294         if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC,
295                                           versionString))
296         {
297             std::optional<MetaRevision> rev =
298                 convertIntelVersion(versionString);
299             if (rev.has_value())
300             {
301                 MetaRevision revision = rev.value();
302                 devId.fwMajor = static_cast<uint7_t>(revision.major);
303 
304                 revision.minor = (revision.minor > 99 ? 99 : revision.minor);
305                 devId.fwMinor = revision.minor % 10 +
306                                 (revision.minor / 10) * 16;
307                 try
308                 {
309                     uint32_t hash = std::stoul(revision.metaHash, 0, 16);
310                     hash = bswap_32(hash);
311                     devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
312                     fwVerInitialized = true;
313                 }
314                 catch (const std::exception& e)
315                 {
316                     phosphor::logging::log<phosphor::logging::level::ERR>(
317                         "Failed to convert git hash",
318                         phosphor::logging::entry("ERROR=%s", e.what()));
319                 }
320             }
321         }
322     }
323 
324     if (!devIdInitialized)
325     {
326         std::ifstream devIdFile(filename);
327         if (devIdFile.is_open())
328         {
329             auto data = nlohmann::json::parse(devIdFile, nullptr, false);
330             if (!data.is_discarded())
331             {
332                 devId.id = data.value("id", 0);
333                 devId.revision = data.value("revision", 0);
334                 devId.addnDevSupport = data.value("addn_dev_support", 0);
335                 devId.manufId = data.value("manuf_id", 0);
336             }
337             else
338             {
339                 phosphor::logging::log<phosphor::logging::level::ERR>(
340                     "Device ID JSON parser failure");
341                 return ipmi::responseUnspecifiedError();
342             }
343         }
344         else
345         {
346             phosphor::logging::log<phosphor::logging::level::ERR>(
347                 "Device ID file not found");
348             return ipmi::responseUnspecifiedError();
349         }
350 
351         // Determine the Product ID. Using the DBus system is painfully slow at
352         // boot time. Avoid using DBus to get the Product ID. The Product ID is
353         // stored in a non-volatile file now. The /usr/bin/checkFru.sh script,
354         // run during bootup, will populate the productIdFile.
355         std::fstream prodIdFile(prodIdFilename);
356         if (prodIdFile.is_open())
357         {
358             std::string id = "0x00";
359             char* end;
360             prodIdFile.getline(&id[0], id.size() + 1);
361             devId.prodId = std::strtol(&id[0], &end, 0);
362             devIdInitialized = true;
363         }
364         else
365         {
366             // For any exception send out platform id as 0,
367             // and make sure to re-query the device id.
368             devIdInitialized = false;
369             devId.prodId = 0;
370         }
371     }
372 
373     if (!bmcStateInitialized)
374     {
375         if (!initBMCDeviceState(ctx))
376         {
377             bmcStateInitialized = true;
378         }
379     }
380 
381     return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor,
382                                  bmcDeviceBusy, devId.fwMinor, devId.ipmiVer,
383                                  devId.addnDevSupport, devId.manufId,
384                                  devId.prodId, devId.aux);
385 }
386 
387 static void registerAPPFunctions(void)
388 {
389     // <Get Device ID>
390     registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
391                     ipmiAppGetDeviceId);
392 }
393 
394 } // namespace ipmi
395