blob: 7ded1600a1ded69430035bb466b57d2ac45d4d90 [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
Vernon Maueryd0b99b12025-05-27 10:43:58 -070029static constexpr auto redundancyIntf =
30 "xyz.openbmc_project.Software.RedundancyPriority";
31static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
32static constexpr auto activationIntf =
33 "xyz.openbmc_project.Software.Activation";
34static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
35
36void registerNetFnAppFunctions() __attribute__((constructor));
37
38using namespace phosphor::logging;
39using namespace sdbusplus::error::xyz::openbmc_project::common;
40using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
41using Activation =
42 sdbusplus::server::xyz::openbmc_project::software::Activation;
Alexander Hansenb1f1f112025-11-07 16:36:43 +010043using BMCState = sdbusplus::server::xyz::openbmc_project::state::BMC;
Vernon Maueryd0b99b12025-05-27 10:43:58 -070044namespace fs = std::filesystem;
45
46/**
47 * @brief Returns the Version info from primary s/w object
48 *
49 * Get the Version info from the active s/w object which is having high
50 * "Priority" value(a smaller number is a higher priority) and "Purpose"
51 * is "BMC" from the list of all s/w objects those are implementing
52 * RedundancyPriority interface from the given softwareRoot path.
53 *
54 * @return On success returns the Version info from primary s/w object.
55 *
56 */
57std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)
58{
59 std::string revision{};
60 ipmi::ObjectTree objectTree;
61 try
62 {
63 objectTree =
64 ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf);
65 }
66 catch (const sdbusplus::exception_t& e)
67 {
68 lg2::error("Failed to fetch redundancy object from dbus, "
69 "interface: {INTERFACE}, error: {ERROR}",
70 "INTERFACE", redundancyIntf, "ERROR", e);
71 elog<InternalFailure>();
72 }
73
74 auto objectFound = false;
75 for (auto& softObject : objectTree)
76 {
77 auto service =
78 ipmi::getService(*ctx->bus, redundancyIntf, softObject.first);
79 auto objValueTree =
80 ipmi::getManagedObjects(*ctx->bus, service, softwareRoot);
81
82 auto minPriority = 0xFF;
83 for (const auto& objIter : objValueTree)
84 {
85 try
86 {
87 auto& intfMap = objIter.second;
88 auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
89 auto& versionProps = intfMap.at(versionIntf);
90 auto& activationProps = intfMap.at(activationIntf);
91 auto priority =
92 std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
93 auto purpose =
94 std::get<std::string>(versionProps.at("Purpose"));
95 auto activation =
96 std::get<std::string>(activationProps.at("Activation"));
97 auto version =
98 std::get<std::string>(versionProps.at("Version"));
99 if ((Version::convertVersionPurposeFromString(purpose) ==
100 Version::VersionPurpose::BMC) &&
101 (Activation::convertActivationsFromString(activation) ==
102 Activation::Activations::Active))
103 {
104 if (priority < minPriority)
105 {
106 minPriority = priority;
107 objectFound = true;
108 revision = std::move(version);
109 }
110 }
111 }
112 catch (const std::exception& e)
113 {
114 lg2::error("error message: {ERROR}", "ERROR", e);
115 }
116 }
117 }
118
119 if (!objectFound)
120 {
121 lg2::error("Could not found an BMC software Object");
122 elog<InternalFailure>();
123 }
124
125 return revision;
126}
127
128bool getCurrentBmcStateWithFallback(ipmi::Context::ptr& ctx,
129 const bool fallbackAvailability)
130{
131 // Get the Inventory object implementing the BMC interface
132 ipmi::DbusObjectInfo bmcObject{};
133 boost::system::error_code ec =
Alexander Hansenb1f1f112025-11-07 16:36:43 +0100134 ipmi::getDbusObject(ctx, BMCState::interface, bmcObject);
Vernon Maueryd0b99b12025-05-27 10:43:58 -0700135 std::string bmcState{};
136 if (ec.value())
137 {
138 return fallbackAvailability;
139 }
Alexander Hansenb1f1f112025-11-07 16:36:43 +0100140 ec = ipmi::getDbusProperty(
141 ctx, bmcObject.second, bmcObject.first, BMCState::interface,
142 BMCState::property_names::current_bmc_state, bmcState);
Vernon Maueryd0b99b12025-05-27 10:43:58 -0700143 if (!ec.value())
144 {
145 return fallbackAvailability;
146 }
Alexander Hansenb1f1f112025-11-07 16:36:43 +0100147 return BMCState::convertBMCStateFromString(bmcState) ==
148 BMCState::BMCState::Ready;
Vernon Maueryd0b99b12025-05-27 10:43:58 -0700149}
150
151typedef struct
152{
153 char major;
154 char minor;
155 uint8_t aux[4];
156} Revision;
157
158/* Use regular expression searching matched pattern X.Y, and convert it to */
159/* Major (X) and Minor (Y) version. */
160/* Example: */
161/* version = 2.14.0-dev */
162/* ^ ^ */
163/* | |---------------- Minor */
164/* |------------------ Major */
165/* */
166/* Default regex string only tries to match Major and Minor version. */
167/* */
168/* To match more firmware version info, platforms need to define it own */
169/* regex string to match more strings, and assign correct mapping index in */
170/* matches array. */
171/* */
172/* matches[0]: matched index for major ver */
173/* matches[1]: matched index for minor ver */
174/* matches[2]: matched index for aux[0] (set 0 to skip) */
175/* matches[3]: matched index for aux[1] (set 0 to skip) */
176/* matches[4]: matched index for aux[2] (set 0 to skip) */
177/* matches[5]: matched index for aux[3] (set 0 to skip) */
178/* Example: */
179/* regex = "([\d]+).([\d]+).([\d]+)-dev-([\d]+)-g([0-9a-fA-F]{2}) */
180/* ([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})" */
181/* matches = {1,2,5,6,7,8} */
182/* version = 2.14.0-dev-750-g37a7c5ad1-dirty */
183/* ^ ^ ^ ^ ^ ^ ^ ^ */
184/* | | | | | | | | */
185/* | | | | | | | |-- Aux byte 3 (0xAD), index 8 */
186/* | | | | | | |---- Aux byte 2 (0xC5), index 7 */
187/* | | | | | |------ Aux byte 1 (0xA7), index 6 */
188/* | | | | |-------- Aux byte 0 (0x37), index 5 */
189/* | | | |------------- Not used, index 4 */
190/* | | |------------------- Not used, index 3 */
191/* | |---------------------- Minor (14), index 2 */
192/* |------------------------ Major (2), index 1 */
193int convertVersion(std::string s, Revision& rev)
194{
195 static const std::vector<size_t> matches = {
196 MAJOR_MATCH_INDEX, MINOR_MATCH_INDEX, AUX_0_MATCH_INDEX,
197 AUX_1_MATCH_INDEX, AUX_2_MATCH_INDEX, AUX_3_MATCH_INDEX};
198 std::regex fw_regex(FW_VER_REGEX);
199 std::smatch m;
200 Revision r = {0};
201 size_t val;
202
203 if (std::regex_search(s, m, fw_regex))
204 {
205 if (m.size() < *std::max_element(matches.begin(), matches.end()))
206 { // max index higher than match count
207 return -1;
208 }
209
210 // convert major
211 {
212 std::string str = m[matches[0]].str();
213 const auto& [ptr, ec] =
214 std::from_chars(str.data(), str.data() + str.size(), val);
215 if (ec != std::errc() || ptr != str.data() + str.size())
216 { // failed to convert major string
217 return -1;
218 }
219
220 if (val >= 2000)
221 { // For the platforms use year as major version, it would expect to
222 // have major version between 0 - 99. If the major version is
223 // greater than or equal to 2000, it is treated as a year and
224 // converted to 0 - 99.
225 r.major = val % 100;
226 }
227 else
228 {
229 r.major = val & 0x7F;
230 }
231 }
232
233 // convert minor
234 {
235 std::string str = m[matches[1]].str();
236 const auto& [ptr, ec] =
237 std::from_chars(str.data(), str.data() + str.size(), val);
238 if (ec != std::errc() || ptr != str.data() + str.size())
239 { // failed to convert minor string
240 return -1;
241 }
242 r.minor = val & 0xFF;
243 }
244
245 // convert aux bytes
246 {
247 size_t i;
248 for (i = 0; i < 4; i++)
249 {
250 if (matches[i + 2] == 0)
251 {
252 continue;
253 }
254
255 std::string str = m[matches[i + 2]].str();
256 const char* cstr = str.c_str();
257 auto [ptr,
258 ec] = std::from_chars(cstr, cstr + str.size(), val, 16);
259 if (ec != std::errc() || ptr != cstr + str.size())
260 { // failed to convert aux byte string
261 break;
262 }
263
264 r.aux[i] = val & 0xFF;
265 }
266
267 if (i != 4)
268 { // something wrong durign converting aux bytes
269 return -1;
270 }
271 }
272
273 // all matched
274 rev = r;
275 return 0;
276 }
277
278 return -1;
279}
280
281/* @brief: Implement the Get Device ID IPMI command per the IPMI spec
282 * @param[in] ctx - shared_ptr to an IPMI context struct
283 *
284 * @returns IPMI completion code plus response data
285 * - Device ID (manufacturer defined)
286 * - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit]
287 * - FW revision major[7 bits] (binary encoded); available[1 bit]
288 * - FW Revision minor (BCD encoded)
289 * - IPMI version (0x02 for IPMI 2.0)
290 * - device support (bitfield of supported options)
291 * - MFG IANA ID (3 bytes)
292 * - product ID (2 bytes)
293 * - AUX info (4 bytes)
294 */
295ipmi::RspType<uint8_t, // Device ID
296 uint8_t, // Device Revision
297 uint8_t, // Firmware Revision Major
298 uint8_t, // Firmware Revision minor
299 uint8_t, // IPMI version
300 uint8_t, // Additional device support
301 uint24_t, // MFG ID
302 uint16_t, // Product ID
303 uint32_t // AUX info
304 >
305 ipmiAppGetDeviceId([[maybe_unused]] ipmi::Context::ptr ctx)
306{
307 static struct
308 {
309 uint8_t id;
310 uint8_t revision;
311 uint8_t fw[2];
312 uint8_t ipmiVer;
313 uint8_t addnDevSupport;
314 uint24_t manufId;
315 uint16_t prodId;
316 uint32_t aux;
317 } devId;
318 static bool dev_id_initialized = false;
319 static bool defaultActivationSetting = true;
320 const char* filename = "/usr/share/ipmi-providers/dev_id.json";
321 constexpr auto ipmiDevIdStateShift = 7;
322 constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
323
324 static bool haveBMCVersion = false;
325 if (!haveBMCVersion || !dev_id_initialized)
326 {
327 int r = -1;
328 Revision rev = {0, 0, {0, 0, 0, 0}};
329 try
330 {
331 auto version = getActiveSoftwareVersionInfo(ctx);
332 r = convertVersion(version, rev);
333 }
334 catch (const std::exception& e)
335 {
336 lg2::error("error message: {ERROR}", "ERROR", e);
337 }
338
339 if (r >= 0)
340 {
341 // bit7 identifies if the device is available
342 // 0=normal operation
343 // 1=device firmware, SDR update,
344 // or self-initialization in progress.
345 // The availability may change in run time, so mask here
346 // and initialize later.
347 devId.fw[0] = rev.major & ipmiDevIdFw1Mask;
348
349 rev.minor = (rev.minor > 99 ? 99 : rev.minor);
350 devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
351 std::memcpy(&devId.aux, rev.aux, sizeof(rev.aux));
352 haveBMCVersion = true;
353 }
354 }
355 if (!dev_id_initialized)
356 {
357 // IPMI Spec version 2.0
358 devId.ipmiVer = 2;
359
360 std::ifstream devIdFile(filename);
361 if (devIdFile.is_open())
362 {
363 auto data = nlohmann::json::parse(devIdFile, nullptr, false);
364 if (!data.is_discarded())
365 {
366 devId.id = data.value("id", 0);
367 devId.revision = data.value("revision", 0);
368 devId.addnDevSupport = data.value("addn_dev_support", 0);
369 devId.manufId = data.value("manuf_id", 0);
370 devId.prodId = data.value("prod_id", 0);
371 if (!(AUX_0_MATCH_INDEX || AUX_1_MATCH_INDEX ||
372 AUX_2_MATCH_INDEX || AUX_3_MATCH_INDEX))
373 {
374 devId.aux = data.value("aux", 0);
375 }
376
377 if (data.contains("firmware_revision"))
378 {
379 const auto& firmwareRevision = data.at("firmware_revision");
380 if (firmwareRevision.contains("major"))
381 {
382 firmwareRevision.at("major").get_to(devId.fw[0]);
383 }
384 if (firmwareRevision.contains("minor"))
385 {
386 firmwareRevision.at("minor").get_to(devId.fw[1]);
387 }
388 }
389
390 // Set the availablitity of the BMC.
391 defaultActivationSetting = data.value("availability", true);
392
393 // Don't read the file every time if successful
394 dev_id_initialized = true;
395 }
396 else
397 {
398 lg2::error("Device ID JSON parser failure");
399 return ipmi::responseUnspecifiedError();
400 }
401 }
402 else
403 {
404 lg2::error("Device ID file not found");
405 return ipmi::responseUnspecifiedError();
406 }
407 }
408
409 // Set availability to the actual current BMC state
410 devId.fw[0] &= ipmiDevIdFw1Mask;
411 if (!getCurrentBmcStateWithFallback(ctx, defaultActivationSetting))
412 {
413 devId.fw[0] |= (1 << ipmiDevIdStateShift);
414 }
415
416 return ipmi::responseSuccess(
417 devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
418 devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
419}
420
421void registerNetFnAppFunctions()
422{
423 // OEM libraries should use ipmi::prioOemBase to override default
424 // implementation of IPMI commands that use ipmi::prioOpenBmcBase
425
426 // <Get Device ID>
427 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,
428 ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,
429 ipmiAppGetDeviceId);
430}