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