blob: 678f71a96e8546f9edbf103f75037ee23f82f27e [file] [log] [blame]
Vernon Maueryd0b99b12025-05-27 10:43:58 -07001#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
29constexpr auto bmcStateInterface = "xyz.openbmc_project.State.BMC";
30constexpr auto bmcStateProperty = "CurrentBMCState";
31
32static constexpr auto redundancyIntf =
33 "xyz.openbmc_project.Software.RedundancyPriority";
34static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
35static constexpr auto activationIntf =
36 "xyz.openbmc_project.Software.Activation";
37static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
38
39void registerNetFnAppFunctions() __attribute__((constructor));
40
41using namespace phosphor::logging;
42using namespace sdbusplus::error::xyz::openbmc_project::common;
43using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
44using Activation =
45 sdbusplus::server::xyz::openbmc_project::software::Activation;
46using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
47namespace 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 */
60std::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
131bool 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
152typedef 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 */
194int 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 */
296ipmi::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 >
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
422void 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}