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 or wht-2000.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             std::string majorVer = results[2].str();
242             // Take only the last two digits of the major version
243             rev.major = static_cast<uint8_t>(
244                 std::stoi(majorVer.substr(majorVer.size() - 2)));
245             rev.minor = static_cast<uint8_t>(std::stoi(results[3]));
246             rev.buildNo = static_cast<uint32_t>(std::stoi(results[4]));
247             rev.openbmcHash = results[6];
248             rev.metaHash = results[5];
249             std::string versionString =
250                 rev.platform + ":" + std::to_string(rev.major) + ":" +
251                 std::to_string(rev.minor) + ":" + std::to_string(rev.buildNo) +
252                 ":" + rev.openbmcHash + ":" + rev.metaHash;
253             phosphor::logging::log<phosphor::logging::level::INFO>(
254                 "Get BMC version",
255                 phosphor::logging::entry("VERSION=%s", versionString.c_str()));
256             return rev;
257         }
258     }
259 
260     return std::nullopt;
261 }
262 
263 RspType<uint8_t,  // Device ID
264         uint8_t,  // Device Revision
265         uint7_t,  // Firmware Revision Major
266         bool,     // Device available(0=NormalMode,1=DeviceFirmware)
267         uint8_t,  // Firmware Revision minor
268         uint8_t,  // IPMI version
269         uint8_t,  // Additional device support
270         uint24_t, // MFG ID
271         uint16_t, // Product ID
272         uint32_t  // AUX info
273         >
274     ipmiAppGetDeviceId(ipmi::Context::ptr ctx)
275 {
276     static struct
277     {
278         uint8_t id;
279         uint8_t revision;
280         uint7_t fwMajor;
281         bool devBusy;
282         uint8_t fwMinor;
283         uint8_t ipmiVer = 2;
284         uint8_t addnDevSupport;
285         uint24_t manufId;
286         uint16_t prodId;
287         uint32_t aux;
288     } devId;
289     static bool fwVerInitialized = false;
290     static bool devIdInitialized = false;
291     static bool bmcStateInitialized = false;
292     const char* filename = "/usr/share/ipmi-providers/dev_id.json";
293     const char* prodIdFilename = "/var/cache/private/prodID";
294     if (!fwVerInitialized)
295     {
296         std::string versionString;
297         if (!getActiveSoftwareVersionInfo(ctx, versionPurposeBMC,
298                                           versionString))
299         {
300             std::optional<MetaRevision> rev =
301                 convertIntelVersion(versionString);
302             if (rev.has_value())
303             {
304                 MetaRevision revision = rev.value();
305                 devId.fwMajor = static_cast<uint7_t>(revision.major);
306 
307                 revision.minor = (revision.minor > 99 ? 99 : revision.minor);
308                 devId.fwMinor = revision.minor % 10 +
309                                 (revision.minor / 10) * 16;
310                 try
311                 {
312                     uint32_t hash = std::stoul(revision.metaHash, 0, 16);
313                     hash = bswap_32(hash);
314                     devId.aux = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00);
315                     fwVerInitialized = true;
316                 }
317                 catch (const std::exception& e)
318                 {
319                     phosphor::logging::log<phosphor::logging::level::ERR>(
320                         "Failed to convert git hash",
321                         phosphor::logging::entry("ERROR=%s", e.what()));
322                 }
323             }
324         }
325     }
326 
327     if (!devIdInitialized)
328     {
329         std::ifstream devIdFile(filename);
330         if (devIdFile.is_open())
331         {
332             auto data = nlohmann::json::parse(devIdFile, nullptr, false);
333             if (!data.is_discarded())
334             {
335                 devId.id = data.value("id", 0);
336                 devId.revision = data.value("revision", 0);
337                 devId.addnDevSupport = data.value("addn_dev_support", 0);
338                 devId.manufId = data.value("manuf_id", 0);
339             }
340             else
341             {
342                 phosphor::logging::log<phosphor::logging::level::ERR>(
343                     "Device ID JSON parser failure");
344                 return ipmi::responseUnspecifiedError();
345             }
346         }
347         else
348         {
349             phosphor::logging::log<phosphor::logging::level::ERR>(
350                 "Device ID file not found");
351             return ipmi::responseUnspecifiedError();
352         }
353 
354         // Determine the Product ID. Using the DBus system is painfully slow at
355         // boot time. Avoid using DBus to get the Product ID. The Product ID is
356         // stored in a non-volatile file now. The /usr/bin/checkFru.sh script,
357         // run during bootup, will populate the productIdFile.
358         std::fstream prodIdFile(prodIdFilename);
359         if (prodIdFile.is_open())
360         {
361             std::string id = "0x00";
362             char* end;
363             prodIdFile.getline(&id[0], id.size() + 1);
364             devId.prodId = std::strtol(&id[0], &end, 0);
365             devIdInitialized = true;
366         }
367         else
368         {
369             // For any exception send out platform id as 0,
370             // and make sure to re-query the device id.
371             devIdInitialized = false;
372             devId.prodId = 0;
373         }
374     }
375 
376     if (!bmcStateInitialized)
377     {
378         if (!initBMCDeviceState(ctx))
379         {
380             bmcStateInitialized = true;
381         }
382     }
383 
384     return ipmi::responseSuccess(devId.id, devId.revision, devId.fwMajor,
385                                  bmcDeviceBusy, devId.fwMinor, devId.ipmiVer,
386                                  devId.addnDevSupport, devId.manufId,
387                                  devId.prodId, devId.aux);
388 }
389 
390 static void registerAPPFunctions(void)
391 {
392     // <Get Device ID>
393     registerHandler(prioOemBase, netFnApp, app::cmdGetDeviceId, Privilege::User,
394                     ipmiAppGetDeviceId);
395 }
396 
397 } // namespace ipmi
398