import phosphor-net-ipmid as rmcpbridge
Change-Id: I4e62b33c178c9ee02d623579ba174c1359720525
diff --git a/.build.sh b/.build.sh
new file mode 100755
index 0000000..d4e4b26
--- /dev/null
+++ b/.build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+Dockerfile=$(cat << EOF
+FROM ubuntu:15.10
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get upgrade -yy
+RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -yy make g++ gcc libsystemd-dev libc6-dev pkg-config
+RUN groupadd -g ${GROUPS[0]} ${USER} && useradd -d ${HOME} -m -u ${UID} -g ${GROUPS[0]} ${USER}
+USER ${USER}
+ENV HOME ${HOME}
+RUN /bin/bash
+EOF
+)
+
+docker pull ubuntu:15.10
+docker build -t temp - <<< "${Dockerfile}"
+
+gcc --version
+
+docker run --cap-add=sys_admin --net=host --rm=true --user="${USER}" \
+ -w "${PWD}" -v "${HOME}":"${HOME}" -t temp make
diff --git a/.clang-tidy b/.clang-tidy
index 4b6eca0..75d64dc 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,7 +1,12 @@
Checks: '
- -*,
- bugprone-unchecked-optional-access,
- readability-identifier-naming
+-*,
+modernize-use-nullptr,
'
+
+# Treat all warnings as errors
WarningsAsErrors: '*'
+
+# Apply checks to all files
HeaderFilterRegex: '(?!^subprojects).*'
+
+CheckOptions: []
diff --git a/.gitignore b/.gitignore
index 51ef08e..f0b223f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,17 @@
+# ignore vim swap files
+.*.sw*
+# failures from patch
+*.orig
+*.rej
+# backup files from some editors
+*~
+.cscope/
+
+# Meson
/build*/
/subprojects/*
-!subprojects/*.wrap
+!/subprojects/*.wrap
+subprojects/CLI11.wrap
+subprojects/function2.wrap
+subprojects/googletest.wrap
+subprojects/stdplus.wrap
diff --git a/.shellcheck b/.shellcheck
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.shellcheck
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d8d6c1d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: c
+
+sudo: required
+
+services:
+ - docker
+
+script:
+ - ./.build.sh
diff --git a/LICENSE b/LICENSE
index 8dada3e..8f71f43 100644
--- a/LICENSE
+++ b/LICENSE
@@ -199,3 +199,4 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+
diff --git a/OWNERS b/OWNERS
index 05f2bbe..9d16375 100644
--- a/OWNERS
+++ b/OWNERS
@@ -32,14 +32,30 @@
# data within them will be kept sorted.
owners:
+- liuxiwei@ieisystem.com
- rushtotom@gmail.com
- vernon.mauery@gmail.com
reviewers:
+- anoo@us.ibm.com
+- deepak.kodihalli.83@gmail.com
+- ratankgupta31@gmail.com
matchers:
openbmc:
+- name: Adriana Kobylak
+ email: anoo@us.ibm.com
+ discord: anoo
+- name: Deepak Kodihalli
+ email: deepak.kodihalli.83@gmail.com
+ discord: dkodihal
+- name: George Liu
+ email: liuxiwei@ieisystem.com
+ discord: George Liu
+- name: Ratan Gupta
+ email: ratankgupta31@gmail.com
+ discord: rgupta
- name: Tom Joseph
email: rushtotom@gmail.com
discord: tomjose
diff --git a/README.md b/README.md
index 89aba20..9c53680 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,41 @@
-# phosphor-net-ipmid
+# phosphor-host-ipmid
-## To Build
+## Compile ipmid with default options
-To build this package, do the following steps:
-
-```sh
-1. ./bootstrap.sh
-2. ./configure ${CONFIGURE_FLAGS}
-3. make
+```ascii
+meson builddir
+ninja -C builddir
```
-To clean the repository run `./bootstrap.sh clean`.
+## Compile ipmid with yocto defaults
+
+```ascii
+meson builddir -Dbuildtype=minsize -Db_lto=true -Dtests=disabled
+ninja -C builddir
+```
+
+If any of the dependencies are not found on the host system during
+configuration, meson automatically gets them via its wrap dependencies mentioned
+in `ipmid/subprojects`.
+
+## Enable/Disable meson wrap feature
+
+```ascii
+meson builddir -Dwrap_mode=nofallback
+ninja -C builddir
+```
+
+## Enable debug traces
+
+```ascii
+meson builddir -Dbuildtype=debug
+ninja -C builddir
+```
+
+## Generate test coverage report
+
+```ascii
+meson builddir -Db_coverage=true -Dtests=enabled
+ninja -C builddir test
+ninja -C builddir coverage
+```
diff --git a/app/channel.cpp b/app/channel.cpp
new file mode 100644
index 0000000..e7acf36
--- /dev/null
+++ b/app/channel.cpp
@@ -0,0 +1,180 @@
+#include "channel.hpp"
+
+#include "user_channel/channel_layer.hpp"
+
+#include <arpa/inet.h>
+
+#include <boost/process/v1/child.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <fstream>
+#include <set>
+#include <string>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace cipher
+{
+
+/** @brief Get the supported Cipher records
+ *
+ * The cipher records are read from the JSON file and converted into
+ * 1. cipher suite record format mentioned in the IPMI specification. The
+ * records can be either OEM or standard cipher. Each json entry is parsed and
+ * converted into the cipher record format and pushed into the vector.
+ * 2. Algorithms listed in vector format
+ *
+ * @return pair of vector containing 1. all the cipher suite records. 2.
+ * Algorithms supported
+ *
+ */
+std::pair<std::vector<uint8_t>, std::vector<uint8_t>> getCipherRecords()
+{
+ std::vector<uint8_t> cipherRecords;
+ std::vector<uint8_t> supportedAlgorithmRecords;
+ // create set to get the unique supported algorithms
+ std::set<uint8_t> supportedAlgorithmSet;
+
+ std::ifstream jsonFile(configFile);
+ if (!jsonFile.is_open())
+ {
+ lg2::error("Channel Cipher suites file not found");
+ elog<InternalFailure>();
+ }
+
+ auto data = Json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("Parsing channel cipher suites JSON failed");
+ elog<InternalFailure>();
+ }
+
+ for (const auto& record : data)
+ {
+ if (record.find(oem) != record.end())
+ {
+ // OEM cipher suite - 0xC1
+ cipherRecords.push_back(oemCipherSuite);
+ // Cipher Suite ID
+ cipherRecords.push_back(record.value(cipher, 0));
+ // OEM IANA - 3 bytes
+ cipherRecords.push_back(record.value(oem, 0));
+ cipherRecords.push_back(record.value(oem, 0) >> 8);
+ cipherRecords.push_back(record.value(oem, 0) >> 16);
+ }
+ else
+ {
+ // Standard cipher suite - 0xC0
+ cipherRecords.push_back(stdCipherSuite);
+ // Cipher Suite ID
+ cipherRecords.push_back(record.value(cipher, 0));
+ }
+
+ // Authentication algorithm number
+ cipherRecords.push_back(record.value(auth, 0));
+ supportedAlgorithmSet.insert(record.value(auth, 0));
+
+ // Integrity algorithm number
+ cipherRecords.push_back(record.value(integrity, 0) | integrityTag);
+ supportedAlgorithmSet.insert(record.value(integrity, 0) | integrityTag);
+
+ // Confidentiality algorithm number
+ cipherRecords.push_back(record.value(conf, 0) | confTag);
+ supportedAlgorithmSet.insert(record.value(conf, 0) | confTag);
+ }
+
+ // copy the set to supportedAlgorithmRecord which is vector based.
+ std::copy(supportedAlgorithmSet.begin(), supportedAlgorithmSet.end(),
+ std::back_inserter(supportedAlgorithmRecords));
+
+ return std::make_pair(cipherRecords, supportedAlgorithmRecords);
+}
+
+} // namespace cipher
+
+/** @brief this command is used to look up what authentication, integrity,
+ * confidentiality algorithms are supported.
+ *
+ * @ param ctx - context pointer
+ * @ param channelNumber - channel number
+ * @ param payloadType - payload type
+ * @ param listIndex - list index
+ * @ param algoSelectBit - list algorithms
+ *
+ * @returns ipmi completion code plus response data
+ * - rspChannel - channel number for authentication algorithm.
+ * - rspRecords - cipher suite records.
+ **/
+ipmi::RspType<uint8_t, // Channel Number
+ std::vector<uint8_t> // Cipher Records
+ >
+ getChannelCipherSuites(ipmi::Context::ptr ctx, uint4_t channelNumber,
+ uint4_t reserved1, uint8_t payloadType,
+ uint6_t listIndex, uint1_t reserved2,
+ uint1_t algoSelectBit)
+{
+ static std::vector<uint8_t> cipherRecords;
+ static std::vector<uint8_t> supportedAlgorithms;
+ static auto recordInit = false;
+
+ uint8_t rspChannel = ipmi::convertCurrentChannelNum(
+ static_cast<uint8_t>(channelNumber), ctx->channel);
+
+ if (!ipmi::isValidChannel(rspChannel) || reserved1 != 0 || reserved2 != 0)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (!ipmi::isValidPayloadType(static_cast<ipmi::PayloadType>(payloadType)))
+ {
+ lg2::debug("Get channel cipher suites - Invalid payload type");
+ constexpr uint8_t ccPayloadTypeNotSupported = 0x80;
+ return ipmi::response(ccPayloadTypeNotSupported);
+ }
+
+ if (!recordInit)
+ {
+ try
+ {
+ std::tie(cipherRecords, supportedAlgorithms) =
+ cipher::getCipherRecords();
+ recordInit = true;
+ }
+ catch (const std::exception& e)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+
+ const std::vector<uint8_t>& records =
+ algoSelectBit ? cipherRecords : supportedAlgorithms;
+ static constexpr auto respSize = 16;
+
+ // Session support is available in active LAN channels.
+ if ((ipmi::getChannelSessionSupport(rspChannel) ==
+ ipmi::EChannelSessSupported::none) ||
+ !(ipmi::doesDeviceExist(rspChannel)))
+ {
+ lg2::debug("Get channel cipher suites - Device does not exist");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // List index(00h-3Fh), 0h selects the first set of 16, 1h selects the next
+ // set of 16 and so on.
+
+ // Calculate the number of record data bytes to be returned.
+ auto start =
+ std::min(static_cast<size_t>(listIndex) * respSize, records.size());
+ auto end = std::min((static_cast<size_t>(listIndex) * respSize) + respSize,
+ records.size());
+ auto size = end - start;
+
+ std::vector<uint8_t> rspRecords;
+ std::copy_n(records.data() + start, size, std::back_inserter(rspRecords));
+
+ return ipmi::responseSuccess(rspChannel, rspRecords);
+}
diff --git a/app/channel.hpp b/app/channel.hpp
new file mode 100644
index 0000000..a6b8703
--- /dev/null
+++ b/app/channel.hpp
@@ -0,0 +1,43 @@
+#include "nlohmann/json.hpp"
+
+#include <ipmid/api.hpp>
+
+/** @brief this command is used to look up what authentication, integrity,
+ * confidentiality algorithms are supported.
+ *
+ * @ param ctx - context pointer
+ * @ param channelNumber - channel number
+ * @ param payloadType - payload type
+ * @ param listIndex - list index
+ * @ param algoSelectBit - list algorithms
+ *
+ * @returns ipmi completion code plus response data
+ * - rspChannel - channel number for authentication algorithm.
+ * - rspRecords - cipher suite records.
+ **/
+ipmi::RspType<uint8_t, // Channel Number
+ std::vector<uint8_t> // Cipher Records
+ >
+ getChannelCipherSuites(ipmi::Context::ptr ctx, uint4_t channelNumber,
+ uint4_t reserved1, uint8_t payloadType,
+ uint6_t listIndex, uint1_t reserved2,
+ uint1_t algoSelectBit);
+
+namespace cipher
+{
+
+static constexpr auto listCipherSuite = 0x80;
+
+using Json = nlohmann::json;
+static constexpr auto configFile = "/usr/share/ipmi-providers/cipher_list.json";
+static constexpr auto cipher = "cipher";
+static constexpr auto stdCipherSuite = 0xC0;
+static constexpr auto oemCipherSuite = 0xC1;
+static constexpr auto oem = "oemiana";
+static constexpr auto auth = "authentication";
+static constexpr auto integrity = "integrity";
+static constexpr auto integrityTag = 0x40;
+static constexpr auto conf = "confidentiality";
+static constexpr auto confTag = 0x80;
+
+} // namespace cipher
diff --git a/app/meson.build b/app/meson.build
new file mode 100644
index 0000000..bc7c598
--- /dev/null
+++ b/app/meson.build
@@ -0,0 +1,14 @@
+app_inc = include_directories('.')
+
+app_pre = declare_dependency(include_directories: [root_inc, app_inc])
+
+app_lib = static_library(
+ 'app',
+ 'channel.cpp',
+ 'watchdog.cpp',
+ 'watchdog_service.cpp',
+ implicit_include_directories: false,
+ dependencies: app_pre,
+)
+
+app_dep = declare_dependency(link_with: app_lib, dependencies: app_pre)
diff --git a/app/watchdog.cpp b/app/watchdog.cpp
new file mode 100644
index 0000000..aea13fd
--- /dev/null
+++ b/app/watchdog.cpp
@@ -0,0 +1,436 @@
+#include "watchdog.hpp"
+
+#include "watchdog_service.hpp"
+
+#include <endian.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <bitset>
+#include <cstdint>
+#include <string>
+
+using phosphor::logging::commit;
+using phosphor::logging::level;
+using phosphor::logging::log;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+static bool lastCallSuccessful = false;
+
+void reportError()
+{
+ // We don't want to fill the SEL with errors if the daemon dies and doesn't
+ // come back but the watchdog keeps on ticking. Instead, we only report the
+ // error if we haven't reported one since the last successful call
+ if (!lastCallSuccessful)
+ {
+ return;
+ }
+ lastCallSuccessful = false;
+
+ // TODO: This slow down the end of the IPMI transaction waiting
+ // for the commit to finish. commit<>() can take at least 5 seconds
+ // to complete. 5s is very slow for an IPMI command and ends up
+ // congesting the IPMI channel needlessly, especially if the watchdog
+ // is ticking fairly quickly and we have some transient issues.
+ commit<InternalFailure>();
+}
+
+ipmi::RspType<> ipmiAppResetWatchdogTimer()
+{
+ try
+ {
+ WatchdogService wd_service;
+
+ // Notify the caller if we haven't initialized our timer yet
+ // so it can configure actions and timeouts
+ if (!wd_service.getInitialized())
+ {
+ lastCallSuccessful = true;
+
+ constexpr uint8_t ccWatchdogNotInit = 0x80;
+ return ipmi::response(ccWatchdogNotInit);
+ }
+
+ // The ipmi standard dictates we enable the watchdog during reset
+ wd_service.resetTimeRemaining(true);
+ lastCallSuccessful = true;
+ return ipmi::responseSuccess();
+ }
+ catch (const InternalFailure& e)
+ {
+ reportError();
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("wd_reset: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (...)
+ {
+ lg2::error("wd_reset: Unknown Error");
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+static constexpr uint8_t wd_timeout_action_mask = 0x3;
+
+static constexpr uint8_t wdTimerUseResTimer1 = 0x0;
+static constexpr uint8_t wdTimerUseResTimer2 = 0x6;
+static constexpr uint8_t wdTimerUseResTimer3 = 0x7;
+
+static constexpr uint8_t wdTimeoutActionMax = 3;
+static constexpr uint8_t wdTimeoutInterruptTimer = 0x04;
+
+enum class IpmiAction : uint8_t
+{
+ None = 0x0,
+ HardReset = 0x1,
+ PowerOff = 0x2,
+ PowerCycle = 0x3,
+};
+
+/** @brief Converts an IPMI Watchdog Action to DBUS defined action
+ * @param[in] ipmi_action The IPMI Watchdog Action
+ * @return The Watchdog Action that the ipmi_action maps to
+ */
+WatchdogService::Action ipmiActionToWdAction(IpmiAction ipmi_action)
+{
+ switch (ipmi_action)
+ {
+ case IpmiAction::None:
+ {
+ return WatchdogService::Action::None;
+ }
+ case IpmiAction::HardReset:
+ {
+ return WatchdogService::Action::HardReset;
+ }
+ case IpmiAction::PowerOff:
+ {
+ return WatchdogService::Action::PowerOff;
+ }
+ case IpmiAction::PowerCycle:
+ {
+ return WatchdogService::Action::PowerCycle;
+ }
+ default:
+ {
+ throw std::domain_error("IPMI Action is invalid");
+ }
+ }
+}
+
+enum class IpmiTimerUse : uint8_t
+{
+ Reserved = 0x0,
+ BIOSFRB2 = 0x1,
+ BIOSPOST = 0x2,
+ OSLoad = 0x3,
+ SMSOS = 0x4,
+ OEM = 0x5,
+};
+
+WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse)
+{
+ switch (ipmiTimerUse)
+ {
+ case IpmiTimerUse::Reserved:
+ {
+ return WatchdogService::TimerUse::Reserved;
+ }
+ case IpmiTimerUse::BIOSFRB2:
+ {
+ return WatchdogService::TimerUse::BIOSFRB2;
+ }
+ case IpmiTimerUse::BIOSPOST:
+ {
+ return WatchdogService::TimerUse::BIOSPOST;
+ }
+ case IpmiTimerUse::OSLoad:
+ {
+ return WatchdogService::TimerUse::OSLoad;
+ }
+ case IpmiTimerUse::SMSOS:
+ {
+ return WatchdogService::TimerUse::SMSOS;
+ }
+ case IpmiTimerUse::OEM:
+ {
+ return WatchdogService::TimerUse::OEM;
+ }
+ default:
+ {
+ return WatchdogService::TimerUse::Reserved;
+ }
+ }
+}
+
+static bool timerNotLogFlags = false;
+static std::bitset<8> timerUseExpirationFlags = 0;
+static uint3_t timerPreTimeoutInterrupt = 0;
+static constexpr uint8_t wdExpirationFlagReservedBit0 = 0x0;
+static constexpr uint8_t wdExpirationFlagReservedBit6 = 0x6;
+static constexpr uint8_t wdExpirationFlagReservedBit7 = 0x7;
+
+/**@brief The Set Watchdog Timer ipmi command.
+ *
+ * @param
+ * - timerUse
+ * - dontStopTimer
+ * - dontLog
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ *
+ * @return completion code on success.
+ **/
+ipmi::RspType<> ipmiSetWatchdogTimer(
+ uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog,
+ uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt,
+ uint1_t reserved2, uint8_t preTimeoutInterval, std::bitset<8> expFlagValue,
+ uint16_t initialCountdown)
+{
+ if ((timerUse == wdTimerUseResTimer1) ||
+ (timerUse == wdTimerUseResTimer2) ||
+ (timerUse == wdTimerUseResTimer3) ||
+ (timeoutAction > wdTimeoutActionMax) ||
+ (preTimeoutInterrupt == wdTimeoutInterruptTimer) ||
+ (reserved | reserved1 | reserved2 |
+ expFlagValue.test(wdExpirationFlagReservedBit0) |
+ expFlagValue.test(wdExpirationFlagReservedBit6) |
+ expFlagValue.test(wdExpirationFlagReservedBit7)))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (preTimeoutInterval > (initialCountdown / 10))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ timerNotLogFlags = dontLog;
+ timerPreTimeoutInterrupt = preTimeoutInterrupt;
+
+ try
+ {
+ WatchdogService wd_service;
+ // Stop the timer if the don't stop bit is not set
+ if (!(dontStopTimer))
+ {
+ wd_service.setEnabled(false);
+ }
+
+ // Set the action based on the request
+ const auto ipmi_action = static_cast<IpmiAction>(
+ static_cast<uint8_t>(timeoutAction) & wd_timeout_action_mask);
+ wd_service.setExpireAction(ipmiActionToWdAction(ipmi_action));
+
+ const auto ipmiTimerUse = types::enum_cast<IpmiTimerUse>(timerUse);
+ wd_service.setTimerUse(ipmiTimerUseToWdTimerUse(ipmiTimerUse));
+
+ wd_service.setExpiredTimerUse(WatchdogService::TimerUse::Reserved);
+
+ timerUseExpirationFlags &= ~expFlagValue;
+
+ // Set the new interval and the time remaining deci -> mill seconds
+ const uint64_t interval = initialCountdown * 100;
+ wd_service.setInterval(interval);
+ wd_service.resetTimeRemaining(false);
+
+ // Mark as initialized so that future resets behave correctly
+ wd_service.setInitialized(true);
+ wd_service.setLogTimeout(!dontLog);
+
+ lastCallSuccessful = true;
+ return ipmi::responseSuccess();
+ }
+ catch (const std::domain_error&)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ catch (const InternalFailure& e)
+ {
+ reportError();
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("wd_set: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (...)
+ {
+ lg2::error("wd_set: Unknown Error");
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+/** @brief Converts a DBUS Watchdog Action to IPMI defined action
+ * @param[in] wd_action The DBUS Watchdog Action
+ * @return The IpmiAction that the wd_action maps to
+ */
+IpmiAction wdActionToIpmiAction(WatchdogService::Action wd_action)
+{
+ switch (wd_action)
+ {
+ case WatchdogService::Action::None:
+ {
+ return IpmiAction::None;
+ }
+ case WatchdogService::Action::HardReset:
+ {
+ return IpmiAction::HardReset;
+ }
+ case WatchdogService::Action::PowerOff:
+ {
+ return IpmiAction::PowerOff;
+ }
+ case WatchdogService::Action::PowerCycle:
+ {
+ return IpmiAction::PowerCycle;
+ }
+ default:
+ {
+ // We have no method via IPMI to signal that the action is unknown
+ // or unmappable in some way.
+ // Just ignore the error and return NONE so the host can reconcile.
+ return IpmiAction::None;
+ }
+ }
+}
+
+IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse)
+{
+ switch (wdTimerUse)
+ {
+ case WatchdogService::TimerUse::Reserved:
+ {
+ return IpmiTimerUse::Reserved;
+ }
+ case WatchdogService::TimerUse::BIOSFRB2:
+ {
+ return IpmiTimerUse::BIOSFRB2;
+ }
+ case WatchdogService::TimerUse::BIOSPOST:
+ {
+ return IpmiTimerUse::BIOSPOST;
+ }
+ case WatchdogService::TimerUse::OSLoad:
+ {
+ return IpmiTimerUse::OSLoad;
+ }
+
+ case WatchdogService::TimerUse::SMSOS:
+ {
+ return IpmiTimerUse::SMSOS;
+ }
+ case WatchdogService::TimerUse::OEM:
+ {
+ return IpmiTimerUse::OEM;
+ }
+ default:
+ {
+ return IpmiTimerUse::Reserved;
+ }
+ }
+}
+
+/**@brief The getWatchdogTimer ipmi command.
+ *
+ * @return Completion code plus timer details.
+ * - timerUse
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ * - presentCountdown
+ **/
+ipmi::RspType<uint3_t, // timerUse - timer use
+ uint3_t, // timerUse - reserved
+ bool, // timerUse - timer is started
+ bool, // timerUse - don't log
+
+ uint3_t, // timerAction - timeout action
+ uint1_t, // timerAction - reserved
+ uint3_t, // timerAction - pre-timeout interrupt
+ uint1_t, // timerAction - reserved
+
+ uint8_t, // pretimeout
+ std::bitset<8>, // expireFlags
+ uint16_t, // initial Countdown - Little Endian (deciseconds)
+ uint16_t // present Countdown - Little Endian (deciseconds)
+ >
+ ipmiGetWatchdogTimer()
+{
+ uint16_t presentCountdown = 0;
+ uint8_t pretimeout = 0;
+
+ try
+ {
+ WatchdogService wd_service;
+ WatchdogService::Properties wd_prop = wd_service.getProperties();
+
+ // Build and return the response
+ // Interval and timeRemaining need converted from milli -> deci seconds
+ uint16_t initialCountdown = htole16(wd_prop.interval / 100);
+
+ if (wd_prop.expiredTimerUse != WatchdogService::TimerUse::Reserved)
+ {
+ timerUseExpirationFlags.set(static_cast<uint8_t>(
+ wdTimerUseToIpmiTimerUse(wd_prop.expiredTimerUse)));
+ }
+
+ if (wd_prop.enabled)
+ {
+ presentCountdown = htole16(wd_prop.timeRemaining / 100);
+ }
+ else
+ {
+ if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved)
+ {
+ presentCountdown = initialCountdown;
+ }
+ else
+ {
+ presentCountdown = 0;
+ // Automatically clear it whenever a timer expiration occurs.
+ timerNotLogFlags = false;
+ }
+ }
+
+ // TODO: Do something about having pretimeout support
+ pretimeout = 0;
+
+ lastCallSuccessful = true;
+ return ipmi::responseSuccess(
+ types::enum_cast<uint3_t>(
+ wdTimerUseToIpmiTimerUse(wd_prop.timerUse)),
+ 0, wd_prop.enabled, timerNotLogFlags,
+ types::enum_cast<uint3_t>(
+ wdActionToIpmiAction(wd_prop.expireAction)),
+ 0, timerPreTimeoutInterrupt, 0, pretimeout, timerUseExpirationFlags,
+ initialCountdown, presentCountdown);
+ }
+ catch (const InternalFailure& e)
+ {
+ reportError();
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("wd_get: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (...)
+ {
+ lg2::error("wd_get: Unknown Error");
+ return ipmi::responseUnspecifiedError();
+ }
+}
diff --git a/app/watchdog.hpp b/app/watchdog.hpp
new file mode 100644
index 0000000..fa53ac7
--- /dev/null
+++ b/app/watchdog.hpp
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <ipmid/api.hpp>
+
+/** @brief The RESET watchdog IPMI command.
+ */
+ipmi::RspType<> ipmiAppResetWatchdogTimer();
+
+/**@brief The setWatchdogTimer ipmi command.
+ *
+ * @param
+ * - timerUse
+ * - dontStopTimer
+ * - dontLog
+ * - timerAction
+ * - pretimeout
+ * - expireFlags
+ * - initialCountdown
+ *
+ * @return completion code on success.
+ **/
+ipmi::RspType<> ipmiSetWatchdogTimer(
+ uint3_t timerUse, uint3_t reserved, bool dontStopTimer, bool dontLog,
+ uint3_t timeoutAction, uint1_t reserved1, uint3_t preTimeoutInterrupt,
+ uint1_t reserved2, uint8_t preTimeoutInterval, std::bitset<8> expFlagValue,
+ uint16_t initialCountdown);
+
+/**@brief The getWatchdogTimer ipmi command.
+ *
+ * @return
+ * - timerUse
+ * - timerActions
+ * - pretimeout
+ * - timeruseFlags
+ * - initialCountdown
+ * - presentCountdown
+ **/
+ipmi::RspType<uint3_t, uint3_t, bool, bool, // timerUse
+ uint3_t, uint1_t, uint3_t, uint1_t, // timerAction
+ uint8_t, // pretimeout
+ std::bitset<8>, // expireFlags
+ uint16_t, // initial Countdown - Little Endian (deciseconds)
+ uint16_t // present Countdown - Little Endian (deciseconds)
+ >
+ ipmiGetWatchdogTimer();
diff --git a/app/watchdog_service.cpp b/app/watchdog_service.cpp
new file mode 100644
index 0000000..b67b5c7
--- /dev/null
+++ b/app/watchdog_service.cpp
@@ -0,0 +1,206 @@
+#include "watchdog_service.hpp"
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/Watchdog/server.hpp>
+
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+using phosphor::logging::elog;
+using phosphor::logging::entry;
+using phosphor::logging::level;
+using phosphor::logging::log;
+using sdbusplus::common::xyz::openbmc_project::state::convertForMessage;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using sdbusplus::server::xyz::openbmc_project::state::Watchdog;
+
+static constexpr char wd_path[] = "/xyz/openbmc_project/watchdog/host0";
+static constexpr char wd_intf[] = "xyz.openbmc_project.State.Watchdog";
+static constexpr char prop_intf[] = "org.freedesktop.DBus.Properties";
+
+ipmi::ServiceCache WatchdogService::wd_service(wd_intf, wd_path);
+
+WatchdogService::WatchdogService() : bus(ipmid_get_sd_bus_connection()) {}
+
+void WatchdogService::resetTimeRemaining(bool enableWatchdog)
+{
+ bool wasValid = wd_service.isValid(bus);
+ auto request = wd_service.newMethodCall(bus, wd_intf, "ResetTimeRemaining");
+ request.append(enableWatchdog);
+ try
+ {
+ auto response = bus.call(request);
+ }
+ catch (const std::exception& e)
+ {
+ wd_service.invalidate();
+ if (wasValid)
+ {
+ // Retry the request once in case the cached service was stale
+ return resetTimeRemaining(enableWatchdog);
+ }
+ lg2::error("WatchdogService: Method error resetting time remaining, "
+ "ENABLE_WATCHDOG: {ENABLE_WATCHDOG}, ERROR: {ERROR}",
+ "ENABLE_WATCHDOG", enableWatchdog, "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+WatchdogService::Properties WatchdogService::getProperties()
+{
+ bool wasValid = wd_service.isValid(bus);
+ auto request = wd_service.newMethodCall(bus, prop_intf, "GetAll");
+ request.append(wd_intf);
+
+ std::map<std::string, std::variant<bool, uint64_t, std::string>> properties;
+ try
+ {
+ auto response = bus.call(request);
+ response.read(properties);
+ }
+ catch (const std::exception& e)
+ {
+ wd_service.invalidate();
+ if (wasValid)
+ {
+ // Retry the request once in case the cached service was stale
+ return getProperties();
+ }
+ lg2::error("WatchdogService: Method error getting properties: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ try
+ {
+ Properties wd_prop;
+ wd_prop.initialized = std::get<bool>(properties.at("Initialized"));
+ wd_prop.enabled = std::get<bool>(properties.at("Enabled"));
+ wd_prop.expireAction = Watchdog::convertActionFromString(
+ std::get<std::string>(properties.at("ExpireAction")));
+ wd_prop.timerUse = Watchdog::convertTimerUseFromString(
+ std::get<std::string>(properties.at("CurrentTimerUse")));
+ wd_prop.expiredTimerUse = Watchdog::convertTimerUseFromString(
+ std::get<std::string>(properties.at("ExpiredTimerUse")));
+
+ wd_prop.interval = std::get<uint64_t>(properties.at("Interval"));
+ wd_prop.timeRemaining =
+ std::get<uint64_t>(properties.at("TimeRemaining"));
+ return wd_prop;
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("WatchdogService: Decode error in get properties: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ // Needed instead of elog<InternalFailure>() since the compiler can't
+ // deduce the that elog<>() always throws
+ throw std::runtime_error(
+ "WatchdogService: Should not reach end of getProperties");
+}
+
+template <typename T>
+T WatchdogService::getProperty(const std::string& key)
+{
+ bool wasValid = wd_service.isValid(bus);
+ auto request = wd_service.newMethodCall(bus, prop_intf, "Get");
+ request.append(wd_intf, key);
+ try
+ {
+ auto response = bus.call(request);
+ std::variant<T> value;
+ response.read(value);
+ return std::get<T>(value);
+ }
+ catch (const std::exception& e)
+ {
+ wd_service.invalidate();
+ if (wasValid)
+ {
+ // Retry the request once in case the cached service was stale
+ return getProperty<T>(key);
+ }
+ lg2::error("WatchdogService: Method error getting {PROPERTY}: {ERROR}",
+ "PROPERTY", key, "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ // Needed instead of elog<InternalFailure>() since the compiler can't
+ // deduce the that elog<>() always throws
+ throw std::runtime_error(
+ "WatchdogService: Should not reach end of getProperty");
+}
+
+template <typename T>
+void WatchdogService::setProperty(const std::string& key, const T& val)
+{
+ bool wasValid = wd_service.isValid(bus);
+ auto request = wd_service.newMethodCall(bus, prop_intf, "Set");
+ request.append(wd_intf, key, std::variant<T>(val));
+ try
+ {
+ auto response = bus.call(request);
+ }
+ catch (const std::exception& e)
+ {
+ wd_service.invalidate();
+ if (wasValid)
+ {
+ // Retry the request once in case the cached service was stale
+ setProperty(key, val);
+ return;
+ }
+ lg2::error("WatchdogService: Method error setting {PROPERTY}: {ERROR}",
+ "PROPERTY", key, "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+bool WatchdogService::getInitialized()
+{
+ return getProperty<bool>("Initialized");
+}
+
+void WatchdogService::setInitialized(bool initialized)
+{
+ setProperty("Initialized", initialized);
+}
+
+void WatchdogService::setEnabled(bool enabled)
+{
+ setProperty("Enabled", enabled);
+}
+
+void WatchdogService::setLogTimeout(bool LogTimeout)
+{
+ setProperty("LogTimeout", LogTimeout);
+}
+
+void WatchdogService::setExpireAction(Action expireAction)
+{
+ setProperty("ExpireAction", convertForMessage(expireAction));
+}
+
+void WatchdogService::setTimerUse(TimerUse timerUse)
+{
+ setProperty("CurrentTimerUse", convertForMessage(timerUse));
+}
+
+void WatchdogService::setExpiredTimerUse(TimerUse timerUse)
+{
+ setProperty("ExpiredTimerUse", convertForMessage(timerUse));
+}
+
+void WatchdogService::setInterval(uint64_t interval)
+{
+ setProperty("Interval", interval);
+}
diff --git a/app/watchdog_service.hpp b/app/watchdog_service.hpp
new file mode 100644
index 0000000..5717066
--- /dev/null
+++ b/app/watchdog_service.hpp
@@ -0,0 +1,122 @@
+#pragma once
+#include <ipmid/utils.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/State/Watchdog/server.hpp>
+
+/** @class WatchdogService
+ * @brief Access to the running OpenBMC watchdog implementation.
+ * @details Easy accessor for servers that implement the
+ * xyz.openbmc_project.State.Watchdog DBus API.
+ */
+class WatchdogService
+{
+ public:
+ WatchdogService();
+
+ using Action =
+ sdbusplus::server::xyz::openbmc_project::state::Watchdog::Action;
+ using TimerUse =
+ sdbusplus::server::xyz::openbmc_project::state::Watchdog::TimerUse;
+
+ /** @brief Resets the time remaining on the watchdog.
+ * Equivalent to setTimeRemaining(getInterval()).
+ * Optionally enables the watchdog.
+ *
+ * @param[in] enableWatchdog - Should the call also enable the watchdog
+ */
+ void resetTimeRemaining(bool enableWatchdog);
+
+ /** @brief Contains a copy of the properties enumerated by the
+ * watchdog service.
+ */
+ struct Properties
+ {
+ bool initialized;
+ bool enabled;
+ Action expireAction;
+ TimerUse timerUse;
+ TimerUse expiredTimerUse;
+ uint64_t interval;
+ uint64_t timeRemaining;
+ };
+
+ /** @brief Retrieves a copy of the currently set properties on the
+ * host watchdog
+ *
+ * @return A populated WatchdogProperties struct
+ */
+ Properties getProperties();
+
+ /** @brief Get the value of the initialized property on the host
+ * watchdog
+ *
+ * @return The value of the property
+ */
+ bool getInitialized();
+
+ /** @brief Sets the value of the initialized property on the host
+ * watchdog
+ *
+ * @param[in] initialized - The new initializedvalue
+ */
+ void setInitialized(bool initialized);
+
+ /** @brief Sets the value of the enabled property on the host watchdog
+ *
+ * @param[in] enabled - The new enabled value
+ */
+ void setEnabled(bool enabled);
+
+ /** @brief Sets the value of the LogTimeout property on the host watchdog
+ *
+ * @param[in] LogTimeout - The new LogTimeout value
+ */
+ void setLogTimeout(bool LogTimeout);
+
+ /** @brief Sets the value of the expireAction property on the host watchdog
+ *
+ * @param[in] expireAction - The new expireAction value
+ */
+ void setExpireAction(Action expireAction);
+
+ /** @brief Sets the value of the timerUse property on the host watchdog
+ *
+ * @param[in] timerUse - The new timerUse value
+ */
+ void setTimerUse(TimerUse timerUse);
+
+ /** @brief Sets the value of the ExpiredTimerUse property on the host
+ * watchdog
+ *
+ * @param[in] timerUse - The new timerUse value
+ */
+ void setExpiredTimerUse(TimerUse timerUse);
+
+ /** @brief Sets the value of the interval property on the host watchdog
+ *
+ * @param[in] interval - The new interval value
+ */
+ void setInterval(uint64_t interval);
+
+ private:
+ /** @brief sdbusplus handle */
+ sdbusplus::bus_t bus;
+ /** @brief The name of the mapped host watchdog service */
+ static ipmi::ServiceCache wd_service;
+
+ /** @brief Gets the value of the property on the host watchdog
+ *
+ * @param[in] key - The name of the property
+ * @return The value of the property
+ */
+ template <typename T>
+ T getProperty(const std::string& key);
+
+ /** @brief Sets the value of the property on the host watchdog
+ *
+ * @param[in] key - The name of the property
+ * @param[in] val - The new value
+ */
+ template <typename T>
+ void setProperty(const std::string& key, const T& val);
+};
diff --git a/apphandler.cpp b/apphandler.cpp
new file mode 100644
index 0000000..1e32061
--- /dev/null
+++ b/apphandler.cpp
@@ -0,0 +1,1873 @@
+#include "config.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <systemd/sd-bus.h>
+#include <unistd.h>
+
+#include <app/channel.hpp>
+#include <app/watchdog.hpp>
+#include <apphandler.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/sessiondef.hpp>
+#include <ipmid/sessionhelper.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sys_info_param.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Power/ACPIPowerState/server.hpp>
+#include <xyz/openbmc_project/Software/Activation/server.hpp>
+#include <xyz/openbmc_project/Software/Version/server.hpp>
+#include <xyz/openbmc_project/State/BMC/server.hpp>
+
+#include <algorithm>
+#include <array>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <regex>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <vector>
+
+extern sd_bus* bus;
+
+constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC";
+constexpr auto bmc_state_property = "CurrentBMCState";
+constexpr auto versionPurposeHostEnd = ".Host";
+
+static constexpr auto redundancyIntf =
+ "xyz.openbmc_project.Software.RedundancyPriority";
+static constexpr auto versionIntf = "xyz.openbmc_project.Software.Version";
+static constexpr auto activationIntf =
+ "xyz.openbmc_project.Software.Activation";
+static constexpr auto softwareRoot = "/xyz/openbmc_project/software";
+
+void registerNetFnAppFunctions() __attribute__((constructor));
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+using Version = sdbusplus::server::xyz::openbmc_project::software::Version;
+using Activation =
+ sdbusplus::server::xyz::openbmc_project::software::Activation;
+using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
+namespace fs = std::filesystem;
+
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+typedef struct
+{
+ uint8_t busId;
+ uint8_t targetAddr;
+ uint8_t targetAddrMask;
+ std::vector<uint8_t> data;
+ std::vector<uint8_t> dataMask;
+} i2cControllerWRAllowlist;
+
+static std::vector<i2cControllerWRAllowlist>& getWRAllowlist()
+{
+ static std::vector<i2cControllerWRAllowlist> wrAllowlist;
+ return wrAllowlist;
+}
+
+static constexpr const char* i2cControllerWRAllowlistFile =
+ "/usr/share/ipmi-providers/master_write_read_white_list.json";
+
+static constexpr const char* filtersStr = "filters";
+static constexpr const char* busIdStr = "busId";
+static constexpr const char* targetAddrStr = "slaveAddr";
+static constexpr const char* targetAddrMaskStr = "slaveAddrMask";
+static constexpr const char* cmdStr = "command";
+static constexpr const char* cmdMaskStr = "commandMask";
+static constexpr int base_16 = 16;
+#endif // ENABLE_I2C_WHITELIST_CHECK
+static constexpr uint8_t oemCmdStart = 192;
+static constexpr uint8_t invalidParamSelectorStart = 8;
+static constexpr uint8_t invalidParamSelectorEnd = 191;
+
+/**
+ * @brief Returns the Version info from primary s/w object
+ *
+ * Get the Version info from the active s/w object which is having high
+ * "Priority" value(a smaller number is a higher priority) and "Purpose"
+ * is "BMC" from the list of all s/w objects those are implementing
+ * RedundancyPriority interface from the given softwareRoot path.
+ *
+ * @return On success returns the Version info from primary s/w object.
+ *
+ */
+std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx)
+{
+ std::string revision{};
+ ipmi::ObjectTree objectTree;
+ try
+ {
+ objectTree =
+ ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to fetch redundancy object from dbus, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", redundancyIntf, "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ auto objectFound = false;
+ for (auto& softObject : objectTree)
+ {
+ auto service =
+ ipmi::getService(*ctx->bus, redundancyIntf, softObject.first);
+ auto objValueTree =
+ ipmi::getManagedObjects(*ctx->bus, service, softwareRoot);
+
+ auto minPriority = 0xFF;
+ for (const auto& objIter : objValueTree)
+ {
+ try
+ {
+ auto& intfMap = objIter.second;
+ auto& redundancyPriorityProps = intfMap.at(redundancyIntf);
+ auto& versionProps = intfMap.at(versionIntf);
+ auto& activationProps = intfMap.at(activationIntf);
+ auto priority =
+ std::get<uint8_t>(redundancyPriorityProps.at("Priority"));
+ auto purpose =
+ std::get<std::string>(versionProps.at("Purpose"));
+ auto activation =
+ std::get<std::string>(activationProps.at("Activation"));
+ auto version =
+ std::get<std::string>(versionProps.at("Version"));
+ if ((Version::convertVersionPurposeFromString(purpose) ==
+ Version::VersionPurpose::BMC) &&
+ (Activation::convertActivationsFromString(activation) ==
+ Activation::Activations::Active))
+ {
+ if (priority < minPriority)
+ {
+ minPriority = priority;
+ objectFound = true;
+ revision = std::move(version);
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("error message: {ERROR}", "ERROR", e);
+ }
+ }
+ }
+
+ if (!objectFound)
+ {
+ lg2::error("Could not found an BMC software Object");
+ elog<InternalFailure>();
+ }
+
+ return revision;
+}
+
+bool getCurrentBmcState()
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ // Get the Inventory object implementing the BMC interface
+ ipmi::DbusObjectInfo bmcObject =
+ ipmi::getDbusObject(bus, bmc_state_interface);
+ auto variant =
+ ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first,
+ bmc_state_interface, bmc_state_property);
+
+ return std::holds_alternative<std::string>(variant) &&
+ BMC::convertBMCStateFromString(std::get<std::string>(variant)) ==
+ BMC::BMCState::Ready;
+}
+
+bool getCurrentBmcStateWithFallback(const bool fallbackAvailability)
+{
+ try
+ {
+ return getCurrentBmcState();
+ }
+ catch (...)
+ {
+ // Nothing provided the BMC interface, therefore return whatever was
+ // configured as the default.
+ return fallbackAvailability;
+ }
+}
+
+namespace acpi_state
+{
+using namespace sdbusplus::server::xyz::openbmc_project::control::power;
+
+const static constexpr char* acpiInterface =
+ "xyz.openbmc_project.Control.Power.ACPIPowerState";
+const static constexpr char* sysACPIProp = "SysACPIStatus";
+const static constexpr char* devACPIProp = "DevACPIStatus";
+
+enum class PowerStateType : uint8_t
+{
+ sysPowerState = 0x00,
+ devPowerState = 0x01,
+};
+
+// Defined in 20.6 of ipmi doc
+enum class PowerState : uint8_t
+{
+ s0G0D0 = 0x00,
+ s1D1 = 0x01,
+ s2D2 = 0x02,
+ s3D3 = 0x03,
+ s4 = 0x04,
+ s5G2 = 0x05,
+ s4S5 = 0x06,
+ g3 = 0x07,
+ sleep = 0x08,
+ g1Sleep = 0x09,
+ override = 0x0a,
+ legacyOn = 0x20,
+ legacyOff = 0x21,
+ unknown = 0x2a,
+ noChange = 0x7f,
+};
+
+static constexpr uint8_t stateChanged = 0x80;
+
+std::map<ACPIPowerState::ACPI, PowerState> dbusToIPMI = {
+ {ACPIPowerState::ACPI::S0_G0_D0, PowerState::s0G0D0},
+ {ACPIPowerState::ACPI::S1_D1, PowerState::s1D1},
+ {ACPIPowerState::ACPI::S2_D2, PowerState::s2D2},
+ {ACPIPowerState::ACPI::S3_D3, PowerState::s3D3},
+ {ACPIPowerState::ACPI::S4, PowerState::s4},
+ {ACPIPowerState::ACPI::S5_G2, PowerState::s5G2},
+ {ACPIPowerState::ACPI::S4_S5, PowerState::s4S5},
+ {ACPIPowerState::ACPI::G3, PowerState::g3},
+ {ACPIPowerState::ACPI::SLEEP, PowerState::sleep},
+ {ACPIPowerState::ACPI::G1_SLEEP, PowerState::g1Sleep},
+ {ACPIPowerState::ACPI::OVERRIDE, PowerState::override},
+ {ACPIPowerState::ACPI::LEGACY_ON, PowerState::legacyOn},
+ {ACPIPowerState::ACPI::LEGACY_OFF, PowerState::legacyOff},
+ {ACPIPowerState::ACPI::Unknown, PowerState::unknown}};
+
+bool isValidACPIState(acpi_state::PowerStateType type, uint8_t state)
+{
+ if (type == acpi_state::PowerStateType::sysPowerState)
+ {
+ if ((state <= static_cast<uint8_t>(acpi_state::PowerState::override)) ||
+ (state == static_cast<uint8_t>(acpi_state::PowerState::legacyOn)) ||
+ (state ==
+ static_cast<uint8_t>(acpi_state::PowerState::legacyOff)) ||
+ (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+ (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (type == acpi_state::PowerStateType::devPowerState)
+ {
+ if ((state <= static_cast<uint8_t>(acpi_state::PowerState::s3D3)) ||
+ (state == static_cast<uint8_t>(acpi_state::PowerState::unknown)) ||
+ (state == static_cast<uint8_t>(acpi_state::PowerState::noChange)))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ return false;
+}
+} // namespace acpi_state
+
+/** @brief implements Set ACPI Power State command
+ * @param sysAcpiState - ACPI system power state to set
+ * @param devAcpiState - ACPI device power state to set
+ *
+ * @return IPMI completion code on success
+ **/
+ipmi::RspType<> ipmiSetAcpiPowerState(uint8_t sysAcpiState,
+ uint8_t devAcpiState)
+{
+ auto s = static_cast<uint8_t>(acpi_state::PowerState::unknown);
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ auto value = acpi_state::ACPIPowerState::ACPI::Unknown;
+
+ if (sysAcpiState & acpi_state::stateChanged)
+ {
+ // set system power state
+ s = sysAcpiState & ~acpi_state::stateChanged;
+
+ if (!acpi_state::isValidACPIState(
+ acpi_state::PowerStateType::sysPowerState, s))
+ {
+ lg2::error("set_acpi_power sys invalid input, S: {S}", "S", s);
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // valid input
+ if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+ {
+ lg2::debug("No change for system power state");
+ }
+ else
+ {
+ auto found = std::find_if(
+ acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+ [&s](const auto& iter) {
+ return (static_cast<uint8_t>(iter.second) == s);
+ });
+
+ value = found->first;
+
+ try
+ {
+ auto acpiObject =
+ ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+ ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+ acpi_state::acpiInterface,
+ acpi_state::sysACPIProp,
+ convertForMessage(value));
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Failed in set ACPI system property: {ERROR}",
+ "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ }
+ else
+ {
+ lg2::debug("Do not change system power state");
+ }
+
+ if (devAcpiState & acpi_state::stateChanged)
+ {
+ // set device power state
+ s = devAcpiState & ~acpi_state::stateChanged;
+ if (!acpi_state::isValidACPIState(
+ acpi_state::PowerStateType::devPowerState, s))
+ {
+ lg2::error("set_acpi_power dev invalid input, S: {S}", "S", s);
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // valid input
+ if (s == static_cast<uint8_t>(acpi_state::PowerState::noChange))
+ {
+ lg2::debug("No change for device power state");
+ }
+ else
+ {
+ auto found = std::find_if(
+ acpi_state::dbusToIPMI.begin(), acpi_state::dbusToIPMI.end(),
+ [&s](const auto& iter) {
+ return (static_cast<uint8_t>(iter.second) == s);
+ });
+
+ value = found->first;
+
+ try
+ {
+ auto acpiObject =
+ ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+ ipmi::setDbusProperty(bus, acpiObject.second, acpiObject.first,
+ acpi_state::acpiInterface,
+ acpi_state::devACPIProp,
+ convertForMessage(value));
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Failed in set ACPI device property: {ERROR}",
+ "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ }
+ else
+ {
+ lg2::debug("Do not change device power state");
+ }
+ return ipmi::responseSuccess();
+}
+
+/**
+ * @brief implements the get ACPI power state command
+ *
+ * @return IPMI completion code plus response data on success.
+ * - ACPI system power state
+ * - ACPI device power state
+ **/
+ipmi::RspType<uint8_t, // acpiSystemPowerState
+ uint8_t // acpiDevicePowerState
+ >
+ ipmiGetAcpiPowerState()
+{
+ uint8_t sysAcpiState;
+ uint8_t devAcpiState;
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ try
+ {
+ auto acpiObject = ipmi::getDbusObject(bus, acpi_state::acpiInterface);
+
+ auto sysACPIVal = ipmi::getDbusProperty(
+ bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+ acpi_state::sysACPIProp);
+ auto sysACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+ std::get<std::string>(sysACPIVal));
+ sysAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI));
+
+ auto devACPIVal = ipmi::getDbusProperty(
+ bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface,
+ acpi_state::devACPIProp);
+ auto devACPI = acpi_state::ACPIPowerState::convertACPIFromString(
+ std::get<std::string>(devACPIVal));
+ devAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI));
+ }
+ catch (const InternalFailure& e)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(sysAcpiState, devAcpiState);
+}
+
+typedef struct
+{
+ char major;
+ char minor;
+ uint8_t aux[4];
+} Revision;
+
+/* Use regular expression searching matched pattern X.Y, and convert it to */
+/* Major (X) and Minor (Y) version. */
+/* Example: */
+/* version = 2.14.0-dev */
+/* ^ ^ */
+/* | |---------------- Minor */
+/* |------------------ Major */
+/* */
+/* Default regex string only tries to match Major and Minor version. */
+/* */
+/* To match more firmware version info, platforms need to define it own */
+/* regex string to match more strings, and assign correct mapping index in */
+/* matches array. */
+/* */
+/* matches[0]: matched index for major ver */
+/* matches[1]: matched index for minor ver */
+/* matches[2]: matched index for aux[0] (set 0 to skip) */
+/* matches[3]: matched index for aux[1] (set 0 to skip) */
+/* matches[4]: matched index for aux[2] (set 0 to skip) */
+/* matches[5]: matched index for aux[3] (set 0 to skip) */
+/* Example: */
+/* regex = "([\d]+).([\d]+).([\d]+)-dev-([\d]+)-g([0-9a-fA-F]{2}) */
+/* ([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})" */
+/* matches = {1,2,5,6,7,8} */
+/* version = 2.14.0-dev-750-g37a7c5ad1-dirty */
+/* ^ ^ ^ ^ ^ ^ ^ ^ */
+/* | | | | | | | | */
+/* | | | | | | | |-- Aux byte 3 (0xAD), index 8 */
+/* | | | | | | |---- Aux byte 2 (0xC5), index 7 */
+/* | | | | | |------ Aux byte 1 (0xA7), index 6 */
+/* | | | | |-------- Aux byte 0 (0x37), index 5 */
+/* | | | |------------- Not used, index 4 */
+/* | | |------------------- Not used, index 3 */
+/* | |---------------------- Minor (14), index 2 */
+/* |------------------------ Major (2), index 1 */
+int convertVersion(std::string s, Revision& rev)
+{
+ static const std::vector<size_t> matches = {
+ MAJOR_MATCH_INDEX, MINOR_MATCH_INDEX, AUX_0_MATCH_INDEX,
+ AUX_1_MATCH_INDEX, AUX_2_MATCH_INDEX, AUX_3_MATCH_INDEX};
+ std::regex fw_regex(FW_VER_REGEX);
+ std::smatch m;
+ Revision r = {0};
+ size_t val;
+
+ if (std::regex_search(s, m, fw_regex))
+ {
+ if (m.size() < *std::max_element(matches.begin(), matches.end()))
+ { // max index higher than match count
+ return -1;
+ }
+
+ // convert major
+ {
+ std::string str = m[matches[0]].str();
+ const auto& [ptr, ec] =
+ std::from_chars(str.data(), str.data() + str.size(), val);
+ if (ec != std::errc() || ptr != str.data() + str.size())
+ { // failed to convert major string
+ return -1;
+ }
+
+ if (val >= 2000)
+ { // For the platforms use year as major version, it would expect to
+ // have major version between 0 - 99. If the major version is
+ // greater than or equal to 2000, it is treated as a year and
+ // converted to 0 - 99.
+ r.major = val % 100;
+ }
+ else
+ {
+ r.major = val & 0x7F;
+ }
+ }
+
+ // convert minor
+ {
+ std::string str = m[matches[1]].str();
+ const auto& [ptr, ec] =
+ std::from_chars(str.data(), str.data() + str.size(), val);
+ if (ec != std::errc() || ptr != str.data() + str.size())
+ { // failed to convert minor string
+ return -1;
+ }
+ r.minor = val & 0xFF;
+ }
+
+ // convert aux bytes
+ {
+ size_t i;
+ for (i = 0; i < 4; i++)
+ {
+ if (matches[i + 2] == 0)
+ {
+ continue;
+ }
+
+ std::string str = m[matches[i + 2]].str();
+ const char* cstr = str.c_str();
+ auto [ptr,
+ ec] = std::from_chars(cstr, cstr + str.size(), val, 16);
+ if (ec != std::errc() || ptr != cstr + str.size())
+ { // failed to convert aux byte string
+ break;
+ }
+
+ r.aux[i] = val & 0xFF;
+ }
+
+ if (i != 4)
+ { // something wrong durign converting aux bytes
+ return -1;
+ }
+ }
+
+ // all matched
+ rev = r;
+ return 0;
+ }
+
+ return -1;
+}
+
+/* @brief: Implement the Get Device ID IPMI command per the IPMI spec
+ * @param[in] ctx - shared_ptr to an IPMI context struct
+ *
+ * @returns IPMI completion code plus response data
+ * - Device ID (manufacturer defined)
+ * - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit]
+ * - FW revision major[7 bits] (binary encoded); available[1 bit]
+ * - FW Revision minor (BCD encoded)
+ * - IPMI version (0x02 for IPMI 2.0)
+ * - device support (bitfield of supported options)
+ * - MFG IANA ID (3 bytes)
+ * - product ID (2 bytes)
+ * - AUX info (4 bytes)
+ */
+ipmi::RspType<uint8_t, // Device ID
+ uint8_t, // Device Revision
+ uint8_t, // Firmware Revision Major
+ uint8_t, // Firmware Revision minor
+ uint8_t, // IPMI version
+ uint8_t, // Additional device support
+ uint24_t, // MFG ID
+ uint16_t, // Product ID
+ uint32_t // AUX info
+ >
+ ipmiAppGetDeviceId([[maybe_unused]] ipmi::Context::ptr ctx)
+{
+ static struct
+ {
+ uint8_t id;
+ uint8_t revision;
+ uint8_t fw[2];
+ uint8_t ipmiVer;
+ uint8_t addnDevSupport;
+ uint24_t manufId;
+ uint16_t prodId;
+ uint32_t aux;
+ } devId;
+ static bool dev_id_initialized = false;
+ static bool defaultActivationSetting = true;
+ const char* filename = "/usr/share/ipmi-providers/dev_id.json";
+ constexpr auto ipmiDevIdStateShift = 7;
+ constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift);
+
+#ifdef GET_DBUS_ACTIVE_SOFTWARE
+ static bool haveBMCVersion = false;
+ if (!haveBMCVersion || !dev_id_initialized)
+ {
+ int r = -1;
+ Revision rev = {0, 0, {0, 0, 0, 0}};
+ try
+ {
+ auto version = getActiveSoftwareVersionInfo(ctx);
+ r = convertVersion(version, rev);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("error message: {ERROR}", "ERROR", e);
+ }
+
+ if (r >= 0)
+ {
+ // bit7 identifies if the device is available
+ // 0=normal operation
+ // 1=device firmware, SDR update,
+ // or self-initialization in progress.
+ // The availability may change in run time, so mask here
+ // and initialize later.
+ devId.fw[0] = rev.major & ipmiDevIdFw1Mask;
+
+ rev.minor = (rev.minor > 99 ? 99 : rev.minor);
+ devId.fw[1] = rev.minor % 10 + (rev.minor / 10) * 16;
+ std::memcpy(&devId.aux, rev.aux, sizeof(rev.aux));
+ haveBMCVersion = true;
+ }
+ }
+#endif
+ if (!dev_id_initialized)
+ {
+ // IPMI Spec version 2.0
+ devId.ipmiVer = 2;
+
+ std::ifstream devIdFile(filename);
+ if (devIdFile.is_open())
+ {
+ auto data = nlohmann::json::parse(devIdFile, nullptr, false);
+ if (!data.is_discarded())
+ {
+ devId.id = data.value("id", 0);
+ devId.revision = data.value("revision", 0);
+ devId.addnDevSupport = data.value("addn_dev_support", 0);
+ devId.manufId = data.value("manuf_id", 0);
+ devId.prodId = data.value("prod_id", 0);
+#ifdef GET_DBUS_ACTIVE_SOFTWARE
+ if (!(AUX_0_MATCH_INDEX || AUX_1_MATCH_INDEX ||
+ AUX_2_MATCH_INDEX || AUX_3_MATCH_INDEX))
+#endif
+ {
+ devId.aux = data.value("aux", 0);
+ }
+
+ if (data.contains("firmware_revision"))
+ {
+ const auto& firmwareRevision = data.at("firmware_revision");
+ if (firmwareRevision.contains("major"))
+ {
+ firmwareRevision.at("major").get_to(devId.fw[0]);
+ }
+ if (firmwareRevision.contains("minor"))
+ {
+ firmwareRevision.at("minor").get_to(devId.fw[1]);
+ }
+ }
+
+ // Set the availablitity of the BMC.
+ defaultActivationSetting = data.value("availability", true);
+
+ // Don't read the file every time if successful
+ dev_id_initialized = true;
+ }
+ else
+ {
+ lg2::error("Device ID JSON parser failure");
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ else
+ {
+ lg2::error("Device ID file not found");
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+
+ // Set availability to the actual current BMC state
+ devId.fw[0] &= ipmiDevIdFw1Mask;
+ if (!getCurrentBmcStateWithFallback(defaultActivationSetting))
+ {
+ devId.fw[0] |= (1 << ipmiDevIdStateShift);
+ }
+
+ return ipmi::responseSuccess(
+ devId.id, devId.revision, devId.fw[0], devId.fw[1], devId.ipmiVer,
+ devId.addnDevSupport, devId.manufId, devId.prodId, devId.aux);
+}
+
+auto ipmiAppGetSelfTestResults() -> ipmi::RspType<uint8_t, uint8_t>
+{
+ // Byte 2:
+ // 55h - No error.
+ // 56h - Self Test function not implemented in this controller.
+ // 57h - Corrupted or inaccesssible data or devices.
+ // 58h - Fatal hardware error.
+ // FFh - reserved.
+ // all other: Device-specific 'internal failure'.
+ // Byte 3:
+ // For byte 2 = 55h, 56h, FFh: 00h
+ // For byte 2 = 58h, all other: Device-specific
+ // For byte 2 = 57h: self-test error bitfield.
+ // Note: returning 57h does not imply that all test were run.
+ // [7] 1b = Cannot access SEL device.
+ // [6] 1b = Cannot access SDR Repository.
+ // [5] 1b = Cannot access BMC FRU device.
+ // [4] 1b = IPMB signal lines do not respond.
+ // [3] 1b = SDR Repository empty.
+ // [2] 1b = Internal Use Area of BMC FRU corrupted.
+ // [1] 1b = controller update 'boot block' firmware corrupted.
+ // [0] 1b = controller operational firmware corrupted.
+ constexpr uint8_t notImplemented = 0x56;
+ constexpr uint8_t zero = 0;
+ return ipmi::responseSuccess(notImplemented, zero);
+}
+
+static constexpr size_t uuidBinaryLength = 16;
+static std::array<uint8_t, uuidBinaryLength> rfc4122ToIpmi(std::string rfc4122)
+{
+ using Argument = xyz::openbmc_project::common::InvalidArgument;
+ // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+ // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte
+ // order
+ // Ex: 0x2332fc2c40e66298e511f2782395a361
+ constexpr size_t uuidHexLength = (2 * uuidBinaryLength);
+ constexpr size_t uuidRfc4122Length = (uuidHexLength + 4);
+ std::array<uint8_t, uuidBinaryLength> uuid;
+ if (rfc4122.size() == uuidRfc4122Length)
+ {
+ rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'),
+ rfc4122.end());
+ }
+ if (rfc4122.size() != uuidHexLength)
+ {
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+ Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+ }
+ for (size_t ind = 0; ind < uuidHexLength; ind += 2)
+ {
+ char v[3];
+ v[0] = rfc4122[ind];
+ v[1] = rfc4122[ind + 1];
+ v[2] = 0;
+ size_t err;
+ long b;
+ try
+ {
+ b = std::stoul(v, &err, 16);
+ }
+ catch (const std::exception& e)
+ {
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+ Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+ }
+ // check that exactly two ascii bytes were converted
+ if (err != 2)
+ {
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"),
+ Argument::ARGUMENT_VALUE(rfc4122.c_str()));
+ }
+ uuid[uuidBinaryLength - (ind / 2) - 1] = static_cast<uint8_t>(b);
+ }
+ return uuid;
+}
+
+auto ipmiAppGetDeviceGuid()
+ -> ipmi::RspType<std::array<uint8_t, uuidBinaryLength>>
+{
+ // return a fixed GUID based on /etc/machine-id
+ // This should match the /redfish/v1/Managers/bmc's UUID data
+
+ // machine specific application ID (for BMC ID)
+ // generated by systemd-id128 -p new as per man page
+ static constexpr sd_id128_t bmcUuidAppId = SD_ID128_MAKE(
+ e0, e1, 73, 76, 64, 61, 47, da, a5, 0c, d0, cc, 64, 12, 45, 78);
+
+ sd_id128_t bmcUuid;
+ // create the UUID from /etc/machine-id via the systemd API
+ sd_id128_get_machine_app_specific(bmcUuidAppId, &bmcUuid);
+
+ char bmcUuidCstr[SD_ID128_STRING_MAX];
+ std::string systemUuid = sd_id128_to_string(bmcUuid, bmcUuidCstr);
+
+ std::array<uint8_t, uuidBinaryLength> uuid = rfc4122ToIpmi(systemUuid);
+ return ipmi::responseSuccess(uuid);
+}
+
+auto ipmiAppGetBtCapabilities()
+ -> ipmi::RspType<uint8_t, uint8_t, uint8_t, uint8_t, uint8_t>
+{
+ // Per IPMI 2.0 spec, the input and output buffer size must be the max
+ // buffer size minus one byte to allocate space for the length byte.
+ constexpr uint8_t nrOutstanding = 0x01;
+ constexpr uint8_t inputBufferSize = MAX_IPMI_BUFFER - 1;
+ constexpr uint8_t outputBufferSize = MAX_IPMI_BUFFER - 1;
+ constexpr uint8_t transactionTime = 0x0A;
+ constexpr uint8_t nrRetries = 0x01;
+
+ return ipmi::responseSuccess(nrOutstanding, inputBufferSize,
+ outputBufferSize, transactionTime, nrRetries);
+}
+
+auto ipmiAppGetSystemGuid(ipmi::Context::ptr& ctx)
+ -> ipmi::RspType<std::array<uint8_t, 16>>
+{
+ static constexpr auto uuidInterface = "xyz.openbmc_project.Common.UUID";
+ static constexpr auto uuidProperty = "UUID";
+
+ // Get the Inventory object implementing BMC interface
+ ipmi::DbusObjectInfo objectInfo{};
+ boost::system::error_code ec = ipmi::getDbusObject(
+ ctx, uuidInterface, ipmi::sensor::inventoryRoot, objectInfo);
+ if (ec.value())
+ {
+ lg2::error("Failed to locate System UUID object, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", uuidInterface, "ERROR", ec.message());
+ }
+
+ // Read UUID property value from bmcObject
+ // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223
+ std::string rfc4122Uuid{};
+ ec = ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first,
+ uuidInterface, uuidProperty, rfc4122Uuid);
+ if (ec.value())
+ {
+ lg2::error("Failed to read System UUID property, "
+ "interface: {INTERFACE}, property: {PROPERTY}, "
+ "error: {ERROR}",
+ "INTERFACE", uuidInterface, "PROPERTY", uuidProperty,
+ "ERROR", ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+ std::array<uint8_t, 16> uuid;
+ try
+ {
+ // convert to IPMI format
+ uuid = rfc4122ToIpmi(rfc4122Uuid);
+ }
+ catch (const InvalidArgument& e)
+ {
+ lg2::error("Failed in parsing BMC UUID property, "
+ "interface: {INTERFACE}, property: {PROPERTY}, "
+ "value: {VALUE}, error: {ERROR}",
+ "INTERFACE", uuidInterface, "PROPERTY", uuidProperty,
+ "VALUE", rfc4122Uuid, "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ return ipmi::responseSuccess(uuid);
+}
+
+/**
+ * @brief set the session state as teardown
+ *
+ * This function is to set the session state to tear down in progress if the
+ * state is active.
+ *
+ * @param[in] busp - Dbus obj
+ * @param[in] service - service name
+ * @param[in] obj - object path
+ *
+ * @return success completion code if it sets the session state to
+ * tearDownInProgress else return the corresponding error completion code.
+ **/
+uint8_t setSessionState(std::shared_ptr<sdbusplus::asio::connection>& busp,
+ const std::string& service, const std::string& obj)
+{
+ try
+ {
+ uint8_t sessionState = std::get<uint8_t>(ipmi::getDbusProperty(
+ *busp, service, obj, session::sessionIntf, "State"));
+
+ if (sessionState == static_cast<uint8_t>(session::State::active))
+ {
+ ipmi::setDbusProperty(
+ *busp, service, obj, session::sessionIntf, "State",
+ static_cast<uint8_t>(session::State::tearDownInProgress));
+ return ipmi::ccSuccess;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed in getting session state property, "
+ "service: {SERVICE}, object path: {OBJECT_PATH}, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "SERVICE", service, "OBJECT_PATH", obj, "INTERFACE",
+ session::sessionIntf, "ERROR", e);
+ return ipmi::ccUnspecifiedError;
+ }
+
+ return ipmi::ccInvalidFieldRequest;
+}
+
+ipmi::RspType<> ipmiAppCloseSession(uint32_t reqSessionId,
+ std::optional<uint8_t> requestSessionHandle)
+{
+ auto busp = getSdBus();
+ uint8_t reqSessionHandle =
+ requestSessionHandle.value_or(session::defaultSessionHandle);
+
+ if (reqSessionId == session::sessionZero &&
+ reqSessionHandle == session::defaultSessionHandle)
+ {
+ return ipmi::response(session::ccInvalidSessionId);
+ }
+
+ if (reqSessionId == session::sessionZero &&
+ reqSessionHandle == session::invalidSessionHandle)
+ {
+ return ipmi::response(session::ccInvalidSessionHandle);
+ }
+
+ if (reqSessionId != session::sessionZero &&
+ reqSessionHandle != session::defaultSessionHandle)
+ {
+ return ipmi::response(ipmi::ccInvalidFieldRequest);
+ }
+
+ try
+ {
+ ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects(
+ *busp, session::sessionManagerRootPath, session::sessionIntf);
+
+ for (auto& objectTreeItr : objectTree)
+ {
+ const std::string obj = objectTreeItr.first;
+
+ if (isSessionObjectMatched(obj, reqSessionId, reqSessionHandle))
+ {
+ auto& serviceMap = objectTreeItr.second;
+
+ // Session id and session handle are unique for each session.
+ // Session id and handler are retrived from the object path and
+ // object path will be unique for each session. Checking if
+ // multiple objects exist with same object path under multiple
+ // services.
+ if (serviceMap.size() != 1)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ auto itr = serviceMap.begin();
+ const std::string service = itr->first;
+ return ipmi::response(setSessionState(busp, service, obj));
+ }
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to fetch object from dbus, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", session::sessionIntf, "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseInvalidFieldRequest();
+}
+
+uint8_t getTotalSessionCount()
+{
+ uint8_t count = 0, ch = 0;
+
+ while (ch < ipmi::maxIpmiChannels &&
+ count < session::maxNetworkInstanceSupported)
+ {
+ ipmi::ChannelInfo chInfo{};
+ ipmi::getChannelInfo(ch, chInfo);
+ if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+ ipmi::EChannelMediumType::lan8032)
+ {
+ count++;
+ }
+ ch++;
+ }
+ return count * session::maxSessionCountPerChannel;
+}
+
+/**
+ * @brief get session info request data.
+ *
+ * This function validates the request data and retrive request session id,
+ * session handle.
+ *
+ * @param[in] ctx - context of current session.
+ * @param[in] sessionIndex - request session index
+ * @param[in] payload - input payload
+ * @param[in] reqSessionId - unpacked session Id will be asigned
+ * @param[in] reqSessionHandle - unpacked session handle will be asigned
+ *
+ * @return success completion code if request data is valid
+ * else return the correcponding error completion code.
+ **/
+uint8_t getSessionInfoRequestData(
+ const ipmi::Context::ptr ctx, const uint8_t sessionIndex,
+ ipmi::message::Payload& payload, uint32_t& reqSessionId,
+ uint8_t& reqSessionHandle)
+{
+ if ((sessionIndex > session::maxSessionCountPerChannel) &&
+ (sessionIndex < session::searchSessionByHandle))
+ {
+ return ipmi::ccInvalidFieldRequest;
+ }
+
+ switch (sessionIndex)
+ {
+ case session::searchCurrentSession:
+
+ ipmi::ChannelInfo chInfo;
+ ipmi::getChannelInfo(ctx->channel, chInfo);
+
+ if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) !=
+ ipmi::EChannelMediumType::lan8032)
+ {
+ return ipmi::ccInvalidFieldRequest;
+ }
+
+ if (!payload.fullyUnpacked())
+ {
+ return ipmi::ccReqDataLenInvalid;
+ }
+ // Check if current sessionId is 0, sessionId 0 is reserved.
+ if (ctx->sessionId == session::sessionZero)
+ {
+ return session::ccInvalidSessionId;
+ }
+ reqSessionId = ctx->sessionId;
+ break;
+
+ case session::searchSessionByHandle:
+
+ if ((payload.unpack(reqSessionHandle)) ||
+ (!payload.fullyUnpacked()))
+ {
+ return ipmi::ccReqDataLenInvalid;
+ }
+
+ if ((reqSessionHandle == session::sessionZero) ||
+ ((reqSessionHandle & session::multiIntfaceSessionHandleMask) >
+ session::maxSessionCountPerChannel))
+ {
+ return session::ccInvalidSessionHandle;
+ }
+ break;
+
+ case session::searchSessionById:
+
+ if ((payload.unpack(reqSessionId)) || (!payload.fullyUnpacked()))
+ {
+ return ipmi::ccReqDataLenInvalid;
+ }
+
+ if (reqSessionId == session::sessionZero)
+ {
+ return session::ccInvalidSessionId;
+ }
+ break;
+
+ default:
+ if (!payload.fullyUnpacked())
+ {
+ return ipmi::ccReqDataLenInvalid;
+ }
+ break;
+ }
+ return ipmi::ccSuccess;
+}
+
+uint8_t getSessionState(ipmi::Context::ptr ctx, const std::string& service,
+ const std::string& objPath, uint8_t& sessionState)
+{
+ boost::system::error_code ec = ipmi::getDbusProperty(
+ ctx, service, objPath, session::sessionIntf, "State", sessionState);
+ if (ec)
+ {
+ lg2::error("Failed to fetch state property, service: {SERVICE}, "
+ "object path: {OBJECTPATH}, interface: {INTERFACE}, "
+ "error: {ERROR}",
+ "SERVICE", service, "OBJECTPATH", objPath, "INTERFACE",
+ session::sessionIntf, "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+ }
+ return ipmi::ccSuccess;
+}
+
+static constexpr uint8_t macAddrLen = 6;
+/** Alias SessionDetails - contain the optional information about an
+ * RMCP+ session.
+ *
+ * @param userID - uint6_t session user ID (0-63)
+ * @param reserved - uint2_t reserved
+ * @param privilege - uint4_t session privilege (0-5)
+ * @param reserved - uint4_t reserved
+ * @param channel - uint4_t session channel number
+ * @param protocol - uint4_t session protocol
+ * @param remoteIP - uint32_t remote IP address
+ * @param macAddr - std::array<uint8_t, 6> mac address
+ * @param port - uint16_t remote port
+ */
+using SessionDetails =
+ std::tuple<uint2_t, uint6_t, uint4_t, uint4_t, uint4_t, uint4_t, uint32_t,
+ std::array<uint8_t, macAddrLen>, uint16_t>;
+
+/** @brief get session details for a given session
+ *
+ * @param[in] ctx - ipmi::Context pointer for accessing D-Bus
+ * @param[in] service - D-Bus service name to fetch details from
+ * @param[in] objPath - D-Bus object path for session
+ * @param[out] sessionHandle - return session handle for session
+ * @param[out] sessionState - return session state for session
+ * @param[out] details - return a SessionDetails tuple containing other
+ * session info
+ * @return - ipmi::Cc success or error code
+ */
+ipmi::Cc getSessionDetails(ipmi::Context::ptr ctx, const std::string& service,
+ const std::string& objPath, uint8_t& sessionHandle,
+ uint8_t& sessionState, SessionDetails& details)
+{
+ ipmi::PropertyMap sessionProps;
+ boost::system::error_code ec = ipmi::getAllDbusProperties(
+ ctx, service, objPath, session::sessionIntf, sessionProps);
+
+ if (ec)
+ {
+ lg2::error("Failed to fetch state property, service: {SERVICE}, "
+ "object path: {OBJECTPATH}, interface: {INTERFACE}, "
+ "error: {ERROR}",
+ "SERVICE", service, "OBJECTPATH", objPath, "INTERFACE",
+ session::sessionIntf, "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+ }
+
+ sessionState = ipmi::mappedVariant<uint8_t>(
+ sessionProps, "State", static_cast<uint8_t>(session::State::inactive));
+ if (sessionState == static_cast<uint8_t>(session::State::active))
+ {
+ sessionHandle =
+ ipmi::mappedVariant<uint8_t>(sessionProps, "SessionHandle", 0);
+ std::get<0>(details) =
+ ipmi::mappedVariant<uint8_t>(sessionProps, "UserID", 0xff);
+ // std::get<1>(details) = 0; // (default constructed to 0)
+ std::get<2>(details) =
+ ipmi::mappedVariant<uint8_t>(sessionProps, "CurrentPrivilege", 0);
+ // std::get<3>(details) = 0; // (default constructed to 0)
+ std::get<4>(details) =
+ ipmi::mappedVariant<uint8_t>(sessionProps, "ChannelNum", 0xff);
+ constexpr uint4_t rmcpPlusProtocol = 1;
+ std::get<5>(details) = rmcpPlusProtocol;
+ std::get<6>(details) =
+ ipmi::mappedVariant<uint32_t>(sessionProps, "RemoteIPAddr", 0);
+ // std::get<7>(details) = {{0}}; // default constructed to all 0
+ std::get<8>(details) =
+ ipmi::mappedVariant<uint16_t>(sessionProps, "RemotePort", 0);
+ }
+
+ return ipmi::ccSuccess;
+}
+
+ipmi::RspType<uint8_t, // session handle,
+ uint8_t, // total session count
+ uint8_t, // active session count
+ std::optional<SessionDetails>>
+ ipmiAppGetSessionInfo(ipmi::Context::ptr ctx, uint8_t sessionIndex,
+ ipmi::message::Payload& payload)
+{
+ uint32_t reqSessionId = 0;
+ uint8_t reqSessionHandle = session::defaultSessionHandle;
+ // initializing state to 0xff as 0 represents state as inactive.
+ uint8_t state = 0xFF;
+
+ uint8_t completionCode = getSessionInfoRequestData(
+ ctx, sessionIndex, payload, reqSessionId, reqSessionHandle);
+
+ if (completionCode)
+ {
+ return ipmi::response(completionCode);
+ }
+ ipmi::ObjectTree objectTree;
+ boost::system::error_code ec = ipmi::getAllDbusObjects(
+ ctx, session::sessionManagerRootPath, session::sessionIntf, objectTree);
+ if (ec)
+ {
+ lg2::error("Failed to fetch object from dbus, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "INTERFACE", session::sessionIntf, "ERROR", ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ uint8_t totalSessionCount = getTotalSessionCount();
+ uint8_t activeSessionCount = 0;
+ uint8_t sessionHandle = session::defaultSessionHandle;
+ uint8_t activeSessionHandle = 0;
+ std::optional<SessionDetails> maybeDetails;
+ uint8_t index = 0;
+ for (auto& objectTreeItr : objectTree)
+ {
+ uint32_t sessionId = 0;
+ std::string objectPath = objectTreeItr.first;
+
+ if (!parseCloseSessionInputPayload(objectPath, sessionId,
+ sessionHandle))
+ {
+ continue;
+ }
+ index++;
+ auto& serviceMap = objectTreeItr.second;
+ auto itr = serviceMap.begin();
+
+ if (serviceMap.size() != 1)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string service = itr->first;
+ uint8_t sessionState = 0;
+ completionCode =
+ getSessionState(ctx, service, objectPath, sessionState);
+ if (completionCode)
+ {
+ return ipmi::response(completionCode);
+ }
+
+ if (sessionState == static_cast<uint8_t>(session::State::active))
+ {
+ activeSessionCount++;
+ }
+
+ if (index == sessionIndex || reqSessionId == sessionId ||
+ reqSessionHandle == sessionHandle)
+ {
+ SessionDetails details{};
+ completionCode = getSessionDetails(ctx, service, objectPath,
+ sessionHandle, state, details);
+
+ if (completionCode)
+ {
+ return ipmi::response(completionCode);
+ }
+ activeSessionHandle = sessionHandle;
+ maybeDetails = std::move(details);
+ }
+ }
+
+ if (state == static_cast<uint8_t>(session::State::active) ||
+ state == static_cast<uint8_t>(session::State::tearDownInProgress))
+ {
+ return ipmi::responseSuccess(activeSessionHandle, totalSessionCount,
+ activeSessionCount, maybeDetails);
+ }
+
+ return ipmi::responseInvalidFieldRequest();
+}
+
+std::optional<std::string> getSysFWVersion(ipmi::Context::ptr& ctx)
+{
+ /*
+ * The System Firmware version is detected via following steps:
+ * - Get all of object paths that include
+ * "xyz.openbmc_project.Software.Version" interface.
+ * - Get the Purpose property of above object paths.
+ * - If the Purpose is Host then get the Version property.
+ */
+ ipmi::ObjectTree objectTree;
+ boost::system::error_code ec =
+ ipmi::getAllDbusObjects(ctx, softwareRoot, versionIntf, objectTree);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+
+ for (const auto& [objPath, serviceMap] : objectTree)
+ {
+ for (const auto& [service, intfs] : serviceMap)
+ {
+ ipmi::PropertyMap props;
+ ec = ipmi::getAllDbusProperties(ctx, service, objPath, versionIntf,
+ props);
+ if (ec.value())
+ {
+ continue;
+ }
+
+ std::string purposeProp = std::string(
+ ipmi::mappedVariant<std::string>(props, "Purpose", ""));
+
+ if (!purposeProp.ends_with(versionPurposeHostEnd))
+ {
+ continue;
+ }
+
+ std::string sysFWVersion = std::string(
+ ipmi::mappedVariant<std::string>(props, "Version", ""));
+
+ if (sysFWVersion.empty())
+ {
+ return std::nullopt;
+ }
+
+ return sysFWVersion;
+ }
+ }
+
+ return std::nullopt;
+}
+
+static std::unique_ptr<SysInfoParamStore> sysInfoParamStore;
+
+static std::string sysInfoReadSystemName()
+{
+ // Use the BMC hostname as the "System Name."
+ char hostname[HOST_NAME_MAX + 1] = {};
+ if (gethostname(hostname, HOST_NAME_MAX) != 0)
+ {
+ perror("System info parameter: system name");
+ }
+ return hostname;
+}
+
+static constexpr uint8_t paramRevision = 0x11;
+static constexpr size_t configParameterLength = 16;
+
+static constexpr size_t smallChunkSize = 14;
+static constexpr size_t fullChunkSize = 16;
+static constexpr uint8_t progressMask = 0x3;
+static constexpr uint8_t maxValidEncodingData = 0x02;
+
+static constexpr uint8_t setComplete = 0x0;
+static constexpr uint8_t setInProgress = 0x1;
+static uint8_t transferStatus = setComplete;
+
+static constexpr uint8_t configDataOverhead = 2;
+
+namespace ipmi
+{
+constexpr Cc ccParmNotSupported = 0x80;
+constexpr Cc ccSetInProgressActive = 0x81;
+
+static inline auto responseParmNotSupported()
+{
+ return response(ccParmNotSupported);
+}
+static inline auto responseSetInProgressActive()
+{
+ return response(ccSetInProgressActive);
+}
+} // namespace ipmi
+
+ipmi::RspType<uint8_t, // Parameter revision
+ std::optional<uint8_t>, // data1 / setSelector / ProgressStatus
+ std::optional<std::vector<uint8_t>>> // data2-17
+ ipmiAppGetSystemInfo(ipmi::Context::ptr ctx, uint7_t reserved,
+ bool getRevision, uint8_t paramSelector,
+ uint8_t setSelector, uint8_t BlockSelector)
+{
+ if (reserved || (paramSelector >= invalidParamSelectorStart &&
+ paramSelector <= invalidParamSelectorEnd))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (paramSelector >= oemCmdStart)
+ {
+ return ipmi::responseParmNotSupported();
+ }
+ if (getRevision)
+ {
+ return ipmi::responseSuccess(paramRevision, std::nullopt, std::nullopt);
+ }
+
+ if (paramSelector == 0)
+ {
+ return ipmi::responseSuccess(paramRevision, transferStatus,
+ std::nullopt);
+ }
+
+ if (BlockSelector != 0) // 00h if parameter does not require a block number
+ {
+ return ipmi::responseParmNotSupported();
+ }
+
+ if (sysInfoParamStore == nullptr)
+ {
+ sysInfoParamStore = std::make_unique<SysInfoParamStore>();
+ sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
+ sysInfoReadSystemName);
+ }
+
+ if (paramSelector == IPMI_SYSINFO_SYSTEM_FW_VERSION)
+ {
+ auto fwVersion = getSysFWVersion(ctx);
+
+ if (fwVersion == std::nullopt)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_FW_VERSION, *fwVersion);
+ }
+
+ // Parameters other than Set In Progress are assumed to be strings.
+ std::tuple<bool, std::string> ret =
+ sysInfoParamStore->lookup(paramSelector);
+ bool found = std::get<0>(ret);
+ if (!found)
+ {
+ return ipmi::responseSensorInvalid();
+ }
+ std::string& paramString = std::get<1>(ret);
+ std::vector<uint8_t> configData;
+ size_t count = 0;
+ if (setSelector == 0)
+ { // First chunk has only 14 bytes.
+ configData.emplace_back(0); // encoding
+ configData.emplace_back(paramString.length()); // string length
+ count = std::min(paramString.length(), smallChunkSize);
+ configData.resize(count + configDataOverhead);
+ std::copy_n(paramString.begin(), count,
+ configData.begin() + configDataOverhead); // 14 bytes chunk
+
+ // Append zero's to remaining bytes
+ if (configData.size() < configParameterLength)
+ {
+ std::fill_n(std::back_inserter(configData),
+ configParameterLength - configData.size(), 0x00);
+ }
+ }
+ else
+ {
+ size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
+ if (offset >= paramString.length())
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+ count = std::min(paramString.length() - offset, fullChunkSize);
+ configData.resize(count);
+ std::copy_n(paramString.begin() + offset, count,
+ configData.begin()); // 16 bytes chunk
+ }
+ return ipmi::responseSuccess(paramRevision, setSelector, configData);
+}
+
+ipmi::RspType<> ipmiAppSetSystemInfo(uint8_t paramSelector, uint8_t data1,
+ std::vector<uint8_t> configData)
+{
+ if (paramSelector >= invalidParamSelectorStart &&
+ paramSelector <= invalidParamSelectorEnd)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (paramSelector >= oemCmdStart)
+ {
+ return ipmi::responseParmNotSupported();
+ }
+
+ if (paramSelector == 0)
+ {
+ // attempt to set the 'set in progress' value (in parameter #0)
+ // when not in the set complete state.
+ if ((transferStatus != setComplete) && (data1 == setInProgress))
+ {
+ return ipmi::responseSetInProgressActive();
+ }
+ // only following 2 states are supported
+ if (data1 > setInProgress)
+ {
+ lg2::error("illegal SetInProgress status");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ transferStatus = data1 & progressMask;
+ return ipmi::responseSuccess();
+ }
+
+ if (configData.size() > configParameterLength)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Append zero's to remaining bytes
+ if (configData.size() < configParameterLength)
+ {
+ fill_n(back_inserter(configData),
+ (configParameterLength - configData.size()), 0x00);
+ }
+
+ if (!sysInfoParamStore)
+ {
+ sysInfoParamStore = std::make_unique<SysInfoParamStore>();
+ sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME,
+ sysInfoReadSystemName);
+ }
+
+ // lookup
+ std::tuple<bool, std::string> ret =
+ sysInfoParamStore->lookup(paramSelector);
+ bool found = std::get<0>(ret);
+ std::string& paramString = std::get<1>(ret);
+ if (!found)
+ {
+ // parameter does not exist. Init new
+ paramString = "";
+ }
+
+ uint8_t setSelector = data1;
+ size_t count = 0;
+ if (setSelector == 0) // First chunk has only 14 bytes.
+ {
+ uint8_t encoding = configData.at(0);
+ if (encoding > maxValidEncodingData)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ size_t stringLen = configData.at(1); // string length
+ count = std::min(stringLen, smallChunkSize);
+ count = std::min(count, configData.size());
+ paramString.resize(stringLen); // reserve space
+ std::copy_n(configData.begin() + configDataOverhead, count,
+ paramString.begin());
+ }
+ else
+ {
+ size_t offset = (setSelector * fullChunkSize) - configDataOverhead;
+ if (offset >= paramString.length())
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+ count = std::min(paramString.length() - offset, configData.size());
+ std::copy_n(configData.begin(), count, paramString.begin() + offset);
+ }
+ sysInfoParamStore->update(paramSelector, paramString);
+ return ipmi::responseSuccess();
+}
+
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+inline std::vector<uint8_t> convertStringToData(const std::string& command)
+{
+ std::istringstream iss(command);
+ std::string token;
+ std::vector<uint8_t> dataValue;
+ while (std::getline(iss, token, ' '))
+ {
+ dataValue.emplace_back(
+ static_cast<uint8_t>(std::stoul(token, nullptr, base_16)));
+ }
+ return dataValue;
+}
+
+static bool populateI2CControllerWRAllowlist()
+{
+ nlohmann::json data = nullptr;
+ std::ifstream jsonFile(i2cControllerWRAllowlistFile);
+
+ if (!jsonFile.good())
+ {
+ lg2::warning("i2c allow list file not found! file name: {FILE_NAME}",
+ "FILE_NAME", i2cControllerWRAllowlistFile);
+ return false;
+ }
+
+ try
+ {
+ data = nlohmann::json::parse(jsonFile, nullptr, false);
+ }
+ catch (const nlohmann::json::parse_error& e)
+ {
+ lg2::error("Corrupted i2c allow list config file, "
+ "file name: {FILE_NAME}, error: {ERROR}",
+ "FILE_NAME", i2cControllerWRAllowlistFile, "ERROR", e);
+ return false;
+ }
+
+ try
+ {
+ // Example JSON Structure format
+ // "filters": [
+ // {
+ // "Description": "Allow full read - ignore first byte write value
+ // for 0x40 to 0x4F",
+ // "busId": "0x01",
+ // "slaveAddr": "0x40",
+ // "slaveAddrMask": "0x0F",
+ // "command": "0x00",
+ // "commandMask": "0xFF"
+ // },
+ // {
+ // "Description": "Allow full read - first byte match 0x05 and
+ // ignore second byte",
+ // "busId": "0x01",
+ // "slaveAddr": "0x57",
+ // "slaveAddrMask": "0x00",
+ // "command": "0x05 0x00",
+ // "commandMask": "0x00 0xFF"
+ // },]
+
+ nlohmann::json filters = data[filtersStr].get<nlohmann::json>();
+ std::vector<i2cControllerWRAllowlist>& allowlist = getWRAllowlist();
+ for (const auto& it : filters.items())
+ {
+ nlohmann::json filter = it.value();
+ if (filter.is_null())
+ {
+ lg2::error(
+ "Corrupted I2C controller write read allowlist config file, "
+ "file name: {FILE_NAME}",
+ "FILE_NAME", i2cControllerWRAllowlistFile);
+ return false;
+ }
+ const std::vector<uint8_t>& writeData =
+ convertStringToData(filter[cmdStr].get<std::string>());
+ const std::vector<uint8_t>& writeDataMask =
+ convertStringToData(filter[cmdMaskStr].get<std::string>());
+ if (writeDataMask.size() != writeData.size())
+ {
+ lg2::error("I2C controller write read allowlist filter "
+ "mismatch for command & mask size");
+ return false;
+ }
+ allowlist.push_back(
+ {static_cast<uint8_t>(std::stoul(
+ filter[busIdStr].get<std::string>(), nullptr, base_16)),
+ static_cast<uint8_t>(
+ std::stoul(filter[targetAddrStr].get<std::string>(),
+ nullptr, base_16)),
+ static_cast<uint8_t>(
+ std::stoul(filter[targetAddrMaskStr].get<std::string>(),
+ nullptr, base_16)),
+ writeData, writeDataMask});
+ }
+ if (allowlist.size() != filters.size())
+ {
+ lg2::error(
+ "I2C controller write read allowlist filter size mismatch");
+ return false;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("I2C controller write read allowlist "
+ "unexpected exception: {ERROR}",
+ "ERROR", e);
+ return false;
+ }
+ return true;
+}
+
+static inline bool isWriteDataAllowlisted(const std::vector<uint8_t>& data,
+ const std::vector<uint8_t>& dataMask,
+ const std::vector<uint8_t>& writeData)
+{
+ std::vector<uint8_t> processedDataBuf(data.size());
+ std::vector<uint8_t> processedReqBuf(dataMask.size());
+ std::transform(writeData.begin(), writeData.end(), dataMask.begin(),
+ processedReqBuf.begin(), std::bit_or<uint8_t>());
+ std::transform(data.begin(), data.end(), dataMask.begin(),
+ processedDataBuf.begin(), std::bit_or<uint8_t>());
+
+ return (processedDataBuf == processedReqBuf);
+}
+
+static bool isCmdAllowlisted(uint8_t busId, uint8_t targetAddr,
+ std::vector<uint8_t>& writeData)
+{
+ std::vector<i2cControllerWRAllowlist>& allowList = getWRAllowlist();
+ for (const auto& wlEntry : allowList)
+ {
+ if ((busId == wlEntry.busId) &&
+ ((targetAddr | wlEntry.targetAddrMask) ==
+ (wlEntry.targetAddr | wlEntry.targetAddrMask)))
+ {
+ const std::vector<uint8_t>& dataMask = wlEntry.dataMask;
+ // Skip as no-match, if requested write data is more than the
+ // write data mask size
+ if (writeData.size() > dataMask.size())
+ {
+ continue;
+ }
+ if (isWriteDataAllowlisted(wlEntry.data, dataMask, writeData))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#else
+static bool populateI2CControllerWRAllowlist()
+{
+ lg2::info("I2C_WHITELIST_CHECK is disabled, do not populate allowlist");
+ return true;
+}
+#endif // ENABLE_I2C_WHITELIST_CHECK
+
+/** @brief implements controller write read IPMI command which can be used for
+ * low-level I2C/SMBus write, read or write-read access
+ * @param isPrivateBus -to indicate private bus usage
+ * @param busId - bus id
+ * @param channelNum - channel number
+ * @param reserved - skip 1 bit
+ * @param targetAddr - target address
+ * @param read count - number of bytes to be read
+ * @param writeData - data to be written
+ *
+ * @returns IPMI completion code plus response data
+ * - readData - i2c response data
+ */
+ipmi::RspType<std::vector<uint8_t>> ipmiControllerWriteRead(
+ [[maybe_unused]] bool isPrivateBus, uint3_t busId,
+ [[maybe_unused]] uint4_t channelNum, bool reserved, uint7_t targetAddr,
+ uint8_t readCount, std::vector<uint8_t> writeData)
+{
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ const size_t writeCount = writeData.size();
+ if (!readCount && !writeCount)
+ {
+ lg2::error("Controller write read command: Read & write count are 0");
+ return ipmi::responseInvalidFieldRequest();
+ }
+#ifdef ENABLE_I2C_WHITELIST_CHECK
+ if (!isCmdAllowlisted(static_cast<uint8_t>(busId),
+ static_cast<uint8_t>(targetAddr), writeData))
+ {
+ lg2::error("Controller write read request blocked!, "
+ "bus: {BUS}, addr: {ADDR}",
+ "BUS", static_cast<uint8_t>(busId), "ADDR", lg2::hex,
+ static_cast<uint8_t>(targetAddr));
+ }
+#endif // ENABLE_I2C_WHITELIST_CHECK
+ std::vector<uint8_t> readBuf(readCount);
+ std::string i2cBus =
+ "/dev/i2c-" + std::to_string(static_cast<uint8_t>(busId));
+
+ ipmi::Cc ret = ipmi::i2cWriteRead(i2cBus, static_cast<uint8_t>(targetAddr),
+ writeData, readBuf);
+ if (ret != ipmi::ccSuccess)
+ {
+ return ipmi::response(ret);
+ }
+ return ipmi::responseSuccess(readBuf);
+}
+
+void registerNetFnAppFunctions()
+{
+ // <Get Device ID>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetDeviceId, ipmi::Privilege::User,
+ ipmiAppGetDeviceId);
+
+ // <Get BT Interface Capabilities>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetBtIfaceCapabilities,
+ ipmi::Privilege::User, ipmiAppGetBtCapabilities);
+
+ // <Reset Watchdog Timer>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdResetWatchdogTimer,
+ ipmi::Privilege::Operator, ipmiAppResetWatchdogTimer);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetSessionInfo, ipmi::Privilege::User,
+ ipmiAppGetSessionInfo);
+
+ // <Set Watchdog Timer>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetWatchdogTimer,
+ ipmi::Privilege::Operator, ipmiSetWatchdogTimer);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdCloseSession, ipmi::Privilege::Callback,
+ ipmiAppCloseSession);
+
+ // <Get Watchdog Timer>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetWatchdogTimer, ipmi::Privilege::User,
+ ipmiGetWatchdogTimer);
+
+ // <Get Self Test Results>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetSelfTestResults,
+ ipmi::Privilege::User, ipmiAppGetSelfTestResults);
+
+ // <Get Device GUID>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetDeviceGuid, ipmi::Privilege::User,
+ ipmiAppGetDeviceGuid);
+
+ // <Set ACPI Power State>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetAcpiPowerState,
+ ipmi::Privilege::Admin, ipmiSetAcpiPowerState);
+ // <Get ACPI Power State>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetAcpiPowerState,
+ ipmi::Privilege::User, ipmiGetAcpiPowerState);
+
+ // Note: For security reason, this command will be registered only when
+ // there are proper I2C Controller write read allowlist
+ if (populateI2CControllerWRAllowlist())
+ {
+ // Note: For security reasons, registering controller write read as
+ // admin privilege command, even though IPMI 2.0 specification allows it
+ // as operator privilege.
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdMasterWriteRead,
+ ipmi::Privilege::Admin, ipmiControllerWriteRead);
+ }
+
+ // <Get System GUID Command>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetSystemGuid, ipmi::Privilege::User,
+ ipmiAppGetSystemGuid);
+
+ // <Get Channel Cipher Suites Command>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetChannelCipherSuites,
+ ipmi::Privilege::None, getChannelCipherSuites);
+
+ // <Get System Info Command>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetSystemInfoParameters,
+ ipmi::Privilege::User, ipmiAppGetSystemInfo);
+ // <Set System Info Command>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetSystemInfoParameters,
+ ipmi::Privilege::Admin, ipmiAppSetSystemInfo);
+ return;
+}
diff --git a/apphandler.hpp b/apphandler.hpp
new file mode 100644
index 0000000..56dd0f8
--- /dev/null
+++ b/apphandler.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <stdint.h>
+
+enum ipmi_app_sysinfo_params
+{
+ IPMI_SYSINFO_SET_STATE = 0x00,
+ IPMI_SYSINFO_SYSTEM_FW_VERSION = 0x01,
+ IPMI_SYSINFO_SYSTEM_NAME = 0x02,
+ IPMI_SYSINFO_PRIMARY_OS_NAME = 0x03,
+ IPMI_SYSINFO_OS_NAME = 0x04,
+ IPMI_SYSINFO_OS_VERSION = 0x05,
+ IPMI_SYSINFO_BMC_URL = 0x06,
+ IPMI_SYSINFO_OS_HYP_URL = 0x07,
+ IPMI_SYSINFO_OEM_START = 0xC0, // Start of range of OEM parameters
+};
diff --git a/chassishandler.cpp b/chassishandler.cpp
new file mode 100644
index 0000000..07e5544
--- /dev/null
+++ b/chassishandler.cpp
@@ -0,0 +1,2430 @@
+#include "config.h"
+
+#include "chassishandler.hpp"
+
+#include <arpa/inet.h>
+#include <endian.h>
+#include <limits.h>
+#include <netinet/in.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdbusplus/timer.hpp>
+#include <settings.hpp>
+#include <xyz/openbmc_project/Chassis/Intrusion/client.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Boot/Mode/server.hpp>
+#include <xyz/openbmc_project/Control/Boot/Source/server.hpp>
+#include <xyz/openbmc_project/Control/Boot/Type/server.hpp>
+#include <xyz/openbmc_project/Control/Power/RestorePolicy/server.hpp>
+#include <xyz/openbmc_project/State/Chassis/server.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
+#include <xyz/openbmc_project/State/PowerOnHours/server.hpp>
+
+#include <array>
+#include <chrono>
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <future>
+#include <map>
+#include <sstream>
+#include <string>
+
+std::unique_ptr<sdbusplus::Timer> identifyTimer
+ __attribute__((init_priority(101)));
+
+static ChassisIDState chassisIDState = ChassisIDState::reserved;
+
+constexpr size_t sizeVersion = 2;
+constexpr size_t DEFAULT_IDENTIFY_TIME_OUT = 15;
+
+// PetiBoot-Specific
+static constexpr uint8_t netConfInitialBytes[] = {0x80, 0x21, 0x70, 0x62,
+ 0x21, 0x00, 0x01, 0x06};
+static constexpr uint8_t oemParmStart = 96;
+static constexpr uint8_t oemParmEnd = 127;
+
+static constexpr size_t cookieOffset = 1;
+static constexpr size_t versionOffset = 5;
+static constexpr size_t addrSizeOffset = 8;
+static constexpr size_t macOffset = 9;
+static constexpr size_t addrTypeOffset = 16;
+static constexpr size_t ipAddrOffset = 17;
+
+namespace ipmi
+{
+constexpr Cc ccParmNotSupported = 0x80;
+
+static inline auto responseParmNotSupported()
+{
+ return response(ccParmNotSupported);
+}
+} // namespace ipmi
+
+void registerNetFnChassisFunctions() __attribute__((constructor));
+
+// Host settings in dbus
+// Service name should be referenced by connection name got via object mapper
+const char* settings_object_name = "/org/openbmc/settings/host0";
+const char* settings_intf_name = "org.freedesktop.DBus.Properties";
+const char* identify_led_object_name =
+ "/xyz/openbmc_project/led/groups/enclosure_identify";
+
+constexpr auto SETTINGS_ROOT = "/";
+constexpr auto SETTINGS_MATCH = "host0";
+
+constexpr auto IP_INTERFACE = "xyz.openbmc_project.Network.IP";
+constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress";
+
+static constexpr auto chassisStateRoot = "/xyz/openbmc_project/state";
+static constexpr auto chassisPOHStateIntf =
+ "xyz.openbmc_project.State.PowerOnHours";
+static constexpr auto pohCounterProperty = "POHCounter";
+static constexpr auto match = "chassis0";
+const static constexpr char chassisCapIntf[] =
+ "xyz.openbmc_project.Control.ChassisCapabilities";
+const static constexpr char chassisIntrusionProp[] = "ChassisIntrusionEnabled";
+const static constexpr char chassisFrontPanelLockoutProp[] =
+ "ChassisFrontPanelLockoutEnabled";
+const static constexpr char chassisNMIProp[] = "ChassisNMIEnabled";
+const static constexpr char chassisPowerInterlockProp[] =
+ "ChassisPowerInterlockEnabled";
+const static constexpr char chassisFRUDevAddrProp[] = "FRUDeviceAddress";
+const static constexpr char chassisSDRDevAddrProp[] = "SDRDeviceAddress";
+const static constexpr char chassisSELDevAddrProp[] = "SELDeviceAddress";
+const static constexpr char chassisSMDevAddrProp[] = "SMDeviceAddress";
+const static constexpr char chassisBridgeDevAddrProp[] = "BridgeDeviceAddress";
+static constexpr uint8_t chassisCapAddrMask = 0xfe;
+static constexpr const char* powerButtonIntf =
+ "xyz.openbmc_project.Chassis.Buttons.Power";
+static constexpr const char* powerButtonPath =
+ "/xyz/openbmc_project/Chassis/Buttons/Power0";
+static constexpr const char* resetButtonIntf =
+ "xyz.openbmc_project.Chassis.Buttons.Reset";
+static constexpr const char* resetButtonPath =
+ "/xyz/openbmc_project/Chassis/Buttons/Reset0";
+
+// Phosphor Host State manager
+namespace State = sdbusplus::server::xyz::openbmc_project::state;
+namespace fs = std::filesystem;
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+using namespace sdbusplus::server::xyz::openbmc_project::control::boot;
+using Intrusion = sdbusplus::client::xyz::openbmc_project::chassis::Intrusion<>;
+
+namespace chassis
+{
+namespace internal
+{
+
+constexpr auto bootSettingsPath = "/xyz/openbmc_project/control/host0/boot";
+constexpr auto bootEnableIntf = "xyz.openbmc_project.Object.Enable";
+constexpr auto bootModeIntf = "xyz.openbmc_project.Control.Boot.Mode";
+constexpr auto bootTypeIntf = "xyz.openbmc_project.Control.Boot.Type";
+constexpr auto bootSourceIntf = "xyz.openbmc_project.Control.Boot.Source";
+constexpr auto bootSettingsOneTimePath =
+ "/xyz/openbmc_project/control/host0/boot/one_time";
+constexpr auto bootOneTimeIntf = "xyz.openbmc_project.Object.Enable";
+
+constexpr auto powerRestoreIntf =
+ "xyz.openbmc_project.Control.Power.RestorePolicy";
+sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
+
+namespace cache
+{
+
+std::unique_ptr<settings::Objects> objectsPtr = nullptr;
+
+settings::Objects& getObjects()
+{
+ if (objectsPtr == nullptr)
+ {
+ objectsPtr = std::make_unique<settings::Objects>(
+ dbus, std::vector<std::string>{bootModeIntf, bootTypeIntf,
+ bootSourceIntf, powerRestoreIntf});
+ }
+ return *objectsPtr;
+}
+
+} // namespace cache
+} // namespace internal
+} // namespace chassis
+
+namespace poh
+{
+
+constexpr auto minutesPerCount = 60;
+
+} // namespace poh
+
+int getHostNetworkData(ipmi::message::Payload& payload)
+{
+ ipmi::PropertyMap properties;
+ int rc = 0;
+ uint8_t addrSize = ipmi::network::IPV4_ADDRESS_SIZE_BYTE;
+
+ try
+ {
+ // TODO There may be cases where an interface is implemented by multiple
+ // objects,to handle such cases we are interested on that object
+ // which are on interested busname.
+ // Currenlty mapper doesn't give the readable busname(gives busid)
+ // so we can't match with bus name so giving some object specific info
+ // as SETTINGS_MATCH.
+ // Later SETTINGS_MATCH will be replaced with busname.
+
+ sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+
+ auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
+ SETTINGS_ROOT, SETTINGS_MATCH);
+
+ auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
+ SETTINGS_ROOT, SETTINGS_MATCH);
+
+ properties = ipmi::getAllDbusProperties(
+ bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE);
+ auto variant = ipmi::getDbusProperty(
+ bus, macObjectInfo.second, macObjectInfo.first, MAC_INTERFACE,
+ "MACAddress");
+
+ auto ipAddress = std::get<std::string>(properties["Address"]);
+
+ auto gateway = std::get<std::string>(properties["Gateway"]);
+
+ auto prefix = std::get<uint8_t>(properties["PrefixLength"]);
+
+ uint8_t isStatic =
+ (std::get<std::string>(properties["Origin"]) ==
+ "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
+ ? 1
+ : 0;
+
+ auto MACAddress = std::get<std::string>(variant);
+
+ // it is expected here that we should get the valid data
+ // but we may also get the default values.
+ // Validation of the data is done by settings.
+ //
+ // if mac address is default mac address then
+ // don't send blank override.
+ if ((MACAddress == ipmi::network::DEFAULT_MAC_ADDRESS))
+ {
+ rc = -1;
+ return rc;
+ }
+ // if addr is static then ipaddress,gateway,prefix
+ // should not be default one,don't send blank override.
+ if (isStatic)
+ {
+ if ((ipAddress == ipmi::network::DEFAULT_ADDRESS) ||
+ (gateway == ipmi::network::DEFAULT_ADDRESS) || (!prefix))
+ {
+ rc = -1;
+ return rc;
+ }
+ }
+
+ std::string token;
+ std::stringstream ss(MACAddress);
+
+ // First pack macOffset no of bytes in payload.
+ // Latter this PetiBoot-Specific data will be populated.
+ std::vector<uint8_t> payloadInitialBytes(macOffset);
+ payload.pack(payloadInitialBytes);
+
+ while (std::getline(ss, token, ':'))
+ {
+ payload.pack(stoi(token, nullptr, 16));
+ }
+
+ payload.pack(0x00);
+
+ payload.pack(isStatic);
+
+ uint8_t addressFamily = (std::get<std::string>(properties["Type"]) ==
+ "xyz.openbmc_project.Network.IP.Protocol.IPv4")
+ ? AF_INET
+ : AF_INET6;
+
+ addrSize = (addressFamily == AF_INET)
+ ? ipmi::network::IPV4_ADDRESS_SIZE_BYTE
+ : ipmi::network::IPV6_ADDRESS_SIZE_BYTE;
+
+ // ipaddress and gateway would be in IPv4 format
+ std::vector<uint8_t> addrInBinary(addrSize);
+ inet_pton(addressFamily, ipAddress.c_str(),
+ reinterpret_cast<void*>(addrInBinary.data()));
+
+ payload.pack(addrInBinary);
+
+ payload.pack(prefix);
+
+ std::vector<uint8_t> gatewayDetails(addrSize);
+ inet_pton(addressFamily, gateway.c_str(),
+ reinterpret_cast<void*>(gatewayDetails.data()));
+ payload.pack(gatewayDetails);
+ }
+ catch (const InternalFailure& e)
+ {
+ commit<InternalFailure>();
+ rc = -1;
+ return rc;
+ }
+
+ // PetiBoot-Specific
+ // If success then copy the first 9 bytes to the payload message
+ // payload first 2 bytes contain the parameter values. Skip that 2 bytes.
+ uint8_t skipFirstTwoBytes = 2;
+ size_t payloadSize = payload.size();
+ uint8_t* configDataStartingAddress = payload.data() + skipFirstTwoBytes;
+
+ if (payloadSize < skipFirstTwoBytes + sizeof(netConfInitialBytes))
+ {
+ lg2::error("Invalid net config");
+ rc = -1;
+ return rc;
+ }
+ std::copy(netConfInitialBytes,
+ netConfInitialBytes + sizeof(netConfInitialBytes),
+ configDataStartingAddress);
+
+ if (payloadSize < skipFirstTwoBytes + addrSizeOffset + sizeof(addrSize))
+ {
+ lg2::error("Invalid length of address size");
+ rc = -1;
+ return rc;
+ }
+ std::copy(&addrSize, &(addrSize) + sizeof(addrSize),
+ configDataStartingAddress + addrSizeOffset);
+
+#ifdef _IPMI_DEBUG_
+ std::printf("\n===Printing the IPMI Formatted Data========\n");
+
+ for (uint8_t pos = 0; pos < index; pos++)
+ {
+ std::printf("%02x ", payloadStartingAddress[pos]);
+ }
+#endif
+
+ return rc;
+}
+
+/** @brief convert IPv4 and IPv6 addresses from binary to text form.
+ * @param[in] family - IPv4/Ipv6
+ * @param[in] data - req data pointer.
+ * @param[in] offset - offset in the data.
+ * @param[in] addrSize - size of the data which needs to be read from offset.
+ * @returns address in text form.
+ */
+
+std::string getAddrStr(uint8_t family, uint8_t* data, uint8_t offset,
+ uint8_t addrSize)
+{
+ char ipAddr[INET6_ADDRSTRLEN] = {};
+
+ switch (family)
+ {
+ case AF_INET:
+ {
+ struct sockaddr_in addr4{};
+ std::memcpy(&addr4.sin_addr.s_addr, &data[offset], addrSize);
+
+ inet_ntop(AF_INET, &addr4.sin_addr, ipAddr, INET_ADDRSTRLEN);
+
+ break;
+ }
+ case AF_INET6:
+ {
+ struct sockaddr_in6 addr6{};
+ std::memcpy(&addr6.sin6_addr.s6_addr, &data[offset], addrSize);
+
+ inet_ntop(AF_INET6, &addr6.sin6_addr, ipAddr, INET6_ADDRSTRLEN);
+
+ break;
+ }
+ default:
+ {
+ return {};
+ }
+ }
+
+ return ipAddr;
+}
+
+ipmi::Cc setHostNetworkData(ipmi::message::Payload& data)
+{
+ using namespace std::string_literals;
+ std::string hostNetworkConfig;
+ std::string mac("00:00:00:00:00:00");
+ std::string ipAddress, gateway;
+ std::string addrOrigin{0};
+ uint8_t addrSize{0};
+ std::string addressOrigin =
+ "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP";
+ std::string addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv4";
+ uint8_t prefix{0};
+ uint8_t family = AF_INET;
+
+ // cookie starts from second byte
+ // version starts from sixth byte
+
+ try
+ {
+ do
+ {
+ // cookie == 0x21 0x70 0x62 0x21
+ data.trailingOk = true;
+ auto msgLen = data.size();
+ std::vector<uint8_t> msgPayloadBytes(msgLen);
+ if (data.unpack(msgPayloadBytes) != 0 || !data.fullyUnpacked())
+ {
+ lg2::error("Error in unpacking message of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+
+ uint8_t* msgPayloadStartingPos = msgPayloadBytes.data();
+ constexpr size_t cookieSize = 4;
+ if (msgLen < cookieOffset + cookieSize)
+ {
+ lg2::error("Error in cookie getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ if (std::equal(msgPayloadStartingPos + cookieOffset,
+ msgPayloadStartingPos + cookieOffset + cookieSize,
+ (netConfInitialBytes + cookieOffset)) != 0)
+ {
+ // all cookie == 0
+ if (std::all_of(msgPayloadStartingPos + cookieOffset,
+ msgPayloadStartingPos + cookieOffset +
+ cookieSize,
+ [](int i) { return i == 0; }) == true)
+ {
+ // need to zero out the network settings.
+ break;
+ }
+
+ lg2::error("Invalid Cookie");
+ elog<InternalFailure>();
+ }
+
+ // vesion == 0x00 0x01
+ if (msgLen < versionOffset + sizeVersion)
+ {
+ lg2::error("Error in version getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ if (std::equal(msgPayloadStartingPos + versionOffset,
+ msgPayloadStartingPos + versionOffset + sizeVersion,
+ (netConfInitialBytes + versionOffset)) != 0)
+ {
+ lg2::error("Invalid Version");
+ elog<InternalFailure>();
+ }
+
+ if (msgLen < macOffset + 6)
+ {
+ lg2::error(
+ "Error in mac address getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ std::stringstream result;
+ std::copy((msgPayloadStartingPos + macOffset),
+ (msgPayloadStartingPos + macOffset + 5),
+ std::ostream_iterator<int>(result, ":"));
+ mac = result.str();
+
+ if (msgLen < addrTypeOffset + sizeof(decltype(addrOrigin)))
+ {
+ lg2::error(
+ "Error in original address getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ std::copy(msgPayloadStartingPos + addrTypeOffset,
+ msgPayloadStartingPos + addrTypeOffset +
+ sizeof(decltype(addrOrigin)),
+ std::ostream_iterator<int>(result, ""));
+ addrOrigin = result.str();
+
+ if (!addrOrigin.empty())
+ {
+ addressOrigin =
+ "xyz.openbmc_project.Network.IP.AddressOrigin.Static";
+ }
+
+ if (msgLen < addrSizeOffset + sizeof(decltype(addrSize)))
+ {
+ lg2::error(
+ "Error in address size getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ // Get the address size
+ std::copy(msgPayloadStartingPos + addrSizeOffset,
+ (msgPayloadStartingPos + addrSizeOffset +
+ sizeof(decltype(addrSize))),
+ &addrSize);
+
+ uint8_t prefixOffset = ipAddrOffset + addrSize;
+ if (msgLen < prefixOffset + sizeof(decltype(prefix)))
+ {
+ lg2::error("Error in prefix getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ // std::copy(msgPayloadStartingPos + prefixOffset,
+ // msgPayloadStartingPos + prefixOffset +
+ // sizeof(decltype(prefix)),
+ // &prefix);
+ // Workaround compiler misdetecting out of bounds memcpy
+ prefix = msgPayloadStartingPos[prefixOffset];
+
+ uint8_t gatewayOffset = prefixOffset + sizeof(decltype(prefix));
+ if (addrSize != ipmi::network::IPV4_ADDRESS_SIZE_BYTE)
+ {
+ addressType = "xyz.openbmc_project.Network.IP.Protocol.IPv6";
+ family = AF_INET6;
+ }
+
+ if (msgLen < ipAddrOffset + addrSize)
+ {
+ lg2::error("Error in IP address getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ ipAddress = getAddrStr(family, msgPayloadStartingPos, ipAddrOffset,
+ addrSize);
+
+ if (msgLen < gatewayOffset + addrSize)
+ {
+ lg2::error(
+ "Error in gateway address getting of setHostNetworkData");
+ return ipmi::ccReqDataLenInvalid;
+ }
+ gateway = getAddrStr(family, msgPayloadStartingPos, gatewayOffset,
+ addrSize);
+
+ } while (0);
+
+ // Cookie == 0 or it is a valid cookie
+ hostNetworkConfig +=
+ "ipaddress="s + ipAddress + ",prefix="s + std::to_string(prefix) +
+ ",gateway="s + gateway + ",mac="s + mac + ",addressOrigin="s +
+ addressOrigin;
+
+ sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+
+ auto ipObjectInfo = ipmi::getDbusObject(bus, IP_INTERFACE,
+ SETTINGS_ROOT, SETTINGS_MATCH);
+ auto macObjectInfo = ipmi::getDbusObject(bus, MAC_INTERFACE,
+ SETTINGS_ROOT, SETTINGS_MATCH);
+ // set the dbus property
+ ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+ IP_INTERFACE, "Address", std::string(ipAddress));
+ ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+ IP_INTERFACE, "PrefixLength", prefix);
+ ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+ IP_INTERFACE, "Origin", addressOrigin);
+ ipmi::setDbusProperty(bus, ipObjectInfo.second, ipObjectInfo.first,
+ IP_INTERFACE, "Gateway", std::string(gateway));
+ ipmi::setDbusProperty(
+ bus, ipObjectInfo.second, ipObjectInfo.first, IP_INTERFACE, "Type",
+ std::string("xyz.openbmc_project.Network.IP.Protocol.IPv4"));
+ ipmi::setDbusProperty(bus, macObjectInfo.second, macObjectInfo.first,
+ MAC_INTERFACE, "MACAddress", std::string(mac));
+
+ lg2::debug("Network configuration changed: {NETWORKCONFIG}",
+ "NETWORKCONFIG", hostNetworkConfig);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ commit<InternalFailure>();
+ lg2::error("Error in ipmiChassisSetSysBootOptions call");
+ return ipmi::ccUnspecifiedError;
+ }
+
+ return ipmi::ccSuccess;
+}
+
+uint32_t getPOHCounter()
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ auto chassisStateObj =
+ ipmi::getDbusObject(bus, chassisPOHStateIntf, chassisStateRoot, match);
+
+ auto service =
+ ipmi::getService(bus, chassisPOHStateIntf, chassisStateObj.first);
+
+ auto propValue =
+ ipmi::getDbusProperty(bus, service, chassisStateObj.first,
+ chassisPOHStateIntf, pohCounterProperty);
+
+ return std::get<uint32_t>(propValue);
+}
+
+/** @brief Implements the get chassis capabilities command
+ *
+ * @returns IPMI completion code plus response data
+ * chassisCapFlags - chassis capability flag
+ * chassisFRUInfoDevAddr - chassis FRU info Device Address
+ * chassisSDRDevAddr - chassis SDR device address
+ * chassisSELDevAddr - chassis SEL device address
+ * chassisSMDevAddr - chassis system management device address
+ * chassisBridgeDevAddr - chassis bridge device address
+ */
+ipmi::RspType<bool, // chassis intrusion sensor
+ bool, // chassis Front panel lockout
+ bool, // chassis NMI
+ bool, // chassis power interlock
+ uint4_t, // reserved
+ uint8_t, // chassis FRU info Device Address
+ uint8_t, // chassis SDR device address
+ uint8_t, // chassis SEL device address
+ uint8_t, // chassis system management device address
+ uint8_t // chassis bridge device address
+ >
+ ipmiGetChassisCap()
+{
+ ipmi::PropertyMap properties;
+ try
+ {
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ ipmi::DbusObjectInfo chassisCapObject =
+ ipmi::getDbusObject(bus, chassisCapIntf);
+
+ // capabilities flags
+ // [7..4] - reserved
+ // [3] – 1b = provides power interlock (IPM 1.5)
+ // [2] – 1b = provides Diagnostic Interrupt (FP NMI)
+ // [1] – 1b = provides “Front Panel Lockout” (indicates that the chassis
+ // has capabilities
+ // to lock out external power control and reset button or
+ // front panel interfaces and/or detect tampering with those
+ // interfaces).
+ // [0] -1b = Chassis provides intrusion (physical security) sensor.
+ // set to default value 0x0.
+
+ properties =
+ ipmi::getAllDbusProperties(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to fetch Chassis Capability properties: {ERROR}",
+ "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ bool* chassisIntrusionFlag =
+ std::get_if<bool>(&properties[chassisIntrusionProp]);
+ if (chassisIntrusionFlag == nullptr)
+ {
+ lg2::error("Error to get chassis Intrusion flags");
+ return ipmi::responseUnspecifiedError();
+ }
+ bool* chassisFrontPanelFlag =
+ std::get_if<bool>(&properties[chassisFrontPanelLockoutProp]);
+ if (chassisFrontPanelFlag == nullptr)
+ {
+ lg2::error("Error to get chassis intrusion flags");
+ return ipmi::responseUnspecifiedError();
+ }
+ bool* chassisNMIFlag = std::get_if<bool>(&properties[chassisNMIProp]);
+ if (chassisNMIFlag == nullptr)
+ {
+ lg2::error("Error to get chassis NMI flags");
+ return ipmi::responseUnspecifiedError();
+ }
+ bool* chassisPowerInterlockFlag =
+ std::get_if<bool>(&properties[chassisPowerInterlockProp]);
+ if (chassisPowerInterlockFlag == nullptr)
+ {
+ lg2::error("Error to get chassis power interlock flags");
+ return ipmi::responseUnspecifiedError();
+ }
+ uint8_t* chassisFRUInfoDevAddr =
+ std::get_if<uint8_t>(&properties[chassisFRUDevAddrProp]);
+ if (chassisFRUInfoDevAddr == nullptr)
+ {
+ lg2::error("Error to get chassis FRU info device address");
+ return ipmi::responseUnspecifiedError();
+ }
+ uint8_t* chassisSDRDevAddr =
+ std::get_if<uint8_t>(&properties[chassisSDRDevAddrProp]);
+ if (chassisSDRDevAddr == nullptr)
+ {
+ lg2::error("Error to get chassis SDR device address");
+ return ipmi::responseUnspecifiedError();
+ }
+ uint8_t* chassisSELDevAddr =
+ std::get_if<uint8_t>(&properties[chassisSELDevAddrProp]);
+ if (chassisSELDevAddr == nullptr)
+ {
+ lg2::error("Error to get chassis SEL device address");
+ return ipmi::responseUnspecifiedError();
+ }
+ uint8_t* chassisSMDevAddr =
+ std::get_if<uint8_t>(&properties[chassisSMDevAddrProp]);
+ if (chassisSMDevAddr == nullptr)
+ {
+ lg2::error("Error to get chassis SM device address");
+ return ipmi::responseUnspecifiedError();
+ }
+ uint8_t* chassisBridgeDevAddr =
+ std::get_if<uint8_t>(&properties[chassisBridgeDevAddrProp]);
+ if (chassisBridgeDevAddr == nullptr)
+ {
+ lg2::error("Error to get chassis bridge device address");
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(
+ *chassisIntrusionFlag, *chassisFrontPanelFlag, *chassisNMIFlag,
+ *chassisPowerInterlockFlag, 0, *chassisFRUInfoDevAddr,
+ *chassisSDRDevAddr, *chassisSELDevAddr, *chassisSMDevAddr,
+ *chassisBridgeDevAddr);
+}
+
+/** @brief implements set chassis capalibities command
+ * @param intrusion - chassis intrusion
+ * @param fpLockout - frontpannel lockout
+ * @param reserved1 - skip one bit
+ * @param fruDeviceAddr - chassis FRU info Device Address
+ * @param sdrDeviceAddr - chassis SDR device address
+ * @param selDeviceAddr - chassis SEL device address
+ * @param smDeviceAddr - chassis system management device address
+ * @param bridgeDeviceAddr - chassis bridge device address
+ *
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSetChassisCap(
+ bool intrusion, bool fpLockout, uint6_t reserved1,
+
+ uint8_t fruDeviceAddr,
+
+ uint8_t sdrDeviceAddr,
+
+ uint8_t selDeviceAddr,
+
+ uint8_t smDeviceAddr,
+
+ uint8_t bridgeDeviceAddr)
+{
+ // check input data
+ if (reserved1 != 0)
+ {
+ lg2::error("Unsupported request parameter");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if ((fruDeviceAddr & ~chassisCapAddrMask) != 0)
+ {
+ lg2::error("Unsupported request parameter(FRU Addr) for REQ={REQ}",
+ "REQ", lg2::hex, fruDeviceAddr);
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if ((sdrDeviceAddr & ~chassisCapAddrMask) != 0)
+ {
+ lg2::error("Unsupported request parameter(SDR Addr) for REQ={REQ}",
+ "REQ", lg2::hex, sdrDeviceAddr);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if ((selDeviceAddr & ~chassisCapAddrMask) != 0)
+ {
+ lg2::error("Unsupported request parameter(SEL Addr) for REQ={REQ}",
+ "REQ", lg2::hex, selDeviceAddr);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if ((smDeviceAddr & ~chassisCapAddrMask) != 0)
+ {
+ lg2::error("Unsupported request parameter(SM Addr) for REQ={REQ}",
+ "REQ", lg2::hex, smDeviceAddr);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if ((bridgeDeviceAddr & ~chassisCapAddrMask) != 0)
+ {
+ lg2::error("Unsupported request parameter(Bridge Addr) for REQ={REQ}",
+ "REQ", lg2::hex, bridgeDeviceAddr);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ try
+ {
+ sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+ ipmi::DbusObjectInfo chassisCapObject =
+ ipmi::getDbusObject(bus, chassisCapIntf);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisIntrusionProp, intrusion);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisFrontPanelLockoutProp, fpLockout);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisFRUDevAddrProp, fruDeviceAddr);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisSDRDevAddrProp, sdrDeviceAddr);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisSELDevAddrProp, selDeviceAddr);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisSMDevAddrProp, smDeviceAddr);
+
+ ipmi::setDbusProperty(bus, chassisCapObject.second,
+ chassisCapObject.first, chassisCapIntf,
+ chassisBridgeDevAddrProp, bridgeDeviceAddr);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to set chassis capability properties: {ERR}", "ERR",
+ e);
+ return ipmi::responseUnspecifiedError();
+ }
+ return ipmi::responseSuccess();
+}
+
+//------------------------------------------
+// Calls into Host State Manager Dbus object
+//------------------------------------------
+int initiateHostStateTransition(ipmi::Context::ptr& ctx,
+ State::Host::Transition transition)
+{
+ // OpenBMC Host State Manager dbus framework
+ constexpr auto hostStatePath = "/xyz/openbmc_project/state/host0";
+ constexpr auto hostStateIntf = "xyz.openbmc_project.State.Host";
+
+ // Convert to string equivalent of the passed in transition enum.
+ auto request =
+ sdbusplus::common::xyz::openbmc_project::state::convertForMessage(
+ transition);
+
+ std::string service;
+ boost::system::error_code ec =
+ ipmi::getService(ctx, hostStateIntf, hostStatePath, service);
+
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, hostStatePath, hostStateIntf,
+ "RequestedHostTransition", request);
+ }
+ if (ec)
+ {
+ lg2::error(
+ "Failed to initiate transition for request {REQUEST}: {EXCEPTION}",
+ "REQUEST", request, "EXCEPTION", ec.message());
+ return -1;
+ }
+ lg2::info(
+ "Transition request {REQUEST} initiated successfully by user {USERID}",
+ "REQUEST", request, "USERID", ctx->userId);
+ return 0;
+}
+
+//------------------------------------------
+// Calls into Chassis State Manager Dbus object
+//------------------------------------------
+int initiateChassisStateTransition(ipmi::Context::ptr& ctx,
+ State::Chassis::Transition transition)
+{
+ // OpenBMC Chassis State Manager dbus framework
+ constexpr auto chassisStatePath = "/xyz/openbmc_project/state/chassis0";
+ constexpr auto chassisStateIntf = "xyz.openbmc_project.State.Chassis";
+
+ std::string service;
+ boost::system::error_code ec =
+ ipmi::getService(ctx, chassisStateIntf, chassisStatePath, service);
+
+ // Convert to string equivalent of the passed in transition enum.
+ auto request =
+ sdbusplus::common::xyz::openbmc_project::state::convertForMessage(
+ transition);
+
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, chassisStatePath,
+ chassisStateIntf, "RequestedPowerTransition",
+ request);
+ }
+ if (ec)
+ {
+ lg2::error("Failed to initiate transition {REQUEST}: {EXCEPTION}",
+ "REQUEST", request, "EXCEPTION", ec.message());
+
+ return -1;
+ }
+
+ return 0;
+}
+
+//------------------------------------------
+// Trigger an NMI on the host via dbus
+//------------------------------------------
+static int doNmi(ipmi::Context::ptr& ctx)
+{
+ constexpr const char* nmiIntfName = "xyz.openbmc_project.Control.Host.NMI";
+ ipmi::DbusObjectInfo nmiObj{};
+ boost::system::error_code ec;
+
+ ec = ipmi::getDbusObject(ctx, nmiIntfName, nmiObj);
+ if (ec)
+ {
+ lg2::error("Failed to find NMI service: {ERROR}", "ERROR",
+ ec.message());
+ return -1;
+ }
+
+ ec = ipmi::callDbusMethod(ctx, nmiObj.second, nmiObj.first, nmiIntfName,
+ "NMI");
+ if (ec)
+ {
+ lg2::error("NMI call failed: {ERROR}", "ERROR", ec.message());
+ elog<InternalFailure>();
+ return -1;
+ }
+
+ return 0;
+}
+
+namespace power_policy
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control::power;
+using IpmiValue = uint8_t;
+using DbusValue = RestorePolicy::Policy;
+
+const std::map<DbusValue, IpmiValue> dbusToIpmi = {
+ {RestorePolicy::Policy::AlwaysOff, 0x00},
+ {RestorePolicy::Policy::Restore, 0x01},
+ {RestorePolicy::Policy::AlwaysOn, 0x02},
+ {RestorePolicy::Policy::None, 0x03}};
+
+static constexpr uint8_t noChange = 0x03;
+static constexpr uint8_t allSupport = 0x01 | 0x02 | 0x04;
+
+/* helper function for Get Chassis Status Command
+ */
+std::optional<uint2_t> getPowerRestorePolicy()
+{
+ uint2_t restorePolicy = 0;
+ using namespace chassis::internal;
+
+ settings::Objects& objects = cache::getObjects();
+
+ try
+ {
+ const auto& powerRestoreSetting =
+ objects.map.at(powerRestoreIntf).front();
+ ipmi::Value result = ipmi::getDbusProperty(
+ *getSdBus(),
+ objects.service(powerRestoreSetting, powerRestoreIntf).c_str(),
+ powerRestoreSetting.c_str(), powerRestoreIntf,
+ "PowerRestorePolicy");
+ auto powerRestore = RestorePolicy::convertPolicyFromString(
+ std::get<std::string>(result));
+ restorePolicy = dbusToIpmi.at(powerRestore);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to fetch pgood property ({PATH}/{INTERFACE}): {ERROR}",
+ "PATH", objects.map.at(powerRestoreIntf).front(), "INTERFACE",
+ powerRestoreIntf, "ERROR", e);
+ cache::objectsPtr.reset();
+ return std::nullopt;
+ }
+ return std::make_optional(restorePolicy);
+}
+
+/*
+ * getPowerStatus
+ * helper function for Get Chassis Status Command
+ * return - optional value for pgood (no value on error)
+ */
+std::optional<bool> getPowerStatus()
+{
+ bool powerGood = false;
+ std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
+ try
+ {
+ constexpr const char* chassisStatePath =
+ "/xyz/openbmc_project/state/chassis0";
+ constexpr const char* chassisStateIntf =
+ "xyz.openbmc_project.State.Chassis";
+ auto service =
+ ipmi::getService(*busp, chassisStateIntf, chassisStatePath);
+
+ ipmi::Value powerState =
+ ipmi::getDbusProperty(*busp, service, chassisStatePath,
+ chassisStateIntf, "CurrentPowerState");
+ std::string powerStateStr = std::get<std::string>(powerState);
+ if (powerStateStr.ends_with(".On") ||
+ powerStateStr.ends_with(".TransitioningToOff"))
+ {
+ powerGood = true;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ try
+ {
+ // FIXME: some legacy modules use the older path; try that next
+ constexpr const char* legacyPwrCtrlObj =
+ "/org/openbmc/control/power0";
+ constexpr const char* legacyPwrCtrlIntf =
+ "org.openbmc.control.Power";
+ auto service =
+ ipmi::getService(*busp, legacyPwrCtrlIntf, legacyPwrCtrlObj);
+
+ ipmi::Value variant = ipmi::getDbusProperty(
+ *busp, service, legacyPwrCtrlObj, legacyPwrCtrlIntf, "pgood");
+ powerGood = static_cast<bool>(std::get<int>(variant));
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to fetch pgood property: {ERROR}", "ERROR", e);
+ return std::nullopt;
+ }
+ }
+ return std::make_optional(powerGood);
+}
+
+/*
+ * getACFailStatus
+ * helper function for Get Chassis Status Command
+ * return - bool value for ACFail (false on error)
+ */
+bool getACFailStatus()
+{
+ constexpr const char* powerControlObj =
+ "/xyz/openbmc_project/Chassis/Control/Power0";
+ constexpr const char* powerControlIntf =
+ "xyz.openbmc_project.Chassis.Control.Power";
+ bool acFail = false;
+ std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
+ try
+ {
+ auto service =
+ ipmi::getService(*bus, powerControlIntf, powerControlObj);
+
+ ipmi::Value variant = ipmi::getDbusProperty(
+ *bus, service, powerControlObj, powerControlIntf, "PFail");
+ acFail = std::get<bool>(variant);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error(
+ "Failed to fetch PFail property ({PATH}/{INTERFACE}): {ERROR}",
+ "PATH", powerControlObj, "INTERFACE", powerControlIntf, "ERROR", e);
+ }
+ return acFail;
+}
+} // namespace power_policy
+
+static std::optional<bool> getButtonDisabled(ipmi::Context::ptr& ctx,
+ const std::string& buttonPath,
+ const std::string& buttonIntf)
+{
+ bool buttonDisabled = false;
+ boost::system::error_code ec;
+ std::string service;
+ ec = ipmi::getService(ctx, buttonIntf, buttonPath, service);
+ if (!ec)
+ {
+ bool enabled;
+ ec = ipmi::getDbusProperty(ctx, service, buttonPath, buttonIntf,
+ "Enabled", enabled);
+ buttonDisabled = !enabled;
+ }
+
+ if (ec)
+ {
+ lg2::error("Fail to get button Enabled property ({PATH}): {ERROR}",
+ "PATH", buttonPath, "ERROR", ec.message());
+ return std::nullopt;
+ }
+ return std::make_optional(buttonDisabled);
+}
+
+static bool setButtonDisabled(ipmi::Context::ptr& ctx,
+ const std::string& buttonPath,
+ const std::string& buttonIntf, bool disable)
+{
+ std::string service;
+ boost::system::error_code ec;
+ ec = ipmi::getService(ctx, buttonIntf, buttonPath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, buttonPath, buttonIntf,
+ "Enabled", !disable);
+ }
+ if (ec)
+ {
+ lg2::error(
+ "Fail to set button Enabled property ({SERVICE}:{PATH}): {ERROR}",
+ "SERVICE", service, "PATH", buttonPath, "ERROR", ec.message());
+ return false;
+ }
+ return true;
+}
+
+static std::optional<bool> getChassisIntrusionStatus(ipmi::Context::ptr& ctx)
+{
+ std::vector<std::string> interfaces = {std::string(Intrusion::interface)};
+ ipmi::ObjectTree objs;
+ std::string propVal;
+ std::optional<bool> ret = std::nullopt;
+
+ boost::system::error_code ec =
+ ipmi::getSubTree(ctx, interfaces, std::string("/"), 0, objs);
+
+ if (ec)
+ {
+ lg2::error("Fail to find Chassis Intrusion Interface on D-Bus "
+ "({INTERFACE}): {ERROR}",
+ "INTERFACE", Intrusion::interface, "ERROR", ec.message());
+ return ret;
+ }
+
+ for (const auto& [path, map] : objs)
+ {
+ for (const auto& [service, intfs] : map)
+ {
+ ec = ipmi::getDbusProperty<std::string>(
+ ctx, service, path, Intrusion::interface, "Status", propVal);
+ if (ec)
+ {
+ lg2::error("Fail to get Chassis Intrusion Status property "
+ "({SERVICE}/{PATH}/{INTERFACE}): {ERROR}",
+ "SERVICE", service, "PATH", path, "INTERFACE",
+ Intrusion::interface, "ERROR", ec.message());
+ }
+ else
+ {
+ // return false if all values are Normal
+ // return true if one value is not Normal
+ // return nullopt when no value can be retrieved from D-Bus
+ auto status =
+ sdbusplus::message::convert_from_string<Intrusion::Status>(
+ propVal)
+ .value();
+ if (status == Intrusion::Status::Normal)
+ {
+ ret = std::make_optional(false);
+ }
+ else
+ {
+ ret = std::make_optional(true);
+ return ret;
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+//----------------------------------------------------------------------
+// Get Chassis Status commands
+//----------------------------------------------------------------------
+ipmi::RspType<bool, // Power is on
+ bool, // Power overload
+ bool, // Interlock
+ bool, // power fault
+ bool, // power control fault
+ uint2_t, // power restore policy
+ bool, // reserved
+
+ bool, // AC failed
+ bool, // last power down caused by a Power overload
+ bool, // last power down caused by a power interlock
+ bool, // last power down caused by power fault
+ bool, // last ‘Power is on’ state was entered via IPMI command
+ uint3_t, // reserved
+
+ bool, // Chassis intrusion active
+ bool, // Front Panel Lockout active
+ bool, // Drive Fault
+ bool, // Cooling/fan fault detected
+ uint2_t, // Chassis Identify State
+ bool, // Chassis Identify command and state info supported
+ bool, // reserved
+
+ bool, // Power off button disabled
+ bool, // Reset button disabled
+ bool, // Diagnostic Interrupt button disabled
+ bool, // Standby (sleep) button disabled
+ bool, // Power off button disable allowed
+ bool, // Reset button disable allowed
+ bool, // Diagnostic Interrupt button disable allowed
+ bool // Standby (sleep) button disable allowed
+ >
+ ipmiGetChassisStatus(ipmi::Context::ptr& ctx)
+{
+ using namespace chassis::internal;
+ std::optional<uint2_t> restorePolicy =
+ power_policy::getPowerRestorePolicy();
+ std::optional<bool> powerGood = power_policy::getPowerStatus();
+ if (!restorePolicy || !powerGood)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ // Front Panel Button Capabilities and disable/enable status(Optional)
+ std::optional<bool> powerButtonReading =
+ getButtonDisabled(ctx, powerButtonPath, powerButtonIntf);
+ // allow disable if the interface is present
+ bool powerButtonDisableAllow = static_cast<bool>(powerButtonReading);
+ // default return the button is enabled (not disabled)
+ bool powerButtonDisabled = false;
+ if (powerButtonDisableAllow)
+ {
+ // return the real value of the button status, if present
+ powerButtonDisabled = *powerButtonReading;
+ }
+
+ std::optional<bool> resetButtonReading =
+ getButtonDisabled(ctx, resetButtonPath, resetButtonIntf);
+ // allow disable if the interface is present
+ bool resetButtonDisableAllow = static_cast<bool>(resetButtonReading);
+ // default return the button is enabled (not disabled)
+ bool resetButtonDisabled = false;
+ if (resetButtonDisableAllow)
+ {
+ // return the real value of the button status, if present
+ resetButtonDisabled = *resetButtonReading;
+ }
+
+ bool powerDownAcFailed = power_policy::getACFailStatus();
+
+ bool chassisIntrusionActive = false;
+ std::optional<bool> chassisIntrusionStatus = getChassisIntrusionStatus(ctx);
+ if (chassisIntrusionStatus)
+ {
+ chassisIntrusionActive = chassisIntrusionStatus.value();
+ }
+
+ // This response has a lot of hard-coded, unsupported fields
+ // They are set to false or 0
+ constexpr bool powerOverload = false;
+ constexpr bool chassisInterlock = false;
+ constexpr bool powerFault = false;
+ constexpr bool powerControlFault = false;
+ constexpr bool powerDownOverload = false;
+ constexpr bool powerDownInterlock = false;
+ constexpr bool powerDownPowerFault = false;
+ constexpr bool powerStatusIPMI = false;
+ constexpr bool frontPanelLockoutActive = false;
+ constexpr bool driveFault = false;
+ constexpr bool coolingFanFault = false;
+ // chassisIdentifySupport set because this command is implemented
+ constexpr bool chassisIdentifySupport = true;
+ uint2_t chassisIdentifyState = types::enum_cast<uint2_t>(chassisIDState);
+ constexpr bool diagButtonDisabled = false;
+ constexpr bool sleepButtonDisabled = false;
+ constexpr bool diagButtonDisableAllow = false;
+ constexpr bool sleepButtonDisableAllow = false;
+
+ return ipmi::responseSuccess(
+ *powerGood, powerOverload, chassisInterlock, powerFault,
+ powerControlFault, *restorePolicy,
+ false, // reserved
+
+ powerDownAcFailed, powerDownOverload, powerDownInterlock,
+ powerDownPowerFault, powerStatusIPMI,
+ uint3_t(0), // reserved
+
+ chassisIntrusionActive, frontPanelLockoutActive, driveFault,
+ coolingFanFault, chassisIdentifyState, chassisIdentifySupport,
+ false, // reserved
+
+ powerButtonDisabled, resetButtonDisabled, diagButtonDisabled,
+ sleepButtonDisabled, powerButtonDisableAllow, resetButtonDisableAllow,
+ diagButtonDisableAllow, sleepButtonDisableAllow);
+}
+
+enum class IpmiRestartCause
+{
+ Unknown = 0x0,
+ RemoteCommand = 0x1,
+ ResetButton = 0x2,
+ PowerButton = 0x3,
+ WatchdogTimer = 0x4,
+ PowerPolicyAlwaysOn = 0x6,
+ PowerPolicyPreviousState = 0x7,
+ SoftReset = 0xa,
+};
+
+static IpmiRestartCause restartCauseToIpmiRestartCause(
+ State::Host::RestartCause cause)
+{
+ switch (cause)
+ {
+ case State::Host::RestartCause::Unknown:
+ {
+ return IpmiRestartCause::Unknown;
+ }
+ case State::Host::RestartCause::RemoteCommand:
+ {
+ return IpmiRestartCause::RemoteCommand;
+ }
+ case State::Host::RestartCause::ResetButton:
+ {
+ return IpmiRestartCause::ResetButton;
+ }
+ case State::Host::RestartCause::PowerButton:
+ {
+ return IpmiRestartCause::PowerButton;
+ }
+ case State::Host::RestartCause::WatchdogTimer:
+ {
+ return IpmiRestartCause::WatchdogTimer;
+ }
+ case State::Host::RestartCause::PowerPolicyAlwaysOn:
+ {
+ return IpmiRestartCause::PowerPolicyAlwaysOn;
+ }
+ case State::Host::RestartCause::PowerPolicyPreviousState:
+ {
+ return IpmiRestartCause::PowerPolicyPreviousState;
+ }
+ case State::Host::RestartCause::SoftReset:
+ {
+ return IpmiRestartCause::SoftReset;
+ }
+ default:
+ {
+ return IpmiRestartCause::Unknown;
+ }
+ }
+}
+
+/*
+ * getRestartCause
+ * helper function for Get Host restart cause Command
+ * return - optional value for RestartCause (no value on error)
+ */
+static std::optional<uint4_t> getRestartCause(ipmi::Context::ptr ctx)
+{
+ constexpr const char* restartCausePath = "/xyz/openbmc_project/state/host0";
+ constexpr const char* restartCauseIntf = "xyz.openbmc_project.State.Host";
+
+ std::string service;
+ boost::system::error_code ec =
+ ipmi::getService(ctx, restartCauseIntf, restartCausePath, service);
+ if (!ec)
+ {
+ std::string restartCauseStr;
+ ec = ipmi::getDbusProperty<std::string>(
+ ctx, service, restartCausePath, restartCauseIntf, "RestartCause",
+ restartCauseStr);
+ if (!ec)
+ {
+ auto cause =
+ State::Host::convertRestartCauseFromString(restartCauseStr);
+ return types::enum_cast<uint4_t>(
+ restartCauseToIpmiRestartCause(cause));
+ }
+ }
+
+ lg2::error(
+ "Failed to fetch RestartCause property ({PATH}/{INTERFACE}): {ERROR}",
+ "ERROR", ec.message(), "PATH", restartCausePath, "INTERFACE",
+ restartCauseIntf);
+ return std::nullopt;
+}
+
+ipmi::RspType<uint4_t, // Restart Cause
+ uint4_t, // reserved
+ uint8_t // channel number (not supported)
+ >
+ ipmiGetSystemRestartCause(ipmi::Context::ptr ctx)
+{
+ std::optional<uint4_t> cause = getRestartCause(ctx);
+ if (!cause)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ constexpr uint4_t reserved = 0;
+ auto channel = static_cast<uint8_t>(ctx->channel);
+ return ipmi::responseSuccess(cause.value(), reserved, channel);
+}
+/** @brief Implementation of chassis control command
+ *
+ * @param - chassisControl command byte
+ *
+ * @return Success or InvalidFieldRequest.
+ */
+ipmi::RspType<> ipmiChassisControl(ipmi::Context::ptr& ctx,
+ uint8_t chassisControl)
+{
+ int rc = 0;
+ switch (chassisControl)
+ {
+ case CMD_POWER_ON:
+ rc = initiateHostStateTransition(ctx, State::Host::Transition::On);
+ break;
+ case CMD_POWER_OFF:
+ rc = initiateChassisStateTransition(
+ ctx, State::Chassis::Transition::Off);
+ break;
+ case CMD_HARD_RESET:
+ rc = initiateHostStateTransition(
+ ctx, State::Host::Transition::ForceWarmReboot);
+ break;
+ case CMD_POWER_CYCLE:
+ rc = initiateHostStateTransition(ctx,
+ State::Host::Transition::Reboot);
+ break;
+ case CMD_SOFT_OFF_VIA_OVER_TEMP:
+ rc = initiateHostStateTransition(ctx, State::Host::Transition::Off);
+ break;
+ case CMD_PULSE_DIAGNOSTIC_INTR:
+ rc = doNmi(ctx);
+ break;
+
+ default:
+ {
+ lg2::error("Invalid Chassis Control command: {CMD}", "CMD",
+ lg2::hex, chassisControl);
+ return ipmi::responseInvalidFieldRequest();
+ }
+ }
+
+ return ((rc < 0) ? ipmi::responseUnspecifiedError()
+ : ipmi::responseSuccess());
+}
+
+/** @brief Return D-Bus connection string to enclosure identify LED object
+ *
+ * @param[in, out] connection - connection to D-Bus object
+ * @return a IPMI return code
+ */
+std::string getEnclosureIdentifyConnection()
+{
+ // lookup enclosure_identify group owner(s) in mapper
+ try
+ {
+ return ipmi::getService(*getSdBus(), "xyz.openbmc_project.Led.Group",
+ identify_led_object_name);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Chassis Identify: Error communicating to mapper: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+/** @brief Turn On/Off enclosure identify LED
+ *
+ * @param[in] flag - true to turn on LED, false to turn off
+ * @return a IPMI return code
+ */
+void enclosureIdentifyLed(bool flag)
+{
+ using namespace chassis::internal;
+ try
+ {
+ std::string connection = getEnclosureIdentifyConnection();
+
+ auto msg = std::string("enclosureIdentifyLed(") +
+ boost::lexical_cast<std::string>(flag) + ")";
+ lg2::debug(msg.c_str());
+
+ ipmi::setDbusProperty(*getSdBus(), connection, identify_led_object_name,
+ "xyz.openbmc_project.Led.Group", "Asserted",
+ flag);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Chassis Identify: Error Setting State {LED_STATE}: {ERROR}",
+ "LED_STATE", flag, "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+/** @brief Callback method to turn off LED
+ */
+void enclosureIdentifyLedOff()
+{
+ try
+ {
+ chassisIDState = ChassisIDState::off;
+ enclosureIdentifyLed(false);
+ }
+ catch (const InternalFailure& e)
+ {
+ report<InternalFailure>();
+ }
+}
+
+/** @brief Create timer to turn on and off the enclosure LED
+ */
+void createIdentifyTimer()
+{
+ if (!identifyTimer)
+ {
+ identifyTimer =
+ std::make_unique<sdbusplus::Timer>(enclosureIdentifyLedOff);
+ }
+}
+
+ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval,
+ std::optional<uint8_t> force)
+{
+ uint8_t identifyInterval = interval.value_or(DEFAULT_IDENTIFY_TIME_OUT);
+ bool forceIdentify = force.value_or(0) & 0x01;
+
+ if (identifyInterval || forceIdentify)
+ {
+ // stop the timer if already started;
+ // for force identify we should not turn off LED
+ identifyTimer->stop();
+ try
+ {
+ chassisIDState = ChassisIDState::temporaryOn;
+ enclosureIdentifyLed(true);
+ }
+ catch (const InternalFailure& e)
+ {
+ report<InternalFailure>();
+ return ipmi::responseResponseError();
+ }
+
+ if (forceIdentify)
+ {
+ chassisIDState = ChassisIDState::indefiniteOn;
+ return ipmi::responseSuccess();
+ }
+ // start the timer
+ auto time = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::seconds(identifyInterval));
+ identifyTimer->start(time);
+ }
+ else if (!identifyInterval)
+ {
+ identifyTimer->stop();
+ enclosureIdentifyLedOff();
+ }
+ return ipmi::responseSuccess();
+}
+
+namespace boot_options
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control::boot;
+using IpmiValue = uint8_t;
+constexpr auto ipmiDefault = 0;
+
+std::map<IpmiValue, Type::Types> typeIpmiToDbus = {{0x00, Type::Types::Legacy},
+ {0x01, Type::Types::EFI}};
+
+std::map<IpmiValue, Source::Sources> sourceIpmiToDbus = {
+ {0x01, Source::Sources::Network},
+ {0x02, Source::Sources::Disk},
+ {0x05, Source::Sources::ExternalMedia},
+ {0x0f, Source::Sources::RemovableMedia},
+ {ipmiDefault, Source::Sources::Default}};
+
+std::map<IpmiValue, Mode::Modes> modeIpmiToDbus = {
+#ifdef ENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT
+ {0x03, Mode::Modes::Safe},
+#endif // ENABLE_BOOT_SAFE_MODE_SUPPORT
+ {0x06, Mode::Modes::Setup},
+ {ipmiDefault, Mode::Modes::Regular}};
+
+std::map<Type::Types, IpmiValue> typeDbusToIpmi = {{Type::Types::Legacy, 0x00},
+ {Type::Types::EFI, 0x01}};
+
+std::map<Source::Sources, IpmiValue> sourceDbusToIpmi = {
+ {Source::Sources::Network, 0x01},
+ {Source::Sources::Disk, 0x02},
+ {Source::Sources::ExternalMedia, 0x05},
+ {Source::Sources::RemovableMedia, 0x0f},
+ {Source::Sources::Default, ipmiDefault}};
+
+std::map<Mode::Modes, IpmiValue> modeDbusToIpmi = {
+#ifdef ENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT
+ {Mode::Modes::Safe, 0x03},
+#endif // ENABLE_BOOT_SAFE_MODE_SUPPORT
+ {Mode::Modes::Setup, 0x06},
+ {Mode::Modes::Regular, ipmiDefault}};
+
+} // namespace boot_options
+
+/** @brief Get the property value for boot source
+ * @param[in] ctx - context pointer
+ * @param[out] source - boot source value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootSource(ipmi::Context::ptr& ctx, Source::Sources& source)
+{
+ using namespace chassis::internal;
+ std::string result;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootSourceIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath,
+ bootSourceIntf, "BootSource", result);
+ if (!ec)
+ {
+ source = Source::convertSourcesFromString(result);
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in BootSource Get: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot source
+ * @param[in] ctx - context pointer
+ * @param[in] source - boot source value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootSource(ipmi::Context::ptr& ctx,
+ const Source::Sources& source)
+{
+ using namespace chassis::internal;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootSourceIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath,
+ bootSourceIntf, "BootSource",
+ convertForMessage(source));
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in BootSource Set: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot mode
+ * @param[in] ctx - context pointer
+ * @param[out] mode - boot mode value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootMode(ipmi::Context::ptr& ctx, Mode::Modes& mode)
+{
+ using namespace chassis::internal;
+ std::string result;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootModeIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath, bootModeIntf,
+ "BootMode", result);
+ if (!ec)
+ {
+ mode = Mode::convertModesFromString(result);
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in BootMode Get: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot mode
+ * @param[in] ctx - context pointer
+ * @param[in] mode - boot mode value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootMode(ipmi::Context::ptr& ctx, const Mode::Modes& mode)
+{
+ using namespace chassis::internal;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootModeIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath, bootModeIntf,
+ "BootMode", convertForMessage(mode));
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in BootMode Set: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot type
+ * @param[in] ctx - context pointer
+ * @param[out] type - boot type value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootType(ipmi::Context::ptr& ctx, Type::Types& type)
+{
+ using namespace chassis::internal;
+ std::string result;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootTypeIntf, bootSettingsPath, service);
+
+ // Don't throw error if BootType interface is not present.
+ // This interface is not relevant for some Host architectures
+ // (for example POWER). In this case we don't won't IPMI to
+ // return an error, but simply return bootType as EFI.
+ type = Type::Types::EFI;
+ if (!ec)
+ {
+ ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath, bootTypeIntf,
+ "BootType", result);
+ if (ec)
+ {
+ lg2::error("Error in BootType Get: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+ }
+ type = Type::convertTypesFromString(result);
+ }
+
+ return ipmi::ccSuccess;
+}
+
+/** @brief Set the property value for boot type
+ * @param[in] ctx - context pointer
+ * @param[in] type - boot type value
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootType(ipmi::Context::ptr& ctx, const Type::Types& type)
+{
+ using namespace chassis::internal;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootTypeIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath, bootTypeIntf,
+ "BootType", convertForMessage(type));
+ if (ec)
+ {
+ lg2::error("Error in BootType Set: {ERROR}", "ERROR", ec.message());
+ return ipmi::ccUnspecifiedError;
+ }
+ }
+ // Don't throw error if BootType interface is not present.
+ // This interface is not relevant for some Host architectures
+ // (for example POWER). In this case we don't won't IPMI to
+ // return an error, but want to just skip this function.
+ return ipmi::ccSuccess;
+}
+
+/** @brief Get the property value for boot override enable
+ * @param[in] ctx - context pointer
+ * @param[out] enable - boot override enable
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootEnable(ipmi::Context::ptr& ctx, bool& enable)
+{
+ using namespace chassis::internal;
+ std::string result;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootEnableIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::getDbusProperty(ctx, service, bootSettingsPath,
+ bootEnableIntf, "Enabled", enable);
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in Boot Override Enable Get: {ERROR}", "ERROR",
+ ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot override enable
+ * @param[in] ctx - context pointer
+ * @param[in] enable - boot override enable
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootEnable(ipmi::Context::ptr& ctx, const bool& enable)
+{
+ using namespace chassis::internal;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootEnableIntf, bootSettingsPath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, bootSettingsPath,
+ bootEnableIntf, "Enabled", enable);
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in Boot Source Override Enable Set: {ERROR}", "ERROR",
+ ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Get the property value for boot override one-time
+ * @param[in] ctx - context pointer
+ * @param[out] onetime - boot override one-time
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc getBootOneTime(ipmi::Context::ptr& ctx, bool& onetime)
+{
+ using namespace chassis::internal;
+ std::string result;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootOneTimeIntf, bootSettingsOneTimePath, service);
+ if (!ec)
+ {
+ ec = ipmi::getDbusProperty(ctx, service, bootSettingsOneTimePath,
+ bootOneTimeIntf, "Enabled", onetime);
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in Boot Override OneTime Get: {ERROR}", "ERROR",
+ ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+/** @brief Set the property value for boot override one-time
+ * @param[in] ctx - context pointer
+ * @param[in] onetime - boot override one-time
+ * @return On failure return IPMI error.
+ */
+static ipmi::Cc setBootOneTime(ipmi::Context::ptr& ctx, const bool& onetime)
+{
+ using namespace chassis::internal;
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, bootOneTimeIntf, bootSettingsOneTimePath, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(ctx, service, bootSettingsOneTimePath,
+ bootOneTimeIntf, "Enabled", onetime);
+ if (!ec)
+ {
+ return ipmi::ccSuccess;
+ }
+ }
+ lg2::error("Error in Boot Source Override OneTime Set: {ERROR}", "ERROR",
+ ec.message());
+ return ipmi::ccUnspecifiedError;
+}
+
+static constexpr uint8_t setComplete = 0x0;
+static constexpr uint8_t setInProgress = 0x1;
+static uint8_t transferStatus = setComplete;
+static uint8_t bootFlagValidBitClr = 0;
+static uint5_t bootInitiatorAckData = 0x0;
+static bool cmosClear = false;
+static uint2_t biosVerbosity = 0x0;
+
+/** @brief implements the Get Chassis system boot option
+ * @param ctx - context pointer
+ * @param bootOptionParameter - boot option parameter selector
+ * @param reserved1 - reserved bit
+ * @param setSelector - selects a particular block or set of parameters
+ * under the given parameter selector
+ * write as 00h if parameter doesn't use a setSelector
+ * @param blockSelector- selects a particular block within a set of
+ * parameters write as 00h if parameter doesn't use a
+ * blockSelector
+ *
+ * @return IPMI completion code plus response data
+ * @return Payload contains below parameters:
+ * version - parameter version
+ * bootOptionParameter - boot option parameter selector
+ * parmIndicator - parameter valid/invalid indicator
+ * data - configuration parameter data
+ */
+ipmi::RspType<ipmi::message::Payload> ipmiChassisGetSysBootOptions(
+ ipmi::Context::ptr ctx, uint7_t bootOptionParameter, bool reserved1,
+ [[maybe_unused]] uint8_t setSelector,
+ [[maybe_unused]] uint8_t blockSelector)
+{
+ ipmi::Cc rc;
+ if (reserved1)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ constexpr uint4_t version = 0x01;
+ ipmi::message::Payload response;
+ response.pack(version, uint4_t{});
+ using namespace boot_options;
+
+ IpmiValue bootOption = ipmiDefault;
+
+ if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+ BootOptionParameter::setInProgress)
+ {
+ response.pack(bootOptionParameter, reserved1, transferStatus);
+ return ipmi::responseSuccess(std::move(response));
+ }
+
+ if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+ BootOptionParameter::bootInfo)
+ {
+ constexpr uint8_t writeMask = 0;
+ response.pack(bootOptionParameter, reserved1, writeMask,
+ bootInitiatorAckData);
+ return ipmi::responseSuccess(std::move(response));
+ }
+
+ if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+ BootOptionParameter::bootFlagValidClr)
+ {
+ response.pack(bootOptionParameter, reserved1,
+ uint5_t{bootFlagValidBitClr}, uint3_t{});
+ return ipmi::responseSuccess(std::move(response));
+ }
+
+ /*
+ * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
+ * This is the only parameter used by petitboot.
+ */
+ if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+ BootOptionParameter::bootFlags)
+ {
+ using namespace chassis::internal;
+ using namespace chassis::internal::cache;
+
+ try
+ {
+ Source::Sources bootSource;
+ rc = getBootSource(ctx, bootSource);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ Type::Types bootType;
+ rc = getBootType(ctx, bootType);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ Mode::Modes bootMode;
+ rc = getBootMode(ctx, bootMode);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ bootOption = sourceDbusToIpmi.at(bootSource);
+ if ((Mode::Modes::Regular == bootMode) &&
+ (Source::Sources::Default == bootSource))
+ {
+ bootOption = ipmiDefault;
+ }
+ else if (Source::Sources::Default == bootSource)
+ {
+ bootOption = modeDbusToIpmi.at(bootMode);
+ }
+
+ IpmiValue biosBootType = typeDbusToIpmi.at(bootType);
+
+ bool oneTimeEnabled;
+ rc = getBootOneTime(ctx, oneTimeEnabled);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ uint1_t permanent = oneTimeEnabled ? 0 : 1;
+
+ bool valid;
+ rc = getBootEnable(ctx, valid);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ uint1_t validFlag = valid ? 1 : 0;
+
+ response.pack(
+ bootOptionParameter, reserved1, uint5_t{},
+ uint1_t{biosBootType}, uint1_t{permanent}, uint1_t{validFlag},
+ uint2_t{}, uint4_t{bootOption}, uint1_t{}, cmosClear, uint5_t{},
+ uint2_t{biosVerbosity}, uint1_t{}, uint8_t{}, uint8_t{});
+ return ipmi::responseSuccess(std::move(response));
+ }
+ catch (const InternalFailure& e)
+ {
+ cache::objectsPtr.reset();
+ report<InternalFailure>();
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ else
+ {
+ if ((bootOptionParameter >= oemParmStart) &&
+ (bootOptionParameter <= oemParmEnd))
+ {
+ if (types::enum_cast<BootOptionParameter>(bootOptionParameter) ==
+ BootOptionParameter::opalNetworkSettings)
+ {
+ response.pack(bootOptionParameter, reserved1);
+ int ret = getHostNetworkData(response);
+ if (ret < 0)
+ {
+ response.trailingOk = true;
+ lg2::error(
+ "getHostNetworkData failed for GetSysBootOptions.");
+ return ipmi::responseUnspecifiedError();
+ }
+ else
+ {
+ return ipmi::responseSuccess(std::move(response));
+ }
+ }
+ else
+ {
+ lg2::error(
+ "ipmiChassisGetSysBootOptions: Unsupported parameter {PARAM}",
+ "PARAM", lg2::hex,
+ static_cast<uint8_t>(bootOptionParameter));
+ return ipmi::responseParmNotSupported();
+ }
+ }
+ else
+ {
+ lg2::error(
+ "ipmiChassisGetSysBootOptions: Unsupported parameter {PARAM}",
+ "PARAM", lg2::hex, static_cast<uint8_t>(bootOptionParameter));
+ return ipmi::responseParmNotSupported();
+ }
+ }
+ return ipmi::responseUnspecifiedError();
+}
+
+ipmi::RspType<> ipmiChassisSetSysBootOptions(ipmi::Context::ptr ctx,
+ uint7_t parameterSelector, bool,
+ ipmi::message::Payload& data)
+{
+ using namespace boot_options;
+ ipmi::Cc rc;
+
+ if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+ BootOptionParameter::setInProgress)
+ {
+ uint2_t setInProgressFlag;
+ uint6_t rsvd;
+ if (data.unpack(setInProgressFlag, rsvd) != 0 || !data.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ if (rsvd)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if ((transferStatus == setInProgress) &&
+ (static_cast<uint8_t>(setInProgressFlag) != setComplete))
+ {
+ return ipmi::response(IPMI_CC_FAIL_SET_IN_PROGRESS);
+ }
+ transferStatus = static_cast<uint8_t>(setInProgressFlag);
+ return ipmi::responseSuccess();
+ }
+
+ /* 000101
+ * Parameter #5 means boot flags. Please refer to 28.13 of ipmi doc.
+ * This is the only parameter used by petitboot.
+ */
+
+ if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+ BootOptionParameter::bootFlags)
+ {
+ uint5_t rsvd;
+ bool validFlag;
+ bool permanent;
+ bool biosBootType;
+ bool lockOutResetButton;
+ bool screenBlank;
+ uint4_t bootDeviceSelector;
+ bool lockKeyboard;
+ uint5_t biosCtrls;
+ bool lockOutPower;
+ uint4_t biosInfo;
+ uint4_t rsvd1;
+ uint5_t deviceInstance;
+ uint3_t rsvd2;
+
+ if (data.unpack(rsvd, biosBootType, permanent, validFlag,
+ lockOutResetButton, screenBlank, bootDeviceSelector,
+ lockKeyboard, cmosClear, biosCtrls, biosVerbosity,
+ lockOutPower, biosInfo, rsvd1, deviceInstance, rsvd2) !=
+ 0 ||
+ !data.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ if (rsvd || rsvd1 || rsvd2)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ using namespace chassis::internal;
+ using namespace chassis::internal::cache;
+
+ try
+ {
+ rc = setBootOneTime(ctx, !permanent);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ rc = setBootEnable(ctx, validFlag);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+
+ auto modeItr =
+ modeIpmiToDbus.find(static_cast<uint8_t>(bootDeviceSelector));
+ auto typeItr =
+ typeIpmiToDbus.find(static_cast<uint8_t>(biosBootType));
+ auto sourceItr =
+ sourceIpmiToDbus.find(static_cast<uint8_t>(bootDeviceSelector));
+ if (sourceIpmiToDbus.end() != sourceItr)
+ {
+ rc = setBootSource(ctx, sourceItr->second);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+ // If a set boot device is mapping to a boot source, then reset
+ // the boot mode D-Bus property to default.
+ // This way the ipmid code can determine which property is not
+ // at the default value
+ if (sourceItr->second != Source::Sources::Default)
+ {
+ rc = setBootMode(ctx, Mode::Modes::Regular);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+ }
+ }
+
+ if (typeIpmiToDbus.end() != typeItr)
+ {
+ rc = setBootType(ctx, typeItr->second);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+ }
+
+ if (modeIpmiToDbus.end() != modeItr)
+ {
+ rc = setBootMode(ctx, modeItr->second);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+ // If a set boot device is mapping to a boot mode, then reset
+ // the boot source D-Bus property to default.
+ // This way the ipmid code can determine which property is not
+ // at the default value
+ if (modeItr->second != Mode::Modes::Regular)
+ {
+ rc = setBootSource(ctx, Source::Sources::Default);
+ if (rc != ipmi::ccSuccess)
+ {
+ return ipmi::response(rc);
+ }
+ }
+ }
+ if ((modeIpmiToDbus.end() == modeItr) &&
+ (typeIpmiToDbus.end() == typeItr) &&
+ (sourceIpmiToDbus.end() == sourceItr))
+ {
+ // return error if boot option is not supported
+ lg2::error(
+ "ipmiChassisSetSysBootOptions: Boot option not supported");
+ return ipmi::responseInvalidFieldRequest();
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ objectsPtr.reset();
+ report<InternalFailure>();
+ lg2::error("ipmiChassisSetSysBootOptions: Error in setting Boot "
+ "flag parameters");
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ else if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+ BootOptionParameter::bootInfo)
+ {
+ uint8_t writeMak;
+ uint5_t bootInfoAck;
+ uint3_t rsvd;
+
+ if (data.unpack(writeMak, bootInfoAck, rsvd) != 0 ||
+ !data.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ if (rsvd)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ bootInitiatorAckData &= ~writeMak;
+ bootInitiatorAckData |= (writeMak & bootInfoAck);
+ lg2::info("ipmiChassisSetSysBootOptions: bootInfo parameter set "
+ "successfully");
+ data.trailingOk = true;
+ return ipmi::responseSuccess();
+ }
+ else if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+ BootOptionParameter::bootFlagValidClr)
+ {
+ uint5_t bootFlagValidClr;
+ uint3_t rsvd;
+
+ if (data.unpack(bootFlagValidClr, rsvd) != 0 || !data.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ if (rsvd)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ // store boot flag valid bits clear value
+ bootFlagValidBitClr = static_cast<uint8_t>(bootFlagValidClr);
+ lg2::info(
+ "ipmiChassisSetSysBootOptions: bootFlagValidBits parameter set "
+ "successfully to {VALUE}",
+ "VALUE", lg2::hex, bootFlagValidBitClr);
+ return ipmi::responseSuccess();
+ }
+ else
+ {
+ if ((parameterSelector >= static_cast<uint7_t>(oemParmStart)) &&
+ (parameterSelector <= static_cast<uint7_t>(oemParmEnd)))
+ {
+ if (types::enum_cast<BootOptionParameter>(parameterSelector) ==
+ BootOptionParameter::opalNetworkSettings)
+ {
+ ipmi::Cc ret = setHostNetworkData(data);
+ if (ret != ipmi::ccSuccess)
+ {
+ lg2::error("ipmiChassisSetSysBootOptions: Error in "
+ "setHostNetworkData");
+ data.trailingOk = true;
+ return ipmi::response(ret);
+ }
+ data.trailingOk = true;
+ return ipmi::responseSuccess();
+ }
+ else
+ {
+ lg2::error(
+ "ipmiChassisSetSysBootOptions: Unsupported param: {PARAM}",
+ "PARAM", lg2::hex, static_cast<uint8_t>(parameterSelector));
+ data.trailingOk = true;
+ return ipmi::responseParmNotSupported();
+ }
+ }
+ data.trailingOk = true;
+ return ipmi::responseParmNotSupported();
+ }
+ return ipmi::responseSuccess();
+}
+
+/** @brief implements Get POH counter command
+ * @parameter
+ * - none
+ * @returns IPMI completion code plus response data
+ * - minPerCount - Minutes per count
+ * - counterReading - counter reading
+ */
+ipmi::RspType<uint8_t, // Minutes per count
+ uint32_t // Counter reading
+ >
+ ipmiGetPOHCounter()
+{
+ // sd_bus error
+ try
+ {
+ return ipmi::responseSuccess(static_cast<uint8_t>(poh::minutesPerCount),
+ getPOHCounter());
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("getPOHCounter error: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+ipmi::RspType<uint3_t, // policy support
+ uint5_t // reserved
+ >
+ ipmiChassisSetPowerRestorePolicy(ipmi::Context::ptr ctx, uint3_t policy,
+ uint5_t reserved)
+{
+ power_policy::DbusValue value =
+ power_policy::RestorePolicy::Policy::AlwaysOff;
+
+ if (reserved || (policy > power_policy::noChange))
+ {
+ lg2::error("Reserved request parameter: {REQ}", "REQ", lg2::hex,
+ static_cast<int>(policy));
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (policy == power_policy::noChange)
+ {
+ // just return the supported policy
+ return ipmi::responseSuccess(power_policy::allSupport, reserved);
+ }
+
+ for (const auto& it : power_policy::dbusToIpmi)
+ {
+ if (it.second == policy)
+ {
+ value = it.first;
+ break;
+ }
+ }
+
+ try
+ {
+ settings::Objects& objects = chassis::internal::cache::getObjects();
+ const settings::Path& powerRestoreSetting =
+ objects.map.at(chassis::internal::powerRestoreIntf).front();
+
+ boost::system::error_code ec = ipmi::setDbusProperty(
+ ctx,
+ objects.service(powerRestoreSetting,
+ chassis::internal::powerRestoreIntf),
+ powerRestoreSetting, chassis::internal::powerRestoreIntf,
+ "PowerRestorePolicy", convertForMessage(value));
+ if (ec)
+ {
+ lg2::error("Unspecified Error");
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ catch (const InternalFailure& e)
+ {
+ chassis::internal::cache::objectsPtr.reset();
+ report<InternalFailure>();
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(power_policy::allSupport, reserved);
+}
+
+ipmi::RspType<> ipmiSetFrontPanelButtonEnables(
+ ipmi::Context::ptr ctx, bool disablePowerButton, bool disableResetButton,
+ bool, bool, uint4_t)
+{
+ using namespace chassis::internal;
+
+ // set power button Enabled property
+ bool success = setButtonDisabled(ctx, powerButtonPath, powerButtonIntf,
+ disablePowerButton);
+
+ // set reset button Enabled property
+ success &= setButtonDisabled(ctx, resetButtonPath, resetButtonIntf,
+ disableResetButton);
+
+ if (!success)
+ {
+ // not all buttons were successfully set
+ return ipmi::responseUnspecifiedError();
+ }
+ return ipmi::responseSuccess();
+}
+
+void registerNetFnChassisFunctions()
+{
+ createIdentifyTimer();
+
+ // Get Chassis Capabilities
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdGetChassisCapabilities,
+ ipmi::Privilege::User, ipmiGetChassisCap);
+
+ // Set Front Panel Button Enables
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdSetFrontPanelButtonEnables,
+ ipmi::Privilege::Admin,
+ ipmiSetFrontPanelButtonEnables);
+
+ // Set Chassis Capabilities
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdSetChassisCapabilities,
+ ipmi::Privilege::User, ipmiSetChassisCap);
+
+ // <Get System Boot Options>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdGetSystemBootOptions,
+ ipmi::Privilege::Operator,
+ ipmiChassisGetSysBootOptions);
+
+ // <Get Chassis Status>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdGetChassisStatus,
+ ipmi::Privilege::User, ipmiGetChassisStatus);
+
+ // <Chassis Get System Restart Cause>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdGetSystemRestartCause,
+ ipmi::Privilege::User, ipmiGetSystemRestartCause);
+
+ // <Chassis Control>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdChassisControl,
+ ipmi::Privilege::Operator, ipmiChassisControl);
+
+ // <Chassis Identify>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdChassisIdentify,
+ ipmi::Privilege::Operator, ipmiChassisIdentify);
+
+ // <Set System Boot Options>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdSetSystemBootOptions,
+ ipmi::Privilege::Operator,
+ ipmiChassisSetSysBootOptions);
+
+ // <Get POH Counter>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdGetPohCounter,
+ ipmi::Privilege::User, ipmiGetPOHCounter);
+
+ // <Set Power Restore Policy>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis,
+ ipmi::chassis::cmdSetPowerRestorePolicy,
+ ipmi::Privilege::Operator,
+ ipmiChassisSetPowerRestorePolicy);
+}
diff --git a/chassishandler.hpp b/chassishandler.hpp
new file mode 100644
index 0000000..8d8af3b
--- /dev/null
+++ b/chassishandler.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <cstddef>
+
+// Command specific completion codes
+enum ipmi_chassis_return_codes
+{
+ IPMI_OK = 0x0,
+ IPMI_CC_PARM_NOT_SUPPORTED = 0x80,
+ IPMI_CC_FAIL_SET_IN_PROGRESS = 0x81,
+};
+
+// Generic completion codes,
+// see IPMI doc section 5.2
+enum ipmi_generic_return_codes
+{
+ IPMI_OUT_OF_SPACE = 0xC4,
+};
+
+// Various Chassis operations under a single command.
+enum ipmi_chassis_control_cmds : uint8_t
+{
+ CMD_POWER_OFF = 0x00,
+ CMD_POWER_ON = 0x01,
+ CMD_POWER_CYCLE = 0x02,
+ CMD_HARD_RESET = 0x03,
+ CMD_PULSE_DIAGNOSTIC_INTR = 0x04,
+ CMD_SOFT_OFF_VIA_OVER_TEMP = 0x05,
+};
+enum class BootOptionParameter : size_t
+{
+ setInProgress = 0x0,
+ bootFlagValidClr = 0x3,
+ bootInfo = 0x4,
+ bootFlags = 0x5,
+ opalNetworkSettings = 0x61
+};
+
+enum class BootOptionResponseSize : size_t
+{
+ setInProgress = 3,
+ bootFlags = 5,
+ opalNetworkSettings = 50
+};
+
+enum class ChassisIDState : uint8_t
+{
+ off = 0x0,
+ temporaryOn = 0x1,
+ indefiniteOn = 0x2,
+ reserved = 0x3
+};
diff --git a/dbus-sdr/meson.build b/dbus-sdr/meson.build
new file mode 100644
index 0000000..a937485
--- /dev/null
+++ b/dbus-sdr/meson.build
@@ -0,0 +1,43 @@
+sensorutils_lib = static_library(
+ 'sensorutils',
+ 'sensorutils.cpp',
+ include_directories: root_inc,
+ implicit_include_directories: false,
+)
+
+sensorutils_dep = declare_dependency(link_with: sensorutils_lib)
+
+hybrid_src = []
+
+if get_option('hybrid-sensors').allowed()
+ hybrid_src = [
+ 'sensorhandler.cpp',
+ 'sensordatahandler.cpp',
+ 'ipmisensor.cpp',
+ generated_src,
+ ]
+endif
+
+sensorsoem_src = []
+if get_option('sensors-oem').allowed()
+ sensorsoem_src = ['dbus-sdr/sensorcommands_oem.cpp']
+endif
+
+dbus_sdr_pre = declare_dependency(
+ include_directories: root_inc,
+ dependencies: [
+ crypto,
+ nlohmann_json_dep,
+ phosphor_logging_dep,
+ ipmid_dep,
+ sensorutils_dep,
+ ],
+)
+
+dbus_sdr_src = [
+ 'dbus-sdr/sdrutils.cpp',
+ 'dbus-sdr/sensorcommands.cpp',
+ 'dbus-sdr/storagecommands.cpp',
+ hybrid_src,
+ sensorsoem_src,
+]
diff --git a/dbus-sdr/sdrutils.cpp b/dbus-sdr/sdrutils.cpp
new file mode 100644
index 0000000..9523593
--- /dev/null
+++ b/dbus-sdr/sdrutils.cpp
@@ -0,0 +1,652 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "dbus-sdr/sdrutils.hpp"
+
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+#include <optional>
+#include <unordered_set>
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include <ipmid/utils.hpp>
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+
+#endif
+
+boost::container::flat_map<
+ const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
+ sensorTypes{
+ {{"temperature", std::make_pair(SensorTypeCodes::temperature,
+ SensorEventTypeCodes::threshold)},
+ {"voltage", std::make_pair(SensorTypeCodes::voltage,
+ SensorEventTypeCodes::threshold)},
+ {"current", std::make_pair(SensorTypeCodes::current,
+ SensorEventTypeCodes::threshold)},
+ {"fan_tach", std::make_pair(SensorTypeCodes::fan,
+ SensorEventTypeCodes::threshold)},
+ {"fan_pwm", std::make_pair(SensorTypeCodes::fan,
+ SensorEventTypeCodes::threshold)},
+ {"intrusion", std::make_pair(SensorTypeCodes::physical_security,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"processor", std::make_pair(SensorTypeCodes::processor,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"power", std::make_pair(SensorTypeCodes::other,
+ SensorEventTypeCodes::threshold)},
+ {"memory", std::make_pair(SensorTypeCodes::memory,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"state", std::make_pair(SensorTypeCodes::power_unit,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"buttons", std::make_pair(SensorTypeCodes::buttons,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"watchdog", std::make_pair(SensorTypeCodes::watchdog2,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"entity", std::make_pair(SensorTypeCodes::entity,
+ SensorEventTypeCodes::sensorSpecified)},
+ {"energy", std::make_pair(SensorTypeCodes::other,
+ SensorEventTypeCodes::threshold)}}};
+
+namespace details
+{
+
+// IPMI supports a smaller number of sensors than are available via Redfish.
+// Trim the list of sensors, via a configuration file.
+// Read the IPMI Sensor Filtering section in docs/configuration.md for
+// a more detailed description.
+static void filterSensors(SensorSubTree& subtree)
+{
+ constexpr const char* filterFilename =
+ "/usr/share/ipmi-providers/sensor_filter.json";
+ std::ifstream filterFile(filterFilename);
+ if (!filterFile.good())
+ {
+ return;
+ }
+ nlohmann::json sensorFilterJSON =
+ nlohmann::json::parse(filterFile, nullptr, false);
+ nlohmann::json::iterator svcFilterit =
+ sensorFilterJSON.find("ServiceFilter");
+ if (svcFilterit == sensorFilterJSON.end())
+ {
+ return;
+ }
+
+ subtree.erase(std::remove_if(subtree.begin(), subtree.end(),
+ [svcFilterit](SensorSubTree::value_type& kv) {
+ auto& [_, serviceToIfaces] = kv;
+
+ for (auto service = svcFilterit->begin();
+ service != svcFilterit->end();
+ ++service)
+ {
+ serviceToIfaces.erase(*service);
+ }
+ return serviceToIfaces.empty();
+ }),
+ subtree.end());
+}
+
+uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree)
+{
+ static std::shared_ptr<SensorSubTree> sensorTreePtr;
+ static uint16_t sensorUpdatedIndex = 0;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+ static sdbusplus::bus::match_t sensorAdded(
+ *dbus,
+ "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
+
+ static sdbusplus::bus::match_t sensorRemoved(
+ *dbus,
+ "type='signal',member='InterfacesRemoved',arg0path='/xyz/"
+ "openbmc_project/sensors/'",
+ [](sdbusplus::message_t&) { sensorTreePtr.reset(); });
+
+ if (sensorTreePtr)
+ {
+ subtree = sensorTreePtr;
+ return sensorUpdatedIndex;
+ }
+
+ sensorTreePtr = std::make_shared<SensorSubTree>();
+
+ static constexpr const int32_t depth = 2;
+
+ auto lbdUpdateSensorTree = [&dbus](const char* path,
+ const auto& interfaces) {
+ auto mapperCall = dbus->new_method_call(
+ "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTree");
+ SensorSubTree sensorTreePartial;
+
+ mapperCall.append(path, depth, interfaces);
+
+ try
+ {
+ auto mapperReply = dbus->call(mapperCall);
+ mapperReply.read(sensorTreePartial);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to update subtree, path: {PATH}, error: {ERROR}",
+ "PATH", path, "ERROR", e);
+ return false;
+ }
+ if constexpr (debug)
+ {
+ std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n",
+ sensorTreePartial.size(), path);
+ }
+ sensorTreePtr->merge(std::move(sensorTreePartial));
+ return true;
+ };
+
+ // Add sensors to SensorTree
+ static constexpr const std::array sensorInterfaces = {
+ "xyz.openbmc_project.Sensor.Value",
+ "xyz.openbmc_project.Sensor.ValueMutability",
+ "xyz.openbmc_project.Sensor.Threshold.Warning",
+ "xyz.openbmc_project.Sensor.Threshold.Critical"};
+ static constexpr const std::array vrInterfaces = {
+ "xyz.openbmc_project.Control.VoltageRegulatorMode"};
+
+ bool sensorRez =
+ lbdUpdateSensorTree("/xyz/openbmc_project/sensors", sensorInterfaces);
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+ if (!ipmi::sensor::sensors.empty())
+ {
+ for (const auto& sensor : ipmi::sensor::sensors)
+ {
+ // Threshold sensors should not be emplaced in here.
+ if (boost::starts_with(sensor.second.sensorPath,
+ "/xyz/openbmc_project/sensors/"))
+ {
+ continue;
+ }
+
+ // The bus service name is not listed in ipmi::sensor::Info. Give it
+ // an empty string. For those function using non-threshold sensors,
+ // the bus service name will be retrieved in an alternative way.
+ boost::container::flat_map<std::string, std::vector<std::string>>
+ connectionMap{
+ {"", {sensor.second.propertyInterfaces.begin()->first}}};
+ sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap);
+ }
+ }
+
+#endif
+
+ // Error if searching for sensors failed.
+ if (!sensorRez)
+ {
+ return sensorUpdatedIndex;
+ }
+
+ filterSensors(*sensorTreePtr);
+ // Add VR control as optional search path.
+ (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces);
+
+ subtree = sensorTreePtr;
+ sensorUpdatedIndex++;
+ // The SDR is being regenerated, wipe the old stats
+ sdrStatsTable.wipeTable();
+ sdrWriteTable.wipeTable();
+ return sensorUpdatedIndex;
+}
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap)
+{
+ static std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ bool sensorNumMapUpated = false;
+ static uint16_t prevSensorUpdatedIndex = 0;
+ std::shared_ptr<SensorSubTree> sensorTree;
+ uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree);
+ if (!sensorTree)
+ {
+ return sensorNumMapUpated;
+ }
+
+ if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr)
+ {
+ sensorNumMap = sensorNumMapPtr;
+ return sensorNumMapUpated;
+ }
+ prevSensorUpdatedIndex = curSensorUpdatedIndex;
+
+ sensorNumMapPtr = std::make_shared<SensorNumMap>();
+
+ uint16_t sensorNum = 0;
+ uint16_t sensorIndex = 0;
+ for (const auto& sensor : *sensorTree)
+ {
+ sensorNumMapPtr->insert(
+ SensorNumMap::value_type(sensorNum, sensor.first));
+ sensorIndex++;
+ if (sensorIndex == maxSensorsPerLUN)
+ {
+ sensorIndex = lun1Sensor0;
+ }
+ else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN))
+ {
+ // Skip assigning LUN 0x2 any sensors
+ sensorIndex = lun3Sensor0;
+ }
+ else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN))
+ {
+ // this is an error, too many IPMI sensors
+ throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
+ }
+ sensorNum = sensorIndex;
+ }
+ sensorNumMap = sensorNumMapPtr;
+ sensorNumMapUpated = true;
+ return sensorNumMapUpated;
+}
+} // namespace details
+
+bool getSensorSubtree(SensorSubTree& subtree)
+{
+ std::shared_ptr<SensorSubTree> sensorTree;
+ details::getSensorSubtree(sensorTree);
+ if (!sensorTree)
+ {
+ return false;
+ }
+
+ subtree = *sensorTree;
+ return true;
+}
+
+#ifdef FEATURE_HYBRID_SENSORS
+// Static sensors are listed in sensor-gen.cpp.
+ipmi::sensor::IdInfoMap::const_iterator findStaticSensor(
+ const std::string& path)
+{
+ return std::find_if(
+ ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(),
+ [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) {
+ return findSensor.second.sensorPath == path;
+ });
+}
+#endif
+
+std::string getSensorTypeStringFromPath(const std::string& path)
+{
+ // get sensor type string from path, path is defined as
+ // /xyz/openbmc_project/sensors/<type>/label
+ size_t typeEnd = path.rfind("/");
+ if (typeEnd == std::string::npos)
+ {
+ return path;
+ }
+ size_t typeStart = path.rfind("/", typeEnd - 1);
+ if (typeStart == std::string::npos)
+ {
+ return path;
+ }
+ // Start at the character after the '/'
+ typeStart++;
+ return path.substr(typeStart, typeEnd - typeStart);
+}
+
+uint8_t getSensorTypeFromPath(const std::string& path)
+{
+ uint8_t sensorType = 0;
+ std::string type = getSensorTypeStringFromPath(path);
+ auto findSensor = sensorTypes.find(type.c_str());
+ if (findSensor != sensorTypes.end())
+ {
+ sensorType =
+ static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second));
+ } // else default 0x0 RESERVED
+
+ return sensorType;
+}
+
+uint16_t getSensorNumberFromPath(const std::string& path)
+{
+ std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ details::getSensorNumMap(sensorNumMapPtr);
+ if (!sensorNumMapPtr)
+ {
+ return invalidSensorNumber;
+ }
+
+ try
+ {
+ return sensorNumMapPtr->right.at(path);
+ }
+ catch (const std::out_of_range& e)
+ {
+ return invalidSensorNumber;
+ }
+}
+
+uint8_t getSensorEventTypeFromPath(const std::string& path)
+{
+ uint8_t sensorEventType = 0;
+ std::string type = getSensorTypeStringFromPath(path);
+ auto findSensor = sensorTypes.find(type.c_str());
+ if (findSensor != sensorTypes.end())
+ {
+ sensorEventType = static_cast<uint8_t>(
+ std::get<sensorEventTypeCodes>(findSensor->second));
+ }
+
+ return sensorEventType;
+}
+
+std::string getPathFromSensorNumber(uint16_t sensorNum)
+{
+ std::shared_ptr<SensorNumMap> sensorNumMapPtr;
+ details::getSensorNumMap(sensorNumMapPtr);
+ if (!sensorNumMapPtr)
+ {
+ return std::string();
+ }
+
+ try
+ {
+ return sensorNumMapPtr->left.at(sensorNum);
+ }
+ catch (const std::out_of_range& e)
+ {
+ return std::string();
+ }
+}
+
+namespace ipmi
+{
+
+std::optional<std::map<std::string, std::vector<std::string>>>
+ getObjectInterfaces(const char* path)
+{
+ std::map<std::string, std::vector<std::string>> interfacesResponse;
+ std::vector<std::string> interfaces;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+ sdbusplus::message_t getObjectMessage =
+ dbus->new_method_call("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetObject");
+ getObjectMessage.append(path, interfaces);
+
+ try
+ {
+ sdbusplus::message_t response = dbus->call(getObjectMessage);
+ response.read(interfacesResponse);
+ }
+ catch (const std::exception& e)
+ {
+ return std::nullopt;
+ }
+
+ return interfacesResponse;
+}
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+ const char* interface)
+{
+ std::map<std::string, Value> properties;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+
+ sdbusplus::message_t getProperties =
+ dbus->new_method_call("xyz.openbmc_project.EntityManager", path,
+ "org.freedesktop.DBus.Properties", "GetAll");
+ getProperties.append(interface);
+
+ try
+ {
+ sdbusplus::message_t response = dbus->call(getProperties);
+ response.read(properties);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to GetAll, path: {PATH}, interface: {INTERFACE}, "
+ "error: {ERROR}",
+ "PATH", path, "INTERFACE", interface, "ERROR", e);
+ }
+
+ return properties;
+}
+
+// Fetch the ipmiDecoratorPaths to get the list of dbus objects that
+// have ipmi decorator to prevent unnessary dbus call to fetch the info
+std::optional<std::unordered_set<std::string>>& getIpmiDecoratorPaths(
+ const std::optional<ipmi::Context::ptr>& ctx)
+{
+ static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths;
+
+ if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt)
+ {
+ return ipmiDecoratorPaths;
+ }
+
+ using Paths = std::vector<std::string>;
+ boost::system::error_code ec;
+ Paths paths = ipmi::callDbusMethod<Paths>(
+ *ctx, ec, "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0),
+ std::array<const char*, 1>{
+ "xyz.openbmc_project.Inventory.Decorator.Ipmi"});
+
+ if (ec)
+ {
+ return ipmiDecoratorPaths;
+ }
+
+ ipmiDecoratorPaths =
+ std::unordered_set<std::string>(paths.begin(), paths.end());
+ return ipmiDecoratorPaths;
+}
+
+const std::string* getSensorConfigurationInterface(
+ const std::map<std::string, std::vector<std::string>>&
+ sensorInterfacesResponse)
+{
+ auto entityManagerService =
+ sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager");
+ if (entityManagerService == sensorInterfacesResponse.end())
+ {
+ return nullptr;
+ }
+
+ // Find the fan configuration first (fans can have multiple configuration
+ // interfaces).
+ for (const auto& entry : entityManagerService->second)
+ {
+ if (entry == "xyz.openbmc_project.Configuration.AspeedFan" ||
+ entry == "xyz.openbmc_project.Configuration.I2CFan" ||
+ entry == "xyz.openbmc_project.Configuration.NuvotonFan")
+ {
+ return &entry;
+ }
+ }
+
+ for (const auto& entry : entityManagerService->second)
+ {
+ if (boost::algorithm::starts_with(entry,
+ "xyz.openbmc_project.Configuration."))
+ {
+ return &entry;
+ }
+ }
+
+ return nullptr;
+}
+
+// Follow Association properties for Sensor back to the Board dbus object to
+// check for an EntityId and EntityInstance property.
+void updateIpmiFromAssociation(
+ const std::string& path,
+ const std::unordered_set<std::string>& ipmiDecoratorPaths,
+ const DbusInterfaceMap& sensorMap, uint8_t& entityId,
+ uint8_t& entityInstance)
+{
+ namespace fs = std::filesystem;
+
+ auto sensorAssociationObject =
+ sensorMap.find("xyz.openbmc_project.Association.Definitions");
+ if (sensorAssociationObject == sensorMap.end())
+ {
+ if constexpr (debug)
+ {
+ std::fprintf(stderr, "path=%s, no association interface found\n",
+ path.c_str());
+ }
+
+ return;
+ }
+
+ auto associationObject =
+ sensorAssociationObject->second.find("Associations");
+ if (associationObject == sensorAssociationObject->second.end())
+ {
+ if constexpr (debug)
+ {
+ std::fprintf(stderr, "path=%s, no association records found\n",
+ path.c_str());
+ }
+
+ return;
+ }
+
+ std::vector<Association> associationValues =
+ std::get<std::vector<Association>>(associationObject->second);
+
+ // loop through the Associations looking for the right one:
+ for (const auto& entry : associationValues)
+ {
+ // forward, reverse, endpoint
+ const std::string& forward = std::get<0>(entry);
+ const std::string& reverse = std::get<1>(entry);
+ const std::string& endpoint = std::get<2>(entry);
+
+ // We only currently concern ourselves with chassis+all_sensors.
+ if (!(forward == "chassis" && reverse == "all_sensors"))
+ {
+ continue;
+ }
+
+ // the endpoint is the board entry provided by
+ // Entity-Manager. so let's grab its properties if it has
+ // the right interface.
+
+ // just try grabbing the properties first.
+ ipmi::PropertyMap::iterator entityIdProp;
+ ipmi::PropertyMap::iterator entityInstanceProp;
+ if (ipmiDecoratorPaths.contains(endpoint))
+ {
+ std::map<std::string, Value> ipmiProperties =
+ getEntityManagerProperties(
+ endpoint.c_str(),
+ "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+
+ entityIdProp = ipmiProperties.find("EntityId");
+ entityInstanceProp = ipmiProperties.find("EntityInstance");
+ if (entityIdProp != ipmiProperties.end())
+ {
+ entityId = static_cast<uint8_t>(
+ std::get<uint64_t>(entityIdProp->second));
+ }
+ if (entityInstanceProp != ipmiProperties.end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(entityInstanceProp->second));
+ }
+ }
+
+ // Now check the entity-manager entry for this sensor to see
+ // if it has its own value and use that instead.
+ //
+ // In theory, checking this first saves us from checking
+ // both, except in most use-cases identified, there won't be
+ // a per sensor override, so we need to always check both.
+ std::string sensorNameFromPath = fs::path(path).filename();
+
+ std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath;
+
+ // Download the interfaces for the sensor from
+ // Entity-Manager to find the name of the configuration
+ // interface.
+ std::optional<std::map<std::string, std::vector<std::string>>>
+ sensorInterfacesResponseOpt =
+ getObjectInterfaces(sensorConfigPath.c_str());
+
+ if (!sensorInterfacesResponseOpt.has_value())
+ {
+ lg2::debug("Failed to GetObject, path: {PATH}", "PATH",
+ sensorConfigPath);
+ continue;
+ }
+
+ const std::string* configurationInterface =
+ getSensorConfigurationInterface(
+ sensorInterfacesResponseOpt.value());
+
+ // If there are multi association path settings and only one path exist,
+ // we need to continue if cannot find configuration interface for this
+ // sensor.
+ if (!configurationInterface)
+ {
+ continue;
+ }
+
+ // We found a configuration interface.
+ std::map<std::string, Value> configurationProperties =
+ getEntityManagerProperties(sensorConfigPath.c_str(),
+ configurationInterface->c_str());
+
+ entityIdProp = configurationProperties.find("EntityId");
+ entityInstanceProp = configurationProperties.find("EntityInstance");
+ if (entityIdProp != configurationProperties.end())
+ {
+ entityId =
+ static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second));
+ }
+ if (entityInstanceProp != configurationProperties.end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(entityInstanceProp->second));
+ }
+
+ // stop searching Association records.
+ break;
+ } // end for Association vectors.
+
+ if constexpr (debug)
+ {
+ std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n",
+ path.c_str(), entityId, entityInstance);
+ }
+}
+
+} // namespace ipmi
diff --git a/dbus-sdr/sensorcommands.cpp b/dbus-sdr/sensorcommands.cpp
new file mode 100644
index 0000000..2ef33b9
--- /dev/null
+++ b/dbus-sdr/sensorcommands.cpp
@@ -0,0 +1,2882 @@
+/*
+// Copyright (c) 2017 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "config.h"
+
+#include "dbus-sdr/sensorcommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "dbus-sdr/sensorutils.hpp"
+#include "dbus-sdr/storagecommands.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cmath>
+#include <cstring>
+#include <format>
+#include <iostream>
+#include <map>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+
+#ifdef FEATURE_HYBRID_SENSORS
+
+#include "sensordatahandler.hpp"
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+#endif
+namespace ipmi
+{
+namespace dcmi
+{
+// Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
+static const std::map<uint8_t, uint8_t> validEntityId{
+ {0x40, 0x37}, {0x37, 0x40}, {0x41, 0x03},
+ {0x03, 0x41}, {0x42, 0x07}, {0x07, 0x42}};
+constexpr uint8_t temperatureSensorType = 0x01;
+constexpr uint8_t maxRecords = 8;
+} // namespace dcmi
+} // namespace ipmi
+constexpr std::array<const char*, 7> suffixes = {
+ "_Output_Voltage", "_Input_Voltage", "_Output_Current", "_Input_Current",
+ "_Output_Power", "_Input_Power", "_Temperature"};
+namespace ipmi
+{
+
+using phosphor::logging::entry;
+using phosphor::logging::level;
+using phosphor::logging::log;
+
+static constexpr int sensorMapUpdatePeriod = 10;
+static constexpr int sensorMapSdrUpdatePeriod = 60;
+
+// BMC I2C address is generally at 0x20
+static constexpr uint8_t bmcI2CAddr = 0x20;
+
+constexpr size_t maxSDRTotalSize =
+ 76; // Largest SDR Record Size (type 01) + SDR Overheader Size
+constexpr static const uint32_t noTimestamp = 0xFFFFFFFF;
+
+static uint16_t sdrReservationID;
+static uint32_t sdrLastAdd = noTimestamp;
+static uint32_t sdrLastRemove = noTimestamp;
+static constexpr size_t lastRecordIndex = 0xFFFF;
+
+// The IPMI spec defines four Logical Units (LUN), each capable of supporting
+// 255 sensors. The 256 values assigned to LUN 2 are special and are not used
+// for general purpose sensors. Each LUN reserves location 0xFF. The maximum
+// number of IPMI sensors are LUN 0 + LUN 1 + LUN 3, less the reserved
+// location.
+static constexpr size_t maxIPMISensors = ((3 * 256) - (3 * 1));
+
+static constexpr uint8_t lun0 = 0x0;
+static constexpr uint8_t lun1 = 0x1;
+static constexpr uint8_t lun3 = 0x3;
+
+static constexpr size_t lun0MaxSensorNum = 0xfe;
+static constexpr size_t lun1MaxSensorNum = 0x1fe;
+static constexpr size_t lun3MaxSensorNum = 0x3fe;
+static constexpr int GENERAL_ERROR = -1;
+
+static boost::container::flat_map<std::string, ObjectValueTree> SensorCache;
+
+// Specify the comparison required to sort and find char* map objects
+struct CmpStr
+{
+ bool operator()(const char* a, const char* b) const
+ {
+ return std::strcmp(a, b) < 0;
+ }
+};
+const static boost::container::flat_map<const char*, SensorUnits, CmpStr>
+ sensorUnits{{{"temperature", SensorUnits::degreesC},
+ {"voltage", SensorUnits::volts},
+ {"current", SensorUnits::amps},
+ {"fan_tach", SensorUnits::rpm},
+ {"power", SensorUnits::watts},
+ {"energy", SensorUnits::joules}}};
+
+void registerSensorFunctions() __attribute__((constructor));
+
+static sdbusplus::bus::match_t sensorAdded(
+ *getSdBus(),
+ "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message_t&) {
+ getSensorTree().clear();
+ getIpmiDecoratorPaths(/*ctx=*/std::nullopt).reset();
+ sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ });
+
+static sdbusplus::bus::match_t sensorRemoved(
+ *getSdBus(),
+ "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
+ "sensors/'",
+ [](sdbusplus::message_t&) {
+ getSensorTree().clear();
+ getIpmiDecoratorPaths(/*ctx=*/std::nullopt).reset();
+ sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ });
+
+ipmi_ret_t getSensorConnection(ipmi::Context::ptr ctx, uint8_t sensnum,
+ std::string& connection, std::string& path,
+ std::vector<std::string>* interfaces)
+{
+ auto& sensorTree = getSensorTree();
+ if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ if (ctx == nullptr)
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+
+ path = getPathFromSensorNumber((ctx->lun << 8) | sensnum);
+ if (path.empty())
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+
+ for (const auto& sensor : sensorTree)
+ {
+ if (path == sensor.first)
+ {
+ connection = sensor.second.begin()->first;
+ if (interfaces)
+ *interfaces = sensor.second.begin()->second;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+SensorSubTree& getSensorTree()
+{
+ static SensorSubTree sensorTree;
+ return sensorTree;
+}
+
+// this keeps track of deassertions for sensor event status command. A
+// deasertion can only happen if an assertion was seen first.
+static boost::container::flat_map<
+ std::string, boost::container::flat_map<std::string, std::optional<bool>>>
+ thresholdDeassertMap;
+
+static sdbusplus::bus::match_t thresholdChanged(
+ *getSdBus(),
+ "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
+ "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
+ [](sdbusplus::message_t& m) {
+ boost::container::flat_map<std::string, std::variant<bool, double>>
+ values;
+ m.read(std::string(), values);
+
+ auto findAssert =
+ std::find_if(values.begin(), values.end(), [](const auto& pair) {
+ return pair.first.find("Alarm") != std::string::npos;
+ });
+ if (findAssert != values.end())
+ {
+ auto ptr = std::get_if<bool>(&(findAssert->second));
+ if (ptr == nullptr)
+ {
+ lg2::error("thresholdChanged: Assert non bool");
+ return;
+ }
+ if (*ptr)
+ {
+ lg2::info(
+ "thresholdChanged: Assert, sensor path: {SENSOR_PATH}",
+ "SENSOR_PATH", m.get_path());
+ thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
+ }
+ else
+ {
+ auto& value =
+ thresholdDeassertMap[m.get_path()][findAssert->first];
+ if (value)
+ {
+ lg2::info(
+ "thresholdChanged: deassert, sensor path: {SENSOR_PATH}",
+ "SENSOR_PATH", m.get_path());
+ value = *ptr;
+ }
+ }
+ }
+ });
+
+namespace sensor
+{
+static constexpr const char* vrInterface =
+ "xyz.openbmc_project.Control.VoltageRegulatorMode";
+static constexpr const char* sensorInterface =
+ "xyz.openbmc_project.Sensor.Value";
+} // namespace sensor
+
+static void getSensorMaxMin(const DbusInterfaceMap& sensorMap, double& max,
+ double& min)
+{
+ max = 127;
+ min = -128;
+
+ auto sensorObject = sensorMap.find(sensor::sensorInterface);
+ auto critical =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ auto warning =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+
+ if (sensorObject != sensorMap.end())
+ {
+ auto maxMap = sensorObject->second.find("MaxValue");
+ auto minMap = sensorObject->second.find("MinValue");
+
+ if (maxMap != sensorObject->second.end())
+ {
+ max = std::visit(VariantToDoubleVisitor(), maxMap->second);
+ }
+ if (minMap != sensorObject->second.end())
+ {
+ min = std::visit(VariantToDoubleVisitor(), minMap->second);
+ }
+ }
+ if (critical != sensorMap.end())
+ {
+ auto lower = critical->second.find("CriticalLow");
+ auto upper = critical->second.find("CriticalHigh");
+ if (lower != critical->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), lower->second);
+ if (std::isfinite(value))
+ {
+ min = std::fmin(value, min);
+ }
+ }
+ if (upper != critical->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), upper->second);
+ if (std::isfinite(value))
+ {
+ max = std::fmax(value, max);
+ }
+ }
+ }
+ if (warning != sensorMap.end())
+ {
+ auto lower = warning->second.find("WarningLow");
+ auto upper = warning->second.find("WarningHigh");
+ if (lower != warning->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), lower->second);
+ if (std::isfinite(value))
+ {
+ min = std::fmin(value, min);
+ }
+ }
+ if (upper != warning->second.end())
+ {
+ double value = std::visit(VariantToDoubleVisitor(), upper->second);
+ if (std::isfinite(value))
+ {
+ max = std::fmax(value, max);
+ }
+ }
+ }
+}
+
+static bool getSensorMap(ipmi::Context::ptr ctx, std::string sensorConnection,
+ std::string sensorPath, DbusInterfaceMap& sensorMap,
+ int updatePeriod = sensorMapUpdatePeriod)
+{
+#ifdef FEATURE_HYBRID_SENSORS
+ if (auto sensor = findStaticSensor(sensorPath);
+ sensor != ipmi::sensor::sensors.end() &&
+ getSensorEventTypeFromPath(sensorPath) !=
+ static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+ {
+ // If the incoming sensor is a discrete sensor, it might fail in
+ // getManagedObjects(), return true, and use its own getFunc to get
+ // value.
+ return true;
+ }
+#endif
+
+ static boost::container::flat_map<
+ std::string, std::chrono::time_point<std::chrono::steady_clock>>
+ updateTimeMap;
+
+ auto updateFind = updateTimeMap.find(sensorConnection);
+ auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
+ if (updateFind != updateTimeMap.end())
+ {
+ lastUpdate = updateFind->second;
+ }
+
+ auto now = std::chrono::steady_clock::now();
+
+ if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
+ .count() > updatePeriod)
+ {
+ bool found = false;
+
+ // Object managers for different kinds of OpenBMC DBus interfaces.
+ // Documented in the phosphor-dbus-interfaces repository.
+ const char* paths[] = {
+ "/xyz/openbmc_project/sensors",
+ "/xyz/openbmc_project/vr",
+ };
+ constexpr size_t num_paths = sizeof(paths) / sizeof(paths[0]);
+ ObjectValueTree allManagedObjects;
+
+ for (size_t i = 0; i < num_paths; i++)
+ {
+ ObjectValueTree managedObjects;
+ boost::system::error_code ec = getManagedObjects(
+ ctx, sensorConnection.c_str(), paths[i], managedObjects);
+ if (ec)
+ {
+ continue;
+ }
+ allManagedObjects.merge(managedObjects);
+ found = true;
+ }
+
+ if (!found)
+ {
+ lg2::error("GetMangagedObjects for getSensorMap failed, "
+ "service: {SERVICE}",
+ "SERVICE", sensorConnection);
+
+ return false;
+ }
+
+ SensorCache[sensorConnection] = allManagedObjects;
+ // Update time after finish building the map which allow the
+ // data to be cached for updatePeriod plus the build time.
+ updateTimeMap[sensorConnection] = std::chrono::steady_clock::now();
+ }
+ auto connection = SensorCache.find(sensorConnection);
+ if (connection == SensorCache.end())
+ {
+ return false;
+ }
+ auto path = connection->second.find(sensorPath);
+ if (path == connection->second.end())
+ {
+ return false;
+ }
+ sensorMap = path->second;
+
+ return true;
+}
+
+namespace sensor
+{
+// Read VR profiles from sensor(daemon) interface
+static std::optional<std::vector<std::string>> getSupportedVrProfiles(
+ const ipmi::DbusInterfaceMap::mapped_type& object)
+{
+ // get VR mode profiles from Supported Interface
+ auto supportedProperty = object.find("Supported");
+ if (supportedProperty == object.end() ||
+ object.find("Selected") == object.end())
+ {
+ lg2::error("Missing the required Supported and Selected properties");
+ return std::nullopt;
+ }
+
+ const auto profilesPtr =
+ std::get_if<std::vector<std::string>>(&supportedProperty->second);
+
+ if (profilesPtr == nullptr)
+ {
+ lg2::error("property is not array of string");
+ return std::nullopt;
+ }
+ return *profilesPtr;
+}
+
+// Calculate VR Mode from input IPMI discrete event bytes
+static std::optional<std::string> calculateVRMode(
+ uint15_t assertOffset, const ipmi::DbusInterfaceMap::mapped_type& VRObject)
+{
+ // get VR mode profiles from Supported Interface
+ auto profiles = getSupportedVrProfiles(VRObject);
+ if (!profiles)
+ {
+ return std::nullopt;
+ }
+
+ // interpret IPMI cmd bits into profiles' index
+ long unsigned int index = 0;
+ // only one bit should be set and the highest bit should not be used.
+ if (assertOffset == 0 || assertOffset == (1u << 15) ||
+ (assertOffset & (assertOffset - 1)))
+ {
+ lg2::error("IPMI cmd format incorrect, bytes: {BYTES}", "BYTES",
+ lg2::hex, static_cast<uint16_t>(assertOffset));
+ return std::nullopt;
+ }
+
+ while (assertOffset != 1)
+ {
+ assertOffset >>= 1;
+ index++;
+ }
+
+ if (index >= profiles->size())
+ {
+ lg2::error("profile index out of boundary");
+ return std::nullopt;
+ }
+
+ return profiles->at(index);
+}
+
+// Calculate sensor value from IPMI reading byte
+static std::optional<double> calculateValue(
+ uint8_t reading, const ipmi::DbusInterfaceMap& sensorMap,
+ const ipmi::DbusInterfaceMap::mapped_type& valueObject)
+{
+ if (valueObject.find("Value") == valueObject.end())
+ {
+ lg2::error("Missing the required Value property");
+ return std::nullopt;
+ }
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return std::nullopt;
+ }
+
+ double value = bSigned ? ((int8_t)reading) : reading;
+
+ value *= ((double)mValue);
+ value += ((double)bValue) * std::pow(10.0, bExp);
+ value *= std::pow(10.0, rExp);
+
+ return value;
+}
+
+// Extract file name from sensor path as the sensors SDR ID. Simplify the name
+// if it is too long.
+std::string parseSdrIdFromPath(const std::string& path)
+{
+ std::string name;
+ size_t nameStart = path.rfind("/");
+ if (nameStart != std::string::npos)
+ {
+ name = path.substr(nameStart + 1, std::string::npos - nameStart);
+ }
+
+ if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
+ {
+#ifdef SHORTNAME_REMOVE_SUFFIX
+ for (const auto& suffix : suffixes)
+ {
+ if (boost::ends_with(name, suffix))
+ {
+ boost::replace_all(name, suffix, "");
+ break;
+ }
+ }
+#endif
+#ifdef SHORTNAME_REPLACE_WORDS
+ constexpr std::array<std::pair<const char*, const char*>, 2>
+ replaceWords = {std::make_pair("Output", "Out"),
+ std::make_pair("Input", "In")};
+ for (const auto& [find, replace] : replaceWords)
+ {
+ boost::replace_all(name, find, replace);
+ }
+#endif
+
+ // as a backup and if nothing else is configured
+ name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
+ }
+ return name;
+}
+
+bool getVrEventStatus(ipmi::Context::ptr ctx, const std::string& connection,
+ const std::string& path,
+ const ipmi::DbusInterfaceMap::mapped_type& object,
+ std::bitset<16>& assertions)
+{
+ auto profiles = sensor::getSupportedVrProfiles(object);
+ if (!profiles)
+ {
+ return false;
+ }
+ std::string mode;
+
+ auto ec = getDbusProperty(ctx, connection, path, sensor::vrInterface,
+ "Selected", mode);
+ if (ec)
+ {
+ lg2::error("Failed to get Selected, path: {PATH}, "
+ "interface: {INTERFACE}, error: {ERROR}",
+ "PATH", path, "INTERFACE", sensor::sensorInterface, "ERROR",
+ ec.message());
+ return false;
+ }
+
+ auto itr = std::find(profiles->begin(), profiles->end(), mode);
+ if (itr == profiles->end())
+ {
+ lg2::error("VR mode doesn't match any of its profiles, path: {PATH}",
+ "PATH", path);
+ return false;
+ }
+ std::size_t index =
+ static_cast<std::size_t>(std::distance(profiles->begin(), itr));
+
+ // map index to response event assertion bit.
+ if (index < 16)
+ {
+ assertions.set(index);
+ }
+ else
+ {
+ lg2::error("VR profile index reaches max assertion bit, "
+ "path: {PATH}, index: {INDEX}",
+ "PATH", path, "INDEX", index);
+ return false;
+ }
+ if constexpr (debug)
+ {
+ std::cerr << "VR sensor " << sensor::parseSdrIdFromPath(path)
+ << " mode is: [" << index << "] " << mode << std::endl;
+ }
+ return true;
+}
+
+/*
+ * Handle every Sensor Data Record besides Type 01
+ *
+ * The D-Bus sensors work well for generating Type 01 SDRs.
+ * After the Type 01 sensors are processed the remaining sensor types require
+ * special handling. Each BMC vendor is going to have their own requirements for
+ * insertion of non-Type 01 records.
+ * Manage non-Type 01 records:
+ *
+ * Create a new file: dbus-sdr/sensorcommands_oem.cpp
+ * Populate it with the two weakly linked functions below, without adding the
+ * 'weak' attribute definition prior to the function definition.
+ * getOtherSensorsCount(...)
+ * getOtherSensorsDataRecord(...)
+ * Example contents are provided in the weak definitions below
+ * Enable 'sensors-oem' in your phosphor-ipmi-host bbappend file
+ * 'EXTRA_OEMESON:append = " -Dsensors-oem=enabled"'
+ * The contents of the sensorcommands_oem.cpp file will then override the code
+ * provided below.
+ */
+
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx) __attribute__((weak));
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx)
+{
+ size_t fruCount = 0;
+
+ ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount);
+ if (ret != ipmi::ccSuccess)
+ {
+ lg2::error("getOtherSensorsCount: getFruSdrCount error");
+ return std::numeric_limits<size_t>::max();
+ }
+
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+ size_t entityCount = entityRecords.size();
+
+ return fruCount + ipmi::storage::type12Count + entityCount;
+}
+
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+ std::vector<uint8_t>& recordData)
+ __attribute__((weak));
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+ std::vector<uint8_t>& recordData)
+{
+ size_t otherCount{ipmi::sensor::getOtherSensorsCount(ctx)};
+ if (otherCount == std::numeric_limits<size_t>::max())
+ {
+ return GENERAL_ERROR;
+ }
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+
+ size_t sdrIndex(recordID - ipmi::getNumberOfSensors());
+ size_t entityCount{entityRecords.size()};
+ size_t fruCount{otherCount - ipmi::storage::type12Count - entityCount};
+
+ if (sdrIndex > otherCount)
+ {
+ return std::numeric_limits<int>::min();
+ }
+ else if (sdrIndex >= fruCount + ipmi::storage::type12Count)
+ {
+ // handle type 8 entity map records
+ ipmi::sensor::EntityInfoMap::const_iterator entity =
+ entityRecords.find(static_cast<uint8_t>(
+ sdrIndex - fruCount - ipmi::storage::type12Count));
+
+ if (entity == entityRecords.end())
+ {
+ return GENERAL_ERROR;
+ }
+ recordData = ipmi::storage::getType8SDRs(entity, recordID);
+ }
+ else if (sdrIndex >= fruCount)
+ {
+ // handle type 12 hardcoded records
+ size_t type12Index = sdrIndex - fruCount;
+ if (type12Index >= ipmi::storage::type12Count)
+ {
+ lg2::error("getSensorDataRecord: type12Index error");
+ return GENERAL_ERROR;
+ }
+ recordData = ipmi::storage::getType12SDRs(type12Index, recordID);
+ }
+ else
+ {
+ // handle fru records
+ get_sdr::SensorDataFruRecord data;
+ if (ipmi::Cc ret = ipmi::storage::getFruSdrs(ctx, sdrIndex, data);
+ ret != IPMI_CC_OK)
+ {
+ return GENERAL_ERROR;
+ }
+ data.header.record_id_msb = recordID >> 8;
+ data.header.record_id_lsb = recordID & 0xFF;
+ recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&data),
+ reinterpret_cast<uint8_t*>(&data) + sizeof(data));
+ }
+
+ return 0;
+}
+
+} // namespace sensor
+
+ipmi::RspType<> ipmiSenPlatformEvent(ipmi::Context::ptr ctx,
+ ipmi::message::Payload& p)
+{
+ constexpr const uint8_t validEnvmRev = 0x04;
+ constexpr const uint8_t lastSensorType = 0x2C;
+ constexpr const uint8_t oemReserved = 0xC0;
+
+ uint8_t sysgeneratorID = 0;
+ uint8_t evmRev = 0;
+ uint8_t sensorType = 0;
+ uint8_t sensorNum = 0;
+ uint8_t eventType = 0;
+ uint8_t eventData1 = 0;
+ std::optional<uint8_t> eventData2 = 0;
+ std::optional<uint8_t> eventData3 = 0;
+ [[maybe_unused]] uint16_t generatorID = 0;
+ ipmi::ChannelInfo chInfo;
+
+ if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess)
+ {
+ lg2::error("Failed to get Channel Info, channel: {CHANNEL}", "CHANNEL",
+ ctx->channel);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
+ ipmi::EChannelMediumType::systemInterface)
+ {
+ p.unpack(sysgeneratorID, evmRev, sensorType, sensorNum, eventType,
+ eventData1, eventData2, eventData3);
+ constexpr const uint8_t isSoftwareID = 0x01;
+ if (!(sysgeneratorID & isSoftwareID))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ // Refer to IPMI Spec Table 32: SEL Event Records
+ generatorID = (ctx->channel << 12) // Channel
+ | (0x0 << 10) // Reserved
+ | (0x0 << 8) // 0x0 for sys-soft ID
+ | sysgeneratorID;
+ }
+ else
+ {
+ p.unpack(evmRev, sensorType, sensorNum, eventType, eventData1,
+ eventData2, eventData3);
+ // Refer to IPMI Spec Table 32: SEL Event Records
+ generatorID = (ctx->channel << 12) // Channel
+ | (0x0 << 10) // Reserved
+ | ((ctx->lun & 0x3) << 8) // Lun
+ | (ctx->rqSA << 1);
+ }
+
+ if (!p.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+
+ // Check for valid evmRev and Sensor Type(per Table 42 of spec)
+ if (evmRev != validEnvmRev)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if ((sensorType > lastSensorType) && (sensorType < oemReserved))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<> ipmiSetSensorReading(
+ ipmi::Context::ptr ctx, uint8_t sensorNumber, uint8_t, uint8_t reading,
+ uint15_t assertOffset, bool, uint15_t, bool, uint8_t, uint8_t, uint8_t)
+{
+ std::string connection;
+ std::string path;
+ std::vector<std::string> interfaces;
+
+ ipmi::Cc status =
+ getSensorConnection(ctx, sensorNumber, connection, path, &interfaces);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+ // we can tell the sensor type by its interface type
+ if (std::find(interfaces.begin(), interfaces.end(),
+ sensor::sensorInterface) != interfaces.end())
+ {
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+ auto sensorObject = sensorMap.find(sensor::sensorInterface);
+ if (sensorObject == sensorMap.end())
+ {
+ return ipmi::responseResponseError();
+ }
+
+ // Only allow external SetSensor if write permission granted
+ if (!details::sdrWriteTable.getWritePermission(
+ (ctx->lun << 8) | sensorNumber))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ auto value =
+ sensor::calculateValue(reading, sensorMap, sensorObject->second);
+ if (!value)
+ {
+ return ipmi::responseResponseError();
+ }
+
+ if constexpr (debug)
+ {
+ lg2::info("IPMI SET_SENSOR, sensor number: {SENSOR_NUM}, "
+ "byte: {BYTE}, value: {VALUE}",
+ "SENSOR_NUM", sensorNumber, "BYTE", (unsigned int)reading,
+ "VALUE", *value);
+ }
+
+ boost::system::error_code ec =
+ setDbusProperty(ctx, connection, path, sensor::sensorInterface,
+ "Value", ipmi::Value(*value));
+
+ // setDbusProperty intended to resolve dbus exception/rc within the
+ // function but failed to achieve that. Catch exception in the ipmi
+ // callback functions for now (e.g. ipmiSetSensorReading).
+ if (ec)
+ {
+ lg2::error("Failed to set Value, path: {PATH}, "
+ "interface: {INTERFACE}, ERROR: {ERROR}",
+ "PATH", path, "INTERFACE", sensor::sensorInterface,
+ "ERROR", ec.message());
+ return ipmi::responseResponseError();
+ }
+ return ipmi::responseSuccess();
+ }
+
+ if (std::find(interfaces.begin(), interfaces.end(), sensor::vrInterface) !=
+ interfaces.end())
+ {
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+ auto sensorObject = sensorMap.find(sensor::vrInterface);
+ if (sensorObject == sensorMap.end())
+ {
+ return ipmi::responseResponseError();
+ }
+
+ // VR sensors are treated as a special case and we will not check the
+ // write permission for VR sensors, since they always deemed writable
+ // and permission table are not applied to VR sensors.
+ auto vrMode =
+ sensor::calculateVRMode(assertOffset, sensorObject->second);
+ if (!vrMode)
+ {
+ return ipmi::responseResponseError();
+ }
+ boost::system::error_code ec = setDbusProperty(
+ ctx, connection, path, sensor::vrInterface, "Selected", *vrMode);
+ // setDbusProperty intended to resolve dbus exception/rc within the
+ // function but failed to achieve that. Catch exception in the ipmi
+ // callback functions for now (e.g. ipmiSetSensorReading).
+ if (ec)
+ {
+ lg2::error("Failed to set Selected, path: {PATH}, "
+ "interface: {INTERFACE}, ERROR: {ERROR}",
+ "PATH", path, "INTERFACE", sensor::sensorInterface,
+ "ERROR", ec.message());
+ }
+ return ipmi::responseSuccess();
+ }
+
+ lg2::error("unknown sensor type, path: {PATH}", "PATH", path);
+ return ipmi::responseResponseError();
+}
+
+ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
+ ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum)
+{
+ std::string connection;
+ std::string path;
+
+ if (sensnum == reservedSensorNumber)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto status = getSensorConnection(ctx, sensnum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+#ifdef FEATURE_HYBRID_SENSORS
+ if (auto sensor = findStaticSensor(path);
+ sensor != ipmi::sensor::sensors.end() &&
+ getSensorEventTypeFromPath(path) !=
+ static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+ {
+ if (ipmi::sensor::Mutability::Read !=
+ (sensor->second.mutability & ipmi::sensor::Mutability::Read))
+ {
+ return ipmi::responseIllegalCommand();
+ }
+
+ uint8_t operation;
+ try
+ {
+ ipmi::sensor::GetSensorResponse getResponse =
+ sensor->second.getFunc(sensor->second);
+
+ if (getResponse.readingOrStateUnavailable)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::readingStateUnavailable);
+ }
+ if (getResponse.scanningEnabled)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::sensorScanningEnable);
+ }
+ if (getResponse.allEventMessagesEnabled)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::eventMessagesEnable);
+ }
+ return ipmi::responseSuccess(
+ getResponse.reading, operation,
+ getResponse.thresholdLevelsStates,
+ getResponse.discreteReadingSensorStates);
+ }
+ catch (const std::exception& e)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::readingStateUnavailable);
+ return ipmi::responseSuccess(0, operation, 0, std::nullopt);
+ }
+ }
+#endif
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+ auto sensorObject = sensorMap.find(sensor::sensorInterface);
+
+ if (sensorObject == sensorMap.end() ||
+ sensorObject->second.find("Value") == sensorObject->second.end())
+ {
+ return ipmi::responseResponseError();
+ }
+ auto& valueVariant = sensorObject->second["Value"];
+ double reading = std::visit(VariantToDoubleVisitor(), valueVariant);
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ uint8_t value =
+ scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned);
+ uint8_t operation =
+ static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable);
+ operation |=
+ static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable);
+ bool notReading = std::isnan(reading);
+
+ if (!notReading)
+ {
+ auto availableObject =
+ sensorMap.find("xyz.openbmc_project.State.Decorator.Availability");
+ if (availableObject != sensorMap.end())
+ {
+ auto findAvailable = availableObject->second.find("Available");
+ if (findAvailable != availableObject->second.end())
+ {
+ bool* available = std::get_if<bool>(&(findAvailable->second));
+ if (available && !(*available))
+ {
+ notReading = true;
+ }
+ }
+ }
+ }
+
+ if (notReading)
+ {
+ operation |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::readingStateUnavailable);
+ }
+
+ if constexpr (details::enableInstrumentation)
+ {
+ int byteValue;
+ if (bSigned)
+ {
+ byteValue = static_cast<int>(static_cast<int8_t>(value));
+ }
+ else
+ {
+ byteValue = static_cast<int>(static_cast<uint8_t>(value));
+ }
+
+ // Keep stats on the reading just obtained, even if it is "NaN"
+ if (details::sdrStatsTable.updateReading((ctx->lun << 8) | sensnum,
+ reading, byteValue))
+ {
+ // This is the first reading, show the coefficients
+ double step = (max - min) / 255.0;
+ std::cerr
+ << "IPMI sensor "
+ << details::sdrStatsTable.getName((ctx->lun << 8) | sensnum)
+ << ": Range min=" << min << " max=" << max << ", step=" << step
+ << ", Coefficients mValue=" << static_cast<int>(mValue)
+ << " rExp=" << static_cast<int>(rExp)
+ << " bValue=" << static_cast<int>(bValue)
+ << " bExp=" << static_cast<int>(bExp)
+ << " bSigned=" << static_cast<int>(bSigned) << "\n";
+ }
+ }
+
+ uint8_t thresholds = 0;
+
+ auto warningObject =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ if (warningObject != sensorMap.end())
+ {
+ auto alarmHigh = warningObject->second.find("WarningAlarmHigh");
+ auto alarmLow = warningObject->second.find("WarningAlarmLow");
+ if (alarmHigh != warningObject->second.end())
+ {
+ if (std::get<bool>(alarmHigh->second))
+ {
+ thresholds |= static_cast<uint8_t>(
+ IPMISensorReadingByte3::upperNonCritical);
+ }
+ }
+ if (alarmLow != warningObject->second.end())
+ {
+ if (std::get<bool>(alarmLow->second))
+ {
+ thresholds |= static_cast<uint8_t>(
+ IPMISensorReadingByte3::lowerNonCritical);
+ }
+ }
+ }
+
+ auto criticalObject =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if (criticalObject != sensorMap.end())
+ {
+ auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh");
+ auto alarmLow = criticalObject->second.find("CriticalAlarmLow");
+ if (alarmHigh != criticalObject->second.end())
+ {
+ if (std::get<bool>(alarmHigh->second))
+ {
+ thresholds |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+ }
+ }
+ if (alarmLow != criticalObject->second.end())
+ {
+ if (std::get<bool>(alarmLow->second))
+ {
+ thresholds |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+ }
+ }
+ }
+
+ // no discrete as of today so optional byte is never returned
+ return ipmi::responseSuccess(value, operation, thresholds, std::nullopt);
+}
+
+/** @brief implements the Set Sensor threshold command
+ * @param sensorNumber - sensor number
+ * @param lowerNonCriticalThreshMask
+ * @param lowerCriticalThreshMask
+ * @param lowerNonRecovThreshMask
+ * @param upperNonCriticalThreshMask
+ * @param upperCriticalThreshMask
+ * @param upperNonRecovThreshMask
+ * @param reserved
+ * @param lowerNonCritical - lower non-critical threshold
+ * @param lowerCritical - Lower critical threshold
+ * @param lowerNonRecoverable - Lower non recovarable threshold
+ * @param upperNonCritical - Upper non-critical threshold
+ * @param upperCritical - Upper critical
+ * @param upperNonRecoverable - Upper Non-recoverable
+ *
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+ ipmi::Context::ptr ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+ bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+ bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+ bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+ uint8_t lowerCritical, [[maybe_unused]] uint8_t lowerNonRecoverable,
+ uint8_t upperNonCritical, uint8_t upperCritical,
+ [[maybe_unused]] uint8_t upperNonRecoverable)
+{
+ if (sensorNum == reservedSensorNumber || reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // lower nc and upper nc not suppported on any sensor
+ if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // if none of the threshold mask are set, nothing to do
+ if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+ lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+ upperCriticalThreshMask | upperNonRecovThreshMask))
+ {
+ return ipmi::responseSuccess();
+ }
+
+ std::string connection;
+ std::string path;
+
+ ipmi::Cc status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ // store a vector of property name, value to set, and interface
+ std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+ // define the indexes of the tuple
+ constexpr uint8_t propertyName = 0;
+ constexpr uint8_t thresholdValue = 1;
+ constexpr uint8_t interface = 2;
+ // verifiy all needed fields are present
+ if (lowerCriticalThreshMask || upperCriticalThreshMask)
+ {
+ auto findThreshold =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if (findThreshold == sensorMap.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (lowerCriticalThreshMask)
+ {
+ auto findLower = findThreshold->second.find("CriticalLow");
+ if (findLower == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+ findThreshold->first);
+ }
+ if (upperCriticalThreshMask)
+ {
+ auto findUpper = findThreshold->second.find("CriticalHigh");
+ if (findUpper == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+ findThreshold->first);
+ }
+ }
+ if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+ {
+ auto findThreshold =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ if (findThreshold == sensorMap.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (lowerNonCriticalThreshMask)
+ {
+ auto findLower = findThreshold->second.find("WarningLow");
+ if (findLower == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+ findThreshold->first);
+ }
+ if (upperNonCriticalThreshMask)
+ {
+ auto findUpper = findThreshold->second.find("WarningHigh");
+ if (findUpper == findThreshold->second.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+ findThreshold->first);
+ }
+ }
+ for (const auto& property : thresholdsToSet)
+ {
+ // from section 36.3 in the IPMI Spec, assume all linear
+ double valueToSet = ((mValue * std::get<thresholdValue>(property)) +
+ (bValue * std::pow(10.0, bExp))) *
+ std::pow(10.0, rExp);
+ setDbusProperty(
+ *getSdBus(), connection, path, std::get<interface>(property),
+ std::get<propertyName>(property), ipmi::Value(valueToSet));
+ }
+ return ipmi::responseSuccess();
+}
+
+IPMIThresholds getIPMIThresholds(const DbusInterfaceMap& sensorMap)
+{
+ IPMIThresholds resp;
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ auto sensorPair = sensorMap.find(sensor::sensorInterface);
+
+ if (sensorPair == sensorMap.end())
+ {
+ // should not have been able to find a sensor not implementing
+ // the sensor object
+ throw std::runtime_error("Invalid sensor map");
+ }
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int16_t bValue = 0;
+ int8_t rExp = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ throw std::runtime_error("Invalid sensor atrributes");
+ }
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningHigh");
+ auto warningLow = warningMap.find("WarningLow");
+
+ if (warningHigh != warningMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningHigh->second);
+ if (std::isfinite(value))
+ {
+ resp.warningHigh = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ if (warningLow != warningMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningLow->second);
+ if (std::isfinite(value))
+ {
+ resp.warningLow = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalHigh");
+ auto criticalLow = criticalMap.find("CriticalLow");
+
+ if (criticalHigh != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalHigh->second);
+ if (std::isfinite(value))
+ {
+ resp.criticalHigh = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalLow->second);
+ if (std::isfinite(value))
+ {
+ resp.criticalLow = scaleIPMIValueFromDouble(
+ value, mValue, rExp, bValue, bExp, bSigned);
+ }
+ }
+ }
+ }
+ return resp;
+}
+
+ipmi::RspType<uint8_t, // readable
+ uint8_t, // lowerNCrit
+ uint8_t, // lowerCrit
+ uint8_t, // lowerNrecoverable
+ uint8_t, // upperNC
+ uint8_t, // upperCrit
+ uint8_t> // upperNRecoverable
+ ipmiSenGetSensorThresholds(ipmi::Context::ptr ctx, uint8_t sensorNumber)
+{
+ std::string connection;
+ std::string path;
+
+ if (sensorNumber == reservedSensorNumber)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto status = getSensorConnection(ctx, sensorNumber, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ IPMIThresholds thresholdData;
+ try
+ {
+ thresholdData = getIPMIThresholds(sensorMap);
+ }
+ catch (const std::exception&)
+ {
+ return ipmi::responseResponseError();
+ }
+
+ uint8_t readable = 0;
+ uint8_t lowerNC = 0;
+ uint8_t lowerCritical = 0;
+ uint8_t lowerNonRecoverable = 0;
+ uint8_t upperNC = 0;
+ uint8_t upperCritical = 0;
+ uint8_t upperNonRecoverable = 0;
+
+ if (thresholdData.warningHigh)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical);
+ upperNC = *thresholdData.warningHigh;
+ }
+ if (thresholdData.warningLow)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical);
+ lowerNC = *thresholdData.warningLow;
+ }
+
+ if (thresholdData.criticalHigh)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical);
+ upperCritical = *thresholdData.criticalHigh;
+ }
+ if (thresholdData.criticalLow)
+ {
+ readable |=
+ 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical);
+ lowerCritical = *thresholdData.criticalLow;
+ }
+
+ return ipmi::responseSuccess(readable, lowerNC, lowerCritical,
+ lowerNonRecoverable, upperNC, upperCritical,
+ upperNonRecoverable);
+}
+
+/** @brief implements the get Sensor event enable command
+ * @param sensorNumber - sensor number
+ *
+ * @returns IPMI completion code plus response data
+ * - enabled - Sensor Event messages
+ * - assertionEnabledLsb - Assertion event messages
+ * - assertionEnabledMsb - Assertion event messages
+ * - deassertionEnabledLsb - Deassertion event messages
+ * - deassertionEnabledMsb - Deassertion event messages
+ */
+
+ipmi::RspType<uint8_t, // enabled
+ uint8_t, // assertionEnabledLsb
+ uint8_t, // assertionEnabledMsb
+ uint8_t, // deassertionEnabledLsb
+ uint8_t> // deassertionEnabledMsb
+ ipmiSenGetSensorEventEnable(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+ std::string connection;
+ std::string path;
+
+ uint8_t enabled = 0;
+ uint8_t assertionEnabledLsb = 0;
+ uint8_t assertionEnabledMsb = 0;
+ uint8_t deassertionEnabledLsb = 0;
+ uint8_t deassertionEnabledMsb = 0;
+
+ if (sensorNum == reservedSensorNumber)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ return ipmi::response(status);
+ }
+
+#ifdef FEATURE_HYBRID_SENSORS
+ if (auto sensor = findStaticSensor(path);
+ sensor != ipmi::sensor::sensors.end() &&
+ getSensorEventTypeFromPath(path) !=
+ static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+ {
+ enabled = static_cast<uint8_t>(
+ IPMISensorEventEnableByte2::sensorScanningEnable);
+ uint16_t assertionEnabled = 0;
+ for (auto& offsetValMap : sensor->second.propertyInterfaces.begin()
+ ->second.begin()
+ ->second.second)
+ {
+ assertionEnabled |= (1 << offsetValMap.first);
+ }
+ assertionEnabledLsb = static_cast<uint8_t>((assertionEnabled & 0xFF));
+ assertionEnabledMsb =
+ static_cast<uint8_t>(((assertionEnabled >> 8) & 0xFF));
+
+ return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+ assertionEnabledMsb, deassertionEnabledLsb,
+ deassertionEnabledMsb);
+ }
+#endif
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ enabled = static_cast<uint8_t>(
+ IPMISensorEventEnableByte2::sensorScanningEnable);
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningHigh");
+ auto warningLow = warningMap.find("WarningLow");
+ if (warningHigh != warningMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningHigh->second);
+ if (std::isfinite(value))
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ upperNonCriticalGoingHigh);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ upperNonCriticalGoingLow);
+ }
+ }
+ if (warningLow != warningMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), warningLow->second);
+ if (std::isfinite(value))
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ lowerNonCriticalGoingLow);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ lowerNonCriticalGoingHigh);
+ }
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalHigh");
+ auto criticalLow = criticalMap.find("CriticalLow");
+
+ if (criticalHigh != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalHigh->second);
+ if (std::isfinite(value))
+ {
+ assertionEnabledMsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ upperCriticalGoingHigh);
+ deassertionEnabledMsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingLow);
+ }
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ double value =
+ std::visit(VariantToDoubleVisitor(), criticalLow->second);
+ if (std::isfinite(value))
+ {
+ assertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ deassertionEnabledLsb |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::
+ lowerCriticalGoingHigh);
+ }
+ }
+ }
+ }
+
+ return ipmi::responseSuccess(enabled, assertionEnabledLsb,
+ assertionEnabledMsb, deassertionEnabledLsb,
+ deassertionEnabledMsb);
+}
+
+/** @brief implements the get Sensor event status command
+ * @param sensorNumber - sensor number, FFh = reserved
+ *
+ * @returns IPMI completion code plus response data
+ * - sensorEventStatus - Sensor Event messages state
+ * - assertions - Assertion event messages
+ * - deassertions - Deassertion event messages
+ */
+ipmi::RspType<uint8_t, // sensorEventStatus
+ std::bitset<16>, // assertions
+ std::bitset<16> // deassertion
+ >
+ ipmiSenGetSensorEventStatus(ipmi::Context::ptr ctx, uint8_t sensorNum)
+{
+ if (sensorNum == reservedSensorNumber)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::string connection;
+ std::string path;
+ auto status = getSensorConnection(ctx, sensorNum, connection, path);
+ if (status)
+ {
+ lg2::error("ipmiSenGetSensorEventStatus: Sensor connection Error, "
+ "sensor number: {SENSOR_NUM}",
+ "SENSOR_NUM", sensorNum);
+ return ipmi::response(status);
+ }
+
+#ifdef FEATURE_HYBRID_SENSORS
+ if (auto sensor = findStaticSensor(path);
+ sensor != ipmi::sensor::sensors.end() &&
+ getSensorEventTypeFromPath(path) !=
+ static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+ {
+ auto response = ipmi::sensor::get::mapDbusToAssertion(
+ sensor->second, path, sensor->second.sensorInterface);
+ std::bitset<16> assertions;
+ // deassertions are not used.
+ std::bitset<16> deassertions = 0;
+ uint8_t sensorEventStatus;
+ if (response.readingOrStateUnavailable)
+ {
+ sensorEventStatus |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::readingStateUnavailable);
+ }
+ if (response.scanningEnabled)
+ {
+ sensorEventStatus |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::sensorScanningEnable);
+ }
+ if (response.allEventMessagesEnabled)
+ {
+ sensorEventStatus |= static_cast<uint8_t>(
+ IPMISensorReadingByte2::eventMessagesEnable);
+ }
+ assertions |= response.discreteReadingSensorStates << 8;
+ assertions |= response.thresholdLevelsStates;
+ return ipmi::responseSuccess(sensorEventStatus, assertions,
+ deassertions);
+ }
+#endif
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, connection, path, sensorMap))
+ {
+ lg2::error("ipmiSenGetSensorEventStatus: Sensor Mapping Error, "
+ "sensor path: {SENSOR_PATH}",
+ "SENSOR_PATH", path);
+ return ipmi::responseResponseError();
+ }
+
+ uint8_t sensorEventStatus =
+ static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
+ std::bitset<16> assertions = 0;
+ std::bitset<16> deassertions = 0;
+
+ // handle VR typed sensor
+ auto vrInterface = sensorMap.find(sensor::vrInterface);
+ if (vrInterface != sensorMap.end())
+ {
+ if (!sensor::getVrEventStatus(ctx, connection, path,
+ vrInterface->second, assertions))
+ {
+ return ipmi::responseResponseError();
+ }
+
+ // both Event Message and Sensor Scanning are disable for VR.
+ sensorEventStatus = 0;
+ return ipmi::responseSuccess(sensorEventStatus, assertions,
+ deassertions);
+ }
+
+ auto warningInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
+ auto criticalInterface =
+ sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
+
+ std::optional<bool> criticalDeassertHigh =
+ thresholdDeassertMap[path]["CriticalAlarmHigh"];
+ std::optional<bool> criticalDeassertLow =
+ thresholdDeassertMap[path]["CriticalAlarmLow"];
+ std::optional<bool> warningDeassertHigh =
+ thresholdDeassertMap[path]["WarningAlarmHigh"];
+ std::optional<bool> warningDeassertLow =
+ thresholdDeassertMap[path]["WarningAlarmLow"];
+
+ if (criticalDeassertHigh && !*criticalDeassertHigh)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperCriticalGoingHigh));
+ }
+ if (criticalDeassertLow && !*criticalDeassertLow)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperCriticalGoingLow));
+ }
+ if (warningDeassertHigh && !*warningDeassertHigh)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::upperNonCriticalGoingHigh));
+ }
+ if (warningDeassertLow && !*warningDeassertLow)
+ {
+ deassertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::lowerNonCriticalGoingHigh));
+ }
+ if ((warningInterface != sensorMap.end()) ||
+ (criticalInterface != sensorMap.end()))
+ {
+ sensorEventStatus = static_cast<size_t>(
+ IPMISensorEventEnableByte2::eventMessagesEnable);
+ if (warningInterface != sensorMap.end())
+ {
+ auto& warningMap = warningInterface->second;
+
+ auto warningHigh = warningMap.find("WarningAlarmHigh");
+ auto warningLow = warningMap.find("WarningAlarmLow");
+ auto warningHighAlarm = false;
+ auto warningLowAlarm = false;
+
+ if (warningHigh != warningMap.end())
+ {
+ warningHighAlarm = std::get<bool>(warningHigh->second);
+ }
+ if (warningLow != warningMap.end())
+ {
+ warningLowAlarm = std::get<bool>(warningLow->second);
+ }
+ if (warningHighAlarm)
+ {
+ assertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::
+ upperNonCriticalGoingHigh));
+ }
+ if (warningLowAlarm)
+ {
+ assertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::
+ lowerNonCriticalGoingLow));
+ }
+ }
+ if (criticalInterface != sensorMap.end())
+ {
+ auto& criticalMap = criticalInterface->second;
+
+ auto criticalHigh = criticalMap.find("CriticalAlarmHigh");
+ auto criticalLow = criticalMap.find("CriticalAlarmLow");
+ auto criticalHighAlarm = false;
+ auto criticalLowAlarm = false;
+
+ if (criticalHigh != criticalMap.end())
+ {
+ criticalHighAlarm = std::get<bool>(criticalHigh->second);
+ }
+ if (criticalLow != criticalMap.end())
+ {
+ criticalLowAlarm = std::get<bool>(criticalLow->second);
+ }
+ if (criticalHighAlarm)
+ {
+ assertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::
+ upperCriticalGoingHigh));
+ }
+ if (criticalLowAlarm)
+ {
+ assertions.set(static_cast<size_t>(
+ IPMIGetSensorEventEnableThresholds::lowerCriticalGoingLow));
+ }
+ }
+ }
+
+ return ipmi::responseSuccess(sensorEventStatus, assertions, deassertions);
+}
+
+// Construct a type 1 SDR for threshold sensor.
+void constructSensorSdrHeaderKey(uint16_t sensorNum, uint16_t recordID,
+ get_sdr::SensorDataFullRecord& record)
+{
+ get_sdr::header::set_record_id(
+ recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
+
+ uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+ uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+ record.header.sdr_version = ipmiSdrVersion;
+ record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
+ record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
+ sizeof(get_sdr::SensorDataRecordHeader);
+ record.key.owner_id = bmcI2CAddr;
+ record.key.owner_lun = lun;
+ record.key.sensor_number = sensornumber;
+}
+bool constructSensorSdr(
+ ipmi::Context::ptr ctx,
+ const std::unordered_set<std::string>& ipmiDecoratorPaths,
+ uint16_t sensorNum, uint16_t recordID, const std::string& service,
+ const std::string& path, get_sdr::SensorDataFullRecord& record)
+{
+ constructSensorSdrHeaderKey(sensorNum, recordID, record);
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, service, path, sensorMap, sensorMapSdrUpdatePeriod))
+ {
+ lg2::error("Failed to update sensor map for threshold sensor, "
+ "service: {SERVICE}, path: {PATH}",
+ "SERVICE", service, "PATH", path);
+ return false;
+ }
+
+ record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis
+ record.body.sensor_type = getSensorTypeFromPath(path);
+ std::string type = getSensorTypeStringFromPath(path);
+ auto typeCstr = type.c_str();
+ auto findUnits = sensorUnits.find(typeCstr);
+ if (findUnits != sensorUnits.end())
+ {
+ record.body.sensor_units_2_base =
+ static_cast<uint8_t>(findUnits->second);
+ } // else default 0x0 unspecified
+
+ record.body.event_reading_type = getSensorEventTypeFromPath(path);
+
+ auto sensorObject = sensorMap.find(sensor::sensorInterface);
+ if (sensorObject == sensorMap.end())
+ {
+ lg2::error("constructSensorSdr: sensorObject error");
+ return false;
+ }
+
+ uint8_t entityId = 0;
+ uint8_t entityInstance = 0x01;
+
+ // follow the association chain to get the parent board's entityid and
+ // entityInstance
+ updateIpmiFromAssociation(path, ipmiDecoratorPaths, sensorMap, entityId,
+ entityInstance);
+
+ record.body.entity_id = entityId;
+ record.body.entity_instance = entityInstance;
+
+ double max = 0;
+ double min = 0;
+ getSensorMaxMin(sensorMap, max, min);
+
+ int16_t mValue = 0;
+ int8_t rExp = 0;
+ int16_t bValue = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
+ {
+ lg2::error("constructSensorSdr: getSensorAttributes error");
+ return false;
+ }
+
+ // The record.body is a struct SensorDataFullRecordBody
+ // from sensorhandler.hpp in phosphor-ipmi-host.
+ // The meaning of these bits appears to come from
+ // table 43.1 of the IPMI spec.
+ // The above 5 sensor attributes are stuffed in as follows:
+ // Byte 21 = AA000000 = analog interpretation, 10 signed, 00 unsigned
+ // Byte 22-24 are for other purposes
+ // Byte 25 = MMMMMMMM = LSB of M
+ // Byte 26 = MMTTTTTT = MSB of M (signed), and Tolerance
+ // Byte 27 = BBBBBBBB = LSB of B
+ // Byte 28 = BBAAAAAA = MSB of B (signed), and LSB of Accuracy
+ // Byte 29 = AAAAEE00 = MSB of Accuracy, exponent of Accuracy
+ // Byte 30 = RRRRBBBB = rExp (signed), bExp (signed)
+
+ // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
+ record.body.m_lsb = mValue & 0xFF;
+
+ uint8_t mBitSign = (mValue < 0) ? 1 : 0;
+ uint8_t mBitNine = (mValue & 0x0100) >> 8;
+
+ // move the smallest bit of the MSB into place (bit 9)
+ // the MSbs are bits 7:8 in m_msb_and_tolerance
+ record.body.m_msb_and_tolerance = (mBitSign << 7) | (mBitNine << 6);
+
+ record.body.b_lsb = bValue & 0xFF;
+
+ uint8_t bBitSign = (bValue < 0) ? 1 : 0;
+ uint8_t bBitNine = (bValue & 0x0100) >> 8;
+
+ // move the smallest bit of the MSB into place (bit 9)
+ // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
+ record.body.b_msb_and_accuracy_lsb = (bBitSign << 7) | (bBitNine << 6);
+
+ uint8_t rExpSign = (rExp < 0) ? 1 : 0;
+ uint8_t rExpBits = rExp & 0x07;
+
+ uint8_t bExpSign = (bExp < 0) ? 1 : 0;
+ uint8_t bExpBits = bExp & 0x07;
+
+ // move rExp and bExp into place
+ record.body.r_b_exponents =
+ (rExpSign << 7) | (rExpBits << 4) | (bExpSign << 3) | bExpBits;
+
+ // Set the analog reading byte interpretation accordingly
+ record.body.sensor_units_1 = (bSigned ? 1 : 0) << 7;
+
+ // TODO(): Perhaps care about Tolerance, Accuracy, and so on
+ // These seem redundant, but derivable from the above 5 attributes
+ // Original comment said "todo fill out rest of units"
+
+ // populate sensor name from path
+ auto name = sensor::parseSdrIdFromPath(path);
+ get_sdr::body::set_id_strlen(name.size(), &record.body);
+ get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+ std::memcpy(record.body.id_string, name.c_str(),
+ std::min(name.length() + 1, sizeof(record.body.id_string)));
+
+ // Remember the sensor name, as determined for this sensor number
+ details::sdrStatsTable.updateName(sensorNum, name);
+
+ bool sensorSettable = false;
+ auto mutability =
+ sensorMap.find("xyz.openbmc_project.Sensor.ValueMutability");
+ if (mutability != sensorMap.end())
+ {
+ sensorSettable =
+ mappedVariant<bool>(mutability->second, "Mutable", false);
+ }
+ get_sdr::body::init_settable_state(sensorSettable, &record.body);
+
+ // Grant write permission to sensors deemed externally settable
+ details::sdrWriteTable.setWritePermission(sensorNum, sensorSettable);
+
+ IPMIThresholds thresholdData;
+ try
+ {
+ thresholdData = getIPMIThresholds(sensorMap);
+ }
+ catch (const std::exception&)
+ {
+ lg2::error("constructSensorSdr: getIPMIThresholds error");
+ return false;
+ }
+
+ if (thresholdData.criticalHigh)
+ {
+ record.body.upper_critical_threshold = *thresholdData.criticalHigh;
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::criticalThreshold);
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
+ }
+ if (thresholdData.warningHigh)
+ {
+ record.body.upper_noncritical_threshold = *thresholdData.warningHigh;
+ record.body.supported_deassertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::nonCriticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical);
+ }
+ if (thresholdData.criticalLow)
+ {
+ record.body.lower_critical_threshold = *thresholdData.criticalLow;
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::criticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
+ }
+ if (thresholdData.warningLow)
+ {
+ record.body.lower_noncritical_threshold = *thresholdData.warningLow;
+ record.body.supported_assertions[1] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::nonCriticalThreshold);
+ record.body.supported_deassertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+ record.body.supported_assertions[0] |= static_cast<uint8_t>(
+ IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
+ record.body.discrete_reading_setting_mask[0] |=
+ static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical);
+ }
+
+ // everything that is readable is setable
+ record.body.discrete_reading_setting_mask[1] =
+ record.body.discrete_reading_setting_mask[0];
+ return true;
+}
+
+#ifdef FEATURE_HYBRID_SENSORS
+// Construct a type 1 SDR for discrete Sensor typed sensor.
+void constructStaticSensorSdr(ipmi::Context::ptr, uint16_t sensorNum,
+ uint16_t recordID,
+ ipmi::sensor::IdInfoMap::const_iterator sensor,
+ get_sdr::SensorDataFullRecord& record)
+{
+ constructSensorSdrHeaderKey(sensorNum, recordID, record);
+
+ record.body.entity_id = sensor->second.entityType;
+ record.body.sensor_type = sensor->second.sensorType;
+ record.body.event_reading_type = sensor->second.sensorReadingType;
+ record.body.entity_instance = sensor->second.instance;
+ if (ipmi::sensor::Mutability::Write ==
+ (sensor->second.mutability & ipmi::sensor::Mutability::Write))
+ {
+ get_sdr::body::init_settable_state(true, &(record.body));
+ }
+
+ auto id_string = sensor->second.sensorName;
+
+ if (id_string.empty())
+ {
+ id_string = sensor->second.sensorNameFunc(sensor->second);
+ }
+
+ if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH)
+ {
+ get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH,
+ &(record.body));
+ }
+ else
+ {
+ get_sdr::body::set_id_strlen(id_string.length(), &(record.body));
+ }
+ get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+ std::strncpy(record.body.id_string, id_string.c_str(),
+ get_sdr::body::get_id_strlen(&(record.body)));
+}
+#endif
+
+// Construct type 3 SDR header and key (for VR and other discrete sensors)
+void constructEventSdrHeaderKey(uint16_t sensorNum, uint16_t recordID,
+ get_sdr::SensorDataEventRecord& record)
+{
+ uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+ uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+ get_sdr::header::set_record_id(
+ recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record));
+
+ record.header.sdr_version = ipmiSdrVersion;
+ record.header.record_type = get_sdr::SENSOR_DATA_EVENT_RECORD;
+ record.header.record_length = sizeof(get_sdr::SensorDataEventRecord) -
+ sizeof(get_sdr::SensorDataRecordHeader);
+ record.key.owner_id = bmcI2CAddr;
+ record.key.owner_lun = lun;
+ record.key.sensor_number = sensornumber;
+
+ record.body.entity_id = 0x00;
+ record.body.entity_instance = 0x01;
+}
+
+// Construct a type 3 SDR for VR typed sensor(daemon).
+bool constructVrSdr(ipmi::Context::ptr ctx,
+ const std::unordered_set<std::string>& ipmiDecoratorPaths,
+ uint16_t sensorNum, uint16_t recordID,
+ const std::string& service, const std::string& path,
+ get_sdr::SensorDataEventRecord& record)
+{
+ constructEventSdrHeaderKey(sensorNum, recordID, record);
+
+ DbusInterfaceMap sensorMap;
+ if (!getSensorMap(ctx, service, path, sensorMap, sensorMapSdrUpdatePeriod))
+ {
+ lg2::error("Failed to update sensor map for VR sensor, "
+ "service: {SERVICE}, path: {PATH}",
+ "SERVICE", service, "PATH", path);
+ return false;
+ }
+ // follow the association chain to get the parent board's entityid and
+ // entityInstance
+ updateIpmiFromAssociation(path, ipmiDecoratorPaths, sensorMap,
+ record.body.entity_id,
+ record.body.entity_instance);
+
+ // Sensor type is hardcoded as a module/board type instead of parsing from
+ // sensor path. This is because VR control is allocated in an independent
+ // path(/xyz/openbmc_project/vr/profile/...) which is not categorized by
+ // types.
+ static constexpr const uint8_t module_board_type = 0x15;
+ record.body.sensor_type = module_board_type;
+ record.body.event_reading_type = 0x00;
+
+ record.body.sensor_record_sharing_1 = 0x00;
+ record.body.sensor_record_sharing_2 = 0x00;
+
+ // populate sensor name from path
+ auto name = sensor::parseSdrIdFromPath(path);
+ int nameSize = std::min(name.size(), sizeof(record.body.id_string));
+ get_sdr::body::set_id_strlen(nameSize, &record.body);
+ get_sdr::body::set_id_type(3, &record.body); // "8-bit ASCII + Latin 1"
+ std::memset(record.body.id_string, 0x00, sizeof(record.body.id_string));
+ std::memcpy(record.body.id_string, name.c_str(), nameSize);
+
+ // Remember the sensor name, as determined for this sensor number
+ details::sdrStatsTable.updateName(sensorNum, name);
+
+ return true;
+}
+
+uint16_t getNumberOfSensors()
+{
+ return std::min(getSensorTree().size(), maxIPMISensors);
+}
+
+static int getSensorDataRecord(
+ ipmi::Context::ptr ctx,
+ const std::unordered_set<std::string>& ipmiDecoratorPaths,
+ std::vector<uint8_t>& recordData, uint16_t recordID,
+ uint8_t readBytes = std::numeric_limits<uint8_t>::max())
+{
+ recordData.clear();
+ size_t lastRecord = ipmi::getNumberOfSensors() +
+ ipmi::sensor::getOtherSensorsCount(ctx) - 1;
+ uint16_t nextRecord(recordID + 1);
+
+ if (recordID == lastRecordIndex)
+ {
+ recordID = lastRecord;
+ }
+ if (recordID == lastRecord)
+ {
+ nextRecord = lastRecordIndex;
+ }
+ if (recordID > lastRecord)
+ {
+ lg2::error("getSensorDataRecord: recordID > lastRecord error");
+ return GENERAL_ERROR;
+ }
+ if (recordID >= ipmi::getNumberOfSensors())
+ {
+ if (auto err = ipmi::sensor::getOtherSensorsDataRecord(ctx, recordID,
+ recordData);
+ err < 0)
+ {
+ return lastRecordIndex;
+ }
+ return nextRecord;
+ }
+
+ // Perform a incremental scan of the SDR Record ID's and translate the
+ // first 765 SDR records (i.e. maxIPMISensors) into IPMI Sensor
+ // Numbers. The IPMI sensor numbers are not linear, and have a reserved
+ // gap at 0xff. This code creates 254 sensors per LUN, excepting LUN 2
+ // which has special meaning.
+ std::string connection;
+ std::string path;
+ std::vector<std::string> interfaces;
+ uint16_t sensNumFromRecID{recordID};
+ if ((recordID > lun0MaxSensorNum) && (recordID < lun1MaxSensorNum))
+ {
+ // LUN 0 has one reserved sensor number. Compensate here by adding one
+ // to the record ID
+ sensNumFromRecID = recordID + 1;
+ ctx->lun = lun1;
+ }
+ else if ((recordID >= lun1MaxSensorNum) && (recordID < maxIPMISensors))
+ {
+ // LUN 0, 1 have a reserved sensor number. Compensate here by adding 2
+ // to the record ID. Skip all 256 sensors in LUN 2, as it has special
+ // rules governing its use.
+ sensNumFromRecID = recordID + (maxSensorsPerLUN + 1) + 2;
+ ctx->lun = lun3;
+ }
+
+ auto status =
+ getSensorConnection(ctx, static_cast<uint8_t>(sensNumFromRecID),
+ connection, path, &interfaces);
+ if (status)
+ {
+ lg2::error("getSensorDataRecord: getSensorConnection error");
+ return GENERAL_ERROR;
+ }
+ uint16_t sensorNum = getSensorNumberFromPath(path);
+ // Return an error on LUN 2 assingments, and any sensor number beyond the
+ // range of LUN 3
+ if (((sensorNum > lun1MaxSensorNum) && (sensorNum <= maxIPMISensors)) ||
+ (sensorNum > lun3MaxSensorNum))
+ {
+ lg2::error("getSensorDataRecord: invalidSensorNumber");
+ return GENERAL_ERROR;
+ }
+ uint8_t sensornumber = static_cast<uint8_t>(sensorNum);
+ uint8_t lun = static_cast<uint8_t>(sensorNum >> 8);
+
+ if ((sensornumber != static_cast<uint8_t>(sensNumFromRecID)) &&
+ (lun != ctx->lun))
+ {
+ lg2::error("getSensorDataRecord: sensor record mismatch");
+ return GENERAL_ERROR;
+ }
+
+ // Construct full record (SDR type 1) for the threshold sensors
+ if (std::find(interfaces.begin(), interfaces.end(),
+ sensor::sensorInterface) != interfaces.end())
+ {
+ get_sdr::SensorDataFullRecord record = {};
+
+ // If the request doesn't read SDR body, construct only header and key
+ // part to avoid additional DBus transaction.
+ if (readBytes <= sizeof(record.header) + sizeof(record.key))
+ {
+ constructSensorSdrHeaderKey(sensorNum, recordID, record);
+ }
+ else if (!constructSensorSdr(ctx, ipmiDecoratorPaths, sensorNum,
+ recordID, connection, path, record))
+ {
+ return GENERAL_ERROR;
+ }
+
+ recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+ reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+
+ return nextRecord;
+ }
+
+#ifdef FEATURE_HYBRID_SENSORS
+ if (auto sensor = findStaticSensor(path);
+ sensor != ipmi::sensor::sensors.end() &&
+ getSensorEventTypeFromPath(path) !=
+ static_cast<uint8_t>(SensorEventTypeCodes::threshold))
+ {
+ get_sdr::SensorDataFullRecord record = {};
+
+ // If the request doesn't read SDR body, construct only header and key
+ // part to avoid additional DBus transaction.
+ if (readBytes <= sizeof(record.header) + sizeof(record.key))
+ {
+ constructSensorSdrHeaderKey(sensorNum, recordID, record);
+ }
+ else
+ {
+ constructStaticSensorSdr(ctx, sensorNum, recordID, sensor, record);
+ }
+
+ recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+ reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+
+ return nextRecord;
+ }
+#endif
+
+ // Contruct SDR type 3 record for VR sensor (daemon)
+ if (std::find(interfaces.begin(), interfaces.end(), sensor::vrInterface) !=
+ interfaces.end())
+ {
+ get_sdr::SensorDataEventRecord record = {};
+
+ // If the request doesn't read SDR body, construct only header and key
+ // part to avoid additional DBus transaction.
+ if (readBytes <= sizeof(record.header) + sizeof(record.key))
+ {
+ constructEventSdrHeaderKey(sensorNum, recordID, record);
+ }
+ else if (!constructVrSdr(ctx, ipmiDecoratorPaths, sensorNum, recordID,
+ connection, path, record))
+ {
+ return GENERAL_ERROR;
+ }
+ recordData.insert(recordData.end(), reinterpret_cast<uint8_t*>(&record),
+ reinterpret_cast<uint8_t*>(&record) + sizeof(record));
+ }
+
+ return nextRecord;
+}
+
+/** @brief implements the get SDR Info command
+ * @param operation : 0 or not supplied returns sensor count
+ * 1 return SDR count
+ *
+ * @returns IPMI completion code plus response data
+ * - sdrCount - sensor/SDR count
+ * - lunsAndDynamicPopulation - static/Dynamic sensor population flag
+ */
+static ipmi::RspType<uint8_t, // respcount
+ uint8_t, // dynamic population flags
+ uint32_t // last time a sensor was added
+ >
+ ipmiSensorGetDeviceSdrInfo(ipmi::Context::ptr ctx,
+ std::optional<uint8_t> operation)
+{
+ auto& sensorTree{getSensorTree()};
+ uint8_t sdrCount{};
+ // Sensors are dynamically allocated
+ uint8_t lunsAndDynamicPopulation{0x80};
+ constexpr uint8_t getSdrCount{1};
+ constexpr uint8_t getSensorCount{0};
+
+ if (!getSensorSubtree(sensorTree) || sensorTree.empty())
+ {
+ return ipmi::responseResponseError();
+ }
+ uint16_t numSensors{ipmi::getNumberOfSensors()};
+ if (operation.value_or(0) == getSdrCount)
+ {
+ sdrCount = numSensors + ipmi::sensor::getOtherSensorsCount(ctx) - 1;
+ }
+ else if (operation.value_or(0) == getSensorCount)
+ {
+ // Return the number of sensors attached to the LUN
+ if ((ctx->lun == lun0) && (numSensors > 0))
+ {
+ sdrCount =
+ (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors;
+ }
+ else if ((ctx->lun == lun1) && (numSensors > maxSensorsPerLUN))
+ {
+ sdrCount = (numSensors > (2 * maxSensorsPerLUN))
+ ? maxSensorsPerLUN
+ : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN;
+ }
+ else if (ctx->lun == lun3)
+ {
+ if (numSensors <= maxIPMISensors)
+ {
+ sdrCount = (numSensors - (2 * maxSensorsPerLUN)) &
+ maxSensorsPerLUN;
+ }
+ else
+ {
+ throw std::out_of_range(
+ "Maximum number of IPMI sensors exceeded.");
+ }
+ }
+ }
+ else
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Flag which LUNs have sensors associated
+ if (numSensors > 0)
+ {
+ lunsAndDynamicPopulation |= 1;
+ }
+ if (numSensors > maxSensorsPerLUN)
+ {
+ lunsAndDynamicPopulation |= 2;
+ }
+ if (numSensors >= (maxSensorsPerLUN * 2))
+ {
+ lunsAndDynamicPopulation |= 8;
+ }
+ if (numSensors > maxIPMISensors)
+ {
+ throw std::out_of_range("Maximum number of IPMI sensors exceeded.");
+ }
+
+ return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation,
+ sdrLastAdd);
+}
+
+/* end sensor commands */
+
+/* storage commands */
+
+ipmi::RspType<uint8_t, // sdr version
+ uint16_t, // record count
+ uint16_t, // free space
+ uint32_t, // most recent addition
+ uint32_t, // most recent erase
+ uint8_t // operationSupport
+ >
+ ipmiStorageGetSDRRepositoryInfo(ipmi::Context::ptr ctx)
+{
+ constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
+ uint16_t recordCount =
+ ipmi::getNumberOfSensors() + ipmi::sensor::getOtherSensorsCount(ctx);
+
+ uint8_t operationSupport = static_cast<uint8_t>(
+ SdrRepositoryInfoOps::overflow); // write not supported
+
+ operationSupport |=
+ static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported);
+ operationSupport |= static_cast<uint8_t>(
+ SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported);
+ return ipmi::responseSuccess(ipmiSdrVersion, recordCount,
+ unspecifiedFreeSpace, sdrLastAdd,
+ sdrLastRemove, operationSupport);
+}
+
+/** @brief implements the get SDR allocation info command
+ *
+ * @returns IPMI completion code plus response data
+ * - allocUnits - Number of possible allocation units
+ * - allocUnitSize - Allocation unit size in bytes.
+ * - allocUnitFree - Number of free allocation units
+ * - allocUnitLargestFree - Largest free block in allocation units
+ * - maxRecordSize - Maximum record size in allocation units.
+ */
+ipmi::RspType<uint16_t, // allocUnits
+ uint16_t, // allocUnitSize
+ uint16_t, // allocUnitFree
+ uint16_t, // allocUnitLargestFree
+ uint8_t // maxRecordSize
+ >
+ ipmiStorageGetSDRAllocationInfo()
+{
+ // 0000h unspecified number of alloc units
+ constexpr uint16_t allocUnits = 0;
+
+ constexpr uint16_t allocUnitFree = 0;
+ constexpr uint16_t allocUnitLargestFree = 0;
+ // only allow one block at a time
+ constexpr uint8_t maxRecordSize = 1;
+
+ return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree,
+ allocUnitLargestFree, maxRecordSize);
+}
+
+/** @brief implements the reserve SDR command
+ * @returns IPMI completion code plus response data
+ * - sdrReservationID
+ */
+ipmi::RspType<uint16_t> ipmiStorageReserveSDR()
+{
+ sdrReservationID++;
+ if (sdrReservationID == 0)
+ {
+ sdrReservationID++;
+ }
+
+ return ipmi::responseSuccess(sdrReservationID);
+}
+
+ipmi::RspType<uint16_t, // next record ID
+ std::vector<uint8_t> // payload
+ >
+ ipmiStorageGetSDR(ipmi::Context::ptr ctx, uint16_t reservationID,
+ uint16_t recordID, uint8_t offset, uint8_t bytesToRead)
+{
+ // reservation required for partial reads with non zero offset into
+ // record
+ if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset)
+ {
+ lg2::error("ipmiStorageGetSDR: responseInvalidReservationId");
+ return ipmi::responseInvalidReservationId();
+ }
+
+ auto& sensorTree = getSensorTree();
+ if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+ {
+ lg2::error("ipmiStorageGetSDR: getSensorSubtree error");
+ return ipmi::responseResponseError();
+ }
+
+ auto& ipmiDecoratorPaths = getIpmiDecoratorPaths(ctx);
+
+ std::vector<uint8_t> record;
+ int nextRecordId = getSensorDataRecord(
+ ctx, ipmiDecoratorPaths.value_or(std::unordered_set<std::string>()),
+ record, recordID, offset + bytesToRead);
+
+ if (nextRecordId < 0)
+ {
+ lg2::error("ipmiStorageGetSDR: fail to get SDR");
+ return ipmi::responseInvalidFieldRequest();
+ }
+ get_sdr::SensorDataRecordHeader* hdr =
+ reinterpret_cast<get_sdr::SensorDataRecordHeader*>(record.data());
+ if (!hdr)
+ {
+ lg2::error("ipmiStorageGetSDR: record header is null");
+ return ipmi::responseSuccess(nextRecordId, record);
+ }
+
+ size_t sdrLength =
+ sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length;
+ if (offset >= sdrLength)
+ {
+ lg2::error("ipmiStorageGetSDR: offset is outside the record");
+ return ipmi::responseParmOutOfRange();
+ }
+ if (sdrLength < (offset + bytesToRead))
+ {
+ bytesToRead = sdrLength - offset;
+ }
+
+ uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset;
+ if (!respStart)
+ {
+ lg2::error("ipmiStorageGetSDR: record is null");
+ return ipmi::responseSuccess(nextRecordId, record);
+ }
+
+ std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
+
+ return ipmi::responseSuccess(nextRecordId, recordData);
+}
+namespace dcmi
+{
+
+std::tuple<uint8_t, // Total of instance sensors
+ std::vector<sensorInfo> // The list of sensors
+ >
+ getSensorsByEntityId(ipmi::Context::ptr ctx, uint8_t entityId,
+ uint8_t entityInstance, uint8_t instanceStart)
+{
+ std::vector<sensorInfo> sensorList;
+ uint8_t totalInstSensor = 0;
+ auto match = ipmi::dcmi::validEntityId.find(entityId);
+
+ if (match == ipmi::dcmi::validEntityId.end())
+ {
+ return std::make_tuple(totalInstSensor, sensorList);
+ }
+
+ auto& sensorTree = getSensorTree();
+ if (!getSensorSubtree(sensorTree) && sensorTree.empty())
+ {
+ return std::make_tuple(totalInstSensor, sensorList);
+ }
+
+ auto& ipmiDecoratorPaths = getIpmiDecoratorPaths(ctx);
+
+ size_t invalidSensorNumberErrCount = 0;
+ for (const auto& sensor : sensorTree)
+ {
+ const std::string& sensorObjPath = sensor.first;
+ const auto& sensorTypeValue = getSensorTypeFromPath(sensorObjPath);
+
+ /*
+ * In the DCMI specification, it only supports the sensor type is 0x01
+ * (temperature type) for both Get Sensor Info and Get Temperature
+ * Readings commands.
+ */
+ if (sensorTypeValue != ipmi::dcmi::temperatureSensorType)
+ {
+ continue;
+ }
+
+ const auto& connection = sensor.second.begin()->first;
+ DbusInterfaceMap sensorMap;
+
+ if (!getSensorMap(ctx, connection, sensorObjPath, sensorMap,
+ sensorMapSdrUpdatePeriod))
+ {
+ lg2::error("Failed to update sensor map for threshold sensor, "
+ "service: {SERVICE}, path: {PATH}",
+ "SERVICE", connection, "PATH", sensorObjPath);
+ continue;
+ }
+
+ uint8_t entityIdValue = 0;
+ uint8_t entityInstanceValue = 0;
+
+ /*
+ * Get the Entity ID, Entity Instance information which are configured
+ * in the Entity-Manger.
+ */
+ updateIpmiFromAssociation(
+ sensorObjPath,
+ ipmiDecoratorPaths.value_or(std::unordered_set<std::string>()),
+ sensorMap, entityIdValue, entityInstanceValue);
+
+ if (entityIdValue == match->first || entityIdValue == match->second)
+ {
+ totalInstSensor++;
+
+ /*
+ * When Entity Instance parameter is not 0, we only get the first
+ * sensor whose Entity Instance number is equal input Entity
+ * Instance parameter.
+ */
+ if (entityInstance)
+ {
+ if (!sensorList.empty())
+ {
+ continue;
+ }
+
+ if (entityInstanceValue == entityInstance)
+ {
+ auto recordId = getSensorNumberFromPath(sensorObjPath);
+ if (recordId == invalidSensorNumber)
+ {
+ ++invalidSensorNumberErrCount;
+ continue;
+ }
+ sensorList.emplace_back(sensorObjPath, sensorTypeValue,
+ recordId, entityIdValue,
+ entityInstanceValue);
+ }
+ }
+ else if (entityInstanceValue >= instanceStart)
+ {
+ auto recordId = getSensorNumberFromPath(sensorObjPath);
+ if (recordId == invalidSensorNumber)
+ {
+ ++invalidSensorNumberErrCount;
+ continue;
+ }
+ sensorList.emplace_back(sensorObjPath, sensorTypeValue,
+ recordId, entityIdValue,
+ entityInstanceValue);
+ }
+ }
+ }
+ if (invalidSensorNumberErrCount != 0)
+ {
+ lg2::error("getSensorNumberFromPath returned invalidSensorNumber "
+ "{ERR_COUNT} times",
+ "ERR_COUNT", invalidSensorNumberErrCount);
+ }
+
+ auto cmpFunc = [](sensorInfo first, sensorInfo second) {
+ return first.entityInstance <= second.entityInstance;
+ };
+
+ sort(sensorList.begin(), sensorList.end(), cmpFunc);
+
+ return std::make_tuple(totalInstSensor, sensorList);
+}
+
+std::tuple<bool, // Reading result
+ uint7_t, // Temp value
+ bool> // Sign bit
+ readTemp(ipmi::Context::ptr ctx, const std::string& objectPath)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, sensor::sensorInterface, objectPath, service);
+ if (ec.value())
+ {
+ return std::make_tuple(false, 0, false);
+ }
+
+ ipmi::PropertyMap properties{};
+ ec = ipmi::getAllDbusProperties(ctx, service, objectPath,
+ sensor::sensorInterface, properties);
+ if (ec.value())
+ {
+ return std::make_tuple(false, 0, false);
+ }
+
+ auto scaleIt = properties.find("Scale");
+ double scaleVal = 0.0;
+ if (scaleIt != properties.end())
+ {
+ scaleVal = std::visit(ipmi::VariantToDoubleVisitor(), scaleIt->second);
+ }
+
+ auto tempValIt = properties.find("Value");
+ double tempVal = 0.0;
+ if (tempValIt == properties.end())
+ {
+ return std::make_tuple(false, 0, false);
+ }
+
+ const double maxTemp = 127;
+ double absTempVal = 0.0;
+ bool signBit = false;
+
+ tempVal = std::visit(ipmi::VariantToDoubleVisitor(), tempValIt->second);
+ tempVal = std::pow(10, scaleVal) * tempVal;
+ absTempVal = std::abs(tempVal);
+ absTempVal = std::min(absTempVal, maxTemp);
+ signBit = (tempVal < 0) ? true : false;
+
+ return std::make_tuple(true, static_cast<uint7_t>(absTempVal), signBit);
+}
+
+ipmi::RspType<uint8_t, // No of instances for requested id
+ uint8_t, // No of record ids in the response
+ std::vector<uint16_t> // SDR Record ID corresponding to the Entity
+ // IDs
+ >
+ getSensorInfo(ipmi::Context::ptr ctx, uint8_t sensorType, uint8_t entityId,
+ uint8_t entityInstance, uint8_t instanceStart)
+{
+ auto match = ipmi::dcmi::validEntityId.find(entityId);
+ if (match == ipmi::dcmi::validEntityId.end())
+ {
+ lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (sensorType != ipmi::dcmi::temperatureSensorType)
+ {
+ lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+ sensorType);
+
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::vector<uint16_t> sensorRec{};
+ const auto& [totalSensorInst, sensorList] =
+ getSensorsByEntityId(ctx, entityId, entityInstance, instanceStart);
+
+ if (sensorList.empty())
+ {
+ return ipmi::responseSuccess(totalSensorInst, 0, sensorRec);
+ }
+
+ /*
+ * As DCMI specification, the maximum number of Record Ids of response data
+ * is 1 if Entity Instance paramter is not 0. Else the maximum number of
+ * Record Ids of response data is 8. Therefore, not all of sensors are shown
+ * in response data.
+ */
+ uint8_t numOfRec = (entityInstance != 0) ? 1 : ipmi::dcmi::maxRecords;
+
+ for (const auto& sensor : sensorList)
+ {
+ sensorRec.emplace_back(sensor.recordId);
+ if (sensorRec.size() >= numOfRec)
+ {
+ break;
+ }
+ }
+
+ return ipmi::responseSuccess(
+ totalSensorInst, static_cast<uint8_t>(sensorRec.size()), sensorRec);
+}
+
+ipmi::RspType<uint8_t, // No of instances for requested id
+ uint8_t, // No of record ids in the response
+ std::vector< // Temperature Data
+ std::tuple<uint7_t, // Temperature value
+ bool, // Sign bit
+ uint8_t // Entity Instance of sensor
+ >>>
+ getTempReadings(ipmi::Context::ptr ctx, uint8_t sensorType,
+ uint8_t entityId, uint8_t entityInstance,
+ uint8_t instanceStart)
+{
+ auto match = ipmi::dcmi::validEntityId.find(entityId);
+ if (match == ipmi::dcmi::validEntityId.end())
+ {
+ lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (sensorType != ipmi::dcmi::temperatureSensorType)
+ {
+ lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+ sensorType);
+
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::vector<std::tuple<uint7_t, bool, uint8_t>> tempReadingVal{};
+ const auto& [totalSensorInst, sensorList] =
+ getSensorsByEntityId(ctx, entityId, entityInstance, instanceStart);
+
+ if (sensorList.empty())
+ {
+ return ipmi::responseSuccess(totalSensorInst, 0, tempReadingVal);
+ }
+
+ /*
+ * As DCMI specification, the maximum number of Record Ids of response data
+ * is 1 if Entity Instance paramter is not 0. Else the maximum number of
+ * Record Ids of response data is 8. Therefore, not all of sensors are shown
+ * in response data.
+ */
+ uint8_t numOfRec = (entityInstance != 0) ? 1 : ipmi::dcmi::maxRecords;
+
+ for (const auto& sensor : sensorList)
+ {
+ const auto& [readResult, tempVal, signBit] =
+ readTemp(ctx, sensor.objectPath);
+
+ if (readResult)
+ {
+ tempReadingVal.emplace_back(
+ std::make_tuple(tempVal, signBit, sensor.entityInstance));
+
+ if (tempReadingVal.size() >= numOfRec)
+ {
+ break;
+ }
+ }
+ }
+
+ return ipmi::responseSuccess(totalSensorInst,
+ static_cast<uint8_t>(tempReadingVal.size()),
+ tempReadingVal);
+}
+
+} // namespace dcmi
+
+/* end storage commands */
+
+void registerSensorFunctions()
+{
+ // <Platform Event>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdPlatformEvent,
+ ipmi::Privilege::Operator, ipmiSenPlatformEvent);
+
+ // <Set Sensor Reading and Event Status>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
+ ipmi::Privilege::Operator, ipmiSetSensorReading);
+
+ // <Get Sensor Reading>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorReading,
+ ipmi::Privilege::User, ipmiSenGetSensorReading);
+
+ // <Get Sensor Threshold>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorThreshold,
+ ipmi::Privilege::User, ipmiSenGetSensorThresholds);
+
+ // <Set Sensor Threshold>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdSetSensorThreshold,
+ ipmi::Privilege::Operator,
+ ipmiSenSetSensorThresholds);
+
+ // <Get Sensor Event Enable>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorEventEnable,
+ ipmi::Privilege::User, ipmiSenGetSensorEventEnable);
+
+ // <Get Sensor Event Status>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorEventStatus,
+ ipmi::Privilege::User, ipmiSenGetSensorEventStatus);
+
+ // register all storage commands for both Sensor and Storage command
+ // versions
+
+ // <Get SDR Repository Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdrRepositoryInfo,
+ ipmi::Privilege::User,
+ ipmiStorageGetSDRRepositoryInfo);
+
+ // <Get Device SDR Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetDeviceSdrInfo,
+ ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
+
+ // <Get SDR Allocation Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdrRepositoryAllocInfo,
+ ipmi::Privilege::User,
+ ipmiStorageGetSDRAllocationInfo);
+
+ // <Reserve SDR Repo>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdReserveDeviceSdrRepository,
+ ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReserveSdrRepository,
+ ipmi::Privilege::User, ipmiStorageReserveSDR);
+
+ // <Get Sdr>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetDeviceSdr,
+ ipmi::Privilege::User, ipmiStorageGetSDR);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdr, ipmi::Privilege::User,
+ ipmiStorageGetSDR);
+ // <Get DCMI Sensor Info>
+ ipmi::registerGroupHandler(
+ ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetDcmiSensorInfo, ipmi::Privilege::Operator,
+ ipmi::dcmi::getSensorInfo);
+ // <Get Temperature Readings>
+ ipmi::registerGroupHandler(
+ ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetTemperatureReadings, ipmi::Privilege::User,
+ ipmi::dcmi::getTempReadings);
+}
+} // namespace ipmi
diff --git a/dbus-sdr/sensorutils.cpp b/dbus-sdr/sensorutils.cpp
new file mode 100644
index 0000000..8c6952b
--- /dev/null
+++ b/dbus-sdr/sensorutils.cpp
@@ -0,0 +1,324 @@
+/*
+// Copyright (c) 2017 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "dbus-sdr/sensorutils.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <iostream>
+
+namespace ipmi
+{
+
+// Helper function to avoid repeated complicated expression
+static bool baseInRange(double base)
+{
+ auto min10 = static_cast<double>(minInt10);
+ auto max10 = static_cast<double>(maxInt10);
+
+ return ((base >= min10) && (base <= max10));
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures floating-point "base" is within bounds,
+// and adjusts integer exponent "expShift" accordingly.
+// To minimize data loss when later truncating to integer,
+// the floating-point "base" will be as large as possible,
+// but still within the bounds (minInt10,maxInt10).
+// The bounds of "expShift" are (minInt4,maxInt4).
+// Consider this equation: n = base * (10.0 ** expShift)
+// This function will try to maximize "base",
+// adjusting "expShift" to keep the value "n" unchanged,
+// while keeping base and expShift within bounds.
+// Returns true if successful, modifies values in-place
+static bool scaleFloatExp(double& base, int8_t& expShift)
+{
+ // Comparing with zero should be OK, zero is special in floating-point
+ // If base is exactly zero, no adjustment of the exponent is necessary
+ if (base == 0.0)
+ {
+ return true;
+ }
+
+ // As long as base value is within allowed range, expand precision
+ // This will help to avoid loss when later rounding to integer
+ while (baseInRange(base))
+ {
+ if (expShift <= minInt4)
+ {
+ // Already at the minimum expShift, can not decrement it more
+ break;
+ }
+
+ // Multiply by 10, but shift decimal point to the left, no net change
+ base *= 10.0;
+ --expShift;
+ }
+
+ // As long as base value is *not* within range, shrink precision
+ // This will pull base value closer to zero, thus within range
+ while (!(baseInRange(base)))
+ {
+ if (expShift >= maxInt4)
+ {
+ // Already at the maximum expShift, can not increment it more
+ break;
+ }
+
+ // Divide by 10, but shift decimal point to the right, no net change
+ base /= 10.0;
+ ++expShift;
+ }
+
+ // If the above loop was not able to pull it back within range,
+ // the base value is beyond what expShift can represent, return false.
+ return baseInRange(base);
+}
+
+// Helper function for internal use by getSensorAttributes()
+// Ensures integer "ibase" is no larger than necessary,
+// by normalizing it so that the decimal point shift is in the exponent,
+// whenever possible.
+// This provides more consistent results,
+// as many equivalent solutions are collapsed into one consistent solution.
+// If integer "ibase" is a clean multiple of 10,
+// divide it by 10 (this is lossless), so it is closer to zero.
+// Also modify floating-point "dbase" at the same time,
+// as both integer and floating-point base share the same expShift.
+// Example: (ibase=300, expShift=2) becomes (ibase=3, expShift=4)
+// because the underlying value is the same: 200*(10**2) == 2*(10**4)
+// Always successful, modifies values in-place
+static void normalizeIntExp(int16_t& ibase, int8_t& expShift, double& dbase)
+{
+ for (;;)
+ {
+ // If zero, already normalized, ensure exponent also zero
+ if (ibase == 0)
+ {
+ expShift = 0;
+ break;
+ }
+
+ // If not cleanly divisible by 10, already normalized
+ if ((ibase % 10) != 0)
+ {
+ break;
+ }
+
+ // If exponent already at max, already normalized
+ if (expShift >= maxInt4)
+ {
+ break;
+ }
+
+ // Bring values closer to zero, correspondingly shift exponent,
+ // without changing the underlying number that this all represents,
+ // similar to what is done by scaleFloatExp().
+ // The floating-point base must be kept in sync with the integer base,
+ // as both floating-point and integer share the same exponent.
+ ibase /= 10;
+ dbase /= 10.0;
+ ++expShift;
+ }
+}
+
+// The IPMI equation:
+// y = (Mx + (B * 10^(bExp))) * 10^(rExp)
+// Section 36.3 of this document:
+// https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
+//
+// The goal is to exactly match the math done by the ipmitool command,
+// at the other side of the interface:
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+//
+// To use with Wolfram Alpha, make all variables single letters
+// bExp becomes E, rExp becomes R
+// https://www.wolframalpha.com/input/?i=y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+ int8_t& rExp, int16_t& bValue, int8_t& bExp,
+ bool& bSigned)
+{
+ if (!(std::isfinite(min)))
+ {
+ std::cerr << "getSensorAttributes: Min value is unusable\n";
+ return false;
+ }
+ if (!(std::isfinite(max)))
+ {
+ std::cerr << "getSensorAttributes: Max value is unusable\n";
+ return false;
+ }
+
+ // Because NAN has already been tested for, this comparison works
+ if (max <= min)
+ {
+ std::cerr << "getSensorAttributes: Max must be greater than min\n";
+ return false;
+ }
+
+ // Given min and max, we must solve for M, B, bExp, rExp
+ // y comes in from D-Bus (the actual sensor reading)
+ // x is calculated from y by scaleIPMIValueFromDouble() below
+ // If y is min, x should equal = 0 (or -128 if signed)
+ // If y is max, x should equal 255 (or 127 if signed)
+ double fullRange = max - min;
+ double lowestX;
+
+ rExp = 0;
+ bExp = 0;
+
+ // TODO(): The IPMI document is ambiguous, as to whether
+ // the resulting byte should be signed or unsigned,
+ // essentially leaving it up to the caller.
+ // The document just refers to it as "raw reading",
+ // or "byte of reading", without giving further details.
+ // Previous code set it signed if min was less than zero,
+ // so I'm sticking with that, until I learn otherwise.
+ if (min < 0.0)
+ {
+ // TODO(): It would be worth experimenting with the range (-127,127),
+ // instead of the range (-128,127), because this
+ // would give good symmetry around zero, and make results look better.
+ // Divide by 254 instead of 255, and change -128 to -127 elsewhere.
+ bSigned = true;
+ lowestX = -128.0;
+ }
+ else
+ {
+ bSigned = false;
+ lowestX = 0.0;
+ }
+
+ // Step 1: Set y to (max - min), set x to 255, set B to 0, solve for M
+ // This works, regardless of signed or unsigned,
+ // because total range is the same.
+ double dM = fullRange / 255.0;
+
+ // Step 2: Constrain M, and set rExp accordingly
+ if (!(scaleFloatExp(dM, rExp)))
+ {
+ std::cerr << "getSensorAttributes: Multiplier range exceeds scale (M="
+ << dM << ", rExp=" << (int)rExp << ")\n";
+ return false;
+ }
+
+ mValue = static_cast<int16_t>(std::round(dM));
+
+ normalizeIntExp(mValue, rExp, dM);
+
+ // The multiplier can not be zero, for obvious reasons
+ if (mValue == 0)
+ {
+ std::cerr << "getSensorAttributes: Multiplier range below scale\n";
+ return false;
+ }
+
+ // Step 3: set y to min, set x to min, keep M and rExp, solve for B
+ // If negative, x will be -128 (the most negative possible byte), not 0
+
+ // Solve the IPMI equation for B, instead of y
+ // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+B
+ // B = 10^(-rExp - bExp) (y - M 10^rExp x)
+ // TODO(): Compare with this alternative solution from SageMathCell
+ // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasjoKTJgDAECTH&lang=sage&interacts=eJyLjgUAARUAuQ==
+ double dB = std::pow(10.0, ((-rExp) - bExp)) *
+ (min - ((dM * std::pow(10.0, rExp) * lowestX)));
+
+ // Step 4: Constrain B, and set bExp accordingly
+ if (!(scaleFloatExp(dB, bExp)))
+ {
+ std::cerr << "getSensorAttributes: Offset (B=" << dB << ", bExp="
+ << (int)bExp << ") exceeds multiplier scale (M=" << dM
+ << ", rExp=" << (int)rExp << ")\n";
+ return false;
+ }
+
+ bValue = static_cast<int16_t>(std::round(dB));
+
+ normalizeIntExp(bValue, bExp, dB);
+
+ // Unlike the multiplier, it is perfectly OK for bValue to be zero
+ return true;
+}
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+ const int8_t rExp, const int16_t bValue,
+ const int8_t bExp, const bool bSigned)
+{
+ // Avoid division by zero below
+ if (mValue == 0)
+ {
+ throw std::out_of_range("Scaling multiplier is uninitialized");
+ }
+
+ auto dM = static_cast<double>(mValue);
+ auto dB = static_cast<double>(bValue);
+
+ // Solve the IPMI equation for x, instead of y
+ // https://www.wolframalpha.com/input/?i=solve+y%3D%28%28M*x%29%2B%28B*%2810%5EE%29%29%29*%2810%5ER%29+for+x
+ // x = (10^(-rExp) (y - B 10^(rExp + bExp)))/M and M 10^rExp!=0
+ // TODO(): Compare with this alternative solution from SageMathCell
+ // https://sagecell.sagemath.org/?z=eJyrtC1LLNJQr1TX5KqAMCuATF8I0xfIdIIwnYDMIteKAggPxAIKJMEFkiACxfk5Zaka0ZUKtrYKGhq-CloKFZoK2goaTkCWhqGBgpaWAkilpqYmQgBklmasDlAlAMB8JP0=&lang=sage&interacts=eJyLjgUAARUAuQ==
+ double dX =
+ (std::pow(10.0, -rExp) * (value - (dB * std::pow(10.0, rExp + bExp)))) /
+ dM;
+
+ auto scaledValue = static_cast<int32_t>(std::round(dX));
+
+ int32_t minClamp;
+ int32_t maxClamp;
+
+ // Because of rounding and integer truncation of scaling factors,
+ // sometimes the resulting byte is slightly out of range.
+ // Still allow this, but clamp the values to range.
+ if (bSigned)
+ {
+ minClamp = std::numeric_limits<int8_t>::lowest();
+ maxClamp = std::numeric_limits<int8_t>::max();
+ }
+ else
+ {
+ minClamp = std::numeric_limits<uint8_t>::lowest();
+ maxClamp = std::numeric_limits<uint8_t>::max();
+ }
+
+ auto clampedValue = std::clamp(scaledValue, minClamp, maxClamp);
+
+ // This works for both signed and unsigned,
+ // because it is the same underlying byte storage.
+ return static_cast<uint8_t>(clampedValue);
+}
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+ const double min)
+{
+ int16_t mValue = 0;
+ int8_t rExp = 0;
+ int16_t bValue = 0;
+ int8_t bExp = 0;
+ bool bSigned = false;
+
+ bool result =
+ getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned);
+ if (!result)
+ {
+ throw std::runtime_error("Illegal sensor attributes");
+ }
+
+ return scaleIPMIValueFromDouble(value, mValue, rExp, bValue, bExp, bSigned);
+}
+
+} // namespace ipmi
diff --git a/dbus-sdr/storagecommands.cpp b/dbus-sdr/storagecommands.cpp
new file mode 100644
index 0000000..e8c4223
--- /dev/null
+++ b/dbus-sdr/storagecommands.cpp
@@ -0,0 +1,1296 @@
+/*
+// Copyright (c) 2017-2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "dbus-sdr/storagecommands.hpp"
+
+#include "dbus-sdr/sdrutils.hpp"
+#include "selutility.hpp"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/asio/detached.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/process.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <stdexcept>
+#include <string_view>
+
+static constexpr bool DEBUG = false;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static const std::filesystem::path selLogDir = "/var/log";
+static const std::string selLogFilename = "ipmi_sel";
+
+static int getFileTimestamp(const std::filesystem::path& file)
+{
+ struct stat st;
+
+ if (stat(file.c_str(), &st) >= 0)
+ {
+ return st.st_mtime;
+ }
+ return ::ipmi::sel::invalidTimeStamp;
+}
+
+namespace erase_time
+{
+static constexpr const char* selEraseTimestamp = "/var/lib/ipmi/sel_erase_time";
+
+int get()
+{
+ return getFileTimestamp(selEraseTimestamp);
+}
+} // namespace erase_time
+} // namespace dynamic_sensors::ipmi::sel
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+constexpr static const size_t maxFruSdrNameSize = 16;
+using ObjectType =
+ boost::container::flat_map<std::string,
+ boost::container::flat_map<std::string, Value>>;
+using ManagedObjectType =
+ boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
+using ManagedEntry = std::pair<sdbusplus::message::object_path, ObjectType>;
+
+constexpr static const char* fruDeviceServiceName =
+ "xyz.openbmc_project.FruDevice";
+constexpr static const size_t writeTimeoutSeconds = 10;
+constexpr static const char* chassisTypeRackMount = "23";
+constexpr static const char* chassisTypeMainServer = "17";
+
+static std::vector<uint8_t> fruCache;
+static constexpr uint16_t invalidBus = 0xFFFF;
+static constexpr uint8_t invalidAddr = 0xFF;
+static constexpr uint8_t typeASCIILatin8 = 0xC0;
+static uint16_t cacheBus = invalidBus;
+static uint8_t cacheAddr = invalidAddr;
+static uint8_t lastDevId = 0xFF;
+
+static uint16_t writeBus = invalidBus;
+static uint8_t writeAddr = invalidAddr;
+
+std::unique_ptr<sdbusplus::Timer> writeTimer = nullptr;
+static std::vector<sdbusplus::bus::match_t> fruMatches;
+
+ManagedObjectType frus;
+
+// we unfortunately have to build a map of hashes in case there is a
+// collision to verify our dev-id
+boost::container::flat_map<uint8_t, std::pair<uint16_t, uint8_t>> deviceHashes;
+void registerStorageFunctions() __attribute__((constructor));
+
+bool writeFru(const std::vector<uint8_t>& fru)
+{
+ if (writeBus == invalidBus && writeAddr == invalidAddr)
+ {
+ return true;
+ }
+ lastDevId = 0xFF;
+ std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
+ sdbusplus::message_t writeFru = dbus->new_method_call(
+ fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "WriteFru");
+ writeFru.append(writeBus, writeAddr, fru);
+ try
+ {
+ sdbusplus::message_t writeFruResp = dbus->call(writeFru);
+ }
+ catch (const sdbusplus::exception_t&)
+ {
+ // todo: log sel?
+ lg2::error("error writing fru");
+ return false;
+ }
+ writeBus = invalidBus;
+ writeAddr = invalidAddr;
+ return true;
+}
+
+void writeFruCache()
+{
+ writeFru(fruCache);
+}
+
+void createTimers()
+{
+ writeTimer = std::make_unique<sdbusplus::Timer>(writeFruCache);
+}
+
+void recalculateHashes()
+{
+ deviceHashes.clear();
+ // hash the object paths to create unique device id's. increment on
+ // collision
+ std::hash<std::string> hasher;
+ for (const auto& fru : frus)
+ {
+ auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
+ if (fruIface == fru.second.end())
+ {
+ continue;
+ }
+
+ auto busFind = fruIface->second.find("BUS");
+ auto addrFind = fruIface->second.find("ADDRESS");
+ if (busFind == fruIface->second.end() ||
+ addrFind == fruIface->second.end())
+ {
+ lg2::info("fru device missing Bus or Address, fru: {FRU}", "FRU",
+ fru.first.str);
+ continue;
+ }
+
+ uint16_t fruBus = std::get<uint32_t>(busFind->second);
+ uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
+ auto chassisFind = fruIface->second.find("CHASSIS_TYPE");
+ std::string chassisType;
+ if (chassisFind != fruIface->second.end())
+ {
+ chassisType = std::get<std::string>(chassisFind->second);
+ }
+
+ uint8_t fruHash = 0;
+ if (chassisType.compare(chassisTypeRackMount) != 0 &&
+ chassisType.compare(chassisTypeMainServer) != 0)
+ {
+ fruHash = hasher(fru.first.str);
+ // can't be 0xFF based on spec, and 0 is reserved for baseboard
+ if (fruHash == 0 || fruHash == 0xFF)
+ {
+ fruHash = 1;
+ }
+ }
+ std::pair<uint16_t, uint8_t> newDev(fruBus, fruAddr);
+
+ bool emplacePassed = false;
+ while (!emplacePassed)
+ {
+ auto resp = deviceHashes.emplace(fruHash, newDev);
+ emplacePassed = resp.second;
+ if (!emplacePassed)
+ {
+ fruHash++;
+ // can't be 0xFF based on spec, and 0 is reserved for
+ // baseboard
+ if (fruHash == 0XFF)
+ {
+ fruHash = 0x1;
+ }
+ }
+ }
+ }
+}
+
+void replaceCacheFru(
+ const std::shared_ptr<sdbusplus::asio::connection>& bus,
+ boost::asio::yield_context& yield,
+ [[maybe_unused]] const std::optional<std::string>& path = std::nullopt)
+{
+ boost::system::error_code ec;
+
+ frus = bus->yield_method_call<ManagedObjectType>(
+ yield, ec, fruDeviceServiceName, "/",
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+ if (ec)
+ {
+ lg2::error("GetMangagedObjects for replaceCacheFru failed: {ERROR}",
+ "ERROR", ec.message());
+
+ return;
+ }
+ recalculateHashes();
+}
+
+std::pair<ipmi::Cc, std::vector<uint8_t>> getFru(ipmi::Context::ptr ctx,
+ uint8_t devId)
+{
+ if (lastDevId == devId && devId != 0xFF)
+ {
+ return {ipmi::ccSuccess, fruCache};
+ }
+
+ auto deviceFind = deviceHashes.find(devId);
+ if (deviceFind == deviceHashes.end())
+ {
+ return {IPMI_CC_SENSOR_INVALID, {}};
+ }
+
+ cacheBus = deviceFind->second.first;
+ cacheAddr = deviceFind->second.second;
+
+ boost::system::error_code ec;
+ std::vector<uint8_t> fru = ipmi::callDbusMethod<std::vector<uint8_t>>(
+ ctx, ec, fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
+ "xyz.openbmc_project.FruDeviceManager", "GetRawFru", cacheBus,
+ cacheAddr);
+
+ if (ec)
+ {
+ lg2::error("Couldn't get raw fru: {ERROR}", "ERROR", ec.message());
+
+ cacheBus = invalidBus;
+ cacheAddr = invalidAddr;
+ return {ipmi::ccResponseError, {}};
+ }
+
+ fruCache.clear();
+ lastDevId = devId;
+ fruCache = fru;
+
+ return {ipmi::ccSuccess, fru};
+}
+
+void writeFruIfRunning()
+{
+ if (!writeTimer->isRunning())
+ {
+ return;
+ }
+ writeTimer->stop();
+ writeFruCache();
+}
+
+void startMatch(void)
+{
+ if (fruMatches.size())
+ {
+ return;
+ }
+
+ fruMatches.reserve(2);
+
+ auto bus = getSdBus();
+ fruMatches.emplace_back(
+ *bus,
+ "type='signal',arg0path='/xyz/openbmc_project/"
+ "FruDevice/',member='InterfacesAdded'",
+ [](sdbusplus::message_t& message) {
+ sdbusplus::message::object_path path;
+ ObjectType object;
+ try
+ {
+ message.read(path, object);
+ }
+ catch (const sdbusplus::exception_t&)
+ {
+ return;
+ }
+ auto findType = object.find("xyz.openbmc_project.FruDevice");
+ if (findType == object.end())
+ {
+ return;
+ }
+ writeFruIfRunning();
+ frus[path] = object;
+ recalculateHashes();
+ lastDevId = 0xFF;
+ });
+
+ fruMatches.emplace_back(
+ *bus,
+ "type='signal',arg0path='/xyz/openbmc_project/"
+ "FruDevice/',member='InterfacesRemoved'",
+ [](sdbusplus::message_t& message) {
+ sdbusplus::message::object_path path;
+ std::set<std::string> interfaces;
+ try
+ {
+ message.read(path, interfaces);
+ }
+ catch (const sdbusplus::exception_t&)
+ {
+ return;
+ }
+ auto findType = interfaces.find("xyz.openbmc_project.FruDevice");
+ if (findType == interfaces.end())
+ {
+ return;
+ }
+ writeFruIfRunning();
+ frus.erase(path);
+ recalculateHashes();
+ lastDevId = 0xFF;
+ });
+
+ // call once to populate
+ boost::asio::spawn(
+ *getIoContext(),
+ [](boost::asio::yield_context yield) {
+ replaceCacheFru(getSdBus(), yield);
+ },
+ boost::asio::detached);
+}
+
+/** @brief implements the read FRU data command
+ * @param fruDeviceId - FRU Device ID
+ * @param fruInventoryOffset - FRU Inventory Offset to write
+ * @param countToRead - Count to read
+ *
+ * @returns ipmi completion code plus response data
+ * - countWritten - Count written
+ */
+ipmi::RspType<uint8_t, // Count
+ std::vector<uint8_t> // Requested data
+ >
+ ipmiStorageReadFruData(ipmi::Context::ptr ctx, uint8_t fruDeviceId,
+ uint16_t fruInventoryOffset, uint8_t countToRead)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto [status, fru] = getFru(ctx, fruDeviceId);
+ if (status != ipmi::ccSuccess)
+ {
+ return ipmi::response(status);
+ }
+
+ size_t fromFruByteLen = 0;
+ if (countToRead + fruInventoryOffset < fru.size())
+ {
+ fromFruByteLen = countToRead;
+ }
+ else if (fru.size() > fruInventoryOffset)
+ {
+ fromFruByteLen = fru.size() - fruInventoryOffset;
+ }
+ else
+ {
+ return ipmi::responseReqDataLenExceeded();
+ }
+
+ std::vector<uint8_t> requestedData;
+
+ requestedData.insert(requestedData.begin(),
+ fru.begin() + fruInventoryOffset,
+ fru.begin() + fruInventoryOffset + fromFruByteLen);
+
+ return ipmi::responseSuccess(static_cast<uint8_t>(requestedData.size()),
+ requestedData);
+}
+
+/** @brief implements the write FRU data command
+ * @param fruDeviceId - FRU Device ID
+ * @param fruInventoryOffset - FRU Inventory Offset to write
+ * @param dataToWrite - Data to write
+ *
+ * @returns ipmi completion code plus response data
+ * - countWritten - Count written
+ */
+ipmi::RspType<uint8_t> ipmiStorageWriteFruData(
+ ipmi::Context::ptr ctx, uint8_t fruDeviceId, uint16_t fruInventoryOffset,
+ std::vector<uint8_t>& dataToWrite)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ size_t writeLen = dataToWrite.size();
+
+ auto [status, fru] = getFru(ctx, fruDeviceId);
+ if (status != ipmi::ccSuccess)
+ {
+ return ipmi::response(status);
+ }
+ size_t lastWriteAddr = fruInventoryOffset + writeLen;
+ if (fru.size() < lastWriteAddr)
+ {
+ fru.resize(fruInventoryOffset + writeLen);
+ }
+
+ std::copy(dataToWrite.begin(), dataToWrite.begin() + writeLen,
+ fru.begin() + fruInventoryOffset);
+
+ bool atEnd = false;
+
+ if (fru.size() >= sizeof(FRUHeader))
+ {
+ FRUHeader* header = reinterpret_cast<FRUHeader*>(fru.data());
+
+ size_t areaLength = 0;
+ size_t lastRecordStart = std::max(
+ {header->internalOffset, header->chassisOffset, header->boardOffset,
+ header->productOffset, header->multiRecordOffset});
+ lastRecordStart *= 8; // header starts in are multiples of 8 bytes
+
+ if (header->multiRecordOffset)
+ {
+ // This FRU has a MultiRecord Area
+ uint8_t endOfList = 0;
+ // Walk the MultiRecord headers until the last record
+ while (!endOfList)
+ {
+ // The MSB in the second byte of the MultiRecord header signals
+ // "End of list"
+ endOfList = fru[lastRecordStart + 1] & 0x80;
+ // Third byte in the MultiRecord header is the length
+ areaLength = fru[lastRecordStart + 2];
+ // This length is in bytes (not 8 bytes like other headers)
+ areaLength += 5; // The length omits the 5 byte header
+ if (!endOfList)
+ {
+ // Next MultiRecord header
+ lastRecordStart += areaLength;
+ }
+ }
+ }
+ else
+ {
+ // This FRU does not have a MultiRecord Area
+ // Get the length of the area in multiples of 8 bytes
+ if (lastWriteAddr > (lastRecordStart + 1))
+ {
+ // second byte in record area is the length
+ areaLength = fru[lastRecordStart + 1];
+ areaLength *= 8; // it is in multiples of 8 bytes
+ }
+ }
+ if (lastWriteAddr >= (areaLength + lastRecordStart))
+ {
+ atEnd = true;
+ }
+ }
+ uint8_t countWritten = 0;
+
+ writeBus = cacheBus;
+ writeAddr = cacheAddr;
+ if (atEnd)
+ {
+ // cancel timer, we're at the end so might as well send it
+ writeTimer->stop();
+ if (!writeFru(fru))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ countWritten = std::min(fru.size(), static_cast<size_t>(0xFF));
+ }
+ else
+ {
+ fruCache = fru; // Write-back
+ // start a timer, if no further data is sent to check to see if it is
+ // valid
+ writeTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::seconds(writeTimeoutSeconds)));
+ countWritten = 0;
+ }
+
+ return ipmi::responseSuccess(countWritten);
+}
+
+/** @brief implements the get FRU inventory area info command
+ * @param fruDeviceId - FRU Device ID
+ *
+ * @returns IPMI completion code plus response data
+ * - inventorySize - Number of possible allocation units
+ * - accessType - Allocation unit size in bytes.
+ */
+ipmi::RspType<uint16_t, // inventorySize
+ uint8_t> // accessType
+ ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruDeviceId)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto [ret, fru] = getFru(ctx, fruDeviceId);
+ if (ret != ipmi::ccSuccess)
+ {
+ return ipmi::response(ret);
+ }
+
+ constexpr uint8_t accessType =
+ static_cast<uint8_t>(GetFRUAreaAccessType::byte);
+
+ return ipmi::responseSuccess(fru.size(), accessType);
+}
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr, size_t& count)
+{
+ count = deviceHashes.size();
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t getFruSdrs([[maybe_unused]] ipmi::Context::ptr ctx, size_t index,
+ get_sdr::SensorDataFruRecord& resp)
+{
+ if (deviceHashes.size() < index)
+ {
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+ auto device = deviceHashes.begin() + index;
+ uint16_t& bus = device->second.first;
+ uint8_t& address = device->second.second;
+
+ boost::container::flat_map<std::string, Value>* fruData = nullptr;
+ auto fru = std::find_if(
+ frus.begin(), frus.end(),
+ [bus, address, &fruData](ManagedEntry& entry) {
+ auto findFruDevice =
+ entry.second.find("xyz.openbmc_project.FruDevice");
+ if (findFruDevice == entry.second.end())
+ {
+ return false;
+ }
+ fruData = &(findFruDevice->second);
+ auto findBus = findFruDevice->second.find("BUS");
+ auto findAddress = findFruDevice->second.find("ADDRESS");
+ if (findBus == findFruDevice->second.end() ||
+ findAddress == findFruDevice->second.end())
+ {
+ return false;
+ }
+ if (std::get<uint32_t>(findBus->second) != bus)
+ {
+ return false;
+ }
+ if (std::get<uint32_t>(findAddress->second) != address)
+ {
+ return false;
+ }
+ return true;
+ });
+ if (fru == frus.end())
+ {
+ return IPMI_CC_RESPONSE_ERROR;
+ }
+ std::string name;
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+
+ boost::container::flat_map<std::string, Value>* entityData = nullptr;
+
+ // todo: this should really use caching, this is a very inefficient lookup
+ boost::system::error_code ec;
+ ManagedObjectType entities = ipmi::callDbusMethod<ManagedObjectType>(
+ ctx, ec, "xyz.openbmc_project.EntityManager",
+ "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+
+ if (ec)
+ {
+ lg2::error("GetMangagedObjects for ipmiStorageGetFruInvAreaInfo "
+ "failed: {ERROR}",
+ "ERROR", ec.message());
+
+ return ipmi::ccResponseError;
+ }
+
+ auto entity = std::find_if(
+ entities.begin(), entities.end(),
+ [bus, address, &entityData, &name](ManagedEntry& entry) {
+ auto findFruDevice = entry.second.find(
+ "xyz.openbmc_project.Inventory.Decorator.I2CDevice");
+ if (findFruDevice == entry.second.end())
+ {
+ return false;
+ }
+
+ // Integer fields added via Entity-Manager json are uint64_ts by
+ // default.
+ auto findBus = findFruDevice->second.find("Bus");
+ auto findAddress = findFruDevice->second.find("Address");
+
+ if (findBus == findFruDevice->second.end() ||
+ findAddress == findFruDevice->second.end())
+ {
+ return false;
+ }
+ if ((std::get<uint64_t>(findBus->second) != bus) ||
+ (std::get<uint64_t>(findAddress->second) != address))
+ {
+ return false;
+ }
+
+ auto fruName = findFruDevice->second.find("Name");
+ if (fruName != findFruDevice->second.end())
+ {
+ name = std::get<std::string>(fruName->second);
+ }
+
+ // At this point we found the device entry and should return
+ // true.
+ auto findIpmiDevice = entry.second.find(
+ "xyz.openbmc_project.Inventory.Decorator.Ipmi");
+ if (findIpmiDevice != entry.second.end())
+ {
+ entityData = &(findIpmiDevice->second);
+ }
+
+ return true;
+ });
+
+ if (entity == entities.end())
+ {
+ if constexpr (DEBUG)
+ {
+ std::fprintf(stderr, "Ipmi or FruDevice Decorator interface "
+ "not found for Fru\n");
+ }
+ }
+
+#endif
+
+ std::vector<std::string> nameProperties = {
+ "PRODUCT_PRODUCT_NAME", "BOARD_PRODUCT_NAME", "PRODUCT_PART_NUMBER",
+ "BOARD_PART_NUMBER", "PRODUCT_MANUFACTURER", "BOARD_MANUFACTURER",
+ "PRODUCT_SERIAL_NUMBER", "BOARD_SERIAL_NUMBER"};
+
+ for (const std::string& prop : nameProperties)
+ {
+ auto findProp = fruData->find(prop);
+ if (findProp != fruData->end())
+ {
+ name = std::get<std::string>(findProp->second);
+ break;
+ }
+ }
+
+ if (name.empty())
+ {
+ name = "UNKNOWN";
+ }
+ if (name.size() > maxFruSdrNameSize)
+ {
+ name = name.substr(0, maxFruSdrNameSize);
+ }
+ size_t sizeDiff = maxFruSdrNameSize - name.size();
+
+ resp.header.record_id_lsb = 0x0; // calling code is to implement these
+ resp.header.record_id_msb = 0x0;
+ resp.header.sdr_version = ipmiSdrVersion;
+ resp.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+ resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
+ resp.key.deviceAddress = 0x20;
+ resp.key.fruID = device->first;
+ resp.key.accessLun = 0x80; // logical / physical fru device
+ resp.key.channelNumber = 0x0;
+ resp.body.reserved = 0x0;
+ resp.body.deviceType = 0x10;
+ resp.body.deviceTypeModifier = 0x0;
+
+ uint8_t entityID = 0;
+ uint8_t entityInstance = 0x1;
+
+#ifdef USING_ENTITY_MANAGER_DECORATORS
+ if (entityData)
+ {
+ auto entityIdProperty = entityData->find("EntityId");
+ auto entityInstanceProperty = entityData->find("EntityInstance");
+
+ if (entityIdProperty != entityData->end())
+ {
+ entityID = static_cast<uint8_t>(
+ std::get<uint64_t>(entityIdProperty->second));
+ }
+ if (entityInstanceProperty != entityData->end())
+ {
+ entityInstance = static_cast<uint8_t>(
+ std::get<uint64_t>(entityInstanceProperty->second));
+ }
+ }
+#endif
+
+ resp.body.entityID = entityID;
+ resp.body.entityInstance = entityInstance;
+
+ resp.body.oem = 0x0;
+ resp.body.deviceIDLen = ipmi::storage::typeASCIILatin8 | name.size();
+ name.copy(resp.body.deviceID, name.size());
+
+ return IPMI_CC_OK;
+}
+
+static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
+{
+ // Loop through the directory looking for ipmi_sel log files
+ for (const std::filesystem::directory_entry& dirEnt :
+ std::filesystem::directory_iterator(
+ dynamic_sensors::ipmi::sel::selLogDir))
+ {
+ std::string filename = dirEnt.path().filename();
+ if (boost::starts_with(filename,
+ dynamic_sensors::ipmi::sel::selLogFilename))
+ {
+ // If we find an ipmi_sel log file, save the path
+ selLogFiles.emplace_back(
+ dynamic_sensors::ipmi::sel::selLogDir / filename);
+ }
+ }
+ // As the log files rotate, they are appended with a ".#" that is higher for
+ // the older logs. Since we don't expect more than 10 log files, we
+ // can just sort the list to get them in order from newest to oldest
+ std::sort(selLogFiles.begin(), selLogFiles.end());
+
+ return !selLogFiles.empty();
+}
+
+static int countSELEntries()
+{
+ // Get the list of ipmi_sel log files
+ std::vector<std::filesystem::path> selLogFiles;
+ if (!getSELLogFiles(selLogFiles))
+ {
+ return 0;
+ }
+ int numSELEntries = 0;
+ // Loop through each log file and count the number of logs
+ for (const std::filesystem::path& file : selLogFiles)
+ {
+ std::ifstream logStream(file);
+ if (!logStream.is_open())
+ {
+ continue;
+ }
+
+ std::string line;
+ while (std::getline(logStream, line))
+ {
+ numSELEntries++;
+ }
+ }
+ return numSELEntries;
+}
+
+static bool findSELEntry(const int recordID,
+ const std::vector<std::filesystem::path>& selLogFiles,
+ std::string& entry)
+{
+ // Record ID is the first entry field following the timestamp. It is
+ // preceded by a space and followed by a comma
+ std::string search = " " + std::to_string(recordID) + ",";
+
+ // Loop through the ipmi_sel log entries
+ for (const std::filesystem::path& file : selLogFiles)
+ {
+ std::ifstream logStream(file);
+ if (!logStream.is_open())
+ {
+ continue;
+ }
+
+ while (std::getline(logStream, entry))
+ {
+ // Check if the record ID matches
+ if (entry.find(search) != std::string::npos)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static uint16_t getNextRecordID(
+ const uint16_t recordID,
+ const std::vector<std::filesystem::path>& selLogFiles)
+{
+ uint16_t nextRecordID = recordID + 1;
+ std::string entry;
+ if (findSELEntry(nextRecordID, selLogFiles, entry))
+ {
+ return nextRecordID;
+ }
+ else
+ {
+ return ipmi::sel::lastEntry;
+ }
+}
+
+static int fromHexStr(const std::string& hexStr, std::vector<uint8_t>& data)
+{
+ for (unsigned int i = 0; i < hexStr.size(); i += 2)
+ {
+ try
+ {
+ data.push_back(static_cast<uint8_t>(
+ std::stoul(hexStr.substr(i, 2), nullptr, 16)));
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Invalid argument: {ERROR}", "ERROR", e);
+ return -1;
+ }
+ catch (const std::out_of_range& e)
+ {
+ lg2::error("Out of range: {ERROR}", "ERROR", e);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+ipmi::RspType<uint8_t, // SEL version
+ uint16_t, // SEL entry count
+ uint16_t, // free space
+ uint32_t, // last add timestamp
+ uint32_t, // last erase timestamp
+ uint8_t> // operation support
+ ipmiStorageGetSELInfo()
+{
+ constexpr uint8_t selVersion = ipmi::sel::selVersion;
+ uint16_t entries = countSELEntries();
+ uint32_t addTimeStamp = dynamic_sensors::ipmi::sel::getFileTimestamp(
+ dynamic_sensors::ipmi::sel::selLogDir /
+ dynamic_sensors::ipmi::sel::selLogFilename);
+ uint32_t eraseTimeStamp = dynamic_sensors::ipmi::sel::erase_time::get();
+ constexpr uint8_t operationSupport =
+ dynamic_sensors::ipmi::sel::selOperationSupport;
+ constexpr uint16_t freeSpace =
+ 0xffff; // Spec indicates that more than 64kB is free
+
+ return ipmi::responseSuccess(selVersion, entries, freeSpace, addTimeStamp,
+ eraseTimeStamp, operationSupport);
+}
+
+using systemEventType = std::tuple<
+ uint32_t, // Timestamp
+ uint16_t, // Generator ID
+ uint8_t, // EvM Rev
+ uint8_t, // Sensor Type
+ uint8_t, // Sensor Number
+ uint7_t, // Event Type
+ bool, // Event Direction
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>>; // Event
+ // Data
+using oemTsEventType = std::tuple<
+ uint32_t, // Timestamp
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemTsEventSize>>; // Event
+ // Data
+using oemEventType =
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemEventSize>; // Event Data
+
+ipmi::RspType<uint16_t, // Next Record ID
+ uint16_t, // Record ID
+ uint8_t, // Record Type
+ std::variant<systemEventType, oemTsEventType,
+ oemEventType>> // Record Content
+ ipmiStorageGetSELEntry(uint16_t reservationID, uint16_t targetID,
+ uint8_t offset, uint8_t size)
+{
+ // Only support getting the entire SEL record. If a partial size or non-zero
+ // offset is requested, return an error
+ if (offset != 0 || size != ipmi::sel::entireRecord)
+ {
+ return ipmi::responseRetBytesUnavailable();
+ }
+
+ // Check the reservation ID if one is provided or required (only if the
+ // offset is non-zero)
+ if (reservationID != 0 || offset != 0)
+ {
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+ }
+
+ // Get the ipmi_sel log files
+ std::vector<std::filesystem::path> selLogFiles;
+ if (!getSELLogFiles(selLogFiles))
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ std::string targetEntry;
+
+ if (targetID == ipmi::sel::firstEntry)
+ {
+ // The first entry will be at the top of the oldest log file
+ std::ifstream logStream(selLogFiles.back());
+ if (!logStream.is_open())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (!std::getline(logStream, targetEntry))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+ else if (targetID == ipmi::sel::lastEntry)
+ {
+ // The last entry will be at the bottom of the newest log file
+ std::ifstream logStream(selLogFiles.front());
+ if (!logStream.is_open())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string line;
+ while (std::getline(logStream, line))
+ {
+ targetEntry = line;
+ }
+ }
+ else
+ {
+ if (!findSELEntry(targetID, selLogFiles, targetEntry))
+ {
+ return ipmi::responseSensorInvalid();
+ }
+ }
+
+ // The format of the ipmi_sel message is "<Timestamp>
+ // <ID>,<Type>,<EventData>,[<Generator ID>,<Path>,<Direction>]".
+ // First get the Timestamp
+ size_t space = targetEntry.find_first_of(" ");
+ if (space == std::string::npos)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string entryTimestamp = targetEntry.substr(0, space);
+ // Then get the log contents
+ size_t entryStart = targetEntry.find_first_not_of(" ", space);
+ if (entryStart == std::string::npos)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string_view entry(targetEntry);
+ entry.remove_prefix(entryStart);
+ // Use split to separate the entry into its fields
+ std::vector<std::string> targetEntryFields;
+ boost::split(targetEntryFields, entry, boost::is_any_of(","),
+ boost::token_compress_on);
+ if (targetEntryFields.size() < 3)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::string& recordIDStr = targetEntryFields[0];
+ std::string& recordTypeStr = targetEntryFields[1];
+ std::string& eventDataStr = targetEntryFields[2];
+
+ uint16_t recordID;
+ uint8_t recordType;
+ try
+ {
+ recordID = std::stoul(recordIDStr);
+ recordType = std::stoul(recordTypeStr, nullptr, 16);
+ }
+ catch (const std::invalid_argument&)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ uint16_t nextRecordID = getNextRecordID(recordID, selLogFiles);
+ std::vector<uint8_t> eventDataBytes;
+ if (fromHexStr(eventDataStr, eventDataBytes) < 0)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (recordType == dynamic_sensors::ipmi::sel::systemEvent)
+ {
+ // Get the timestamp
+ std::tm timeStruct = {};
+ std::istringstream entryStream(entryTimestamp);
+
+ uint32_t timestamp = ipmi::sel::invalidTimeStamp;
+ if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+ {
+ timeStruct.tm_isdst = -1;
+ timestamp = std::mktime(&timeStruct);
+ }
+
+ // Set the event message revision
+ uint8_t evmRev = dynamic_sensors::ipmi::sel::eventMsgRev;
+
+ uint16_t generatorID = 0;
+ uint8_t sensorType = 0;
+ uint16_t sensorAndLun = 0;
+ uint8_t sensorNum = 0xFF;
+ uint7_t eventType = 0;
+ bool eventDir = 0;
+ // System type events should have six fields
+ if (targetEntryFields.size() >= 6)
+ {
+ std::string& generatorIDStr = targetEntryFields[3];
+ std::string& sensorPath = targetEntryFields[4];
+ std::string& eventDirStr = targetEntryFields[5];
+
+ // Get the generator ID
+ try
+ {
+ generatorID = std::stoul(generatorIDStr, nullptr, 16);
+ }
+ catch (const std::invalid_argument&)
+ {
+ std::cerr << "Invalid Generator ID\n";
+ }
+
+ // Get the sensor type, sensor number, and event type for the sensor
+ sensorType = getSensorTypeFromPath(sensorPath);
+ sensorAndLun = getSensorNumberFromPath(sensorPath);
+ sensorNum = static_cast<uint8_t>(sensorAndLun);
+ if ((generatorID & 0x0001) == 0)
+ {
+ // IPMB Address
+ generatorID |= sensorAndLun & 0x0300;
+ }
+ else
+ {
+ // system software
+ generatorID |= sensorAndLun >> 8;
+ }
+ eventType = getSensorEventTypeFromPath(sensorPath);
+
+ // Get the event direction
+ try
+ {
+ eventDir = std::stoul(eventDirStr) ? 0 : 1;
+ }
+ catch (const std::invalid_argument&)
+ {
+ std::cerr << "Invalid Event Direction\n";
+ }
+ }
+
+ // Only keep the eventData bytes that fit in the record
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::systemEventSize>
+ eventData{};
+ std::copy_n(eventDataBytes.begin(),
+ std::min(eventDataBytes.size(), eventData.size()),
+ eventData.begin());
+
+ return ipmi::responseSuccess(
+ nextRecordID, recordID, recordType,
+ systemEventType{timestamp, generatorID, evmRev, sensorType,
+ sensorNum, eventType, eventDir, eventData});
+ }
+
+ if (recordType >= dynamic_sensors::ipmi::sel::oemTsEventFirst &&
+ recordType <= dynamic_sensors::ipmi::sel::oemTsEventLast)
+ {
+ // Get the timestamp
+ std::tm timeStruct = {};
+ std::istringstream entryStream(entryTimestamp);
+
+ uint32_t timestamp = ipmi::sel::invalidTimeStamp;
+ if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
+ {
+ timeStruct.tm_isdst = -1;
+ timestamp = std::mktime(&timeStruct);
+ }
+
+ // Only keep the bytes that fit in the record
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemTsEventSize>
+ eventData{};
+ std::copy_n(eventDataBytes.begin(),
+ std::min(eventDataBytes.size(), eventData.size()),
+ eventData.begin());
+
+ return ipmi::responseSuccess(nextRecordID, recordID, recordType,
+ oemTsEventType{timestamp, eventData});
+ }
+
+ if (recordType >= dynamic_sensors::ipmi::sel::oemEventFirst)
+ {
+ // Only keep the bytes that fit in the record
+ std::array<uint8_t, dynamic_sensors::ipmi::sel::oemEventSize>
+ eventData{};
+ std::copy_n(eventDataBytes.begin(),
+ std::min(eventDataBytes.size(), eventData.size()),
+ eventData.begin());
+
+ return ipmi::responseSuccess(nextRecordID, recordID, recordType,
+ eventData);
+ }
+
+ return ipmi::responseUnspecifiedError();
+}
+
+/*
+Unused arguments
+ uint16_t recordID, uint8_t recordType, uint32_t timestamp,
+ uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, uint8_t sensorNum,
+ uint8_t eventType, uint8_t eventData1, uint8_t eventData2,
+ uint8_t eventData3
+*/
+ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(
+ uint16_t, uint8_t, uint32_t, uint16_t, uint8_t, uint8_t, uint8_t, uint8_t,
+ uint8_t, uint8_t, uint8_t)
+{
+ // Per the IPMI spec, need to cancel any reservation when a SEL entry is
+ // added
+ cancelSELReservation();
+
+ uint16_t responseID = 0xFFFF;
+ return ipmi::responseSuccess(responseID);
+}
+
+ipmi::RspType<uint8_t> ipmiStorageClearSEL(
+ ipmi::Context::ptr ctx, uint16_t reservationID,
+ const std::array<uint8_t, 3>& clr, uint8_t eraseOperation)
+{
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+
+ static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
+ if (clr != clrExpected)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Erasure status cannot be fetched, so always return erasure status as
+ // `erase completed`.
+ if (eraseOperation == ipmi::sel::getEraseStatus)
+ {
+ return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+ }
+
+ // Check that initiate erase is correct
+ if (eraseOperation != ipmi::sel::initiateErase)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Per the IPMI spec, need to cancel any reservation when the SEL is
+ // cleared
+ cancelSELReservation();
+
+ boost::system::error_code ec =
+ ipmi::callDbusMethod(ctx, "xyz.openbmc_project.Logging.IPMI",
+ "/xyz/openbmc_project/Logging/IPMI",
+ "xyz.openbmc_project.Logging.IPMI", "Clear");
+ if (ec)
+ {
+ std::cerr << "error in clear SEL: " << ec.message() << std::endl;
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(ipmi::sel::eraseComplete);
+}
+
+std::vector<uint8_t> getType8SDRs(
+ ipmi::sensor::EntityInfoMap::const_iterator& entity, uint16_t recordId)
+{
+ std::vector<uint8_t> resp;
+ get_sdr::SensorDataEntityRecord data{};
+
+ /* Header */
+ get_sdr::header::set_record_id(recordId, &(data.header));
+ // Based on IPMI Spec v2.0 rev 1.1
+ data.header.sdr_version = SDR_VERSION;
+ data.header.record_type = 0x08;
+ data.header.record_length = sizeof(data.key) + sizeof(data.body);
+
+ /* Key */
+ data.key.containerEntityId = entity->second.containerEntityId;
+ data.key.containerEntityInstance = entity->second.containerEntityInstance;
+ get_sdr::key::set_flags(entity->second.isList, entity->second.isLinked,
+ &(data.key));
+ data.key.entityId1 = entity->second.containedEntities[0].first;
+ data.key.entityInstance1 = entity->second.containedEntities[0].second;
+
+ /* Body */
+ data.body.entityId2 = entity->second.containedEntities[1].first;
+ data.body.entityInstance2 = entity->second.containedEntities[1].second;
+ data.body.entityId3 = entity->second.containedEntities[2].first;
+ data.body.entityInstance3 = entity->second.containedEntities[2].second;
+ data.body.entityId4 = entity->second.containedEntities[3].first;
+ data.body.entityInstance4 = entity->second.containedEntities[3].second;
+
+ resp.insert(resp.end(), (uint8_t*)&data, ((uint8_t*)&data) + sizeof(data));
+
+ return resp;
+}
+
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId)
+{
+ std::vector<uint8_t> resp;
+ if (index == 0)
+ {
+ std::string bmcName = "Basbrd Mgmt Ctlr";
+ Type12Record bmc(recordId, 0x20, 0, 0, 0xbf, 0x2e, 1, 0, bmcName);
+ uint8_t* bmcPtr = reinterpret_cast<uint8_t*>(&bmc);
+ resp.insert(resp.end(), bmcPtr, bmcPtr + sizeof(Type12Record));
+ }
+ else if (index == 1)
+ {
+ std::string meName = "Mgmt Engine";
+ Type12Record me(recordId, 0x2c, 6, 0x24, 0x21, 0x2e, 2, 0, meName);
+ uint8_t* mePtr = reinterpret_cast<uint8_t*>(&me);
+ resp.insert(resp.end(), mePtr, mePtr + sizeof(Type12Record));
+ }
+ else
+ {
+ throw std::runtime_error(
+ "getType12SDRs:: Illegal index " + std::to_string(index));
+ }
+
+ return resp;
+}
+
+void registerStorageFunctions()
+{
+ createTimers();
+ startMatch();
+
+ // <Get FRU Inventory Area Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetFruInventoryAreaInfo,
+ ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
+ // <READ FRU Data>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReadFruData, ipmi::Privilege::User,
+ ipmiStorageReadFruData);
+
+ // <WRITE FRU Data>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdWriteFruData,
+ ipmi::Privilege::Operator, ipmiStorageWriteFruData);
+
+ // <Get SEL Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+ ipmiStorageGetSELInfo);
+
+ // <Get SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
+ ipmiStorageGetSELEntry);
+
+ // <Add SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdAddSelEntry,
+ ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
+
+ // <Clear SEL>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
+ ipmiStorageClearSEL);
+}
+} // namespace storage
+} // namespace ipmi
diff --git a/dcmihandler.cpp b/dcmihandler.cpp
new file mode 100644
index 0000000..769081a
--- /dev/null
+++ b/dcmihandler.cpp
@@ -0,0 +1,1245 @@
+#include "config.h"
+
+#include "dcmihandler.hpp"
+
+#include "user_channel/channel_layer.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
+
+#include <bitset>
+#include <cmath>
+#include <fstream>
+#include <variant>
+
+using namespace phosphor::logging;
+using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface;
+
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+void registerNetFnDcmiFunctions() __attribute__((constructor));
+
+constexpr auto pcapPath = "/xyz/openbmc_project/control/host0/power_cap";
+constexpr auto pcapInterface = "xyz.openbmc_project.Control.Power.Cap";
+
+constexpr auto powerCapProp = "PowerCap";
+constexpr auto powerCapEnableProp = "PowerCapEnable";
+
+using namespace phosphor::logging;
+
+namespace dcmi
+{
+constexpr auto assetTagMaxOffset = 62;
+constexpr auto assetTagMaxSize = 63;
+constexpr auto maxBytes = 16;
+constexpr size_t maxCtrlIdStrLen = 63;
+
+constexpr uint8_t parameterRevision = 2;
+constexpr uint8_t specMajorVersion = 1;
+constexpr uint8_t specMinorVersion = 5;
+constexpr auto sensorValueIntf = "xyz.openbmc_project.Sensor.Value";
+constexpr auto sensorValueProp = "Value";
+constexpr uint8_t configParameterRevision = 1;
+constexpr auto option12Mask = 0x01;
+constexpr auto activateDhcpReply = 0x00;
+constexpr uint8_t dhcpTiming1 = 0x04; // 4 sec
+constexpr uint16_t dhcpTiming2 = 0x78; // 120 sec
+constexpr uint16_t dhcpTiming3 = 0x40; // 60 sec
+// When DHCP Option 12 is enabled the string "SendHostName=true" will be
+// added into n/w configuration file and the parameter
+// SendHostNameEnabled will set to true.
+constexpr auto dhcpOpt12Enabled = "SendHostNameEnabled";
+
+enum class DCMIConfigParameters : uint8_t
+{
+ ActivateDHCP = 1,
+ DiscoveryConfig,
+ DHCPTiming1,
+ DHCPTiming2,
+ DHCPTiming3,
+};
+
+// Refer Table 6-14, DCMI Entity ID Extension, DCMI v1.5 spec
+static const std::map<uint8_t, std::string> entityIdToName{
+ {0x40, "inlet"}, {0x37, "inlet"}, {0x41, "cpu"},
+ {0x03, "cpu"}, {0x42, "baseboard"}, {0x07, "baseboard"}};
+
+nlohmann::json parseJSONConfig(const std::string& configFile)
+{
+ std::ifstream jsonFile(configFile);
+ if (!jsonFile.is_open())
+ {
+ lg2::error("Temperature readings JSON file not found");
+ elog<InternalFailure>();
+ }
+
+ auto data = nlohmann::json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("Temperature readings JSON parser failure");
+ elog<InternalFailure>();
+ }
+
+ return data;
+}
+
+bool isDCMIPowerMgmtSupported()
+{
+ static bool parsed = false;
+ static bool supported = false;
+ if (!parsed)
+ {
+ auto data = parseJSONConfig(gDCMICapabilitiesConfig);
+
+ supported = (gDCMIPowerMgmtSupported ==
+ data.value(gDCMIPowerMgmtCapability, 0));
+ }
+ return supported;
+}
+
+std::optional<uint32_t> getPcap(ipmi::Context::ptr& ctx)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, pcapInterface, pcapPath, service);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+ uint32_t pcap{};
+ ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
+ powerCapProp, pcap);
+ if (ec.value())
+ {
+ lg2::error("Error in getPcap prop: {ERROR}", "ERROR", ec.message());
+ elog<InternalFailure>();
+ return std::nullopt;
+ }
+ return pcap;
+}
+
+std::optional<bool> getPcapEnabled(ipmi::Context::ptr& ctx)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, pcapInterface, pcapPath, service);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+ bool pcapEnabled{};
+ ec = ipmi::getDbusProperty(ctx, service, pcapPath, pcapInterface,
+ powerCapEnableProp, pcapEnabled);
+ if (ec.value())
+ {
+ lg2::error("Error in getPcap prop");
+ elog<InternalFailure>();
+ return std::nullopt;
+ }
+ return pcapEnabled;
+}
+
+bool setPcap(ipmi::Context::ptr& ctx, const uint32_t powerCap)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, pcapInterface, pcapPath, service);
+ if (ec.value())
+ {
+ return false;
+ }
+
+ ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
+ powerCapProp, powerCap);
+ if (ec.value())
+ {
+ lg2::error("Error in setPcap property: {ERROR}", "ERROR", ec.message());
+ elog<InternalFailure>();
+ return false;
+ }
+ return true;
+}
+
+bool setPcapEnable(ipmi::Context::ptr& ctx, bool enabled)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, pcapInterface, pcapPath, service);
+ if (ec.value())
+ {
+ return false;
+ }
+
+ ec = ipmi::setDbusProperty(ctx, service, pcapPath, pcapInterface,
+ powerCapEnableProp, enabled);
+ if (ec.value())
+ {
+ lg2::error("Error in setPcapEnabled property: {ERROR}", "ERROR",
+ ec.message());
+ elog<InternalFailure>();
+ return false;
+ }
+ return true;
+}
+
+std::optional<std::string> readAssetTag(ipmi::Context::ptr& ctx)
+{
+ // Read the object tree with the inventory root to figure out the object
+ // that has implemented the Asset tag interface.
+ ipmi::DbusObjectInfo objectInfo;
+ boost::system::error_code ec = getDbusObject(
+ ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+
+ std::string assetTag{};
+ ec =
+ ipmi::getDbusProperty(ctx, objectInfo.second, objectInfo.first,
+ dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
+ if (ec.value())
+ {
+ lg2::error("Error in reading asset tag: {ERROR}", "ERROR",
+ ec.message());
+ elog<InternalFailure>();
+ return std::nullopt;
+ }
+
+ return assetTag;
+}
+
+bool writeAssetTag(ipmi::Context::ptr& ctx, const std::string& assetTag)
+{
+ // Read the object tree with the inventory root to figure out the object
+ // that has implemented the Asset tag interface.
+ ipmi::DbusObjectInfo objectInfo;
+ boost::system::error_code ec = getDbusObject(
+ ctx, dcmi::assetTagIntf, ipmi::sensor::inventoryRoot, "", objectInfo);
+ if (ec.value())
+ {
+ return false;
+ }
+
+ ec =
+ ipmi::setDbusProperty(ctx, objectInfo.second, objectInfo.first,
+ dcmi::assetTagIntf, dcmi::assetTagProp, assetTag);
+ if (ec.value())
+ {
+ lg2::error("Error in writing asset tag: {ERROR}", "ERROR",
+ ec.message());
+ elog<InternalFailure>();
+ return false;
+ }
+ return true;
+}
+
+std::optional<std::string> getHostName(ipmi::Context::ptr& ctx)
+{
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, networkConfigIntf, networkConfigObj, service);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+ std::string hostname{};
+ ec = ipmi::getDbusProperty(ctx, service, networkConfigObj,
+ networkConfigIntf, hostNameProp, hostname);
+ if (ec.value())
+ {
+ lg2::error("Error fetching hostname");
+ elog<InternalFailure>();
+ return std::nullopt;
+ }
+ return hostname;
+}
+
+std::optional<EthernetInterface::DHCPConf> getDHCPEnabled(
+ ipmi::Context::ptr& ctx)
+{
+ auto ethdevice = ipmi::getChannelName(ethernetDefaultChannelNum);
+ ipmi::DbusObjectInfo ethernetObj{};
+ boost::system::error_code ec = ipmi::getDbusObject(
+ ctx, ethernetIntf, networkRoot, ethdevice, ethernetObj);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+ std::string service{};
+ ec = ipmi::getService(ctx, ethernetIntf, ethernetObj.first, service);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+ std::string dhcpVal{};
+ ec = ipmi::getDbusProperty(ctx, service, ethernetObj.first, ethernetIntf,
+ "DHCPEnabled", dhcpVal);
+ if (ec.value())
+ {
+ return std::nullopt;
+ }
+
+ return EthernetInterface::convertDHCPConfFromString(dhcpVal);
+}
+
+std::optional<bool> getDHCPOption(ipmi::Context::ptr& ctx,
+ const std::string& prop)
+{
+ ipmi::ObjectTree objectTree;
+ if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
+ {
+ return std::nullopt;
+ }
+
+ for (const auto& [path, serviceMap] : objectTree)
+ {
+ for (const auto& [service, object] : serviceMap)
+ {
+ bool value{};
+ if (ipmi::getDbusProperty(ctx, service, path, dhcpIntf, prop,
+ value))
+ {
+ return std::nullopt;
+ }
+
+ if (value)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool setDHCPOption(ipmi::Context::ptr& ctx, std::string prop, bool value)
+{
+ ipmi::ObjectTree objectTree;
+ if (ipmi::getAllDbusObjects(ctx, networkRoot, dhcpIntf, objectTree))
+ {
+ return false;
+ }
+
+ for (const auto& [path, serviceMap] : objectTree)
+ {
+ for (const auto& [service, object] : serviceMap)
+ {
+ if (ipmi::setDbusProperty(ctx, service, path, dhcpIntf, prop,
+ value))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace dcmi
+
+constexpr uint8_t exceptionPowerOff = 0x01;
+ipmi::RspType<uint16_t, // reserved
+ uint8_t, // exception actions
+ uint16_t, // power limit requested in watts
+ uint32_t, // correction time in milliseconds
+ uint16_t, // reserved
+ uint16_t // statistics sampling period in seconds
+ >
+ getPowerLimit(ipmi::Context::ptr ctx, uint16_t reserved)
+{
+ if (!dcmi::isDCMIPowerMgmtSupported())
+ {
+ return ipmi::responseInvalidCommand();
+ }
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::optional<uint16_t> pcapValue = dcmi::getPcap(ctx);
+ std::optional<bool> pcapEnable = dcmi::getPcapEnabled(ctx);
+ if (!pcapValue || !pcapEnable)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ constexpr uint16_t reserved1{};
+ constexpr uint16_t reserved2{};
+ /*
+ * Exception action if power limit is exceeded and cannot be controlled
+ * with the correction time limit is hardcoded to Hard Power Off system
+ * and log event to SEL.
+ */
+ constexpr uint8_t exception = exceptionPowerOff;
+ /*
+ * Correction time limit and Statistics sampling period is currently not
+ * populated.
+ */
+ constexpr uint32_t correctionTime{};
+ constexpr uint16_t statsPeriod{};
+ if (*pcapEnable == false)
+ {
+ constexpr ipmi::Cc responseNoPowerLimitSet = 0x80;
+ return ipmi::response(responseNoPowerLimitSet, reserved1, exception,
+ *pcapValue, correctionTime, reserved2,
+ statsPeriod);
+ }
+ return ipmi::responseSuccess(reserved1, exception, *pcapValue,
+ correctionTime, reserved2, statsPeriod);
+}
+
+ipmi::RspType<> setPowerLimit(ipmi::Context::ptr& ctx, uint16_t reserved1,
+ uint8_t reserved2, uint8_t exceptionAction,
+ uint16_t powerLimit, uint32_t correctionTime,
+ uint16_t reserved3, uint16_t statsPeriod)
+{
+ if (!dcmi::isDCMIPowerMgmtSupported())
+ {
+ lg2::error("DCMI Power management is unsupported!");
+ return ipmi::responseInvalidCommand();
+ }
+
+ // Only process the power limit requested in watts. Return errors
+ // for other fields that are set
+ if (reserved1 || reserved2 || reserved3 || correctionTime || statsPeriod ||
+ exceptionAction != exceptionPowerOff)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (!dcmi::setPcap(ctx, powerLimit))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ lg2::info("Set Power Cap: {POWERCAP}", "POWERCAP", powerLimit);
+
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<> applyPowerLimit(ipmi::Context::ptr& ctx, bool enabled,
+ uint7_t reserved1, uint16_t reserved2)
+{
+ if (!dcmi::isDCMIPowerMgmtSupported())
+ {
+ lg2::error("DCMI Power management is unsupported!");
+ return ipmi::responseInvalidCommand();
+ }
+ if (reserved1 || reserved2)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (!dcmi::setPcapEnable(ctx, enabled))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ lg2::info("Set Power Cap Enable: {POWERCAPENABLE}", "POWERCAPENABLE",
+ enabled);
+
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t, // total tag length
+ std::vector<char> // tag data
+ >
+ getAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
+{
+ // Verify offset to read and number of bytes to read are not exceeding
+ // the range.
+ if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
+ ((offset + count) > dcmi::assetTagMaxSize))
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
+ if (!assetTagResp)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string& assetTag = assetTagResp.value();
+ // If the asset tag is longer than 63 bytes, restrict it to 63 bytes to
+ // suit Get Asset Tag command.
+ if (assetTag.size() > dcmi::assetTagMaxSize)
+ {
+ assetTag.resize(dcmi::assetTagMaxSize);
+ }
+
+ if (offset >= assetTag.size())
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // silently truncate reads beyond the end of assetTag
+ if ((offset + count) >= assetTag.size())
+ {
+ count = assetTag.size() - offset;
+ }
+
+ auto totalTagSize = static_cast<uint8_t>(assetTag.size());
+ std::vector<char> data{assetTag.begin() + offset,
+ assetTag.begin() + offset + count};
+
+ return ipmi::responseSuccess(totalTagSize, data);
+}
+
+ipmi::RspType<uint8_t // new asset tag length
+ >
+ setAssetTag(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count,
+ const std::vector<char>& data)
+{
+ // Verify offset to read and number of bytes to read are not exceeding
+ // the range.
+ if ((offset > dcmi::assetTagMaxOffset) || (count > dcmi::maxBytes) ||
+ ((offset + count) > dcmi::assetTagMaxSize))
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+ if (data.size() != count)
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+
+ std::optional<std::string> assetTagResp = dcmi::readAssetTag(ctx);
+ if (!assetTagResp)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string& assetTag = assetTagResp.value();
+
+ if (offset > assetTag.size())
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // operation is to truncate at offset and append new data
+ assetTag.resize(offset);
+ assetTag.append(data.begin(), data.end());
+
+ if (!dcmi::writeAssetTag(ctx, assetTag))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ auto totalTagSize = static_cast<uint8_t>(assetTag.size());
+ return ipmi::responseSuccess(totalTagSize);
+}
+
+ipmi::RspType<uint8_t, // length
+ std::vector<char> // data
+ >
+ getMgmntCtrlIdStr(ipmi::Context::ptr& ctx, uint8_t offset, uint8_t count)
+{
+ if (count > dcmi::maxBytes || offset + count > dcmi::maxCtrlIdStrLen)
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
+ if (!hostnameResp)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ std::string& hostname = hostnameResp.value();
+ // If the id string is longer than 63 bytes, restrict it to 63 bytes to
+ // suit set management ctrl str command.
+ if (hostname.size() > dcmi::maxCtrlIdStrLen)
+ {
+ hostname.resize(dcmi::maxCtrlIdStrLen);
+ }
+
+ if (offset >= hostname.size())
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // silently truncate reads beyond the end of hostname
+ if ((offset + count) >= hostname.size())
+ {
+ count = hostname.size() - offset;
+ }
+
+ auto nameSize = static_cast<uint8_t>(hostname.size());
+ std::vector<char> data{hostname.begin() + offset,
+ hostname.begin() + offset + count};
+
+ return ipmi::responseSuccess(nameSize, data);
+}
+
+ipmi::RspType<uint8_t> setMgmntCtrlIdStr(ipmi::Context::ptr& ctx,
+ uint8_t offset, uint8_t count,
+ std::vector<char> data)
+{
+ if ((offset > dcmi::maxCtrlIdStrLen) || (count > dcmi::maxBytes) ||
+ ((offset + count) > dcmi::maxCtrlIdStrLen))
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+ if (data.size() != count)
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ bool terminalWrite{data.back() == '\0'};
+ if (terminalWrite)
+ {
+ // remove the null termination from the data (no need with std::string)
+ data.resize(count - 1);
+ }
+
+ static std::string hostname{};
+ // read in the current value if not starting at offset 0
+ if (hostname.size() == 0 && offset != 0)
+ {
+ /* read old ctrlIdStr */
+ std::optional<std::string> hostnameResp = dcmi::getHostName(ctx);
+ if (!hostnameResp)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ hostname = hostnameResp.value();
+ hostname.resize(offset);
+ }
+
+ // operation is to truncate at offset and append new data
+ hostname.append(data.begin(), data.end());
+
+ // do the update if this is the last write
+ if (terminalWrite)
+ {
+ boost::system::error_code ec = ipmi::setDbusProperty(
+ ctx, dcmi::networkServiceName, dcmi::networkConfigObj,
+ dcmi::networkConfigIntf, dcmi::hostNameProp, hostname);
+ hostname.clear();
+ if (ec.value())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ }
+
+ auto totalIdSize = static_cast<uint8_t>(offset + count);
+ return ipmi::responseSuccess(totalIdSize);
+}
+
+ipmi::RspType<ipmi::message::Payload> getDCMICapabilities(uint8_t parameter)
+{
+ std::ifstream dcmiCapFile(dcmi::gDCMICapabilitiesConfig);
+ if (!dcmiCapFile.is_open())
+ {
+ lg2::error("DCMI Capabilities file not found");
+ return ipmi::responseUnspecifiedError();
+ }
+
+ auto data = nlohmann::json::parse(dcmiCapFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("DCMI Capabilities JSON parser failure");
+ return ipmi::responseUnspecifiedError();
+ }
+
+ constexpr bool reserved1{};
+ constexpr uint5_t reserved5{};
+ constexpr uint7_t reserved7{};
+ constexpr uint8_t reserved8{};
+ constexpr uint16_t reserved16{};
+
+ ipmi::message::Payload payload;
+ payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
+ dcmi::parameterRevision);
+
+ enum class DCMICapParameters : uint8_t
+ {
+ SupportedDcmiCaps = 0x01, // Supported DCMI Capabilities
+ MandatoryPlatAttributes = 0x02, // Mandatory Platform Attributes
+ OptionalPlatAttributes = 0x03, // Optional Platform Attributes
+ ManageabilityAccessAttributes = 0x04, // Manageability Access Attributes
+ };
+
+ switch (static_cast<DCMICapParameters>(parameter))
+ {
+ case DCMICapParameters::SupportedDcmiCaps:
+ {
+ bool powerManagement = data.value("PowerManagement", 0);
+ bool oobSecondaryLan = data.value("OOBSecondaryLan", 0);
+ bool serialTMode = data.value("SerialTMODE", 0);
+ bool inBandSystemInterfaceChannel =
+ data.value("InBandSystemInterfaceChannel", 0);
+ payload.pack(reserved8, powerManagement, reserved7,
+ inBandSystemInterfaceChannel, serialTMode,
+ oobSecondaryLan, reserved5);
+ break;
+ }
+ // Mandatory Platform Attributes
+ case DCMICapParameters::MandatoryPlatAttributes:
+ {
+ bool selAutoRollOver = data.value("SELAutoRollOver", 0);
+ bool flushEntireSELUponRollOver =
+ data.value("FlushEntireSELUponRollOver", 0);
+ bool recordLevelSELFlushUponRollOver =
+ data.value("RecordLevelSELFlushUponRollOver", 0);
+ uint12_t numberOfSELEntries =
+ data.value("NumberOfSELEntries", 0xcac);
+ uint8_t tempMonitoringSamplingFreq =
+ data.value("TempMonitoringSamplingFreq", 0);
+ payload.pack(numberOfSELEntries, reserved1,
+ recordLevelSELFlushUponRollOver,
+ flushEntireSELUponRollOver, selAutoRollOver,
+ reserved16, tempMonitoringSamplingFreq);
+ break;
+ }
+ // Optional Platform Attributes
+ case DCMICapParameters::OptionalPlatAttributes:
+ {
+ uint7_t powerMgmtDeviceTargetAddress =
+ data.value("PowerMgmtDeviceSlaveAddress", 0);
+ uint4_t bmcChannelNumber = data.value("BMCChannelNumber", 0);
+ uint4_t deviceRivision = data.value("DeviceRivision", 0);
+ payload.pack(powerMgmtDeviceTargetAddress, reserved1,
+ deviceRivision, bmcChannelNumber);
+ break;
+ }
+ // Manageability Access Attributes
+ case DCMICapParameters::ManageabilityAccessAttributes:
+ {
+ uint8_t mandatoryPrimaryLanOOBSupport =
+ data.value("MandatoryPrimaryLanOOBSupport", 0xff);
+ uint8_t optionalSecondaryLanOOBSupport =
+ data.value("OptionalSecondaryLanOOBSupport", 0xff);
+ uint8_t optionalSerialOOBMTMODECapability =
+ data.value("OptionalSerialOOBMTMODECapability", 0xff);
+ payload.pack(mandatoryPrimaryLanOOBSupport,
+ optionalSecondaryLanOOBSupport,
+ optionalSerialOOBMTMODECapability);
+ break;
+ }
+ default:
+ {
+ lg2::error("Invalid input parameter");
+ return ipmi::responseInvalidFieldRequest();
+ }
+ }
+
+ return ipmi::responseSuccess(payload);
+}
+
+namespace dcmi
+{
+namespace temp_readings
+{
+
+std::tuple<bool, bool, uint8_t> readTemp(ipmi::Context::ptr& ctx,
+ const std::string& dbusService,
+ const std::string& dbusPath)
+{
+ // Read the temperature value from d-bus object. Need some conversion.
+ // As per the interface xyz.openbmc_project.Sensor.Value, the
+ // temperature is an double and in degrees C. It needs to be scaled by
+ // using the formula Value * 10^Scale. The ipmi spec has the temperature
+ // as a uint8_t, with a separate single bit for the sign.
+
+ ipmi::PropertyMap result{};
+ boost::system::error_code ec = ipmi::getAllDbusProperties(
+ ctx, dbusService, dbusPath, "xyz.openbmc_project.Sensor.Value", result);
+ if (ec.value())
+ {
+ return std::make_tuple(false, false, 0);
+ }
+ auto temperature =
+ std::visit(ipmi::VariantToDoubleVisitor(), result.at("Value"));
+ double absTemp = std::abs(temperature);
+
+ auto findFactor = result.find("Scale");
+ double factor = 0.0;
+ if (findFactor != result.end())
+ {
+ factor = std::visit(ipmi::VariantToDoubleVisitor(), findFactor->second);
+ }
+ double scale = std::pow(10, factor);
+
+ auto tempDegrees = absTemp * scale;
+ // Max absolute temp as per ipmi spec is 127.
+ constexpr auto maxTemp = 127;
+ if (tempDegrees > maxTemp)
+ {
+ tempDegrees = maxTemp;
+ }
+
+ return std::make_tuple(true, (temperature < 0),
+ static_cast<uint8_t>(tempDegrees));
+}
+
+std::tuple<std::vector<std::tuple<uint7_t, bool, uint8_t>>, uint8_t> read(
+ ipmi::Context::ptr& ctx, const std::string& type, uint8_t instance,
+ size_t count)
+{
+ std::vector<std::tuple<uint7_t, bool, uint8_t>> response{};
+
+ auto data = parseJSONConfig(gDCMISensorsConfig);
+ static const std::vector<nlohmann::json> empty{};
+ std::vector<nlohmann::json> readings = data.value(type, empty);
+ for (const auto& j : readings)
+ {
+ // Max of 8 response data sets
+ if (response.size() == count)
+ {
+ break;
+ }
+
+ uint8_t instanceNum = j.value("instance", 0);
+ // Not in the instance range we're interested in
+ if (instanceNum < instance)
+ {
+ continue;
+ }
+
+ std::string path = j.value("dbus", "");
+ std::string service{};
+ boost::system::error_code ec = ipmi::getService(
+ ctx, "xyz.openbmc_project.Sensor.Value", path, service);
+ if (ec.value())
+ {
+ // not found on dbus
+ continue;
+ }
+
+ const auto& [ok, sign, temp] = readTemp(ctx, service, path);
+ if (ok)
+ {
+ response.emplace_back(uint7_t{temp}, sign, instanceNum);
+ }
+ }
+
+ auto totalInstances =
+ static_cast<uint8_t>(std::min(readings.size(), maxInstances));
+ return std::make_tuple(response, totalInstances);
+}
+
+} // namespace temp_readings
+} // namespace dcmi
+
+ipmi::RspType<uint8_t, // total instances for entity id
+ uint8_t, // number of instances in this reply
+ std::vector< // zero or more of the following two bytes
+ std::tuple<uint7_t, // temperature value
+ bool, // sign bit
+ uint8_t // entity instance
+ >>>
+ getTempReadings(ipmi::Context::ptr& ctx, uint8_t sensorType,
+ uint8_t entityId, uint8_t entityInstance,
+ uint8_t instanceStart)
+{
+ auto it = dcmi::entityIdToName.find(entityId);
+ if (it == dcmi::entityIdToName.end())
+ {
+ lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (sensorType != dcmi::temperatureSensorType)
+ {
+ lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+ sensorType);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
+
+ // Read requested instances
+ const auto& [temps, totalInstances] = dcmi::temp_readings::read(
+ ctx, it->second, instanceStart, requestedRecords);
+
+ auto numInstances = static_cast<uint8_t>(temps.size());
+
+ return ipmi::responseSuccess(totalInstances, numInstances, temps);
+}
+
+ipmi::RspType<> setDCMIConfParams(ipmi::Context::ptr& ctx, uint8_t parameter,
+ uint8_t setSelector,
+ ipmi::message::Payload& payload)
+{
+ if (setSelector)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ // Take action based on the Parameter Selector
+ switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
+ {
+ case dcmi::DCMIConfigParameters::ActivateDHCP:
+ {
+ uint7_t reserved{};
+ bool activate{};
+ if (payload.unpack(activate, reserved) || !payload.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ std::optional<EthernetInterface::DHCPConf> dhcpEnabled =
+ dcmi::getDHCPEnabled(ctx);
+ if (!dhcpEnabled)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ if (activate &&
+ (dhcpEnabled.value() != EthernetInterface::DHCPConf::none))
+ {
+ // When these conditions are met we have to trigger DHCP
+ // protocol restart using the latest parameter settings,
+ // but as per n/w manager design, each time when we
+ // update n/w parameters, n/w service is restarted. So
+ // we no need to take any action in this case.
+ }
+ break;
+ }
+ case dcmi::DCMIConfigParameters::DiscoveryConfig:
+ {
+ bool option12{};
+ uint6_t reserved1{};
+ bool randBackOff{};
+ if (payload.unpack(option12, reserved1, randBackOff) ||
+ !payload.fullyUnpacked())
+ {
+ return ipmi::responseReqDataLenInvalid();
+ }
+ // Systemd-networkd doesn't support Random Back off
+ if (reserved1 || randBackOff)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ dcmi::setDHCPOption(ctx, dcmi::dhcpOpt12Enabled, option12);
+ break;
+ }
+ // Systemd-networkd doesn't allow to configure DHCP timigs
+ case dcmi::DCMIConfigParameters::DHCPTiming1:
+ case dcmi::DCMIConfigParameters::DHCPTiming2:
+ case dcmi::DCMIConfigParameters::DHCPTiming3:
+ default:
+ return ipmi::responseInvalidFieldRequest();
+ }
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<ipmi::message::Payload> getDCMIConfParams(
+ ipmi::Context::ptr& ctx, uint8_t parameter, uint8_t setSelector)
+{
+ if (setSelector)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ ipmi::message::Payload payload;
+ payload.pack(dcmi::specMajorVersion, dcmi::specMinorVersion,
+ dcmi::configParameterRevision);
+
+ // Take action based on the Parameter Selector
+ switch (static_cast<dcmi::DCMIConfigParameters>(parameter))
+ {
+ case dcmi::DCMIConfigParameters::ActivateDHCP:
+ payload.pack(dcmi::activateDhcpReply);
+ break;
+ case dcmi::DCMIConfigParameters::DiscoveryConfig:
+ {
+ uint8_t discovery{};
+ std::optional<bool> enabled =
+ dcmi::getDHCPOption(ctx, dcmi::dhcpOpt12Enabled);
+ if (!enabled.has_value())
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ if (enabled.value())
+ {
+ discovery = dcmi::option12Mask;
+ }
+ payload.pack(discovery);
+ break;
+ }
+ // Get below values from Systemd-networkd source code
+ case dcmi::DCMIConfigParameters::DHCPTiming1:
+ payload.pack(dcmi::dhcpTiming1);
+ break;
+ case dcmi::DCMIConfigParameters::DHCPTiming2:
+ payload.pack(dcmi::dhcpTiming2);
+ break;
+ case dcmi::DCMIConfigParameters::DHCPTiming3:
+ payload.pack(dcmi::dhcpTiming3);
+ break;
+ default:
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ return ipmi::responseSuccess(payload);
+}
+
+static std::optional<uint16_t> readPower(ipmi::Context::ptr& ctx)
+{
+ std::ifstream sensorFile(POWER_READING_SENSOR);
+ std::string objectPath;
+ if (!sensorFile.is_open())
+ {
+ lg2::error(
+ "Power reading configuration file not found: {POWER_SENSOR_FILE}",
+ "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+ return std::nullopt;
+ }
+
+ auto data = nlohmann::json::parse(sensorFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("Error in parsing configuration file: {POWER_SENSOR_FILE}",
+ "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+ return std::nullopt;
+ }
+
+ objectPath = data.value("path", "");
+ if (objectPath.empty())
+ {
+ lg2::error(
+ "Power sensor D-Bus object path is empty: {POWER_SENSOR_FILE}",
+ "POWER_SENSOR_FILE", std::string_view{POWER_READING_SENSOR});
+ return std::nullopt;
+ }
+
+ // Return default value if failed to read from D-Bus object
+ std::string service{};
+ boost::system::error_code ec =
+ ipmi::getService(ctx, dcmi::sensorValueIntf, objectPath, service);
+ if (ec.value())
+ {
+ lg2::error("Failed to fetch service for D-Bus object, "
+ "object path: {OBJECT_PATH}, interface: {INTERFACE}",
+ "OBJECT_PATH", objectPath, "INTERFACE",
+ dcmi::sensorValueIntf);
+ return std::nullopt;
+ }
+
+ // Read the sensor value and scale properties
+ double value{};
+ ec = ipmi::getDbusProperty(ctx, service, objectPath, dcmi::sensorValueIntf,
+ dcmi::sensorValueProp, value);
+ if (ec.value())
+ {
+ lg2::error("Failed to read power value from D-Bus object, "
+ "object path: {OBJECT_PATH}, interface: {INTERFACE}",
+ "OBJECT_PATH", objectPath, "INTERFACE",
+ dcmi::sensorValueIntf);
+ return std::nullopt;
+ }
+ auto power = static_cast<uint16_t>(value);
+ return power;
+}
+
+ipmi::RspType<uint16_t, // current power
+ uint16_t, // minimum power
+ uint16_t, // maximum power
+ uint16_t, // average power
+ uint32_t, // timestamp
+ uint32_t, // sample period ms
+ uint6_t, // reserved
+ bool, // power measurement active
+ bool // reserved
+ >
+ getPowerReading(ipmi::Context::ptr& ctx, uint8_t mode, uint8_t attributes,
+ uint8_t reserved)
+{
+ if (!dcmi::isDCMIPowerMgmtSupported())
+ {
+ lg2::error("DCMI Power management is unsupported!");
+ return ipmi::responseInvalidCommand();
+ }
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ enum class PowerMode : uint8_t
+ {
+ SystemPowerStatistics = 1,
+ EnhancedSystemPowerStatistics = 2,
+ };
+
+ if (static_cast<PowerMode>(mode) != PowerMode::SystemPowerStatistics)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (attributes)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ std::optional<uint16_t> powerResp = readPower(ctx);
+ if (!powerResp)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ auto& power = powerResp.value();
+
+ // TODO: openbmc/openbmc#2819
+ // Minimum, Maximum, Average power, TimeFrame, TimeStamp,
+ // PowerReadingState readings need to be populated
+ // after Telemetry changes.
+ constexpr uint32_t samplePeriod = 1;
+ constexpr uint6_t reserved1 = 0;
+ constexpr bool measurementActive = true;
+ constexpr bool reserved2 = false;
+ auto timestamp = static_cast<uint32_t>(time(nullptr));
+ return ipmi::responseSuccess(power, power, power, power, timestamp,
+ samplePeriod, reserved1, measurementActive,
+ reserved2);
+}
+
+namespace dcmi
+{
+namespace sensor_info
+{
+
+std::tuple<std::vector<uint16_t>, uint8_t> read(
+ const std::string& type, uint8_t instance, const nlohmann::json& config,
+ uint8_t count)
+{
+ std::vector<uint16_t> responses{};
+
+ static const std::vector<nlohmann::json> empty{};
+ std::vector<nlohmann::json> readings = config.value(type, empty);
+ uint8_t totalInstances = std::min(readings.size(), maxInstances);
+ for (const auto& reading : readings)
+ {
+ // limit to requested count
+ if (responses.size() == count)
+ {
+ break;
+ }
+
+ uint8_t instanceNum = reading.value("instance", 0);
+ // Not in the instance range we're interested in
+ if (instanceNum < instance)
+ {
+ continue;
+ }
+
+ uint16_t recordId = reading.value("record_id", 0);
+ responses.emplace_back(recordId);
+ }
+
+ return std::make_tuple(responses, totalInstances);
+}
+
+} // namespace sensor_info
+} // namespace dcmi
+
+ipmi::RspType<uint8_t, // total available instances
+ uint8_t, // number of records in this response
+ std::vector<uint16_t> // records
+ >
+ getSensorInfo(uint8_t sensorType, uint8_t entityId, uint8_t entityInstance,
+ uint8_t instanceStart)
+{
+ auto it = dcmi::entityIdToName.find(entityId);
+ if (it == dcmi::entityIdToName.end())
+ {
+ lg2::error("Unknown Entity ID: {ENTITY_ID}", "ENTITY_ID", entityId);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (sensorType != dcmi::temperatureSensorType)
+ {
+ lg2::error("Invalid sensor type: {SENSOR_TYPE}", "SENSOR_TYPE",
+ sensorType);
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ nlohmann::json config = dcmi::parseJSONConfig(dcmi::gDCMISensorsConfig);
+
+ uint8_t requestedRecords = (entityInstance == 0) ? dcmi::maxRecords : 1;
+ // Read requested instances
+ const auto& [sensors, totalInstances] = dcmi::sensor_info::read(
+ it->second, instanceStart, config, requestedRecords);
+ uint8_t numRecords = sensors.size();
+
+ return ipmi::responseSuccess(totalInstances, numRecords, sensors);
+}
+
+void registerNetFnDcmiFunctions()
+{
+ // <Get Power Limit>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetPowerLimit, ipmi::Privilege::User,
+ getPowerLimit);
+
+ // <Set Power Limit>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdSetPowerLimit,
+ ipmi::Privilege::Operator, setPowerLimit);
+
+ // <Activate/Deactivate Power Limit>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdActDeactivatePwrLimit,
+ ipmi::Privilege::Operator, applyPowerLimit);
+
+ // <Get Asset Tag>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetAssetTag, ipmi::Privilege::User,
+ getAssetTag);
+
+ // <Set Asset Tag>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdSetAssetTag, ipmi::Privilege::Operator,
+ setAssetTag);
+
+ // <Get Management Controller Identifier String>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetMgmtCntlrIdString,
+ ipmi::Privilege::User, getMgmntCtrlIdStr);
+
+ // <Set Management Controller Identifier String>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdSetMgmtCntlrIdString,
+ ipmi::Privilege::Admin, setMgmntCtrlIdStr);
+
+ // <Get DCMI capabilities>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetDcmiCapabilitiesInfo,
+ ipmi::Privilege::User, getDCMICapabilities);
+
+ // <Get Power Reading>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetPowerReading, ipmi::Privilege::User,
+ getPowerReading);
+
+// The Get sensor should get the senor details dynamically when
+// FEATURE_DYNAMIC_SENSORS is enabled.
+#ifndef FEATURE_DYNAMIC_SENSORS
+ // <Get Sensor Info>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetDcmiSensorInfo,
+ ipmi::Privilege::Operator, getSensorInfo);
+
+ // <Get Temperature Readings>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetTemperatureReadings,
+ ipmi::Privilege::User, getTempReadings);
+#endif
+ // <Get DCMI Configuration Parameters>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdGetDcmiConfigParameters,
+ ipmi::Privilege::User, getDCMIConfParams);
+
+ // <Set DCMI Configuration Parameters>
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ ipmi::dcmi::cmdSetDcmiConfigParameters,
+ ipmi::Privilege::Admin, setDCMIConfParams);
+
+ return;
+}
diff --git a/dcmihandler.hpp b/dcmihandler.hpp
new file mode 100644
index 0000000..8fa5e82
--- /dev/null
+++ b/dcmihandler.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "nlohmann/json.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace dcmi
+{
+
+static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+static constexpr auto assetTagIntf =
+ "xyz.openbmc_project.Inventory.Decorator.AssetTag";
+static constexpr auto assetTagProp = "AssetTag";
+static constexpr auto networkServiceName = "xyz.openbmc_project.Network";
+static constexpr auto networkConfigObj = "/xyz/openbmc_project/network/config";
+static constexpr auto networkConfigIntf =
+ "xyz.openbmc_project.Network.SystemConfiguration";
+static constexpr auto hostNameProp = "HostName";
+static constexpr auto temperatureSensorType = 0x01;
+static constexpr size_t maxInstances = 255;
+static constexpr uint8_t maxRecords = 8;
+static constexpr auto gDCMISensorsConfig =
+ "/usr/share/ipmi-providers/dcmi_sensors.json";
+static constexpr auto ethernetIntf =
+ "xyz.openbmc_project.Network.EthernetInterface";
+static constexpr auto ethernetDefaultChannelNum = 0x1;
+static constexpr auto networkRoot = "/xyz/openbmc_project/network";
+static constexpr auto dhcpIntf =
+ "xyz.openbmc_project.Network.DHCPConfiguration";
+static constexpr auto systemBusName = "org.freedesktop.systemd1";
+static constexpr auto systemPath = "/org/freedesktop/systemd1";
+static constexpr auto systemIntf = "org.freedesktop.systemd1.Manager";
+static constexpr auto gDCMICapabilitiesConfig =
+ "/usr/share/ipmi-providers/dcmi_cap.json";
+static constexpr auto gDCMIPowerMgmtCapability = "PowerManagement";
+static constexpr auto gDCMIPowerMgmtSupported = 0x1;
+static constexpr auto gMaxSELEntriesMask = 0xFFF;
+static constexpr auto gByteBitSize = 8;
+
+/** @brief Check whether DCMI power management is supported
+ * in the DCMI Capabilities config file.
+ *
+ * @return True if DCMI power management is supported
+ */
+bool isDCMIPowerMgmtSupported();
+
+} // namespace dcmi
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..139f025
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,58 @@
+# Configuration
+
+## Device ID Configuration
+
+There is a default dev_id.json file provided by
+meta-phosphor/common/recipes-phosphor/ipmi/phosphor-ipmi-host.bb
+
+Any target can override the default json file by providing a
+phosphor-ipmi-host.bbappend with an ODM or platform customizable configuration.
+
+For a specific example, see:
+[Witherspoon](https://github.com/openbmc/openbmc/blob/master/meta-openbmc-machines/meta-openpower/meta-ibm/meta-witherspoon/recipes-phosphor/ipmi/phosphor-ipmi-host.bbappend)
+
+The JSON format for get_device_id:
+
+ {"id": 0, "revision": 0, "addn_dev_support": 0,
+ "manuf_id": 0, "prod_id": 0, "aux": 0}
+
+Each value in this JSON object should be an integer. The file is placed in
+/usr/share/ipmi-providers/ by Yocto, and will be parsed upon the first call to
+get_device_id. The data is then cached for future use. If you change the data at
+runtime, simply restart the service to see the new data fetched by a call to
+get_device_id.
+
+## IPMI D-Bus Sensor Filtering
+
+Phosphor-ipmi-host provides a compile time option to control how IPMI sensors
+are populated. The default model is for the sensors to be culled from a set of
+JSON files. There is another model that collects the sensors to display from
+D-Bus. The D-Bus method is the default mode used by Redfish. Redfish does not
+have a fixed limit on the number of sensors that can be reported.
+
+IPMI supports a smaller number of sensors than are available via Redfish. The
+limit being the number of Sensor Data Records (SDR) supported in the IPMI
+specification. Enabling IPMI to use D-Bus may cause the number of sensors
+retrieved to exceed the number SDRs IPMI can support. Even if the number of
+sensors retrieved is within the SDR limit IPMI can support, it may be desirable
+to filter entries that are uninteresting.
+
+Meson uses the _dyanmic-sensors_ configuration option to enable retrieving the
+sensors via D-Bus. When dynamic sensors are active all of the sensors placed on
+D-Bus by a service are added to the IPMI sensor list. In the event that too many
+sensors are exposed on D-Bus, the list can be filtered by adding a list of
+services to filter to _/usr/share/ipmi-providers/sensor_filter.json_.
+
+Example filtering:
+
+```json
+{
+ "ServiceFilter": [
+ "xyz.openbmc_project.RedfishSensors1",
+ "xyz.openbmc_project.RedfishSensors2"
+ ]
+}
+```
+
+Any sensors published by the example services are blocked from being added to
+the IPMI SDR table.
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..8be4ecf
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,122 @@
+# Contributing Guidelines
+
+This document attempts to outline some basic rules to follow when contributing
+to OpenBMC's IPMI stack. It does _not_ outline coding style; we follow the
+[OpenBMC C++ style guide](https://github.com/openbmc/docs/blob/master/cpp-style-and-conventions)
+for that.
+
+## Organizing Commits
+
+A good commit does exactly one thing. We prefer many small, atomic commits to
+one large commit which makes many functional changes.
+
+- Too large: "convert foo to new API; also fix CVE 1234 in bar"
+- Too small: "move abc.h to top of include list" and "move xyz.h to bottom of
+ include list"
+- Just right: "convert foo to new API" and "convert foo from tab to space"
+
+Often, creating small commits this way results in a number of commits which are
+dependent on prior commits; Gerrit handles this situation well, so feel free to
+push commits which are based on your change still in review. However, when
+possible, your commit should stand alone on top of master - "Fix whitespace in
+bar()" does not need to depend on "refactor foo()". Said differently, ensure
+that topics which are not related to each other semantically are also not
+related to each other in Git until they are merged into master.
+
+When pushing a stack of patches (current branch is >1 commits ahead of
+origin/master), these commits will show up with that same relationship in
+gerrit. This means that each patch must be merged in order of that relationship.
+So if one of the patches in the middle needs to be changed, all the patches from
+that point on would need to be pushed to maintain the relationship. This will
+effectively rebase the unchanged patches, which would in turn trigger a new CI
+build. Ideally, changes from the entire patchset could be done all in one go to
+reduce unnecessary rebasing.
+
+When someone makes a comment on your commit in Gerrit, modify that commit and
+send it again to Gerrit. This typically involves `git rebase --interactive` or
+`git commit --amend`, for which there are many guides online. As mentioned in
+the paragraph above, when possible you should make changes to multiple patches
+in the same stack before you push, in order to minimize CI and notification
+churn from the rebase operations.
+
+Commits which include changes that can be tested by a unit test should also
+include a unit test to exercise that change, within the same commit. Unit tests
+should be clearly written - even moreso than production code, unit tests are
+meant primarily to be read by humans - and should test both good and bad
+behaviors. Refer to the
+[testing documentation](https://github.com/openbmc/phosphor-host-ipmid/blob/master/docs/testing.md)
+for help writing tests, as well as best practices.
+
+## Formatting Commit Messages
+
+Your commit message should explain:
+
+- Concisely, _what_ change are you making? e.g. "docs: convert from US to UK
+ spelling" This first line of your commit message is the subject line.
+- Comprehensively, _why_ are you making that change? In some cases, like a small
+ refactor, the why is fairly obvious. But in cases like the inclusion of a new
+ feature, you should explain why the feature is needed.
+- Concisely, _how_ did you test? This comes in the form of a "Tested:" footer in
+ your commit message and is required for all code changes in the IPMI stack. It
+ typically consists of copy-pasted ipmitool requests and responses. When
+ possible, use the high-level ipmitool commands (e.g. "ipmitool sensor read
+ 0x1"). In cases where that's not possible, or when testing edge or error
+ cases, it is acceptable to use "ipmitool raw" - but an explanation of your
+ output is appreciated. If the change can be validated entirely by running unit
+ tests, say so in the "Tested:" tag.
+
+Try to include the component you are changing at the front of your subject line;
+this typically comes in the form of the class, module, handler, or directory you
+are modifying. e.g. "apphandler: refactor foo to new API"
+
+Loosely, we try to follow the 50/72 rule for commit messages - that is, the
+subject line should not exceed 50 characters and the body should not exceed 72
+characters. This is common practice in many projects which use Git.
+
+All commit messages must include a Signed-off-by line, which indicates that you
+the contributor have agreed to the Developer Certificate of Origin. This line
+must include the name you commonly use, often a given name and a family name or
+surname. (ok: A. U. Thor, Sam Samuelsson, robert a. heinlein; not ok: xXthorXx,
+Sam, RAH)
+
+## Sending Patches
+
+Like most projects in OpenBMC, we use Gerrit to review patches. Please check the
+MAINTAINERS file to determine who needs to approve your review in order for your
+change to be merged. Submitters will need to manually add their reviewers in
+Gerrit; reviewers are not currently added automatically. Maintainers may not see
+the commit if they have not been added to the review!
+
+## Pace of Review
+
+Contributors who are used to code reviews by their team internal to their own
+company, or who are not used to code reviews at all, are sometimes surprised by
+the pace of code reviews in open source projects. Try to keep in mind that those
+reviewing your patch may be contributing to OpenBMC in a volunteer or
+partial-time capacity, may be in a timezone far removed from your own, and may
+have very deep review queues already of patches which have been waiting longer
+than yours.
+
+If you feel your patch has been missed entirely, of course it's alright to email
+the maintainers (addresses available in MAINTAINERS file) - but a reasonable
+timeframe to do so is on the order of a week, not on the order of hours.
+
+Additionally, the IPMI stack has a set of policies for when and how changes can
+be approved; please check the MAINTAINERS file for the full list ("Change
+approval rules").
+
+The maintainers' job is to ensure that incoming patches are as correct and easy
+to maintain as possible. Part of the nature of open source is attrition -
+contributors can come and go easily - so maintainers tend not to put stock in
+promises such as "I will add unit tests in a later patch" or "I will be
+implementing this proposal by the end of next month." This often manifests as
+reviews which may seem harsh or exacting; please keep in mind that the community
+is trying to collaborate with you to build a patch that will benefit the project
+on its own.
+
+## Code of Conduct
+
+We enthusiastically adhere to the same
+[Code of Conduct](https://github.com/openbmc/docs/blob/master/code-of-conduct.md)
+as the rest of OpenBMC. If you have any concerns, please check that document for
+guidelines on who can help you resolve them.
diff --git a/docs/ipmi-network-format.md b/docs/ipmi-network-format.md
new file mode 100644
index 0000000..140afd5
--- /dev/null
+++ b/docs/ipmi-network-format.md
@@ -0,0 +1,57 @@
+# IPMI network format
+
+On platforms running AMI BMC firmware (Habanero and Firestone at the moment),
+the 'Get/Set' System Boot Options' command has been extended to allow overrides
+to the network configuration to be specified. In Petitboot this will cause any
+existing network configuration to be overridden by the specified configuration.
+The format of each IPMI request is similar to that of the usual boot options
+request, with a slightly different payload.
+
+The start of the request is similar to a usual boot option override, but
+specifies the fields of interest differently, ie;
+
+Specify 'chassis bootdev', field 96, data1 0x00 0x08 0x61 0x80
+
+The rest of request format is defined by Petitboot as: - 4 byte cookie value
+(always 0x21 0x70 0x62 0x21) - 2 byte version value (always 0x00 0x01) - 1 byte
+hardware address size (eg. 0x06 for MAC address) - 1 byte IP address size (eg.
+0x04 for IPv4) - Hardware (MAC) address - 1 byte flags for 'ignore' and
+'method', where method = 0 is DHCP and method = 1 is Static. And for static
+configs: - IP Address - 1 byte subnet value - Gateway address
+
+Describing each field in more detail:
+
+Specify 'chassis bootdev', field 96, data1 0x00 0x08 0x61 0x80
+
+Set a special cookie that Petitboot will recognise: 0x21 0x70 0x62 0x21
+
+Specify the version (only 1 at the moment) 0x00 0x01
+
+Specify the size of the MAC address and IP address. This is used to
+differentiate between IPv4 and IPv6 addresses, or potential future support for
+Infiniband/etc. 0x06 0x04 (6-byte MAC address, IPv4 IP address)
+
+Set the hardware address of the interface you want to override, eg: 0xf4 0x52
+0x14 0xf3 0x01 0xdf
+
+Specify 'ignore' or 'static/dynamic' flags. The second byte specifies to use
+either DHCP or static IP configuration (0 for DHCP). 0x00 0x01
+
+The below fields are required if setting a static configuration:
+
+Set the IP address you want to use, eg: 0x0a 0x3d 0xa1 0x42
+
+Set the subnet mask (short notation), eg '16': 0x10
+
+Set the gateway address, eg: 0x0a 0x3d 0x2 0x1
+
+All together this should look like: 0x00 0x08 0x61 0x80 0x21 0x70 0x62 0x21 0x00
+0x01 0x06 0x04 0xf4 0x52 0x14 0xf3 0x01 0xdf 0x00 0x01 0x0a 0x3d 0xa1 0x42 0x10
+0x0a 0x3d 0x2 0x1
+
+To clear a network override, it is sufficient to clear out the request, or set a
+zero-cookie which Petitboot will reject. Eg: 0x00 0x08 0x61 0x80 0x00 0x00 0x00
+0x00
+
+You can 'Get' the override back with 0x00 0x09 0x61 0x80 0x00 which should
+return whatever is currently set.
diff --git a/docs/ipmitool-commands-cheatsheet.md b/docs/ipmitool-commands-cheatsheet.md
new file mode 100644
index 0000000..b35317f
--- /dev/null
+++ b/docs/ipmitool-commands-cheatsheet.md
@@ -0,0 +1,36 @@
+# IPMI command cheat sheet
+
+This document is intended to provide a set of IPMI commands for quick reference.
+
+Note: If the ipmitool is on the BMC then set the interface as "-I dbus" and if
+the ipmitool is outside the BMC (i.e on the network) then set the interface as
+"-I lanplus".
+
+## Network Configuration
+
+### Set the interface mode
+
+`ipmitool lan set <channel> ipsrc static`
+
+### Set the IP Address
+
+`ipmitool lan set <channel> ipaddr <x.x.x.x>`
+
+### Set the network mask
+
+`ipmitool lan set <channel> netmask <x.x.x.x>`
+
+### Set the default gateway
+
+`ipmitool lan set <channel> defgw ipaddr <x.x.x.x>`
+
+### Set the VLAN
+
+`ipmitool lan set <channel> vlan id <id>`
+
+### Delete the VLAN
+
+`ipmitool lan set <channel> vlan id off`
+
+NOTE: The user can group multiple set operations since the IPMI daemon waits for
+10 seconds after each set operation before applying the configuration.
diff --git a/docs/oem-extension-numbering.md b/docs/oem-extension-numbering.md
new file mode 100644
index 0000000..29daa3c
--- /dev/null
+++ b/docs/oem-extension-numbering.md
@@ -0,0 +1,170 @@
+# Sketch of OpenBMC OEM message formats
+
+This document describes OEM Requests to be supported using the OpenBMC OEM
+Number.
+
+## What's in the box?
+
+- Briefly recap OEM Extension layout as described in IPMI Specification.
+- Enumerate Command codes allocated for use with the OpenBMC OEM Number.
+- For each such code, describe the associated messages, including
+ representation, function, meaning, limits, and so on.
+
+## OEM Extensions, Block Transfer Transport Example
+
+This table and the next briefly recap OEM Extension messages as described in the
+[IPMI Specification](http://www.intel.com/content/www/us/en/servers/ipmi/ipmi-second-gen-interface-spec-v2-rev1-1.html).
+To keep it both simple and concrete, only BT Tansport is described. Please
+consult that specification for the full story.
+
+### OEM Request
+
+Per section 11.1 of IPMI Spec
+
+| Bytes | Bits | Spec ID | Value | Description |
+| :-----: | ---: | :------- | :---: | :---------------------------------------- |
+| 0 | | Length\* | - | Number of bytes to follow in this request |
+| 1 | 2:7 | NetFn | 0x2E | OEM Request |
+| 1 | 0:1 | LUN | - | Allow any LUN |
+| 2 | | Seq\* | - | Per section 11.3 of IPMI Spec |
+| 3 | | Cmd | - | Table 3 - OpenBMC Cmd Codes |
+| 4 ~ 6 | | OEN | 49871 | OEM Enterprise Number |
+| 2 ~ n+6 | | Data | - | n data bytes, encoding depends on Cmd |
+
+Notes:
+
+- Length and Seq are specific to BT transport - other transports may not have
+ them.
+
+- Serialize numbers larger than 1 byte LSB first - e.g., represent OEM
+ Enterprise Number 49871 as `{0xcf, 0xc2, 0x00}`
+
+### OEM Response
+
+Per section 11.2 of IPMI Spec
+
+| Bytes | Bits | Spec ID | Value | Description |
+| :-----: | ---: | :------- | :---: | :---------------------------------------------- |
+| 0 | | Length\* | - | Number of bytes to follow in this response |
+| 1 | 2:7 | NetFn | 0x2F | OEM Response |
+| 1 | 0:1 | LUN | - | LUN of request to which this is a response |
+| 2 | | Seq\* | - | Seq of request to which this is a response |
+| 3 | | Cmd | - | Cmd code of request to which this is a response |
+| 4 | | CC | - | Completion code, Section 5.2 of IPMI Spec v2.0 |
+| 5 ~ 7 | | OEN | 49871 | OEM Enterprise Number |
+| 8 ~ n+7 | | Data | - | n data bytes, encoding depends on Cmd |
+
+## OpenBMC OEM Cmd Codes
+
+```c
+/*
+ * This is the OpenBMC IANA OEM Number
+ */
+constexpr OemNumber obmcOemNumber = 49871;
+```
+
+These are the Command codes allocated for use with the OpenBMC OEM Number.
+
+| Cmd | Identifier | Description |
+| :-----: | :----------- | :------------------ |
+| 0 | - | Reserved |
+| 1 | gpioCmd | GPIO Access |
+| 2 | i2cCmd | I2C Device Access |
+| 3 | flashCmd | Flash Device Access |
+| 4 | fanManualCmd | Manual Fan Controls |
+| 5 ~ 255 | - | Unallocated |
+
+## I2C Device Access (Command 2)
+
+The next subsections describe command and response messages supporting the I2C
+Device Access extension OpenBMC OEM extension (command code 2).
+
+### I2C Request Message - Overall
+
+| Bytes | Bits | Identifier | Description |
+| :-----: | :--- | :------------ | :------------------------ |
+| 0 | | bus | Logical I2C bus. |
+| 1 | | xferFlags | Flags for all steps. |
+| | 7 | I2cFlagUsePec | 1 => use PEC - see note. |
+| | 6:0 | | Reserved(0) |
+| 2 ~ n-1 | | | Step sequence - see next. |
+
+Notes
+
+- Total length of step sequence must exactly fill request.
+
+- Intent is to handle
+ [Linux Kernel SMBus Protocol](https://www.kernel.org/doc/Documentation/i2c/smbus-protocol),
+ with com generalized to m byte sequence - e.g., at24c64 uses 2 address bytes,
+ and n bytes of received data, rather than specific operations for various
+ sizes.
+
+- Goal is to support SMBus v2 32-byte data block length limit; but easily
+ supports new 4 and 8 byte transfers added for
+ [SMBus v3](http://smbus.org/specs/SMBus_3_0_20141220.pdf).
+
+- PEC refers to SMBus Packet Error Check.
+
+- SMBus address resolution, alerts, and non-standard protocols not supported. So
+ for example, there is no way to insert a stop command within a transfer.
+
+- Depending on options, i2cdetect uses either quick write or 1 byte read;
+ default is 1-byte read for eeprom/spd memory ranges, else quick write.
+
+### I2C Request Message - Step Properties
+
+| Bytes | Bits | Identifier | Description |
+| :---: | :--: | :------------- | :-------------------------------------------- |
+| 0 | | devAndDir | |
+| | 7:1 | dev | 7-bit I2C device address. |
+| | 0 | isRead | 1 = read, 0 = write. |
+| 1 | | stepFlags | |
+| | 7 | i2cFlagRecvLen | 1 if block read, else regular; see table. |
+| | 6 | i2cFlagNoStart | 1 to suppress I2C start. |
+| | 5:0 | | Reserved(0) |
+| 2 | | p | Count parameter; see table |
+| 3:m+2 | | wrPayload | Nonempty iff p supplies nonzero m; see table. |
+
+#### Allowed step property combinations
+
+| is_read | RecvLen | p | Size | Description |
+| :-----: | :-----: | :-: | :--- | :-------------------------------------- |
+| 0 | 0 | 0 | 3 | Quick write 0; same as write m=0 bytes. |
+| 0 | 0 | m | m+3 | Consume and write next m bytes. |
+| 0 | 1 | - | 3 | Reserved. |
+| 1 | 0 | 0 | 3 | Quick write 1; same as read n=0 bytes. |
+| 1 | 0 | n | 3 | Read n bytes. |
+| 1 | 1 | - | 3 | Read count from device. |
+
+Notes
+
+- All other combinations are reserved.
+
+- RecvLen corresponds to Linux
+ [I2C_M_RECV_LEN message flags bit](http://elixir.free-electrons.com/linux/v4.10.17/source/include/uapi/linux/i2c.h#L78)
+
+- Transfers include byte count in SMBUS block modes, and PEC byte when selected.
+ Allows for use with SMBUS compatibility layer.
+
+- In case of write steps, wrPayload may include:
+ - register address byte or bytes;
+ - count byte if implementing SMBUS block or call operation;
+ - up to 32 bytes of logical payload; and
+ - PEC byte.
+
+### I2C Response Message
+
+| Bytes | Identifier | Size | Description |
+| :---: | :--------- | :----: | :----------------- |
+| 0:n-1 | rd_data | 0 ~ 34 | byte sequence read |
+
+Notes
+
+- Maximum logical payload is 32.
+
+- In the unlikely event a transfer specifies multiple read steps, all bytes read
+ are simply concatenated in the order read.
+
+- However, maximum reply limit is sized for the largest single read.
+
+- RecvLen case w/ PEC can return up to 34 bytes: count + payload + PEC
diff --git a/docs/testing.md b/docs/testing.md
new file mode 100644
index 0000000..9ee1dcb
--- /dev/null
+++ b/docs/testing.md
@@ -0,0 +1,473 @@
+# Running Tests
+
+## Setting Up Your Environment
+
+For the purposes of this tutorial, we'll be setting up an environment in Docker.
+Docker is handy because it's fairly portable, and won't interfere with the rest
+of your machine setup. It also offers a strong guarantee that you're testing the
+same way that others working on the project are. Finally, we can get away with
+using the same Docker image that's run by the OpenBMC continuous integration
+bot, so we have even more confidence that we're running relevant tests the way
+they'd be run upstream.
+
+### Install Docker
+
+Installation of Docker CE (Community Edition) varies by platform, and may differ
+in your organization. But the
+[Docker Docs](https://docs.docker.com/v17.12/install) are a good place to start
+looking.
+
+Check that the installation was successful by running
+`sudo docker run hello-world`.
+
+### Download OpenBMC Continuous Integration Image
+
+You'll want a couple of different repositories, so start by making a place for
+it all to go, and clone the CI scripts:
+
+```shell
+mkdir openbmc-ci-tests
+cd openbmc-ci-tests
+git clone https://github.com/openbmc/openbmc-build-scripts.git
+```
+
+## Add `phosphor-host-ipmid`
+
+We also need to put a copy of the project you want to test against here. But
+you've probably got a copy checked out already, so we're going to make a _git
+worktree_. You can read more about those by running `git help worktree`, but the
+basic idea is that it's like having a second copy of your repo - but the second
+copy is in sync with your main copy, knows about your local branches, and
+protects you from checking out the same branch in two places.
+
+Your new worktree doesn't know about any untracked files you have in your main
+worktree, so you should get in the habit of committing everything you want to
+run tests against. However, because of the implementation of
+`run-unit-test-docker.sh`, you can't run the CI with untracked changes anyways,
+so this isn't the worst thing in the world. (If you make untracked changes in
+your testing worktree, it's easy to update a commit with those.)
+
+Note the placeholders in the following steps; modify the commands to match your
+directory layout.
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git worktree add /path/to/openbmc-ci-tests/phosphor-host-ipmid
+```
+
+Now, if you `cd /path/to/openbmc-ci-tests`, you should see a directory
+`phosphor-host-ipmid/`, and if you enter it and run `git status` you will see
+that you're likely on a new branch named `phosphor-host-ipmid`. This is just for
+convenience, since you can't check out a branch in your worktree that's already
+checked out somewhere else; you can safely ignore or delete that branch later.
+
+However, Git won't be able to figure out how to get to your main worktree
+(`/my/dir/for/phosphor-host-ipmid`), so we'll need to mount it when we run. Open
+up `/path/to/openbmc-ci-tests/openbmc-build-scripts/run-unit-test-docker.sh` and
+find where we call `docker run`, way down at the bottom. Add an additional
+argument, remembering to escape the newline ('\'):
+
+```shell
+PHOSPHOR_IPMI_HOST_PATH="/my/dir/for/phosphor-host-ipmid"
+
+docker run --blah-blah-existing-flags \
+ -v ${PHOSPHOR_IPMI_HOST_PATH}:${PHOSPHOR_IPMI_HOST_PATH} \
+ -other \
+ -args
+```
+
+Then commit this, so you can make sure not to lose it if you update the scripts
+repo:
+
+```shell
+cd openbmc-build-scripts
+git add run-unit-test-docker.sh
+git commit -m "mount phosphor-host-ipmid"
+```
+
+NOTE: There are other ways to do this besides a worktree; other approaches trade
+the cruft of mounting extra paths to the Docker container for different cruft:
+
+You can create a local upstream:
+
+```shell
+cd openbmc-ci-tests
+mkdir phosphor-host-ipmid
+cd phosphor-host-ipmid
+git init
+cd /my/dir/for/phosphor-host-ipmid
+git remote add /path/to/openbmc-ci-tests/phosphor-host-ipmid ci
+git push ci
+```
+
+This method would require you to push your topic branch to `ci` and then
+`git checkout` the appropriate branch every time you switched topics:
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git commit -m "my changes to be tested"
+git push ci
+cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
+git checkout topic-branch
+```
+
+You can also create a symlink from your Git workspace into `openbmc-ci-tests/`.
+This is especially not recommended, since you won't be able to work on your code
+in parallel while the tests run, and since the CI scripts are unhappy when you
+have untracked changes - which you're likely to have during active development.
+
+### Building and Running
+
+The OpenBMC CI scripts take care of the build for you, and run the test suite.
+Build and run like so:
+
+```shell
+sudo WORKSPACE=$(pwd) UNIT_TEST_PKG=phosphor-host-ipmid \
+ ./openbmc-build-scripts/run-unit-test-docker.sh
+```
+
+The first run will take a long time! But afterwards it shouldn't be so bad, as
+many parts of the Docker container are already downloaded and configured.
+
+### Reading Output
+
+Your results will appear in
+`openbmc-ci-tests/phosphor-host-ipmid/test/test-suite.log`, as well as being
+printed to `stdout`. You will also see other `.log` files generated for each
+test file, for example `sample_unittest.log`. All these `*.log` files are
+human-readable and can be examined to determine why something failed
+
+### Writing Tests
+
+Now that you've got an easy working environment for running tests, let's write
+some new ones.
+
+### Setting Up Your Environment
+
+In `/my/dir/for/phosphor-host-ipmid`, create a new branch based on `master` (or
+your current patchset). For this tutorial, let's call it `sensorhandler-tests`.
+
+```shell
+git checkout -b sensorhandler-tests master
+```
+
+This creates a new branch `sensorhandler-tests` which is based on `master`, then
+checks out that branch for you to start hacking.
+
+### Write Some Tests
+
+For this tutorial, we'll be adding some basic unit testing of the struct
+accessors for `get_sdr::GetSdrReq`, just because they're fairly simple. The text
+of the struct and accessors is recreated here:
+
+```c++
+/**
+ * Get SDR
+ */
+namespace get_sdr
+{
+
+struct GetSdrReq
+{
+ uint8_t reservation_id_lsb;
+ uint8_t reservation_id_msb;
+ uint8_t record_id_lsb;
+ uint8_t record_id_msb;
+ uint8_t offset;
+ uint8_t bytes_to_read;
+} __attribute__((packed));
+
+namespace request
+{
+
+inline uint8_t get_reservation_id(GetSdrReq* req)
+{
+ return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
+};
+
+inline uint16_t get_record_id(GetSdrReq* req)
+{
+ return (req->record_id_lsb + (req->record_id_msb << 8));
+};
+
+} // namespace request
+
+...
+} // namespace get_sdr
+```
+
+We'll create the tests in `test/sensorhandler_unittest.cpp`; go ahead and start
+that file with your editor.
+
+First, include the header you want to test, as well as the GTest header:
+
+```c++
+#include <sensorhandler.hpp>
+
+#include <gtest/gtest.h>
+```
+
+Let's plan the test cases we care about before we build any additional
+scaffolding. We've only got two functions - `get_reservation_id()` and
+`get_record_id()`. We want to test:
+
+- "Happy path" - in an ideal case, everything works correctly
+- Error handling - when given bad input, things break as expected
+- Edge cases - when given extremes (e.g. very large or very small numbers),
+ things work correctly or break as expected
+
+For `get_reservation_id()`:
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
+{
+}
+```
+
+For `get_record_id()`, we have pretty much the same set of tests:
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_HappyPath)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_NullInputDies)
+{
+}
+
+TEST(SensorHandlerTest, GetSdrReq_get_record_id_Uint16MaxWorksCorrectly)
+{
+}
+```
+
+In the case of these two methods, there's really not much else to test. Some
+types of edge cases - like overflow/underflow - are prevented by C++'s strong
+typing; other types - like passing the incorrect type - are impossible to
+insulate against because it's possible to cast anything to a `GetSdrReq*` if we
+want. Since these are particularly boring, they make a good example for a
+tutorial like this; in practice, tests you write will likely be for more
+complicated code! We'll talk more about this in the Best Practices section
+below.
+
+Let's implement the `get_reservation_id()` items first. The implementations for
+`get_record_id()` will be identical, so we won't cover them here.
+
+For the happy path, we want to make it very clear that the test value we're
+using is within range, so we express it in binary. We also want to be able to
+ensure that the MSB and LSB are being combined in the correct order, so we make
+sure that the MSB and LSB values are different (don't use `0x3333` as the
+expected ID here). Finally, we want it to be obvious to the reader if we have
+populated the `GetSdrReq` incorrectly, so we've labeled all the fields. Since we
+are only testing one operation, it's okay to use either `ASSERT_EQ` or
+`EXPECT_EQ`; more on that in the Best Practices section.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
+{
+ uint16_t expected_id = 0x1234; // Expected ID spans both bytes.
+ GetSdrReq input = {0x34, // Reservation ID LSB
+ 0x12, // Reservation ID MSB
+ 0x00, // Record ID LSB
+ 0x00, // Record ID MSB
+ 0x00, // Offset
+ 0x00}; // Bytes to Read
+
+ uint16_t actual = get_sdr::request::get_reservation_id(&input);
+ ASSERT_EQ(actual, expected_id);
+}
+```
+
+We don't expect that our `GetSdrReq` pointer will ever be null; in this case,
+the null pointer validation is done much, much earlier. So it's okay for us to
+specify that in the unlikely case we're given a null pointer, we die. We don't
+really care what the output message is.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
+{
+ ASSERT_DEATH(get_sdr::request::get_reservation_id(nullptr), ".*");
+}
+```
+
+Finally, while negative values are taken care of by C++'s type system, we can at
+least check that our code still works happily with `UINT16_MAX`. This test is
+similar to the happy path test, but uses an edge value instead.
+
+```c++
+TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
+{
+ uint16_t expected_id = 0xFFFF; // Expected ID spans both bytes.
+ GetSdrReq input = {0xFF, // Reservation ID LSB
+ 0xFF, // Reservation ID MSB
+ 0x00, // Record ID LSB
+ 0x00, // Record ID MSB
+ 0x00, // Offset
+ 0x00}; // Bytes to Read
+
+ uint16_t actual = get_sdr::request::get_reservation_id(&input);
+ ASSERT_EQ(actual, expected_id);
+}
+```
+
+The `get_record_id()` tests are identical, except that they are testing the
+Record ID field. They will not be duplicated here.
+
+Finally, we'll need to add the new tests to `test/Makefile.am`. You can mimic
+other existing test setups:
+
+```make
+# Build/add sensorhandler_unittest to test suite
+sensorhandler_unittest_CPPFLAGS = \
+ -Igtest \
+ $(GTEST_CPPFLAGS) \
+ $(AM_CPPFLAGS)
+sensorhandler_unittest_CXXFLAGS = \
+ $(PTHREAD_CFLAGS) \
+ $(CODE_COVERAGE_CXXFLAGS) \
+ $(CODE_COVERAGE_CFLAGS) \
+ -DBOOST_COROUTINES_NO_DEPRECATION_WARNING
+sensorhandler_unittest_LDFLAGS = \
+ -lgtest_main \
+ -lgtest \
+ -pthread \
+ $(OESDK_TESTCASE_FLAGS) \
+ $(CODE_COVERAGE_LDFLAGS)
+sensorhandler_unittest_SOURCES = \
+ %reldir%/sensorhandler_unittest.cpp
+check_PROGRAMS += %reldir%/sensorhandler_unittest
+```
+
+### Run the New Tests
+
+Commit your test changes. Then, you'll want to checkout the
+`sensorhandler-tests` branch in your CI worktree, which will involve releasing
+it from your main worktree:
+
+```shell
+cd /my/dir/for/phosphor-host-ipmid
+git add test/sensorhandler_unittest.cpp
+git commit -s
+git checkout master # Here you can use any branch except sensorhandler-tests
+cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
+git checkout sensorhandler-tests
+```
+
+Now you can run the test suite as described earlier in the document. If you see
+a linter error when you run, you can actually apply the cleaned-up code easily:
+
+```shell
+cd ./phosphor-host-ipmid
+git diff # Examine the proposed changes
+git add -u # Apply the proposed changes
+git commit --amend
+```
+
+(If you will need to apply the proposed changes to multiple commits, you can do
+this with interactive rebase, which won't be described here.)
+
+### Best Practices
+
+While a good unit test can ensure your code's stability, a flaky or
+poorly-written unit test can make life harder for contributors down the road.
+Some things to remember:
+
+Include both positive (happy-path) and negative (error) testing in your
+testbench. It's not enough to know that the code works when it's supposed to; we
+also need to know that it fails gracefully when something goes wrong. Applying
+edge-case testing helps us find bugs that may take years to occur (and
+reproduce!) in the field.
+
+Keep your tests small. Avoid branching - if you feel a desire to, instead
+explore that codepath in another test. The best tests are easy to read and
+understand.
+
+When a test fails, it's useful if the test is named in such a way that you can
+tell _what it's supposed to do_ and _when_. That way you can be certain whether
+the change you made really broke it or not. A good pattern is
+`Object_NameCircumstanceResult` - for example,
+`FooFactory_OutOfBoundsNameThrowsException`. From the name, it's very clear that
+when some "name" is out of bounds, an exception should be thrown. (What "name"
+is should be clear from the context of the function in `FooFactory`.)
+
+Don't test other people's code. Make sure to limit the test assertions to the
+code under test. For example, don't test what happens if you give a bad input to
+`sdbusplus` when you're supposed to be testing `phosphor-host-ipmid`.
+
+However, don't trust other people's code, either! Try to test _how you respond_
+when a service fails unexpectedly. Rather than checking if `sdbusplus` fails on
+bad inputs, check whether you handle an `sdbusplus` failure gracefully. You can
+use GMock for this kind of testing.
+
+Think about testing when you write your business logic code. Concepts like
+dependency injection and small functions make your code more testable - you'll
+be thanking yourself later when you're writing tests.
+
+Finally, you're very likely to find bugs while writing tests, especially if it's
+for code that wasn't previously unit-tested. It's okay to check in a bugfix
+along with a test that verifies the fix worked, if you're only doing one test
+and one bugfix. If you're checking in a large suite of tests, do the bugfixes in
+independent commits which your test suite commit is based on:
+
+master -> fix Foo.Abc() -> fix Foo.Def() -> Fix Foo.Ghi() -> test Foo class
+
+### Sending for Review
+
+You can send your test update and any associated bugfixes for review to Gerrit
+as you would send any other change. For the `Tested:` field in the commit
+message, you can state that you ran the new unit tests written.
+
+### Reviewing Tests
+
+Tests are written primarily to be read. So when you review a test, you're the
+first customer of that test!
+
+## Best Practices
+
+First, all the best practices listed above for writing tests are things you
+should check for and encourage when you're reading tests.
+
+Next, you should ensure that you can tell what's going on when you read the
+test. If it's not clear to you, it's not going to be clear to someone else, and
+the test is more prone to error - ask!
+
+Finally, think about what's _not_ being tested. If there's a case you're curious
+about and it isn't covered, you should mention it to the committer.
+
+## Quickly Running At Home
+
+Now that you've got a handy setup as described earlier in this document, you can
+quickly download and run the tests yourself. Within the Gerrit change, you
+should be able to find a button that says "Download", which will give you
+commands for various types of downloads into an existing Git repo. Use
+"Checkout", for example:
+
+```shell
+cd openbmc-ci-tests/phosphor-host-ipmid
+git fetch "https://gerrit.openbmc-project.xyz/openbmc/phosphor-host-ipmid" \
+ refs/changes/43/23043/1 && git checkout FETCH_HEAD
+```
+
+This won't disturb the rest of your Git repo state, and will put your CI
+worktree into detached-HEAD mode pointing to the commit that's under review. You
+can then run your tests normally, and even make changes and push again if the
+commit was abandoned or otherwise left to rot by its author.
+
+Doing so can be handy in a number of scenarios:
+
+- Jenkins isn't responding
+- The Jenkins build is broken for a reason beyond the committer's control
+- The committer doesn't have "Ok-To-Test" permission, and you don't have
+ permission to grant it to them
+
+## Credits
+
+Thanks very much to Patrick Venture for his prior work putting together
+documentation on this topic internal to Google.
diff --git a/error-HostEvent.hpp b/error-HostEvent.hpp
new file mode 100644
index 0000000..9e6397a
--- /dev/null
+++ b/error-HostEvent.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <sdbusplus/exception.hpp>
+
+#include <cerrno>
+
+namespace sdbusplus::error::org::open_power::host
+{
+struct Event final : public sdbusplus::exception::generated_exception
+{
+ static constexpr auto errName = "org.open_power.Host.Error.Event";
+ static constexpr auto errDesc = "A host system event was received";
+ static constexpr auto errWhat =
+ "org.open_power.Host.Error.Event: A host system event was received";
+
+ const char* name() const noexcept override
+ {
+ return errName;
+ }
+ const char* description() const noexcept override
+ {
+ return errDesc;
+ }
+ const char* what() const noexcept override
+ {
+ return errWhat;
+ }
+};
+struct MaintenanceProcedure final :
+ public sdbusplus::exception::generated_exception
+{
+ static constexpr auto errName =
+ "org.open_power.Host.Error.MaintenanceProcedure";
+ static constexpr auto errDesc =
+ "A host system event with a procedure callout";
+ static constexpr auto errWhat =
+ "org.open_power.Host.Error.MaintenanceProcedure: A host system event with a procedure callout";
+
+ const char* name() const noexcept override
+ {
+ return errName;
+ }
+ const char* description() const noexcept override
+ {
+ return errDesc;
+ }
+ const char* what() const noexcept override
+ {
+ return errWhat;
+ }
+};
+} // namespace sdbusplus::error::org::open_power::host
+
+#ifndef SDBUSPP_REMOVE_DEPRECATED_NAMESPACE
+namespace sdbusplus::org::open_power::Host::Error
+{
+using Event = sdbusplus::error::org::open_power::host::Event;
+using MaintenanceProcedure =
+ sdbusplus::error::org::open_power::host::MaintenanceProcedure;
+} // namespace sdbusplus::org::open_power::Host::Error
+#endif
diff --git a/fruread.hpp b/fruread.hpp
new file mode 100644
index 0000000..a2fb650
--- /dev/null
+++ b/fruread.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <systemd/sd-bus.h>
+
+#include <array>
+#include <map>
+#include <string>
+#include <vector>
+
+struct IPMIFruData
+{
+ std::string section;
+ std::string property;
+ std::string delimiter;
+};
+
+using DbusProperty = std::string;
+using DbusPropertyVec = std::vector<std::pair<DbusProperty, IPMIFruData>>;
+
+using DbusInterface = std::string;
+using DbusInterfaceVec = std::vector<std::pair<DbusInterface, DbusPropertyVec>>;
+
+using FruInstancePath = std::string;
+
+struct FruInstance
+{
+ uint8_t entityID;
+ uint8_t entityInstance;
+ FruInstancePath path;
+ DbusInterfaceVec interfaces;
+};
+
+using FruInstanceVec = std::vector<FruInstance>;
+
+using FruId = uint32_t;
+using FruMap = std::map<FruId, FruInstanceVec>;
diff --git a/generate_whitelist.sh b/generate_whitelist.sh
new file mode 100755
index 0000000..d3d03e3
--- /dev/null
+++ b/generate_whitelist.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Ensure some files have been passed.
+if [ -z "$*" ]; then
+ echo "Usage: $0 [allowlist_files+]" >&2
+ exit 1
+fi
+
+cat << EOF
+#include <ipmiallowlist.hpp>
+
+const std::vector<netfncmd_pair> allowlist = {
+
+EOF
+
+# Output each row of allowlist vector.
+# Concatenate all the passed files.
+# Remove comments and empty lines.
+# Sort the list [numerically].
+# Remove any duplicates.
+# Turn "a:b //<NetFn>:<Command>" -> "{ a, b }, //<NetFn>:<Command>"
+sed "s/#.*//" "$*" | sed '/^$/d' | sort -n | uniq | sed "s/^/ { /" | \
+ sed "s/\:\(....\)\(.*\)/ , \1 }, \2/"
+
+cat << EOF
+};
+EOF
diff --git a/generate_whitelist_create.sh b/generate_whitelist_create.sh
new file mode 100755
index 0000000..654a20a
--- /dev/null
+++ b/generate_whitelist_create.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# Ensure some files have been passed.
+./generate_whitelist.sh "$*" > ipmiwhitelist.cpp
diff --git a/globalhandler.cpp b/globalhandler.cpp
new file mode 100644
index 0000000..7b64d42
--- /dev/null
+++ b/globalhandler.cpp
@@ -0,0 +1,54 @@
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/State/BMC/server.hpp>
+
+#include <string>
+
+static constexpr auto bmcStateRoot = "/xyz/openbmc_project/state";
+static constexpr auto bmcStateIntf = "xyz.openbmc_project.State.BMC";
+static constexpr auto reqTransition = "RequestedBMCTransition";
+static constexpr auto match = "bmc0";
+
+using BMC = sdbusplus::server::xyz::openbmc_project::state::BMC;
+
+void registerNetFnGlobalFunctions() __attribute__((constructor));
+
+/** @brief implements cold and warm reset commands
+ * @param - None
+ * @returns IPMI completion code.
+ */
+ipmi::RspType<> ipmiGlobalReset(ipmi::Context::ptr ctx)
+{
+ ipmi::DbusObjectInfo bmcStateObj;
+ boost::system::error_code ec = ipmi::getDbusObject(
+ ctx, bmcStateIntf, bmcStateRoot, match, bmcStateObj);
+ if (!ec)
+ {
+ std::string service;
+ ec = ipmi::getService(ctx, bmcStateIntf, bmcStateObj.first, service);
+ if (!ec)
+ {
+ ec = ipmi::setDbusProperty(
+ ctx, service, bmcStateObj.first, bmcStateIntf, reqTransition,
+ convertForMessage(BMC::Transition::Reboot));
+ }
+ }
+ if (ec)
+ {
+ lg2::error("Exception in Global Reset: {ERROR}", "ERROR", ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ // Status code.
+ return ipmi::responseSuccess();
+}
+
+void registerNetFnGlobalFunctions()
+{
+ // Cold Reset
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdColdReset, ipmi::Privilege::Admin,
+ ipmiGlobalReset);
+ return;
+}
diff --git a/groupext.cpp b/groupext.cpp
new file mode 100644
index 0000000..f61bf1f
--- /dev/null
+++ b/groupext.cpp
@@ -0,0 +1,31 @@
+#include <ipmid/api.hpp>
+
+#include <cstdio>
+
+#define GRPEXT_GET_GROUP_CMD 0
+void registerNetFnGroupExtFunctions() __attribute__((constructor));
+
+ipmi_ret_t ipmi_groupext(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t,
+ ipmi_response_t response, ipmi_data_len_t data_len,
+ ipmi_context_t)
+{
+ // Generic return from IPMI commands.
+ ipmi_ret_t rc = IPMI_CC_OK;
+ uint8_t* p = (uint8_t*)response;
+
+ std::printf("IPMI GROUP EXTENSIONS\n");
+
+ *data_len = 1;
+ *p = 0;
+
+ return rc;
+}
+
+void registerNetFnGroupExtFunctions()
+{
+ // <Group Extension Command>
+ ipmi_register_callback(NETFUN_GRPEXT, GRPEXT_GET_GROUP_CMD, nullptr,
+ ipmi_groupext, PRIVILEGE_USER);
+
+ return;
+}
diff --git a/host-cmd-manager.cpp b/host-cmd-manager.cpp
new file mode 100644
index 0000000..3b3304f
--- /dev/null
+++ b/host-cmd-manager.cpp
@@ -0,0 +1,195 @@
+#include "config.h"
+
+#include "host-cmd-manager.hpp"
+
+#include "systemintfcmds.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+constexpr auto HOST_STATE_PATH = "/xyz/openbmc_project/state/host0";
+constexpr auto HOST_STATE_INTERFACE = "xyz.openbmc_project.State.Host";
+constexpr auto HOST_TRANS_PROP = "RequestedHostTransition";
+
+// For throwing exceptions
+using namespace phosphor::logging;
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
+Manager::Manager(sdbusplus::bus_t& bus) :
+ bus(bus), timer(std::bind(&Manager::hostTimeout, this)),
+ hostTransitionMatch(
+ bus,
+ sdbusRule::propertiesChanged(HOST_STATE_PATH, HOST_STATE_INTERFACE),
+ std::bind(&Manager::clearQueueOnPowerOn, this, std::placeholders::_1))
+{
+ // Nothing to do here.
+}
+
+// Called as part of READ_MSG_DATA command
+IpmiCmdData Manager::getNextCommand()
+{
+ // Stop the timer. Don't have to Err failure doing so.
+ auto r = timer.stop();
+ if (r < 0)
+ {
+ lg2::error("Failure to STOP the timer: {ERROR}", "ERROR", strerror(-r));
+ }
+
+ if (this->workQueue.empty())
+ {
+ // Just return a heartbeat in this case. A spurious SMS_ATN was
+ // asserted for the host (probably from a previous boot).
+ lg2::debug("Control Host work queue is empty!");
+
+ return std::make_pair(CMD_HEARTBEAT, 0x00);
+ }
+
+ // Pop the processed entry off the queue
+ auto command = this->workQueue.front();
+ this->workQueue.pop();
+
+ // IPMI command is the first element in pair
+ auto ipmiCmdData = std::get<0>(command);
+
+ // Now, call the user registered functions so that
+ // implementation specific CommandComplete signals
+ // can be sent. `true` indicating Success.
+ std::get<CallBack>(command)(ipmiCmdData, true);
+
+ // Check for another entry in the queue and kick it off
+ this->checkQueueAndAlertHost();
+
+ // Tuple of command and data
+ return ipmiCmdData;
+}
+
+// Called when initial timer goes off post sending SMS_ATN
+void Manager::hostTimeout()
+{
+ lg2::error("Host control timeout hit!");
+
+ clearQueue();
+}
+
+void Manager::clearQueue()
+{
+ // Dequeue all entries and send fail signal
+ while (!this->workQueue.empty())
+ {
+ auto command = this->workQueue.front();
+ this->workQueue.pop();
+
+ // IPMI command is the first element in pair
+ auto ipmiCmdData = std::get<0>(command);
+
+ // Call the implementation specific Command Failure.
+ // `false` indicating Failure
+ std::get<CallBack>(command)(ipmiCmdData, false);
+ }
+}
+
+// Called for alerting the host
+void Manager::checkQueueAndAlertHost()
+{
+ if (this->workQueue.size() >= 1)
+ {
+ lg2::debug("Asserting SMS Attention");
+
+ std::string HOST_IPMI_SVC("org.openbmc.HostIpmi");
+ std::string IPMI_PATH("/org/openbmc/HostIpmi/1");
+ std::string IPMI_INTERFACE("org.openbmc.HostIpmi");
+
+ // Start the timer for this transaction
+ auto time = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS));
+
+ auto r = timer.start(time);
+ if (r < 0)
+ {
+ lg2::error("Error starting timer for control host");
+ return;
+ }
+
+ auto method =
+ this->bus.new_method_call(HOST_IPMI_SVC.c_str(), IPMI_PATH.c_str(),
+ IPMI_INTERFACE.c_str(), "setAttention");
+
+ try
+ {
+ auto reply = this->bus.call(method);
+
+ lg2::debug("SMS Attention asserted");
+ }
+ catch (sdbusplus::exception_t& e)
+ {
+ lg2::error("Error when call setAttention method");
+ }
+ }
+}
+
+// Called by specific implementations that provide commands
+void Manager::execute(CommandHandler command)
+{
+ lg2::debug("Pushing cmd on to queue, command: {COMMAND}", "COMMAND",
+ std::get<0>(command).first);
+
+ this->workQueue.emplace(command);
+
+ // Alert host if this is only command in queue otherwise host will
+ // be notified of next message after processing the current one
+ if (this->workQueue.size() == 1)
+ {
+ this->checkQueueAndAlertHost();
+ }
+ else
+ {
+ lg2::info("Command in process, no attention");
+ }
+
+ return;
+}
+
+void Manager::clearQueueOnPowerOn(sdbusplus::message_t& msg)
+{
+ namespace server = sdbusplus::server::xyz::openbmc_project::state;
+
+ ::ipmi::DbusInterface interface;
+ ::ipmi::PropertyMap properties;
+
+ msg.read(interface, properties);
+
+ if (properties.find(HOST_TRANS_PROP) == properties.end())
+ {
+ return;
+ }
+
+ auto& requestedState =
+ std::get<std::string>(properties.at(HOST_TRANS_PROP));
+
+ if (server::Host::convertTransitionFromString(requestedState) ==
+ server::Host::Transition::On)
+ {
+ clearQueue();
+ }
+}
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-cmd-manager.hpp b/host-cmd-manager.hpp
new file mode 100644
index 0000000..8937e43
--- /dev/null
+++ b/host-cmd-manager.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <ipmid-host/cmd-utils.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <queue>
+#include <tuple>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+/** @class
+ * @brief Manages commands that are to be sent to Host
+ */
+class Manager
+{
+ public:
+ Manager() = delete;
+ ~Manager() = default;
+ Manager(const Manager&) = delete;
+ Manager& operator=(const Manager&) = delete;
+ Manager(Manager&&) = delete;
+ Manager& operator=(Manager&&) = delete;
+
+ /** @brief Constructs Manager object
+ *
+ * @param[in] bus - dbus handler
+ * @param[in] event - pointer to sd_event
+ */
+ explicit Manager(sdbusplus::bus_t& bus);
+
+ /** @brief Extracts the next entry in the queue and returns
+ * Command and data part of it.
+ *
+ * @detail Also calls into the registered handlers so that they can now
+ * send the CommandComplete signal since the interface contract
+ * is that we emit this signal once the message has been
+ * passed to the host (which is required when calling this)
+ *
+ * Also, if the queue has more commands, then it will alert the
+ * host
+ */
+ IpmiCmdData getNextCommand();
+
+ /** @brief Pushes the command onto the queue.
+ *
+ * @detail If the queue is empty, then it alerts the Host. If not,
+ * then it returns and the API documented above will handle
+ * the commands in Queue.
+ *
+ * @param[in] command - tuple of <IPMI command, data, callback>
+ */
+ void execute(CommandHandler command);
+
+ private:
+ /** @brief Check if anything in queue and alert host if so */
+ void checkQueueAndAlertHost();
+
+ /** @brief Call back interface on message timeouts to host.
+ *
+ * @detail When this happens, a failure message would be sent
+ * to all the commands that are in the Queue and queue
+ * will be purged
+ */
+ void hostTimeout();
+
+ /** @brief Clears the command queue
+ *
+ * @detail Clears the command queue and calls all callbacks
+ * specifying the command wasn't successful.
+ */
+ void clearQueue();
+
+ /** @brief Clears the command queue on a power on
+ *
+ * @detail The properties changed handler for the
+ * RequestedHostTransition property. When this property
+ * changes to 'On', this function will purge the command
+ * queue.
+ *
+ * This is done to avoid having commands that were issued
+ * before the host powers on from getting sent to the host,
+ * either due to race conditions around state transitions
+ * or from a user doing something like requesting an already
+ * powered off system to power off again and then immediately
+ * requesting a power on.
+ *
+ * @param[in] msg - the sdbusplus message containing the property
+ */
+ void clearQueueOnPowerOn(sdbusplus::message_t& msg);
+
+ /** @brief Reference to the dbus handler */
+ sdbusplus::bus_t& bus;
+
+ /** @brief Queue to store the requested commands */
+ std::queue<CommandHandler> workQueue{};
+
+ /** @brief Timer for commands to host */
+ sdbusplus::Timer timer;
+
+ /** @brief Match handler for the requested host state */
+ sdbusplus::bus::match_t hostTransitionMatch;
+};
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-interface.cpp b/host-interface.cpp
new file mode 100644
index 0000000..bc01d65
--- /dev/null
+++ b/host-interface.cpp
@@ -0,0 +1,122 @@
+
+#include "config.h"
+
+#include "host-interface.hpp"
+
+#include "systemintfcmds.hpp"
+
+#include <ipmid-host/cmd-utils.hpp>
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+// When you see Base:: you know we're referencing our base class
+namespace Base = sdbusplus::server::xyz::openbmc_project::control;
+
+// IPMI OEM command.
+// https://github.com/openbmc/openbmc/issues/2082 for handling
+// Non-OEM commands that need to send SMS_ATN
+using OEMCmd = uint8_t;
+
+// Map of IPMI OEM command to its equivalent interface command.
+// This is needed when invoking the callback handler to indicate
+// the status of the executed command.
+static const std::map<OEMCmd, Host::Command> intfCommand = {
+ {CMD_HEARTBEAT, Base::Host::Command::Heartbeat},
+ {CMD_POWER, Base::Host::Command::SoftOff}};
+
+// Map of Interface command to its corresponding IPMI OEM command.
+// This is needed when pushing IPMI commands to command manager's
+// queue. The same pair will be returned when IPMI asks us
+// why a SMS_ATN was sent
+static const std::map<Host::Command, IpmiCmdData> ipmiCommand = {
+ {Base::Host::Command::Heartbeat, std::make_pair(CMD_HEARTBEAT, 0x00)},
+ {Base::Host::Command::SoftOff, std::make_pair(CMD_POWER, SOFT_OFF)}};
+
+// Called at user request
+void Host::execute(Base::Host::Command command)
+{
+ lg2::debug("Pushing cmd on to queue, control host cmd: {CONTROL_HOST_CMD}",
+ "CONTROL_HOST_CMD", convertForMessage(command));
+
+ auto cmd = std::make_tuple(
+ ipmiCommand.at(command),
+ std::bind(&Host::commandStatusHandler, this, std::placeholders::_1,
+ std::placeholders::_2));
+
+ ipmid_send_cmd_to_host(std::move(cmd));
+}
+
+// Called into by Command Manager
+void Host::commandStatusHandler(IpmiCmdData cmd, bool status)
+{
+ // Need to convert <cmd> to the equivalent one mentioned in spec
+ auto value = status ? Result::Success : Result::Failure;
+
+ // Fire a signal
+ this->commandComplete(intfCommand.at(std::get<0>(cmd)), value);
+}
+
+Host::FirmwareCondition Host::currentFirmwareCondition() const
+{
+ // shared object used to wait for host response
+ auto hostCondition =
+ std::make_shared<std::optional<Host::FirmwareCondition>>();
+
+ // callback for command to host
+ auto hostAckCallback = [hostCondition](IpmiCmdData, bool status) {
+ auto value = status ? Host::FirmwareCondition::Running
+ : Host::FirmwareCondition::Off;
+
+ lg2::debug("currentFirmwareCondition:hostAckCallback fired, "
+ "control host cmd: {CONTROL_HOST_CMD}",
+ "CONTROL_HOST_CMD", value);
+
+ *(hostCondition.get()) = value;
+ return;
+ };
+
+ auto cmd = phosphor::host::command::CommandHandler(
+ ipmiCommand.at(Base::Host::Command::Heartbeat),
+ std::move(hostAckCallback));
+
+ ipmid_send_cmd_to_host(std::move(cmd));
+
+ // Timer to ensure this function returns something within a reasonable time
+ sdbusplus::Timer hostAckTimer([hostCondition]() {
+ lg2::debug("currentFirmwareCondition: timer expired!");
+ *(hostCondition.get()) = Host::FirmwareCondition::Off;
+ });
+
+ // Wait 1 second past the ATN_ACK timeout to ensure we wait for as
+ // long as the timeout
+ hostAckTimer.start(std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS + 1));
+
+ auto io = getIoContext();
+
+ while (!hostCondition.get()->has_value())
+ {
+ lg2::debug("currentFirmwareCondition: waiting for host response");
+ io->run_for(std::chrono::milliseconds(100));
+ }
+ hostAckTimer.stop();
+
+ lg2::debug("currentFirmwareCondition: hostCondition is ready!");
+ return hostCondition.get()->value();
+}
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-interface.hpp b/host-interface.hpp
new file mode 100644
index 0000000..52dbab1
--- /dev/null
+++ b/host-interface.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <host-cmd-manager.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Condition/HostFirmware/server.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+
+/** @class Host
+ * @brief OpenBMC control and condition host interface implementation.
+ * @details A concrete implementation for xyz.openbmc_project.Control.Host
+ * and xyz.openbmc_project.Condition.HostFirmware DBus API's.
+ */
+class Host :
+ public sdbusplus::server::object_t<
+ sdbusplus::server::xyz::openbmc_project::control::Host,
+ sdbusplus::server::xyz::openbmc_project::condition::HostFirmware>
+{
+ public:
+ /** @brief Constructs Host Control and Condition Interfaces
+ *
+ * @param[in] bus - The Dbus bus object
+ * @param[in] objPath - The Dbus object path
+ */
+ Host(sdbusplus::bus_t& bus, const char* objPath) :
+ sdbusplus::server::object_t<
+ sdbusplus::server::xyz::openbmc_project::control::Host,
+ sdbusplus::server::xyz::openbmc_project::condition::HostFirmware>(
+ bus, objPath),
+ bus(bus)
+ {
+ // Nothing to do
+ }
+
+ /** @brief Send input command to host
+ * Note that the command will be queued in a FIFO if
+ * other commands to the host have yet to be run
+ *
+ * @param[in] command - Input command to execute
+ */
+ void execute(Command command) override;
+
+ /** @brief Override reads to CurrentFirmwareCondition */
+ FirmwareCondition currentFirmwareCondition() const override;
+
+ private:
+ /** @brief sdbusplus DBus bus connection. */
+ sdbusplus::bus_t& bus;
+
+ /** @brief Callback function to be invoked by command manager
+ *
+ * @detail Conveys the status of the last Host bound command.
+ * Depending on the status, a CommandComplete or
+ * CommandFailure signal would be sent
+ *
+ * @param[in] cmd - IPMI command and data sent to Host
+ * @param[in] status - Success or Failure
+ */
+ void commandStatusHandler(IpmiCmdData cmd, bool status);
+};
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/host-ipmid-whitelist.conf b/host-ipmid-whitelist.conf
new file mode 100644
index 0000000..9dd1eb1
--- /dev/null
+++ b/host-ipmid-whitelist.conf
@@ -0,0 +1,51 @@
+#<NetFn>:<Command
+0x00:0x00 //<Chassis>:<Chassis Capabiliti>
+0x00:0x01 //<Chassis>:<Get Chassis Status>
+0x00:0x02 //<Chassis>:<Chassis Control>
+0x00:0x05 //<Chassis>:<Set Chassis Capabilities>
+0x00:0x06 //<Chassis>:<Set Power Restore Policy>
+0x00:0x08 //<Chassis>:<Set System Boot Options>
+0x00:0x09 //<Chassis>:<Get System Boot Options>
+0x00:0x0F //<Chassis>:<Get POH Counter Command>
+0x04:0x02 //<Sensor/Event>:<Platform event>
+0x04:0x2D //<Sensor/Event>:<Get Sensor Reading>
+0x04:0x2F //<Sensor/Event>:<Get Sensor Type>
+0x04:0x30 //<Sensor/Event>:<Set Sensor Reading and Event Status>
+0x06:0x01 //<App>:<Get Device ID>
+0x06:0x04 //<App>:<Get Self Test Results>
+0x06:0x06 //<App>:<Set ACPI Power State>
+0x06:0x07 //<App>:<Get ACPI Power State>
+0x06:0x08 //<App>:<Get Device GUID>
+0x06:0x22 //<App>:<Reset Watchdog Timer>
+0x06:0x24 //<App>:<Set Watchdog Timer>
+0x06:0x25 //<App>:<Get Watchdog Timer>
+0x06:0x2E //<App>:<Set BMC Global Enables>
+0x06:0x2F //<App>:<Get BMC Global Enables>
+0x06:0x31 //<App>:<Get Message Flags>
+0x06:0x35 //<App>:<Read Event Message Buffer>
+0x06:0x36 //<App>:<Get BT Interface Capabilities>
+0x06:0x37 //<App>:<Get System GUID>
+0x06:0x42 //<App>:<Get Channel Info Command>
+0x06:0x4D //<App>:<Get User Payload Access>
+0x06:0x4E //<App>:<Get Channel Payload Support>
+0x06:0x4F //<App>:<Get Channel Payload Version>
+0x06:0x54 //<App>:<Get Channel Cipher Suites>
+0x0A:0x10 //<Storage>:<Get FRU Inventory Area Info>
+0x0A:0x11 //<Storage>:<Read FRU Data>
+0x0A:0x20 //<Storage>:<Get SDR Repository Info>
+0x0A:0x22 //<Storage>:<Reserve SDR Repository>
+0x0A:0x23 //<Storage>:<Get SDR>
+0x0A:0x40 //<Storage>:<Get SEL Info>
+0x0A:0x42 //<Storage>:<Reserve SEL>
+0x0A:0x44 //<Storage>:<Add SEL Entry>
+0x0A:0x48 //<Storage>:<Get SEL Time>
+0x0A:0x49 //<Storage>:<Set SEL Time>
+0x0A:0x5C //<Storage>:<Get SEL Time UTC Offset>
+0x0C:0x02 //<Transport>:<Get LAN Configuration Parameters>
+0x2C:0x00 //<Group Extension>:<Group Extension Command>
+0x2C:0x01 //<Group Extension>:<Get DCMI Capabilities>
+0x2C:0x02 //<Group Extension>:<Get Power Reading>
+0x2C:0x03 //<Group Extension>:<Get Power Limit>
+0x2C:0x06 //<Group Extension>:<Get Asset Tag>
+0x2C:0x07 //<Group Extension>:<Get Sensor Info>
+0x2C:0x10 //<Group Extension>:<Get Temperature Readings>
diff --git a/include/dbus-sdr/sdrutils.hpp b/include/dbus-sdr/sdrutils.hpp
new file mode 100644
index 0000000..b52351d
--- /dev/null
+++ b/include/dbus-sdr/sdrutils.hpp
@@ -0,0 +1,363 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include <boost/algorithm/string.hpp>
+#include <boost/bimap.hpp>
+#include <boost/container/flat_map.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <cstdio>
+#include <cstring>
+#include <exception>
+#include <filesystem>
+#include <map>
+#include <optional>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#pragma once
+
+static constexpr bool debug = false;
+
+struct CmpStrVersion
+{
+ bool operator()(std::string a, std::string b) const
+ {
+ return strverscmp(a.c_str(), b.c_str()) < 0;
+ }
+};
+
+using SensorSubTree = boost::container::flat_map<
+ std::string,
+ boost::container::flat_map<std::string, std::vector<std::string>>,
+ CmpStrVersion>;
+
+using SensorNumMap = boost::bimap<int, std::string>;
+
+static constexpr uint16_t maxSensorsPerLUN = 255;
+static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3);
+static constexpr uint16_t lun1Sensor0 = 0x100;
+static constexpr uint16_t lun3Sensor0 = 0x300;
+static constexpr uint16_t invalidSensorNumber = 0xFFFF;
+static constexpr uint8_t reservedSensorNumber = 0xFF;
+
+namespace details
+{
+// Enable/disable the logging of stats instrumentation
+static constexpr bool enableInstrumentation = false;
+
+class IPMIStatsEntry
+{
+ private:
+ int numReadings = 0;
+ int numMissings = 0;
+ int numStreakRead = 0;
+ int numStreakMiss = 0;
+ double minValue = 0.0;
+ double maxValue = 0.0;
+ std::string sensorName;
+
+ public:
+ const std::string& getName(void) const
+ {
+ return sensorName;
+ }
+
+ void updateName(std::string_view name)
+ {
+ sensorName = name;
+ }
+
+ // Returns true if this is the first successful reading
+ // This is so the caller can log the coefficients used
+ bool updateReading(double reading, int raw)
+ {
+ if constexpr (!enableInstrumentation)
+ {
+ return false;
+ }
+
+ bool first = ((numReadings == 0) && (numMissings == 0));
+
+ // Sensors can use "nan" to indicate unavailable reading
+ if (!(std::isfinite(reading)))
+ {
+ // Only show this if beginning a new streak
+ if (numStreakMiss == 0)
+ {
+ std::cerr << "IPMI sensor " << sensorName
+ << ": Missing reading, byte=" << raw
+ << ", Reading counts good=" << numReadings
+ << " miss=" << numMissings
+ << ", Prior good streak=" << numStreakRead << "\n";
+ }
+
+ numStreakRead = 0;
+ ++numMissings;
+ ++numStreakMiss;
+
+ return first;
+ }
+
+ // Only show this if beginning a new streak and not the first time
+ if ((numStreakRead == 0) && (numReadings != 0))
+ {
+ std::cerr << "IPMI sensor " << sensorName
+ << ": Recovered reading, value=" << reading << " byte="
+ << raw << ", Reading counts good=" << numReadings
+ << " miss=" << numMissings
+ << ", Prior miss streak=" << numStreakMiss << "\n";
+ }
+
+ // Initialize min/max if the first successful reading
+ if (numReadings == 0)
+ {
+ std::cerr << "IPMI sensor " << sensorName
+ << ": First reading, value=" << reading << " byte=" << raw
+ << "\n";
+
+ minValue = reading;
+ maxValue = reading;
+ }
+
+ numStreakMiss = 0;
+ ++numReadings;
+ ++numStreakRead;
+
+ // Only provide subsequent output if new min/max established
+ if (reading < minValue)
+ {
+ std::cerr << "IPMI sensor " << sensorName
+ << ": Lowest reading, value=" << reading
+ << " byte=" << raw << "\n";
+
+ minValue = reading;
+ }
+
+ if (reading > maxValue)
+ {
+ std::cerr << "IPMI sensor " << sensorName
+ << ": Highest reading, value=" << reading
+ << " byte=" << raw << "\n";
+
+ maxValue = reading;
+ }
+
+ return first;
+ }
+};
+
+class IPMIStatsTable
+{
+ private:
+ std::vector<IPMIStatsEntry> entries;
+
+ private:
+ void padEntries(size_t index)
+ {
+ char hexbuf[16];
+
+ // Pad vector until entries[index] becomes a valid index
+ while (entries.size() <= index)
+ {
+ // As name not known yet, use human-readable hex as name
+ IPMIStatsEntry newEntry;
+ sprintf(hexbuf, "0x%02zX", entries.size());
+ newEntry.updateName(hexbuf);
+
+ entries.push_back(std::move(newEntry));
+ }
+ }
+
+ public:
+ void wipeTable(void)
+ {
+ entries.clear();
+ }
+
+ const std::string& getName(size_t index)
+ {
+ padEntries(index);
+ return entries[index].getName();
+ }
+
+ void updateName(size_t index, std::string_view name)
+ {
+ padEntries(index);
+ entries[index].updateName(name);
+ }
+
+ bool updateReading(size_t index, double reading, int raw)
+ {
+ padEntries(index);
+ return entries[index].updateReading(reading, raw);
+ }
+};
+
+class IPMIWriteEntry
+{
+ private:
+ bool writePermission = false;
+
+ public:
+ bool getWritePermission(void) const
+ {
+ return writePermission;
+ }
+
+ void setWritePermission(bool permission)
+ {
+ writePermission = permission;
+ }
+};
+
+class IPMIWriteTable
+{
+ private:
+ std::vector<IPMIWriteEntry> entries;
+
+ private:
+ void padEntries(size_t index)
+ {
+ // Pad vector until entries[index] becomes a valid index
+ if (entries.size() <= index)
+ {
+ entries.resize(index + 1);
+ }
+ }
+
+ public:
+ void wipeTable(void)
+ {
+ entries.clear();
+ }
+
+ bool getWritePermission(size_t index)
+ {
+ padEntries(index);
+ return entries[index].getWritePermission();
+ }
+
+ void setWritePermission(size_t index, bool permission)
+ {
+ padEntries(index);
+ entries[index].setWritePermission(permission);
+ }
+};
+
+// Store information for threshold sensors and they are not used by VR
+// sensors. These objects are global singletons, used from a variety of places.
+inline IPMIStatsTable sdrStatsTable;
+inline IPMIWriteTable sdrWriteTable;
+
+/**
+ * Search ObjectMapper for sensors and update them to subtree.
+ *
+ * The function will search for sensors under either
+ * /xyz/openbmc_project/sensors or /xyz/openbmc_project/extsensors. It will
+ * optionally search VR typed sensors under /xyz/openbmc_project/vr
+ *
+ * @return the updated amount of times any of "sensors" or "extsensors" sensor
+ * paths updated successfully, previous amount if all failed. The "vr"
+ * sensor path is optional, and does not participate in the return value.
+ */
+uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree);
+
+bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap);
+} // namespace details
+
+bool getSensorSubtree(SensorSubTree& subtree);
+
+#ifdef FEATURE_HYBRID_SENSORS
+ipmi::sensor::IdInfoMap::const_iterator findStaticSensor(
+ const std::string& path);
+#endif
+
+struct CmpStr
+{
+ bool operator()(const char* a, const char* b) const
+ {
+ return std::strcmp(a, b) < 0;
+ }
+};
+
+static constexpr size_t sensorTypeCodes = 0;
+static constexpr size_t sensorEventTypeCodes = 1;
+
+enum class SensorTypeCodes : uint8_t
+{
+ reserved = 0x00,
+ temperature = 0x01,
+ voltage = 0x02,
+ current = 0x03,
+ fan = 0x04,
+ physical_security = 0x5,
+ processor = 0x07,
+ power_unit = 0x09,
+ other = 0x0b,
+ memory = 0x0c,
+ buttons = 0x14,
+ watchdog2 = 0x23,
+ entity = 0x25,
+ oemC0 = 0xc0,
+};
+
+enum class SensorEventTypeCodes : uint8_t
+{
+ unspecified = 0x00,
+ threshold = 0x01,
+ sensorSpecified = 0x6f
+};
+
+extern boost::container::flat_map<
+ const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
+ sensorTypes;
+
+std::string getSensorTypeStringFromPath(const std::string& path);
+
+uint8_t getSensorTypeFromPath(const std::string& path);
+
+uint16_t getSensorNumberFromPath(const std::string& path);
+
+uint8_t getSensorEventTypeFromPath(const std::string& path);
+
+std::string getPathFromSensorNumber(uint16_t sensorNum);
+
+namespace ipmi
+{
+std::optional<std::map<std::string, std::vector<std::string>>>
+ getObjectInterfaces(const char* path);
+
+std::map<std::string, Value> getEntityManagerProperties(const char* path,
+ const char* interface);
+
+std::optional<std::unordered_set<std::string>>& getIpmiDecoratorPaths(
+ const std::optional<ipmi::Context::ptr>& ctx);
+
+const std::string* getSensorConfigurationInterface(
+ const std::map<std::string, std::vector<std::string>>&
+ sensorInterfacesResponse);
+
+void updateIpmiFromAssociation(
+ const std::string& path,
+ const std::unordered_set<std::string>& ipmiDecoratorPaths,
+ const DbusInterfaceMap& sensorMap, uint8_t& entityId,
+ uint8_t& entityInstance);
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorcommands.hpp b/include/dbus-sdr/sensorcommands.hpp
new file mode 100644
index 0000000..9184633
--- /dev/null
+++ b/include/dbus-sdr/sensorcommands.hpp
@@ -0,0 +1,163 @@
+/*
+// Copyright (c) 2017 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include <dbus-sdr/sdrutils.hpp>
+
+#include <cstdint>
+
+#pragma pack(push, 1)
+
+struct SensorThresholdResp
+{
+ uint8_t readable;
+ uint8_t lowernc;
+ uint8_t lowercritical;
+ uint8_t lowernonrecoverable;
+ uint8_t uppernc;
+ uint8_t uppercritical;
+ uint8_t uppernonrecoverable;
+};
+
+#pragma pack(pop)
+
+enum class IPMIThresholdRespBits
+{
+ lowerNonCritical,
+ lowerCritical,
+ lowerNonRecoverable,
+ upperNonCritical,
+ upperCritical,
+ upperNonRecoverable
+};
+
+enum class IPMISensorReadingByte2 : uint8_t
+{
+ eventMessagesEnable = (1 << 7),
+ sensorScanningEnable = (1 << 6),
+ readingStateUnavailable = (1 << 5),
+};
+
+enum class IPMISensorReadingByte3 : uint8_t
+{
+ upperNonRecoverable = (1 << 5),
+ upperCritical = (1 << 4),
+ upperNonCritical = (1 << 3),
+ lowerNonRecoverable = (1 << 2),
+ lowerCritical = (1 << 1),
+ lowerNonCritical = (1 << 0),
+};
+
+enum class IPMISensorEventEnableByte2 : uint8_t
+{
+ eventMessagesEnable = (1 << 7),
+ sensorScanningEnable = (1 << 6),
+};
+
+enum class IPMISensorEventEnableThresholds : uint8_t
+{
+ nonRecoverableThreshold = (1 << 6),
+ criticalThreshold = (1 << 5),
+ nonCriticalThreshold = (1 << 4),
+ upperNonRecoverableGoingHigh = (1 << 3),
+ upperNonRecoverableGoingLow = (1 << 2),
+ upperCriticalGoingHigh = (1 << 1),
+ upperCriticalGoingLow = (1 << 0),
+ upperNonCriticalGoingHigh = (1 << 7),
+ upperNonCriticalGoingLow = (1 << 6),
+ lowerNonRecoverableGoingHigh = (1 << 5),
+ lowerNonRecoverableGoingLow = (1 << 4),
+ lowerCriticalGoingHigh = (1 << 3),
+ lowerCriticalGoingLow = (1 << 2),
+ lowerNonCriticalGoingHigh = (1 << 1),
+ lowerNonCriticalGoingLow = (1 << 0),
+};
+
+enum class IPMIGetSensorEventEnableThresholds : uint8_t
+{
+ lowerNonCriticalGoingLow = 0,
+ lowerNonCriticalGoingHigh = 1,
+ lowerCriticalGoingLow = 2,
+ lowerCriticalGoingHigh = 3,
+ lowerNonRecoverableGoingLow = 4,
+ lowerNonRecoverableGoingHigh = 5,
+ upperNonCriticalGoingLow = 6,
+ upperNonCriticalGoingHigh = 7,
+ upperCriticalGoingLow = 8,
+ upperCriticalGoingHigh = 9,
+ upperNonRecoverableGoingLow = 10,
+ upperNonRecoverableGoingHigh = 11,
+};
+
+namespace ipmi
+{
+
+uint16_t getNumberOfSensors();
+
+SensorSubTree& getSensorTree();
+
+ipmi_ret_t getSensorConnection(ipmi::Context::ptr ctx, uint8_t sensnum,
+ std::string& connection, std::string& path,
+ std::vector<std::string>* interfaces = nullptr);
+
+struct IPMIThresholds
+{
+ std::optional<uint8_t> warningLow;
+ std::optional<uint8_t> warningHigh;
+ std::optional<uint8_t> criticalLow;
+ std::optional<uint8_t> criticalHigh;
+};
+
+namespace sensor
+{
+/**
+ * @brief Retrieve the number of sensors that are not included in the list of
+ * sensors published via D-Bus
+ *
+ * @param[in]: ctx: the pointer to the D-Bus context
+ * @return: The number of additional sensors separate from those published
+ * dynamically on D-Bus
+ */
+size_t getOtherSensorsCount(ipmi::Context::ptr ctx);
+
+/**
+ * @brief Retrieve the record data for the sensors not published via D-Bus
+ *
+ * @param[in]: ctx: the pointer to the D-Bus context
+ * @param[in]: recordID: the integer index for the sensor to retrieve
+ * @param[out]: SDR data for the indexed sensor
+ * @return: 0: success
+ * negative number: error condition
+ */
+int getOtherSensorsDataRecord(ipmi::Context::ptr ctx, uint16_t recordID,
+ std::vector<uint8_t>& recordData);
+} // namespace sensor
+
+namespace dcmi
+{
+
+struct sensorInfo
+{
+ std::string objectPath;
+ uint8_t type;
+ uint16_t recordId;
+ uint8_t entityId;
+ uint8_t entityInstance;
+};
+
+} // namespace dcmi
+
+} // namespace ipmi
diff --git a/include/dbus-sdr/sensorutils.hpp b/include/dbus-sdr/sensorutils.hpp
new file mode 100644
index 0000000..51fbeaf
--- /dev/null
+++ b/include/dbus-sdr/sensorutils.hpp
@@ -0,0 +1,40 @@
+/*
+// Copyright (c) 2017 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <iostream>
+
+namespace ipmi
+{
+static constexpr int16_t maxInt10 = 0x1FF;
+static constexpr int16_t minInt10 = -0x200;
+static constexpr int8_t maxInt4 = 7;
+static constexpr int8_t minInt4 = -8;
+
+bool getSensorAttributes(const double max, const double min, int16_t& mValue,
+ int8_t& rExp, int16_t& bValue, int8_t& bExp,
+ bool& bSigned);
+
+uint8_t scaleIPMIValueFromDouble(const double value, const int16_t mValue,
+ const int8_t rExp, const int16_t bValue,
+ const int8_t bExp, const bool bSigned);
+
+uint8_t getScaledIPMIValue(const double value, const double max,
+ const double min);
+} // namespace ipmi
diff --git a/include/dbus-sdr/storagecommands.hpp b/include/dbus-sdr/storagecommands.hpp
new file mode 100644
index 0000000..21c7613
--- /dev/null
+++ b/include/dbus-sdr/storagecommands.hpp
@@ -0,0 +1,136 @@
+/*
+// Copyright (c) 2017 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include "sensorhandler.hpp"
+
+#include <cstdint>
+
+static constexpr uint8_t ipmiSdrVersion = 0x51;
+
+namespace dynamic_sensors::ipmi::sel
+{
+static constexpr uint8_t selOperationSupport = 0x02;
+static constexpr uint8_t systemEvent = 0x02;
+static constexpr size_t systemEventSize = 3;
+static constexpr uint8_t oemTsEventFirst = 0xC0;
+static constexpr uint8_t oemTsEventLast = 0xDF;
+static constexpr size_t oemTsEventSize = 9;
+static constexpr uint8_t oemEventFirst = 0xE0;
+static constexpr uint8_t oemEventLast = 0xFF;
+static constexpr size_t oemEventSize = 13;
+static constexpr uint8_t eventMsgRev = 0x04;
+} // namespace dynamic_sensors::ipmi::sel
+
+enum class SdrRepositoryInfoOps : uint8_t
+{
+ allocCommandSupported = 0x1,
+ reserveSDRRepositoryCommandSupported = 0x2,
+ partialAddSDRSupported = 0x4,
+ deleteSDRSupported = 0x8,
+ reserved = 0x10,
+ modalLSB = 0x20,
+ modalMSB = 0x40,
+ overflow = 0x80
+};
+
+enum class GetFRUAreaAccessType : uint8_t
+{
+ byte = 0x0,
+ words = 0x1
+};
+
+enum class SensorUnits : uint8_t
+{
+ unspecified = 0x0,
+ degreesC = 0x1,
+ volts = 0x4,
+ amps = 0x5,
+ watts = 0x6,
+ joules = 0x7,
+ rpm = 0x12,
+};
+
+#pragma pack(push, 1)
+struct FRUHeader
+{
+ uint8_t commonHeaderFormat;
+ uint8_t internalOffset;
+ uint8_t chassisOffset;
+ uint8_t boardOffset;
+ uint8_t productOffset;
+ uint8_t multiRecordOffset;
+ uint8_t pad;
+ uint8_t checksum;
+};
+#pragma pack(pop)
+
+#pragma pack(push, 1)
+struct Type12Record
+{
+ get_sdr::SensorDataRecordHeader header;
+ uint8_t targetAddress;
+ uint8_t channelNumber;
+ uint8_t powerStateNotification;
+ uint8_t deviceCapabilities;
+ // define reserved bytes explicitly. The uint24_t is silently expanded to
+ // uint32_t, which ruins the byte alignment required by this structure.
+ uint8_t reserved[3];
+ uint8_t entityID;
+ uint8_t entityInstance;
+ uint8_t oem;
+ uint8_t typeLengthCode;
+ char name[16];
+
+ Type12Record(uint16_t recordID, uint8_t address, uint8_t chNumber,
+ uint8_t pwrStateNotification, uint8_t capabilities,
+ uint8_t eid, uint8_t entityInst, uint8_t mfrDefined,
+ const std::string& sensorname) :
+ targetAddress(address), channelNumber(chNumber),
+ powerStateNotification(pwrStateNotification),
+ deviceCapabilities(capabilities), reserved{}, entityID(eid),
+ entityInstance(entityInst), oem(mfrDefined)
+ {
+ get_sdr::header::set_record_id(recordID, &header);
+ header.sdr_version = ipmiSdrVersion;
+ header.record_type = 0x12;
+ size_t nameLen = std::min(sensorname.size(), sizeof(name));
+ header.record_length =
+ sizeof(Type12Record) - sizeof(get_sdr::SensorDataRecordHeader) -
+ sizeof(name) + nameLen;
+ typeLengthCode = 0xc0 | nameLen;
+ std::copy(sensorname.begin(), sensorname.begin() + nameLen, name);
+ }
+};
+#pragma pack(pop)
+
+namespace ipmi
+{
+namespace storage
+{
+
+constexpr const size_t type12Count = 2;
+ipmi_ret_t getFruSdrs(ipmi::Context::ptr ctx, size_t index,
+ get_sdr::SensorDataFruRecord& resp);
+
+ipmi_ret_t getFruSdrCount(ipmi::Context::ptr ctx, size_t& count);
+
+std::vector<uint8_t> getType8SDRs(
+ ipmi::sensor::EntityInfoMap::const_iterator& entity, uint16_t recordId);
+std::vector<uint8_t> getType12SDRs(uint16_t index, uint16_t recordId);
+std::vector<uint8_t> getNMDiscoverySDR(uint16_t index, uint16_t recordId);
+} // namespace storage
+} // namespace ipmi
diff --git a/include/ipmid-host/cmd-utils.hpp b/include/ipmid-host/cmd-utils.hpp
new file mode 100644
index 0000000..6646f03
--- /dev/null
+++ b/include/ipmid-host/cmd-utils.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <unistd.h>
+
+#include <cstdint>
+#include <functional>
+#include <tuple>
+
+namespace phosphor
+{
+namespace host
+{
+namespace command
+{
+/** @detail After sending SMS_ATN to the Host, Host comes down and
+ * asks why an 'SMS_ATN` was sent.
+ * BMC then sends 'There is a Message to be Read` as response.
+ * Host then comes down asks for Message and the specified
+ * commands and data would go as data conforming to IPMI spec.
+ *
+ * Refer: 6.13.2 Send Message Command From System Interface
+ * in IPMI V2.0 spec.
+ */
+
+/** @brief IPMI command */
+using IPMIcmd = uint8_t;
+
+/** @brief Data associated with command */
+using Data = uint8_t;
+
+/** @brief <IPMI command, Data> to be sent as payload when Host asks for
+ * the message that can be associated with the previous SMS_ATN
+ */
+using IpmiCmdData = std::pair<IPMIcmd, Data>;
+
+/** @detail Implementation specific callback function to be invoked
+ * conveying the status of the executed command. Specific
+ * implementations may then broadcast an agreed signal
+ */
+using CallBack = std::function<void(IpmiCmdData, bool)>;
+
+/** @detail Tuple encapsulating above 2 to enable using Manager by
+ * different implementations. Users of Manager will supply
+ * <Ipmi command, Data> along with the callback handler.
+ * Manager will invoke the handler onveying the status of
+ * the command.
+ */
+using CommandHandler = std::tuple<IpmiCmdData, CallBack>;
+
+} // namespace command
+} // namespace host
+} // namespace phosphor
diff --git a/include/ipmid-host/cmd.hpp b/include/ipmid-host/cmd.hpp
new file mode 100644
index 0000000..e844e8f
--- /dev/null
+++ b/include/ipmid-host/cmd.hpp
@@ -0,0 +1,9 @@
+#include <ipmid-host/cmd-utils.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+// Global Host Bound Command manager
+extern void ipmid_send_cmd_to_host(phosphor::host::command::CommandHandler&&);
+extern std::unique_ptr<sdbusplus::asio::connection>&
+ ipmid_get_sdbus_plus_handler();
diff --git a/include/ipmid/api-types.hpp b/include/ipmid/api-types.hpp
new file mode 100644
index 0000000..52c68ee
--- /dev/null
+++ b/include/ipmid/api-types.hpp
@@ -0,0 +1,565 @@
+/*
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+#include <ipmid/iana.hpp>
+
+#include <cstdint>
+#include <optional>
+#include <tuple>
+
+namespace ipmi
+{
+
+using Iana = oem::Number;
+
+using Group = uint8_t;
+constexpr Group groupPICMG = 0x00;
+constexpr Group groupDMTG = 0x01;
+constexpr Group groupSSI = 0x02;
+constexpr Group groupVSO = 0x03;
+#ifdef ARM_SBMR_SUPPORT
+constexpr Group groupSBMR = 0xAE;
+#endif
+constexpr Group groupDCMI = 0xDC;
+
+/*
+ * Set the priority as the lowest number that is necessary so
+ * it is possible that others can override it if desired.
+ * This may be linked to what level of integration the handler
+ * is being created at.
+ */
+constexpr int prioOpenBmcBase = 10;
+constexpr int prioOemBase = 20;
+constexpr int prioOdmBase = 30;
+constexpr int prioCustomBase = 40;
+constexpr int prioMax = 50;
+
+/*
+ * Channel IDs pulled from the IPMI 2.0 specification
+ */
+constexpr int channelPrimaryIpmb = 0x00;
+// 0x01-0x0B Implementation specific
+// Implementation specific channel numbers are specified
+// by a configuration file external to ipmid
+// 0x0C-0x0D reserved
+constexpr int channelCurrentIface = 0x0E; // 'Present I/F'
+constexpr int channelSystemIface = 0x0F;
+
+/*
+ * Specifies the minimum privilege level required to execute the command
+ * This means the command can be executed at a given privilege level or higher
+ * privilege level. Those commands which can be executed via system interface
+ * only should use SYSTEM_INTERFACE
+ */
+enum class Privilege : uint8_t
+{
+ None = 0x00,
+ Callback,
+ User,
+ Operator,
+ Admin,
+ Oem,
+};
+
+// IPMI Net Function number as specified by IPMI V2.0 spec.
+using NetFn = uint8_t;
+
+// IPMI Command for a Net Function number as specified by IPMI V2.0 spec.
+using Cmd = uint8_t;
+
+// ipmi function return the status code
+using Cc = uint8_t;
+
+// IPMI 2.0 and DCMI 1.5 standard commands, namespaced by NetFn
+// OEM and non-standard commands should be defined where they are used
+namespace app
+{
+// 0x00 reserved
+constexpr Cmd cmdGetDeviceId = 0x01;
+constexpr Cmd cmdColdReset = 0x02;
+constexpr Cmd cmdWarmReset = 0x03;
+constexpr Cmd cmdGetSelfTestResults = 0x04;
+constexpr Cmd cmdManufacturingTestOn = 0x05;
+constexpr Cmd cmdSetAcpiPowerState = 0x06;
+constexpr Cmd cmdGetAcpiPowerState = 0x07;
+constexpr Cmd cmdGetDeviceGuid = 0x08;
+constexpr Cmd cmdGetNetFnSupport = 0x09;
+constexpr Cmd cmdGetCmdSupport = 0x0A;
+constexpr Cmd cmdGetCmdSubFnSupport = 0x0B;
+constexpr Cmd cmdGetConfigurableCmds = 0x0C;
+constexpr Cmd cmdGetConfigurableCmdSubFns = 0x0D;
+// 0x0E-0x21 unassigned
+constexpr Cmd cmdResetWatchdogTimer = 0x22;
+// 0x23 unassigned
+constexpr Cmd cmdSetWatchdogTimer = 0x24;
+constexpr Cmd cmdGetWatchdogTimer = 0x25;
+// 0x26-0x2D unassigned
+constexpr Cmd cmdSetBmcGlobalEnables = 0x2E;
+constexpr Cmd cmdGetBmcGlobalEnables = 0x2F;
+constexpr Cmd cmdClearMessageFlags = 0x30;
+constexpr Cmd cmdGetMessageFlags = 0x31;
+constexpr Cmd cmdEnableMessageChannelRcv = 0x32;
+constexpr Cmd cmdGetMessage = 0x33;
+constexpr Cmd cmdSendMessage = 0x34;
+constexpr Cmd cmdReadEventMessageBuffer = 0x35;
+constexpr Cmd cmdGetBtIfaceCapabilities = 0x36;
+constexpr Cmd cmdGetSystemGuid = 0x37;
+constexpr Cmd cmdGetChannelAuthCapabilities = 0x38;
+constexpr Cmd cmdGetSessionChallenge = 0x39;
+constexpr Cmd cmdActivateSession = 0x3A;
+constexpr Cmd cmdSetSessionPrivilegeLevel = 0x3B;
+constexpr Cmd cmdCloseSession = 0x3C;
+constexpr Cmd cmdGetSessionInfo = 0x3D;
+// 0x3E unassigned
+constexpr Cmd cmdGetAuthCode = 0x3F;
+constexpr Cmd cmdSetChannelAccess = 0x40;
+constexpr Cmd cmdGetChannelAccess = 0x41;
+constexpr Cmd cmdGetChannelInfoCommand = 0x42;
+constexpr Cmd cmdSetUserAccessCommand = 0x43;
+constexpr Cmd cmdGetUserAccessCommand = 0x44;
+constexpr Cmd cmdSetUserName = 0x45;
+constexpr Cmd cmdGetUserNameCommand = 0x46;
+constexpr Cmd cmdSetUserPasswordCommand = 0x47;
+constexpr Cmd cmdActivatePayload = 0x48;
+constexpr Cmd cmdDeactivatePayload = 0x49;
+constexpr Cmd cmdGetPayloadActivationStatus = 0x4A;
+constexpr Cmd cmdGetPayloadInstanceInfo = 0x4B;
+constexpr Cmd cmdSetUserPayloadAccess = 0x4C;
+constexpr Cmd cmdGetUserPayloadAccess = 0x4D;
+constexpr Cmd cmdGetChannelPayloadSupport = 0x4E;
+constexpr Cmd cmdGetChannelPayloadVersion = 0x4F;
+constexpr Cmd cmdGetChannelOemPayloadInfo = 0x50;
+// 0x51 unassigned
+constexpr Cmd cmdMasterWriteRead = 0x52;
+// 0x53 unassigned
+constexpr Cmd cmdGetChannelCipherSuites = 0x54;
+constexpr Cmd cmdSuspendResumePayloadEnc = 0x55;
+constexpr Cmd cmdSetChannelSecurityKeys = 0x56;
+constexpr Cmd cmdGetSystemIfCapabilities = 0x57;
+constexpr Cmd cmdSetSystemInfoParameters = 0x58;
+constexpr Cmd cmdGetSystemInfoParameters = 0x59;
+// 0x5A-0x5F unassigned
+constexpr Cmd cmdSetCommandEnables = 0x60;
+constexpr Cmd cmdGetCommandEnables = 0x61;
+constexpr Cmd cmdSetCommandSubFnEnables = 0x62;
+constexpr Cmd cmdGetCommandSubFnEnables = 0x63;
+constexpr Cmd cmdGetOemNetFnIanaSupport = 0x64;
+// 0x65-0xff unassigned
+} // namespace app
+
+namespace chassis
+{
+constexpr Cmd cmdGetChassisCapabilities = 0x00;
+constexpr Cmd cmdGetChassisStatus = 0x01;
+constexpr Cmd cmdChassisControl = 0x02;
+constexpr Cmd cmdChassisReset = 0x03;
+constexpr Cmd cmdChassisIdentify = 0x04;
+constexpr Cmd cmdSetChassisCapabilities = 0x05;
+constexpr Cmd cmdSetPowerRestorePolicy = 0x06;
+constexpr Cmd cmdGetSystemRestartCause = 0x07;
+constexpr Cmd cmdSetSystemBootOptions = 0x08;
+constexpr Cmd cmdGetSystemBootOptions = 0x09;
+constexpr Cmd cmdSetFrontPanelButtonEnables = 0x0A;
+constexpr Cmd cmdSetPowerCycleInterval = 0x0B;
+// 0x0C-0x0E unassigned
+constexpr Cmd cmdGetPohCounter = 0x0F;
+// 0x10-0xFF unassigned
+} // namespace chassis
+
+namespace sensor_event
+{
+constexpr Cmd cmdSetEventReceiver = 0x00;
+constexpr Cmd cmdGetEventReceiver = 0x01;
+constexpr Cmd cmdPlatformEvent = 0x02;
+// 0x03-0x0F unassigned
+constexpr Cmd cmdGetPefCapabilities = 0x10;
+constexpr Cmd cmdArmPefPostponeTimer = 0x11;
+constexpr Cmd cmdSetPefConfigurationParams = 0x12;
+constexpr Cmd cmdGetPefConfigurationParams = 0x13;
+constexpr Cmd cmdSetLastProcessedEventId = 0x14;
+constexpr Cmd cmdGetLastProcessedEventId = 0x15;
+constexpr Cmd cmdAlertImmediate = 0x16;
+constexpr Cmd cmdPetAcknowledge = 0x17;
+constexpr Cmd cmdGetDeviceSdrInfo = 0x20;
+constexpr Cmd cmdGetDeviceSdr = 0x21;
+constexpr Cmd cmdReserveDeviceSdrRepository = 0x22;
+constexpr Cmd cmdGetSensorReadingFactors = 0x23;
+constexpr Cmd cmdSetSensorHysteresis = 0x24;
+constexpr Cmd cmdGetSensorHysteresis = 0x25;
+constexpr Cmd cmdSetSensorThreshold = 0x26;
+constexpr Cmd cmdGetSensorThreshold = 0x27;
+constexpr Cmd cmdSetSensorEventEnable = 0x28;
+constexpr Cmd cmdGetSensorEventEnable = 0x29;
+constexpr Cmd cmdRearmSensorEvents = 0x2A;
+constexpr Cmd cmdGetSensorEventStatus = 0x2B;
+constexpr Cmd cmdGetSensorReading = 0x2D;
+constexpr Cmd cmdSetSensorType = 0x2E;
+constexpr Cmd cmdGetSensorType = 0x2F;
+constexpr Cmd cmdSetSensorReadingAndEvtSts = 0x30;
+// 0x31-0xFF unassigned
+} // namespace sensor_event
+
+namespace storage
+{
+// 0x00-0x0F unassigned
+constexpr Cmd cmdGetFruInventoryAreaInfo = 0x10;
+constexpr Cmd cmdReadFruData = 0x11;
+constexpr Cmd cmdWriteFruData = 0x12;
+// 0x13-0x1F unassigned
+constexpr Cmd cmdGetSdrRepositoryInfo = 0x20;
+constexpr Cmd cmdGetSdrRepositoryAllocInfo = 0x21;
+constexpr Cmd cmdReserveSdrRepository = 0x22;
+constexpr Cmd cmdGetSdr = 0x23;
+constexpr Cmd cmdAddSdr = 0x24;
+constexpr Cmd cmdPartialAddSdr = 0x25;
+constexpr Cmd cmdDeleteSdr = 0x26;
+constexpr Cmd cmdClearSdrRepository = 0x27;
+constexpr Cmd cmdGetSdrRepositoryTime = 0x28;
+constexpr Cmd cmdSetSdrRepositoryTime = 0x29;
+constexpr Cmd cmdEnterSdrRepoUpdateMode = 0x2A;
+constexpr Cmd cmdExitSdrReposUpdateMode = 0x2B;
+constexpr Cmd cmdRunInitializationAgent = 0x2C;
+// 0x2D-0x3F unassigned
+constexpr Cmd cmdGetSelInfo = 0x40;
+constexpr Cmd cmdGetSelAllocationInfo = 0x41;
+constexpr Cmd cmdReserveSel = 0x42;
+constexpr Cmd cmdGetSelEntry = 0x43;
+constexpr Cmd cmdAddSelEntry = 0x44;
+constexpr Cmd cmdPartialAddSelEntry = 0x45;
+constexpr Cmd cmdDeleteSelEntry = 0x46;
+constexpr Cmd cmdClearSel = 0x47;
+constexpr Cmd cmdGetSelTime = 0x48;
+constexpr Cmd cmdSetSelTime = 0x49;
+constexpr Cmd cmdGetAuxiliaryLogStatus = 0x5A;
+constexpr Cmd cmdSetAuxiliaryLogStatus = 0x5B;
+constexpr Cmd cmdGetSelTimeUtcOffset = 0x5C;
+constexpr Cmd cmdSetSelTimeUtcOffset = 0x5D;
+// 0x5E-0xFF unassigned
+} // namespace storage
+
+namespace transport
+{
+constexpr Cmd cmdSetLanConfigParameters = 0x01;
+constexpr Cmd cmdGetLanConfigParameters = 0x02;
+constexpr Cmd cmdSuspendBmcArps = 0x03;
+constexpr Cmd cmdGetIpUdpRmcpStatistics = 0x04;
+constexpr Cmd cmdSetSerialModemConfig = 0x10;
+constexpr Cmd cmdGetSerialModemConfig = 0x11;
+constexpr Cmd cmdSetSerialModemMux = 0x12;
+constexpr Cmd cmdGetTapResponseCodes = 0x13;
+constexpr Cmd cmdSetPppUdpProxyTransmitData = 0x14;
+constexpr Cmd cmdGetPppUdpProxyTransmitData = 0x15;
+constexpr Cmd cmdSendPppUdpProxyPacket = 0x16;
+constexpr Cmd cmdGetPppUdpProxyReceiveData = 0x17;
+constexpr Cmd cmdSerialModemConnActive = 0x18;
+constexpr Cmd cmdCallback = 0x19;
+constexpr Cmd cmdSetUserCallbackOptions = 0x1A;
+constexpr Cmd cmdGetUserCallbackOptions = 0x1B;
+constexpr Cmd cmdSetSerialRoutingMux = 0x1C;
+constexpr Cmd cmdSolActivating = 0x20;
+constexpr Cmd cmdSetSolConfigParameters = 0x21;
+constexpr Cmd cmdGetSolConfigParameters = 0x22;
+constexpr Cmd cmdForwardedCommand = 0x30;
+constexpr Cmd cmdSetForwardedCommands = 0x31;
+constexpr Cmd cmdGetForwardedCommands = 0x32;
+constexpr Cmd cmdEnableForwardedCommands = 0x33;
+} // namespace transport
+
+namespace bridge
+{
+constexpr Cmd cmdGetBridgeState = 0x00;
+constexpr Cmd cmdSetBridgeState = 0x01;
+constexpr Cmd cmdGetIcmbAddress = 0x02;
+constexpr Cmd cmdSetIcmbAddress = 0x03;
+constexpr Cmd cmdSetBridgeProxyAddress = 0x04;
+constexpr Cmd cmdGetBridgeStatistics = 0x05;
+constexpr Cmd cmdGetIcmbCapabilities = 0x06;
+constexpr Cmd cmdClearBridgeStatistics = 0x08;
+constexpr Cmd cmdGetBridgeProxyAddress = 0x09;
+constexpr Cmd cmdGetIcmbConnectorInfo = 0x0A;
+constexpr Cmd cmdGetIcmbConnectionId = 0x0B;
+constexpr Cmd cmdSendIcmbConnectionId = 0x0C;
+constexpr Cmd cmdPrepareForDiscovery = 0x10;
+constexpr Cmd cmdGetAddresses = 0x11;
+constexpr Cmd cmdSetDiscovered = 0x12;
+constexpr Cmd cmdGetChassisDeviceId = 0x13;
+constexpr Cmd cmdSetChassisDeviceId = 0x14;
+constexpr Cmd cmdBridgeRequest = 0x20;
+constexpr Cmd cmdBridgeMessage = 0x21;
+// 0x22-0x2F unassigned
+constexpr Cmd cmdGetEventCount = 0x30;
+constexpr Cmd cmdSetEventDestination = 0x31;
+constexpr Cmd cmdSetEventReceptionState = 0x32;
+constexpr Cmd cmdSendIcmbEventMessage = 0x33;
+constexpr Cmd cmdGetEventDestination = 0x34;
+constexpr Cmd cmdGetEventReceptionState = 0x35;
+// 0xC0-0xFE OEM Commands
+constexpr Cmd cmdErrorReport = 0xFF;
+} // namespace bridge
+
+namespace dcmi
+{
+constexpr Cmd cmdGetDcmiCapabilitiesInfo = 0x01;
+constexpr Cmd cmdGetPowerReading = 0x02;
+constexpr Cmd cmdGetPowerLimit = 0x03;
+constexpr Cmd cmdSetPowerLimit = 0x04;
+constexpr Cmd cmdActDeactivatePwrLimit = 0x05;
+constexpr Cmd cmdGetAssetTag = 0x06;
+constexpr Cmd cmdGetDcmiSensorInfo = 0x07;
+constexpr Cmd cmdSetAssetTag = 0x08;
+constexpr Cmd cmdGetMgmtCntlrIdString = 0x09;
+constexpr Cmd cmdSetMgmtCntlrIdString = 0x0A;
+constexpr Cmd cmdSetThermalLimit = 0x0B;
+constexpr Cmd cmdGetThermalLimit = 0x0C;
+constexpr Cmd cmdGetTemperatureReadings = 0x10;
+constexpr Cmd cmdSetDcmiConfigParameters = 0x12;
+constexpr Cmd cmdGetDcmiConfigParameters = 0x13;
+} // namespace dcmi
+
+#ifdef ARM_SBMR_SUPPORT
+namespace sbmr
+{
+constexpr Cmd cmdSendBootProgressCode = 0x02;
+constexpr Cmd cmdGetBootProgressCode = 0x03;
+} // namespace sbmr
+#endif
+
+// These are the command network functions, the response
+// network functions are the function + 1. So to determine
+// the proper network function which issued the command
+// associated with a response, subtract 1.
+// Note: these will be left shifted when combined with the LUN
+constexpr NetFn netFnChassis = 0x00;
+constexpr NetFn netFnBridge = 0x02;
+constexpr NetFn netFnSensor = 0x04;
+constexpr NetFn netFnApp = 0x06;
+constexpr NetFn netFnFirmware = 0x08;
+constexpr NetFn netFnStorage = 0x0A;
+constexpr NetFn netFnTransport = 0x0C;
+// reserved 0Eh..28h
+constexpr NetFn netFnGroup = 0x2C;
+constexpr NetFn netFnOem = 0x2E;
+constexpr NetFn netFnOemOne = 0x30;
+constexpr NetFn netFnOemTwo = 0x32;
+constexpr NetFn netFnOemThree = 0x34;
+constexpr NetFn netFnOemFour = 0x36;
+constexpr NetFn netFnOemFive = 0x38;
+constexpr NetFn netFnOemSix = 0x3A;
+constexpr NetFn netFnOemSeven = 0x3C;
+constexpr NetFn netFnOemEight = 0x3E;
+
+// IPMI commands for net functions. Callbacks using this should be careful to
+// parse arguments to the sub-functions and can take advantage of the built-in
+// message handling mechanism to create custom routing
+constexpr Cmd cmdWildcard = 0xFF;
+
+// IPMI standard completion codes specified by the IPMI V2.0 spec.
+//
+// This might have been an enum class, but that would make it hard for
+// OEM- and command-specific completion codes to be added elsewhere.
+//
+// Custom completion codes can be defined in individual modules for
+// command specific errors in the 0x80-0xBE range
+//
+// Alternately, OEM completion codes are in the 0x01-0x7E range
+constexpr Cc ccSuccess = 0x00;
+constexpr Cc ccBusy = 0xC0;
+constexpr Cc ccInvalidCommand = 0xC1;
+constexpr Cc ccInvalidCommandOnLun = 0xC2;
+constexpr Cc ccTimeout = 0xC3;
+constexpr Cc ccOutOfSpace = 0xC4;
+constexpr Cc ccInvalidReservationId = 0xC5;
+constexpr Cc ccReqDataTruncated = 0xC6;
+constexpr Cc ccReqDataLenInvalid = 0xC7;
+constexpr Cc ccReqDataLenExceeded = 0xC8;
+constexpr Cc ccParmOutOfRange = 0xC9;
+constexpr Cc ccRetBytesUnavailable = 0xCA;
+constexpr Cc ccSensorInvalid = 0xCB;
+constexpr Cc ccInvalidFieldRequest = 0xCC;
+constexpr Cc ccIllegalCommand = 0xCD;
+constexpr Cc ccResponseError = 0xCE;
+constexpr Cc ccDuplicateRequest = 0xCF;
+constexpr Cc ccCmdFailSdrMode = 0xD0;
+constexpr Cc ccCmdFailFwUpdMode = 0xD1;
+constexpr Cc ccCmdFailInitAgent = 0xD2;
+constexpr Cc ccDestinationUnavailable = 0xD3;
+constexpr Cc ccInsufficientPrivilege = 0xD4;
+constexpr Cc ccCommandNotAvailable = 0xD5;
+constexpr Cc ccCommandDisabled = 0xD6;
+constexpr Cc ccUnspecifiedError = 0xFF;
+
+/* ipmi often has two return types:
+ * 1. Failure: CC is non-zero; no trailing data
+ * 2. Success: CC is zero; trailing data (usually a fixed type)
+ *
+ * using ipmi::response(cc, ...), it will automatically always pack
+ * the correct type for the response without having to explicitly type out all
+ * the parameters that the function would return.
+ *
+ * To enable this feature, you just define the ipmi function as returning an
+ * ipmi::RspType which has the optional trailing data built in, with your types
+ * defined as parameters.
+ */
+
+template <typename... RetTypes>
+using RspType = std::tuple<ipmi::Cc, std::optional<std::tuple<RetTypes...>>>;
+
+/**
+ * @brief helper function to create an IPMI response tuple
+ *
+ * IPMI handlers all return a tuple with two parts: a completion code and an
+ * optional tuple containing the rest of the data to return. This helper
+ * function makes it easier by constructing that out of an arbitrary number of
+ * arguments.
+ *
+ * @param cc - the completion code for the response
+ * @param args... - the optional list of values to return
+ *
+ * @return a standard IPMI return type (as described above)
+ */
+template <typename... Args>
+static inline auto response(ipmi::Cc cc, Args&&... args)
+{
+ return std::make_tuple(cc, std::make_optional(std::make_tuple(args...)));
+}
+static inline auto response(ipmi::Cc cc)
+{
+ return std::make_tuple(cc, std::nullopt);
+}
+
+/**
+ * @brief helper function to create an IPMI success response tuple
+ *
+ * IPMI handlers all return a tuple with two parts: a completion code and an
+ * optional tuple containing the rest of the data to return. This helper
+ * function makes it easier by constructing that out of an arbitrary number of
+ * arguments. Because it is a success response, this automatically packs
+ * the completion code, without needing to explicitly pass it in.
+ *
+ * @param args... - the optional list of values to return
+ *
+ * @return a standard IPMI return type (as described above)
+ */
+template <typename... Args>
+static inline auto responseSuccess(Args&&... args)
+{
+ return response(ipmi::ccSuccess, args...);
+}
+static inline auto responseSuccess()
+{
+ return response(ipmi::ccSuccess);
+}
+
+/* helper functions for the various standard error response types */
+static inline auto responseBusy()
+{
+ return response(ccBusy);
+}
+static inline auto responseInvalidCommand()
+{
+ return response(ccInvalidCommand);
+}
+static inline auto responseInvalidCommandOnLun()
+{
+ return response(ccInvalidCommandOnLun);
+}
+static inline auto responseTimeout()
+{
+ return response(ccTimeout);
+}
+static inline auto responseOutOfSpace()
+{
+ return response(ccOutOfSpace);
+}
+static inline auto responseInvalidReservationId()
+{
+ return response(ccInvalidReservationId);
+}
+static inline auto responseReqDataTruncated()
+{
+ return response(ccReqDataTruncated);
+}
+static inline auto responseReqDataLenInvalid()
+{
+ return response(ccReqDataLenInvalid);
+}
+static inline auto responseReqDataLenExceeded()
+{
+ return response(ccReqDataLenExceeded);
+}
+static inline auto responseParmOutOfRange()
+{
+ return response(ccParmOutOfRange);
+}
+static inline auto responseRetBytesUnavailable()
+{
+ return response(ccRetBytesUnavailable);
+}
+static inline auto responseSensorInvalid()
+{
+ return response(ccSensorInvalid);
+}
+static inline auto responseInvalidFieldRequest()
+{
+ return response(ccInvalidFieldRequest);
+}
+static inline auto responseIllegalCommand()
+{
+ return response(ccIllegalCommand);
+}
+static inline auto responseResponseError()
+{
+ return response(ccResponseError);
+}
+static inline auto responseDuplicateRequest()
+{
+ return response(ccDuplicateRequest);
+}
+static inline auto responseCmdFailSdrMode()
+{
+ return response(ccCmdFailSdrMode);
+}
+static inline auto responseCmdFailFwUpdMode()
+{
+ return response(ccCmdFailFwUpdMode);
+}
+static inline auto responseCmdFailInitAgent()
+{
+ return response(ccCmdFailInitAgent);
+}
+static inline auto responseDestinationUnavailable()
+{
+ return response(ccDestinationUnavailable);
+}
+static inline auto responseInsufficientPrivilege()
+{
+ return response(ccInsufficientPrivilege);
+}
+static inline auto responseCommandNotAvailable()
+{
+ return response(ccCommandNotAvailable);
+}
+static inline auto responseCommandDisabled()
+{
+ return response(ccCommandDisabled);
+}
+static inline auto responseUnspecifiedError()
+{
+ return response(ccUnspecifiedError);
+}
+
+} // namespace ipmi
diff --git a/include/ipmid/api.h b/include/ipmid/api.h
new file mode 100644
index 0000000..fd6fee6
--- /dev/null
+++ b/include/ipmid/api.h
@@ -0,0 +1,153 @@
+#ifndef __HOST_IPMID_IPMI_COMMON_H__
+#define __HOST_IPMID_IPMI_COMMON_H__
+
+#include <systemd/sd-bus.h>
+
+#include <cstddef>
+
+/*
+ * Specifies the minimum privilege level required to execute the command
+ * This means the command can be executed at a given privilege level or higher
+ * privilege level. Those commands which can be executed via system interface
+ * only should use SYSTEM_INTERFACE
+ */
+enum CommandPrivilege
+{
+ PRIVILEGE_CALLBACK = 0x01,
+ PRIVILEGE_USER,
+ PRIVILEGE_OPERATOR,
+ PRIVILEGE_ADMIN,
+ PRIVILEGE_OEM,
+ SYSTEM_INTERFACE = 0xFF,
+};
+
+// length of Completion Code and its ALWAYS _1_
+#define IPMI_CC_LEN 1
+
+// IPMI Net Function number as specified by IPMI V2.0 spec.
+// Example :
+// NETFUN_APP = (0x06 << 2),
+typedef unsigned char ipmi_netfn_t;
+
+// IPMI Command for a Net Function number as specified by IPMI V2.0 spec.
+typedef unsigned char ipmi_cmd_t;
+
+// Buffer containing data from sender of netfn and command as part of request
+typedef void* ipmi_request_t;
+
+// This is the response buffer that the provider of [netfn,cmd] will send back
+// to the caller. Provider will allocate the memory inside the handler and then
+// will do a memcpy to this response buffer and also will set the data size
+// parameter to the size of the buffer.
+// EXAMPLE :
+// unsigned char str[] = {0x00, 0x01, 0xFE, 0xFF, 0x0A, 0x01};
+// *data_len = 6;
+// memcpy(response, &str, *data_len);
+typedef void* ipmi_response_t;
+
+// This buffer contains any *user specific* data that is of interest only to the
+// plugin. For a ipmi function router, this data is opaque. At the time of
+// registering the plugin handlers, plugin may optionally allocate a memory and
+// fill in whatever needed that will be of help during the actual handling of
+// command. IPMID will just pass the netfn, cmd and also this data to plugins
+// during the command handler invocation.
+typedef void* ipmi_context_t;
+
+// Length of request / response buffer depending on whether the data is a
+// request or a response from a plugin handler.
+typedef std::size_t* ipmi_data_len_t;
+
+// Plugin function return the status code
+typedef unsigned char ipmi_ret_t;
+
+typedef enum CommandPrivilege ipmi_cmd_privilege_t;
+
+// This is the callback handler that the plugin registers with IPMID. IPMI
+// function router will then make a call to this callback handler with the
+// necessary arguments of netfn, cmd, request, response, size and context.
+typedef ipmi_ret_t (*ipmid_callback_t)(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t,
+ ipmi_response_t, ipmi_data_len_t,
+ ipmi_context_t);
+
+// This is the constructor function that is called into by each plugin handlers.
+// When ipmi sets up the callback handlers, a call is made to this with
+// information of netfn, cmd, callback handler pointer and context data.
+void ipmi_register_callback(ipmi_netfn_t, ipmi_cmd_t, ipmi_context_t,
+ ipmid_callback_t, ipmi_cmd_privilege_t);
+
+unsigned short reserveSel(void);
+bool checkSELReservation(unsigned short id);
+void cancelSELReservation(void);
+
+// These are the command network functions, the response
+// network functions are the function + 1. So to determine
+// the proper network function which issued the command
+// associated with a response, subtract 1.
+// Note: these are also shifted left to make room for the LUN.
+enum ipmi_net_fns
+{
+ NETFUN_CHASSIS = 0x00,
+ NETFUN_BRIDGE = 0x02,
+ NETFUN_SENSOR = 0x04,
+ NETFUN_APP = 0x06,
+ NETFUN_FIRMWARE = 0x08,
+ NETFUN_STORAGE = 0x0a,
+ NETFUN_TRANSPORT = 0x0c,
+ NETFUN_GRPEXT = 0x2c,
+ NETFUN_OEM_GROUP = 0x2e,
+ NETFUN_NONE = 0x30,
+ NETFUN_OEM = 0x32,
+ NETFUN_IBM_OEM = 0x3A
+};
+
+// Return (completion) codes from a IPMI operation as needed by IPMI V2.0 spec.
+enum ipmi_return_codes
+{
+ IPMI_CC_OK = 0x00,
+ IPMI_DCMI_CC_NO_ACTIVE_POWER_LIMIT = 0x80,
+ IPMI_WDOG_CC_NOT_INIT = 0x80,
+ IPMI_CC_SYSTEM_INFO_PARAMETER_NOT_SUPPORTED = 0x80,
+ IPMI_CC_SYSTEM_INFO_PARAMETER_SET_READ_ONLY = 0x82,
+ IPMI_CC_BUSY = 0xC0,
+ IPMI_CC_INVALID = 0xC1,
+ IPMI_CC_TIMEOUT = 0xC3,
+ IPMI_CC_OUT_OF_SPACE = 0xC4,
+ IPMI_CC_INVALID_RESERVATION_ID = 0xC5,
+ IPMI_CC_REQ_DATA_TRUNCATED = 0xC6,
+ IPMI_CC_REQ_DATA_LEN_INVALID = 0xC7,
+ IPMI_CC_PARM_OUT_OF_RANGE = 0xC9,
+ IPMI_CC_REQUESTED_TOO_MANY_BYTES = 0xCA,
+ IPMI_CC_SENSOR_INVALID = 0xCB,
+ IPMI_CC_INVALID_FIELD_REQUEST = 0xCC,
+ IPMI_CC_ILLEGAL_COMMAND = 0xCD,
+ IPMI_CC_RESPONSE_ERROR = 0xCE,
+ IPMI_CC_INSUFFICIENT_PRIVILEGE = 0xD4,
+ IPMI_CC_UNSPECIFIED_ERROR = 0xFF,
+};
+
+// Temp solution: To detect the request source channel.
+// There is no stright forward way to get the exact
+// channel so we are hardcoding it to KCS in case host-ipmid
+// and LAN1 for netipmid.
+// we can't differentiate between LAN1 & LAN2 for netipmid in this logic.
+// As our current design will not be able to support the same. This is done
+// so that in all the places where ever we need to use the self channel can be
+// be implemented properly and based on new architecture.this can be updated.
+typedef enum
+{
+ interfaceKCS = 0,
+ interfaceLAN1 = 1,
+ interfaceUnknown = 0xFF
+} EInterfaceIndex;
+
+EInterfaceIndex getInterfaceIndex(void);
+
+sd_bus* ipmid_get_sd_bus_connection(void);
+sd_event* ipmid_get_sd_event_connection(void);
+sd_bus_slot* ipmid_get_sd_bus_slot(void);
+
+// move this from ipmid.hpp, which is now gone
+// this should not be used. Use the channel API to get the channel size
+#define MAX_IPMI_BUFFER 64
+
+#endif
diff --git a/include/ipmid/api.hpp b/include/ipmid/api.hpp
new file mode 100644
index 0000000..68e24ea
--- /dev/null
+++ b/include/ipmid/api.hpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#define ALLOW_DEPRECATED_API 1
+// make it possible to ONLY include api.hpp during the transition
+#ifdef ALLOW_DEPRECATED_API
+#include <ipmid/api.h>
+#endif
+
+#include <boost/asio/io_context.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/filter.hpp>
+#include <ipmid/handler.hpp>
+#include <ipmid/message/types.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+// any client can interact with the main asio context
+std::shared_ptr<boost::asio::io_context> getIoContext();
+
+// any client can interact with the main sdbus
+std::shared_ptr<sdbusplus::asio::connection> getSdBus();
+
+/**
+ * @brief post some work to the async exection queue
+ *
+ * The IPMI daemon runs an async exection queue; this allows any function to
+ * pass in work to be executed in that context
+ *
+ * @tparam WorkFn - a function of type void(void)
+ * @param work - the callback function to be executed
+ */
+template <typename WorkFn>
+static inline void post_work(WorkFn work)
+{
+ boost::asio::post(*getIoContext(), std::forward<WorkFn>(work));
+}
+
+enum class SignalResponse : int
+{
+ breakExecution,
+ continueExecution,
+};
+
+/**
+ * @brief add a signal handler
+ *
+ * This registers a handler to be called asynchronously via the execution
+ * queue when the specified signal is received.
+ *
+ * Priority allows a signal handler to specify what order in the handler
+ * chain it gets called. Lower priority numbers will cause the handler to
+ * be executed later in the chain, while the highest priority numbers will cause
+ * the handler to be executed first.
+ *
+ * In order to facilitate a chain of handlers, each handler in the chain will be
+ * able to return breakExecution or continueExecution. Returning breakExecution
+ * will break the chain and no further handlers will execute for that signal.
+ * Returning continueExecution will allow lower-priority handlers to execute.
+ *
+ * By default, the main asio execution loop will register a low priority
+ * (prioOpenBmcBase) handler for SIGINT and SIGTERM to cause the process to stop
+ * on either of those signals. To prevent one of those signals from causing the
+ * process to stop, simply register a higher priority handler that returns
+ * breakExecution.
+ *
+ * @param int - priority of handler
+ * @param int - signal number to wait for
+ * @param handler - the callback function to be executed
+ */
+void registerSignalHandler(int priority, int signalNumber,
+ const std::function<SignalResponse(int)>& handler);
diff --git a/include/ipmid/entity_map_json.hpp b/include/ipmid/entity_map_json.hpp
new file mode 100644
index 0000000..bfc8e72
--- /dev/null
+++ b/include/ipmid/entity_map_json.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <memory>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+/**
+ * @brief Grab a handle to the entity map.
+ */
+const EntityInfoMap& getIpmiEntityRecords();
+
+/**
+ * @brief Open the default entity map json file, and if present and valid json,
+ * return a built entity map.
+ *
+ * @return the map
+ */
+EntityInfoMap buildEntityMapFromFile();
+
+/**
+ * @brief Given json data validate the data matches the expected format for the
+ * entity map configuration and parse the data into a map of the entities.
+ *
+ * If any entry is invalid, the entire contents passed in is disregarded as
+ * possibly corrupt.
+ *
+ * @param[in] data - the json data
+ * @return the map
+ */
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data);
+
+/**
+ * @brief Owner of the EntityInfoMap.
+ */
+class EntityInfoMapContainer
+{
+ public:
+ /** Get ahold of the owner. */
+ static EntityInfoMapContainer* getContainer();
+ /** Get ahold of the records. */
+ const EntityInfoMap& getIpmiEntityRecords();
+
+ private:
+ EntityInfoMapContainer(const EntityInfoMap& entityRecords) :
+ entityRecords(entityRecords)
+ {}
+ EntityInfoMap entityRecords;
+};
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/include/ipmid/filter.hpp b/include/ipmid/filter.hpp
new file mode 100644
index 0000000..d57b838
--- /dev/null
+++ b/include/ipmid/filter.hpp
@@ -0,0 +1,136 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include <boost/callable_traits.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <tuple>
+#include <utility>
+
+namespace ipmi
+{
+
+using FilterFunction = ipmi::Cc(ipmi::message::Request::ptr);
+
+/**
+ * @brief Filter base class for dealing with IPMI request/response
+ *
+ * The subclasses are all templated so they can provide access to any type of
+ * command callback functions.
+ */
+class FilterBase
+{
+ public:
+ using ptr = std::shared_ptr<FilterBase>;
+
+ virtual ~FilterBase() = default;
+
+ virtual ipmi::Cc call(message::Request::ptr request) = 0;
+};
+
+/**
+ * @brief filter concrete class
+ *
+ * This is the base template that ipmi filters will resolve into. This is
+ * essentially just a wrapper to hold the filter callback so it can be stored in
+ * the filter list.
+ *
+ * Filters are called with a ipmi::message::Request shared_ptr on all IPMI
+ * commands in priority order and each filter has the opportunity to reject the
+ * command (by returning an IPMI error competion code.) If all the filters
+ * return success, the actual IPMI command will be executed. Filters can reject
+ * the command for any reason, based on system state, the context, the command
+ * payload, etc.
+ */
+template <typename Filter>
+class IpmiFilter : public FilterBase
+{
+ public:
+ IpmiFilter(Filter&& filter) : filter_(std::move(filter)) {}
+
+ ipmi::Cc call(message::Request::ptr request) override
+ {
+ return filter_(request);
+ }
+
+ private:
+ Filter filter_;
+};
+
+/**
+ * @brief helper function to construct a filter object
+ *
+ * This is called internally by the ipmi::registerFilter function.
+ */
+template <typename Filter>
+static inline auto makeFilter(Filter&& filter)
+{
+ FilterBase::ptr ptr(new IpmiFilter<Filter>(std::forward<Filter>(filter)));
+ return ptr;
+}
+template <typename Filter>
+static inline auto makeFilter(const Filter& filter)
+{
+ Filter lFilter = filter;
+ return makeFilter(std::forward<Filter>(lFilter));
+}
+
+namespace impl
+{
+
+// IPMI command filter registration implementation
+void registerFilter(int prio, ::ipmi::FilterBase::ptr filter);
+
+} // namespace impl
+
+/**
+ * @brief IPMI command filter registration function
+ *
+ * This function should be used to register IPMI command filter functions.
+ * This function just passes the callback to makeFilter, which creates a
+ * wrapper functor object that ultimately calls the callback.
+ *
+ * Filters are called with a ipmi::message::Request shared_ptr on all IPMI
+ * commands in priority order and each filter has the opportunity to reject the
+ * command (by returning an IPMI error competion code.) If all the filters
+ * return success, the actual IPMI command will be executed. Filters can reject
+ * the command for any reason, based on system state, the context, the command
+ * payload, etc.
+ *
+ * @param prio - priority at which to register; see api.hpp
+ * @param filter - the callback function that will handle this request
+ *
+ * @return bool - success of registering the handler
+ */
+template <typename Filter>
+void registerFilter(int prio, Filter&& filter)
+{
+ auto f = ipmi::makeFilter(std::forward<Filter>(filter));
+ impl::registerFilter(prio, f);
+}
+
+template <typename Filter>
+void registerFilter(int prio, const Filter& filter)
+{
+ auto f = ipmi::makeFilter(filter);
+ impl::registerFilter(prio, f);
+}
+
+} // namespace ipmi
diff --git a/include/ipmid/handler.hpp b/include/ipmid/handler.hpp
new file mode 100644
index 0000000..c3d5f91
--- /dev/null
+++ b/include/ipmid/handler.hpp
@@ -0,0 +1,677 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include <cxxabi.h>
+
+#include <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+#include <user_channel/channel_layer.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <tuple>
+#include <utility>
+
+#ifdef ALLOW_DEPRECATED_API
+#include <ipmid/api.h>
+
+#include <ipmid/oemrouter.hpp>
+#endif /* ALLOW_DEPRECATED_API */
+
+namespace ipmi
+{
+
+template <typename... Args>
+static inline message::Response::ptr errorResponse(
+ message::Request::ptr request, ipmi::Cc cc, Args&&... args)
+{
+ message::Response::ptr response = request->makeResponse();
+ response->cc = cc;
+ response->pack(args...);
+ return response;
+}
+static inline message::Response::ptr errorResponse(
+ message::Request::ptr request, ipmi::Cc cc)
+{
+ message::Response::ptr response = request->makeResponse();
+ response->cc = cc;
+ return response;
+}
+
+/** @brief Exception extension that allows setting an IPMI return code */
+class HandlerCompletion
+{
+ public:
+ HandlerCompletion(Cc cc) noexcept : cc(cc) {}
+
+ Cc code() const noexcept
+ {
+ return cc;
+ }
+
+ private:
+ Cc cc;
+};
+
+/** @brief Exception extension that allows setting an IPMI return code and
+ * printing out a logged error */
+class HandlerException : public HandlerCompletion, public std::runtime_error
+{
+ public:
+ HandlerException(Cc cc, const char* what) :
+ HandlerCompletion(cc), std::runtime_error(what)
+ {}
+ HandlerException(Cc cc, const std::string& what) :
+ HandlerException(cc, what.c_str())
+ {}
+};
+
+static inline const char* currentExceptionType()
+{
+ int status;
+ return abi::__cxa_demangle(abi::__cxa_current_exception_type()->name(),
+ nullptr, nullptr, &status);
+}
+
+/**
+ * @brief Handler base class for dealing with IPMI request/response
+ *
+ * The subclasses are all templated so they can provide access to any type
+ * of command callback functions.
+ */
+class HandlerBase
+{
+ public:
+ using ptr = std::shared_ptr<HandlerBase>;
+
+ virtual ~HandlerBase() = default;
+
+ /** @brief wrap the call to the registered handler with the request
+ *
+ * This is called from the running queue context after it has already
+ * created a request object that contains all the information required to
+ * execute the ipmi command. This function will return the response object
+ * pointer that owns the response object that will ultimately get sent back
+ * to the requester.
+ *
+ * This is a non-virtual function wrapper to the virtualized executeCallback
+ * function that actually does the work. This is required because of how
+ * templates and virtualization work together.
+ *
+ * @param request a shared_ptr to a Request object
+ *
+ * @return a shared_ptr to a Response object
+ */
+ message::Response::ptr call(message::Request::ptr request)
+ {
+ return executeCallback(request);
+ }
+
+ private:
+ /** @brief call the registered handler with the request
+ *
+ * This is called from the running queue context after it has already
+ * created a request object that contains all the information required to
+ * execute the ipmi command. This function will return the response object
+ * pointer that owns the response object that will ultimately get sent back
+ * to the requester.
+ *
+ * @param request a shared_ptr to a Request object
+ *
+ * @return a shared_ptr to a Response object
+ */
+ virtual message::Response::ptr executeCallback(
+ message::Request::ptr request) = 0;
+};
+
+/**
+ * @brief Main IPMI handler class
+ *
+ * New IPMI handlers will resolve into this class, which will read the signature
+ * of the registering function, attempt to extract the appropriate arguments
+ * from a request, pass the arguments to the function, and then pack the
+ * response of the function back into an IPMI response.
+ */
+template <typename Handler>
+class IpmiHandler final : public HandlerBase
+{
+ public:
+ explicit IpmiHandler(Handler&& handler) :
+ handler_(std::forward<Handler>(handler))
+ {}
+
+ private:
+ Handler handler_;
+
+ /** @brief call the registered handler with the request
+ *
+ * This is called from the running queue context after it has already
+ * created a request object that contains all the information required to
+ * execute the ipmi command. This function will return the response object
+ * pointer that owns the response object that will ultimately get sent back
+ * to the requester.
+ *
+ * Because this is the new variety of IPMI handler, this is the function
+ * that attempts to extract the requested parameters in order to pass them
+ * onto the callback function and then packages up the response into a plain
+ * old vector to pass back to the caller.
+ *
+ * @param request a shared_ptr to a Request object
+ *
+ * @return a shared_ptr to a Response object
+ */
+ message::Response::ptr executeCallback(
+ message::Request::ptr request) override
+ {
+ message::Response::ptr response = request->makeResponse();
+
+ using CallbackSig = boost::callable_traits::args_t<Handler>;
+ using InputArgsType = typename utility::DecayTuple<CallbackSig>::type;
+ using UnpackArgsType = typename utility::StripFirstArgs<
+ utility::NonIpmiArgsCount<InputArgsType>::size(),
+ InputArgsType>::type;
+ using ResultType = boost::callable_traits::return_type_t<Handler>;
+
+ UnpackArgsType unpackArgs{};
+ request->payload.trailingOk = false;
+ ipmi::Cc unpackError = request->unpack(unpackArgs);
+ if (unpackError != ipmi::ccSuccess)
+ {
+ response->cc = unpackError;
+ return response;
+ }
+ /* callbacks can contain an optional first argument of one of:
+ * 1) boost::asio::yield_context
+ * 2) ipmi::Context::ptr
+ * 3) ipmi::message::Request::ptr
+ *
+ * If any of those is part of the callback signature as the first
+ * argument, it will automatically get packed into the parameter pack
+ * here.
+ *
+ * One more special optional argument is an ipmi::message::Payload.
+ * This argument can be in any position, though logically it makes the
+ * most sense if it is the last. If this class is included in the
+ * handler signature, it will allow for the handler to unpack optional
+ * parameters. For example, the Set LAN Configuration Parameters
+ * command takes variable length (and type) values for each of the LAN
+ * parameters. This means that the only fixed data is the channel and
+ * parameter selector. All the remaining data can be extracted using
+ * the Payload class and the unpack API available to the Payload class.
+ */
+ ResultType result;
+ try
+ {
+ std::optional<InputArgsType> inputArgs;
+ if constexpr (std::tuple_size<InputArgsType>::value > 0)
+ {
+ if constexpr (std::is_same<
+ std::tuple_element_t<0, InputArgsType>,
+ boost::asio::yield_context>::value)
+ {
+ inputArgs.emplace(std::tuple_cat(
+ std::forward_as_tuple(request->ctx->yield),
+ std::move(unpackArgs)));
+ }
+ else if constexpr (std::is_same<
+ std::tuple_element_t<0, InputArgsType>,
+ ipmi::Context::ptr>::value)
+ {
+ inputArgs.emplace(
+ std::tuple_cat(std::forward_as_tuple(request->ctx),
+ std::move(unpackArgs)));
+ }
+ else if constexpr (std::is_same<
+ std::tuple_element_t<0, InputArgsType>,
+ ipmi::message::Request::ptr>::value)
+ {
+ inputArgs.emplace(std::tuple_cat(
+ std::forward_as_tuple(request), std::move(unpackArgs)));
+ }
+ else
+ {
+ // no special parameters were requested (but others were)
+ inputArgs.emplace(std::move(unpackArgs));
+ }
+ }
+ else
+ {
+ // no parameters were requested
+ inputArgs = std::move(unpackArgs);
+ }
+
+ // execute the registered callback function and get the
+ // ipmi::RspType<>
+ result = std::apply(handler_, *inputArgs);
+ }
+ catch (const HandlerException& e)
+ {
+ lg2::info("Handler produced exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, e.code());
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+ catch (const HandlerCompletion& c)
+ {
+ return errorResponse(request, c.code());
+ }
+ catch (...)
+ {
+ const char* what = currentExceptionType();
+ lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", what);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+
+ response->cc = std::get<0>(result);
+ auto payload = std::get<1>(result);
+ // check for optional payload
+ if (payload)
+ {
+ response->pack(*payload);
+ }
+ return response;
+ }
+};
+
+#ifdef ALLOW_DEPRECATED_API
+static constexpr size_t maxLegacyBufferSize = 64 * 1024;
+/**
+ * @brief Legacy IPMI handler class
+ *
+ * Legacy IPMI handlers will resolve into this class, which will behave the same
+ * way as the legacy IPMI queue, passing in a big buffer for the request and a
+ * big buffer for the response.
+ *
+ * As soon as all the handlers have been rewritten, this class will be marked as
+ * deprecated and eventually removed.
+ */
+template <>
+class IpmiHandler<ipmid_callback_t> final : public HandlerBase
+{
+ public:
+ explicit IpmiHandler(const ipmid_callback_t& handler, void* ctx = nullptr) :
+ handler_(handler), handlerCtx(ctx)
+ {}
+
+ private:
+ ipmid_callback_t handler_;
+ void* handlerCtx;
+
+ /** @brief call the registered handler with the request
+ *
+ * This is called from the running queue context after it has already
+ * created a request object that contains all the information required to
+ * execute the ipmi command. This function will return the response object
+ * pointer that owns the response object that will ultimately get sent back
+ * to the requester.
+ *
+ * Because this is the legacy variety of IPMI handler, this function does
+ * not really have to do much other than pass the payload to the callback
+ * and return response to the caller.
+ *
+ * @param request a shared_ptr to a Request object
+ *
+ * @return a shared_ptr to a Response object
+ */
+ message::Response::ptr executeCallback(
+ message::Request::ptr request) override
+ {
+ message::Response::ptr response = request->makeResponse();
+ // allocate a big response buffer here
+ response->payload.resize(maxLegacyBufferSize);
+
+ size_t len = request->payload.size() - request->payload.rawIndex;
+ Cc ccRet{ccSuccess};
+ try
+ {
+ ccRet =
+ handler_(request->ctx->netFn, request->ctx->cmd,
+ request->payload.data() + request->payload.rawIndex,
+ response->payload.data(), &len, handlerCtx);
+ }
+ catch (const HandlerException& e)
+ {
+ lg2::info("Legacy Handler produced exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, e.code());
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Legacy Handler failed to catch exception, "
+ "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+ catch (const HandlerCompletion& c)
+ {
+ return errorResponse(request, c.code());
+ }
+ catch (...)
+ {
+ const char* what = currentExceptionType();
+ lg2::error("Handler failed to catch exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", what);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+ response->cc = ccRet;
+ response->payload.resize(len);
+ return response;
+ }
+};
+
+/**
+ * @brief Legacy IPMI OEM handler class
+ *
+ * Legacy IPMI OEM handlers will resolve into this class, which will behave the
+ * same way as the legacy IPMI queue, passing in a big buffer for the request
+ * and a big buffer for the response.
+ *
+ * As soon as all the handlers have been rewritten, this class will be marked as
+ * deprecated and eventually removed.
+ */
+template <>
+class IpmiHandler<oem::Handler> final : public HandlerBase
+{
+ public:
+ explicit IpmiHandler(const oem::Handler& handler) : handler_(handler) {}
+
+ private:
+ oem::Handler handler_;
+
+ /** @brief call the registered handler with the request
+ *
+ * This is called from the running queue context after it has already
+ * created a request object that contains all the information required to
+ * execute the ipmi command. This function will return the response object
+ * pointer that owns the response object that will ultimately get sent back
+ * to the requester.
+ *
+ * Because this is the legacy variety of IPMI handler, this function does
+ * not really have to do much other than pass the payload to the callback
+ * and return response to the caller.
+ *
+ * @param request a shared_ptr to a Request object
+ *
+ * @return a shared_ptr to a Response object
+ */
+ message::Response::ptr executeCallback(
+ message::Request::ptr request) override
+ {
+ message::Response::ptr response = request->makeResponse();
+ // allocate a big response buffer here
+ response->payload.resize(maxLegacyBufferSize);
+
+ size_t len = request->payload.size() - request->payload.rawIndex;
+ Cc ccRet{ccSuccess};
+ try
+ {
+ ccRet =
+ handler_(request->ctx->cmd,
+ request->payload.data() + request->payload.rawIndex,
+ response->payload.data(), &len);
+ }
+ catch (const HandlerException& e)
+ {
+ lg2::info("Legacy OEM Handler produced exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, e.code());
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Legacy OEM Handler failed to catch exception, "
+ "NetFn: {NETFN}, Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", e);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+ catch (const HandlerCompletion& c)
+ {
+ return errorResponse(request, c.code());
+ }
+ catch (...)
+ {
+ const char* what = currentExceptionType();
+ lg2::error("Legacy failed to catch exception, NetFn: {NETFN}, "
+ "Cmd: {CMD}: {ERROR}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd, "ERROR", what);
+ return errorResponse(request, ccUnspecifiedError);
+ }
+ response->cc = ccRet;
+ response->payload.resize(len);
+ return response;
+ }
+};
+
+/**
+ * @brief create a legacy IPMI handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the ipmi_register_callback function.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+inline auto makeLegacyHandler(const ipmid_callback_t& handler,
+ void* ctx = nullptr)
+{
+ HandlerBase::ptr ptr(new IpmiHandler<ipmid_callback_t>(handler, ctx));
+ return ptr;
+}
+
+/**
+ * @brief create a legacy IPMI OEM handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the Router::registerHandler method.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+inline auto makeLegacyHandler(oem::Handler&& handler)
+{
+ HandlerBase::ptr ptr(
+ new IpmiHandler<oem::Handler>(std::forward<oem::Handler>(handler)));
+ return ptr;
+}
+#endif // ALLOW_DEPRECATED_API
+
+/**
+ * @brief create an IPMI handler class and return a shared_ptr
+ *
+ * The queue uses a map of pointers to do the lookup. This function returns the
+ * shared_ptr that owns the Handler object.
+ *
+ * This is called internally via the ipmi::registerHandler function.
+ *
+ * @param handler the function pointer to the callback
+ *
+ * @return A shared_ptr to the created handler object
+ */
+template <typename Handler>
+inline auto makeHandler(Handler&& handler)
+{
+ HandlerBase::ptr ptr(
+ new IpmiHandler<Handler>(std::forward<Handler>(handler)));
+ return ptr;
+}
+
+namespace impl
+{
+
+// IPMI command handler registration implementation
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+ ::ipmi::HandlerBase::ptr handler);
+bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+ ::ipmi::HandlerBase::ptr handler);
+bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
+ ::ipmi::HandlerBase::ptr handler);
+
+} // namespace impl
+
+/**
+ * @brief main IPMI handler registration function
+ *
+ * This function should be used to register all new-style IPMI handler
+ * functions. This function just passes the callback to makeHandler, which
+ * creates a new wrapper object that will automatically extract the appropriate
+ * parameters for the callback function as well as pack up the response.
+ *
+ * @param prio - priority at which to register; see api.hpp
+ * @param netFn - the IPMI net function number to register
+ * @param cmd - the IPMI command number to register
+ * @param priv - the IPMI user privilige required for this command
+ * @param handler - the callback function that will handle this request
+ *
+ * @return bool - success of registering the handler
+ */
+template <typename Handler>
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+ Handler&& handler)
+{
+ auto h = ipmi::makeHandler(std::forward<Handler>(handler));
+ return impl::registerHandler(prio, netFn, cmd, priv, h);
+}
+
+/**
+ * @brief register a IPMI OEM group handler
+ *
+ * From IPMI 2.0 spec Network Function Codes Table (Row 2Ch):
+ * The first data byte position in requests and responses under this network
+ * function identifies the defining body that specifies command functionality.
+ * Software assumes that the command and completion code field positions will
+ * hold command and completion code values.
+ *
+ * The following values are used to identify the defining body:
+ * 00h PICMG - PCI Industrial Computer Manufacturer’s Group. (www.picmg.com)
+ * 01h DMTF Pre-OS Working Group ASF Specification (www.dmtf.org)
+ * 02h Server System Infrastructure (SSI) Forum (www.ssiforum.org)
+ * 03h VITA Standards Organization (VSO) (www.vita.com)
+ * DCh DCMI Specifications (www.intel.com/go/dcmi)
+ * all other Reserved
+ *
+ * When this network function is used, the ID for the defining body occupies
+ * the first data byte in a request, and the second data byte (following the
+ * completion code) in a response.
+ *
+ * @tparam Handler - implicitly specified callback function type
+ * @param prio - priority at which to register; see api.hpp
+ * @param netFn - the IPMI net function number to register
+ * @param cmd - the IPMI command number to register
+ * @param priv - the IPMI user privilige required for this command
+ * @param handler - the callback function that will handle this request
+ *
+ * @return bool - success of registering the handler
+ *
+ */
+template <typename Handler>
+void registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+ Handler&& handler)
+{
+ auto h = ipmi::makeHandler(handler);
+ impl::registerGroupHandler(prio, group, cmd, priv, h);
+}
+
+/**
+ * @brief register a IPMI OEM IANA handler
+ *
+ * From IPMI spec Network Function Codes Table (Row 2Eh):
+ * The first three data bytes of requests and responses under this network
+ * function explicitly identify the OEM or non-IPMI group that specifies the
+ * command functionality. While the OEM or non-IPMI group defines the
+ * functional semantics for the cmd and remaining data fields, the cmd field
+ * is required to hold the same value in requests and responses for a given
+ * operation in order to be supported under the IPMI message handling and
+ * transport mechanisms.
+ *
+ * When this network function is used, the IANA Enterprise Number for the
+ * defining body occupies the first three data bytes in a request, and the
+ * first three data bytes following the completion code position in a
+ * response.
+ *
+ * @tparam Handler - implicitly specified callback function type
+ * @param prio - priority at which to register; see api.hpp
+ * @param netFn - the IPMI net function number to register
+ * @param cmd - the IPMI command number to register
+ * @param priv - the IPMI user privilige required for this command
+ * @param handler - the callback function that will handle this request
+ *
+ * @return bool - success of registering the handler
+ *
+ */
+template <typename Handler>
+void registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
+ Handler&& handler)
+{
+ auto h = ipmi::makeHandler(handler);
+ impl::registerOemHandler(prio, iana, cmd, priv, h);
+}
+
+} // namespace ipmi
+
+#ifdef ALLOW_DEPRECATED_API
+/**
+ * @brief legacy IPMI handler registration function
+ *
+ * This function should be used to register all legacy IPMI handler
+ * functions. This function just behaves just as the legacy registration
+ * mechanism did, silently replacing any existing handler with a new one.
+ *
+ * @param netFn - the IPMI net function number to register
+ * @param cmd - the IPMI command number to register
+ * @param context - ignored
+ * @param handler - the callback function that will handle this request
+ * @param priv - the IPMI user privilige required for this command
+ */
+// [[deprecated("Use ipmi::registerHandler() instead")]]
+void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
+ ipmi_context_t context, ipmid_callback_t handler,
+ ipmi_cmd_privilege_t priv);
+
+#endif /* ALLOW_DEPRECATED_API */
diff --git a/include/ipmid/iana.hpp b/include/ipmid/iana.hpp
new file mode 100644
index 0000000..aa9e173
--- /dev/null
+++ b/include/ipmid/iana.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <cstdint>
+
+namespace oem
+{
+using Number = std::uint32_t; // smallest standard size >= 24.
+
+/*
+ * This is the OpenBMC IANA OEM Number
+ */
+constexpr Number obmcOemNumber = 49871;
+
+/*
+ * This is the Google IANA OEM Number
+ */
+constexpr Number googOemNumber = 11129;
+
+} // namespace oem
diff --git a/include/ipmid/message.hpp b/include/ipmid/message.hpp
new file mode 100644
index 0000000..6698581
--- /dev/null
+++ b/include/ipmid/message.hpp
@@ -0,0 +1,668 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <boost/asio/spawn.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message/types.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace ipmi
+{
+
+struct Context
+{
+ using ptr = std::shared_ptr<Context>;
+
+ Context() = delete;
+ Context(const Context&) = default;
+ Context& operator=(const Context&) = default;
+ Context(Context&&) = delete;
+ Context& operator=(Context&&) = delete;
+
+ Context(std::shared_ptr<sdbusplus::asio::connection> bus, NetFn netFn,
+ uint8_t lun, Cmd cmd, int channel, int userId, uint32_t sessionId,
+ Privilege priv, int rqSA, int hostIdx,
+ boost::asio::yield_context& yield) :
+ bus(bus), netFn(netFn), lun(lun), cmd(cmd), channel(channel),
+ userId(userId), sessionId(sessionId), priv(priv), group(0), rqSA(rqSA),
+ hostIdx(hostIdx), yield(yield)
+ {}
+
+ std::shared_ptr<sdbusplus::asio::connection> bus;
+ // normal IPMI context (what call is this, from whence it came...)
+ NetFn netFn;
+ uint8_t lun;
+ Cmd cmd;
+ int channel;
+ int userId;
+ uint32_t sessionId;
+ Privilege priv;
+ // defining body code for netFnGroup
+ Group group;
+ // srcAddr is only set on IPMB requests because
+ // Platform Event Message needs it to determine the incoming format
+ int rqSA;
+ int hostIdx;
+ boost::asio::yield_context yield;
+};
+
+namespace message
+{
+
+namespace details
+{
+
+template <typename A>
+struct UnpackSingle;
+
+template <typename T>
+using UnpackSingle_t = UnpackSingle<utility::TypeIdDowncast_t<T>>;
+
+template <typename A>
+struct PackSingle;
+
+template <typename T>
+using PackSingle_t = PackSingle<utility::TypeIdDowncast_t<T>>;
+
+// size to hold 64 bits plus one (possibly-)partial byte
+static constexpr size_t bitStreamSize = ((sizeof(uint64_t) + 1) * CHAR_BIT);
+
+} // namespace details
+
+/**
+ * @brief a payload class that provides a mechanism to pack and unpack data
+ *
+ * When a new request is being executed, the Payload class is responsible for
+ * attempting to unpack all the required arguments from the incoming blob. For
+ * variable-length functions, it is possible to have function signature have a
+ * Payload object, which will then allow the remaining data to be extracted as
+ * needed.
+ *
+ * When creating a response, the parameters returned from the callback use a
+ * newly created payload object to pack all the parameters into a buffer that is
+ * then returned to the requester.
+ *
+ * These interfaces make calls into the message/pack.hpp and message/unpack.hpp
+ * functions.
+ */
+struct Payload
+{
+ Payload() = default;
+ Payload(const Payload&) = default;
+ Payload& operator=(const Payload&) = default;
+ Payload(Payload&&) = default;
+ Payload& operator=(Payload&&) = default;
+
+ explicit Payload(SecureBuffer&& data) : raw(std::move(data)) {}
+
+ ~Payload()
+ {
+ if (raw.size() != 0 && std::uncaught_exceptions() == 0 && !trailingOk &&
+ !unpackCheck && !unpackError)
+ {
+ lg2::error(
+ "Failed to check request for full unpack: raw size: {RAW_SIZE}",
+ "RAW_SIZE", raw.size());
+ }
+ }
+
+ /******************************************************************
+ * raw vector access
+ *****************************************************************/
+ /**
+ * @brief return the size of the underlying raw buffer
+ */
+ size_t size() const
+ {
+ return raw.size();
+ }
+ /**
+ * @brief resize the underlying raw buffer to a new size
+ *
+ * @param sz - new size for the buffer
+ */
+ void resize(size_t sz)
+ {
+ raw.resize(sz);
+ }
+ /**
+ * @brief return a pointer to the underlying raw buffer
+ */
+ uint8_t* data()
+ {
+ return raw.data();
+ }
+ /**
+ * @brief return a const pointer to the underlying raw buffer
+ */
+ const uint8_t* data() const
+ {
+ return raw.data();
+ }
+
+ /******************************************************************
+ * Response operations
+ *****************************************************************/
+ /**
+ * @brief append a series of bytes to the buffer
+ *
+ * @tparam T - the type pointer to return; must be compatible to a byte
+ *
+ * @param begin - a pointer to the beginning of the series
+ * @param end - a pointer to the end of the series
+ */
+ template <typename T>
+ void append(T* begin, T* end)
+ {
+ static_assert(
+ std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
+ std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
+ std::is_same_v<utility::TypeIdDowncast_t<T>, char>,
+ "begin and end must be signed or unsigned byte pointers");
+ // this interface only allows full-byte access; pack in partial bytes
+ drain();
+ raw.insert(raw.end(), reinterpret_cast<const uint8_t*>(begin),
+ reinterpret_cast<const uint8_t*>(end));
+ }
+
+ /**
+ * @brief append a series of bits to the buffer
+ *
+ * Only the lowest @count order of bits will be appended, with the most
+ * significant of those bits getting appended first.
+ *
+ * @param count - number of bits to append
+ * @param bits - a byte with count significant bits to append
+ */
+ void appendBits(size_t count, uint8_t bits)
+ {
+ // drain whole bytes out
+ drain(true);
+
+ // add in the new bits as the higher-order bits, filling LSBit first
+ fixed_uint_t<details::bitStreamSize> tmp = bits;
+ tmp <<= bitCount;
+ bitStream |= tmp;
+ bitCount += count;
+
+ // drain any whole bytes we have appended
+ drain(true);
+ }
+
+ /**
+ * @brief empty out the bucket and pack it as bytes LSB-first
+ *
+ * @param wholeBytesOnly - if true, only the whole bytes will be drained
+ */
+ void drain(bool wholeBytesOnly = false)
+ {
+ while (bitCount > 0)
+ {
+ uint8_t retVal;
+ if (bitCount < CHAR_BIT)
+ {
+ if (wholeBytesOnly)
+ {
+ break;
+ }
+ }
+ size_t bitsOut = std::min(static_cast<size_t>(CHAR_BIT), bitCount);
+ retVal = static_cast<uint8_t>(bitStream);
+ raw.push_back(retVal);
+ bitStream >>= bitsOut;
+ bitCount -= bitsOut;
+ }
+ }
+
+ // base empty pack
+ int pack()
+ {
+ return 0;
+ }
+
+ /**
+ * @brief pack arbitrary values (of any supported type) into the buffer
+ *
+ * @tparam Arg - the type of the first argument
+ * @tparam Args - the type of the optional remaining arguments
+ *
+ * @param arg - the first argument to pack
+ * @param args... - the optional remaining arguments to pack
+ *
+ * @return int - non-zero on pack errors
+ */
+ template <typename Arg, typename... Args>
+ int pack(Arg&& arg, Args&&... args)
+ {
+ int packRet =
+ details::PackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
+ if (packRet)
+ {
+ return packRet;
+ }
+ packRet = pack(std::forward<Args>(args)...);
+ drain();
+ return packRet;
+ }
+
+ /**
+ * @brief Prepends another payload to this one
+ *
+ * Avoid using this unless absolutely required since it inserts into the
+ * front of the response payload.
+ *
+ * @param p - The payload to prepend
+ *
+ * @retunr int - non-zero on prepend errors
+ */
+ int prepend(const ipmi::message::Payload& p)
+ {
+ if (bitCount != 0 || p.bitCount != 0)
+ {
+ return 1;
+ }
+ raw.reserve(raw.size() + p.raw.size());
+ raw.insert(raw.begin(), p.raw.begin(), p.raw.end());
+ return 0;
+ }
+
+ /******************************************************************
+ * Request operations
+ *****************************************************************/
+ /**
+ * @brief pop a series of bytes from the raw buffer
+ *
+ * @tparam T - the type pointer to return; must be compatible to a byte
+ *
+ * @param count - the number of bytes to return
+ *
+ * @return - a tuple of pointers (begin,begin+count)
+ */
+ template <typename T>
+ auto pop(size_t count)
+ {
+ static_assert(
+ std::is_same_v<utility::TypeIdDowncast_t<T>, int8_t> ||
+ std::is_same_v<utility::TypeIdDowncast_t<T>, uint8_t> ||
+ std::is_same_v<utility::TypeIdDowncast_t<T>, char>,
+ "T* must be signed or unsigned byte pointers");
+ // this interface only allows full-byte access; skip partial bits
+ if (bitCount)
+ {
+ // WARN on unused bits?
+ discardBits();
+ }
+ if (count <= (raw.size() - rawIndex))
+ {
+ auto range = std::make_tuple(
+ reinterpret_cast<T*>(raw.data() + rawIndex),
+ reinterpret_cast<T*>(raw.data() + rawIndex + count));
+ rawIndex += count;
+ return range;
+ }
+ unpackError = true;
+ return std::make_tuple(reinterpret_cast<T*>(NULL),
+ reinterpret_cast<T*>(NULL));
+ }
+
+ /**
+ * @brief fill bit stream with at least count bits for consumption
+ *
+ * @param count - number of bit needed
+ *
+ * @return - unpackError
+ */
+ bool fillBits(size_t count)
+ {
+ // add more bits to the top end of the bitstream
+ // so we consume bits least-significant first
+ if (count > (details::bitStreamSize - CHAR_BIT))
+ {
+ unpackError = true;
+ return unpackError;
+ }
+ while (bitCount < count)
+ {
+ if (rawIndex < raw.size())
+ {
+ fixed_uint_t<details::bitStreamSize> tmp = raw[rawIndex++];
+ tmp <<= bitCount;
+ bitStream |= tmp;
+ bitCount += CHAR_BIT;
+ }
+ else
+ {
+ // raw has run out of bytes to pop
+ unpackError = true;
+ return unpackError;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @brief consume count bits from bitstream (must call fillBits first)
+ *
+ * @param count - number of bit needed
+ *
+ * @return - count bits from stream
+ */
+ uint8_t popBits(size_t count)
+ {
+ if (bitCount < count)
+ {
+ unpackError = true;
+ return 0;
+ }
+ // consume bits low-order bits first
+ auto bits = bitStream.convert_to<uint8_t>();
+ bits &= ((1 << count) - 1);
+ bitStream >>= count;
+ bitCount -= count;
+ return bits;
+ }
+
+ /**
+ * @brief discard all partial bits
+ */
+ void discardBits()
+ {
+ bitStream = 0;
+ bitCount = 0;
+ }
+
+ /**
+ * @brief fully reset the unpack stream
+ */
+ void reset()
+ {
+ discardBits();
+ rawIndex = 0;
+ unpackError = false;
+ }
+
+ /**
+ * @brief check to see if the stream has been fully unpacked
+ *
+ * @return bool - true if the stream has been unpacked and has no errors
+ */
+ bool fullyUnpacked()
+ {
+ unpackCheck = true;
+ return raw.size() == rawIndex && bitCount == 0 && !unpackError;
+ }
+
+ // base empty unpack
+ int unpack()
+ {
+ return 0;
+ }
+
+ /**
+ * @brief unpack arbitrary values (of any supported type) from the buffer
+ *
+ * @tparam Arg - the type of the first argument
+ * @tparam Args - the type of the optional remaining arguments
+ *
+ * @param arg - the first argument to unpack
+ * @param args... - the optional remaining arguments to unpack
+ *
+ * @return int - non-zero for unpack error
+ */
+ template <typename Arg, typename... Args>
+ int unpack(Arg&& arg, Args&&... args)
+ {
+ int unpackRet =
+ details::UnpackSingle_t<Arg>::op(*this, std::forward<Arg>(arg));
+ if (unpackRet)
+ {
+ unpackError = true;
+ return unpackRet;
+ }
+ return unpack(std::forward<Args>(args)...);
+ }
+
+ /**
+ * @brief unpack a tuple of values (of any supported type) from the buffer
+ *
+ * This will unpack the elements of the tuple as if each one was passed in
+ * individually, as if passed into the above variadic function.
+ *
+ * @tparam Types - the implicitly declared list of the tuple element types
+ *
+ * @param t - the tuple of values to unpack
+ *
+ * @return int - non-zero on unpack error
+ */
+ template <typename... Types>
+ int unpack(std::tuple<Types...>& t)
+ {
+ // roll back checkpoint so that unpacking a tuple is atomic
+ size_t priorBitCount = bitCount;
+ size_t priorIndex = rawIndex;
+ fixed_uint_t<details::bitStreamSize> priorBits = bitStream;
+
+ int ret =
+ std::apply([this](Types&... args) { return unpack(args...); }, t);
+ if (ret)
+ {
+ bitCount = priorBitCount;
+ bitStream = priorBits;
+ rawIndex = priorIndex;
+ }
+
+ return ret;
+ }
+
+ // partial bytes in the form of bits
+ fixed_uint_t<details::bitStreamSize> bitStream;
+ size_t bitCount = 0;
+ SecureBuffer raw;
+ size_t rawIndex = 0;
+ bool trailingOk = true;
+ bool unpackCheck = false;
+ bool unpackError = false;
+};
+
+/**
+ * @brief high-level interface to an IPMI response
+ *
+ * Make it easy to just pack in the response args from the callback into a
+ * buffer that goes back to the requester.
+ */
+struct Response
+{
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * Allowed:
+ * - Copy operations.
+ * - Move operations.
+ * - Destructor.
+ */
+ Response() = delete;
+ Response(const Response&) = default;
+ Response& operator=(const Response&) = default;
+ Response(Response&&) = default;
+ Response& operator=(Response&&) = default;
+ ~Response() = default;
+
+ using ptr = std::shared_ptr<Response>;
+
+ explicit Response(Context::ptr& context) :
+ payload(), ctx(context), cc(ccSuccess)
+ {}
+
+ /**
+ * @brief pack arbitrary values (of any supported type) into the payload
+ *
+ * @tparam Args - the type of the optional arguments
+ *
+ * @param args... - the optional arguments to pack
+ *
+ * @return int - non-zero on pack errors
+ */
+ template <typename... Args>
+ int pack(Args&&... args)
+ {
+ return payload.pack(std::forward<Args>(args)...);
+ }
+
+ /**
+ * @brief pack a tuple of values (of any supported type) into the payload
+ *
+ * This will pack the elements of the tuple as if each one was passed in
+ * individually, as if passed into the above variadic function.
+ *
+ * @tparam Types - the implicitly declared list of the tuple element types
+ *
+ * @param t - the tuple of values to pack
+ *
+ * @return int - non-zero on pack errors
+ */
+ template <typename... Types>
+ int pack(std::tuple<Types...>& t)
+ {
+ return payload.pack(t);
+ }
+
+ /**
+ * @brief Prepends another payload to this one
+ *
+ * Avoid using this unless absolutely required since it inserts into the
+ * front of the response payload.
+ *
+ * @param p - The payload to prepend
+ *
+ * @retunr int - non-zero on prepend errors
+ */
+ int prepend(const ipmi::message::Payload& p)
+ {
+ return payload.prepend(p);
+ }
+
+ Payload payload;
+ Context::ptr ctx;
+ Cc cc;
+};
+
+/**
+ * @brief high-level interface to an IPMI request
+ *
+ * Make it easy to unpack the buffer into the request args for the callback.
+ */
+struct Request
+{
+ /* Define all of the basic class operations:
+ * Not allowed:
+ * - Default constructor to avoid nullptrs.
+ * Allowed:
+ * - Copy operations.
+ * - Move operations.
+ * - Destructor.
+ */
+ Request() = delete;
+ Request(const Request&) = default;
+ Request& operator=(const Request&) = default;
+ Request(Request&&) = default;
+ Request& operator=(Request&&) = default;
+ ~Request() = default;
+
+ using ptr = std::shared_ptr<Request>;
+
+ explicit Request(Context::ptr context, SecureBuffer&& d) :
+ payload(std::forward<SecureBuffer>(d)), ctx(context)
+ {}
+
+ /**
+ * @brief unpack arbitrary values (of any supported type) from the payload
+ *
+ * @tparam Args - the type of the optional arguments
+ *
+ * @param args... - the optional arguments to unpack
+ *
+ * @return int - non-zero for unpack error
+ */
+ template <typename... Args>
+ int unpack(Args&&... args)
+ {
+ int unpackRet = payload.unpack(std::forward<Args>(args)...);
+ if (unpackRet != ipmi::ccSuccess)
+ {
+ // not all bits were consumed by requested parameters
+ return ipmi::ccReqDataLenInvalid;
+ }
+ if (!payload.trailingOk)
+ {
+ if (!payload.fullyUnpacked())
+ {
+ // not all bits were consumed by requested parameters
+ return ipmi::ccReqDataLenInvalid;
+ }
+ }
+ return ipmi::ccSuccess;
+ }
+
+ /**
+ * @brief unpack a tuple of values (of any supported type) from the payload
+ *
+ * This will unpack the elements of the tuple as if each one was passed in
+ * individually, as if passed into the above variadic function.
+ *
+ * @tparam Types - the implicitly declared list of the tuple element types
+ *
+ * @param t - the tuple of values to unpack
+ *
+ * @return int - non-zero on unpack error
+ */
+ template <typename... Types>
+ int unpack(std::tuple<Types...>& t)
+ {
+ return std::apply([this](Types&... args) { return unpack(args...); },
+ t);
+ }
+
+ /** @brief Create a response message that corresponds to this request
+ *
+ * @return A shared_ptr to the response message created
+ */
+ Response::ptr makeResponse()
+ {
+ return std::make_shared<Response>(ctx);
+ }
+
+ Payload payload;
+ Context::ptr ctx;
+};
+
+} // namespace message
+
+} // namespace ipmi
+
+// include packing and unpacking of types
+#include <ipmid/message/pack.hpp>
+#include <ipmid/message/unpack.hpp>
diff --git a/include/ipmid/message/pack.hpp b/include/ipmid/message/pack.hpp
new file mode 100644
index 0000000..724bffc
--- /dev/null
+++ b/include/ipmid/message/pack.hpp
@@ -0,0 +1,340 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <ipmid/message.hpp>
+#include <ipmid/message/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <array>
+#include <memory>
+#include <optional>
+#include <span>
+#include <string_view>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytes(uint8_t* pointer, const NumericType& i)
+{
+ if constexpr (byteIndex < sizeof(NumericType))
+ {
+ *pointer = static_cast<uint8_t>(i >> (8 * byteIndex));
+ PackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+ }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void PackBytesUnaligned(Payload& p, const NumericType& i)
+{
+ if constexpr (byteIndex < sizeof(NumericType))
+ {
+ p.appendBits(CHAR_BIT, static_cast<uint8_t>(i >> (8 * byteIndex)));
+ PackBytesUnaligned<NumericType, byteIndex + 1>(p, i);
+ }
+}
+
+/** @struct PackSingle
+ * @brief Utility to pack a single C++ element into a Payload
+ *
+ * User-defined types are expected to specialize this template in order to
+ * get their functionality.
+ *
+ * @tparam S - Type of element to pack.
+ */
+template <typename T>
+struct PackSingle
+{
+ /** @brief Do the operation to pack element.
+ *
+ * @param[in] p - Payload to pack into.
+ * @param[out] t - The reference to pack item into.
+ */
+ static int op(Payload& p, const T& t)
+ {
+ static_assert(std::is_integral_v<T>,
+ "Attempt to pack a type that has no IPMI pack operation");
+ // if not on a byte boundary, must pack values LSbit/LSByte first
+ if (p.bitCount)
+ {
+ PackBytesUnaligned<T>(p, t);
+ }
+ else
+ {
+ // copy in bits to vector....
+ p.raw.resize(p.raw.size() + sizeof(T));
+ uint8_t* out = p.raw.data() + p.raw.size() - sizeof(T);
+ PackBytes<T>(out, t);
+ }
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::tuple<T> */
+template <typename... T>
+struct PackSingle<std::tuple<T...>>
+{
+ static int op(Payload& p, const std::tuple<T...>& v)
+ {
+ return std::apply([&p](const T&... args) { return p.pack(args...); },
+ v);
+ }
+};
+
+/** @brief Specialization of PackSingle for std::string
+ * represented as a UCSD-Pascal style string
+ */
+template <>
+struct PackSingle<std::string>
+{
+ static int op(Payload& p, const std::string& t)
+ {
+ // check length first
+ uint8_t len;
+ if (t.length() > std::numeric_limits<decltype(len)>::max())
+ {
+ lg2::error("long string truncated on IPMI message pack");
+ return 1;
+ }
+ len = static_cast<uint8_t>(t.length());
+ PackSingle<uint8_t>::op(p, len);
+ p.append(t.c_str(), t.c_str() + t.length());
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for fixed_uint_t types
+ */
+template <bitcount_t N>
+struct PackSingle<fixed_uint_t<N>>
+{
+ static int op(Payload& p, const fixed_uint_t<N>& t)
+ {
+ size_t count = N;
+ static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+ static_assert(N <= std::numeric_limits<uint64_t>::digits,
+ "Type exceeds uint64_t limit");
+ uint64_t bits = static_cast<uint64_t>(t);
+ while (count > 0)
+ {
+ size_t appendCount = std::min(count, static_cast<size_t>(CHAR_BIT));
+ p.appendBits(appendCount, static_cast<uint8_t>(bits));
+ bits >>= CHAR_BIT;
+ count -= appendCount;
+ }
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for bool. */
+template <>
+struct PackSingle<bool>
+{
+ static int op(Payload& p, const bool& b)
+ {
+ p.appendBits(1, b);
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::bitset<N> */
+template <size_t N>
+struct PackSingle<std::bitset<N>>
+{
+ static int op(Payload& p, const std::bitset<N>& t)
+ {
+ size_t count = N;
+ static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+ unsigned long long bits = t.to_ullong();
+ while (count > 0)
+ {
+ size_t appendCount = std::min(count, size_t(CHAR_BIT));
+ p.appendBits(appendCount, static_cast<uint8_t>(bits));
+ bits >>= CHAR_BIT;
+ count -= appendCount;
+ }
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::optional<T> */
+template <typename T>
+struct PackSingle<std::optional<T>>
+{
+ static int op(Payload& p, const std::optional<T>& t)
+ {
+ int ret = 0;
+ if (t)
+ {
+ ret = PackSingle<T>::op(p, *t);
+ }
+ return ret;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::array<T, N> */
+template <typename T, size_t N>
+struct PackSingle<std::array<T, N>>
+{
+ static int op(Payload& p, const std::array<T, N>& t)
+ {
+ int ret = 0;
+ for (const auto& v : t)
+ {
+ int ret = PackSingle<T>::op(p, v);
+ if (ret)
+ {
+ break;
+ }
+ }
+ return ret;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::vector<T> */
+template <typename T>
+struct PackSingle<std::vector<T>>
+{
+ static int op(Payload& p, const std::vector<T>& t)
+ {
+ int ret = 0;
+ for (const auto& v : t)
+ {
+ int ret = PackSingle<T>::op(p, v);
+ if (ret)
+ {
+ break;
+ }
+ }
+ return ret;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::vector<uint8_t> */
+template <>
+struct PackSingle<std::vector<uint8_t>>
+{
+ static int op(Payload& p, const std::vector<uint8_t>& t)
+ {
+ if (p.bitCount != 0)
+ {
+ return 1;
+ }
+ p.raw.reserve(p.raw.size() + t.size());
+ p.raw.insert(p.raw.end(), t.begin(), t.end());
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for SecureBuffer */
+template <>
+struct PackSingle<SecureBuffer>
+{
+ static int op(Payload& p, const SecureBuffer& t)
+ {
+ if (p.bitCount != 0)
+ {
+ return 1;
+ }
+ p.raw.reserve(p.raw.size() + t.size());
+ p.raw.insert(p.raw.end(), t.begin(), t.end());
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::span<const uint8_t> */
+template <>
+struct PackSingle<std::span<const uint8_t>>
+{
+ static int op(Payload& p, const std::span<const uint8_t>& t)
+ {
+ if (p.bitCount != 0)
+ {
+ return 1;
+ }
+ p.raw.reserve(p.raw.size() + t.size());
+ p.raw.insert(p.raw.end(), t.begin(), t.end());
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::string_view */
+template <>
+struct PackSingle<std::string_view>
+{
+ static int op(Payload& p, const std::string_view& t)
+ {
+ if (p.bitCount != 0)
+ {
+ return 1;
+ }
+ p.raw.reserve(p.raw.size() + t.size());
+ p.raw.insert(p.raw.end(), t.begin(), t.end());
+ return 0;
+ }
+};
+
+/** @brief Specialization of PackSingle for std::variant<T, N> */
+template <typename... T>
+struct PackSingle<std::variant<T...>>
+{
+ static int op(Payload& p, const std::variant<T...>& v)
+ {
+ return std::visit(
+ [&p](const auto& arg) {
+ return PackSingle<std::decay_t<decltype(arg)>>::op(p, arg);
+ },
+ v);
+ }
+};
+
+/** @brief Specialization of PackSingle for Payload */
+template <>
+struct PackSingle<Payload>
+{
+ static int op(Payload& p, const Payload& t)
+ {
+ if (p.bitCount != 0 || t.bitCount != 0)
+ {
+ return 1;
+ }
+ p.raw.reserve(p.raw.size() + t.raw.size());
+ p.raw.insert(p.raw.end(), t.raw.begin(), t.raw.end());
+ return 0;
+ }
+};
+
+} // namespace details
+
+} // namespace message
+
+} // namespace ipmi
diff --git a/include/ipmid/message/types.hpp b/include/ipmid/message/types.hpp
new file mode 100644
index 0000000..c53529b
--- /dev/null
+++ b/include/ipmid/message/types.hpp
@@ -0,0 +1,138 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <boost/multiprecision/cpp_int.hpp>
+#include <boost/version.hpp>
+#include <ipmid/utility.hpp>
+
+#include <bitset>
+#include <tuple>
+
+#if BOOST_VERSION < 107900
+using bitcount_t = unsigned;
+#else
+using bitcount_t = std::size_t;
+#endif
+
+// unsigned fixed-bit sizes
+template <bitcount_t N>
+using fixed_uint_t =
+ boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+ N, N, boost::multiprecision::unsigned_magnitude,
+ boost::multiprecision::unchecked, void>>;
+// signed fixed-bit sizes
+template <bitcount_t N>
+using fixed_int_t =
+ boost::multiprecision::number<boost::multiprecision::cpp_int_backend<
+ N, N, boost::multiprecision::signed_magnitude,
+ boost::multiprecision::unchecked, void>>;
+
+using uint1_t = fixed_uint_t<1>;
+using uint2_t = fixed_uint_t<2>;
+using uint3_t = fixed_uint_t<3>;
+using uint4_t = fixed_uint_t<4>;
+using uint5_t = fixed_uint_t<5>;
+using uint6_t = fixed_uint_t<6>;
+using uint7_t = fixed_uint_t<7>;
+// native uint8_t
+using uint9_t = fixed_uint_t<9>;
+using uint10_t = fixed_uint_t<10>;
+using uint11_t = fixed_uint_t<11>;
+using uint12_t = fixed_uint_t<12>;
+using uint13_t = fixed_uint_t<13>;
+using uint14_t = fixed_uint_t<14>;
+using uint15_t = fixed_uint_t<15>;
+// native uint16_t
+using uint24_t = fixed_uint_t<24>;
+
+// signed fixed-bit sizes
+using int2_t = fixed_int_t<2>;
+using int3_t = fixed_int_t<3>;
+using int4_t = fixed_int_t<4>;
+using int5_t = fixed_int_t<5>;
+using int6_t = fixed_int_t<6>;
+using int7_t = fixed_int_t<7>;
+// native int8_t
+using int9_t = fixed_int_t<9>;
+using int10_t = fixed_int_t<10>;
+using int11_t = fixed_int_t<11>;
+using int12_t = fixed_int_t<12>;
+using int13_t = fixed_int_t<13>;
+using int14_t = fixed_int_t<14>;
+using int15_t = fixed_int_t<15>;
+// native int16_t
+using int24_t = fixed_int_t<24>;
+
+// bool is more efficient than a uint1_t
+using bit = bool;
+
+// Mechanism for going from uint7_t, int7_t, or std::bitset<7> to 7 bits
+// use nrFixedBits<uint7_t> or nrFixedBits<decltype(u7)>
+namespace types
+{
+namespace details
+{
+
+template <bitcount_t N>
+struct Size
+{
+ static constexpr bitcount_t value = N;
+};
+
+template <bitcount_t Bits>
+constexpr auto getNrBits(const fixed_int_t<Bits>&) -> Size<Bits>;
+template <bitcount_t Bits>
+constexpr auto getNrBits(const fixed_uint_t<Bits>&) -> Size<Bits>;
+template <bitcount_t Bits>
+constexpr auto getNrBits(const std::bitset<Bits>&) -> Size<Bits>;
+
+template <typename U>
+using underlying_t =
+ typename std::conditional_t<std::is_enum_v<U>, std::underlying_type<U>,
+ std::enable_if<true, U>>::type;
+
+} // namespace details
+
+/**
+ * @brief mechanism to get N from a type like fixed_int_t<N>
+ *
+ * helper template to extract N from a fixed_(u)int_t variable
+ *
+ * @tparam T - a type of fixed_int_t<N> or fixed_unint_t<N>
+ *
+ * @return size_t - evaluates to a constexpr size_t of N
+ */
+template <typename T>
+constexpr auto nrFixedBits =
+ decltype(details::getNrBits(std::declval<T>()))::value;
+
+/**
+ * @brief Converts a number or enum class to another
+ * @tparam R - The output type
+ * @tparam T - The input type
+ * @param t - An enum or integer value to cast
+ * @return The value in R form
+ */
+template <typename R, typename T>
+inline R enum_cast(T t)
+{
+ auto tu = static_cast<details::underlying_t<T>>(t);
+ auto ru = static_cast<details::underlying_t<R>>(tu);
+ return static_cast<R>(ru);
+}
+
+} // namespace types
diff --git a/include/ipmid/message/unpack.hpp b/include/ipmid/message/unpack.hpp
new file mode 100644
index 0000000..2e545b0
--- /dev/null
+++ b/include/ipmid/message/unpack.hpp
@@ -0,0 +1,378 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <ipmid/message.hpp>
+#include <ipmid/message/types.hpp>
+
+#include <array>
+#include <optional>
+#include <span>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace ipmi
+{
+
+namespace message
+{
+
+namespace details
+{
+
+/**************************************
+ * ipmi return type helpers
+ **************************************/
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytes(uint8_t* pointer, NumericType& i)
+{
+ if constexpr (byteIndex < sizeof(NumericType))
+ {
+ i |= static_cast<NumericType>(*pointer) << (CHAR_BIT * byteIndex);
+ UnpackBytes<NumericType, byteIndex + 1>(pointer + 1, i);
+ }
+}
+
+template <typename NumericType, size_t byteIndex = 0>
+void UnpackBytesUnaligned(Payload& p, NumericType& i)
+{
+ if constexpr (byteIndex < sizeof(NumericType))
+ {
+ i |= static_cast<NumericType>(p.popBits(CHAR_BIT))
+ << (CHAR_BIT * byteIndex);
+ UnpackBytesUnaligned<NumericType, byteIndex + 1>(p, i);
+ }
+}
+
+/** @struct UnpackSingle
+ * @brief Utility to unpack a single C++ element from a Payload
+ *
+ * User-defined types are expected to specialize this template in order to
+ * get their functionality.
+ *
+ * @tparam T - Type of element to unpack.
+ */
+template <typename T>
+struct UnpackSingle
+{
+ /** @brief Do the operation to unpack element.
+ *
+ * @param[in] p - Payload to unpack from.
+ * @param[out] t - The reference to unpack item into.
+ */
+ static int op(Payload& p, T& t)
+ {
+ if constexpr (std::is_fundamental<T>::value)
+ {
+ t = 0;
+ if (p.bitCount)
+ {
+ if (p.fillBits(CHAR_BIT * sizeof(t)))
+ {
+ return 1;
+ }
+ UnpackBytesUnaligned<T>(p, t);
+ }
+ else
+ {
+ // copy out bits from vector....
+ if (p.raw.size() < (p.rawIndex + sizeof(t)))
+ {
+ return 1;
+ }
+ auto iter = p.raw.data() + p.rawIndex;
+ t = 0;
+ UnpackBytes<T>(iter, t);
+ p.rawIndex += sizeof(t);
+ }
+ return 0;
+ }
+ else if constexpr (utility::is_tuple<T>::value)
+ {
+ bool priorError = p.unpackError;
+ size_t priorIndex = p.rawIndex;
+ // more stuff to unroll if partial bytes are out
+ size_t priorBitCount = p.bitCount;
+ fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
+ int ret = p.unpack(t);
+ if (ret != 0)
+ {
+ t = T();
+ p.rawIndex = priorIndex;
+ p.bitStream = priorBits;
+ p.bitCount = priorBitCount;
+ p.unpackError = priorError;
+ }
+ return ret;
+ }
+ else
+ {
+ static_assert(
+ utility::dependent_false<T>::value,
+ "Attempt to unpack a type that has no IPMI unpack operation");
+ }
+ }
+};
+
+/** @struct UnpackSingle
+ * @brief Utility to unpack a single C++ element from a Payload
+ *
+ * Specialization to unpack std::string represented as a
+ * UCSD-Pascal style string
+ */
+template <>
+struct UnpackSingle<std::string>
+{
+ static int op(Payload& p, std::string& t)
+ {
+ // pop len first
+ if (p.rawIndex > (p.raw.size() - sizeof(uint8_t)))
+ {
+ return 1;
+ }
+ uint8_t len = p.raw[p.rawIndex++];
+ // check to see that there are n bytes left
+ auto [first, last] = p.pop<char>(len);
+ if (first == last)
+ {
+ return 1;
+ }
+ t.reserve(last - first);
+ t.insert(0, first, (last - first));
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for fixed_uint_t types
+ */
+template <bitcount_t N>
+struct UnpackSingle<fixed_uint_t<N>>
+{
+ static int op(Payload& p, fixed_uint_t<N>& t)
+ {
+ static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+ constexpr size_t count = N;
+ // acquire enough bits in the stream to fulfill the Payload
+ if (p.fillBits(count))
+ {
+ return -1;
+ }
+ fixed_uint_t<details::bitStreamSize> bitmask = ((1 << count) - 1);
+ t = (p.bitStream & bitmask).convert_to<fixed_uint_t<N>>();
+ p.bitStream >>= count;
+ p.bitCount -= count;
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for bool. */
+template <>
+struct UnpackSingle<bool>
+{
+ static int op(Payload& p, bool& b)
+ {
+ // acquire enough bits in the stream to fulfill the Payload
+ if (p.fillBits(1))
+ {
+ return -1;
+ }
+ b = static_cast<bool>(p.bitStream & 0x01);
+ // clear bits from stream
+ p.bitStream >>= 1;
+ p.bitCount -= 1;
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::bitset<N>
+ */
+template <size_t N>
+struct UnpackSingle<std::bitset<N>>
+{
+ static int op(Payload& p, std::bitset<N>& t)
+ {
+ static_assert(N <= (details::bitStreamSize - CHAR_BIT));
+ size_t count = N;
+ // acquire enough bits in the stream to fulfill the Payload
+ if (p.fillBits(count))
+ {
+ return -1;
+ }
+ fixed_uint_t<details::bitStreamSize> bitmask =
+ ~fixed_uint_t<details::bitStreamSize>(0) >>
+ (details::bitStreamSize - count);
+ t |= (p.bitStream & bitmask).convert_to<unsigned long long>();
+ p.bitStream >>= count;
+ p.bitCount -= count;
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::optional<T> */
+template <typename T>
+struct UnpackSingle<std::optional<T>>
+{
+ static int op(Payload& p, std::optional<T>& t)
+ {
+ bool priorError = p.unpackError;
+ size_t priorIndex = p.rawIndex;
+ // more stuff to unroll if partial bytes are out
+ size_t priorBitCount = p.bitCount;
+ fixed_uint_t<details::bitStreamSize> priorBits = p.bitStream;
+ T value;
+ int ret = UnpackSingle<T>::op(p, value);
+ if (ret != 0)
+ {
+ t.reset();
+ p.rawIndex = priorIndex;
+ p.bitStream = priorBits;
+ p.bitCount = priorBitCount;
+ p.unpackError = priorError;
+ }
+ else
+ {
+ t.emplace(std::move(value));
+ }
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::array<T, N> */
+template <typename T, size_t N>
+struct UnpackSingle<std::array<T, N>>
+{
+ static int op(Payload& p, std::array<T, N>& t)
+ {
+ int ret = 0;
+ size_t priorIndex = p.rawIndex;
+ for (auto& v : t)
+ {
+ ret = UnpackSingle<T>::op(p, v);
+ if (ret)
+ {
+ p.rawIndex = priorIndex;
+ t = std::array<T, N>();
+ break;
+ }
+ }
+ return ret;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::array<uint8_t> */
+template <size_t N>
+struct UnpackSingle<std::array<uint8_t, N>>
+{
+ static int op(Payload& p, std::array<uint8_t, N>& t)
+ {
+ if (p.raw.size() - p.rawIndex < N)
+ {
+ t.fill(0);
+ return -1;
+ }
+ // copy out the bytes
+ std::copy(p.raw.begin() + p.rawIndex, p.raw.begin() + p.rawIndex + N,
+ t.begin());
+ p.rawIndex += N;
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::vector<T> */
+template <typename T>
+struct UnpackSingle<std::vector<T>>
+{
+ static int op(Payload& p, std::vector<T>& t)
+ {
+ while (p.rawIndex < p.raw.size())
+ {
+ t.emplace_back();
+ if (UnpackSingle<T>::op(p, t.back()))
+ {
+ t.pop_back();
+ break;
+ }
+ }
+ // unpacking a vector is always successful:
+ // either stuff was unpacked successfully (return 0)
+ // or stuff was not unpacked, but should still return
+ // success because an empty vector or a not-fully-unpacked
+ // payload is not a failure.
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::vector<uint8_t> */
+template <>
+struct UnpackSingle<std::vector<uint8_t>>
+{
+ static int op(Payload& p, std::vector<uint8_t>& t)
+ {
+ // copy out the remainder of the message
+ t.reserve(p.raw.size() - p.rawIndex);
+ t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end());
+ p.rawIndex = p.raw.size();
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for SecureBuffer */
+template <>
+struct UnpackSingle<SecureBuffer>
+{
+ static int op(Payload& p, SecureBuffer& t)
+ {
+ // copy out the remainder of the message
+ t.reserve(p.raw.size() - p.rawIndex);
+ t.insert(t.begin(), p.raw.begin() + p.rawIndex, p.raw.end());
+ p.rawIndex = p.raw.size();
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for std::span<const uint8_t> */
+template <>
+struct UnpackSingle<std::span<const uint8_t>>
+{
+ static int op(Payload& p, std::span<const uint8_t>& t)
+ {
+ // copy out the remainder of the message
+ t = std::span<const uint8_t>(p.raw.begin() + p.rawIndex, p.raw.end());
+ p.rawIndex = p.raw.size();
+ return 0;
+ }
+};
+
+/** @brief Specialization of UnpackSingle for Payload */
+template <>
+struct UnpackSingle<Payload>
+{
+ static int op(Payload& p, Payload& t)
+ {
+ t = p;
+ // mark that this payload is being included in the args
+ p.trailingOk = true;
+ return 0;
+ }
+};
+
+} // namespace details
+
+} // namespace message
+
+} // namespace ipmi
diff --git a/include/ipmid/oemopenbmc.hpp b/include/ipmid/oemopenbmc.hpp
new file mode 100644
index 0000000..b7b3a30
--- /dev/null
+++ b/include/ipmid/oemopenbmc.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <ipmid/api.h>
+
+#include <ipmid/oemrouter.hpp>
+
+namespace oem
+{
+/*
+ * OpenBMC OEM Extension IPMI Command codes.
+ */
+enum Cmd
+{
+ gpioCmd = 1,
+ i2cCmd = 2,
+ flashCmd = 3,
+ fanManualCmd = 4,
+ ethStatsCmd = 48,
+ blobTransferCmd = 128,
+};
+
+} // namespace oem
diff --git a/include/ipmid/oemrouter.hpp b/include/ipmid/oemrouter.hpp
new file mode 100644
index 0000000..4759950
--- /dev/null
+++ b/include/ipmid/oemrouter.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <ipmid/api.h>
+
+#include <ipmid/iana.hpp>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <vector>
+
+namespace oem
+{
+constexpr std::size_t groupMagicSize = 3;
+
+using Group = std::array<std::uint8_t, groupMagicSize>;
+
+// Handler signature includes ipmi cmd to support wildcard cmd match.
+// Buffers and lengths exclude the OemGroup bytes in the IPMI message.
+// dataLen supplies length of reqBuf upon call, and should be set to the
+// length of replyBuf upon return - conventional in this code base.
+using Handler = std::function<ipmi_ret_t(ipmi_cmd_t, // cmd byte
+ const std::uint8_t*, // reqBuf
+ std::uint8_t*, // replyBuf
+ std::size_t*)>; // dataLen
+
+/// Router Interface class.
+/// @brief Abstract Router Interface
+class Router
+{
+ public:
+ virtual ~Router() {}
+
+ /// Enable message routing to begin.
+ virtual void activate() = 0;
+
+ /// Register a handler for given OEMNumber & cmd.
+ /// Use cmdWildcard to catch any unregistered cmd
+ /// for the given OEMNumber.
+ ///
+ /// @param[in] oen - the OEM Number.
+ /// @param[in] cmd - the Command.
+ /// @param[in] handler - the handler to call given that OEN and
+ /// command.
+ virtual void registerHandler(Number oen, ipmi_cmd_t cmd,
+ Handler handler) = 0;
+};
+
+/// Expose mutable Router for configuration & activation.
+///
+/// @returns pointer to OEM Router to use.
+Router* mutableRouter();
+
+/// Convert a group to an OEN.
+///
+/// @param[in] oeg - request buffer for IPMI command.
+/// @return the OEN.
+constexpr Number toOemNumber(const std::uint8_t oeg[groupMagicSize])
+{
+ return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given a Group convert to an OEN.
+///
+/// @param[in] oeg - OEM Group reference.
+/// @return the OEN.
+constexpr Number toOemNumber(const Group& oeg)
+{
+ return (oeg[2] << 16) | (oeg[1] << 8) | oeg[0];
+}
+
+/// Given an OEN, conver to the OEM Group.
+///
+/// @param[in] oen - the OEM Number.
+/// @return the OEM Group.
+constexpr Group toOemGroup(Number oen)
+{
+ return Group{static_cast<std::uint8_t>(oen),
+ static_cast<std::uint8_t>(oen >> 8),
+ static_cast<std::uint8_t>(oen >> 16)};
+}
+
+} // namespace oem
diff --git a/include/ipmid/sessiondef.hpp b/include/ipmid/sessiondef.hpp
new file mode 100644
index 0000000..0311c91
--- /dev/null
+++ b/include/ipmid/sessiondef.hpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace session
+{
+
+static constexpr auto sessionManagerRootPath =
+ "/xyz/openbmc_project/ipmi/session";
+static constexpr auto sessionIntf = "xyz.openbmc_project.Ipmi.SessionInfo";
+static constexpr uint8_t ipmi20VerSession = 0x01;
+static constexpr size_t maxSessionCountPerChannel = 15;
+static constexpr size_t sessionZero = 0;
+static constexpr size_t maxSessionlessCount = 1;
+static constexpr uint8_t invalidSessionID = 0;
+static constexpr uint8_t invalidSessionHandle = 0;
+static constexpr uint8_t defaultSessionHandle = 0xFF;
+static constexpr uint8_t maxNetworkInstanceSupported = 4;
+static constexpr uint8_t ccInvalidSessionId = 0x87;
+static constexpr uint8_t ccInvalidSessionHandle = 0x88;
+static constexpr uint8_t searchCurrentSession = 0;
+static constexpr uint8_t searchSessionByHandle = 0xFE;
+static constexpr uint8_t searchSessionById = 0xFF;
+// MSB BIT 7 BIT 6 assigned for netipmid instance in session handle.
+static constexpr uint8_t multiIntfaceSessionHandleMask = 0x3F;
+
+// MSB BIT 31-BIT30 assigned for netipmid instance in session ID
+static constexpr uint32_t multiIntfaceSessionIDMask = 0x3FFFFFFF;
+
+enum class State : uint8_t
+{
+ inactive, // Session is not in use
+ setupInProgress, // Session Setup Sequence is progressing
+ active, // Session is active
+ tearDownInProgress, // When Closing Session
+};
+
+} // namespace session
diff --git a/include/ipmid/sessionhelper.hpp b/include/ipmid/sessionhelper.hpp
new file mode 100644
index 0000000..5c09bfc
--- /dev/null
+++ b/include/ipmid/sessionhelper.hpp
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+/**
+ * @brief parse session input payload.
+ *
+ * This function retrives the session id and session handle from the session
+ * object path.
+ * A valid object path will be in the form
+ * "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+ *
+ * Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ * SessionId : 0X12a4567d
+ * SessionHandle: 0X8a
+
+ * @param[in] objectPath - session object path
+ * @param[in] sessionId - retrived session id will be asigned.
+ * @param[in] sessionHandle - retrived session handle will be asigned.
+ *
+ * @return true if session id and session handle are retrived else returns
+ * false.
+ */
+bool parseCloseSessionInputPayload(const std::string& objectPath,
+ uint32_t& sessionId, uint8_t& sessionHandle)
+{
+ if (objectPath.empty())
+ {
+ return false;
+ }
+ // getting the position of session id and session handle string from
+ // object path.
+ std::size_t ptrPosition = objectPath.rfind("/");
+ uint16_t tempSessionHandle = 0;
+
+ if (ptrPosition != std::string::npos)
+ {
+ // get the sessionid & session handle string from the session object
+ // path Ex: sessionIdString: "12a4567d_8a"
+ std::string sessionIdString = objectPath.substr(ptrPosition + 1);
+ std::size_t pos = sessionIdString.rfind("_");
+
+ if (pos != std::string::npos)
+ {
+ // extracting the session handle
+ std::string sessionHandleString = sessionIdString.substr(pos + 1);
+ // extracting the session id
+ sessionIdString = sessionIdString.substr(0, pos);
+ // converting session id string and session handle string to
+ // hexadecimal.
+ std::stringstream handle(sessionHandleString);
+ handle >> std::hex >> tempSessionHandle;
+ sessionHandle = tempSessionHandle & 0xFF;
+ std::stringstream idString(sessionIdString);
+ idString >> std::hex >> sessionId;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * @brief is session object matched.
+ *
+ * This function checks whether the objectPath contains reqSessionId and
+ * reqSessionHandle, e.g., "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ * matches sessionId 0x12a4567d and sessionHandle 0x8a.
+ *
+ * @param[in] objectPath - session object path
+ * @param[in] reqSessionId - request session id
+ * @param[in] reqSessionHandle - request session handle
+ *
+ * @return true if the object is matched else return false
+ **/
+bool isSessionObjectMatched(const std::string objectPath,
+ const uint32_t reqSessionId,
+ const uint8_t reqSessionHandle)
+{
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0;
+
+ if (parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle))
+ {
+ return (reqSessionId == sessionId) ||
+ (reqSessionHandle == sessionHandle);
+ }
+
+ return false;
+}
diff --git a/include/ipmid/types.hpp b/include/ipmid/types.hpp
new file mode 100644
index 0000000..91ff346
--- /dev/null
+++ b/include/ipmid/types.hpp
@@ -0,0 +1,326 @@
+#pragma once
+
+#include <openssl/crypto.h>
+#include <stdint.h>
+
+#include <sdbusplus/server.hpp>
+
+#include <map>
+#include <string>
+#include <variant>
+
+namespace ipmi
+{
+
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using DbusObjectInfo = std::pair<DbusObjectPath, DbusService>;
+using DbusProperty = std::string;
+
+using Association = std::tuple<std::string, std::string, std::string>;
+using BootProgressCode = std::tuple<std::vector<uint8_t>, std::vector<uint8_t>>;
+
+using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
+ int64_t, uint64_t, double, std::string,
+ std::vector<uint8_t>, std::vector<uint16_t>,
+ std::vector<uint32_t>, std::vector<std::string>,
+ std::vector<Association>, BootProgressCode>;
+
+using PropertyMap = std::map<DbusProperty, Value>;
+
+using ObjectTree =
+ std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
+
+using InterfaceList = std::vector<std::string>;
+
+using DbusInterfaceMap = std::map<DbusInterface, PropertyMap>;
+
+using ObjectValueTree =
+ std::map<sdbusplus::message::object_path, DbusInterfaceMap>;
+
+namespace sensor
+{
+
+using Offset = uint8_t;
+
+/**
+ * @enum SkipAssertion
+ * Matching value for skipping the update
+ */
+enum class SkipAssertion
+{
+ NONE, // No skip defined
+ ASSERT, // Skip on Assert
+ DEASSERT, // Skip on Deassert
+};
+
+struct Values
+{
+ SkipAssertion skip;
+ Value assert;
+ Value deassert;
+};
+
+/**
+ * @enum PreReqValues
+ * Pre-req conditions for a property.
+ */
+struct PreReqValues
+{
+ Value assert; // Value in case of assert.
+ Value deassert; // Value in case of deassert.
+};
+
+using PreReqOffsetValueMap = std::map<Offset, PreReqValues>;
+
+/**
+ * @struct SetSensorReadingReq
+ *
+ * IPMI Request data for Set Sensor Reading and Event Status Command
+ */
+struct SetSensorReadingReq
+{
+ uint8_t number;
+ uint8_t operation;
+ uint8_t reading;
+ uint8_t assertOffset0_7;
+ uint8_t assertOffset8_14;
+ uint8_t deassertOffset0_7;
+ uint8_t deassertOffset8_14;
+ uint8_t eventData1;
+ uint8_t eventData2;
+ uint8_t eventData3;
+} __attribute__((packed));
+
+/**
+ * @struct GetReadingResponse
+ *
+ * IPMI response data for Get Sensor Reading command.
+ */
+struct GetReadingResponse
+{
+ uint8_t reading; //!< Sensor reading.
+ uint8_t operation; //!< Sensor scanning status / reading state.
+ uint8_t assertOffset0_7; //!< Discrete assertion states(0-7).
+ uint8_t assertOffset8_14; //!< Discrete assertion states(8-14).
+} __attribute__((packed));
+
+constexpr auto inventoryRoot = "/xyz/openbmc_project/inventory";
+
+struct GetSensorResponse
+{
+ uint8_t reading; // sensor reading
+ bool readingOrStateUnavailable; // 1 = reading/state unavailable
+ bool scanningEnabled; // 0 = sensor scanning disabled
+ bool allEventMessagesEnabled; // 0 = All Event Messages disabled
+ uint8_t thresholdLevelsStates; // threshold/discrete sensor states
+ uint8_t discreteReadingSensorStates; // discrete-only, optional states
+};
+
+using OffsetValueMap = std::map<Offset, Values>;
+
+using DbusPropertyValues = std::pair<PreReqOffsetValueMap, OffsetValueMap>;
+
+using DbusPropertyMap = std::map<DbusProperty, DbusPropertyValues>;
+
+using DbusInterfaceMap = std::map<DbusInterface, DbusPropertyMap>;
+
+using InstancePath = std::string;
+using Type = uint8_t;
+using ReadingType = uint8_t;
+using Multiplier = uint16_t;
+using OffsetB = int16_t;
+using Exponent = int8_t;
+using ScaledOffset = double;
+using Scale = int16_t;
+using Unit = std::string;
+using EntityType = uint8_t;
+using EntityInst = uint8_t;
+using SensorName = std::string;
+using SensorUnits1 = uint8_t;
+
+enum class Mutability
+{
+ Read = 1 << 0,
+ Write = 1 << 1,
+};
+
+inline Mutability operator|(Mutability lhs, Mutability rhs)
+{
+ return static_cast<Mutability>(
+ static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
+}
+
+inline Mutability operator&(Mutability lhs, Mutability rhs)
+{
+ return static_cast<Mutability>(
+ static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
+}
+
+struct Info
+{
+ EntityType entityType;
+ EntityInst instance;
+ Type sensorType;
+ InstancePath sensorPath;
+ DbusInterface sensorInterface;
+ ReadingType sensorReadingType;
+ Multiplier coefficientM;
+ OffsetB coefficientB;
+ Exponent exponentB;
+ ScaledOffset scaledOffset;
+ Exponent exponentR;
+ bool hasScale;
+ Scale scale;
+ SensorUnits1 sensorUnits1;
+ Unit unit;
+ std::function<uint8_t(SetSensorReadingReq&, const Info&)> updateFunc;
+#ifndef FEATURE_SENSORS_CACHE
+ std::function<GetSensorResponse(const Info&)> getFunc;
+#else
+ std::function<std::optional<GetSensorResponse>(uint8_t, const Info&,
+ const ipmi::PropertyMap&)>
+ getFunc;
+#endif
+ Mutability mutability;
+ SensorName sensorName;
+ std::function<SensorName(const Info&)> sensorNameFunc;
+ DbusInterfaceMap propertyInterfaces;
+};
+
+using Id = uint8_t;
+using IdInfoMap = std::map<Id, Info>;
+
+using PropertyMap = ipmi::PropertyMap;
+
+using InterfaceMap = std::map<DbusInterface, PropertyMap>;
+
+using Object = sdbusplus::message::object_path;
+using ObjectMap = std::map<Object, InterfaceMap>;
+
+using IpmiUpdateData = sdbusplus::message_t;
+
+struct SelData
+{
+ Id sensorID;
+ Type sensorType;
+ ReadingType eventReadingType;
+ Offset eventOffset;
+};
+
+using InventoryPath = std::string;
+
+using InvObjectIDMap = std::map<InventoryPath, SelData>;
+
+enum class ThresholdMask
+{
+ NON_CRITICAL_LOW_MASK = 0x01,
+ CRITICAL_LOW_MASK = 0x02,
+ NON_RECOVERABLE_LOW_MASK = 0x4,
+ NON_CRITICAL_HIGH_MASK = 0x08,
+ CRITICAL_HIGH_MASK = 0x10,
+ NON_RECOVERABLE_HIGH_MASK = 0x20,
+};
+
+static constexpr uint8_t maxContainedEntities = 4;
+using ContainedEntitiesArray =
+ std::array<std::pair<uint8_t, uint8_t>, maxContainedEntities>;
+
+struct EntityInfo
+{
+ uint8_t containerEntityId;
+ uint8_t containerEntityInstance;
+ bool isList;
+ bool isLinked;
+ ContainedEntitiesArray containedEntities;
+};
+
+using EntityInfoMap = std::map<Id, EntityInfo>;
+
+#ifdef FEATURE_SENSORS_CACHE
+/**
+ * @struct SensorData
+ *
+ * The data to cache for sensors
+ */
+struct SensorData
+{
+ double value;
+ bool available;
+ bool functional;
+ GetSensorResponse response;
+};
+
+using SensorCacheMap = std::map<uint8_t, std::optional<SensorData>>;
+#endif
+
+} // namespace sensor
+
+namespace network
+{
+constexpr auto MAC_ADDRESS_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx";
+
+constexpr auto IPV4_ADDRESS_SIZE_BYTE = 4;
+constexpr auto IPV6_ADDRESS_SIZE_BYTE = 16;
+
+constexpr auto DEFAULT_MAC_ADDRESS = "00:00:00:00:00:00";
+constexpr auto DEFAULT_ADDRESS = "0.0.0.0";
+
+} // namespace network
+
+template <typename T>
+class SecureAllocator : public std::allocator<T>
+{
+ public:
+ template <typename U>
+ struct rebind
+ {
+ typedef SecureAllocator<U> other;
+ };
+
+ void deallocate(T* p, size_t n)
+ {
+ OPENSSL_cleanse(p, n);
+ return std::allocator<T>::deallocate(p, n);
+ }
+};
+
+using SecureStringBase =
+ std::basic_string<char, std::char_traits<char>, SecureAllocator<char>>;
+class SecureString : public SecureStringBase
+{
+ public:
+ using SecureStringBase::basic_string;
+ SecureString(const SecureStringBase& other) : SecureStringBase(other) {};
+ SecureString(SecureString&) = default;
+ SecureString(const SecureString&) = default;
+ SecureString(SecureString&&) = default;
+ SecureString& operator=(SecureString&&) = default;
+ SecureString& operator=(const SecureString&) = default;
+
+ ~SecureString()
+ {
+ OPENSSL_cleanse(this->data(), this->size());
+ }
+};
+
+using SecureBufferBase = std::vector<uint8_t, SecureAllocator<uint8_t>>;
+
+class SecureBuffer : public SecureBufferBase
+{
+ public:
+ using SecureBufferBase::vector;
+ SecureBuffer(const SecureBufferBase& other) : SecureBufferBase(other) {};
+ SecureBuffer(SecureBuffer&) = default;
+ SecureBuffer(const SecureBuffer&) = default;
+ SecureBuffer(SecureBuffer&&) = default;
+ SecureBuffer& operator=(SecureBuffer&&) = default;
+ SecureBuffer& operator=(const SecureBuffer&) = default;
+
+ ~SecureBuffer()
+ {
+ OPENSSL_cleanse(this->data(), this->size());
+ }
+};
+} // namespace ipmi
diff --git a/include/ipmid/utility.hpp b/include/ipmid/utility.hpp
new file mode 100644
index 0000000..a3f306f
--- /dev/null
+++ b/include/ipmid/utility.hpp
@@ -0,0 +1,223 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include <boost/asio/spawn.hpp>
+#include <boost/callable_traits.hpp>
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+namespace ipmi
+{
+
+// forward declare Context and Request for NonIpmiArgsCount
+struct Context;
+namespace message
+{
+struct Request;
+}
+
+namespace utility
+{
+
+/**
+ * @brief a utility template to extract the args after N from a tuple
+ *
+ * Given a tuple of type <T1, ...TN, TN+1, ...>, provide type = tuple<TN+1,...>
+ */
+template <std::size_t N, typename FirstArg, typename... Rest>
+struct StripFirstArgs;
+
+template <std::size_t N, typename FirstArg, typename... Rest>
+struct StripFirstArgs<N, std::tuple<FirstArg, Rest...>> :
+ StripFirstArgs<N - 1, std::tuple<Rest...>>
+{};
+
+template <typename FirstArg, typename... Rest>
+struct StripFirstArgs<0, std::tuple<FirstArg, Rest...>>
+{
+ using type = std::tuple<FirstArg, Rest...>;
+};
+template <std::size_t N>
+struct StripFirstArgs<N, std::tuple<>>
+{
+ using type = std::tuple<>;
+};
+
+/**
+ * @brief a utility template to extract the remaining args from a tuple
+ *
+ * Given a tuple of type <T1, T2,...>, provide type = tuple<T2,...>
+ */
+template <typename T>
+using StripFirstArg = StripFirstArgs<1, T>;
+
+/**
+ * @brief a utility template to find the number of non-special arguments
+ *
+ * Given a tuple, count the args after the first special args
+ */
+template <typename FirstArg, typename... Rest>
+struct NonIpmiArgsCount;
+
+template <>
+struct NonIpmiArgsCount<std::tuple<>>
+{
+ constexpr static std::size_t size()
+ {
+ return 0;
+ }
+};
+template <typename FirstArg, typename... OtherArgs>
+struct NonIpmiArgsCount<std::tuple<FirstArg, OtherArgs...>>
+{
+ constexpr static std::size_t size()
+ {
+ if constexpr (std::is_same<
+ FirstArg,
+ std::shared_ptr<ipmi::message::Request>>::value ||
+ std::is_same<FirstArg,
+ std::shared_ptr<ipmi::Context>>::value ||
+ std::is_same<FirstArg, boost::asio::yield_context>::value)
+ {
+ return 1 + NonIpmiArgsCount<std::tuple<OtherArgs...>>::size();
+ }
+ else
+ {
+ return NonIpmiArgsCount<std::tuple<OtherArgs...>>::size();
+ }
+ }
+};
+
+/**
+ * @brief a utility template to find the type of the first arg
+ *
+ * Given a tuple, provide the type of the first element
+ */
+template <typename T>
+struct GetFirstArg
+{
+ using type = void;
+};
+
+template <typename FirstArg, typename... Rest>
+struct GetFirstArg<std::tuple<FirstArg, Rest...>>
+{
+ using type = FirstArg;
+};
+
+/**
+ * @brief a utility template to remove const and reference from types
+ *
+ * Given a tuple, provide the type of the first element
+ */
+template <typename... Args>
+struct DecayTuple;
+
+template <typename... Args>
+struct DecayTuple<std::tuple<Args...>>
+{
+ using type = std::tuple<typename std::decay<Args>::type...>;
+};
+
+/** @brief Convert T[N] to T* if is_same<Tbase,T>
+ *
+ * @tparam Tbase - The base type expected.
+ * @tparam T - The type to convert.
+ */
+template <typename Tbase, typename T>
+using ArrayToPtr_t = typename std::conditional_t<
+ std::is_array<T>::value,
+ std::conditional_t<std::is_same<Tbase, std::remove_extent_t<T>>::value,
+ std::add_pointer_t<std::remove_extent_t<T>>, T>,
+ T>;
+
+/** @brief Downcast type submembers.
+ *
+ * This allows std::tuple and std::pair members to be downcast to their
+ * non-const, nonref versions of themselves to limit duplication in template
+ * specializations
+ *
+ * 1. Remove references.
+ * 2. Remove 'const' and 'volatile'.
+ * 3. Convert 'char[N]' to 'char*'.
+ */
+template <typename T>
+struct DowncastMembers
+{
+ using type = T;
+};
+template <typename... Args>
+struct DowncastMembers<std::pair<Args...>>
+{
+ using type = std::pair<utility::ArrayToPtr_t<
+ char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename... Args>
+struct DowncastMembers<std::tuple<Args...>>
+{
+ using type = std::tuple<utility::ArrayToPtr_t<
+ char, std::remove_cv_t<std::remove_reference_t<Args>>>...>;
+};
+
+template <typename T>
+using DowncastMembers_t = typename DowncastMembers<T>::type;
+
+/** @brief Convert some C++ types to others for 'TypeId' conversion purposes.
+ *
+ * Similar C++ types have the same dbus type-id, so 'downcast' those to limit
+ * duplication in TypeId template specializations.
+ *
+ * 1. Remove references.
+ * 2. Remove 'const' and 'volatile'.
+ * 3. Convert 'char[N]' to 'char*'.
+ */
+template <typename T>
+struct TypeIdDowncast
+{
+ using type = utility::ArrayToPtr_t<
+ char, DowncastMembers_t<std::remove_cv_t<std::remove_reference_t<T>>>>;
+};
+
+template <typename T>
+using TypeIdDowncast_t = typename TypeIdDowncast<T>::type;
+
+/** @brief Detect if a type is a tuple
+ *
+ */
+template <typename>
+struct is_tuple : std::false_type
+{};
+
+template <typename... T>
+struct is_tuple<std::tuple<T...>> : std::true_type
+{};
+
+/** @brief used for static_assert in a constexpr-if else statement
+ */
+template <typename T>
+struct dependent_false : std::false_type
+{};
+
+} // namespace utility
+
+} // namespace ipmi
diff --git a/include/ipmid/utils.hpp b/include/ipmid/utils.hpp
new file mode 100644
index 0000000..4547dc0
--- /dev/null
+++ b/include/ipmid/utils.hpp
@@ -0,0 +1,506 @@
+#pragma once
+
+#include <boost/system/error_code.hpp>
+#include <ipmid/api-types.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <chrono>
+#include <optional>
+
+namespace ipmi
+{
+
+using namespace std::literals::chrono_literals;
+
+constexpr auto MAPPER_BUS_NAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_OBJ = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_INTF = "xyz.openbmc_project.ObjectMapper";
+
+constexpr auto ROOT = "/";
+constexpr auto HOST_MATCH = "host0";
+
+constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties";
+constexpr auto DELETE_INTERFACE = "xyz.openbmc_project.Object.Delete";
+
+constexpr auto METHOD_GET = "Get";
+constexpr auto METHOD_GET_ALL = "GetAll";
+constexpr auto METHOD_SET = "Set";
+
+/* Use a value of 5s which aligns with BT/KCS bridged timeouts, rather
+ * than the default 25s D-Bus timeout. */
+constexpr std::chrono::microseconds IPMI_DBUS_TIMEOUT = 5s;
+
+/** @class ServiceCache
+ * @brief Caches lookups of service names from the object mapper.
+ * @details Most ipmi commands need to talk to other dbus daemons to perform
+ * their intended actions on the BMC. This usually means they will
+ * first look up the service name providing the interface they
+ * require. This class reduces the number of such calls by caching
+ * the lookup for a specific service.
+ */
+class ServiceCache
+{
+ public:
+ /** @brief Creates a new service cache for the given interface
+ * and path.
+ *
+ * @param[in] intf - The interface used for each lookup
+ * @param[in] path - The path used for each lookup
+ */
+ ServiceCache(const std::string& intf, const std::string& path);
+ ServiceCache(std::string&& intf, std::string&& path);
+
+ /** @brief Gets the service name from the cache or does in a
+ * lookup when invalid.
+ *
+ * @param[in] bus - The bus associated with and used for looking
+ * up the service.
+ */
+ const std::string& getService(sdbusplus::bus_t& bus);
+
+ /** @brief Invalidates the current service name */
+ void invalidate();
+
+ /** @brief A wrapper around sdbusplus bus.new_method_call
+ *
+ * @param[in] bus - The bus used for calling the method
+ * @param[in] intf - The interface containing the method
+ * @param[in] method - The method name
+ * @return The message containing the method call.
+ */
+ sdbusplus::message_t newMethodCall(sdbusplus::bus_t& bus, const char* intf,
+ const char* method);
+
+ /** @brief Check to see if the current cache is valid
+ *
+ * @param[in] bus - The bus used for the service lookup
+ * @return True if the cache is valid false otherwise.
+ */
+ bool isValid(sdbusplus::bus_t& bus) const;
+
+ private:
+ /** @brief DBUS interface provided by the service */
+ const std::string intf;
+ /** @brief DBUS path provided by the service */
+ const std::string path;
+ /** @brief The name of the service if valid */
+ std::optional<std::string> cachedService;
+ /** @brief The name of the bus used in the service lookup */
+ std::optional<std::string> cachedBusName;
+};
+
+/**
+ * @brief Get the DBUS Service name for the input dbus path
+ *
+ * @param[in] bus - DBUS Bus Object
+ * @param[in] intf - DBUS Interface
+ * @param[in] path - DBUS Object Path
+ *
+ */
+std::string getService(sdbusplus::bus_t& bus, const std::string& intf,
+ const std::string& path);
+
+/** @brief Gets the dbus sub tree implementing the given interface.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] interfaces - Dbus interface.
+ * @param[in] subtreePath - subtree from where the search should start.
+ * @param[in] depth - Search depth
+ * @return map of object path and service info.
+ */
+ObjectTree getSubTree(sdbusplus::bus_t& bus, const InterfaceList& interface,
+ const std::string& subtreePath = ROOT, int32_t depth = 0);
+
+/** @brief Gets the dbus object info implementing the given interface
+ * from the given subtree.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] interface - Dbus interface.
+ * @param[in] subtreePath - subtree from where the search should start.
+ * @param[in] match - identifier for object.
+ * @return On success returns the object having objectpath and servicename.
+ */
+DbusObjectInfo getDbusObject(
+ sdbusplus::bus_t& bus, const std::string& interface,
+ const std::string& subtreePath = ROOT, const std::string& match = {});
+
+/** @brief Gets the value associated with the given object
+ * and the interface.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] service - Dbus service name.
+ * @param[in] objPath - Dbus object path.
+ * @param[in] interface - Dbus interface.
+ * @param[in] property - name of the property.
+ * @return On success returns the value of the property.
+ */
+Value getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& property,
+ std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief Gets all the properties associated with the given object
+ * and the interface.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] service - Dbus service name.
+ * @param[in] objPath - Dbus object path.
+ * @param[in] interface - Dbus interface.
+ * @return On success returns the map of name value pair.
+ */
+PropertyMap getAllDbusProperties(
+ sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief Gets all managed objects associated with the given object
+ * path and service.
+ * @param[in] bus - D-Bus Bus Object.
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @return On success returns the map of name value pair.
+ */
+ObjectValueTree getManagedObjects(sdbusplus::bus_t& bus,
+ const std::string& service,
+ const std::string& objPath);
+
+/** @brief Sets the property value of the given object.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] service - Dbus service name.
+ * @param[in] objPath - Dbus object path.
+ * @param[in] interface - Dbus interface.
+ * @param[in] property - name of the property.
+ * @param[in] value - value which needs to be set.
+ */
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& property, const Value& value,
+ std::chrono::microseconds timeout = IPMI_DBUS_TIMEOUT);
+
+/** @brief Gets all the dbus objects from the given service root
+ * which matches the object identifier.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] serviceRoot - Service root path.
+ * @param[in] interface - Dbus interface.
+ * @param[in] match - Identifier for a path.
+ * @returns map of object path and service info.
+ */
+ObjectTree getAllDbusObjects(
+ sdbusplus::bus_t& bus, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match = {});
+
+/** @brief Deletes all the dbus objects from the given service root
+ which matches the object identifier.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] serviceRoot - Service root path.
+ * @param[in] interface - Dbus interface.
+ * @param[in] match - Identifier for object.
+ */
+void deleteAllDbusObjects(sdbusplus::bus_t& bus, const std::string& serviceRoot,
+ const std::string& interface,
+ const std::string& match = {})
+ __attribute__((deprecated));
+
+/** @brief Gets the ancestor objects of the given object
+ which implements the given interface.
+ * @param[in] bus - Dbus bus object.
+ * @param[in] path - Child Dbus object path.
+ * @param[in] interfaces - Dbus interface list.
+ * @return map of object path and service info.
+ */
+ObjectTree getAllAncestors(sdbusplus::bus_t& bus, const std::string& path,
+ InterfaceList&& interfaces)
+ __attribute__((deprecated));
+
+/********* Begin co-routine yielding alternatives ***************/
+
+/** @brief Get the D-Bus Service name for the input D-Bus path
+ *
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] intf - D-Bus Interface
+ * @param[in] path - D-Bus Object Path
+ * @param[out] service - requested service name
+ * @return boost error code
+ *
+ */
+boost::system::error_code getService(Context::ptr ctx, const std::string& intf,
+ const std::string& path,
+ std::string& service);
+
+/** @brief Gets the dbus sub tree implementing the given interface.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] interfaces - Dbus interface.
+ * @param[in] subtreePath - subtree from where the search should start.
+ * @param[in] depth - Search depth
+ * @param[out] objectTree - map of object path and service info.
+ * @return map of object path and service info.
+ */
+boost::system::error_code getSubTree(
+ Context::ptr ctx, const InterfaceList& interface,
+ const std::string& subtreePath, int32_t depth, ObjectTree& objectTree);
+
+/** @brief Gets the D-Bus object info implementing the given interface
+ * from the given subtree.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] interface - D-Bus interface.
+ * @param[in][optional] subtreePath - subtree from where the search starts.
+ * @param[in][optional] match - identifier for object.
+ * @param[out] D-Bus object with path and service name
+ * @return - boost error code object
+ */
+boost::system::error_code getDbusObject(
+ Context::ptr ctx, const std::string& interface,
+ const std::string& subtreePath, const std::string& match,
+ DbusObjectInfo& dbusObject);
+
+// default for ROOT for subtreePath and std::string{} for match
+static inline boost::system::error_code getDbusObject(
+ Context::ptr ctx, const std::string& interface, DbusObjectInfo& dbusObject)
+{
+ return getDbusObject(ctx, interface, ROOT, {}, dbusObject);
+}
+
+// default std::string{} for match
+static inline boost::system::error_code getDbusObject(
+ Context::ptr ctx, const std::string& interface,
+ const std::string& subtreePath, DbusObjectInfo& dbusObject)
+{
+ return getDbusObject(ctx, interface, subtreePath, {}, dbusObject);
+}
+
+/** @brief Gets the value associated with the given object
+ * and the interface.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @param[in] interface - D-Bus interface.
+ * @param[in] property - name of the property.
+ * @param[out] propertyValue - value of the D-Bus property.
+ * @return - boost error code object
+ */
+template <typename Type>
+boost::system::error_code getDbusProperty(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& property,
+ Type& propertyValue)
+{
+ boost::system::error_code ec;
+ auto variant = ctx->bus->yield_method_call<std::variant<Type>>(
+ ctx->yield, ec, service.c_str(), objPath.c_str(), PROP_INTF, METHOD_GET,
+ interface, property);
+ if (!ec)
+ {
+ Type* tmp = std::get_if<Type>(&variant);
+ if (tmp)
+ {
+ propertyValue = *tmp;
+ return ec;
+ }
+ // user requested incorrect type; make an error code for them
+ ec = boost::system::errc::make_error_code(
+ boost::system::errc::invalid_argument);
+ }
+ return ec;
+}
+
+/** @brief Gets all the properties associated with the given object
+ * and the interface.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @param[in] interface - D-Bus interface.
+ * @param[out] properties - map of name value pair.
+ * @return - boost error code object
+ */
+boost::system::error_code getAllDbusProperties(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, PropertyMap& properties);
+
+/** @brief Sets the property value of the given object.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @param[in] interface - D-Bus interface.
+ * @param[in] property - name of the property.
+ * @param[in] value - value which needs to be set.
+ * @return - boost error code object
+ */
+boost::system::error_code setDbusProperty(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& property,
+ const Value& value);
+
+/** @brief Gets all the D-Bus objects from the given service root
+ * which matches the object identifier.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] serviceRoot - Service root path.
+ * @param[in] interface - D-Bus interface.
+ * @param[in][optional] match - Identifier for a path.
+ * @param[out] objectree - map of object path and service info.
+ * @return - boost error code object
+ */
+boost::system::error_code getAllDbusObjects(
+ Context::ptr ctx, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match,
+ ObjectTree& objectTree);
+
+// default std::string{} for match
+static inline boost::system::error_code getAllDbusObjects(
+ Context::ptr ctx, const std::string& serviceRoot,
+ const std::string& interface, ObjectTree& objectTree)
+{
+ return getAllDbusObjects(ctx, serviceRoot, interface, {}, objectTree);
+}
+
+/** @brief Deletes all the D-Bus objects from the given service root
+ which matches the object identifier.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[out] ec - boost error code object
+ * @param[in] serviceRoot - Service root path.
+ * @param[in] interface - D-Bus interface.
+ * @param[in] match - Identifier for object.
+ */
+boost::system::error_code deleteAllDbusObjects(
+ Context::ptr ctx, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match = {})
+ __attribute__((deprecated));
+
+/** @brief Gets all managed objects associated with the given object
+ * path and service.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @param[out] objects - map of name value pair.
+ * @return - boost error code object
+ */
+boost::system::error_code getManagedObjects(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ ObjectValueTree& objects);
+
+/** @brief Gets the ancestor objects of the given object
+ which implements the given interface.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] path - Child D-Bus object path.
+ * @param[in] interfaces - D-Bus interface list.
+ * @param[out] ObjectTree - map of object path and service info.
+ * @return - boost error code object
+ */
+boost::system::error_code getAllAncestors(
+ Context::ptr ctx, const std::string& path, const InterfaceList& interfaces,
+ ObjectTree& objectTree) __attribute__((deprecated));
+
+/** @brief Gets the value associated with the given object
+ * and the interface.
+ * @param[in] ctx - ipmi::Context::ptr
+ * @param[in] service - D-Bus service name.
+ * @param[in] objPath - D-Bus object path.
+ * @param[in] interface - D-Bus interface.
+ * @param[in] method - name of the method.
+ * @return - boost error code object
+ */
+
+boost::system::error_code callDbusMethod(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& method);
+
+/********* End co-routine yielding alternatives ***************/
+
+/** @brief Retrieve the value from map of variants,
+ * returning a default if the key does not exist or the
+ * type of the value does not match the expected type
+ *
+ * @tparam T - type of expected value to return
+ * @param[in] props - D-Bus propery map (Map of variants)
+ * @param[in] name - key name of property to fetch
+ * @param[in] defaultValue - default value to return on error
+ * @return - value from propery map at name, or defaultValue
+ */
+template <typename T>
+T mappedVariant(const ipmi::PropertyMap& props, const std::string& name,
+ const T& defaultValue)
+{
+ auto item = props.find(name);
+ if (item == props.end())
+ {
+ return defaultValue;
+ }
+ const T* prop = std::get_if<T>(&item->second);
+ if (!prop)
+ {
+ return defaultValue;
+ }
+ return *prop;
+}
+
+/** @struct VariantToDoubleVisitor
+ * @brief Visitor to convert variants to doubles
+ * @details Performs a static cast on the underlying type
+ */
+struct VariantToDoubleVisitor
+{
+ template <typename T>
+ std::enable_if_t<std::is_arithmetic<T>::value, double> operator()(
+ const T& t) const
+ {
+ return static_cast<double>(t);
+ }
+
+ template <typename T>
+ std::enable_if_t<!std::is_arithmetic<T>::value, double> operator()(
+ const T&) const
+ {
+ throw std::invalid_argument("Cannot translate type to double");
+ }
+};
+
+namespace method_no_args
+{
+
+/** @brief Calls the Dbus method which waits for response.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] service - Dbus service name.
+ * @param[in] objPath - Dbus object path.
+ * @param[in] interface - Dbus interface.
+ * @param[in] method - Dbus method.
+ */
+void callDbusMethod(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& method);
+
+} // namespace method_no_args
+
+template <typename... InputArgs>
+boost::system::error_code callDbusMethod(
+ ipmi::Context::ptr ctx, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& method, const InputArgs&... args)
+{
+ boost::system::error_code ec;
+ ctx->bus->yield_method_call(ctx->yield, ec, service, objPath, interface,
+ method, args...);
+
+ return ec;
+}
+
+template <typename RetType, typename... InputArgs>
+RetType callDbusMethod(ipmi::Context::ptr ctx, boost::system::error_code& ec,
+ const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& method,
+ const InputArgs&... args)
+{
+ auto rc = ctx->bus->yield_method_call<RetType>(
+ ctx->yield, ec, service, objPath, interface, method, args...);
+
+ return rc;
+}
+
+/** @brief Perform the low-level i2c bus write-read.
+ * @param[in] i2cBus - i2c bus device node name, such as /dev/i2c-2.
+ * @param[in] targetAddr - i2c device target address.
+ * @param[in] writeData - The data written to i2c device.
+ * @param[out] readBuf - Data read from the i2c device.
+ */
+ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t targetAddr,
+ std::vector<uint8_t> writeData,
+ std::vector<uint8_t>& readBuf);
+} // namespace ipmi
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 0000000..65b8385
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1,24 @@
+install_subdir(
+ 'dbus-sdr',
+ install_dir: get_option('includedir'),
+ strip_directory: false,
+ exclude_files: '*.cpp',
+)
+
+install_subdir(
+ 'ipmid',
+ install_dir: get_option('includedir'),
+ strip_directory: false,
+ exclude_files: '*.cpp',
+)
+
+install_subdir(
+ 'ipmid-host',
+ install_dir: get_option('includedir'),
+ strip_directory: false,
+ exclude_files: '*.cpp',
+)
+
+# install the ipmid-host and ipmid includes
+install_subdir('ipmid-host', install_dir: get_option('includedir'))
+install_subdir('ipmid', install_dir: get_option('includedir'))
diff --git a/ipmi_fru_info_area.cpp b/ipmi_fru_info_area.cpp
new file mode 100644
index 0000000..d259731
--- /dev/null
+++ b/ipmi_fru_info_area.cpp
@@ -0,0 +1,485 @@
+#include "ipmi_fru_info_area.hpp"
+
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <ctime>
+#include <iomanip>
+#include <map>
+#include <numeric>
+#include <sstream>
+
+namespace ipmi
+{
+namespace fru
+{
+using namespace phosphor::logging;
+
+// Property variables
+static constexpr auto partNumber = "Part Number";
+static constexpr auto serialNumber = "Serial Number";
+static constexpr auto manufacturer = "Manufacturer";
+static constexpr auto buildDate = "Mfg Date";
+static constexpr auto modelNumber = "Model Number";
+static constexpr auto prettyName = "Name";
+static constexpr auto version = "Version";
+static constexpr auto type = "Type";
+
+// Board info areas
+static constexpr auto board = "Board";
+static constexpr auto chassis = "Chassis";
+static constexpr auto product = "Product";
+
+static constexpr auto specVersion = 0x1;
+static constexpr auto recordUnitOfMeasurement = 0x8; // size in bytes
+static constexpr auto checksumSize = 0x1; // size in bytes
+static constexpr auto recordNotPresent = 0x0;
+static constexpr auto englishLanguageCode = 0x0;
+static constexpr auto typeLengthByteNull = 0x0;
+static constexpr auto endOfCustomFields = 0xC1;
+static constexpr auto commonHeaderFormatSize = 0x8; // size in bytes
+static constexpr auto areaSizeOffset = 0x1;
+static constexpr uint8_t typeASCII = 0xC0;
+static constexpr auto maxRecordAttributeValue = 0x3F;
+
+static constexpr auto secs_from_1970_1996 = 820454400;
+static constexpr auto maxMfgDateValue = 0xFFFFFF; // 3 Byte length
+static constexpr auto secs_per_min = 60;
+static constexpr auto secsToMaxMfgdate =
+ secs_from_1970_1996 + secs_per_min * maxMfgDateValue;
+
+// Minimum size of resulting FRU blob.
+// This is also the theoretical maximum size according to the spec:
+// 8 bytes header + 5 areas at 0xff*8 bytes max each
+// 8 + 5*0xff*8 = 0x27e0
+static constexpr auto fruMinSize = 0x27E0;
+
+// Value to use for padding.
+// Using 0xff to match the default (blank) value in a physical EEPROM.
+static constexpr auto fruPadValue = 0xff;
+
+/**
+ * @brief Format Beginning of Individual IPMI FRU Data Section
+ *
+ * @param[in] langCode Language code
+ * @param[in/out] data FRU area data
+ */
+void preFormatProcessing(bool langCode, FruAreaData& data)
+{
+ // Add id for version of FRU Info Storage Spec used
+ data.emplace_back(specVersion);
+
+ // Add Data Size - 0 as a placeholder, can edit after the data is finalized
+ data.emplace_back(typeLengthByteNull);
+
+ if (langCode)
+ {
+ data.emplace_back(englishLanguageCode);
+ }
+}
+
+/**
+ * @brief Append checksum of the FRU area data
+ *
+ * @param[in/out] data FRU area data
+ */
+void appendDataChecksum(FruAreaData& data)
+{
+ uint8_t checksumVal = std::accumulate(data.begin(), data.end(), 0);
+ // Push the Zero checksum as the last byte of this data
+ // This appears to be a simple summation of all the bytes
+ data.emplace_back(-checksumVal);
+}
+
+/**
+ * @brief Append padding bytes for the FRU area data
+ *
+ * @param[in/out] data FRU area data
+ */
+void padData(FruAreaData& data)
+{
+ uint8_t pad = (data.size() + checksumSize) % recordUnitOfMeasurement;
+ if (pad)
+ {
+ data.resize((data.size() + recordUnitOfMeasurement - pad));
+ }
+}
+
+/**
+ * @brief Format End of Individual IPMI FRU Data Section
+ *
+ * @param[in/out] fruAreaData FRU area info data
+ */
+void postFormatProcessing(FruAreaData& data)
+{
+ // This area needs to be padded to a multiple of 8 bytes (after checksum)
+ padData(data);
+
+ // Set size of data info area
+ data.at(areaSizeOffset) =
+ (data.size() + checksumSize) / (recordUnitOfMeasurement);
+
+ // Finally add area checksum
+ appendDataChecksum(data);
+}
+
+/**
+ * @brief Read chassis type property value from inventory and append to the FRU
+ * area data.
+ *
+ * @param[in] propMap map of property values
+ * @param[in,out] data FRU area data to be appended
+ */
+void appendChassisType(const PropertyMap& propMap, FruAreaData& data)
+{
+ uint8_t chassisType = 0; // Not specified
+ auto iter = propMap.find(type);
+ if (iter != propMap.end())
+ {
+ auto value = iter->second;
+ try
+ {
+ chassisType = std::stoi(value);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Could not parse chassis type, value: {VALUE}, "
+ "error: {ERROR}",
+ "VALUE", value, "ERROR", e);
+ chassisType = 0;
+ }
+ }
+ data.emplace_back(chassisType);
+}
+
+/**
+ * @brief Read property value from inventory and append to the FRU area data
+ *
+ * @param[in] key key to search for in the property inventory data
+ * @param[in] propMap map of property values
+ * @param[in,out] data FRU area data to be appended
+ */
+void appendData(const Property& key, const PropertyMap& propMap,
+ FruAreaData& data)
+{
+ auto iter = propMap.find(key);
+ if (iter != propMap.end())
+ {
+ auto value = iter->second;
+ // If starts with 0x or 0X remove them
+ // ex: 0x123a just take 123a
+ if ((value.compare(0, 2, "0x")) == 0 ||
+ (value.compare(0, 2, "0X") == 0))
+ {
+ value.erase(0, 2);
+ }
+
+ // 6 bits for length as per FRU spec v1.0
+ // if length is greater then 63(2^6) bytes then trim the data to 63
+ // bytess.
+ auto valueLength = (value.length() > maxRecordAttributeValue)
+ ? maxRecordAttributeValue
+ : value.length();
+ // 2 bits for type
+ // Set the type to ascii
+ uint8_t typeLength = valueLength | ipmi::fru::typeASCII;
+
+ data.emplace_back(typeLength);
+ std::copy(value.begin(), value.begin() + valueLength,
+ std::back_inserter(data));
+ }
+ else
+ {
+ // set 0 size
+ data.emplace_back(typeLengthByteNull);
+ }
+}
+
+std::time_t timeStringToRaw(const std::string& input)
+{
+ // TODO: For non-US region timestamps, pass in region information for the
+ // FRU to avoid the month/day swap.
+ // 2017-02-24 - 13:59:00, Tue Nov 20 23:08:00 2018
+ static const std::vector<std::string> patterns = {"%Y-%m-%d - %H:%M:%S",
+ "%a %b %d %H:%M:%S %Y"};
+
+ std::tm time = {};
+
+ for (const auto& pattern : patterns)
+ {
+ std::istringstream timeStream(input);
+ timeStream >> std::get_time(&time, pattern.c_str());
+ if (!timeStream.fail())
+ {
+ break;
+ }
+ }
+
+ return timegm(&time);
+}
+
+/**
+ * @brief Appends Build Date
+ *
+ * @param[in] propMap map of property values
+ * @param[in/out] data FRU area to add the manfufacture date
+ */
+void appendMfgDate(const PropertyMap& propMap, FruAreaData& data)
+{
+ // MFG Date/Time
+ auto iter = propMap.find(buildDate);
+ if ((iter != propMap.end()) && (iter->second.size() > 0))
+ {
+ std::time_t raw = timeStringToRaw(iter->second);
+
+ // From FRU Spec:
+ // "Mfg. Date / Time
+ // Number of minutes from 0:00 hrs 1/1/96.
+ // LSbyte first (little endian)
+ // 00_00_00h = unspecified."
+ if ((raw >= secs_from_1970_1996) && (raw <= secsToMaxMfgdate))
+ {
+ raw -= secs_from_1970_1996;
+ raw /= secs_per_min;
+ uint8_t fru_raw[3];
+ fru_raw[0] = raw & 0xFF;
+ fru_raw[1] = (raw >> 8) & 0xFF;
+ fru_raw[2] = (raw >> 16) & 0xFF;
+ std::copy(fru_raw, fru_raw + 3, std::back_inserter(data));
+ return;
+ }
+ std::fprintf(stderr, "MgfDate invalid date: %u secs since UNIX epoch\n",
+ static_cast<unsigned int>(raw));
+ }
+ // Blank date
+ data.emplace_back(0);
+ data.emplace_back(0);
+ data.emplace_back(0);
+}
+
+/**
+ * @brief Builds a section of the common header
+ *
+ * @param[in] infoAreaSize size of the FRU area to write
+ * @param[in] offset Current offset for data in overall record
+ * @param[in/out] data Common Header section data container
+ */
+void buildCommonHeaderSection(const uint32_t& infoAreaSize, uint16_t& offset,
+ FruAreaData& data)
+{
+ // Check if data for internal use section populated
+ if (infoAreaSize == 0)
+ {
+ // Indicate record not present
+ data.emplace_back(recordNotPresent);
+ }
+ else
+ {
+ // offset should be multiple of 8.
+ auto remainder = offset % recordUnitOfMeasurement;
+ // add the padding bytes in the offset so that offset
+ // will be multiple of 8 byte.
+ offset += (remainder > 0) ? recordUnitOfMeasurement - remainder : 0;
+ // Place data to define offset to area data section
+ data.emplace_back(offset / recordUnitOfMeasurement);
+
+ offset += infoAreaSize;
+ }
+}
+
+/**
+ * @brief Builds the Chassis info area data section
+ *
+ * @param[in] propMap map of properties for chassis info area
+ * @return FruAreaData container with chassis info area
+ */
+FruAreaData buildChassisInfoArea(const PropertyMap& propMap)
+{
+ FruAreaData fruAreaData;
+ if (!propMap.empty())
+ {
+ // Set formatting data that goes at the beginning of the record
+ preFormatProcessing(false, fruAreaData);
+
+ // chassis type
+ appendChassisType(propMap, fruAreaData);
+
+ // Chasiss part number, in config.yaml it is configured as model
+ appendData(modelNumber, propMap, fruAreaData);
+
+ // Board serial number
+ appendData(serialNumber, propMap, fruAreaData);
+
+ // Indicate End of Custom Fields
+ fruAreaData.emplace_back(endOfCustomFields);
+
+ // Complete record data formatting
+ postFormatProcessing(fruAreaData);
+ }
+ return fruAreaData;
+}
+
+/**
+ * @brief Builds the Board info area data section
+ *
+ * @param[in] propMap map of properties for board info area
+ * @return FruAreaData container with board info area
+ */
+FruAreaData buildBoardInfoArea(const PropertyMap& propMap)
+{
+ FruAreaData fruAreaData;
+ if (!propMap.empty())
+ {
+ preFormatProcessing(true, fruAreaData);
+
+ // Manufacturing date
+ appendMfgDate(propMap, fruAreaData);
+
+ // manufacturer
+ appendData(manufacturer, propMap, fruAreaData);
+
+ // Product name/Pretty name
+ appendData(prettyName, propMap, fruAreaData);
+
+ // Board serial number
+ appendData(serialNumber, propMap, fruAreaData);
+
+ // Board part number
+ appendData(partNumber, propMap, fruAreaData);
+
+ // FRU File ID - Empty
+ fruAreaData.emplace_back(typeLengthByteNull);
+
+ // Empty FRU File ID bytes
+ fruAreaData.emplace_back(recordNotPresent);
+
+ // End of custom fields
+ fruAreaData.emplace_back(endOfCustomFields);
+
+ postFormatProcessing(fruAreaData);
+ }
+ return fruAreaData;
+}
+
+/**
+ * @brief Builds the Product info area data section
+ *
+ * @param[in] propMap map of FRU properties for Board info area
+ * @return FruAreaData container with product info area data
+ */
+FruAreaData buildProductInfoArea(const PropertyMap& propMap)
+{
+ FruAreaData fruAreaData;
+ if (!propMap.empty())
+ {
+ // Set formatting data that goes at the beginning of the record
+ preFormatProcessing(true, fruAreaData);
+
+ // manufacturer
+ appendData(manufacturer, propMap, fruAreaData);
+
+ // Product name/Pretty name
+ appendData(prettyName, propMap, fruAreaData);
+
+ // Product part/model number
+ appendData(modelNumber, propMap, fruAreaData);
+
+ // Product version
+ appendData(version, propMap, fruAreaData);
+
+ // Serial Number
+ appendData(serialNumber, propMap, fruAreaData);
+
+ // Add Asset Tag
+ fruAreaData.emplace_back(recordNotPresent);
+
+ // FRU File ID - Empty
+ fruAreaData.emplace_back(typeLengthByteNull);
+
+ // Empty FRU File ID bytes
+ fruAreaData.emplace_back(recordNotPresent);
+
+ // End of custom fields
+ fruAreaData.emplace_back(endOfCustomFields);
+
+ postFormatProcessing(fruAreaData);
+ }
+ return fruAreaData;
+}
+
+FruAreaData buildFruAreaData(const FruInventoryData& inventory)
+{
+ FruAreaData combFruArea{};
+ // Now build common header with data for this FRU Inv Record
+ // Use this variable to increment size of header as we go along to determine
+ // offset for the subsequent area offsets
+ uint16_t curDataOffset = commonHeaderFormatSize;
+ // First byte is id for version of FRU Info Storage Spec used
+ combFruArea.emplace_back(specVersion);
+
+ // 2nd byte is offset to internal use data
+ combFruArea.emplace_back(recordNotPresent);
+
+ // 3rd byte is offset to chassis data
+ FruAreaData chassisArea;
+ auto chassisIt = inventory.find(chassis);
+ if (chassisIt != inventory.end())
+ {
+ chassisArea = buildChassisInfoArea(chassisIt->second);
+ }
+ // update the offset to chassis data.
+ buildCommonHeaderSection(chassisArea.size(), curDataOffset, combFruArea);
+
+ // 4th byte is offset to board data
+ FruAreaData boardArea;
+ auto boardIt = inventory.find(board);
+ if (boardIt != inventory.end())
+ {
+ boardArea = buildBoardInfoArea(boardIt->second);
+ }
+ // update the offset to the board data.
+ buildCommonHeaderSection(boardArea.size(), curDataOffset, combFruArea);
+
+ // 5th byte is offset to product data
+ FruAreaData prodArea;
+ auto prodIt = inventory.find(product);
+ if (prodIt != inventory.end())
+ {
+ prodArea = buildProductInfoArea(prodIt->second);
+ }
+ // update the offset to the product data.
+ buildCommonHeaderSection(prodArea.size(), curDataOffset, combFruArea);
+
+ // 6th byte is offset to multirecord data
+ combFruArea.emplace_back(recordNotPresent);
+
+ // 7th byte is PAD
+ combFruArea.emplace_back(recordNotPresent);
+
+ // 8th (Final byte of Header Format) is the checksum
+ appendDataChecksum(combFruArea);
+
+ // Combine everything into one full IPMI FRU specification Record
+ // add chassis use area data
+ combFruArea.insert(combFruArea.end(), chassisArea.begin(),
+ chassisArea.end());
+
+ // add board area data
+ combFruArea.insert(combFruArea.end(), boardArea.begin(), boardArea.end());
+
+ // add product use area data
+ combFruArea.insert(combFruArea.end(), prodArea.begin(), prodArea.end());
+
+ // If area is smaller than the minimum size, pad it. This enables ipmitool
+ // to update the FRU blob with values longer than the original payload.
+ if (combFruArea.size() < fruMinSize)
+ {
+ combFruArea.resize(fruMinSize, fruPadValue);
+ }
+
+ return combFruArea;
+}
+
+} // namespace fru
+} // namespace ipmi
diff --git a/ipmi_fru_info_area.hpp b/ipmi_fru_info_area.hpp
new file mode 100644
index 0000000..ea70401
--- /dev/null
+++ b/ipmi_fru_info_area.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace ipmi
+{
+namespace fru
+{
+using FruAreaData = std::vector<uint8_t>;
+using Section = std::string;
+using Value = std::string;
+using Property = std::string;
+using PropertyMap = std::map<Property, Value>;
+using FruInventoryData = std::map<Section, PropertyMap>;
+
+/**
+ * @brief Builds Fru area data from inventory data
+ *
+ * @param[in] invData FRU properties values read from inventory
+ *
+ * @return FruAreaData FRU area data as per IPMI specification
+ */
+FruAreaData buildFruAreaData(const FruInventoryData& inventory);
+
+} // namespace fru
+} // namespace ipmi
diff --git a/ipmiallowlist.hpp b/ipmiallowlist.hpp
new file mode 100644
index 0000000..7519de8
--- /dev/null
+++ b/ipmiallowlist.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <utility>
+#include <vector>
+
+using netfncmd_pair = std::pair<unsigned char, unsigned char>;
+
+extern const std::vector<netfncmd_pair> allowlist;
diff --git a/ipmid-new.cpp b/ipmid-new.cpp
new file mode 100644
index 0000000..7d3dd2d
--- /dev/null
+++ b/ipmid-new.cpp
@@ -0,0 +1,910 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "settings.hpp"
+
+#include <dlfcn.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/asio/detached.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/spawn.hpp>
+#include <host-cmd-manager.hpp>
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/handler.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/oemrouter.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <algorithm>
+#include <any>
+#include <exception>
+#include <filesystem>
+#include <forward_list>
+#include <map>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace fs = std::filesystem;
+
+using namespace phosphor::logging;
+
+// IPMI Spec, shared Reservation ID.
+static unsigned short selReservationID = 0xFFFF;
+static bool selReservationValid = false;
+
+unsigned short reserveSel(void)
+{
+ // IPMI spec, Reservation ID, the value simply increases against each
+ // execution of the Reserve SEL command.
+ if (++selReservationID == 0)
+ {
+ selReservationID = 1;
+ }
+ selReservationValid = true;
+ return selReservationID;
+}
+
+bool checkSELReservation(unsigned short id)
+{
+ return (selReservationValid && selReservationID == id);
+}
+
+void cancelSELReservation(void)
+{
+ selReservationValid = false;
+}
+
+EInterfaceIndex getInterfaceIndex(void)
+{
+ return interfaceKCS;
+}
+
+sd_bus* bus;
+sd_event* events = nullptr;
+sd_event* ipmid_get_sd_event_connection(void)
+{
+ return events;
+}
+sd_bus* ipmid_get_sd_bus_connection(void)
+{
+ return bus;
+}
+
+namespace ipmi
+{
+
+static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
+{
+ return (cluster << 8) | cmd;
+}
+
+using HandlerTuple = std::tuple<int, /* prio */
+ Privilege, HandlerBase::ptr /* handler */
+ >;
+
+/* map to handle standard registered commands */
+static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
+ HandlerTuple>
+ handlerMap;
+
+/* special map for decoding Group registered commands (NetFn 2Ch) */
+static std::unordered_map<unsigned int, /* key is Group/Cmd (NetFn is 2Ch) */
+ HandlerTuple>
+ groupHandlerMap;
+
+/* special map for decoding OEM registered commands (NetFn 2Eh) */
+static std::unordered_map<unsigned int, /* key is Iana/Cmd (NetFn is 2Eh) */
+ HandlerTuple>
+ oemHandlerMap;
+
+using FilterTuple = std::tuple<int, /* prio */
+ FilterBase::ptr /* filter */
+ >;
+
+/* list to hold all registered ipmi command filters */
+static std::forward_list<FilterTuple> filterList;
+
+namespace impl
+{
+/* common function to register all standard IPMI handlers */
+bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
+ HandlerBase::ptr handler)
+{
+ // check for valid NetFn: even; 00-0Ch, 30-3Eh
+ if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
+ netFn > netFnOemEight)
+ {
+ return false;
+ }
+
+ // create key and value for this handler
+ unsigned int netFnCmd = makeCmdKey(netFn, cmd);
+ HandlerTuple item(prio, priv, handler);
+
+ // consult the handler map and look for a match
+ auto& mapCmd = handlerMap[netFnCmd];
+ if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+ {
+ mapCmd = item;
+ return true;
+ }
+ return false;
+}
+
+/* common function to register all Group IPMI handlers */
+bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
+ HandlerBase::ptr handler)
+{
+ // create key and value for this handler
+ unsigned int netFnCmd = makeCmdKey(group, cmd);
+ HandlerTuple item(prio, priv, handler);
+
+ // consult the handler map and look for a match
+ auto& mapCmd = groupHandlerMap[netFnCmd];
+ if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+ {
+ mapCmd = item;
+ return true;
+ }
+ return false;
+}
+
+/* common function to register all OEM IPMI handlers */
+bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
+ HandlerBase::ptr handler)
+{
+ // create key and value for this handler
+ unsigned int netFnCmd = makeCmdKey(iana, cmd);
+ HandlerTuple item(prio, priv, handler);
+
+ // consult the handler map and look for a match
+ auto& mapCmd = oemHandlerMap[netFnCmd];
+ if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
+ {
+ mapCmd = item;
+ lg2::debug("registered OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
+ lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
+ netFnCmd);
+ return true;
+ }
+
+ lg2::warning("could not register OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
+ lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
+ netFnCmd);
+ return false;
+}
+
+/* common function to register all IPMI filter handlers */
+void registerFilter(int prio, FilterBase::ptr filter)
+{
+ // check for initial placement
+ if (filterList.empty() || std::get<int>(filterList.front()) < prio)
+ {
+ filterList.emplace_front(std::make_tuple(prio, filter));
+ return;
+ }
+ // walk the list and put it in the right place
+ auto j = filterList.begin();
+ for (auto i = j; i != filterList.end() && std::get<int>(*i) > prio; i++)
+ {
+ j = i;
+ }
+ filterList.emplace_after(j, std::make_tuple(prio, filter));
+}
+
+} // namespace impl
+
+message::Response::ptr filterIpmiCommand(message::Request::ptr request)
+{
+ // pass the command through the filter mechanism
+ // This can be the firmware firewall or any OEM mechanism like
+ // whitelist filtering based on operational mode
+ for (auto& item : filterList)
+ {
+ FilterBase::ptr filter = std::get<FilterBase::ptr>(item);
+ ipmi::Cc cc = filter->call(request);
+ if (ipmi::ccSuccess != cc)
+ {
+ return errorResponse(request, cc);
+ }
+ }
+ return message::Response::ptr();
+}
+
+message::Response::ptr executeIpmiCommandCommon(
+ std::unordered_map<unsigned int, HandlerTuple>& handlers,
+ unsigned int keyCommon, message::Request::ptr request)
+{
+ // filter the command first; a non-null message::Response::ptr
+ // means that the message has been rejected for some reason
+ message::Response::ptr filterResponse = filterIpmiCommand(request);
+
+ Cmd cmd = request->ctx->cmd;
+ unsigned int key = makeCmdKey(keyCommon, cmd);
+ auto cmdIter = handlers.find(key);
+ if (cmdIter != handlers.end())
+ {
+ // only return the filter response if the command is found
+ if (filterResponse)
+ {
+ lg2::debug("request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
+ "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
+ return filterResponse;
+ }
+ HandlerTuple& chosen = cmdIter->second;
+ if (request->ctx->priv < std::get<Privilege>(chosen))
+ {
+ return errorResponse(request, ccInsufficientPrivilege);
+ }
+ return std::get<HandlerBase::ptr>(chosen)->call(request);
+ }
+ else
+ {
+ unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
+ cmdIter = handlers.find(wildcard);
+ if (cmdIter != handlers.end())
+ {
+ // only return the filter response if the command is found
+ if (filterResponse)
+ {
+ lg2::debug(
+ "request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
+ "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
+ return filterResponse;
+ }
+ HandlerTuple& chosen = cmdIter->second;
+ if (request->ctx->priv < std::get<Privilege>(chosen))
+ {
+ return errorResponse(request, ccInsufficientPrivilege);
+ }
+ return std::get<HandlerBase::ptr>(chosen)->call(request);
+ }
+ }
+ return errorResponse(request, ccInvalidCommand);
+}
+
+message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request)
+{
+ // look up the group for this request
+ uint8_t bytes;
+ if (0 != request->payload.unpack(bytes))
+ {
+ return errorResponse(request, ccReqDataLenInvalid);
+ }
+ auto group = static_cast<Group>(bytes);
+ // Set defining body code
+ request->ctx->group = group;
+ message::Response::ptr response =
+ executeIpmiCommandCommon(groupHandlerMap, group, request);
+ ipmi::message::Payload prefix;
+ prefix.pack(bytes);
+ response->prepend(prefix);
+ return response;
+}
+
+message::Response::ptr executeIpmiOemCommand(message::Request::ptr request)
+{
+ // look up the iana for this request
+ uint24_t bytes;
+ if (0 != request->payload.unpack(bytes))
+ {
+ return errorResponse(request, ccReqDataLenInvalid);
+ }
+ auto iana = static_cast<Iana>(bytes);
+
+ lg2::debug("unpack IANA {IANA}", "IANA", lg2::hex, iana);
+
+ message::Response::ptr response =
+ executeIpmiCommandCommon(oemHandlerMap, iana, request);
+ ipmi::message::Payload prefix;
+ prefix.pack(bytes);
+ response->prepend(prefix);
+ return response;
+}
+
+message::Response::ptr executeIpmiCommand(message::Request::ptr request)
+{
+ NetFn netFn = request->ctx->netFn;
+ if (netFnGroup == netFn)
+ {
+ return executeIpmiGroupCommand(request);
+ }
+ else if (netFnOem == netFn)
+ {
+ return executeIpmiOemCommand(request);
+ }
+ return executeIpmiCommandCommon(handlerMap, netFn, request);
+}
+
+namespace utils
+{
+template <typename AssocContainer, typename UnaryPredicate>
+void assoc_erase_if(AssocContainer& c, UnaryPredicate p)
+{
+ typename AssocContainer::iterator next = c.begin();
+ typename AssocContainer::iterator last = c.end();
+ while ((next = std::find_if(next, last, p)) != last)
+ {
+ c.erase(next++);
+ }
+}
+} // namespace utils
+
+namespace
+{
+std::unordered_map<std::string, uint8_t> uniqueNameToChannelNumber;
+
+// sdbusplus::bus::match::rules::arg0namespace() wants the prefix
+// to match without any trailing '.'
+constexpr const char ipmiDbusChannelMatch[] =
+ "xyz.openbmc_project.Ipmi.Channel";
+void updateOwners(sdbusplus::asio::connection& conn, const std::string& name)
+{
+ conn.async_method_call(
+ [name](const boost::system::error_code ec,
+ const std::string& nameOwner) {
+ if (ec)
+ {
+ lg2::error("Error getting dbus owner for {INTERFACE}",
+ "INTERFACE", name);
+ return;
+ }
+ // start after ipmiDbusChannelPrefix (after the '.')
+ std::string chName =
+ name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
+ try
+ {
+ uint8_t channel = getChannelByName(chName);
+ uniqueNameToChannelNumber[nameOwner] = channel;
+ lg2::info(
+ "New interface mapping: {INTERFACE} -> channel {CHANNEL}",
+ "INTERFACE", name, "CHANNEL", channel);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("Failed interface mapping, no such name: {INTERFACE}",
+ "INTERFACE", name);
+ }
+ },
+ "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
+ name);
+}
+
+void doListNames(sdbusplus::asio::connection& conn)
+{
+ conn.async_method_call(
+ [&conn](const boost::system::error_code ec,
+ std::vector<std::string> busNames) {
+ if (ec)
+ {
+ lg2::error("Error getting dbus names: {ERROR}", "ERROR",
+ ec.message());
+ std::exit(EXIT_FAILURE);
+ return;
+ }
+ // Try to make startup consistent
+ std::sort(busNames.begin(), busNames.end());
+
+ const std::string channelPrefix =
+ std::string(ipmiDbusChannelMatch) + ".";
+ for (const std::string& busName : busNames)
+ {
+ if (busName.find(channelPrefix) == 0)
+ {
+ updateOwners(conn, busName);
+ }
+ }
+ },
+ "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
+ "ListNames");
+}
+
+void nameChangeHandler(sdbusplus::message_t& message)
+{
+ std::string name;
+ std::string oldOwner;
+ std::string newOwner;
+
+ message.read(name, oldOwner, newOwner);
+
+ if (!oldOwner.empty())
+ {
+ if (boost::starts_with(oldOwner, ":"))
+ {
+ // Connection removed
+ auto it = uniqueNameToChannelNumber.find(oldOwner);
+ if (it != uniqueNameToChannelNumber.end())
+ {
+ uniqueNameToChannelNumber.erase(it);
+ }
+ }
+ }
+ if (!newOwner.empty())
+ {
+ // start after ipmiDbusChannelMatch (and after the '.')
+ std::string chName = name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
+ try
+ {
+ uint8_t channel = getChannelByName(chName);
+ uniqueNameToChannelNumber[newOwner] = channel;
+ lg2::info("New interface mapping: {INTERFACE} -> channel {CHANNEL}",
+ "INTERFACE", name, "CHANNEL", channel);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::info("Failed interface mapping, no such name: {INTERFACE}",
+ "INTERFACE", name);
+ }
+ }
+};
+
+} // anonymous namespace
+
+static constexpr const char intraBmcName[] = "INTRABMC";
+uint8_t channelFromMessage(sdbusplus::message_t& msg)
+{
+ // channel name for ipmitool to resolve to
+ std::string sender = msg.get_sender();
+ auto chIter = uniqueNameToChannelNumber.find(sender);
+ if (chIter != uniqueNameToChannelNumber.end())
+ {
+ return chIter->second;
+ }
+ // FIXME: currently internal connections are ephemeral and hard to pin down
+ try
+ {
+ return getChannelByName(intraBmcName);
+ }
+ catch (const std::exception& e)
+ {
+ return invalidChannel;
+ }
+} // namespace ipmi
+
+/* called from sdbus async server context */
+auto executionEntry(boost::asio::yield_context yield, sdbusplus::message_t& m,
+ NetFn netFn, uint8_t lun, Cmd cmd, ipmi::SecureBuffer& data,
+ std::map<std::string, ipmi::Value>& options)
+{
+ const auto dbusResponse =
+ [netFn, lun, cmd](Cc cc, const ipmi::SecureBuffer& data = {}) {
+ constexpr uint8_t netFnResponse = 0x01;
+ uint8_t retNetFn = netFn | netFnResponse;
+ return std::make_tuple(retNetFn, lun, cmd, cc, data);
+ };
+ std::string sender = m.get_sender();
+ Privilege privilege = Privilege::None;
+ int rqSA = 0;
+ int hostIdx = 0;
+ uint8_t userId = 0; // undefined user
+ uint32_t sessionId = 0;
+
+ // figure out what channel the request came in on
+ uint8_t channel = channelFromMessage(m);
+ if (channel == invalidChannel)
+ {
+ // unknown sender channel; refuse to service the request
+ lg2::error("ERROR determining source IPMI channel from "
+ "{SENDER} NetFn/Cmd {NETFN}/{CMD}",
+ "SENDER", sender, "NETFN", lg2::hex, netFn, "CMD", lg2::hex,
+ cmd);
+ return dbusResponse(ipmi::ccDestinationUnavailable);
+ }
+
+ // session-based channels are required to provide userId, privilege and
+ // sessionId
+ if (getChannelSessionSupport(channel) != EChannelSessSupported::none)
+ {
+ try
+ {
+ Value requestPriv = options.at("privilege");
+ Value requestUserId = options.at("userId");
+ Value requestSessionId = options.at("currentSessionId");
+ privilege = static_cast<Privilege>(std::get<int>(requestPriv));
+ userId = static_cast<uint8_t>(std::get<int>(requestUserId));
+ sessionId =
+ static_cast<uint32_t>(std::get<uint32_t>(requestSessionId));
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("ERROR determining IPMI session credentials on "
+ "channel {CHANNEL} for userid {USERID}",
+ "CHANNEL", channel, "USERID", userId, "NETFN", lg2::hex,
+ netFn, "CMD", lg2::hex, cmd);
+ return dbusResponse(ipmi::ccUnspecifiedError);
+ }
+ }
+ else
+ {
+ // get max privilege for session-less channels
+ // For now, there is not a way to configure this, default to Admin
+ privilege = Privilege::Admin;
+
+ // ipmb should supply rqSA
+ ChannelInfo chInfo;
+ getChannelInfo(channel, chInfo);
+ if (static_cast<EChannelMediumType>(chInfo.mediumType) ==
+ EChannelMediumType::ipmb)
+ {
+ const auto iter = options.find("rqSA");
+ if (iter != options.end())
+ {
+ if (std::holds_alternative<int>(iter->second))
+ {
+ rqSA = std::get<int>(iter->second);
+ }
+ }
+ const auto iteration = options.find("hostId");
+ if (iteration != options.end())
+ {
+ if (std::holds_alternative<int>(iteration->second))
+ {
+ hostIdx = std::get<int>(iteration->second);
+ }
+ }
+ }
+ }
+ // check to see if the requested priv/username is valid
+ lg2::debug("Set up ipmi context: Ch:NetFn/Cmd={CHANNEL}:{NETFN}/{CMD}",
+ "SENDER", sender, "NETFN", lg2::hex, netFn, "LUN", lg2::hex, lun,
+ "CMD", lg2::hex, cmd, "CHANNEL", channel, "USERID", userId,
+ "SESSIONID", lg2::hex, sessionId, "PRIVILEGE",
+ static_cast<uint8_t>(privilege), "RQSA", lg2::hex, rqSA);
+
+ auto ctx = std::make_shared<ipmi::Context>(
+ getSdBus(), netFn, lun, cmd, channel, userId, sessionId, privilege,
+ rqSA, hostIdx, yield);
+ auto request = std::make_shared<ipmi::message::Request>(
+ ctx, std::forward<ipmi::SecureBuffer>(data));
+ message::Response::ptr response = executeIpmiCommand(request);
+
+ return dbusResponse(response->cc, response->payload.raw);
+}
+
+/** @struct IpmiProvider
+ *
+ * RAII wrapper for dlopen so that dlclose gets called on exit
+ */
+struct IpmiProvider
+{
+ public:
+ /** @brief address of the opened library */
+ void* addr;
+ std::string name;
+
+ IpmiProvider() = delete;
+ IpmiProvider(const IpmiProvider&) = delete;
+ IpmiProvider& operator=(const IpmiProvider&) = delete;
+ IpmiProvider(IpmiProvider&&) = delete;
+ IpmiProvider& operator=(IpmiProvider&&) = delete;
+
+ /** @brief dlopen a shared object file by path
+ * @param[in] filename - path of shared object to open
+ */
+ explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
+ {
+ lg2::debug("Open IPMI provider library: {PROVIDER}", "PROVIDER", name);
+ try
+ {
+ addr = dlopen(name.c_str(), RTLD_NOW);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+ "PROVIDER", name, "ERROR", e);
+ }
+ catch (...)
+ {
+ const char* what = currentExceptionType();
+ lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+ "PROVIDER", name, "ERROR", what);
+ }
+ if (!isOpen())
+ {
+ lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
+ "PROVIDER", name, "ERROR", dlerror());
+ }
+ }
+
+ ~IpmiProvider()
+ {
+ if (isOpen())
+ {
+ dlclose(addr);
+ }
+ }
+ bool isOpen() const
+ {
+ return (nullptr != addr);
+ }
+};
+
+// Plugin libraries need to contain .so either at the end or in the middle
+constexpr const char ipmiPluginExtn[] = ".so";
+
+/* return a list of self-closing library handles */
+std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
+{
+ std::vector<fs::path> libs;
+ for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
+ {
+ std::error_code ec;
+ fs::path fname = libPath.path();
+ if (fs::is_symlink(fname, ec) || ec)
+ {
+ // it's a symlink or some other error; skip it
+ continue;
+ }
+ while (fname.has_extension())
+ {
+ fs::path extn = fname.extension();
+ if (extn == ipmiPluginExtn)
+ {
+ libs.push_back(libPath.path());
+ break;
+ }
+ fname.replace_extension();
+ }
+ }
+ std::sort(libs.begin(), libs.end());
+
+ std::forward_list<IpmiProvider> handles;
+ for (auto& lib : libs)
+ {
+#ifdef __IPMI_DEBUG__
+ lg2::debug("Registering handler {HANDLER}", "HANDLER", lib);
+#endif
+ handles.emplace_front(lib.c_str());
+ }
+ return handles;
+}
+
+} // namespace ipmi
+
+#ifdef ALLOW_DEPRECATED_API
+/* legacy registration */
+void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
+ ipmi_context_t context, ipmid_callback_t handler,
+ ipmi_cmd_privilege_t priv)
+{
+ auto h = ipmi::makeLegacyHandler(handler, context);
+ // translate priv from deprecated enum to current
+ ipmi::Privilege realPriv;
+ switch (priv)
+ {
+ case PRIVILEGE_CALLBACK:
+ realPriv = ipmi::Privilege::Callback;
+ break;
+ case PRIVILEGE_USER:
+ realPriv = ipmi::Privilege::User;
+ break;
+ case PRIVILEGE_OPERATOR:
+ realPriv = ipmi::Privilege::Operator;
+ break;
+ case PRIVILEGE_ADMIN:
+ realPriv = ipmi::Privilege::Admin;
+ break;
+ case PRIVILEGE_OEM:
+ realPriv = ipmi::Privilege::Oem;
+ break;
+ case SYSTEM_INTERFACE:
+ realPriv = ipmi::Privilege::Admin;
+ break;
+ default:
+ realPriv = ipmi::Privilege::Admin;
+ break;
+ }
+ // The original ipmi_register_callback allowed for group OEM handlers
+ // to be registered via this same interface. It just so happened that
+ // all the handlers were part of the DCMI group, so default to that.
+ if (netFn == NETFUN_GRPEXT)
+ {
+ ipmi::impl::registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
+ cmd, realPriv, h);
+ }
+ else
+ {
+ ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv,
+ h);
+ }
+}
+
+namespace oem
+{
+
+class LegacyRouter : public oem::Router
+{
+ public:
+ virtual ~LegacyRouter() {}
+
+ /// Enable message routing to begin.
+ void activate() override {}
+
+ void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override
+ {
+ auto h = ipmi::makeLegacyHandler(std::forward<Handler>(handler));
+ ipmi::impl::registerOemHandler(ipmi::prioOpenBmcBase, oen, cmd,
+ ipmi::Privilege::Admin, h);
+ }
+};
+static LegacyRouter legacyRouter;
+
+Router* mutableRouter()
+{
+ return &legacyRouter;
+}
+
+} // namespace oem
+
+/* legacy alternative to executionEntry */
+void handleLegacyIpmiCommand(sdbusplus::message_t& m)
+{
+ // make a copy so the next two moves don't wreak havoc on the stack
+ sdbusplus::message_t b{m};
+ boost::asio::spawn(
+ *getIoContext(),
+ [b = std::move(b)](boost::asio::yield_context yield) {
+ sdbusplus::message_t m{std::move(b)};
+ unsigned char seq = 0, netFn = 0, lun = 0, cmd = 0;
+ ipmi::SecureBuffer data;
+
+ m.read(seq, netFn, lun, cmd, data);
+ std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
+ auto ctx = std::make_shared<ipmi::Context>(
+ bus, netFn, lun, cmd, 0, 0, 0, ipmi::Privilege::Admin, 0, 0,
+ yield);
+ auto request = std::make_shared<ipmi::message::Request>(
+ ctx, std::forward<ipmi::SecureBuffer>(data));
+ ipmi::message::Response::ptr response =
+ ipmi::executeIpmiCommand(request);
+
+ // Responses in IPMI require a bit set. So there ya go...
+ netFn |= 0x01;
+
+ constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
+
+ std::string dest = m.get_sender();
+ std::string path = m.get_path();
+ boost::system::error_code ec = ipmi::callDbusMethod(
+ ctx, dest, path, DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
+ response->cc, response->payload.raw);
+
+ if (ec)
+ {
+ lg2::error(
+ "Failed to send response to requestor ({NETFN}/{CMD}): {ERROR}",
+ "ERROR", ec.message(), "SENDER", dest, "NETFN", lg2::hex,
+ netFn, "CMD", lg2::hex, cmd);
+ }
+ },
+ boost::asio::detached);
+}
+
+#endif /* ALLOW_DEPRECATED_API */
+
+// Calls host command manager to do the right thing for the command
+using CommandHandler = phosphor::host::command::CommandHandler;
+std::unique_ptr<phosphor::host::command::Manager> cmdManager;
+void ipmid_send_cmd_to_host(CommandHandler&& cmd)
+{
+ cmdManager->execute(std::forward<CommandHandler>(cmd));
+}
+
+std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
+{
+ return cmdManager;
+}
+
+// These are symbols that are present in libipmid, but not expected
+// to be used except here (or maybe a unit test), so declare them here
+extern void setIoContext(std::shared_ptr<boost::asio::io_context>& newIo);
+extern void setSdBus(std::shared_ptr<sdbusplus::asio::connection>& newBus);
+
+int main(int argc, char* argv[])
+{
+ // Connect to system bus
+ auto io = std::make_shared<boost::asio::io_context>();
+ setIoContext(io);
+ if (argc > 1 && std::string(argv[1]) == "-session")
+ {
+ sd_bus_default_user(&bus);
+ }
+ else
+ {
+ sd_bus_default_system(&bus);
+ }
+ auto sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
+ setSdBus(sdbusp);
+
+ // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
+ // queue stops running if we don't have a timer that keeps re-arming
+ sdbusplus::Timer t2([]() { ; });
+ t2.start(std::chrono::microseconds(500000), true);
+
+ // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
+ // until that is done, add the sd_event wrapper to the io object
+ sdbusplus::asio::sd_event_wrapper sdEvents(*io);
+
+ cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
+
+ // Register all command providers and filters
+ std::forward_list<ipmi::IpmiProvider> providers =
+ ipmi::loadProviders(HOST_IPMI_LIB_PATH);
+
+#ifdef ALLOW_DEPRECATED_API
+ // listen on deprecated signal interface for kcs/bt commands
+ constexpr const char* FILTER = "type='signal',interface='org.openbmc."
+ "HostIpmi',member='ReceivedMessage'";
+ sdbusplus::bus::match_t oldIpmiInterface(*sdbusp, FILTER,
+ handleLegacyIpmiCommand);
+#endif /* ALLOW_DEPRECATED_API */
+
+ // set up bus name watching to match channels with bus names
+ sdbusplus::bus::match_t nameOwnerChanged(
+ *sdbusp,
+ sdbusplus::bus::match::rules::nameOwnerChanged() +
+ sdbusplus::bus::match::rules::arg0namespace(
+ ipmi::ipmiDbusChannelMatch),
+ ipmi::nameChangeHandler);
+ ipmi::doListNames(*sdbusp);
+
+ int exitCode = 0;
+ // set up boost::asio signal handling
+ std::function<SignalResponse(int)> stopAsioRunLoop = [&io, &exitCode](
+ int signalNumber) {
+ lg2::info("Received signal {SIGNAL}; quitting", "SIGNAL", signalNumber);
+ io->stop();
+ exitCode = signalNumber;
+ return SignalResponse::breakExecution;
+ };
+ registerSignalHandler(ipmi::prioOpenBmcBase, SIGINT, stopAsioRunLoop);
+ registerSignalHandler(ipmi::prioOpenBmcBase, SIGTERM, stopAsioRunLoop);
+
+ sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
+ // Add bindings for inbound IPMI requests
+ auto server = sdbusplus::asio::object_server(sdbusp);
+ auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server");
+ iface->register_method("execute", ipmi::executionEntry);
+ iface->initialize();
+
+ io->run();
+
+ // destroy all the IPMI handlers so the providers can unload safely
+ ipmi::handlerMap.clear();
+ ipmi::groupHandlerMap.clear();
+ ipmi::oemHandlerMap.clear();
+ ipmi::filterList.clear();
+ // unload the provider libraries
+ providers.clear();
+
+ std::exit(exitCode);
+}
diff --git a/ipmisensor.cpp b/ipmisensor.cpp
new file mode 100644
index 0000000..b66c585
--- /dev/null
+++ b/ipmisensor.cpp
@@ -0,0 +1,347 @@
+#include "sensorhandler.hpp"
+
+#include <malloc.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+extern uint8_t find_type_for_sensor_number(uint8_t);
+
+struct sensorRES_t
+{
+ uint8_t sensor_number;
+ uint8_t operation;
+ uint8_t sensor_reading;
+ uint8_t assert_state7_0;
+ uint8_t assert_state14_8;
+ uint8_t deassert_state7_0;
+ uint8_t deassert_state14_8;
+ uint8_t event_data1;
+ uint8_t event_data2;
+ uint8_t event_data3;
+} __attribute__((packed));
+
+#define ISBITSET(x, y) (((x) >> (y)) & 0x01)
+#define ASSERTINDEX 0
+#define DEASSERTINDEX 1
+
+// Sensor Type, Offset, function handler, Dbus Method, Assert value, Deassert
+// value
+struct lookup_t
+{
+ uint8_t sensor_type;
+ uint8_t offset;
+ int (*func)(const sensorRES_t*, const lookup_t*, const char*);
+ char member[16];
+ char assertion[64];
+ char deassertion[64];
+};
+
+extern int updateDbusInterface(uint8_t, const char*, const char*);
+
+int set_sensor_dbus_state_simple(const sensorRES_t* pRec,
+ const lookup_t* pTable, const char* value)
+{
+ return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, value);
+}
+
+struct event_data_t
+{
+ uint8_t data;
+ char text[64];
+};
+
+event_data_t g_fwprogress02h[] = {
+ {0x00, "Unspecified"},
+ {0x01, "Memory Init"},
+ {0x02, "HD Init"},
+ {0x03, "Secondary Proc Init"},
+ {0x04, "User Authentication"},
+ {0x05, "User init system setup"},
+ {0x06, "USB configuration"},
+ {0x07, "PCI configuration"},
+ {0x08, "Option ROM Init"},
+ {0x09, "Video Init"},
+ {0x0A, "Cache Init"},
+ {0x0B, "SM Bus init"},
+ {0x0C, "Keyboard Init"},
+ {0x0D, "Embedded ctrl init"},
+ {0x0E, "Docking station attachment"},
+ {0x0F, "Enable docking station"},
+ {0x10, "Docking station ejection"},
+ {0x11, "Disabling docking station"},
+ {0x12, "Calling OS Wakeup"},
+ {0x13, "Starting OS"},
+ {0x14, "Baseboard Init"},
+ {0x15, ""},
+ {0x16, "Floppy Init"},
+ {0x17, "Keyboard Test"},
+ {0x18, "Pointing Device Test"},
+ {0x19, "Primary Proc Init"},
+ {0xFF, "Unknown"}};
+
+event_data_t g_fwprogress00h[] = {
+ {0x00, "Unspecified."},
+ {0x01, "No system memory detected"},
+ {0x02, "No usable system memory"},
+ {0x03, "Unrecoverable hard-disk/ATAPI/IDE"},
+ {0x04, "Unrecoverable system-board"},
+ {0x05, "Unrecoverable diskette"},
+ {0x06, "Unrecoverable hard-disk controller"},
+ {0x07, "Unrecoverable PS/2 or USB keyboard"},
+ {0x08, "Removable boot media not found"},
+ {0x09, "Unrecoverable video controller"},
+ {0x0A, "No video device detected"},
+ {0x0B, "Firmware ROM corruption detected"},
+ {0x0C, "CPU voltage mismatch"},
+ {0x0D, "CPU speed matching"},
+ {0xFF, "unknown"},
+};
+
+char* event_data_lookup(event_data_t* p, uint8_t b)
+{
+ while (p->data != 0xFF)
+ {
+ if (p->data == b)
+ {
+ break;
+ }
+ p++;
+ }
+
+ return p->text;
+}
+
+// The fw progress sensor contains some additional information that needs to be
+// processed prior to calling the dbus code.
+int set_sensor_dbus_state_fwprogress(const sensorRES_t* pRec,
+ const lookup_t* pTable, const char*)
+{
+ char valuestring[128];
+ char* p = valuestring;
+
+ switch (pTable->offset)
+ {
+ case 0x00:
+ std::snprintf(
+ p, sizeof(valuestring), "POST Error, %s",
+ event_data_lookup(g_fwprogress00h, pRec->event_data2));
+ break;
+ case 0x01: /* Using g_fwprogress02h for 0x01 because that's what the
+ ipmi spec says to do */
+ std::snprintf(
+ p, sizeof(valuestring), "FW Hang, %s",
+ event_data_lookup(g_fwprogress02h, pRec->event_data2));
+ break;
+ case 0x02:
+ std::snprintf(
+ p, sizeof(valuestring), "FW Progress, %s",
+ event_data_lookup(g_fwprogress02h, pRec->event_data2));
+ break;
+ default:
+ std::snprintf(
+ p, sizeof(valuestring),
+ "Internal warning, fw_progres offset unknown (0x%02x)",
+ pTable->offset);
+ break;
+ }
+
+ return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, p);
+}
+
+// Handling this special OEM sensor by coping what is in byte 4. I also think
+// that is odd considering byte 3 is for sensor reading. This seems like a
+// misuse of the IPMI spec
+int set_sensor_dbus_state_osbootcount(const sensorRES_t* pRec, const lookup_t*,
+ const char*)
+{
+ return set_sensor_dbus_state_y(pRec->sensor_number, "setValue",
+ pRec->assert_state7_0);
+}
+
+int set_sensor_dbus_state_system_event(const sensorRES_t* pRec,
+ const lookup_t* pTable, const char*)
+{
+ char valuestring[128];
+ char* p = valuestring;
+
+ switch (pTable->offset)
+ {
+ case 0x00:
+ std::snprintf(p, sizeof(valuestring), "System Reconfigured");
+ break;
+ case 0x01:
+ std::snprintf(p, sizeof(valuestring), "OEM Boot Event");
+ break;
+ case 0x02:
+ std::snprintf(p, sizeof(valuestring),
+ "Undetermined System Hardware Failure");
+ break;
+ case 0x03:
+ std::snprintf(
+ p, sizeof(valuestring),
+ "System Failure see error log for more details (0x%02x)",
+ pRec->event_data2);
+ break;
+ case 0x04:
+ std::snprintf(
+ p, sizeof(valuestring),
+ "System Failure see PEF error log for more details (0x%02x)",
+ pRec->event_data2);
+ break;
+ default:
+ std::snprintf(
+ p, sizeof(valuestring),
+ "Internal warning, system_event offset unknown (0x%02x)",
+ pTable->offset);
+ break;
+ }
+
+ return set_sensor_dbus_state_s(pRec->sensor_number, pTable->member, p);
+}
+
+// This table lists only senors we care about telling dbus about.
+// Offset definition cab be found in section 42.2 of the IPMI 2.0
+// spec. Add more if/when there are more items of interest.
+lookup_t g_ipmidbuslookup[] = {
+
+ {0xe9, 0x00, set_sensor_dbus_state_simple, "setValue", "Disabled",
+ ""}, // OCC Inactive 0
+ {0xe9, 0x01, set_sensor_dbus_state_simple, "setValue", "Enabled",
+ ""}, // OCC Active 1
+ // Turbo Allowed
+ {0xda, 0x00, set_sensor_dbus_state_simple, "setValue", "True", "False"},
+ // Power Supply Derating
+ {0xb4, 0x00, set_sensor_dbus_state_simple, "setValue", "", ""},
+ // Power Cap
+ {0xC2, 0x00, set_sensor_dbus_state_simple, "setValue", "", ""},
+ {0x07, 0x07, set_sensor_dbus_state_simple, "setPresent", "True", "False"},
+ {0x07, 0x08, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+ {0x0C, 0x06, set_sensor_dbus_state_simple, "setPresent", "True", "False"},
+ {0x0C, 0x04, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+ {0x0F, 0x02, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+ {0x0F, 0x01, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+ {0x0F, 0x00, set_sensor_dbus_state_fwprogress, "setValue", "True", "False"},
+ {0xC7, 0x01, set_sensor_dbus_state_simple, "setFault", "True", "False"},
+ {0xc3, 0x00, set_sensor_dbus_state_osbootcount, "setValue", "", ""},
+ {0x1F, 0x00, set_sensor_dbus_state_simple, "setValue",
+ "Boot completed (00)", ""},
+ {0x1F, 0x01, set_sensor_dbus_state_simple, "setValue",
+ "Boot completed (01)", ""},
+ {0x1F, 0x02, set_sensor_dbus_state_simple, "setValue", "PXE boot completed",
+ ""},
+ {0x1F, 0x03, set_sensor_dbus_state_simple, "setValue",
+ "Diagnostic boot completed", ""},
+ {0x1F, 0x04, set_sensor_dbus_state_simple, "setValue",
+ "CD-ROM boot completed", ""},
+ {0x1F, 0x05, set_sensor_dbus_state_simple, "setValue", "ROM boot completed",
+ ""},
+ {0x1F, 0x06, set_sensor_dbus_state_simple, "setValue",
+ "Boot completed (06)", ""},
+ {0x12, 0x00, set_sensor_dbus_state_system_event, "setValue", "", ""},
+ {0x12, 0x01, set_sensor_dbus_state_system_event, "setValue", "", ""},
+ {0x12, 0x02, set_sensor_dbus_state_system_event, "setValue", "", ""},
+ {0x12, 0x03, set_sensor_dbus_state_system_event, "setValue", "", ""},
+ {0x12, 0x04, set_sensor_dbus_state_system_event, "setValue", "", ""},
+ {0xCA, 0x00, set_sensor_dbus_state_simple, "setValue", "Disabled", ""},
+ {0xCA, 0x01, set_sensor_dbus_state_simple, "setValue", "Enabled", ""},
+ {0xFF, 0xFF, nullptr, "", "", ""}};
+
+void reportSensorEventAssert(const sensorRES_t* pRec, int index)
+{
+ lookup_t* pTable = &g_ipmidbuslookup[index];
+ (*pTable->func)(pRec, pTable, pTable->assertion);
+}
+void reportSensorEventDeassert(const sensorRES_t* pRec, int index)
+{
+ lookup_t* pTable = &g_ipmidbuslookup[index];
+ (*pTable->func)(pRec, pTable, pTable->deassertion);
+}
+
+int findindex(const uint8_t sensor_type, int offset, int* index)
+{
+ int i = 0, rc = 0;
+ lookup_t* pTable = g_ipmidbuslookup;
+
+ do
+ {
+ if (((pTable + i)->sensor_type == sensor_type) &&
+ ((pTable + i)->offset == offset))
+ {
+ rc = 1;
+ *index = i;
+ break;
+ }
+ i++;
+ } while ((pTable + i)->sensor_type != 0xFF);
+
+ return rc;
+}
+
+bool shouldReport(uint8_t sensorType, int offset, int* index)
+{
+ bool rc = false;
+
+ if (findindex(sensorType, offset, index))
+ {
+ rc = true;
+ }
+ if (rc == false)
+ {
+#ifdef __IPMI_DEBUG__
+ lg2::debug("LOOKATME: Sensor should not be reported, "
+ "sensor type: {SENSORTYPE}, offset: {OFFSET}",
+ SENSORTYPE, lg2::hex, sensorType, "OFFSET", lg2::hex,
+ offset);
+#endif
+ }
+
+ return rc;
+}
+
+int updateSensorRecordFromSSRAESC(const void* record)
+{
+ auto pRec = static_cast<const sensorRES_t*>(record);
+ uint8_t stype;
+ int index;
+
+ stype = find_type_for_sensor_number(pRec->sensor_number);
+
+ // 0xC3 types use the assertion7_0 for the value to be set
+ // so skip the reseach and call the correct event reporting
+ // function
+ if (stype == 0xC3)
+ {
+ shouldReport(stype, 0x00, &index);
+ reportSensorEventAssert(pRec, index);
+ }
+ else
+ {
+ // Scroll through each bit position . Determine
+ // if any bit is either asserted or Deasserted.
+ for (int i = 0; i < 8; i++)
+ {
+ if ((ISBITSET(pRec->assert_state7_0, i)) &&
+ (shouldReport(stype, i, &index)))
+ {
+ reportSensorEventAssert(pRec, index);
+ }
+ if ((ISBITSET(pRec->assert_state14_8, i)) &&
+ (shouldReport(stype, i + 8, &index)))
+ {
+ reportSensorEventAssert(pRec, index);
+ }
+ if ((ISBITSET(pRec->deassert_state7_0, i)) &&
+ (shouldReport(stype, i, &index)))
+ {
+ reportSensorEventDeassert(pRec, index);
+ }
+ if ((ISBITSET(pRec->deassert_state14_8, i)) &&
+ (shouldReport(stype, i + 8, &index)))
+ {
+ reportSensorEventDeassert(pRec, index);
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/libipmid/entity_map_json.cpp b/libipmid/entity_map_json.cpp
new file mode 100644
index 0000000..76fbb25
--- /dev/null
+++ b/libipmid/entity_map_json.cpp
@@ -0,0 +1,119 @@
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <exception>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+EntityInfoMapContainer* EntityInfoMapContainer::getContainer()
+{
+ static std::unique_ptr<EntityInfoMapContainer> instance;
+
+ if (!instance)
+ {
+ /* TODO: With multi-threading this would all need to be locked so
+ * the first thread to hit it would set it up.
+ */
+ EntityInfoMap builtEntityMap = buildEntityMapFromFile();
+ instance = std::unique_ptr<EntityInfoMapContainer>(
+ new EntityInfoMapContainer(builtEntityMap));
+ }
+
+ return instance.get();
+}
+
+const EntityInfoMap& EntityInfoMapContainer::getIpmiEntityRecords()
+{
+ return entityRecords;
+}
+
+EntityInfoMap buildEntityMapFromFile()
+{
+ const char* entityMapJsonFilename =
+ "/usr/share/ipmi-providers/entity-map.json";
+ EntityInfoMap builtMap;
+
+ std::ifstream mapFile(entityMapJsonFilename);
+ if (!mapFile.is_open())
+ {
+ return builtMap;
+ }
+
+ auto data = nlohmann::json::parse(mapFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ return builtMap;
+ }
+
+ return buildJsonEntityMap(data);
+}
+
+EntityInfoMap buildJsonEntityMap(const nlohmann::json& data)
+{
+ EntityInfoMap builtMap;
+
+ if (data.type() != nlohmann::json::value_t::array)
+ {
+ return builtMap;
+ }
+
+ try
+ {
+ for (const auto& entry : data)
+ {
+ /* It's an array entry with the following fields: id,
+ * containerEntityId, containerEntityInstance, isList, isLinked,
+ * entities[4]
+ */
+ EntityInfo obj;
+ Id recordId = entry.at("id").get<Id>();
+ obj.containerEntityId =
+ entry.at("containerEntityId").get<uint8_t>();
+ obj.containerEntityInstance =
+ entry.at("containerEntityInstance").get<uint8_t>();
+ obj.isList = entry.at("isList").get<bool>();
+ obj.isLinked = entry.at("isLinked").get<bool>();
+
+ auto jsonEntities = entry.at("entities");
+
+ if (jsonEntities.type() != nlohmann::json::value_t::array)
+ {
+ throw std::runtime_error(
+ "Invalid type for entities entry, must be array");
+ }
+ if (jsonEntities.size() != obj.containedEntities.size())
+ {
+ throw std::runtime_error(
+ "Entities must be in pairs of " +
+ std::to_string(obj.containedEntities.size()));
+ }
+
+ for (std::size_t i = 0; i < obj.containedEntities.size(); i++)
+ {
+ obj.containedEntities[i] = std::make_pair(
+ jsonEntities[i].at("id").get<uint8_t>(),
+ jsonEntities[i].at("instance").get<uint8_t>());
+ }
+
+ builtMap.insert({recordId, obj});
+ }
+ }
+ catch (const std::exception& e)
+ {
+ /* If any entry is invalid, the entire file cannot be trusted. */
+ builtMap.clear();
+ }
+
+ return builtMap;
+}
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/libipmid/meson.build b/libipmid/meson.build
new file mode 100644
index 0000000..f9953b0
--- /dev/null
+++ b/libipmid/meson.build
@@ -0,0 +1,44 @@
+ipmid_pre = [
+ boost,
+ libsystemd_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+]
+
+entity_map_json_lib = static_library(
+ 'entity_map_json',
+ 'entity_map_json.cpp',
+ include_directories: root_inc,
+ dependencies: [nlohmann_json_dep, sdbusplus_dep],
+ implicit_include_directories: false,
+)
+
+entity_map_json_dep = declare_dependency(link_whole: entity_map_json_lib)
+
+libipmid = library(
+ 'ipmid',
+ 'sdbus-asio.cpp',
+ 'signals.cpp',
+ 'systemintf-sdbus.cpp',
+ 'utils.cpp',
+ dependencies: [ipmid_pre, entity_map_json_dep],
+ version: meson.project_version(),
+ include_directories: root_inc,
+ install: true,
+ install_dir: get_option('libdir'),
+ override_options: ['b_lundef=false'],
+)
+
+ipmid_dep = declare_dependency(
+ dependencies: ipmid_pre,
+ include_directories: root_inc,
+ link_with: libipmid,
+)
+
+import('pkgconfig').generate(
+ libipmid,
+ name: 'libipmid',
+ version: meson.project_version(),
+ description: 'ipmid',
+)
diff --git a/libipmid/sdbus-asio.cpp b/libipmid/sdbus-asio.cpp
new file mode 100644
index 0000000..0f4cdaa
--- /dev/null
+++ b/libipmid/sdbus-asio.cpp
@@ -0,0 +1,32 @@
+#include <boost/asio/io_context.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+namespace
+{
+
+std::shared_ptr<boost::asio::io_context> ioCtx;
+std::shared_ptr<sdbusplus::asio::connection> sdbusp;
+
+} // namespace
+
+void setIoContext(std::shared_ptr<boost::asio::io_context>& newIo)
+{
+ ioCtx = newIo;
+}
+
+std::shared_ptr<boost::asio::io_context> getIoContext()
+{
+ return ioCtx;
+}
+
+void setSdBus(std::shared_ptr<sdbusplus::asio::connection>& newBus)
+{
+ sdbusp = newBus;
+}
+
+std::shared_ptr<sdbusplus::asio::connection> getSdBus()
+{
+ return sdbusp;
+}
diff --git a/libipmid/signals.cpp b/libipmid/signals.cpp
new file mode 100644
index 0000000..5eca6ad
--- /dev/null
+++ b/libipmid/signals.cpp
@@ -0,0 +1,106 @@
+#include <boost/asio/signal_set.hpp>
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <forward_list>
+#include <memory>
+#include <vector>
+
+namespace
+{
+
+class SignalHandler
+{
+ public:
+ SignalHandler(std::shared_ptr<boost::asio::io_context>& io, int sigNum) :
+ signal(std::make_unique<boost::asio::signal_set>(*io, sigNum))
+ {
+ asyncWait();
+ }
+
+ ~SignalHandler()
+ {
+ // unregister with asio to unmask the signal
+ signal->cancel();
+ signal->clear();
+ }
+
+ void registerHandler(int prio,
+ const std::function<SignalResponse(int)>& handler)
+ {
+ // check for initial placement
+ if (handlers.empty() || std::get<0>(handlers.front()) < prio)
+ {
+ handlers.emplace_front(std::make_tuple(prio, handler));
+ return;
+ }
+ // walk the list and put it in the right place
+ auto j = handlers.begin();
+ for (auto i = j; i != handlers.end() && std::get<0>(*i) > prio; i++)
+ {
+ j = i;
+ }
+ handlers.emplace_after(j, std::make_tuple(prio, handler));
+ }
+
+ void handleSignal(const boost::system::error_code& ec, int sigNum)
+ {
+ if (ec)
+ {
+ lg2::error("Error in common signal handler, "
+ "signal: {SIGNAL}, error: {ERROR}",
+ "SIGNAL", sigNum, "ERROR", ec.message());
+ return;
+ }
+ for (auto h = handlers.begin(); h != handlers.end(); h++)
+ {
+ std::function<SignalResponse(int)>& handler = std::get<1>(*h);
+ if (handler(sigNum) == SignalResponse::breakExecution)
+ {
+ break;
+ }
+ }
+ // start the wait for the next signal
+ asyncWait();
+ }
+
+ protected:
+ void asyncWait()
+ {
+ signal->async_wait([this](const boost::system::error_code& ec,
+ int sigNum) { handleSignal(ec, sigNum); });
+ }
+
+ std::forward_list<std::tuple<int, std::function<SignalResponse(int)>>>
+ handlers;
+ std::unique_ptr<boost::asio::signal_set> signal;
+};
+
+// SIGRTMAX is defined as a non-constexpr function call and thus cannot be used
+// as an array size. Get around this by making a vector and resizing it the
+// first time it is needed
+std::vector<std::unique_ptr<SignalHandler>> signals;
+
+} // namespace
+
+void registerSignalHandler(int priority, int signalNumber,
+ const std::function<SignalResponse(int)>& handler)
+{
+ if (signalNumber >= SIGRTMAX)
+ {
+ return;
+ }
+
+ if (signals.empty())
+ {
+ signals.resize(SIGRTMAX);
+ }
+
+ if (!signals[signalNumber])
+ {
+ std::shared_ptr<boost::asio::io_context> io = getIoContext();
+ signals[signalNumber] =
+ std::make_unique<SignalHandler>(io, signalNumber);
+ }
+ signals[signalNumber]->registerHandler(priority, handler);
+}
diff --git a/libipmid/systemintf-sdbus.cpp b/libipmid/systemintf-sdbus.cpp
new file mode 100644
index 0000000..a9a4e18
--- /dev/null
+++ b/libipmid/systemintf-sdbus.cpp
@@ -0,0 +1,33 @@
+#include <ipmid/api.hpp>
+#include <sdbusplus/asio/connection.hpp>
+
+#include <memory>
+
+namespace
+{
+
+std::unique_ptr<sdbusplus::asio::connection> sdbusp;
+
+} // namespace
+
+/**
+ * @brief ipmid_get_sdbus_plus_handler is used by some ipmi providers
+ *
+ * @return: a reference to a unique pointer of the systemd connection
+ * managed by the systemintfcmds code
+ */
+std::unique_ptr<sdbusplus::asio::connection>& ipmid_get_sdbus_plus_handler()
+{
+ if (!sdbusp)
+ {
+ // Create a new sdbus connection so it can have a well-known name
+ sd_bus* bus = nullptr;
+ sd_bus_open_system(&bus);
+ if (bus)
+ {
+ sdbusp = std::make_unique<sdbusplus::asio::connection>(
+ *getIoContext(), bus);
+ }
+ }
+ return sdbusp;
+}
diff --git a/libipmid/utils.cpp b/libipmid/utils.cpp
new file mode 100644
index 0000000..f8e8083
--- /dev/null
+++ b/libipmid/utils.cpp
@@ -0,0 +1,609 @@
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/i2c-dev.h>
+#include <linux/i2c.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <chrono>
+
+namespace ipmi
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace network
+{
+
+/** @brief checks if the given ip is Link Local Ip or not.
+ * @param[in] ipaddress - IPAddress.
+ */
+bool isLinkLocalIP(const std::string& ipaddress);
+
+} // namespace network
+
+// TODO There may be cases where an interface is implemented by multiple
+// objects,to handle such cases we are interested on that object
+// which are on interested busname.
+// Currently mapper doesn't give the readable busname(gives busid) so we can't
+// use busname to find the object,will do later once the support is there.
+
+DbusObjectInfo getDbusObject(
+ sdbusplus::bus_t& bus, const std::string& interface,
+ const std::string& serviceRoot, const std::string& match)
+{
+ std::vector<DbusInterface> interfaces;
+ interfaces.emplace_back(interface);
+
+ ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
+ if (objectTree.empty())
+ {
+ lg2::error("No Object has implemented the interface: {INTERFACE}",
+ "INTERFACE", interface);
+ elog<InternalFailure>();
+ }
+
+ DbusObjectInfo objectInfo;
+
+ // if match is empty then return the first object
+ if (match == "")
+ {
+ objectInfo = std::make_pair(
+ objectTree.begin()->first,
+ std::move(objectTree.begin()->second.begin()->first));
+ return objectInfo;
+ }
+
+ // else search the match string in the object path
+ auto found = std::find_if(
+ objectTree.begin(), objectTree.end(), [&match](const auto& object) {
+ return (object.first.find(match) != std::string::npos);
+ });
+
+ if (found == objectTree.end())
+ {
+ lg2::error("Failed to find object which matches: {MATCH}", "MATCH",
+ match);
+ elog<InternalFailure>();
+ // elog<> throws an exception.
+ }
+
+ return make_pair(found->first, std::move(found->second.begin()->first));
+}
+
+Value getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& property,
+ std::chrono::microseconds timeout)
+{
+ Value value;
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ PROP_INTF, METHOD_GET);
+
+ method.append(interface, property);
+
+ auto reply = bus.call(method, timeout.count());
+ reply.read(value);
+
+ return value;
+}
+
+PropertyMap getAllDbusProperties(
+ sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ std::chrono::microseconds timeout)
+{
+ PropertyMap properties;
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ PROP_INTF, METHOD_GET_ALL);
+
+ method.append(interface);
+
+ auto reply = bus.call(method, timeout.count());
+ reply.read(properties);
+
+ return properties;
+}
+
+ObjectValueTree getManagedObjects(sdbusplus::bus_t& bus,
+ const std::string& service,
+ const std::string& objPath)
+{
+ ipmi::ObjectValueTree interfaces;
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ "org.freedesktop.DBus.ObjectManager",
+ "GetManagedObjects");
+ auto reply = bus.call(method);
+ reply.read(interfaces);
+
+ return interfaces;
+}
+
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& property, const Value& value,
+ std::chrono::microseconds timeout)
+{
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ PROP_INTF, METHOD_SET);
+
+ method.append(interface, property, value);
+
+ if (!bus.call(method, timeout.count()))
+ {
+ lg2::error("Failed to set {PROPERTY}, path: {PATH}, "
+ "interface: {INTERFACE}",
+ "PROPERTY", property, "PATH", objPath, "INTERFACE",
+ interface);
+ elog<InternalFailure>();
+ }
+}
+
+ServiceCache::ServiceCache(const std::string& intf, const std::string& path) :
+ intf(intf), path(path), cachedService(std::nullopt),
+ cachedBusName(std::nullopt)
+{}
+
+ServiceCache::ServiceCache(std::string&& intf, std::string&& path) :
+ intf(std::move(intf)), path(std::move(path)), cachedService(std::nullopt),
+ cachedBusName(std::nullopt)
+{}
+
+const std::string& ServiceCache::getService(sdbusplus::bus_t& bus)
+{
+ if (!isValid(bus))
+ {
+ cachedBusName = bus.get_unique_name();
+ cachedService = ::ipmi::getService(bus, intf, path);
+ }
+ return cachedService.value();
+}
+
+void ServiceCache::invalidate()
+{
+ cachedBusName = std::nullopt;
+ cachedService = std::nullopt;
+}
+
+sdbusplus::message_t ServiceCache::newMethodCall(
+ sdbusplus::bus_t& bus, const char* intf, const char* method)
+{
+ return bus.new_method_call(getService(bus).c_str(), path.c_str(), intf,
+ method);
+}
+
+bool ServiceCache::isValid(sdbusplus::bus_t& bus) const
+{
+ return cachedService && cachedBusName == bus.get_unique_name();
+}
+
+std::string getService(sdbusplus::bus_t& bus, const std::string& intf,
+ const std::string& path)
+{
+ auto mapperCall =
+ bus.new_method_call("xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetObject");
+
+ mapperCall.append(path);
+ mapperCall.append(std::vector<std::string>({intf}));
+
+ auto mapperResponseMsg = bus.call(mapperCall);
+
+ std::map<std::string, std::vector<std::string>> mapperResponse;
+ mapperResponseMsg.read(mapperResponse);
+
+ if (mapperResponse.begin() == mapperResponse.end())
+ {
+ throw std::runtime_error("ERROR in reading the mapper response");
+ }
+
+ return mapperResponse.begin()->first;
+}
+
+ObjectTree getSubTree(sdbusplus::bus_t& bus, const InterfaceList& interfaces,
+ const std::string& subtreePath, int32_t depth)
+{
+ auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
+ MAPPER_INTF, "GetSubTree");
+
+ mapperCall.append(subtreePath, depth, interfaces);
+
+ auto mapperReply = bus.call(mapperCall);
+ ObjectTree objectTree;
+ mapperReply.read(objectTree);
+
+ return objectTree;
+}
+
+ipmi::ObjectTree getAllDbusObjects(
+ sdbusplus::bus_t& bus, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match)
+{
+ std::vector<std::string> interfaces;
+ interfaces.emplace_back(interface);
+
+ ObjectTree objectTree = getSubTree(bus, interfaces, serviceRoot);
+ for (auto it = objectTree.begin(); it != objectTree.end();)
+ {
+ if (it->first.find(match) == std::string::npos)
+ {
+ it = objectTree.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ return objectTree;
+}
+
+void deleteAllDbusObjects(sdbusplus::bus_t& bus, const std::string& serviceRoot,
+ const std::string& interface,
+ const std::string& match)
+{
+ try
+ {
+ auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match);
+
+ for (auto& object : objectTree)
+ {
+ method_no_args::callDbusMethod(bus, object.second.begin()->first,
+ object.first, DELETE_INTERFACE,
+ "Delete");
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::info("sdbusplus exception - Unable to delete the objects, "
+ "service: {SERVICE}, interface: {INTERFACE}, error: {ERROR}",
+ "SERVICE", serviceRoot, "INTERFACE", interface, "ERROR", e);
+ }
+}
+
+static inline std::string convertToString(const InterfaceList& interfaces)
+{
+ std::string intfStr;
+ for (const auto& intf : interfaces)
+ {
+ intfStr += "," + intf;
+ }
+ return intfStr;
+}
+
+ObjectTree getAllAncestors(sdbusplus::bus_t& bus, const std::string& path,
+ InterfaceList&& interfaces)
+{
+ auto mapperCall = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
+ MAPPER_INTF, "GetAncestors");
+ mapperCall.append(path, interfaces);
+
+ auto mapperReply = bus.call(mapperCall);
+ ObjectTree objectTree;
+ mapperReply.read(objectTree);
+
+ if (objectTree.empty())
+ {
+ lg2::error("No Object has implemented the interface: {INTERFACE}, "
+ "path: {PATH}",
+ "INTERFACE", convertToString(interfaces), "PATH", path);
+ elog<InternalFailure>();
+ }
+
+ return objectTree;
+}
+
+namespace method_no_args
+{
+
+void callDbusMethod(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& method)
+
+{
+ auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(),
+ interface.c_str(), method.c_str());
+ auto reply = bus.call(busMethod);
+}
+
+} // namespace method_no_args
+
+/********* Begin co-routine yielding alternatives ***************/
+
+boost::system::error_code getService(Context::ptr ctx, const std::string& intf,
+ const std::string& path,
+ std::string& service)
+{
+ boost::system::error_code ec;
+ std::map<std::string, std::vector<std::string>> mapperResponse =
+ ctx->bus->yield_method_call<decltype(mapperResponse)>(
+ ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
+ "/xyz/openbmc_project/object_mapper",
+ "xyz.openbmc_project.ObjectMapper", "GetObject", path,
+ std::vector<std::string>({intf}));
+
+ if (!ec)
+ {
+ service = std::move(mapperResponse.begin()->first);
+ }
+ return ec;
+}
+
+boost::system::error_code getSubTree(
+ Context::ptr ctx, const InterfaceList& interfaces,
+ const std::string& subtreePath, int32_t depth, ObjectTree& objectTree)
+{
+ boost::system::error_code ec;
+ objectTree = ctx->bus->yield_method_call<ObjectTree>(
+ ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, "GetSubTree",
+ subtreePath, depth, interfaces);
+
+ return ec;
+}
+
+boost::system::error_code getDbusObject(
+ Context::ptr ctx, const std::string& interface,
+ const std::string& subtreePath, const std::string& match,
+ DbusObjectInfo& dbusObject)
+{
+ std::vector<DbusInterface> interfaces;
+ interfaces.emplace_back(interface);
+
+ auto depth = 0;
+ ObjectTree objectTree;
+ boost::system::error_code ec =
+ getSubTree(ctx, interfaces, subtreePath, depth, objectTree);
+
+ if (ec)
+ {
+ return ec;
+ }
+
+ if (objectTree.empty())
+ {
+ lg2::error("No Object has implemented the interface: {INTERFACE}, "
+ "NetFn: {NETFN}, Cmd: {CMD}",
+ "INTERFACE", interface, "NETFN", lg2::hex, ctx->netFn, "CMD",
+ lg2::hex, ctx->cmd);
+ return boost::system::errc::make_error_code(
+ boost::system::errc::no_such_process);
+ }
+
+ // if match is empty then return the first object
+ if (match == "")
+ {
+ dbusObject = std::make_pair(
+ std::move(objectTree.begin()->first),
+ std::move(objectTree.begin()->second.begin()->first));
+ return ec;
+ }
+
+ // else search the match string in the object path
+ auto found = std::find_if(
+ objectTree.begin(), objectTree.end(), [&match](const auto& object) {
+ return (object.first.find(match) != std::string::npos);
+ });
+
+ if (found == objectTree.end())
+ {
+ lg2::error("Failed to find object which matches: {MATCH}, "
+ "NetFn: {NETFN}, Cmd: {CMD}",
+ "MATCH", match, "NETFN", lg2::hex, ctx->netFn, "CMD",
+ lg2::hex, ctx->cmd);
+ // set ec
+ return boost::system::errc::make_error_code(
+ boost::system::errc::no_such_file_or_directory);
+ }
+
+ dbusObject = std::make_pair(std::move(found->first),
+ std::move(found->second.begin()->first));
+ return ec;
+}
+
+boost::system::error_code getAllDbusProperties(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, PropertyMap& properties)
+{
+ boost::system::error_code ec;
+ properties = ctx->bus->yield_method_call<PropertyMap>(
+ ctx->yield, ec, service.c_str(), objPath.c_str(), PROP_INTF,
+ METHOD_GET_ALL, interface);
+ return ec;
+}
+
+boost::system::error_code setDbusProperty(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& property,
+ const Value& value)
+{
+ boost::system::error_code ec;
+ ctx->bus->yield_method_call(ctx->yield, ec, service.c_str(),
+ objPath.c_str(), PROP_INTF, METHOD_SET,
+ interface, property, value);
+ return ec;
+}
+
+boost::system::error_code getAllDbusObjects(
+ Context::ptr ctx, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match,
+ ObjectTree& objectTree)
+{
+ std::vector<std::string> interfaces;
+ interfaces.emplace_back(interface);
+
+ auto depth = 0;
+ boost::system::error_code ec =
+ getSubTree(ctx, interfaces, serviceRoot, depth, objectTree);
+ if (ec)
+ {
+ return ec;
+ }
+
+ for (auto it = objectTree.begin(); it != objectTree.end();)
+ {
+ if (it->first.find(match) == std::string::npos)
+ {
+ it = objectTree.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ return ec;
+}
+
+boost::system::error_code deleteAllDbusObjects(
+ Context::ptr ctx, const std::string& serviceRoot,
+ const std::string& interface, const std::string& match)
+{
+ ObjectTree objectTree;
+ boost::system::error_code ec =
+ getAllDbusObjects(ctx, serviceRoot, interface, match, objectTree);
+ if (ec)
+ {
+ return ec;
+ }
+
+ for (auto& object : objectTree)
+ {
+ ctx->bus->yield_method_call(ctx->yield, ec,
+ object.second.begin()->first, object.first,
+ DELETE_INTERFACE, "Delete");
+ if (ec)
+ {
+ lg2::error("Failed to delete all objects, service: {SERVICE}, "
+ "interface: {INTERFACE}, NetFn: {NETFN}, "
+ "Cmd: {CMD}, Error: {ERROR}",
+ "SERVICE", serviceRoot, "INTERFACE", interface, "NETFN",
+ lg2::hex, ctx->netFn, "CMD", lg2::hex, ctx->cmd, "ERROR",
+ ec.message());
+ break;
+ }
+ }
+ return ec;
+}
+
+boost::system::error_code getManagedObjects(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ ObjectValueTree& objects)
+{
+ boost::system::error_code ec;
+ objects = ctx->bus->yield_method_call<ipmi::ObjectValueTree>(
+ ctx->yield, ec, service.c_str(), objPath.c_str(),
+ "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+ return ec;
+}
+
+boost::system::error_code getAllAncestors(
+ Context::ptr ctx, const std::string& path, const InterfaceList& interfaces,
+ ObjectTree& objectTree)
+{
+ std::string interfaceList = convertToString(interfaces);
+
+ boost::system::error_code ec;
+ objectTree = ctx->bus->yield_method_call<ObjectTree>(
+ ctx->yield, ec, MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF,
+ "GetAncestors", path, interfaceList);
+
+ if (ec)
+ {
+ return ec;
+ }
+
+ if (objectTree.empty())
+ {
+ lg2::error("No Object has implemented the interface: {INTERFACE}, "
+ "path: {PATH}",
+ "INTERFACE", interfaceList, "PATH", path);
+ elog<InternalFailure>();
+ }
+
+ return ec;
+}
+
+boost::system::error_code callDbusMethod(
+ Context::ptr ctx, const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& method)
+{
+ boost::system::error_code ec;
+ ctx->bus->yield_method_call(ctx->yield, ec, service, objPath, interface,
+ method);
+ return ec;
+}
+
+/********* End co-routine yielding alternatives ***************/
+
+ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t targetAddr,
+ std::vector<uint8_t> writeData,
+ std::vector<uint8_t>& readBuf)
+{
+ // Open the i2c device, for low-level combined data write/read
+ int i2cDev = ::open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+ if (i2cDev < 0)
+ {
+ lg2::error("Failed to open i2c bus: {BUS}", "BUS", i2cBus);
+ return ipmi::ccInvalidFieldRequest;
+ }
+
+ const size_t writeCount = writeData.size();
+ const size_t readCount = readBuf.size();
+ int msgCount = 0;
+ i2c_msg i2cmsg[2] = {};
+ if (writeCount)
+ {
+ // Data will be writtern to the target address
+ i2cmsg[msgCount].addr = targetAddr;
+ i2cmsg[msgCount].flags = 0x00;
+ i2cmsg[msgCount].len = writeCount;
+ i2cmsg[msgCount].buf = writeData.data();
+ msgCount++;
+ }
+ if (readCount)
+ {
+ // Data will be read into the buffer from the target address
+ i2cmsg[msgCount].addr = targetAddr;
+ i2cmsg[msgCount].flags = I2C_M_RD;
+ i2cmsg[msgCount].len = readCount;
+ i2cmsg[msgCount].buf = readBuf.data();
+ msgCount++;
+ }
+
+ i2c_rdwr_ioctl_data msgReadWrite = {};
+ msgReadWrite.msgs = i2cmsg;
+ msgReadWrite.nmsgs = msgCount;
+
+ // Perform the combined write/read
+ int ret = ::ioctl(i2cDev, I2C_RDWR, &msgReadWrite);
+ ::close(i2cDev);
+
+ if (ret < 0)
+ {
+ lg2::error("I2C WR Failed! {RET}", "RET", ret);
+ return ipmi::ccUnspecifiedError;
+ }
+ if (readCount)
+ {
+ readBuf.resize(msgReadWrite.msgs[msgCount - 1].len);
+ }
+
+ return ipmi::ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/meson.build b/meson.build
index 0e449d4..a492697 100644
--- a/meson.build
+++ b/meson.build
@@ -1,40 +1,78 @@
project(
- 'phosphor-net-ipmid',
+ 'phosphor-host-ipmid',
'cpp',
- version: '1.0.0',
+ version: '0.1',
meson_version: '>=1.1.1',
default_options: [
- 'warning_level=3',
'werror=true',
+ 'warning_level=3',
'cpp_std=c++23',
- 'buildtype=debugoptimized',
'b_lto=true',
],
)
+# Setting up config data
conf_data = configuration_data()
-conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed())
-conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed())
-configure_file(output: 'config.h', configuration: conf_data)
+# The name of the callout's forward association
+conf_data.set_quoted('CALLOUT_FWD_ASSOCIATION', 'callout')
+conf_data.set_quoted('BOARD_SENSOR', get_option('board-sensor'))
+conf_data.set_quoted('SYSTEM_SENSOR', get_option('system-sensor'))
+conf_data.set(
+ 'IPMI_SMS_ATN_ACK_TIMEOUT_SECS',
+ get_option('ipmi-sms-atn-ack-timeout-secs'),
+)
-sdbusplus_dep = dependency('sdbusplus')
-phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
-phosphor_logging_dep = dependency('phosphor-logging')
-libsystemd_dep = dependency('libsystemd')
-libcrypto_dep = dependency('libcrypto')
-ipmid_dep = dependency('libipmid')
-userlayer_dep = dependency('libuserlayer')
-channellayer_dep = dependency('libchannellayer')
+# Soft Power off related.
+if get_option('softoff').allowed()
+ conf_data.set_quoted('SOFTOFF_BUSNAME', get_option('softoff-busname'))
+ conf_data.set_quoted('SOFTOFF_OBJPATH', get_option('softoff-objpath'))
+ conf_data.set(
+ 'IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS',
+ get_option('ipmi-host-shutdown-complete-timeout-secs'),
+ )
+ conf_data.set_quoted(
+ 'HOST_INBAND_REQUEST_DIR',
+ get_option('host-inband-request-dir'),
+ )
+ conf_data.set_quoted(
+ 'HOST_INBAND_REQUEST_FILE',
+ get_option('host-inband-request-file'),
+ )
+endif
+
+conf_data.set_quoted('CONTROL_HOST_BUSNAME', get_option('control-host-busname'))
+conf_data.set_quoted('CONTROL_HOST_OBJ_MGR', get_option('control-host-obj-mgr'))
+conf_data.set_quoted('HOST_NAME', get_option('host-name'))
+conf_data.set_quoted('POWER_READING_SENSOR', get_option('power-reading-sensor'))
+conf_data.set_quoted('HOST_IPMI_LIB_PATH', get_option('host-ipmi-lib-path'))
+conf_data.set_quoted('FW_VER_REGEX', get_option('fw-ver-regex'))
+
+if get_option('shortname-remove-suffix').allowed()
+ conf_data.set_quoted('SHORTNAME_REMOVE_SUFFIX', '1')
+endif
+if get_option('shortname-replace-words').allowed()
+ conf_data.set_quoted('SHORTNAME_REPLACE_WORDS', '1')
+endif
+if get_option('open-power').allowed()
+ conf_data.set_quoted('OPEN_POWER_SUPPORT', '1')
+endif
+
+matches_map = get_option('matches-map')
+conf_data.set('MAJOR_MATCH_INDEX', matches_map[0])
+conf_data.set('MINOR_MATCH_INDEX', matches_map[1])
+conf_data.set('AUX_0_MATCH_INDEX', matches_map[2])
+conf_data.set('AUX_1_MATCH_INDEX', matches_map[3])
+conf_data.set('AUX_2_MATCH_INDEX', matches_map[4])
+conf_data.set('AUX_3_MATCH_INDEX', matches_map[5])
+
+conf_h = configure_file(output: 'config.h', configuration: conf_data)
+
+root = meson.current_source_dir()
+root_inc = include_directories('.', 'include')
# Project Arguments
cpp = meson.get_compiler('cpp')
-if cpp.has_header('CLI/CLI.hpp')
- cli11_dep = declare_dependency()
-else
- cli11_dep = dependency('CLI11')
-endif
-
add_project_arguments(
cpp.get_supported_arguments(
[
@@ -48,73 +86,283 @@
language: 'cpp',
)
-deps = [
- cli11_dep,
- ipmid_dep,
- userlayer_dep,
- channellayer_dep,
- libcrypto_dep,
- libsystemd_dep,
- phosphor_dbus_interfaces_dep,
- phosphor_logging_dep,
+if get_option('get-dbus-active-software').allowed()
+ add_project_arguments(
+ cpp.get_supported_arguments(['-DGET_DBUS_ACTIVE_SOFTWARE']),
+ language: 'cpp',
+ )
+endif
+
+feature_map = {
+ 'boot-flag-safe-mode-support': '-DENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT',
+ 'i2c-whitelist-check' : '-DENABLE_I2C_WHITELIST_CHECK',
+ 'update-functional-on-fail' : '-DUPDATE_FUNCTIONAL_ON_FAIL',
+ 'dynamic-sensors' : '-DFEATURE_DYNAMIC_SENSORS',
+ 'dynamic-sensors-write' : '-DFEATURE_DYNAMIC_SENSORS_WRITE',
+ 'entity-manager-decorators' : '-DUSING_ENTITY_MANAGER_DECORATORS',
+ 'hybrid-sensors' : '-DFEATURE_HYBRID_SENSORS',
+ 'sensors-cache' : '-DFEATURE_SENSORS_CACHE',
+ 'dynamic-storages-only' : '-DFEATURE_DYNAMIC_STORAGES_ONLY',
+ 'arm-sbmr' : '-DARM_SBMR_SUPPORT',
+}
+
+foreach option_key, option_value : feature_map
+ if (get_option(option_key).allowed())
+ summary(option_key, option_value, section: 'Enabled Features')
+ add_project_arguments(option_value, language: 'cpp')
+ endif
+endforeach
+
+add_project_arguments(
+ cpp.get_supported_arguments(
+ [
+ '-Wno-psabi',
+ '-Wno-missing-field-initializers',
+ '-Wno-pedantic',
+ '-Wno-non-virtual-dtor',
+ ],
+ ),
+ language: 'cpp',
+)
+
+# Dependencies
+
+boost = dependency('boost', modules: ['context', 'coroutine'], required: false)
+
+if not boost.found()
+ cmake = import('cmake')
+ opt = cmake.subproject_options()
+ opt.add_cmake_defines(
+ {
+ 'BOOST_INCLUDE_LIBRARIES': 'asio;bimap;callable_traits;context;coroutine;interprocess;multiprecision;process',
+ 'CMAKE_POSITION_INDEPENDENT_CODE': true,
+ },
+ )
+ boost_cmake = cmake.subproject('boost', required: true, options: opt)
+ boost_asio = boost_cmake.dependency('boost_asio').as_system()
+ boost_bimap = boost_cmake.dependency('boost_bimap').as_system()
+ boost_callable_traits = boost_cmake.dependency('boost_callable_traits').as_system()
+ boost_context = boost_cmake.dependency('boost_context').as_system()
+ boost_coroutine = boost_cmake.dependency('boost_coroutine').as_system()
+ boost_interprocess = boost_cmake.dependency('boost_interprocess').as_system()
+ boost_multiprecision = boost_cmake.dependency('boost_multiprecision').as_system()
+ boost_process = boost_cmake.dependency('boost_process').as_system()
+ boost = [
+ boost_asio,
+ boost_bimap,
+ boost_callable_traits,
+ boost_context,
+ boost_coroutine,
+ boost_interprocess,
+ boost_multiprecision,
+ boost_process,
+ ]
+endif
+
+phosphor_logging_dep = dependency('phosphor-logging')
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+sdeventplus_dep = dependency('sdeventplus')
+libsystemd_dep = dependency('libsystemd')
+crypto = dependency('libcrypto', version: '>=1.0.2g')
+pam = cpp.find_library('pam', required: true)
+sdbusplus_dep = dependency('sdbusplus')
+stdplus_dep = dependency('stdplus')
+
+nlohmann_json_dep = dependency('nlohmann_json', include_type: 'system')
+
+generated_src = []
+
+# Subfolders
+subdir('libipmid')
+subdir('include')
+subdir('user_channel')
+subdir('scripts')
+
+if get_option('softoff').allowed()
+ subdir('xyz/openbmc_project/Ipmi/Internal/SoftPowerOff')
+ subdir('softoff')
+endif
+
+# whitelist
+if get_option('ipmi-whitelist').allowed()
+ generate_whitelist_script = files('generate_whitelist_create.sh')
+
+ whitelist_conf = get_option('whitelist-conf')
+ ipmiwhitelist = run_command( \
+ 'bash', \
+ generate_whitelist_script, \
+ whitelist_conf,
+ )
+
+ whitelist_pre = declare_dependency(
+ include_directories: root_inc,
+ dependencies: [
+ crypto,
+ ipmid_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ ],
+ )
+
+ whitelist_lib = library(
+ 'whitelist',
+ 'whitelist-filter.cpp',
+ 'ipmiwhitelist.cpp',
+ implicit_include_directories: false,
+ dependencies: whitelist_pre,
+ version: meson.project_version(),
+ override_options: ['b_lundef=false'],
+ install: true,
+ install_dir: get_option('libdir') / 'ipmid-providers',
+ )
+endif
+
+# libsysintfcmds
+sysintfcmds_pre = declare_dependency(
+ include_directories: root_inc,
+ dependencies: [
+ channellayer_dep,
+ crypto,
+ nlohmann_json_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ ipmid_dep,
+ ],
+)
+
+sysintfcmds_lib = library(
+ 'sysintfcmds',
+ 'systemintfcmds.cpp',
+ 'host-interface.cpp',
+ implicit_include_directories: false,
+ dependencies: sysintfcmds_pre,
+ version: meson.project_version(),
+ override_options: ['b_lundef=false'],
+ install: true,
+ install_dir: get_option('libdir') / 'ipmid-providers',
+)
+
+# ipmid
+ipmid_pre = [
sdbusplus_dep,
+ stdplus_dep,
+ phosphor_logging_dep,
+ phosphor_dbus_interfaces_dep,
+ boost,
+ crypto,
+ ipmid_dep,
+ channellayer_dep,
]
-sources = [
- 'auth_algo.cpp',
- 'sessions_manager.cpp',
- 'message_parsers.cpp',
- 'message_handler.cpp',
- 'command_table.cpp',
- 'command/channel_auth.cpp',
- 'command/guid.cpp',
- 'command/open_session.cpp',
- 'command/rakp12.cpp',
- 'command/rakp34.cpp',
- 'command/session_cmds.cpp',
- 'comm_module.cpp',
- 'main.cpp',
- 'integrity_algo.cpp',
- 'crypt_algo.cpp',
- 'sd_event_loop.cpp',
- 'sol/sol_manager.cpp',
- 'sol/sol_context.cpp',
- 'command/sol_cmds.cpp',
- 'command/payload_cmds.cpp',
- 'sol_module.cpp',
+transportoem_src = []
+if get_option('transport-oem').allowed()
+ transportoem_src = ['transporthandler_oem.cpp']
+endif
+
+storage_cmds_src = []
+if get_option('dynamic-sensors').disabled() and not get_option(
+ 'dynamic-storages-only',
+).disabled()
+ storage_cmds_src = ['dbus-sdr/storagecommands.cpp', 'dbus-sdr/sdrutils.cpp']
+endif
+
+openpower_cmds_src = []
+if get_option('open-power').allowed()
+ openpower_cmds_src = ['storageaddsel.cpp']
+endif
+
+arm_sbmr_cmds_src = []
+if get_option('arm-sbmr').allowed()
+ arm_sbmr_cmds_src = ['sbmrhandler.cpp']
+endif
+
+libipmi20_src = [
+ 'app/channel.cpp',
+ 'app/watchdog.cpp',
+ 'app/watchdog_service.cpp',
+ 'apphandler.cpp',
+ 'sys_info_param.cpp',
+ 'sensorhandler.cpp',
+ 'storagehandler.cpp',
+ 'chassishandler.cpp',
+ 'dcmihandler.cpp',
+ 'ipmisensor.cpp',
+ 'transporthandler.cpp',
+ 'globalhandler.cpp',
+ 'groupext.cpp',
+ 'selutility.cpp',
+ 'ipmi_fru_info_area.cpp',
+ 'read_fru_data.cpp',
+ 'sensordatahandler.cpp',
+ 'user_channel/channelcommands.cpp',
+ generated_src,
+ transportoem_src,
+ storage_cmds_src,
+ openpower_cmds_src,
+ arm_sbmr_cmds_src,
+ conf_h,
]
+ipmi20_lib = library(
+ 'ipmi20',
+ libipmi20_src,
+ dependencies: [ipmid_pre, nlohmann_json_dep],
+ include_directories: root_inc,
+ install: true,
+ install_dir: get_option('libdir') / 'ipmid-providers',
+ version: meson.project_version(),
+ override_options: ['b_lundef=false'],
+)
+
+libipmi20_dep = declare_dependency(
+ dependencies: ipmid_pre,
+ include_directories: root_inc,
+ link_with: ipmi20_lib,
+)
+
+# ipmid binary
executable(
- 'netipmid',
- sources,
- implicit_include_directories: true,
- include_directories: ['command', 'sol'],
- dependencies: deps,
+ 'ipmid',
+ 'ipmid-new.cpp',
+ 'host-cmd-manager.cpp',
+ 'settings.cpp',
+ implicit_include_directories: false,
+ dependencies: [libipmi20_dep],
+ include_directories: root_inc,
+ export_dynamic: true,
install: true,
install_dir: get_option('bindir'),
)
-systemd = dependency('systemd')
-systemd_system_unit_dir = systemd.get_variable(
- 'systemdsystemunitdir',
- pkgconfig_define: ['prefix', get_option('prefix')],
-)
+# Dynamic Sensor Stack
+subdir('dbus-sdr')
-configure_file(
- input: 'phosphor-ipmi-net@.service',
- output: 'phosphor-ipmi-net@.service',
- copy: true,
- install_dir: systemd_system_unit_dir,
-)
-
-configure_file(
- input: 'phosphor-ipmi-net@.socket',
- output: 'phosphor-ipmi-net@.socket',
- copy: true,
- install_dir: systemd_system_unit_dir,
-)
-
-build_tests = get_option('tests')
-if build_tests.allowed()
- subdir('test')
+if get_option('dynamic-sensors').disabled() or not get_option('tests').allowed()
+ library(
+ 'dynamiccmds',
+ dbus_sdr_src,
+ implicit_include_directories: false,
+ dependencies: dbus_sdr_pre,
+ version: meson.project_version(),
+ override_options: ['b_lundef=false'],
+ install: true,
+ install_dir: get_option('libdir') / 'ipmid-providers',
+ )
endif
+
+if get_option('tests').allowed()
+ subdir('test')
+ subdir('transport/serialbridge')
+endif
+
+install_subdir(
+ 'user_channel',
+ install_dir: get_option('includedir'),
+ strip_directory: false,
+ exclude_files: '*.cpp',
+)
+
+# HW Transport
+subdir('transport')
diff --git a/meson.options b/meson.options
index 6f9cbaa..fcc8851 100644
--- a/meson.options
+++ b/meson.options
@@ -1,15 +1,260 @@
-option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
+option('tests', type: 'feature', description: 'Build tests')
option(
- 'rmcp_ping',
+ 'boot-flag-safe-mode-support',
type: 'feature',
- value: 'enabled',
- description: 'Enable RMCP Ping support',
+ description: 'Add option to enable/disable safe mode in boot flags',
+)
+option(
+ 'i2c-whitelist-check',
+ type: 'feature',
+ description: 'Add option to enable/disable i2c master write read command white list checking',
)
+# SoftPowerOff
+option('softoff', type: 'feature', description: 'Builds soft power off')
option(
- 'pam_authenticate',
+ 'softoff-busname',
+ type: 'string',
+ value: 'xyz.openbmc_project.Ipmi.Internal.SoftPowerOff',
+ description: 'The Dbus busname to own for SoftPowerOff',
+)
+option(
+ 'softoff-objpath',
+ type: 'string',
+ value: '/xyz/openbmc_project/ipmi/internal/soft_power_off',
+ description: 'The SoftPowerOff Dbus root',
+)
+option(
+ 'ipmi-sms-atn-ack-timeout-secs',
+ type: 'integer',
+ value: 3,
+ description: 'Timeout for host to ack and query SMS_ATN from BMC',
+)
+option(
+ 'ipmi-host-shutdown-complete-timeout-secs',
+ type: 'integer',
+ value: 2700,
+ description: 'Wait time for host to shutdown',
+)
+# Indicates an in-band power off or reboot request from the host
+# This file is used to ensure the soft off service does not run for host
+# initiated shutdown or reboot requests
+option(
+ 'host-inband-request-dir',
+ type: 'string',
+ value: '/run/openbmc/',
+ description: 'Directory to store host initiated shutdown file',
+)
+option(
+ 'host-inband-request-file',
+ type: 'string',
+ value: 'host@%u-request',
+ description: 'File to create if host has initiated shutdown or reboot',
+)
+
+
+# Config Variables
+option(
+ 'board-sensor',
+ type: 'string',
+ value: '/xyz/openbmc_project/inventory/system/chassis/motherboard',
+ description: 'The inventory path to the motherboard fault sensor',
+)
+option(
+ 'system-sensor',
+ type: 'string',
+ value: '/xyz/openbmc_project/inventory/system',
+ description: 'The inventory path to the system event sensor',
+)
+
+# Control Host Interfaces
+option(
+ 'control-host-busname',
+ type: 'string',
+ value: 'xyz.openbmc_project.Control.Host',
+ description: 'The Control Host Dbus busname to own',
+)
+option(
+ 'control-host-obj-mgr',
+ type: 'string',
+ value: '/xyz/openbmc_project/control',
+ description: 'The Control Host D-Bus Object Manager',
+)
+option(
+ 'host-name',
+ type: 'string',
+ value: 'host',
+ description: 'The Control Host D-Bus Object Manager',
+)
+
+# Power reading sensor configuration file
+option(
+ 'power-reading-sensor',
+ type: 'string',
+ value: '/usr/share/ipmi-providers/power_reading.json',
+ description: 'Power reading sensor configuration file',
+)
+option(
+ 'host-ipmi-lib-path',
+ type: 'string',
+ value: '/usr/lib/ipmid-providers/',
+ description: 'The file path to search for libraries',
+)
+
+# When a sensor read fails, hwmon will update the OperationalState interface's Functional property.
+# This will mark the sensor as not functional and we will skip reading from that sensor.
+option(
+ 'update-functional-on-fail',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Check functional property to skip reading from faulty sensors',
+)
+
+# Features
+
+# When libuserlayer is disabled, libuserlayer won't be included in the build.
+option(
+ 'libuserlayer',
+ type: 'feature',
+ description: 'Option to exclue exclude libuserlayer',
+)
+
+# When transport-oem is enabled, the transporthandler_oem.cpp contents
+# are compiled and added to the project. The transporthandler_oem.cpp file is
+# copied from your own customization layer in the
+# phosphor-ipmi-host_%.bbappend file. It is not necessary to create this file
+# unless OEM Parameter extensions are required.
+option(
+ 'transport-oem',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Enable transporthandler_oem contents',
+)
+
+# IPMI whitelist mechanism is not needed by everyone; offer a way to disable it
+option(
+ 'ipmi-whitelist',
+ type: 'feature',
+ description: 'Enable/disable IPMI whitelist filtering',
+)
+option(
+ 'whitelist-conf',
+ type: 'string',
+ value: 'host-ipmid-whitelist.conf',
+ description: 'Paths to IPMI whitelisted commands conf files',
+)
+
+# Entity Manager Decorators
+option(
+ 'entity-manager-decorators',
type: 'feature',
value: 'enabled',
- description: 'Enable Pam Authenticate',
+ description: 'The Entity Manager Decorators flag is enabled by default; offer a way to disable it',
+)
+
+# Dynamic Sensor Stack
+option(
+ 'dynamic-sensors',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Dynamic sensors stack is enabled by default; offer a way to disable it',
+)
+option(
+ 'dynamic-sensors-write',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Dynamic sensors stack is enabled by default; offer a way to disable it',
+)
+option(
+ 'hybrid-sensors',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Hybrid sensors stack is disabled by default; offer a way to enable it',
+)
+option(
+ 'sensors-oem',
+ type: 'feature',
+ value: 'disabled',
+ description: 'OEM sensor SDR parsing is disabled by default; offer a way to enable it',
+)
+
+# Sensor Cache
+option(
+ 'sensors-cache',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Sensor cache stack is disabled by default; offer a way to enable it',
+)
+
+# Short Sensor Names for IPMI
+option(
+ 'shortname-remove-suffix',
+ type: 'feature',
+ value: 'enabled',
+ description: 'shortname-remove-suffix is enabled by default',
+)
+option(
+ 'shortname-replace-words',
+ type: 'feature',
+ value: 'disabled',
+ description: 'shortname-replace-words is disabled by default',
+)
+
+# Generate configuration from Yaml
+option('sensor-yaml-gen', type: 'string', value: 'sensor-example.yaml')
+option(
+ 'invsensor-yaml-gen',
+ type: 'string',
+ value: 'inventory-sensor-example.yaml',
+)
+option('fru-yaml-gen', type: 'string', value: 'fru-read-example.yaml')
+
+# Software Version
+option(
+ 'get-dbus-active-software',
+ type: 'feature',
+ description: 'Use the getActiveSoftwareVersionInfo for the BMC version and dev_id.json as backup',
+)
+option(
+ 'fw-ver-regex',
+ type: 'string',
+ value: '(\\\\d+)\\\\.(\\\\d+)',
+ description: 'Regular expressions for parsing firmware revision',
+)
+option(
+ 'matches-map',
+ type: 'array',
+ value: ['1', '2', '0', '0', '0', '0'],
+ description: 'An array of integers',
+)
+
+# libipmi20.so library
+option(
+ 'dynamic-storages-only',
+ type: 'feature',
+ value: 'disabled',
+ description: 'Request to compile storage commands in the libipmi20 library',
+)
+
+# open-power specific functionality.
+option(
+ 'open-power',
+ type: 'feature',
+ description: 'Support open-power specific functions',
+)
+
+# HW transport
+option(
+ 'transport-implementation',
+ type: 'combo',
+ choices: ['null', 'serial'],
+ description: 'transport',
+)
+
+# arm-sbmr specific functionality.
+option(
+ 'arm-sbmr',
+ type: 'feature',
+ description: 'Support Arm SBMR specific functions',
)
diff --git a/read_fru_data.cpp b/read_fru_data.cpp
new file mode 100644
index 0000000..9e5fd59
--- /dev/null
+++ b/read_fru_data.cpp
@@ -0,0 +1,180 @@
+#include "read_fru_data.hpp"
+
+#include "fruread.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <algorithm>
+#include <map>
+
+extern const FruMap frus;
+namespace ipmi
+{
+namespace fru
+{
+
+using namespace phosphor::logging;
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+std::unique_ptr<sdbusplus::bus::match_t> matchPtr
+ __attribute__((init_priority(101)));
+
+namespace cache
+{
+// User initiate read FRU info area command followed by
+// FRU read command. Also data is read in small chunks of
+// the specified offset and count.
+// Caching the data which will be invalidated when ever there
+// is a change in FRU properties.
+FRUAreaMap fruMap;
+} // namespace cache
+/**
+ * @brief Read all the property value's for the specified interface
+ * from Inventory.
+ *
+ * @param[in] intf Interface
+ * @param[in] path Object path
+ * @return map of properties
+ */
+ipmi::PropertyMap readAllProperties(const std::string& intf,
+ const std::string& path)
+{
+ ipmi::PropertyMap properties;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ std::string service;
+ std::string objPath;
+
+ // Is the path the full dbus path?
+ if (path.find(xyzPrefix) != std::string::npos)
+ {
+ service = ipmi::getService(bus, intf, path);
+ objPath = path;
+ }
+ else
+ {
+ service = ipmi::getService(bus, invMgrInterface, invObjPath);
+ objPath = invObjPath + path;
+ }
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ propInterface, "GetAll");
+ method.append(intf);
+ try
+ {
+ auto reply = bus.call(method);
+ reply.read(properties);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ // If property is not found simply return empty value
+ lg2::error("Error in reading property values: {ERROR}, path: {PATH}, "
+ "interface: {INTERFACE}",
+ "ERROR", e, "PATH", objPath, "INTERFACE", intf);
+ }
+
+ return properties;
+}
+
+void processFruPropChange(sdbusplus::message_t& msg)
+{
+ if (cache::fruMap.empty())
+ {
+ return;
+ }
+ std::string path = msg.get_path();
+ // trim the object base path, if found at the beginning
+ if (path.compare(0, strlen(invObjPath), invObjPath) == 0)
+ {
+ path.erase(0, strlen(invObjPath));
+ }
+ for (const auto& [fruId, instanceList] : frus)
+ {
+ auto found = std::find_if(
+ instanceList.begin(), instanceList.end(),
+ [&path](const auto& iter) { return (iter.path == path); });
+
+ if (found != instanceList.end())
+ {
+ cache::fruMap.erase(fruId);
+ break;
+ }
+ }
+}
+
+// register for fru property change
+int registerCallbackHandler()
+{
+ if (matchPtr == nullptr)
+ {
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ matchPtr = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ path_namespace(invObjPath) + type::signal() +
+ member("PropertiesChanged") + interface(propInterface),
+ std::bind(processFruPropChange, std::placeholders::_1));
+ }
+ return 0;
+}
+
+/**
+ * @brief Read FRU property values from Inventory
+ *
+ * @param[in] fruNum FRU id
+ * @return populate FRU Inventory data
+ */
+FruInventoryData readDataFromInventory(const FRUId& fruNum)
+{
+ auto iter = frus.find(fruNum);
+ if (iter == frus.end())
+ {
+ lg2::error("Unsupported FRU ID: {FRUID}", "FRUID", fruNum);
+ elog<InternalFailure>();
+ }
+
+ FruInventoryData data;
+ auto& instanceList = iter->second;
+ for (auto& instance : instanceList)
+ {
+ for (auto& intf : instance.interfaces)
+ {
+ ipmi::PropertyMap allProp =
+ readAllProperties(intf.first, instance.path);
+ for (auto& properties : intf.second)
+ {
+ auto iter = allProp.find(properties.first);
+ if (iter != allProp.end())
+ {
+ data[properties.second.section].emplace(
+ properties.second.property,
+ std::move(
+ std::get<std::string>(allProp[properties.first])));
+ }
+ }
+ }
+ }
+ return data;
+}
+
+const FruAreaData& getFruAreaData(const FRUId& fruNum)
+{
+ auto iter = cache::fruMap.find(fruNum);
+ if (iter != cache::fruMap.end())
+ {
+ return iter->second;
+ }
+ auto invData = readDataFromInventory(fruNum);
+
+ // Build area info based on inventory data
+ FruAreaData newdata = buildFruAreaData(std::move(invData));
+ cache::fruMap.emplace(fruNum, std::move(newdata));
+ return cache::fruMap.at(fruNum);
+}
+} // namespace fru
+} // namespace ipmi
diff --git a/read_fru_data.hpp b/read_fru_data.hpp
new file mode 100644
index 0000000..e0997fd
--- /dev/null
+++ b/read_fru_data.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include "ipmi_fru_info_area.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <string>
+
+namespace ipmi
+{
+namespace fru
+{
+using FRUId = uint8_t;
+using FRUAreaMap = std::map<FRUId, FruAreaData>;
+
+static constexpr auto xyzPrefix = "/xyz/openbmc_project/";
+static constexpr auto invMgrInterface = "xyz.openbmc_project.Inventory.Manager";
+static constexpr auto invObjPath = "/xyz/openbmc_project/inventory";
+static constexpr auto propInterface = "org.freedesktop.DBus.Properties";
+static constexpr auto invItemInterface = "xyz.openbmc_project.Inventory.Item";
+static constexpr auto itemPresentProp = "Present";
+
+/**
+ * @brief Get fru area data as per IPMI specification
+ *
+ * @param[in] fruNum FRU ID
+ *
+ * @return FRU area data as per IPMI specification
+ */
+const FruAreaData& getFruAreaData(const FRUId& fruNum);
+
+/**
+ * @brief Register callback handler into DBUS for PropertyChange events
+ *
+ * @return negative value on failure
+ */
+int registerCallbackHandler();
+} // namespace fru
+} // namespace ipmi
diff --git a/sbmrhandler.cpp b/sbmrhandler.cpp
new file mode 100644
index 0000000..328d503
--- /dev/null
+++ b/sbmrhandler.cpp
@@ -0,0 +1,317 @@
+#include <ipmid/api.hpp>
+#include <ipmid/filter.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+constexpr auto sbmrBootStateIntf = "xyz.openbmc_project.State.Boot.Raw";
+constexpr auto sbmrHostStateIntf = "xyz.openbmc_project.State.Boot.Progress";
+constexpr auto sbmrBootProgressCodeSize = 9;
+
+constexpr auto bootProgressOem = "OEM";
+constexpr auto bootProgressOsRuning = "OSRunning";
+constexpr auto bootProgressOsStart = "OSStart";
+constexpr auto bootProgressPciInit = "PCIInit";
+constexpr auto bootProgressSystemInitComplete = "SystemInitComplete";
+constexpr auto bootProgressSystemSetup = "SystemSetup";
+
+// EFI_STATUS_CODE_TYPE
+constexpr auto efiProgressCode = 0x01;
+constexpr auto efiCodeSeverityNone = 0;
+
+// EFI_STATUS_CODE_CLASS
+constexpr auto efiIoBus = 0x02;
+constexpr auto efiSoftware = 0x03;
+
+// EFI_STATUS_CODE_SUBCLASS
+constexpr auto efiIoBusPci = 0x01;
+constexpr auto efiSoftwareDxeCore = 0x04;
+constexpr auto efiSoftwareDxeBsDriver = 0x05;
+constexpr auto efiSoftwareEfiBootService = 0x10;
+
+// EFI_STATUS_CODE_OPERATION
+constexpr auto efiIoBusPciResAlloc = 0x0110;
+constexpr auto efiSwDxeCorePcHandoffToNext = 0x0110;
+constexpr auto efiSwPcUserSetup = 0x0700;
+constexpr auto efiSwOsLoaderStart = 0x0180;
+constexpr auto efiSwBsPcExitBootServices = 0x1910;
+
+void registerNetfnSBMRFunctions() __attribute__((constructor));
+
+namespace ipmi
+{
+
+std::string getSbmrBootProgressStage(uint8_t codeType, uint8_t codeSeverity,
+ uint8_t codeClass, uint8_t codeSubClass,
+ uint16_t codeOperation)
+{
+ // Return OEM if code type or severity are unexpected
+ if (codeType != efiProgressCode || codeSeverity != efiCodeSeverityNone)
+ {
+ return bootProgressOem;
+ }
+
+ // Code Class Software
+ if (codeClass == efiSoftware)
+ {
+ if (codeSubClass == efiSoftwareDxeCore &&
+ codeOperation == efiSwDxeCorePcHandoffToNext)
+ {
+ return bootProgressSystemInitComplete;
+ }
+ else if (codeSubClass == efiSoftwareDxeBsDriver &&
+ codeOperation == efiSwPcUserSetup)
+ {
+ return bootProgressSystemSetup;
+ }
+ else if (codeSubClass == efiSoftwareDxeBsDriver &&
+ codeOperation == efiSwOsLoaderStart)
+ {
+ return bootProgressOsStart;
+ }
+ else if (codeSubClass == efiSoftwareEfiBootService &&
+ codeOperation == efiSwBsPcExitBootServices)
+ {
+ return bootProgressOsRuning;
+ }
+ }
+ // Code Class IO Bus
+ else if (codeClass == efiIoBus)
+ {
+ if (codeSubClass == efiIoBusPci && codeOperation == efiIoBusPciResAlloc)
+ {
+ return bootProgressPciInit;
+ }
+ }
+
+ // Fallback to OEM if no conditions met
+ return bootProgressOem;
+}
+
+bool updateBootProgressProperty(ipmi::Context::ptr& ctx,
+ const std::string& value)
+{
+ std::string bootProgress =
+ "xyz.openbmc_project.State.Boot.Progress.ProgressStages." + value;
+ ipmi::DbusObjectInfo sbmrHostStateObject{};
+
+ /* Get Host State Object */
+ boost::system::error_code ec =
+ ipmi::getDbusObject(ctx, sbmrHostStateIntf, sbmrHostStateObject);
+ if (ec.value())
+ {
+ lg2::error("Failed to get Host State object, Error={ERROR}", "ERROR",
+ ec.message());
+ return false;
+ }
+
+ /* Set Host State property */
+ ec = ipmi::setDbusProperty(ctx, sbmrHostStateObject.second,
+ sbmrHostStateObject.first, sbmrHostStateIntf,
+ "BootProgress", bootProgress);
+ if (ec.value())
+ {
+ lg2::error(
+ "updateBootProgressProperty, can't set progerty - Error={ERROR}",
+ "ERROR", ec.message());
+ return false;
+ }
+
+ return true;
+}
+
+bool updateBootProgressLastUpdateProperty(ipmi::Context::ptr& ctx,
+ uint64_t timeStamp)
+{
+ ipmi::DbusObjectInfo sbmrHostStateObject{};
+
+ /* Get Host State Object */
+ boost::system::error_code ec =
+ ipmi::getDbusObject(ctx, sbmrHostStateIntf, sbmrHostStateObject);
+ if (ec.value())
+ {
+ lg2::error("Failed to get Host State object, Error={ERROR}", "ERROR",
+ ec.message());
+ return false;
+ }
+
+ /* Set Host State property */
+ ec = ipmi::setDbusProperty(ctx, sbmrHostStateObject.second,
+ sbmrHostStateObject.first, sbmrHostStateIntf,
+ "BootProgressLastUpdate", timeStamp);
+ if (ec.value())
+ {
+ lg2::error(
+ "updateBootProgressLastUpdateProperty, can't set property - Error={ERROR}",
+ "ERROR", ec.message());
+ return false;
+ }
+
+ return true;
+}
+
+ipmi::RspType<> sendBootProgressCode(
+ ipmi::Context::ptr ctx, uint8_t codeType, uint8_t codeReserved1,
+ uint8_t codeReserved2, uint8_t codeSeverity, uint8_t codeOperation1,
+ uint8_t codeOperation2, uint8_t codeSubClass, uint8_t codeClass,
+ uint8_t instance)
+{
+ /* Update boot progress code to Dbus property */
+ ipmi::DbusObjectInfo sbmrBootStateObject{};
+
+ /* Get Boot State Object */
+ boost::system::error_code ec =
+ ipmi::getDbusObject(ctx, sbmrBootStateIntf, sbmrBootStateObject);
+ if (ec.value())
+ {
+ lg2::error("Failed to get Boot State object, Error={ERROR}", "ERROR",
+ ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ /* Set Boot State property */
+ BootProgressCode bpCode(
+ {codeType, codeReserved1, codeReserved2, codeSeverity, codeOperation1,
+ codeOperation2, codeSubClass, codeClass, instance},
+ {});
+ ec = ipmi::setDbusProperty(ctx, sbmrBootStateObject.second,
+ sbmrBootStateObject.first, sbmrBootStateIntf,
+ "Value", bpCode);
+ if (ec.value())
+ {
+ lg2::error("Failed to set boot progress code, Error={ERROR}", "ERROR",
+ ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ /* Update Redfish BootProgress object */
+ auto timeStamp = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ if (!updateBootProgressLastUpdateProperty(ctx, timeStamp))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ /* Chek for BootProgressTypes */
+ uint16_t codeOperation =
+ static_cast<uint16_t>(codeOperation1) << 8 | codeOperation2;
+
+ std::string stage = getSbmrBootProgressStage(
+ codeType, codeSeverity, codeClass, codeSubClass, codeOperation);
+
+ if (!updateBootProgressProperty(ctx, stage))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t, // STATUS_CODE_TYPE
+ uint8_t, // STATUS_CODE_RESERVED1
+ uint8_t, // STATUS_CODE_RESERVED2
+ uint8_t, // STATUS_CODE_SEVERITY
+ uint8_t, // STATUS_CODE_OPERATION1
+ uint8_t, // STATUS_CODE_OPERATION2
+ uint8_t, // STATUS_CODE_SUBCLASS
+ uint8_t, // STATUS_CODE_CLASS
+ uint8_t> // Instance
+ getBootProgressCode(ipmi::Context::ptr ctx)
+{
+ ipmi::DbusObjectInfo sbmrBootStateObject{};
+
+ /* Get Boot State Object */
+ boost::system::error_code ec =
+ ipmi::getDbusObject(ctx, sbmrBootStateIntf, sbmrBootStateObject);
+ if (ec.value())
+ {
+ lg2::error("Failed to get Boot State object, Error={ERROR}", "ERROR",
+ ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ /* Get Boot State property */
+ BootProgressCode value;
+ ec = ipmi::getDbusProperty(ctx, sbmrBootStateObject.second,
+ sbmrBootStateObject.first, sbmrBootStateIntf,
+ "Value", value);
+ if (ec.value())
+ {
+ lg2::error("Can't get property Value, Error={ERROR}", "ERROR",
+ ec.message());
+ return ipmi::responseUnspecifiedError();
+ }
+
+ auto respBootProgressCode = std::get<0>(std::move(value));
+ if (respBootProgressCode.size() != sbmrBootProgressCodeSize)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(
+ respBootProgressCode[0], respBootProgressCode[1],
+ respBootProgressCode[2], respBootProgressCode[3],
+ respBootProgressCode[4], respBootProgressCode[5],
+ respBootProgressCode[6], respBootProgressCode[7],
+ respBootProgressCode[8]);
+}
+
+bool checkAllowedMediumType(uint8_t mediumType)
+{
+ if (mediumType ==
+ static_cast<uint8_t>(ipmi::EChannelMediumType::smbusV20) ||
+ mediumType ==
+ static_cast<uint8_t>(ipmi::EChannelMediumType::systemInterface) ||
+ mediumType == static_cast<uint8_t>(ipmi::EChannelMediumType::oem))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+ipmi::Cc sbmrFilterCommands(ipmi::message::Request::ptr request)
+{
+ if (request->ctx->netFn != ipmi::netFnGroup ||
+ request->ctx->group != ipmi::groupSBMR)
+ {
+ // Skip if not group SBMR
+ return ipmi::ccSuccess;
+ }
+
+ ipmi::ChannelInfo chInfo;
+ if (ipmi::getChannelInfo(request->ctx->channel, chInfo) != ipmi::ccSuccess)
+ {
+ lg2::error("Failed to get Channel Info, channel={CHANNEL}", "CHANNEL",
+ request->ctx->channel);
+ return ipmi::ccUnspecifiedError;
+ }
+
+ if (request->ctx->cmd == ipmi::sbmr::cmdSendBootProgressCode &&
+ !checkAllowedMediumType(chInfo.mediumType))
+ {
+ lg2::error("Error - Medium interface not supported, medium={TYPE}",
+ "TYPE", chInfo.mediumType);
+ return ipmi::ccCommandNotAvailable;
+ }
+
+ return ipmi::ccSuccess;
+}
+
+} // namespace ipmi
+
+void registerNetfnSBMRFunctions()
+{
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupSBMR,
+ ipmi::sbmr::cmdSendBootProgressCode,
+ ipmi::Privilege::Admin, ipmi::sendBootProgressCode);
+
+ registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupSBMR,
+ ipmi::sbmr::cmdGetBootProgressCode,
+ ipmi::Privilege::User, ipmi::getBootProgressCode);
+
+ ipmi::registerFilter(ipmi::prioOemBase,
+ [](ipmi::message::Request::ptr request) {
+ return ipmi::sbmrFilterCommands(request);
+ });
+}
diff --git a/scripts/entity-example.md b/scripts/entity-example.md
new file mode 100644
index 0000000..abeb581
--- /dev/null
+++ b/scripts/entity-example.md
@@ -0,0 +1,156 @@
+# Entity-example
+
+If your platform requires the entity container map, you can provide a json file
+of the format:
+
+```json
+[
+ {
+ "id": 1,
+ "containerEntityId": 2,
+ "containerEntityInstance": 3,
+ "isList": false,
+ "isLinked": false,
+ "entities": [
+ { "id": 1, "instance": 2 },
+ { "id": 1, "instance": 3 },
+ { "id": 1, "instance": 4 },
+ { "id": 1, "instance": 5 }
+ ]
+ }
+]
+```
+
+as part of your `phosphor-ipmi-config`
+
+The above json is identical to the original YAML documented below:
+
+```text
+# This record has:
+# Container Entity Id and Container Entity Instance = (0x13, 0x81)
+# Contained Entity Id and Contained Entity Instance = (0x0A, 0x1),
+# (0x0A, 0x3), (0x0A, 0x5), (0x0A, 0x7)
+# Entity Record id is the key
+0x01:
+ # Container entity contains other entities
+ # Entity Id and entity Instance for the container entity
+ containerEntityId: 0x13
+ containerEntityInstance: 0x81
+ # A record can have contained entities as a four entry list or as upto
+ # two ranges of entity instances; this record has contained entities
+ # as a four entry list
+ isList: "true"
+ # Records can be linked if necessary to extend the number of contained
+ # entities within a container entity; this record is not linked
+ isLinked: "false"
+ entityId1: 0x0A
+ entityInstance1: 0x1
+ entityId2: 0x0A
+ entityInstance2: 0x3
+ entityId3: 0x0A
+ entityInstance3: 0x5
+ entityId4: 0x0A
+ entityInstance4: 0x7
+
+# The below two records have:
+# Container Entity Id and Container Entity Instance = (0x18, 0x2)
+# Contained Entity Id and Contained Entity Instance = (0x1D, 0x1),
+# (0x1D, 0x4), (0x1D, 0x6), (0x2B, 0x1), (0x2B, 0x3), (0x0F, 0x1),
+# (0x0F, 0x3), (0x10, 0x5)
+0x02:
+ containerEntityId: 0x18
+ containerEntityInstance: 0x2
+ # This record has contained entities as a four entry list
+ isList: "true"
+ # This record is linked with the below record; this record and the
+ # below record have the same container entity Id and container entity
+ # instance;
+ isLinked: "true"
+ entityId1: 0x1D
+ entityInstance1: 0x1
+ entityId2: 0x1D
+ entityInstance2: 0x4
+ entityId3: 0x1D
+ entityInstance3: 0x6
+ entityId4: 0x2B
+ entityInstance4: 0x1
+
+0x03:
+ containerEntityId: 0x18
+ containerEntityInstance: 0x2
+ # This record has contained entities as a four entry list
+ isList: "true"
+ # This record is linked with the above record; this record and the
+ # above record have the same container entity Id and container entity
+ # instance
+ isLinked: "true"
+ entityId1: 0x2B
+ entityInstance1: 0x3
+ entityId2: 0x0F
+ entityInstance2: 0x1
+ entityId3: 0x0F
+ entityInstance3: 0x3
+ entityId4: 0x10
+ entityInstance4: 0x5
+
+# This record has:
+# Container Entity Id and Container Entity Instance = (0x1E, 0x1)
+# Contained Entity Id and Contained Entity Instance = (0x20, 0x1),
+# (0x20, 0x2), (0x20, 0x3), (0x20, 0x7), (0x20, 0x8), (0x20, 0x9)
+0x04:
+ containerEntityId: 0x1E
+ containerEntityInstance: 0x1
+ # This record has contained entities as two ranges of entity instances
+ isList: "false"
+ # This record is not linked
+ isLinked: "false"
+ entityId1: 0x20
+ entityInstance1: 0x1
+ entityId2: 0x20
+ entityInstance2: 0x3
+ entityId3: 0x20
+ entityInstance3: 0x7
+ entityId4: 0x20
+ entityInstance4: 0x9
+
+# The below two records have:
+# Container Entity Id and Container Entity Instance = (0x1E, 0x3)
+# Contained Entity Id and Contained Entity Instance = (0x20, 0x1),
+# (0x20, 0x2), (0x20, 0x3), (0x20, 0x6), (0x20, 0x7), (0x20, 0x8),
+# (0x20, 0xA), (0x20, 0xB), (0x20, 0xD), (0x20, 0xE), (0x20, 0xF)
+0x05:
+ containerEntityId: 0x1E
+ containerEntityInstance: 0x03
+ # This record has contained entities as two ranges of entity instances
+ isList: "false"
+ # This record is linked with the below record; this record and the
+ # below record have the same container entity Id and container entity
+ # instance;
+ isLinked: "true"
+ entityId1: 0x20
+ entityInstance1: 0x1
+ entityId2: 0x20
+ entityInstance2: 0x3
+ entityId3: 0x20
+ entityInstance3: 0x6
+ entityId4: 0x20
+ entityInstance4: 0x8
+
+0x06:
+ containerEntityId: 0x1E
+ containerEntityInstance: 0x03
+ # This record has contained entities as two ranges of entity instances
+ isList: "false"
+ # This record is linked with the above record; this record and the
+ # above record have the same container entity Id and container entity
+ # instance;
+ isLinked: "true"
+ entityId1: 0x20
+ entityInstance1: 0xA
+ entityId2: 0x20
+ entityInstance2: 0xB
+ entityId3: 0x20
+ entityInstance3: 0xD
+ entityId4: 0x20
+ entityInstance4: 0xF
+```
diff --git a/scripts/fru-read-example.yaml b/scripts/fru-read-example.yaml
new file mode 100644
index 0000000..a186794
--- /dev/null
+++ b/scripts/fru-read-example.yaml
@@ -0,0 +1,139 @@
+# A YAML similar to this example would have to be generated, for eg with MRW
+# inputs and system configuration, to depict IPMI Fru information.
+#
+# This file maps IPMI properties to phosphor dbus inventory properties
+#
+# This YAML could help generate C++ code.
+# Format of the YAML:
+# Fruid:
+# Associated Fru paths
+# d-bus Interfaces
+# d-bus Properties
+# IPMI Fru mapping
+0:
+ /system:
+ entityID: 23
+ entityInstance: 1
+ interfaces:
+ xyz.openbmc_project.Inventory.Item:
+ PrettyName:
+ IPMIFruProperty: Product Name
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Asset:
+ Manufacturer:
+ IPMIFruProperty: Manufacturer
+ IPMIFruSection: Product
+ PartNumber:
+ IPMIFruProperty: Part Number
+ IPMIFruSection: Product
+ SerialNumber:
+ IPMIFruProperty: Serial Number
+ IPMIFruSection: Product
+ BuildDate:
+ IPMIFruProperty: Mfg Date
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Revision:
+ Version:
+ IPMIFruProperty: Version
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Item.System:
+1:
+ /system/chassis/motherboard/dimm0:
+ entityID: 32
+ entityInstance: 1
+ interfaces:
+ xyz.openbmc_project.Inventory.Item:
+ PrettyName:
+ IPMIFruProperty: Product Name
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Asset:
+ Manufacturer:
+ IPMIFruProperty: Manufacturer
+ IPMIFruSection: Product
+ BuildDate:
+ IPMIFruProperty: Mfg Date
+ IPMIFruSection: Product
+ SerialNumber:
+ IPMIFruProperty: Serial Number
+ IPMIFruSection: Product
+ PartNumber:
+ IPMIFruProperty: Part Number
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Revision:
+ Version:
+ IPMIFruProperty: Version
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Item.Dimm:
+2:
+ /system/chassis/motherboard/dimm1:
+ entityID: 32
+ entityInstance: 2
+ interfaces:
+ xyz.openbmc_project.Inventory.Item:
+ PrettyName:
+ IPMIFruProperty: Product Name
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Asset:
+ Manufacturer:
+ IPMIFruProperty: Manufacturer
+ IPMIFruSection: Product
+ BuildDate:
+ IPMIFruProperty: Mfg Date
+ IPMIFruSection: Product
+ SerialNumber:
+ IPMIFruProperty: Serial Number
+ IPMIFruSection: Product
+ PartNumber:
+ IPMIFruProperty: Part Number
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Decorator.Revision:
+ Version:
+ IPMIFruProperty: Version
+ IPMIFruSection: Product
+ xyz.openbmc_project.Inventory.Item.Dimm:
+3:
+ /system/chassis/motherboard/cpu0:
+ entityID: 3
+ entityInstance: 1
+ interfaces:
+ xyz.openbmc_project.Inventory.Item:
+ PrettyName:
+ IPMIFruProperty: Product Name
+ IPMIFruSection: Board
+ xyz.openbmc_project.Inventory.Decorator.Asset:
+ BuildDate:
+ IPMIFruProperty: Mfg Date
+ IPMIFruSection: Board
+ SerialNumber:
+ IPMIFruProperty: Serial Number
+ IPMIFruSection: Board
+ PartNumber:
+ IPMIFruProperty: Part Number
+ IPMIFruSection: Board
+ Manufacturer:
+ IPMIFruProperty: Manufacturer
+ IPMIFruSection: Board
+ xyz.openbmc_project.Inventory.Item.Cpu:
+4:
+ /system/chassis/motherboard/cpu1:
+ entityID: 3
+ entityInstance: 2
+ interfaces:
+ xyz.openbmc_project.Inventory.Item:
+ PrettyName:
+ IPMIFruProperty: Product Name
+ IPMIFruSection: Board
+ xyz.openbmc_project.Inventory.Decorator.Asset:
+ BuildDate:
+ IPMIFruProperty: Mfg Date
+ IPMIFruSection: Board
+ SerialNumber:
+ IPMIFruProperty: Serial Number
+ IPMIFruSection: Board
+ PartNumber:
+ IPMIFruProperty: Part Number
+ IPMIFruSection: Board
+ Manufacturer:
+ IPMIFruProperty: Manufacturer
+ IPMIFruSection: Board
+ xyz.openbmc_project.Inventory.Item.Cpu:
diff --git a/scripts/fru_gen.py b/scripts/fru_gen.py
new file mode 100755
index 0000000..fd0941c
--- /dev/null
+++ b/scripts/fru_gen.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(inventory_yaml, output_dir):
+ with open(inventory_yaml, "r") as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(script_dir, "readfru.cpp.mako"))
+
+ output_hpp = os.path.join(output_dir, "fru-read-gen.cpp")
+ with open(output_hpp, "w") as fd:
+ fd.write(t.render(fruDict=ifile))
+
+
+def main():
+ valid_commands = {"generate-cpp": generate_cpp}
+ parser = argparse.ArgumentParser(description="IPMI FRU map code generator")
+
+ parser.add_argument(
+ "-i",
+ "--inventory_yaml",
+ dest="inventory_yaml",
+ default="example.yaml",
+ help="input inventory yaml file to parse",
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output-dir",
+ dest="outputdir",
+ default=".",
+ help="output directory",
+ )
+
+ parser.add_argument(
+ "command",
+ metavar="COMMAND",
+ type=str,
+ choices=valid_commands.keys(),
+ help="Command to run.",
+ )
+
+ args = parser.parse_args()
+
+ if not (os.path.isfile(args.inventory_yaml)):
+ sys.exit("Can not find input yaml file " + args.inventory_yaml)
+
+ function = valid_commands[args.command]
+ function(args.inventory_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/scripts/inventory-sensor-example.yaml b/scripts/inventory-sensor-example.yaml
new file mode 100644
index 0000000..5dde38f
--- /dev/null
+++ b/scripts/inventory-sensor-example.yaml
@@ -0,0 +1,340 @@
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0:
+ sensorID: 0xa6
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1:
+ sensorID: 0xa8
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm10:
+ sensorID: 0xba
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm11:
+ sensorID: 0xbc
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm12:
+ sensorID: 0xbe
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm13:
+ sensorID: 0xc0
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm14:
+ sensorID: 0xc2
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm15:
+ sensorID: 0xc4
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm2:
+ sensorID: 0xaa
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm3:
+ sensorID: 0xac
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm4:
+ sensorID: 0xae
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm5:
+ sensorID: 0xb0
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm6:
+ sensorID: 0xb2
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm7:
+ sensorID: 0xb4
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm8:
+ sensorID: 0xb6
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm9:
+ sensorID: 0xb8
+ sensorType: 0x0C
+ eventReadingType: 0x6F
+ offset: 0x04
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0:
+ sensorID: 0x5a
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core0:
+ sensorID: 0x12
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core1:
+ sensorID: 0x15
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core2:
+ sensorID: 0x18
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core3:
+ sensorID: 0x1b
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core4:
+ sensorID: 0x1e
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core5:
+ sensorID: 0x21
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core6:
+ sensorID: 0x24
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core7:
+ sensorID: 0x27
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core8:
+ sensorID: 0x2a
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core9:
+ sensorID: 0x2d
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core10:
+ sensorID: 0x30
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core11:
+ sensorID: 0x33
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core12:
+ sensorID: 0x36
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core13:
+ sensorID: 0x39
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core14:
+ sensorID: 0x3c
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core15:
+ sensorID: 0x3f
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core16:
+ sensorID: 0x42
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core17:
+ sensorID: 0x45
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core18:
+ sensorID: 0x48
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core19:
+ sensorID: 0x4b
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core20:
+ sensorID: 0x4e
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core21:
+ sensorID: 0x51
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core22:
+ sensorID: 0x54
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu0/core23:
+ sensorID: 0x57
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1:
+ sensorID: 0xa4
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core0:
+ sensorID: 0x5c
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core1:
+ sensorID: 0x5f
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core2:
+ sensorID: 0x62
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core3:
+ sensorID: 0x65
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core4:
+ sensorID: 0x68
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core5:
+ sensorID: 0x6b
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core6:
+ sensorID: 0x6e
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core7:
+ sensorID: 0x71
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core8:
+ sensorID: 0x74
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core9:
+ sensorID: 0x77
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core10:
+ sensorID: 0x7a
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core11:
+ sensorID: 0x7d
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core12:
+ sensorID: 0x80
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core13:
+ sensorID: 0x83
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core14:
+ sensorID: 0x86
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core15:
+ sensorID: 0x89
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core16:
+ sensorID: 0x8c
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core17:
+ sensorID: 0x8f
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core18:
+ sensorID: 0x92
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core19:
+ sensorID: 0x95
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core20:
+ sensorID: 0x98
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core21:
+ sensorID: 0x9b
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core22:
+ sensorID: 0x9e
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1/core23:
+ sensorID: 0xa1
+ sensorType: 0x07
+ eventReadingType: 0x6F
+ offset: 0x08
+/xyz/openbmc_project/inventory/system/chassis/motherboard:
+ sensorID: 0x0c
+ sensorType: 0xC7
+ eventReadingType: 0x03
+ offset: 0x00
+/xyz/openbmc_project/inventory/system:
+ sensorID: 0x01
+ sensorType: 0x12
+ eventReadingType: 0x6F
+ offset: 0x02
diff --git a/scripts/inventory-sensor.py b/scripts/inventory-sensor.py
new file mode 100755
index 0000000..8c7bc19
--- /dev/null
+++ b/scripts/inventory-sensor.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(sensor_yaml, "r") as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(
+ filename=os.path.join(script_dir, "inventorysensor.cpp.mako")
+ )
+
+ output_cpp = os.path.join(output_dir, "inventory-sensor-gen.cpp")
+ with open(output_cpp, "w") as fd:
+ fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+ valid_commands = {"generate-cpp": generate_cpp}
+ parser = argparse.ArgumentParser(
+ description="Inventory Object to IPMI SensorID code generator"
+ )
+
+ parser.add_argument(
+ "-i",
+ "--sensor_yaml",
+ dest="sensor_yaml",
+ default="example.yaml",
+ help="input sensor yaml file to parse",
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output-dir",
+ dest="outputdir",
+ default=".",
+ help="output directory",
+ )
+
+ parser.add_argument(
+ "command",
+ metavar="COMMAND",
+ type=str,
+ choices=valid_commands.keys(),
+ help="Command to run.",
+ )
+
+ args = parser.parse_args()
+
+ if not (os.path.isfile(args.sensor_yaml)):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/scripts/inventorysensor.cpp.mako b/scripts/inventorysensor.cpp.mako
new file mode 100644
index 0000000..be3bb1e
--- /dev/null
+++ b/scripts/inventorysensor.cpp.mako
@@ -0,0 +1,26 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+
+#include <ipmid/types.hpp>
+using namespace ipmi::sensor;
+
+extern const InvObjectIDMap __attribute__((init_priority(101))) invSensors = {
+% for key in sensorDict.keys():
+ % if key:
+{"${key}",
+ {
+<%
+ objectPath = sensorDict[key]
+ sensorID = objectPath["sensorID"]
+ sensorType = objectPath["sensorType"]
+ eventReadingType = objectPath["eventReadingType"]
+ offset = objectPath["offset"]
+%>
+ ${sensorID},${sensorType},${eventReadingType},${offset}
+ }
+},
+ % endif
+% endfor
+};
diff --git a/scripts/meson.build b/scripts/meson.build
new file mode 100644
index 0000000..c5fcae5
--- /dev/null
+++ b/scripts/meson.build
@@ -0,0 +1,50 @@
+# Generate Configuration Files from Yaml
+python_exe = find_program('python3', 'python')
+
+sensor_gen = custom_target(
+ 'sensor-gen',
+ output: 'sensor-gen.cpp',
+ input: ['sensor_gen.py', get_option('sensor-yaml-gen')],
+ command: [
+ python_exe,
+ '@INPUT0@',
+ '-i',
+ '@INPUT1@',
+ '-o',
+ meson.current_build_dir(),
+ 'generate-cpp',
+ ],
+)
+generated_src += sensor_gen
+
+invsensor_gen = custom_target(
+ 'invsensor-gen',
+ output: 'inventory-sensor-gen.cpp',
+ input: ['inventory-sensor.py', get_option('invsensor-yaml-gen')],
+ command: [
+ python_exe,
+ '@INPUT0@',
+ '-i',
+ '@INPUT1@',
+ '-o',
+ meson.current_build_dir(),
+ 'generate-cpp',
+ ],
+)
+generated_src += invsensor_gen
+
+fru_gen = custom_target(
+ 'fru-gen',
+ output: 'fru-read-gen.cpp',
+ input: ['fru_gen.py', get_option('fru-yaml-gen')],
+ command: [
+ python_exe,
+ '@INPUT0@',
+ '-i',
+ '@INPUT1@',
+ '-o',
+ meson.current_build_dir(),
+ 'generate-cpp',
+ ],
+)
+generated_src += fru_gen
diff --git a/scripts/readfru.cpp.mako b/scripts/readfru.cpp.mako
new file mode 100644
index 0000000..e7fbab8
--- /dev/null
+++ b/scripts/readfru.cpp.mako
@@ -0,0 +1,42 @@
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+#include <iostream>
+#include "fruread.hpp"
+
+extern const FruMap __attribute__((init_priority(101))) frus = {
+% for key in fruDict.keys():
+ {${key},{
+<%
+ instanceList = fruDict[key]
+%>
+ % for instancePath,instanceInfo in instanceList.items():
+<%
+ entityID = instanceInfo["entityID"]
+ entityInstance = instanceInfo["entityInstance"]
+ interfaces = instanceInfo["interfaces"]
+%>
+ {${entityID}, ${entityInstance}, "${instancePath}",{
+ % for interface,properties in interfaces.items():
+ {"${interface}",{
+ % if properties:
+ % for dbus_property,property_value in properties.items():
+ {"${dbus_property}",{
+ "${property_value.get("IPMIFruSection", "")}",
+ "${property_value.get("IPMIFruProperty", "")}",\
+<%
+ delimiter = property_value.get("IPMIFruValueDelimiter")
+ if not delimiter:
+ delimiter = ""
+ else:
+ delimiter = '\\' + hex(delimiter)[1:]
+%>
+ "${delimiter}"
+ }},
+ % endfor
+ %endif
+ }},
+ % endfor
+ }},
+ % endfor
+ }},
+% endfor
+};
diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml
new file mode 100644
index 0000000..89430e4
--- /dev/null
+++ b/scripts/sensor-example.yaml
@@ -0,0 +1,194 @@
+# Sensor id is the key
+0x60:
+ sensorType: 0x07
+ sensorReadingType: 0x6F
+ # A "set" operation on this sensor should update this d-bus path.
+ # If the path is not specified, an MRW parser will try to determine the path
+ # based on the sensor id, on MRW based systems. This typically happens for
+ # inventory items.
+ path: /org/open_power/control/occ0
+ # The interface that exposes method(s) to update the path above.
+ serviceInterface: org.freedesktop.DBus.Properties
+ # Where the sensor value is represented - assertion bits/reading/event data
+ readingType: assertion
+ # indicate if a sensor is READ/WRITE/RW.
+ # This particular sensor read and write operation is allowed
+ mutability: Mutability::Write|Mutability::Read
+ # Sensor name would be occ0
+ sensorNamePattern: nameLeaf
+ eventType: 0x6F
+ # All the d-bus interfaces : properties that must be updated for this path
+ interfaces:
+ # One or more interface dict entries
+ org.open_power.OCC.Status:
+ OccActive:
+ Offsets:
+ # Sensor type specific offset
+ 0x06:
+ # OccActive is a boolean
+ type: "bool"
+ # If offset 0x06 is asserted, set OccActive as false.
+ assert: "false"
+ deassert: "true"
+
+0x61:
+ sensorType: 0x04
+ sensorReadingType: 0x6F
+ # Inventory paths intentionally leave out the inventory root,
+ # /xyz/openbmc_project/inventory, because phosphor-inventory-manager
+ # adds that.
+ path: /system/chassis/motherboard/dimm1
+ serviceInterface: xyz.openbmc_project.Inventory.Manager
+ readingType: assertion
+ mutability: Mutability::Write|Mutability::Read
+ sensorNamePattern: nameLeaf
+ interfaces:
+ xyz.openbmc_project.State.Decorator.OperationalStatus:
+ Functional:
+ #Offsets contain the offsets in the sensor data.
+ Offsets:
+ 0x06:
+ assert: true
+ deassert: false
+ type: bool
+ #Prereqs are pre-requisites for this property value to be true.
+ Prereqs:
+ 0x04:
+ assert: false
+ deassert: true
+ type: bool
+ xyz.openbmc_project.Inventory.Item:
+ Present:
+ Offsets:
+ 0x04:
+ assert: false
+ deassert: true
+ type: bool
+
+0x63:
+ interfaces:
+ xyz.openbmc_project.Control.Boot.RebootAttempts:
+ AttemptsLeft:
+ Offsets:
+ 0xFF:
+ type: uint32_t
+ path: /xyz/openbmc_project/state/host0
+ # A special case of assertion, where the entire assert bitfield
+ # serves as the value, or reading. Hence, the offset above is intentionally
+ # 0xFF, to indicate not to check any specific bits in the assertion.
+ readingType: readingAssertion
+ # Sensor name would be AttemptsLeft
+ sensorNamePattern: nameProperty
+ sensorReadingType: 0x6F
+ mutability: Mutability::Write|Mutability::Read
+ sensorType: 0xC3
+ serviceInterface: org.freedesktop.DBus.Properties
+
+0x62:
+ interfaces:
+ xyz.openbmc_project.Control.Boot.RebootAttempts:
+ AttemptsLeft:
+ Offsets:
+ 0xFF:
+ type: uint32_t
+ path: /xyz/openbmc_project/state/host1
+ readingType: readingAssertion
+ # set an explicit name for the sensor
+ sensorName: RebootAttempts
+ sensorReadingType: 0x6F
+ mutability: Mutability::Write|Mutability::Read
+ sensorType: 0xC3
+ serviceInterface: org.freedesktop.DBus.Properties
+
+0xD0:
+ sensorType: 0x01
+ path: /xyz/openbmc_project/sensors/temperature/fleeting0
+ sensorReadingType: 0x01
+ multiplierM: 511
+ offsetB: 0
+ bExp: 0
+ # Result exponent field in Type 1 SDR(2's complement, signed)
+ rExp: 0
+ # Applies for analog sensors, the actual reading value for the sensor is
+ # Value * 10^N
+ scale: -3
+ # Indicate Analog Data Format, Rate unit, Modifier unit and Percentage
+ sensorUnits1: 0x80
+ mutability: Mutability::Write|Mutability::Read
+ serviceInterface: org.freedesktop.DBus.Properties
+ readingType: readingData
+ sensorNamePattern: nameLeaf
+ interfaces:
+ xyz.openbmc_project.Sensor.Value:
+ Value:
+ Offsets:
+ 0xFF:
+ type: int64_t
+
+0x54:
+ sensorType: 0x07
+ path: /system/chassis/motherboard/cpu0/core22
+ sensorReadingType: 0x6F
+ serviceInterface: xyz.openbmc_project.Inventory.Manager
+ readingType: assertion
+ mutability: Mutability::Write|Mutability::Read
+ # Sensor name would be cpu0_core22
+ sensorNamePattern: nameParentLeaf
+ interfaces:
+ xyz.openbmc_project.State.Decorator.OperationalStatus:
+ Functional:
+ Offsets:
+ 0x08:
+ assert: false
+ deassert: true
+ type: bool
+ Prereqs:
+ 0x07:
+ assert: true
+ deassert: false
+ xyz.openbmc_project.Inventory.Item:
+ Present:
+ Offsets:
+ 0x07:
+ assert: true
+ deassert: false
+ #The update will be skipped based on the value of skipOn
+ #in this case if offset 0x07 is deasserted
+ #the update will be skipped.
+ skipOn: deassert
+ type: bool
+
+0xC5:
+ sensorType: 0x17
+ path: /system/chassis/motherboard/gv100card0
+ sensorReadingType: 1
+ serviceInterface: xyz.openbmc_project.Inventory.Manager
+ readingType: assertion
+ mutability: Mutability::Write|Mutability::Read
+ sensorNamePattern: nameLeaf
+ interfaces:
+ xyz.openbmc_project.Inventory.Decorator.Replaceable:
+ FieldReplaceable:
+ Offsets:
+ 7:
+ assert: true
+ type: bool
+ xyz.openbmc_project.Inventory.Item:
+ Present:
+ Offsets:
+ 7:
+ assert: true
+ deassert: false
+ type: bool
+ # Example of an interface with no attached properties
+ xyz.openbmc_project.Inventory.Item.Accelerator:
+ xyz.openbmc_project.State.Decorator.OperationalStatus:
+ Functional:
+ Offsets:
+ 8:
+ assert: false
+ type: bool
+ Prereqs:
+ 7:
+ assert: true
+ type: bool
diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py
new file mode 100755
index 0000000..5da5909
--- /dev/null
+++ b/scripts/sensor_gen.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+
+import yaml
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+ with open(sensor_yaml, "r") as f:
+ ifile = yaml.safe_load(f)
+ if not isinstance(ifile, dict):
+ ifile = {}
+
+ # Render the mako template
+
+ t = Template(filename=os.path.join(script_dir, "writesensor.cpp.mako"))
+
+ output_cpp = os.path.join(output_dir, "sensor-gen.cpp")
+ with open(output_cpp, "w") as fd:
+ fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+ valid_commands = {"generate-cpp": generate_cpp}
+ parser = argparse.ArgumentParser(
+ description="IPMI Sensor parser and code generator"
+ )
+
+ parser.add_argument(
+ "-i",
+ "--sensor_yaml",
+ dest="sensor_yaml",
+ default="example.yaml",
+ help="input sensor yaml file to parse",
+ )
+
+ parser.add_argument(
+ "-o",
+ "--output-dir",
+ dest="outputdir",
+ default=".",
+ help="output directory",
+ )
+
+ parser.add_argument(
+ "command",
+ metavar="COMMAND",
+ type=str,
+ choices=valid_commands.keys(),
+ help="Command to run.",
+ )
+
+ args = parser.parse_args()
+
+ if not (os.path.isfile(args.sensor_yaml)):
+ sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+ function = valid_commands[args.command]
+ function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == "__main__":
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ main()
diff --git a/scripts/writesensor.cpp.mako b/scripts/writesensor.cpp.mako
new file mode 100644
index 0000000..7123c37
--- /dev/null
+++ b/scripts/writesensor.cpp.mako
@@ -0,0 +1,182 @@
+## This file is a template. The comment below is emitted
+## into the rendered file; feel free to edit this file.
+// !!! WARNING: This is a GENERATED Code..Please do NOT Edit !!!
+<%
+interfaceDict = {}
+sensorNameMaxLength = 16
+%>\
+%for key in sensorDict.keys():
+<%
+ sensor = sensorDict[key]
+ serviceInterface = sensor["serviceInterface"]
+ if serviceInterface == "org.freedesktop.DBus.Properties":
+ updateFunc = "set::"
+ getFunc = "get::"
+ elif serviceInterface == "xyz.openbmc_project.Inventory.Manager":
+ updateFunc = "notify::"
+ getFunc = "inventory::get::"
+ else:
+ assert "Un-supported interface: " + serviceInterface
+ endif
+ if serviceInterface not in interfaceDict:
+ interfaceDict[serviceInterface] = {}
+ interfaceDict[serviceInterface]["updateFunc"] = updateFunc
+ interfaceDict[serviceInterface]["getFunc"] = getFunc
+%>\
+% endfor
+
+#include "sensordatahandler.hpp"
+
+#include <ipmid/types.hpp>
+
+namespace ipmi {
+namespace sensor {
+
+extern const IdInfoMap __attribute__((init_priority(101))) sensors = {
+% for key in sensorDict.keys():
+ % if key:
+{${key},{
+<%
+ sensor = sensorDict[key]
+ interfaces = sensor["interfaces"]
+ path = sensor["path"]
+ serviceInterface = sensor["serviceInterface"]
+ sensorType = sensor["sensorType"]
+ entityID = sensor.get("entityID", 0)
+ instance = sensor.get("entityInstance", 0)
+ readingType = sensor["sensorReadingType"]
+ multiplier = sensor.get("multiplierM", 1)
+ offsetB = sensor.get("offsetB", 0)
+ bExp = sensor.get("bExp", 0)
+ rExp = sensor.get("rExp", 0)
+ sensorUnits1 = sensor.get("sensorUnits1", 0)
+ unit = sensor.get("unit", "")
+ scale = sensor.get("scale", 0)
+ hasScale = "true" if "scale" in sensor.keys() else "false"
+ valueReadingType = sensor["readingType"]
+ updateFunc = interfaceDict[serviceInterface]["updateFunc"]
+ updateFunc += sensor["readingType"]
+ getFunc = interfaceDict[serviceInterface]["getFunc"]
+ getFunc += sensor["readingType"]
+ sensorName = sensor.get("sensorName", None)
+ if sensorName:
+ assert len(sensorName) <= sensorNameMaxLength, \
+ "sensor name '%s' is too long (%d bytes max)" % \
+ (sensorName, sensorNameMaxLength)
+ else:
+ sensorNameFunc = "get::" + sensor.get("sensorNamePattern",
+ "nameLeaf")
+
+ if "readingAssertion" == valueReadingType or "readingData" == valueReadingType:
+ for interface,properties in interfaces.items():
+ for dbus_property,property_value in properties.items():
+ for offset,values in property_value["Offsets"].items():
+ valueType = values["type"]
+ updateFunc = "set::" + valueReadingType + "<" + valueType + ">"
+ getFunc = "get::" + valueReadingType + "<" + valueType + ">"
+ sensorInterface = serviceInterface
+ if serviceInterface == "org.freedesktop.DBus.Properties":
+ sensorInterface = next(iter(interfaces))
+ mutability = sensor.get("mutability", "Mutability::Read")
+%>
+ .entityType = ${entityID},
+ .instance = ${instance},
+ .sensorType = ${sensorType},
+ .sensorPath = "${path}",
+ .sensorInterface = "${sensorInterface}",
+ .sensorReadingType = ${readingType},
+ .coefficientM = ${multiplier},
+ .coefficientB = ${offsetB},
+ .exponentB = ${bExp},
+ .scaledOffset = ${offsetB * pow(10,bExp)},
+ .exponentR = ${rExp},
+ .hasScale = ${hasScale},
+ .scale = ${scale},
+ .sensorUnits1 = ${sensorUnits1},
+ .unit = "${unit}",
+ .updateFunc = ${updateFunc},
+ .getFunc = ${getFunc},
+ .mutability = Mutability(${mutability}),
+ % if sensorName:
+ .sensorName = "${sensorName}",
+ .sensorNameFunc = nullptr,
+ % else:
+ .sensorName = "",
+ .sensorNameFunc = ${sensorNameFunc},
+ % endif
+ .propertyInterfaces = {
+ % for interface,properties in interfaces.items():
+ {"${interface}",{
+ % if properties:
+ % for dbus_property,property_value in properties.items():
+ {"${dbus_property}",{
+<%
+try:
+ preReq = property_value["Prereqs"]
+except KeyError:
+ preReq = dict()
+%>\
+ {
+ % for preOffset,preValues in preReq.items():
+ { ${preOffset},{
+ % for name,value in preValues.items():
+ % if name == "type":
+<% continue %>\
+ % endif
+<% value = str(value).lower() %>\
+ ${value},
+ % endfor
+ }
+ },
+ % endfor
+ },
+ {
+ % for offset,values in property_value["Offsets"].items():
+ { ${offset},{
+ % if offset == 0xFF:
+ }},
+<% continue %>\
+ % endif
+<% valueType = values["type"] %>\
+<%
+try:
+ skip = values["skipOn"]
+ if skip == "assert":
+ skipVal = "SkipAssertion::ASSERT"
+ elif skip == "deassert":
+ skipVal = "SkipAssertion::DEASSERT"
+ else:
+ assert "Unknown skip value " + str(skip)
+except KeyError:
+ skipVal = "SkipAssertion::NONE"
+%>\
+ ${skipVal},
+ % for name,value in values.items():
+ % if name == "type" or name == "skipOn":
+<% continue %>\
+ % endif
+ % if valueType == "string":
+ std::string("${value}"),
+ % elif valueType == "bool":
+<% value = str(value).lower() %>\
+ ${value},
+ % else:
+ ${value},
+ % endif
+ % endfor
+ }
+ },
+ % endfor
+ }}},
+ % endfor
+ % endif
+ }},
+ % endfor
+ },
+}},
+ % endif
+% endfor
+};
+
+} // namespace sensor
+} // namespace ipmi
diff --git a/selutility.cpp b/selutility.cpp
new file mode 100644
index 0000000..944b21c
--- /dev/null
+++ b/selutility.cpp
@@ -0,0 +1,411 @@
+#include "config.h"
+
+#include "selutility.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <charconv>
+#include <chrono>
+#include <filesystem>
+#include <vector>
+
+extern const ipmi::sensor::InvObjectIDMap invSensors;
+using namespace phosphor::logging;
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+namespace
+{
+
+constexpr auto systemEventRecord = 0x02;
+constexpr auto generatorID = 0x2000;
+constexpr auto eventMsgRevision = 0x04;
+constexpr auto assertEvent = 0x00;
+constexpr auto deassertEvent = 0x80;
+constexpr auto selDataSize = 3;
+constexpr auto oemCDDataSize = 9;
+constexpr auto oemEFDataSize = 13;
+
+constexpr auto propAdditionalData = "AdditionalData";
+constexpr auto propResolved = "Resolved";
+
+constexpr auto strEventDir = "EVENT_DIR";
+constexpr auto strGenerateId = "GENERATOR_ID";
+constexpr auto strRecordType = "RECORD_TYPE";
+constexpr auto strSensorData = "SENSOR_DATA";
+constexpr auto strSensorPath = "SENSOR_PATH";
+
+} // namespace
+
+namespace ipmi
+{
+
+namespace sel
+{
+
+namespace internal
+{
+
+inline bool isRecordOEM(uint8_t recordType)
+{
+ return recordType != systemEventRecord;
+}
+
+using entryDataMap = std::map<PropertyName, PropertyType>;
+
+int convert(const std::string_view& str, int base = 10)
+{
+ int ret = 0;
+ std::from_chars(str.data(), str.data() + str.size(), ret, base);
+ return ret;
+}
+
+// Convert the string to a vector of uint8_t, where the str is formatted as hex
+std::vector<uint8_t> convertVec(const std::string_view& str)
+{
+ std::vector<uint8_t> ret;
+ auto len = str.size() / 2;
+ ret.reserve(len);
+ for (size_t i = 0; i < len; ++i)
+ {
+ ret.emplace_back(
+ static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
+ }
+ return ret;
+}
+
+/** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
+void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
+ const AdditionalData& data, GetSELEntryResponse& record)
+{
+ auto dataIter = data.find(strSensorData);
+ assert(dataIter != data.end());
+ auto sensorData = convertVec(dataIter->second);
+ if (recordType >= 0xC0 && recordType < 0xE0)
+ {
+ record.event.oemCD.timeStamp = static_cast<uint32_t>(
+ std::chrono::duration_cast<std::chrono::seconds>(timestamp)
+ .count());
+ record.event.oemCD.recordType = recordType;
+ // The ManufactureID and OEM Defined are packed in the sensor data
+ // Fill the 9 bytes of Manufacture ID and oemDefined
+ memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
+ std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
+ }
+ else if (recordType >= 0xE0)
+ {
+ record.event.oemEF.recordType = recordType;
+ // The remaining 13 bytes are the OEM Defined data
+ memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
+ std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
+ }
+}
+
+void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
+ const AdditionalData& data, const entryDataMap&,
+ GetSELEntryResponse& record)
+{
+ if (recordType != systemEventRecord)
+ {
+ lg2::error("Invalid recordType");
+ elog<InternalFailure>();
+ }
+
+ // Default values when there is no matched sensor
+ record.event.eventRecord.sensorType = 0;
+ record.event.eventRecord.sensorNum = 0xFF;
+ record.event.eventRecord.eventType = 0;
+
+ auto iter = data.find(strSensorPath);
+ assert(iter != data.end());
+ const auto& sensorPath = iter->second;
+ auto sensorIter = invSensors.find(sensorPath);
+
+ if (sensorIter != invSensors.end())
+ {
+ // There is a matched sensor
+ record.event.eventRecord.sensorType = sensorIter->second.sensorType;
+ record.event.eventRecord.sensorNum = sensorIter->second.sensorID;
+
+ iter = data.find(strEventDir);
+ assert(iter != data.end());
+ auto eventDir = static_cast<uint8_t>(convert(iter->second));
+ uint8_t assert = eventDir ? assertEvent : deassertEvent;
+ record.event.eventRecord.eventType =
+ assert | sensorIter->second.eventReadingType;
+ }
+ record.event.eventRecord.recordType = recordType;
+ record.event.eventRecord.timeStamp = static_cast<uint32_t>(
+ std::chrono::duration_cast<std::chrono::seconds>(timestamp).count());
+ iter = data.find(strGenerateId);
+ assert(iter != data.end());
+ record.event.eventRecord.generatorID =
+ static_cast<uint16_t>(convert(iter->second));
+ record.event.eventRecord.eventMsgRevision = eventMsgRevision;
+ iter = data.find(strSensorData);
+ assert(iter != data.end());
+ auto sensorData = convertVec(iter->second);
+ // The remaining 3 bytes are the sensor data
+ memcpy(&record.event.eventRecord.eventData1, sensorData.data(),
+ std::min(sensorData.size(), static_cast<size_t>(selDataSize)));
+}
+
+GetSELEntryResponse prepareSELEntry(
+ const std::string& objPath,
+ ipmi::sensor::InvObjectIDMap::const_iterator iter)
+{
+ GetSELEntryResponse record{};
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ auto service = ipmi::getService(bus, logEntryIntf, objPath);
+
+ // Read all the log entry properties.
+ auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
+ propIntf, "GetAll");
+ methodCall.append(logEntryIntf);
+
+ entryDataMap entryData;
+ try
+ {
+ auto reply = bus.call(methodCall);
+ reply.read(entryData);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Error in reading logging property entries: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ // Read Id from the log entry.
+ static constexpr auto propId = "Id";
+ auto iterId = entryData.find(propId);
+ if (iterId == entryData.end())
+ {
+ lg2::error("Error in reading Id of logging entry");
+ elog<InternalFailure>();
+ }
+
+ // Read Timestamp from the log entry.
+ static constexpr auto propTimeStamp = "Timestamp";
+ auto iterTimeStamp = entryData.find(propTimeStamp);
+ if (iterTimeStamp == entryData.end())
+ {
+ lg2::error("Error in reading Timestamp of logging entry");
+ elog<InternalFailure>();
+ }
+ std::chrono::milliseconds chronoTimeStamp(
+ std::get<uint64_t>(iterTimeStamp->second));
+
+ bool isFromSELLogger = false;
+
+ // The recordID are with the same offset between different types,
+ // so we are safe to set the recordID here
+ record.event.eventRecord.recordID =
+ static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
+
+ iterId = entryData.find(propAdditionalData);
+ if (iterId != entryData.end())
+ {
+ // Check if it's a SEL from phosphor-sel-logger which shall contain
+ // the record ID, etc
+ const auto& addData = std::get<AdditionalData>(iterId->second);
+ auto recordTypeIter = addData.find(strRecordType);
+ if (recordTypeIter != addData.end())
+ {
+ // It is a SEL from phosphor-sel-logger
+ isFromSELLogger = true;
+ }
+ else
+ {
+ // Not a SEL from phosphor-sel-logger, it shall have a valid
+ // invSensor
+ if (iter == invSensors.end())
+ {
+ lg2::error("System event sensor not found");
+ elog<InternalFailure>();
+ }
+ }
+ }
+
+ if (isFromSELLogger)
+ {
+ // It is expected to be a custom SEL entry
+ const auto& addData = std::get<AdditionalData>(iterId->second);
+ auto recordType =
+ static_cast<uint8_t>(convert(addData.find(strRecordType)->second));
+ auto isOEM = isRecordOEM(recordType);
+ if (isOEM)
+ {
+ constructOEMSEL(recordType, chronoTimeStamp, addData, record);
+ }
+ else
+ {
+ constructSEL(recordType, chronoTimeStamp, addData, entryData,
+ record);
+ }
+ }
+ else
+ {
+ record.event.eventRecord.timeStamp = static_cast<uint32_t>(
+ std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
+ .count());
+
+ record.event.eventRecord.recordType = systemEventRecord;
+ record.event.eventRecord.generatorID = generatorID;
+ record.event.eventRecord.eventMsgRevision = eventMsgRevision;
+
+ record.event.eventRecord.sensorType = iter->second.sensorType;
+ record.event.eventRecord.sensorNum = iter->second.sensorID;
+ record.event.eventRecord.eventData1 = iter->second.eventOffset;
+
+ // Read Resolved from the log entry.
+ auto iterResolved = entryData.find(propResolved);
+ if (iterResolved == entryData.end())
+ {
+ lg2::error("Error in reading Resolved field of logging entry");
+ elog<InternalFailure>();
+ }
+
+ // Evaluate if the event is assertion or deassertion event
+ if (std::get<bool>(iterResolved->second))
+ {
+ record.event.eventRecord.eventType =
+ deassertEvent | iter->second.eventReadingType;
+ }
+ else
+ {
+ record.event.eventRecord.eventType = iter->second.eventReadingType;
+ }
+ }
+
+ return record;
+}
+
+} // namespace internal
+
+GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ static constexpr auto assocIntf =
+ "xyz.openbmc_project.Association.Definitions";
+ static constexpr auto assocProp = "Associations";
+
+ std::vector<ipmi::Association> assocs;
+ try
+ {
+ auto service = ipmi::getService(bus, assocIntf, objPath);
+ auto propValue =
+ ipmi::getDbusProperty(bus, service, objPath, assocIntf, assocProp);
+ assocs = std::get<std::vector<ipmi::Association>>(propValue);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Error in reading Associations interface: {ERROR}", "ERROR",
+ e);
+ elog<InternalFailure>();
+ }
+
+ /*
+ * Check if the log entry has any callout associations, if there is a
+ * callout association try to match the inventory path to the corresponding
+ * IPMI sensor.
+ */
+ for (const auto& item : assocs)
+ {
+ if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
+ {
+ auto iter = invSensors.find(std::get<2>(item));
+ if (iter == invSensors.end())
+ {
+ iter = invSensors.find(BOARD_SENSOR);
+ if (iter == invSensors.end())
+ {
+ lg2::error("Motherboard sensor not found");
+ elog<InternalFailure>();
+ }
+ }
+
+ return internal::prepareSELEntry(objPath, iter);
+ }
+ }
+
+ // If there are no callout associations link the log entry to system event
+ // sensor
+ auto iter = invSensors.find(SYSTEM_SENSOR);
+ return internal::prepareSELEntry(objPath, iter);
+}
+
+std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ static constexpr auto propTimeStamp = "Timestamp";
+
+ uint64_t timeStamp;
+ try
+ {
+ auto service = ipmi::getService(bus, logEntryIntf, objPath);
+ auto propValue = ipmi::getDbusProperty(bus, service, objPath,
+ logEntryIntf, propTimeStamp);
+ timeStamp = std::get<uint64_t>(propValue);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Error in reading Timestamp from Entry interface: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ std::chrono::milliseconds chronoTimeStamp(timeStamp);
+
+ return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
+}
+
+void readLoggingObjectPaths(ObjectPaths& paths)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ auto depth = 0;
+ paths.clear();
+
+ auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
+ mapperIntf, "GetSubTreePaths");
+ mapperCall.append(logBasePath);
+ mapperCall.append(depth);
+ mapperCall.append(ObjectPaths({logEntryIntf}));
+
+ try
+ {
+ auto reply = bus.call(mapperCall);
+ reply.read(paths);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ if (strcmp(e.name(),
+ "xyz.openbmc_project.Common.Error.ResourceNotFound"))
+ {
+ throw;
+ }
+ }
+
+ std::sort(paths.begin(), paths.end(),
+ [](const std::string& a, const std::string& b) {
+ namespace fs = std::filesystem;
+ fs::path pathA(a);
+ fs::path pathB(b);
+ auto idA = std::stoul(pathA.filename().string());
+ auto idB = std::stoul(pathB.filename().string());
+
+ return idA < idB;
+ });
+}
+
+} // namespace sel
+
+} // namespace ipmi
diff --git a/selutility.hpp b/selutility.hpp
new file mode 100644
index 0000000..c6ba6b0
--- /dev/null
+++ b/selutility.hpp
@@ -0,0 +1,210 @@
+#pragma once
+
+#include <ipmid/types.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <chrono>
+#include <cstdint>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+namespace ipmi
+{
+
+namespace sel
+{
+
+static constexpr auto mapperBusName = "xyz.openbmc_project.ObjectMapper";
+static constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
+static constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+
+static constexpr auto logWatchPath = "/xyz/openbmc_project/logging";
+static constexpr auto logBasePath = "/xyz/openbmc_project/logging/entry";
+static constexpr auto logEntryIntf = "xyz.openbmc_project.Logging.Entry";
+static constexpr auto logDeleteIntf = "xyz.openbmc_project.Object.Delete";
+
+static constexpr auto logObj = "/xyz/openbmc_project/logging";
+static constexpr auto logIntf = "xyz.openbmc_project.Collection.DeleteAll";
+static constexpr auto logDeleteAllMethod = "DeleteAll";
+
+static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+
+using ObjectPaths = std::vector<std::string>;
+using PropertyName = std::string;
+using Resolved = bool;
+using Id = uint32_t;
+using Timestamp = uint64_t;
+using Message = std::string;
+using AdditionalData = std::map<std::string, std::string>;
+using PropertyType =
+ std::variant<Resolved, Id, Timestamp, Message, AdditionalData>;
+
+static constexpr auto selVersion = 0x51;
+static constexpr auto invalidTimeStamp = 0xFFFFFFFF;
+
+static constexpr auto firstEntry = 0x0000;
+static constexpr auto lastEntry = 0xFFFF;
+static constexpr auto entireRecord = 0xFF;
+static constexpr auto selRecordSize = 16;
+
+namespace operationSupport
+{
+static constexpr bool overflow = false;
+static constexpr bool deleteSel = true;
+static constexpr bool partialAddSelEntry = false;
+static constexpr bool reserveSel = true;
+static constexpr bool getSelAllocationInfo = false;
+} // namespace operationSupport
+
+/** @struct GetSELEntryRequest
+ *
+ * IPMI payload for Get SEL Entry command request.
+ */
+struct GetSELEntryRequest
+{
+ uint16_t reservationID; //!< Reservation ID.
+ uint16_t selRecordID; //!< SEL Record ID.
+ uint8_t offset; //!< Offset into record.
+ uint8_t readLength; //!< Bytes to read.
+} __attribute__((packed));
+
+constexpr size_t SELRecordLength = 16;
+
+/** @struct SELEventRecord
+ *
+ * IPMI SEL Event Record
+ */
+struct SELEventRecord
+{
+ uint16_t recordID; //!< Record ID.
+ uint8_t recordType; //!< Record Type.
+ uint32_t timeStamp; //!< Timestamp.
+ uint16_t generatorID; //!< Generator ID.
+ uint8_t eventMsgRevision; //!< Event Message Revision.
+ uint8_t sensorType; //!< Sensor Type.
+ uint8_t sensorNum; //!< Sensor Number.
+ uint8_t eventType; //!< Event Dir | Event Type.
+ uint8_t eventData1; //!< Event Data 1.
+ uint8_t eventData2; //!< Event Data 2.
+ uint8_t eventData3; //!< Event Data 3.
+} __attribute__((packed));
+
+static_assert(sizeof(SELEventRecord) == SELRecordLength);
+
+/** @struct SELOEMRecordTypeCD
+ *
+ * IPMI SEL OEM Record - Type C0h-DFh
+ */
+struct SELOEMRecordTypeCD
+{
+ uint16_t recordID; //!< Record ID.
+ uint8_t recordType; //!< Record Type.
+ uint32_t timeStamp; //!< Timestamp.
+ uint8_t manufacturerID[3]; //!< Manufacturer ID.
+ uint8_t oemDefined[6]; //!< OEM Defined data.
+} __attribute__((packed));
+
+static_assert(sizeof(SELOEMRecordTypeCD) == SELRecordLength);
+
+/** @struct SELOEMRecordTypeEF
+ *
+ * IPMI SEL OEM Record - Type E0h-FFh
+ */
+struct SELOEMRecordTypeEF
+{
+ uint16_t recordID; //!< Record ID.
+ uint8_t recordType; //!< Record Type.
+ uint8_t oemDefined[13]; //!< OEM Defined data.
+} __attribute__((packed));
+
+static_assert(sizeof(SELOEMRecordTypeEF) == SELRecordLength);
+
+union SELEventRecordFormat
+{
+ SELEventRecord eventRecord;
+ SELOEMRecordTypeCD oemCD;
+ SELOEMRecordTypeEF oemEF;
+};
+
+/** @struct GetSELEntryResponse
+ *
+ * IPMI payload for Get SEL Entry command response.
+ */
+struct GetSELEntryResponse
+{
+ uint16_t nextRecordID; //!< Next RecordID.
+ SELEventRecordFormat event; // !< The Event Record.
+} __attribute__((packed));
+
+static_assert(sizeof(GetSELEntryResponse) ==
+ SELRecordLength + sizeof(uint16_t));
+
+static constexpr auto initiateErase = 0xAA;
+static constexpr auto getEraseStatus = 0x00;
+static constexpr auto eraseComplete = 0x01;
+
+/** @brief Convert logging entry to SEL
+ *
+ * @param[in] objPath - DBUS object path of the logging entry.
+ *
+ * @return On success return the response of Get SEL entry command.
+ */
+GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath);
+
+/** @brief Get the timestamp of the log entry
+ *
+ * @param[in] objPath - DBUS object path of the logging entry.
+ *
+ * @return On success return the timestamp of the log entry as number of
+ * seconds from epoch.
+ */
+std::chrono::seconds getEntryTimeStamp(const std::string& objPath);
+
+/** @brief Read the logging entry object paths
+ *
+ * This API would read the logging dbus logging entry object paths and sorting
+ * the filename in the numeric order. The paths is cleared before populating
+ * the object paths.
+ *
+ * @param[in,out] paths - sorted list of logging entry object paths.
+ *
+ * @note This function is invoked when the Get SEL Info command or the Delete
+ * SEL entry command is invoked. The Get SEL Entry command is preceded
+ * typically by Get SEL Info command, so readLoggingObjectPaths is not
+ * invoked before each Get SEL entry command.
+ */
+void readLoggingObjectPaths(ObjectPaths& paths);
+
+template <typename T>
+std::string toHexStr(const T& data)
+{
+ std::stringstream stream;
+ stream << std::hex << std::uppercase << std::setfill('0');
+ for (const auto& v : data)
+ {
+ stream << std::setw(2) << static_cast<int>(v);
+ }
+ return stream.str();
+}
+namespace internal
+{
+
+/** @brief Convert logging entry to SEL event record
+ *
+ * @param[in] objPath - DBUS object path of the logging entry.
+ * @param[in] iter - Iterator to the sensor data corresponding to the logging
+ * entry
+ *
+ * @return On success return the SEL event record, throw an exception in case
+ * of failure.
+ */
+GetSELEntryResponse prepareSELEntry(
+ const std::string& objPath,
+ ipmi::sensor::InvObjectIDMap::const_iterator iter);
+
+} // namespace internal
+
+} // namespace sel
+
+} // namespace ipmi
diff --git a/sensordatahandler.cpp b/sensordatahandler.cpp
new file mode 100644
index 0000000..3505dfb
--- /dev/null
+++ b/sensordatahandler.cpp
@@ -0,0 +1,419 @@
+#include "sensordatahandler.hpp"
+
+#include "sensorhandler.hpp"
+
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <bitset>
+#include <filesystem>
+#include <optional>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+using namespace phosphor::logging;
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData)
+{
+ Assertion assertionStates =
+ (static_cast<Assertion>(cmdData.assertOffset8_14)) << 8 |
+ cmdData.assertOffset0_7;
+ Deassertion deassertionStates =
+ (static_cast<Deassertion>(cmdData.deassertOffset8_14)) << 8 |
+ cmdData.deassertOffset0_7;
+ return std::make_pair(assertionStates, deassertionStates);
+}
+
+ipmi_ret_t updateToDbus(IpmiUpdateData& msg)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ try
+ {
+ auto serviceResponseMsg = bus.call(msg);
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Error in D-Bus call: {ERROR}", "ERROR", e);
+ commit<InternalFailure>();
+ return IPMI_CC_UNSPECIFIED_ERROR;
+ }
+ return IPMI_CC_OK;
+}
+
+namespace get
+{
+
+SensorName nameParentLeaf(const Info& sensorInfo)
+{
+ const auto pos = sensorInfo.sensorPath.find_last_of('/');
+ const auto leaf = sensorInfo.sensorPath.substr(pos + 1);
+
+ const auto remaining = sensorInfo.sensorPath.substr(0, pos);
+
+ const auto parentPos = remaining.find_last_of('/');
+ auto parent = remaining.substr(parentPos + 1);
+
+ parent += "_" + leaf;
+ return parent;
+}
+
+GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
+ const InstancePath& path,
+ const DbusInterface& interface)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ GetSensorResponse response{};
+
+ enableScanning(&response);
+
+ auto service = ipmi::getService(bus, interface, path);
+
+ const auto& interfaceList = sensorInfo.propertyInterfaces;
+
+ for (const auto& interface : interfaceList)
+ {
+ for (const auto& property : interface.second)
+ {
+ auto propValue = ipmi::getDbusProperty(
+ bus, service, path, interface.first, property.first);
+
+ for (const auto& value : std::get<OffsetValueMap>(property.second))
+ {
+ if (propValue == value.second.assert)
+ {
+ setOffset(value.first, &response);
+ break;
+ }
+ }
+ }
+ }
+
+ return response;
+}
+
+GetSensorResponse mapDbusToEventdata2(const Info& sensorInfo)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ GetSensorResponse response{};
+
+ enableScanning(&response);
+
+ auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+ sensorInfo.sensorPath);
+
+ const auto& interfaceList = sensorInfo.propertyInterfaces;
+
+ for (const auto& interface : interfaceList)
+ {
+ for (const auto& property : interface.second)
+ {
+ auto propValue =
+ ipmi::getDbusProperty(bus, service, sensorInfo.sensorPath,
+ interface.first, property.first);
+
+ for (const auto& value : std::get<OffsetValueMap>(property.second))
+ {
+ if (propValue == value.second.assert)
+ {
+ setReading(value.first, &response);
+ break;
+ }
+ }
+ }
+ }
+
+ return response;
+}
+
+#ifndef FEATURE_SENSORS_CACHE
+GetSensorResponse assertion(const Info& sensorInfo)
+{
+ return mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
+ sensorInfo.sensorInterface);
+}
+
+GetSensorResponse eventdata2(const Info& sensorInfo)
+{
+ return mapDbusToEventdata2(sensorInfo);
+}
+#else
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& /*properties*/)
+{
+ // The assertion may contain multiple properties
+ // So we have to get the properties from DBus anyway
+ auto response = mapDbusToAssertion(sensorInfo, sensorInfo.sensorPath,
+ sensorInfo.sensorInterface);
+
+ if (!sensorCacheMap[id].has_value())
+ {
+ sensorCacheMap[id] = SensorData{};
+ }
+ sensorCacheMap[id]->response = response;
+ return response;
+}
+
+std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& /*properties*/)
+{
+ // The eventdata2 may contain multiple properties
+ // So we have to get the properties from DBus anyway
+ auto response = mapDbusToEventdata2(sensorInfo);
+
+ if (!sensorCacheMap[id].has_value())
+ {
+ sensorCacheMap[id] = SensorData{};
+ }
+ sensorCacheMap[id]->response = response;
+ return response;
+}
+
+#endif // FEATURE_SENSORS_CACHE
+
+} // namespace get
+
+namespace set
+{
+
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+ const std::string& sensorPath,
+ const std::string& command,
+ const std::string& sensorInterface)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ using namespace std::string_literals;
+
+ auto dbusService = getService(bus, sensorInterface, sensorPath);
+
+ return bus.new_method_call(dbusService.c_str(), sensorPath.c_str(),
+ updateInterface.c_str(), command.c_str());
+}
+
+ipmi_ret_t eventdata(const SetSensorReadingReq&, const Info& sensorInfo,
+ uint8_t data)
+{
+ auto msg =
+ makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+ "Set", sensorInfo.sensorInterface);
+
+ const auto& interface = sensorInfo.propertyInterfaces.begin();
+ msg.append(interface->first);
+ for (const auto& property : interface->second)
+ {
+ msg.append(property.first);
+ const auto& iter = std::get<OffsetValueMap>(property.second).find(data);
+ if (iter == std::get<OffsetValueMap>(property.second).end())
+ {
+ lg2::error("Invalid event data");
+ return IPMI_CC_PARM_OUT_OF_RANGE;
+ }
+ msg.append(iter->second.assert);
+ }
+ return updateToDbus(msg);
+}
+
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
+{
+ std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
+ std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
+ auto bothSet = assertionSet ^ deassertionSet;
+
+ const auto& interface = sensorInfo.propertyInterfaces.begin();
+
+ for (const auto& property : interface->second)
+ {
+ std::optional<Value> tmp;
+ for (const auto& value : std::get<OffsetValueMap>(property.second))
+ {
+ if (bothSet.size() <= value.first || !bothSet.test(value.first))
+ {
+ // A BIOS shouldn't do this but ignore if they do.
+ continue;
+ }
+
+ if (assertionSet.test(value.first))
+ {
+ tmp = value.second.assert;
+ break;
+ }
+ if (deassertionSet.test(value.first))
+ {
+ tmp = value.second.deassert;
+ break;
+ }
+ }
+
+ if (tmp)
+ {
+ auto msg = makeDbusMsg("org.freedesktop.DBus.Properties",
+ sensorInfo.sensorPath, "Set",
+ sensorInfo.sensorInterface);
+ msg.append(interface->first);
+ msg.append(property.first);
+ msg.append(*tmp);
+
+ auto rc = updateToDbus(msg);
+ if (rc)
+ {
+ return rc;
+ }
+ }
+ }
+
+ return IPMI_CC_OK;
+}
+
+} // namespace set
+
+namespace notify
+{
+
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+ const std::string&, const std::string& command,
+ const std::string&)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ using namespace std::string_literals;
+
+ static const auto dbusPath = "/xyz/openbmc_project/inventory"s;
+ std::string dbusService = ipmi::getService(bus, updateInterface, dbusPath);
+
+ return bus.new_method_call(dbusService.c_str(), dbusPath.c_str(),
+ updateInterface.c_str(), command.c_str());
+}
+
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo)
+{
+ auto msg = makeDbusMsg(sensorInfo.sensorInterface, sensorInfo.sensorPath,
+ "Notify", sensorInfo.sensorInterface);
+
+ std::bitset<16> assertionSet(getAssertionSet(cmdData).first);
+ std::bitset<16> deassertionSet(getAssertionSet(cmdData).second);
+ ipmi::sensor::ObjectMap objects;
+ ipmi::sensor::InterfaceMap interfaces;
+ for (const auto& interface : sensorInfo.propertyInterfaces)
+ {
+ // An interface with no properties - It is possible that the sensor
+ // object on DBUS implements a DBUS interface with no properties.
+ // Make sure we add the interface to the list if interfaces on the
+ // object with an empty property map.
+ if (interface.second.empty())
+ {
+ interfaces.emplace(interface.first, ipmi::sensor::PropertyMap{});
+ continue;
+ }
+ // For a property like functional state the result will be
+ // calculated based on the true value of all conditions.
+ for (const auto& property : interface.second)
+ {
+ ipmi::sensor::PropertyMap props;
+ bool valid = false;
+ auto result = true;
+ for (const auto& value : std::get<OffsetValueMap>(property.second))
+ {
+ if (assertionSet.test(value.first))
+ {
+ // Skip update if skipOn is ASSERT
+ if (SkipAssertion::ASSERT == value.second.skip)
+ {
+ return IPMI_CC_OK;
+ }
+ result = result && std::get<bool>(value.second.assert);
+ valid = true;
+ }
+ else if (deassertionSet.test(value.first))
+ {
+ // Skip update if skipOn is DEASSERT
+ if (SkipAssertion::DEASSERT == value.second.skip)
+ {
+ return IPMI_CC_OK;
+ }
+ result = result && std::get<bool>(value.second.deassert);
+ valid = true;
+ }
+ }
+ for (const auto& value :
+ std::get<PreReqOffsetValueMap>(property.second))
+ {
+ if (assertionSet.test(value.first))
+ {
+ result = result && std::get<bool>(value.second.assert);
+ }
+ else if (deassertionSet.test(value.first))
+ {
+ result = result && std::get<bool>(value.second.deassert);
+ }
+ }
+ if (valid)
+ {
+ props.emplace(property.first, result);
+ interfaces.emplace(interface.first, std::move(props));
+ }
+ }
+ }
+
+ objects.emplace(sensorInfo.sensorPath, std::move(interfaces));
+ msg.append(std::move(objects));
+ return updateToDbus(msg);
+}
+
+} // namespace notify
+
+namespace inventory
+{
+
+namespace get
+{
+
+#ifndef FEATURE_SENSORS_CACHE
+
+GetSensorResponse assertion(const Info& sensorInfo)
+{
+ namespace fs = std::filesystem;
+
+ fs::path path{ipmi::sensor::inventoryRoot};
+ path += sensorInfo.sensorPath;
+
+ return ipmi::sensor::get::mapDbusToAssertion(
+ sensorInfo, path.string(),
+ sensorInfo.propertyInterfaces.begin()->first);
+}
+
+#else
+
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& /*properties*/)
+{
+ // The assertion may contain multiple properties
+ // So we have to get the properties from DBus anyway
+ namespace fs = std::filesystem;
+
+ fs::path path{ipmi::sensor::inventoryRoot};
+ path += sensorInfo.sensorPath;
+
+ auto response = ipmi::sensor::get::mapDbusToAssertion(
+ sensorInfo, path.string(),
+ sensorInfo.propertyInterfaces.begin()->first);
+
+ if (!sensorCacheMap[id].has_value())
+ {
+ sensorCacheMap[id] = SensorData{};
+ }
+ sensorCacheMap[id]->response = response;
+ return response;
+}
+
+#endif
+
+} // namespace get
+
+} // namespace inventory
+} // namespace sensor
+} // namespace ipmi
diff --git a/sensordatahandler.hpp b/sensordatahandler.hpp
new file mode 100644
index 0000000..1cedda5
--- /dev/null
+++ b/sensordatahandler.hpp
@@ -0,0 +1,670 @@
+#pragma once
+
+#include "config.h"
+
+#include "sensorhandler.hpp"
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+
+#include <cmath>
+
+#ifdef FEATURE_SENSORS_CACHE
+
+extern ipmi::sensor::SensorCacheMap sensorCacheMap;
+
+// The signal's message type is 0x04 from DBus spec:
+// https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
+static constexpr auto msgTypeSignal = 0x04;
+
+#endif
+
+namespace ipmi
+{
+namespace sensor
+{
+
+using Assertion = uint16_t;
+using Deassertion = uint16_t;
+using AssertionSet = std::pair<Assertion, Deassertion>;
+using Service = std::string;
+using Path = std::string;
+using Interface = std::string;
+using ServicePath = std::pair<Path, Service>;
+using Interfaces = std::vector<Interface>;
+using MapperResponseType = std::map<Path, std::map<Service, Interfaces>>;
+using PropertyMap = ipmi::PropertyMap;
+
+using namespace phosphor::logging;
+
+/** @brief Make assertion set from input data
+ * @param[in] cmdData - Input sensor data
+ * @return pair of assertion and deassertion set
+ */
+AssertionSet getAssertionSet(const SetSensorReadingReq& cmdData);
+
+/** @brief send the message to DBus
+ * @param[in] msg - message to send
+ * @return failure status in IPMI error code
+ */
+ipmi_ret_t updateToDbus(IpmiUpdateData& msg);
+
+namespace get
+{
+
+/** @brief Populate sensor name from the D-Bus property associated with the
+ * sensor. In the example entry from the yaml, the name of the D-bus
+ * property "AttemptsLeft" is the sensor name.
+ *
+ * 0x07:
+ * sensorType: 195
+ * path: /xyz/openbmc_project/state/host0
+ * sensorReadingType: 0x6F
+ * serviceInterface: org.freedesktop.DBus.Properties
+ * readingType: readingAssertion
+ * sensorNamePattern: nameProperty
+ * interfaces:
+ * xyz.openbmc_project.Control.Boot.RebootAttempts:
+ * AttemptsLeft:
+ * Offsets:
+ * 0xFF:
+ * type: uint32_t
+ *
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameProperty(const Info& sensorInfo)
+{
+ return sensorInfo.propertyInterfaces.begin()->second.begin()->first;
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ * sensor. If the object path is /system/chassis/motherboard/dimm0 then
+ * the leaf dimm0 is considered as the sensor name.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameLeaf(const Info& sensorInfo)
+{
+ return sensorInfo.sensorPath.substr(
+ sensorInfo.sensorPath.find_last_of('/') + 1,
+ sensorInfo.sensorPath.length());
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ * sensor and the property.
+ * If the object path is /xyz/openbmc_project/inventory/Fan0 and
+ * the property is Present, the leaf Fan0 and the Property is
+ * joined to Fan0_Present as the sensor name.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return On success return the sensor name for the sensor.
+ */
+inline SensorName nameLeafProperty(const Info& sensorInfo)
+{
+ return nameLeaf(sensorInfo) + "_" + nameProperty(sensorInfo);
+}
+
+/** @brief Populate sensor name from the D-Bus object associated with the
+ * sensor. If the object path is /system/chassis/motherboard/cpu0/core0
+ * then the sensor name is cpu0_core0. The leaf and the parent is put
+ * together to get the sensor name.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return On success return the sensor name for the sensor.
+ */
+SensorName nameParentLeaf(const Info& sensorInfo);
+
+/**
+ * @brief Helper function to map the dbus info to sensor's assertion status
+ * for the get sensor reading command.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] path - Dbus object path.
+ * @param[in] interface - Dbus interface.
+ *
+ * @return Response for get sensor reading command.
+ */
+GetSensorResponse mapDbusToAssertion(const Info& sensorInfo,
+ const InstancePath& path,
+ const DbusInterface& interface);
+
+#ifndef FEATURE_SENSORS_CACHE
+/**
+ * @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ * reading command response.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return Response for get sensor reading command.
+ */
+GetSensorResponse assertion(const Info& sensorInfo);
+
+/**
+ * @brief Maps the Dbus info to the reading field in the Get sensor reading
+ * command response.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return Response for get sensor reading command.
+ */
+GetSensorResponse eventdata2(const Info& sensorInfo);
+
+/**
+ * @brief readingAssertion is a case where the entire assertion state field
+ * serves as the sensor value.
+ *
+ * @tparam T - type of the dbus property related to sensor.
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return Response for get sensor reading command.
+ */
+template <typename T>
+GetSensorResponse readingAssertion(const Info& sensorInfo)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ GetSensorResponse response{};
+
+ enableScanning(&response);
+
+ auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+ sensorInfo.sensorPath);
+
+ auto propValue = ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ sensorInfo.propertyInterfaces.begin()->first,
+ sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+
+ setAssertionBytes(static_cast<uint16_t>(std::get<T>(propValue)), &response);
+
+ return response;
+}
+
+/** @brief Map the Dbus info to the reading field in the Get sensor reading
+ * command response
+ *
+ * @tparam T - type of the dbus property related to sensor.
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return Response for get sensor reading command.
+ */
+template <typename T>
+GetSensorResponse readingData(const Info& sensorInfo)
+{
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+
+ GetSensorResponse response{};
+
+ enableScanning(&response);
+
+ auto service = ipmi::getService(bus, sensorInfo.sensorInterface,
+ sensorInfo.sensorPath);
+
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+ // Check the OperationalStatus interface for functional property
+ if (sensorInfo.propertyInterfaces.begin()->first ==
+ "xyz.openbmc_project.Sensor.Value")
+ {
+ bool functional = true;
+ try
+ {
+ auto funcValue = ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ "xyz.openbmc_project.State.Decorator.OperationalStatus",
+ "Functional");
+ functional = std::get<bool>(funcValue);
+ }
+ catch (...)
+ {
+ // No-op if Functional property could not be found since this
+ // check is only valid for Sensor.Value read for hwmonio
+ }
+ if (!functional)
+ {
+ throw SensorFunctionalError();
+ }
+ }
+#endif
+
+ auto propValue = ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ sensorInfo.propertyInterfaces.begin()->first,
+ sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+
+ double value = std::get<T>(propValue) *
+ std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
+ int32_t rawData =
+ (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
+
+ constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+ constexpr uint8_t signedDataFormat = 0x80;
+ // if sensorUnits1 [7:6] = 10b, sensor is signed
+ int32_t minClamp;
+ int32_t maxClamp;
+ if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+ {
+ minClamp = std::numeric_limits<int8_t>::lowest();
+ maxClamp = std::numeric_limits<int8_t>::max();
+ }
+ else
+ {
+ minClamp = std::numeric_limits<uint8_t>::lowest();
+ maxClamp = std::numeric_limits<uint8_t>::max();
+ }
+ setReading(static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp)),
+ &response);
+
+ if (!std::isfinite(value))
+ {
+ response.readingOrStateUnavailable = 1;
+ }
+
+ bool critAlarmHigh;
+ try
+ {
+ critAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ "xyz.openbmc_project.Sensor.Threshold.Critical",
+ "CriticalAlarmHigh"));
+ }
+ catch (const std::exception& e)
+ {
+ critAlarmHigh = false;
+ }
+ bool critAlarmLow;
+ try
+ {
+ critAlarmLow = std::get<bool>(ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ "xyz.openbmc_project.Sensor.Threshold.Critical",
+ "CriticalAlarmLow"));
+ }
+ catch (const std::exception& e)
+ {
+ critAlarmLow = false;
+ }
+ bool warningAlarmHigh;
+ try
+ {
+ warningAlarmHigh = std::get<bool>(ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ "xyz.openbmc_project.Sensor.Threshold.Warning",
+ "WarningAlarmHigh"));
+ }
+ catch (const std::exception& e)
+ {
+ warningAlarmHigh = false;
+ }
+ bool warningAlarmLow;
+ try
+ {
+ warningAlarmLow = std::get<bool>(ipmi::getDbusProperty(
+ bus, service, sensorInfo.sensorPath,
+ "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningAlarmLow"));
+ }
+ catch (const std::exception& e)
+ {
+ warningAlarmLow = false;
+ }
+ response.thresholdLevelsStates =
+ (static_cast<uint8_t>(critAlarmHigh) << 3) |
+ (static_cast<uint8_t>(critAlarmLow) << 2) |
+ (static_cast<uint8_t>(warningAlarmHigh) << 1) |
+ (static_cast<uint8_t>(warningAlarmLow));
+
+ return response;
+}
+
+#else
+
+/**
+ * @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ * reading command response.
+ *
+ * @param[in] id - The sensor id
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] msg - Dbus message from match callback.
+ *
+ * @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& properties);
+
+/**
+ * @brief Maps the Dbus info to the reading field in the Get sensor reading
+ * command response.
+ *
+ * @param[in] id - The sensor id
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] msg - Dbus message from match callback.
+ *
+ * @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> eventdata2(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& properties);
+
+/**
+ * @brief readingAssertion is a case where the entire assertion state field
+ * serves as the sensor value.
+ *
+ * @tparam T - type of the dbus property related to sensor.
+ * @param[in] id - The sensor id
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] msg - Dbus message from match callback.
+ *
+ * @return Response for get sensor reading command.
+ */
+template <typename T>
+std::optional<GetSensorResponse> readingAssertion(
+ uint8_t id, const Info& sensorInfo, const PropertyMap& properties)
+{
+ GetSensorResponse response{};
+ enableScanning(&response);
+
+ auto iter = properties.find(
+ sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+ if (iter == properties.end())
+ {
+ return {};
+ }
+
+ setAssertionBytes(static_cast<uint16_t>(std::get<T>(iter->second)),
+ &response);
+
+ if (!sensorCacheMap[id].has_value())
+ {
+ sensorCacheMap[id] = SensorData{};
+ }
+ sensorCacheMap[id]->response = response;
+ return response;
+}
+
+/** @brief Get sensor reading from the dbus message from match
+ *
+ * @tparam T - type of the dbus property related to sensor.
+ * @param[in] id - The sensor id
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] msg - Dbus message from match callback.
+ *
+ * @return Response for get sensor reading command.
+ */
+template <typename T>
+std::optional<GetSensorResponse> readingData(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& properties)
+{
+ auto iter = properties.find("Functional");
+ if (iter != properties.end())
+ {
+ sensorCacheMap[id]->functional = std::get<bool>(iter->second);
+ }
+ iter = properties.find("Available");
+ if (iter != properties.end())
+ {
+ sensorCacheMap[id]->available = std::get<bool>(iter->second);
+ }
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+ if (sensorCacheMap[id])
+ {
+ if (!sensorCacheMap[id]->functional)
+ {
+ throw SensorFunctionalError();
+ }
+ }
+#endif
+
+ GetSensorResponse response{};
+
+ enableScanning(&response);
+
+ iter = properties.find(
+ sensorInfo.propertyInterfaces.begin()->second.begin()->first);
+ if (iter == properties.end())
+ {
+ return {};
+ }
+
+ double value = std::get<T>(iter->second) *
+ std::pow(10, sensorInfo.scale - sensorInfo.exponentR);
+ int32_t rawData =
+ (value - sensorInfo.scaledOffset) / sensorInfo.coefficientM;
+
+ constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+ constexpr uint8_t signedDataFormat = 0x80;
+ // if sensorUnits1 [7:6] = 10b, sensor is signed
+ if ((sensorInfo.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+ {
+ if (rawData > std::numeric_limits<int8_t>::max() ||
+ rawData < std::numeric_limits<int8_t>::lowest())
+ {
+ lg2::error("Value out of range");
+ throw std::out_of_range("Value out of range");
+ }
+ setReading(static_cast<int8_t>(rawData), &response);
+ }
+ else
+ {
+ if (rawData > std::numeric_limits<uint8_t>::max() ||
+ rawData < std::numeric_limits<uint8_t>::lowest())
+ {
+ lg2::error("Value out of range");
+ throw std::out_of_range("Value out of range");
+ }
+ setReading(static_cast<uint8_t>(rawData), &response);
+ }
+
+ if (!std::isfinite(value))
+ {
+ response.readingOrStateUnavailable = 1;
+ }
+
+ if (!sensorCacheMap[id].has_value())
+ {
+ sensorCacheMap[id] = SensorData{};
+ }
+ sensorCacheMap[id]->response = response;
+
+ return response;
+}
+
+#endif // FEATURE_SENSORS_CACHE
+
+} // namespace get
+
+namespace set
+{
+
+/** @brief Make a DBus message for a Dbus call
+ * @param[in] updateInterface - Interface name
+ * @param[in] sensorPath - Path of the sensor
+ * @param[in] command - command to be executed
+ * @param[in] sensorInterface - DBus interface of sensor
+ * @return a dbus message
+ */
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+ const std::string& sensorPath,
+ const std::string& command,
+ const std::string& sensorInterface);
+
+/** @brief Update d-bus based on assertion type sensor data
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo);
+
+/** @brief Update d-bus based on a reading assertion
+ * @tparam T - type of d-bus property mapping this sensor
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+template <typename T>
+ipmi_ret_t readingAssertion(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo)
+{
+ auto msg =
+ makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+ "Set", sensorInfo.sensorInterface);
+
+ const auto& interface = sensorInfo.propertyInterfaces.begin();
+ msg.append(interface->first);
+ for (const auto& property : interface->second)
+ {
+ msg.append(property.first);
+ std::variant<T> value = static_cast<T>(
+ (cmdData.assertOffset8_14 << 8) | cmdData.assertOffset0_7);
+ msg.append(value);
+ }
+ return updateToDbus(msg);
+}
+
+/** @brief Update d-bus based on a discrete reading
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return an IPMI error code
+ */
+template <typename T>
+ipmi_ret_t readingData(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo)
+{
+ T raw_value = (sensorInfo.coefficientM * cmdData.reading) +
+ sensorInfo.scaledOffset;
+
+ raw_value *= std::pow(10, sensorInfo.exponentR - sensorInfo.scale);
+
+ auto msg =
+ makeDbusMsg("org.freedesktop.DBus.Properties", sensorInfo.sensorPath,
+ "Set", sensorInfo.sensorInterface);
+
+ const auto& interface = sensorInfo.propertyInterfaces.begin();
+ msg.append(interface->first);
+
+ for (const auto& property : interface->second)
+ {
+ msg.append(property.first);
+ std::variant<T> value = raw_value;
+ msg.append(value);
+ }
+ return updateToDbus(msg);
+}
+
+/** @brief Update d-bus based on eventdata type sensor data
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+ipmi_ret_t eventdata(const SetSensorReadingReq& cmdData, const Info& sensorInfo,
+ uint8_t data);
+
+/** @brief Update d-bus based on eventdata1 type sensor data
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata1(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo)
+{
+ return eventdata(cmdData, sensorInfo, cmdData.eventData1);
+}
+
+/** @brief Update d-bus based on eventdata2 type sensor data
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata2(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo)
+{
+ return eventdata(cmdData, sensorInfo, cmdData.eventData2);
+}
+
+/** @brief Update d-bus based on eventdata3 type sensor data
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+inline ipmi_ret_t eventdata3(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo)
+{
+ return eventdata(cmdData, sensorInfo, cmdData.eventData3);
+}
+
+} // namespace set
+
+namespace notify
+{
+
+/** @brief Make a DBus message for a Dbus call
+ * @param[in] updateInterface - Interface name
+ * @param[in] sensorPath - Path of the sensor
+ * @param[in] command - command to be executed
+ * @param[in] sensorInterface - DBus interface of sensor
+ * @return a dbus message
+ */
+IpmiUpdateData makeDbusMsg(const std::string& updateInterface,
+ const std::string& sensorPath,
+ const std::string& command,
+ const std::string& sensorInterface);
+
+/** @brief Update d-bus based on assertion type sensor data
+ * @param[in] interfaceMap - sensor interface
+ * @param[in] cmdData - input sensor data
+ * @param[in] sensorInfo - sensor d-bus info
+ * @return a IPMI error code
+ */
+ipmi_ret_t assertion(const SetSensorReadingReq& cmdData,
+ const Info& sensorInfo);
+
+} // namespace notify
+
+namespace inventory
+{
+
+namespace get
+{
+
+#ifndef FEATURE_SENSORS_CACHE
+
+/**
+ * @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ * reading command response.
+ *
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ *
+ * @return Response for get sensor reading command.
+ */
+GetSensorResponse assertion(const Info& sensorInfo);
+
+#else
+
+/**
+ * @brief Map the Dbus info to sensor's assertion status in the Get sensor
+ * reading command response.
+ *
+ * @param[in] id - The sensor id
+ * @param[in] sensorInfo - Dbus info related to sensor.
+ * @param[in] msg - Dbus message from match callback.
+ *
+ * @return Response for get sensor reading command.
+ */
+std::optional<GetSensorResponse> assertion(uint8_t id, const Info& sensorInfo,
+ const PropertyMap& properties);
+
+#endif
+
+} // namespace get
+
+} // namespace inventory
+} // namespace sensor
+} // namespace ipmi
diff --git a/sensorhandler.cpp b/sensorhandler.cpp
new file mode 100644
index 0000000..134eaa6
--- /dev/null
+++ b/sensorhandler.cpp
@@ -0,0 +1,1588 @@
+#include "config.h"
+
+#include "sensorhandler.hpp"
+
+#include "fruread.hpp"
+
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+
+#include <bitset>
+#include <cmath>
+#include <cstring>
+#include <set>
+
+static constexpr uint8_t fruInventoryDevice = 0x10;
+static constexpr uint8_t IPMIFruInventory = 0x02;
+static constexpr uint8_t BMCTargetAddress = 0x20;
+
+extern int updateSensorRecordFromSSRAESC(const void*);
+extern sd_bus* bus;
+
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+
+extern const FruMap frus;
+
+using namespace phosphor::logging;
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+void registerNetFnSenFunctions() __attribute__((constructor));
+
+struct sensorTypemap_t
+{
+ uint8_t number;
+ uint8_t typecode;
+ char dbusname[32];
+};
+
+sensorTypemap_t g_SensorTypeMap[] = {
+
+ {0x01, 0x6F, "Temp"},
+ {0x0C, 0x6F, "DIMM"},
+ {0x0C, 0x6F, "MEMORY_BUFFER"},
+ {0x07, 0x6F, "PROC"},
+ {0x07, 0x6F, "CORE"},
+ {0x07, 0x6F, "CPU"},
+ {0x0F, 0x6F, "BootProgress"},
+ {0xe9, 0x09, "OccStatus"}, // E9 is an internal mapping to handle sensor
+ // type code os 0x09
+ {0xC3, 0x6F, "BootCount"},
+ {0x1F, 0x6F, "OperatingSystemStatus"},
+ {0x12, 0x6F, "SYSTEM_EVENT"},
+ {0xC7, 0x03, "SYSTEM"},
+ {0xC7, 0x03, "MAIN_PLANAR"},
+ {0xC2, 0x6F, "PowerCap"},
+ {0x0b, 0xCA, "PowerSupplyRedundancy"},
+ {0xDA, 0x03, "TurboAllowed"},
+ {0xD8, 0xC8, "PowerSupplyDerating"},
+ {0xFF, 0x00, ""},
+};
+
+struct sensor_data_t
+{
+ uint8_t sennum;
+} __attribute__((packed));
+
+using SDRCacheMap = std::unordered_map<uint8_t, get_sdr::SensorDataFullRecord>;
+SDRCacheMap sdrCacheMap __attribute__((init_priority(101)));
+
+using SensorThresholdMap =
+ std::unordered_map<uint8_t, get_sdr::GetSensorThresholdsResponse>;
+SensorThresholdMap sensorThresholdMap __attribute__((init_priority(101)));
+
+#ifdef FEATURE_SENSORS_CACHE
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorAddedMatches
+ __attribute__((init_priority(101)));
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorUpdatedMatches
+ __attribute__((init_priority(101)));
+std::map<uint8_t, std::unique_ptr<sdbusplus::bus::match_t>> sensorRemovedMatches
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> sensorsOwnerMatch
+ __attribute__((init_priority(101)));
+
+ipmi::sensor::SensorCacheMap sensorCacheMap __attribute__((init_priority(101)));
+
+// It is needed to know which objects belong to which service, so that when a
+// service exits without interfacesRemoved signal, we could invaildate the cache
+// that is related to the service. It uses below two variables:
+// - idToServiceMap records which sensors are known to have a related service;
+// - serviceToIdMap maps a service to the sensors.
+using sensorIdToServiceMap = std::unordered_map<uint8_t, std::string>;
+sensorIdToServiceMap idToServiceMap __attribute__((init_priority(101)));
+
+using sensorServiceToIdMap = std::unordered_map<std::string, std::set<uint8_t>>;
+sensorServiceToIdMap serviceToIdMap __attribute__((init_priority(101)));
+
+static void fillSensorIdServiceMap(const std::string&,
+ const std::string& /*intf*/, uint8_t id,
+ const std::string& service)
+{
+ if (idToServiceMap.find(id) != idToServiceMap.end())
+ {
+ return;
+ }
+ idToServiceMap[id] = service;
+ serviceToIdMap[service].insert(id);
+}
+
+static void fillSensorIdServiceMap(const std::string& obj,
+ const std::string& intf, uint8_t id)
+{
+ if (idToServiceMap.find(id) != idToServiceMap.end())
+ {
+ return;
+ }
+ try
+ {
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ auto service = ipmi::getService(bus, intf, obj);
+ idToServiceMap[id] = service;
+ serviceToIdMap[service].insert(id);
+ }
+ catch (...)
+ {
+ // Ignore
+ }
+}
+
+void initSensorMatches()
+{
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ for (const auto& s : ipmi::sensor::sensors)
+ {
+ sensorAddedMatches.emplace(
+ s.first,
+ std::make_unique<sdbusplus::bus::match_t>(
+ bus, interfacesAdded() + argNpath(0, s.second.sensorPath),
+ [id = s.first, obj = s.second.sensorPath,
+ intf = s.second.propertyInterfaces.begin()->first](
+ auto& /*msg*/) { fillSensorIdServiceMap(obj, intf, id); }));
+ sensorRemovedMatches.emplace(
+ s.first,
+ std::make_unique<sdbusplus::bus::match_t>(
+ bus, interfacesRemoved() + argNpath(0, s.second.sensorPath),
+ [id = s.first](auto& /*msg*/) {
+ // Ideally this should work.
+ // But when a service is terminated or crashed, it does not
+ // emit interfacesRemoved signal. In that case it's handled
+ // by sensorsOwnerMatch
+ sensorCacheMap[id].reset();
+ }));
+ sensorUpdatedMatches.emplace(
+ s.first,
+ std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ type::signal() + path(s.second.sensorPath) +
+ member("PropertiesChanged"s) +
+ interface("org.freedesktop.DBus.Properties"s),
+ [&s](auto& msg) {
+ fillSensorIdServiceMap(
+ s.second.sensorPath,
+ s.second.propertyInterfaces.begin()->first, s.first);
+ try
+ {
+ // This is signal callback
+ std::string interfaceName;
+ msg.read(interfaceName);
+ ipmi::PropertyMap props;
+ msg.read(props);
+ s.second.getFunc(s.first, s.second, props);
+ }
+ catch (const std::exception& e)
+ {
+ sensorCacheMap[s.first].reset();
+ }
+ }));
+ }
+ sensorsOwnerMatch = std::make_unique<sdbusplus::bus::match_t>(
+ bus, nameOwnerChanged(), [](auto& msg) {
+ std::string name;
+ std::string oldOwner;
+ std::string newOwner;
+ msg.read(name, oldOwner, newOwner);
+
+ if (!name.empty() && newOwner.empty())
+ {
+ // The service exits
+ const auto it = serviceToIdMap.find(name);
+ if (it == serviceToIdMap.end())
+ {
+ return;
+ }
+ for (const auto& id : it->second)
+ {
+ // Invalidate cache
+ sensorCacheMap[id].reset();
+ }
+ }
+ });
+}
+#endif
+
+// Use a lookup table to find the interface name of a specific sensor
+// This will be used until an alternative is found. this is the first
+// step for mapping IPMI
+int find_openbmc_path(uint8_t num, dbus_interface_t* interface)
+{
+ const auto& sensor_it = ipmi::sensor::sensors.find(num);
+ if (sensor_it == ipmi::sensor::sensors.end())
+ {
+ // The sensor map does not contain the sensor requested
+ return -EINVAL;
+ }
+
+ const auto& info = sensor_it->second;
+
+ std::string serviceName{};
+ try
+ {
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ serviceName =
+ ipmi::getService(bus, info.sensorInterface, info.sensorPath);
+ }
+ catch (const sdbusplus::exception_t&)
+ {
+ std::fprintf(stderr, "Failed to get %s busname: %s\n",
+ info.sensorPath.c_str(), serviceName.c_str());
+ return -EINVAL;
+ }
+
+ interface->sensortype = info.sensorType;
+ strcpy(interface->bus, serviceName.c_str());
+ strcpy(interface->path, info.sensorPath.c_str());
+ // Take the interface name from the beginning of the DbusInterfaceMap. This
+ // works for the Value interface but may not suffice for more complex
+ // sensors.
+ // tracked https://github.com/openbmc/phosphor-host-ipmid/issues/103
+ strcpy(interface->interface,
+ info.propertyInterfaces.begin()->first.c_str());
+ interface->sensornumber = num;
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////
+//
+// Routines used by ipmi commands wanting to interact on the dbus
+//
+/////////////////////////////////////////////////////////////////////
+int set_sensor_dbus_state_s(uint8_t number, const char* method,
+ const char* value)
+{
+ dbus_interface_t a;
+ int r;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message* m = nullptr;
+
+ r = find_openbmc_path(number, &a);
+
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number);
+ return 0;
+ }
+
+ r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface,
+ method);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to create a method call: %s",
+ strerror(-r));
+ goto final;
+ }
+
+ r = sd_bus_message_append(m, "v", "s", value);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to create a input parameter: %s",
+ strerror(-r));
+ goto final;
+ }
+
+ r = sd_bus_call(bus, m, 0, &error, nullptr);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to call the method: %s", strerror(-r));
+ }
+
+final:
+ sd_bus_error_free(&error);
+ m = sd_bus_message_unref(m);
+
+ return 0;
+}
+int set_sensor_dbus_state_y(uint8_t number, const char* method,
+ const uint8_t value)
+{
+ dbus_interface_t a;
+ int r;
+ sd_bus_error error = SD_BUS_ERROR_NULL;
+ sd_bus_message* m = nullptr;
+
+ r = find_openbmc_path(number, &a);
+
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number);
+ return 0;
+ }
+
+ r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface,
+ method);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to create a method call: %s",
+ strerror(-r));
+ goto final;
+ }
+
+ r = sd_bus_message_append(m, "v", "i", value);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Failed to create a input parameter: %s",
+ strerror(-r));
+ goto final;
+ }
+
+ r = sd_bus_call(bus, m, 0, &error, nullptr);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "12 Failed to call the method: %s", strerror(-r));
+ }
+
+final:
+ sd_bus_error_free(&error);
+ m = sd_bus_message_unref(m);
+
+ return 0;
+}
+
+uint8_t dbus_to_sensor_type(char* p)
+{
+ sensorTypemap_t* s = g_SensorTypeMap;
+ char r = 0;
+ while (s->number != 0xFF)
+ {
+ if (!strcmp(s->dbusname, p))
+ {
+ r = s->typecode;
+ break;
+ }
+ s++;
+ }
+
+ if (s->number == 0xFF)
+ printf("Failed to find Sensor Type %s\n", p);
+
+ return r;
+}
+
+uint8_t get_type_from_interface(dbus_interface_t dbus_if)
+{
+ uint8_t type;
+
+ // This is where sensors that do not exist in dbus but do
+ // exist in the host code stop. This should indicate it
+ // is not a supported sensor
+ if (dbus_if.interface[0] == 0)
+ {
+ return 0;
+ }
+
+ // Fetch type from interface itself.
+ if (dbus_if.sensortype != 0)
+ {
+ type = dbus_if.sensortype;
+ }
+ else
+ {
+ // Non InventoryItems
+ char* p = strrchr(dbus_if.path, '/');
+ type = dbus_to_sensor_type(p + 1);
+ }
+
+ return type;
+}
+
+// Replaces find_sensor
+uint8_t find_type_for_sensor_number(uint8_t num)
+{
+ int r;
+ dbus_interface_t dbus_if;
+ r = find_openbmc_path(num, &dbus_if);
+ if (r < 0)
+ {
+ std::fprintf(stderr, "Could not find sensor %d\n", num);
+ return 0;
+ }
+ return get_type_from_interface(dbus_if);
+}
+
+/**
+ * @brief implements the get sensor type command.
+ * @param - sensorNumber
+ *
+ * @return IPMI completion code plus response data on success.
+ * - sensorType
+ * - eventType
+ **/
+
+ipmi::RspType<uint8_t, // sensorType
+ uint8_t // eventType
+ >
+ ipmiGetSensorType(uint8_t sensorNumber)
+{
+ const auto it = ipmi::sensor::sensors.find(sensorNumber);
+ if (it == ipmi::sensor::sensors.end())
+ {
+ // The sensor map does not contain the sensor requested
+ return ipmi::responseSensorInvalid();
+ }
+
+ const auto& info = it->second;
+ uint8_t sensorType = info.sensorType;
+ uint8_t eventType = info.sensorReadingType;
+
+ return ipmi::responseSuccess(sensorType, eventType);
+}
+
+const std::set<std::string> analogSensorInterfaces = {
+ "xyz.openbmc_project.Sensor.Value",
+ "xyz.openbmc_project.Control.FanPwm",
+};
+
+bool isAnalogSensor(const std::string& interface)
+{
+ return (analogSensorInterfaces.count(interface));
+}
+
+/**
+@brief This command is used to set sensorReading.
+
+@param
+ - sensorNumber
+ - operation
+ - reading
+ - assertOffset0_7
+ - assertOffset8_14
+ - deassertOffset0_7
+ - deassertOffset8_14
+ - eventData1
+ - eventData2
+ - eventData3
+
+@return completion code on success.
+**/
+
+ipmi::RspType<> ipmiSetSensorReading(
+ uint8_t sensorNumber, uint8_t operation, uint8_t reading,
+ uint8_t assertOffset0_7, uint8_t assertOffset8_14,
+ uint8_t deassertOffset0_7, uint8_t deassertOffset8_14, uint8_t eventData1,
+ uint8_t eventData2, uint8_t eventData3)
+{
+ lg2::debug("IPMI SET_SENSOR, sensorNumber: {SENSOR_NUM}", "SENSOR_NUM",
+ lg2::hex, sensorNumber);
+
+ if (sensorNumber == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ ipmi::sensor::SetSensorReadingReq cmdData;
+
+ cmdData.number = sensorNumber;
+ cmdData.operation = operation;
+ cmdData.reading = reading;
+ cmdData.assertOffset0_7 = assertOffset0_7;
+ cmdData.assertOffset8_14 = assertOffset8_14;
+ cmdData.deassertOffset0_7 = deassertOffset0_7;
+ cmdData.deassertOffset8_14 = deassertOffset8_14;
+ cmdData.eventData1 = eventData1;
+ cmdData.eventData2 = eventData2;
+ cmdData.eventData3 = eventData3;
+
+ // Check if the Sensor Number is present
+ const auto iter = ipmi::sensor::sensors.find(sensorNumber);
+ if (iter == ipmi::sensor::sensors.end())
+ {
+ updateSensorRecordFromSSRAESC(&sensorNumber);
+ return ipmi::responseSuccess();
+ }
+
+ try
+ {
+ if (ipmi::sensor::Mutability::Write !=
+ (iter->second.mutability & ipmi::sensor::Mutability::Write))
+ {
+ lg2::error("Sensor Set operation is not allowed, "
+ "sensorNumber: {SENSOR_NUM}",
+ "SENSOR_NUM", lg2::hex, sensorNumber);
+ return ipmi::responseIllegalCommand();
+ }
+ auto ipmiRC = iter->second.updateFunc(cmdData, iter->second);
+ return ipmi::response(ipmiRC);
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Set sensor failed, sensorNumber: {SENSOR_NUM}",
+ "SENSOR_NUM", lg2::hex, sensorNumber);
+ commit<InternalFailure>();
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::runtime_error& e)
+ {
+ lg2::error("runtime error: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+/** @brief implements the get sensor reading command
+ * @param sensorNum - sensor number
+ *
+ * @returns IPMI completion code plus response data
+ * - senReading - sensor reading
+ * - reserved
+ * - readState - sensor reading state enabled
+ * - senScanState - sensor scan state disabled
+ * - allEventMessageState - all Event message state disabled
+ * - assertionStatesLsb - threshold levels states
+ * - assertionStatesMsb - discrete reading sensor states
+ */
+ipmi::RspType<uint8_t, // sensor reading
+
+ uint5_t, // reserved
+ bool, // reading state
+ bool, // 0 = sensor scanning state disabled
+ bool, // 0 = all event messages disabled
+
+ uint8_t, // threshold levels states
+ uint8_t // discrete reading sensor states
+ >
+ ipmiSensorGetSensorReading([[maybe_unused]] ipmi::Context::ptr& ctx,
+ uint8_t sensorNum)
+{
+ if (sensorNum == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ const auto iter = ipmi::sensor::sensors.find(sensorNum);
+ if (iter == ipmi::sensor::sensors.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+ if (ipmi::sensor::Mutability::Read !=
+ (iter->second.mutability & ipmi::sensor::Mutability::Read))
+ {
+ return ipmi::responseIllegalCommand();
+ }
+
+ try
+ {
+#ifdef FEATURE_SENSORS_CACHE
+ auto& sensorData = sensorCacheMap[sensorNum];
+ if (!sensorData.has_value())
+ {
+ // No cached value, try read it
+ std::string service;
+ boost::system::error_code ec;
+ const auto& sensorInfo = iter->second;
+ ec = ipmi::getService(ctx, sensorInfo.sensorInterface,
+ sensorInfo.sensorPath, service);
+ if (ec)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ fillSensorIdServiceMap(sensorInfo.sensorPath,
+ sensorInfo.propertyInterfaces.begin()->first,
+ iter->first, service);
+
+ ipmi::PropertyMap props;
+ ec = ipmi::getAllDbusProperties(
+ ctx, service, sensorInfo.sensorPath,
+ sensorInfo.propertyInterfaces.begin()->first, props);
+ if (ec)
+ {
+ fprintf(stderr, "Failed to get sensor %s, %d: %s\n",
+ sensorInfo.sensorPath.c_str(), ec.value(),
+ ec.message().c_str());
+ // Intitilizing with default values
+ constexpr uint8_t senReading = 0;
+ constexpr uint5_t reserved{0};
+ constexpr bool readState = true;
+ constexpr bool senScanState = false;
+ constexpr bool allEventMessageState = false;
+ constexpr uint8_t assertionStatesLsb = 0;
+ constexpr uint8_t assertionStatesMsb = 0;
+
+ return ipmi::responseSuccess(
+ senReading, reserved, readState, senScanState,
+ allEventMessageState, assertionStatesLsb,
+ assertionStatesMsb);
+ }
+ sensorInfo.getFunc(sensorNum, sensorInfo, props);
+ }
+ return ipmi::responseSuccess(
+ sensorData->response.reading, uint5_t(0),
+ sensorData->response.readingOrStateUnavailable,
+ sensorData->response.scanningEnabled,
+ sensorData->response.allEventMessagesEnabled,
+ sensorData->response.thresholdLevelsStates,
+ sensorData->response.discreteReadingSensorStates);
+
+#else
+ ipmi::sensor::GetSensorResponse getResponse =
+ iter->second.getFunc(iter->second);
+
+ return ipmi::responseSuccess(
+ getResponse.reading, uint5_t(0),
+ getResponse.readingOrStateUnavailable, getResponse.scanningEnabled,
+ getResponse.allEventMessagesEnabled,
+ getResponse.thresholdLevelsStates,
+ getResponse.discreteReadingSensorStates);
+#endif
+ }
+#ifdef UPDATE_FUNCTIONAL_ON_FAIL
+ catch (const SensorFunctionalError& e)
+ {
+ return ipmi::responseResponseError();
+ }
+#endif
+ catch (const std::exception& e)
+ {
+ // Intitilizing with default values
+ constexpr uint8_t senReading = 0;
+ constexpr uint5_t reserved{0};
+ constexpr bool readState = true;
+ constexpr bool senScanState = false;
+ constexpr bool allEventMessageState = false;
+ constexpr uint8_t assertionStatesLsb = 0;
+ constexpr uint8_t assertionStatesMsb = 0;
+
+ return ipmi::responseSuccess(senReading, reserved, readState,
+ senScanState, allEventMessageState,
+ assertionStatesLsb, assertionStatesMsb);
+ }
+}
+
+void updateWarningThreshold(uint8_t lowerValue, uint8_t upperValue,
+ get_sdr::GetSensorThresholdsResponse& resp)
+{
+ resp.lowerNonCritical = lowerValue;
+ resp.upperNonCritical = upperValue;
+ if (lowerValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::NON_CRITICAL_LOW_MASK);
+ }
+
+ if (upperValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::NON_CRITICAL_HIGH_MASK);
+ }
+}
+
+void updateCriticalThreshold(uint8_t lowerValue, uint8_t upperValue,
+ get_sdr::GetSensorThresholdsResponse& resp)
+{
+ resp.lowerCritical = lowerValue;
+ resp.upperCritical = upperValue;
+ if (lowerValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::CRITICAL_LOW_MASK);
+ }
+
+ if (upperValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::CRITICAL_HIGH_MASK);
+ }
+}
+
+void updateNonRecoverableThreshold(uint8_t lowerValue, uint8_t upperValue,
+ get_sdr::GetSensorThresholdsResponse& resp)
+{
+ resp.lowerNonRecoverable = lowerValue;
+ resp.upperNonRecoverable = upperValue;
+ if (lowerValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::NON_RECOVERABLE_LOW_MASK);
+ }
+
+ if (upperValue)
+ {
+ resp.validMask |= static_cast<uint8_t>(
+ ipmi::sensor::ThresholdMask::NON_RECOVERABLE_HIGH_MASK);
+ }
+}
+
+get_sdr::GetSensorThresholdsResponse getSensorThresholds(
+ ipmi::Context::ptr& ctx, uint8_t sensorNum)
+{
+ get_sdr::GetSensorThresholdsResponse resp{};
+ const auto iter = ipmi::sensor::sensors.find(sensorNum);
+ const auto info = iter->second;
+
+ std::string service;
+ boost::system::error_code ec;
+ ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service);
+ if (ec)
+ {
+ return resp;
+ }
+
+ int32_t minClamp;
+ int32_t maxClamp;
+ int32_t rawData;
+ constexpr uint8_t sensorUnitsSignedBits = 2 << 6;
+ constexpr uint8_t signedDataFormat = 0x80;
+ if ((info.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat)
+ {
+ minClamp = std::numeric_limits<int8_t>::lowest();
+ maxClamp = std::numeric_limits<int8_t>::max();
+ }
+ else
+ {
+ minClamp = std::numeric_limits<uint8_t>::lowest();
+ maxClamp = std::numeric_limits<uint8_t>::max();
+ }
+
+ static std::vector<std::string> thresholdNames{"Warning", "Critical",
+ "NonRecoverable"};
+
+ for (const auto& thresholdName : thresholdNames)
+ {
+ std::string thresholdInterface =
+ "xyz.openbmc_project.Sensor.Threshold." + thresholdName;
+ std::string thresholdLow = thresholdName + "Low";
+ std::string thresholdHigh = thresholdName + "High";
+
+ ipmi::PropertyMap thresholds;
+ ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+ thresholdInterface, thresholds);
+ if (ec)
+ {
+ continue;
+ }
+
+ double lowValue = ipmi::mappedVariant<double>(
+ thresholds, thresholdLow, std::numeric_limits<double>::quiet_NaN());
+ double highValue = ipmi::mappedVariant<double>(
+ thresholds, thresholdHigh,
+ std::numeric_limits<double>::quiet_NaN());
+
+ uint8_t lowerValue = 0;
+ uint8_t upperValue = 0;
+ if (std::isfinite(lowValue))
+ {
+ lowValue *= std::pow(10, info.scale - info.exponentR);
+ rawData = round((lowValue - info.scaledOffset) / info.coefficientM);
+ lowerValue =
+ static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp));
+ }
+
+ if (std::isfinite(highValue))
+ {
+ highValue *= std::pow(10, info.scale - info.exponentR);
+ rawData =
+ round((highValue - info.scaledOffset) / info.coefficientM);
+ upperValue =
+ static_cast<uint8_t>(std::clamp(rawData, minClamp, maxClamp));
+ }
+
+ if (thresholdName == "Warning")
+ {
+ updateWarningThreshold(lowerValue, upperValue, resp);
+ }
+ else if (thresholdName == "Critical")
+ {
+ updateCriticalThreshold(lowerValue, upperValue, resp);
+ }
+ else if (thresholdName == "NonRecoverable")
+ {
+ updateNonRecoverableThreshold(lowerValue, upperValue, resp);
+ }
+ }
+
+ return resp;
+}
+
+/** @brief implements the get sensor thresholds command
+ * @param ctx - IPMI context pointer
+ * @param sensorNum - sensor number
+ *
+ * @returns IPMI completion code plus response data
+ * - validMask - threshold mask
+ * - lower non-critical threshold - IPMI messaging state
+ * - lower critical threshold - link authentication state
+ * - lower non-recoverable threshold - callback state
+ * - upper non-critical threshold
+ * - upper critical
+ * - upper non-recoverable
+ */
+ipmi::RspType<uint8_t, // validMask
+ uint8_t, // lowerNonCritical
+ uint8_t, // lowerCritical
+ uint8_t, // lowerNonRecoverable
+ uint8_t, // upperNonCritical
+ uint8_t, // upperCritical
+ uint8_t // upperNonRecoverable
+ >
+ ipmiSensorGetSensorThresholds(ipmi::Context::ptr& ctx, uint8_t sensorNum)
+{
+ constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
+
+ const auto iter = ipmi::sensor::sensors.find(sensorNum);
+ if (iter == ipmi::sensor::sensors.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ const auto info = iter->second;
+
+ // Proceed only if the sensor value interface is implemented.
+ if (info.propertyInterfaces.find(valueInterface) ==
+ info.propertyInterfaces.end())
+ {
+ // return with valid mask as 0
+ return ipmi::responseSuccess();
+ }
+
+ auto it = sensorThresholdMap.find(sensorNum);
+ if (it == sensorThresholdMap.end())
+ {
+ auto resp = getSensorThresholds(ctx, sensorNum);
+ if (resp.validMask == 0)
+ {
+ return ipmi::responseSensorInvalid();
+ }
+ sensorThresholdMap[sensorNum] = std::move(resp);
+ }
+
+ const auto& resp = sensorThresholdMap[sensorNum];
+
+ return ipmi::responseSuccess(
+ resp.validMask, resp.lowerNonCritical, resp.lowerCritical,
+ resp.lowerNonRecoverable, resp.upperNonCritical, resp.upperCritical,
+ resp.upperNonRecoverable);
+}
+
+/** @brief implements the Set Sensor threshold command
+ * @param sensorNumber - sensor number
+ * @param lowerNonCriticalThreshMask
+ * @param lowerCriticalThreshMask
+ * @param lowerNonRecovThreshMask
+ * @param upperNonCriticalThreshMask
+ * @param upperCriticalThreshMask
+ * @param upperNonRecovThreshMask
+ * @param reserved
+ * @param lowerNonCritical - lower non-critical threshold
+ * @param lowerCritical - Lower critical threshold
+ * @param lowerNonRecoverable - Lower non recovarable threshold
+ * @param upperNonCritical - Upper non-critical threshold
+ * @param upperCritical - Upper critical
+ * @param upperNonRecoverable - Upper Non-recoverable
+ *
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSenSetSensorThresholds(
+ ipmi::Context::ptr& ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask,
+ bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
+ bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
+ bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
+ uint8_t lowerCritical, uint8_t, uint8_t upperNonCritical,
+ uint8_t upperCritical, uint8_t)
+{
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // lower nc and upper nc not suppported on any sensor
+ if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // if none of the threshold mask are set, nothing to do
+ if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
+ lowerNonRecovThreshMask | upperNonCriticalThreshMask |
+ upperCriticalThreshMask | upperNonRecovThreshMask))
+ {
+ return ipmi::responseSuccess();
+ }
+
+ constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
+
+ const auto iter = ipmi::sensor::sensors.find(sensorNum);
+ if (iter == ipmi::sensor::sensors.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ const auto& info = iter->second;
+
+ // Proceed only if the sensor value interface is implemented.
+ if (info.propertyInterfaces.find(valueInterface) ==
+ info.propertyInterfaces.end())
+ {
+ // return with valid mask as 0
+ return ipmi::responseSuccess();
+ }
+
+ constexpr auto warningThreshIntf =
+ "xyz.openbmc_project.Sensor.Threshold.Warning";
+ constexpr auto criticalThreshIntf =
+ "xyz.openbmc_project.Sensor.Threshold.Critical";
+
+ std::string service;
+ boost::system::error_code ec;
+ ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service);
+ if (ec)
+ {
+ return ipmi::responseResponseError();
+ }
+ // store a vector of property name, value to set, and interface
+ std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
+
+ // define the indexes of the tuple
+ constexpr uint8_t propertyName = 0;
+ constexpr uint8_t thresholdValue = 1;
+ constexpr uint8_t interface = 2;
+ // verifiy all needed fields are present
+ if (lowerCriticalThreshMask || upperCriticalThreshMask)
+ {
+ ipmi::PropertyMap findThreshold;
+ ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+ criticalThreshIntf, findThreshold);
+
+ if (!ec)
+ {
+ if (lowerCriticalThreshMask)
+ {
+ auto findLower = findThreshold.find("CriticalLow");
+ if (findLower == findThreshold.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
+ criticalThreshIntf);
+ }
+ if (upperCriticalThreshMask)
+ {
+ auto findUpper = findThreshold.find("CriticalHigh");
+ if (findUpper == findThreshold.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
+ criticalThreshIntf);
+ }
+ }
+ }
+ if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
+ {
+ ipmi::PropertyMap findThreshold;
+ ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath,
+ warningThreshIntf, findThreshold);
+
+ if (!ec)
+ {
+ if (lowerNonCriticalThreshMask)
+ {
+ auto findLower = findThreshold.find("WarningLow");
+ if (findLower == findThreshold.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
+ warningThreshIntf);
+ }
+ if (upperNonCriticalThreshMask)
+ {
+ auto findUpper = findThreshold.find("WarningHigh");
+ if (findUpper == findThreshold.end())
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
+ warningThreshIntf);
+ }
+ }
+ }
+ for (const auto& property : thresholdsToSet)
+ {
+ // from section 36.3 in the IPMI Spec, assume all linear
+ double valueToSet =
+ ((info.coefficientM * std::get<thresholdValue>(property)) +
+ (info.scaledOffset * std::pow(10.0, info.scale))) *
+ std::pow(10.0, info.exponentR);
+ ipmi::setDbusProperty(
+ ctx, service, info.sensorPath, std::get<interface>(property),
+ std::get<propertyName>(property), ipmi::Value(valueToSet));
+ }
+
+ // Invalidate the cache
+ sensorThresholdMap.erase(sensorNum);
+ return ipmi::responseSuccess();
+}
+
+/** @brief implements the get SDR Info command
+ * @param count - Operation
+ *
+ * @returns IPMI completion code plus response data
+ * - sdrCount - sensor/SDR count
+ * - lunsAndDynamicPopulation - static/Dynamic sensor population flag
+ */
+ipmi::RspType<uint8_t, // respcount
+ uint8_t // dynamic population flags
+ >
+ ipmiSensorGetDeviceSdrInfo(std::optional<uint8_t> count)
+{
+ uint8_t sdrCount;
+ // multiple LUNs not supported.
+ constexpr uint8_t lunsAndDynamicPopulation = 1;
+ constexpr uint8_t getSdrCount = 0x01;
+ constexpr uint8_t getSensorCount = 0x00;
+
+ if (count.value_or(0) == getSdrCount)
+ {
+ // Get SDR count. This returns the total number of SDRs in the device.
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+ sdrCount = ipmi::sensor::sensors.size() + frus.size() +
+ entityRecords.size();
+ }
+ else if (count.value_or(0) == getSensorCount)
+ {
+ // Get Sensor count. This returns the number of sensors
+ sdrCount = ipmi::sensor::sensors.size();
+ }
+ else
+ {
+ return ipmi::responseInvalidCommandOnLun();
+ }
+
+ return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation);
+}
+
+/** @brief implements the reserve SDR command
+ * @returns IPMI completion code plus response data
+ * - reservationID - reservation ID
+ */
+ipmi::RspType<uint16_t> ipmiSensorReserveSdr()
+{
+ // A constant reservation ID is okay until we implement add/remove SDR.
+ constexpr uint16_t reservationID = 1;
+
+ return ipmi::responseSuccess(reservationID);
+}
+
+void setUnitFieldsForObject(const ipmi::sensor::Info* info,
+ get_sdr::SensorDataFullRecordBody* body)
+{
+ namespace server = sdbusplus::server::xyz::openbmc_project::sensor;
+ try
+ {
+ auto unit = server::Value::convertUnitFromString(info->unit);
+ // Unit strings defined in
+ // phosphor-dbus-interfaces/xyz/openbmc_project/Sensor/Value.interface.yaml
+ switch (unit)
+ {
+ case server::Value::Unit::DegreesC:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_DEGREES_C;
+ break;
+ case server::Value::Unit::RPMS:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_RPM;
+ break;
+ case server::Value::Unit::Volts:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_VOLTS;
+ break;
+ case server::Value::Unit::Meters:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_METERS;
+ break;
+ case server::Value::Unit::Amperes:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_AMPERES;
+ break;
+ case server::Value::Unit::Joules:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_JOULES;
+ break;
+ case server::Value::Unit::Watts:
+ body->sensor_units_2_base = get_sdr::SENSOR_UNIT_WATTS;
+ break;
+ default:
+ // Cannot be hit.
+ std::fprintf(stderr, "Unknown value unit type: = %s\n",
+ info->unit.c_str());
+ }
+ }
+ catch (const sdbusplus::exception::InvalidEnumString& e)
+ {
+ lg2::warning("Warning: no unit provided for sensor!");
+ }
+}
+
+ipmi_ret_t populate_record_from_dbus(get_sdr::SensorDataFullRecordBody* body,
+ const ipmi::sensor::Info* info,
+ ipmi_data_len_t)
+{
+ /* Functional sensor case */
+ if (isAnalogSensor(info->propertyInterfaces.begin()->first))
+ {
+ body->sensor_units_1 = info->sensorUnits1; // default is 0. unsigned, no
+ // rate, no modifier, not a %
+ /* Unit info */
+ setUnitFieldsForObject(info, body);
+
+ get_sdr::body::set_b(info->coefficientB, body);
+ get_sdr::body::set_m(info->coefficientM, body);
+ get_sdr::body::set_b_exp(info->exponentB, body);
+ get_sdr::body::set_r_exp(info->exponentR, body);
+ }
+
+ /* ID string */
+ auto id_string = info->sensorName;
+
+ if (id_string.empty())
+ {
+ id_string = info->sensorNameFunc(*info);
+ }
+
+ if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH)
+ {
+ get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH, body);
+ }
+ else
+ {
+ get_sdr::body::set_id_strlen(id_string.length(), body);
+ }
+ get_sdr::body::set_id_type(3, body); // "8-bit ASCII + Latin 1"
+ strncpy(body->id_string, id_string.c_str(),
+ get_sdr::body::get_id_strlen(body));
+
+ return IPMI_CC_OK;
+};
+
+ipmi_ret_t ipmi_fru_get_sdr(ipmi_request_t request, ipmi_response_t response,
+ ipmi_data_len_t data_len)
+{
+ auto req = reinterpret_cast<get_sdr::GetSdrReq*>(request);
+ auto resp = reinterpret_cast<get_sdr::GetSdrResp*>(response);
+ get_sdr::SensorDataFruRecord record{};
+ auto dataLength = 0;
+
+ auto fru = frus.begin();
+ uint8_t fruID{};
+ auto recordID = get_sdr::request::get_record_id(req);
+
+ fruID = recordID - FRU_RECORD_ID_START;
+ fru = frus.find(fruID);
+ if (fru == frus.end())
+ {
+ return IPMI_CC_SENSOR_INVALID;
+ }
+
+ /* Header */
+ get_sdr::header::set_record_id(recordID, &(record.header));
+ record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1
+ record.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD;
+ record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+ /* Key */
+ record.key.fruID = fruID;
+ record.key.accessLun |= IPMI_LOGICAL_FRU;
+ record.key.deviceAddress = BMCTargetAddress;
+
+ /* Body */
+ record.body.entityID = fru->second[0].entityID;
+ record.body.entityInstance = fru->second[0].entityInstance;
+ record.body.deviceType = fruInventoryDevice;
+ record.body.deviceTypeModifier = IPMIFruInventory;
+
+ /* Device ID string */
+ auto deviceID =
+ fru->second[0].path.substr(fru->second[0].path.find_last_of('/') + 1,
+ fru->second[0].path.length());
+
+ if (deviceID.length() > get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH)
+ {
+ get_sdr::body::set_device_id_strlen(
+ get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH, &(record.body));
+ }
+ else
+ {
+ get_sdr::body::set_device_id_strlen(deviceID.length(), &(record.body));
+ }
+
+ strncpy(record.body.deviceID, deviceID.c_str(),
+ get_sdr::body::get_device_id_strlen(&(record.body)));
+
+ if (++fru == frus.end())
+ {
+ // we have reached till end of fru, so assign the next record id to
+ // 512(Max fru ID = 511) + Entity Record ID(may start with 0).
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+ auto next_record_id =
+ (entityRecords.size())
+ ? entityRecords.begin()->first + ENTITY_RECORD_ID_START
+ : END_OF_RECORD;
+ get_sdr::response::set_next_record_id(next_record_id, resp);
+ }
+ else
+ {
+ get_sdr::response::set_next_record_id(
+ (FRU_RECORD_ID_START + fru->first), resp);
+ }
+
+ // Check for invalid offset size
+ if (req->offset > sizeof(record))
+ {
+ return IPMI_CC_PARM_OUT_OF_RANGE;
+ }
+
+ dataLength = std::min(static_cast<size_t>(req->bytes_to_read),
+ sizeof(record) - req->offset);
+
+ std::memcpy(resp->record_data,
+ reinterpret_cast<uint8_t*>(&record) + req->offset, dataLength);
+
+ *data_len = dataLength;
+ *data_len += 2; // additional 2 bytes for next record ID
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmi_entity_get_sdr(ipmi_request_t request, ipmi_response_t response,
+ ipmi_data_len_t data_len)
+{
+ auto req = reinterpret_cast<get_sdr::GetSdrReq*>(request);
+ auto resp = reinterpret_cast<get_sdr::GetSdrResp*>(response);
+ get_sdr::SensorDataEntityRecord record{};
+ auto dataLength = 0;
+
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+ auto entity = entityRecords.begin();
+ uint8_t entityRecordID;
+ auto recordID = get_sdr::request::get_record_id(req);
+
+ entityRecordID = recordID - ENTITY_RECORD_ID_START;
+ entity = entityRecords.find(entityRecordID);
+ if (entity == entityRecords.end())
+ {
+ return IPMI_CC_SENSOR_INVALID;
+ }
+
+ /* Header */
+ get_sdr::header::set_record_id(recordID, &(record.header));
+ record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1
+ record.header.record_type = get_sdr::SENSOR_DATA_ENTITY_RECORD;
+ record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+ /* Key */
+ record.key.containerEntityId = entity->second.containerEntityId;
+ record.key.containerEntityInstance = entity->second.containerEntityInstance;
+ get_sdr::key::set_flags(entity->second.isList, entity->second.isLinked,
+ &(record.key));
+ record.key.entityId1 = entity->second.containedEntities[0].first;
+ record.key.entityInstance1 = entity->second.containedEntities[0].second;
+
+ /* Body */
+ record.body.entityId2 = entity->second.containedEntities[1].first;
+ record.body.entityInstance2 = entity->second.containedEntities[1].second;
+ record.body.entityId3 = entity->second.containedEntities[2].first;
+ record.body.entityInstance3 = entity->second.containedEntities[2].second;
+ record.body.entityId4 = entity->second.containedEntities[3].first;
+ record.body.entityInstance4 = entity->second.containedEntities[3].second;
+
+ if (++entity == entityRecords.end())
+ {
+ get_sdr::response::set_next_record_id(END_OF_RECORD,
+ resp); // last record
+ }
+ else
+ {
+ get_sdr::response::set_next_record_id(
+ (ENTITY_RECORD_ID_START + entity->first), resp);
+ }
+
+ // Check for invalid offset size
+ if (req->offset > sizeof(record))
+ {
+ return IPMI_CC_PARM_OUT_OF_RANGE;
+ }
+
+ dataLength = std::min(static_cast<size_t>(req->bytes_to_read),
+ sizeof(record) - req->offset);
+
+ std::memcpy(resp->record_data,
+ reinterpret_cast<uint8_t*>(&record) + req->offset, dataLength);
+
+ *data_len = dataLength;
+ *data_len += 2; // additional 2 bytes for next record ID
+
+ return IPMI_CC_OK;
+}
+
+ipmi_ret_t ipmi_sen_get_sdr(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request,
+ ipmi_response_t response, ipmi_data_len_t data_len,
+ ipmi_context_t)
+{
+ ipmi_ret_t ret = IPMI_CC_OK;
+ get_sdr::GetSdrReq* req = (get_sdr::GetSdrReq*)request;
+ get_sdr::GetSdrResp* resp = (get_sdr::GetSdrResp*)response;
+
+ // Note: we use an iterator so we can provide the next ID at the end of
+ // the call.
+ auto sensor = ipmi::sensor::sensors.begin();
+ auto recordID = get_sdr::request::get_record_id(req);
+
+ // At the beginning of a scan, the host side will send us id=0.
+ if (recordID != 0)
+ {
+ // recordID 0 to 255 means it is a FULL record.
+ // recordID 256 to 511 means it is a FRU record.
+ // recordID greater then 511 means it is a Entity Association
+ // record. Currently we are supporting three record types: FULL
+ // record, FRU record and Enttiy Association record.
+ if (recordID >= ENTITY_RECORD_ID_START)
+ {
+ return ipmi_entity_get_sdr(request, response, data_len);
+ }
+ else if (recordID >= FRU_RECORD_ID_START &&
+ recordID < ENTITY_RECORD_ID_START)
+ {
+ return ipmi_fru_get_sdr(request, response, data_len);
+ }
+ else
+ {
+ sensor = ipmi::sensor::sensors.find(recordID);
+ if (sensor == ipmi::sensor::sensors.end())
+ {
+ return IPMI_CC_SENSOR_INVALID;
+ }
+ }
+ }
+
+ uint8_t sensor_id = sensor->first;
+
+ auto it = sdrCacheMap.find(sensor_id);
+ if (it == sdrCacheMap.end())
+ {
+ /* Header */
+ get_sdr::SensorDataFullRecord record = {};
+ get_sdr::header::set_record_id(sensor_id, &(record.header));
+ record.header.sdr_version = 0x51; // Based on IPMI Spec v2.0 rev 1.1
+ record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
+ record.header.record_length = sizeof(record.key) + sizeof(record.body);
+
+ /* Key */
+ get_sdr::key::set_owner_id_bmc(&(record.key));
+ record.key.sensor_number = sensor_id;
+
+ /* Body */
+ record.body.entity_id = sensor->second.entityType;
+ record.body.sensor_type = sensor->second.sensorType;
+ record.body.event_reading_type = sensor->second.sensorReadingType;
+ record.body.entity_instance = sensor->second.instance;
+ if (ipmi::sensor::Mutability::Write ==
+ (sensor->second.mutability & ipmi::sensor::Mutability::Write))
+ {
+ get_sdr::body::init_settable_state(true, &(record.body));
+ }
+
+ // Set the type-specific details given the DBus interface
+ populate_record_from_dbus(&(record.body), &(sensor->second), data_len);
+ sdrCacheMap[sensor_id] = std::move(record);
+ }
+
+ const auto& record = sdrCacheMap[sensor_id];
+
+ if (++sensor == ipmi::sensor::sensors.end())
+ {
+ // we have reached till end of sensor, so assign the next record id
+ // to 256(Max Sensor ID = 255) + FRU ID(may start with 0).
+ auto next_record_id = (frus.size())
+ ? frus.begin()->first + FRU_RECORD_ID_START
+ : END_OF_RECORD;
+
+ get_sdr::response::set_next_record_id(next_record_id, resp);
+ }
+ else
+ {
+ get_sdr::response::set_next_record_id(sensor->first, resp);
+ }
+
+ if (req->offset > sizeof(record))
+ {
+ return IPMI_CC_PARM_OUT_OF_RANGE;
+ }
+
+ // data_len will ultimately be the size of the record, plus
+ // the size of the next record ID:
+ *data_len = std::min(static_cast<size_t>(req->bytes_to_read),
+ sizeof(record) - req->offset);
+
+ std::memcpy(resp->record_data,
+ reinterpret_cast<const uint8_t*>(&record) + req->offset,
+ *data_len);
+
+ // data_len should include the LSB and MSB:
+ *data_len += sizeof(resp->next_record_id_lsb) +
+ sizeof(resp->next_record_id_msb);
+
+ return ret;
+}
+
+static bool isFromSystemChannel()
+{
+ // TODO we could not figure out where the request is from based on IPMI
+ // command handler parameters. because of it, we can not differentiate
+ // request from SMS/SMM or IPMB channel
+ return true;
+}
+
+ipmi_ret_t ipmicmdPlatformEvent(ipmi_netfn_t, ipmi_cmd_t,
+ ipmi_request_t request, ipmi_response_t,
+ ipmi_data_len_t dataLen, ipmi_context_t)
+{
+ uint16_t generatorID;
+ size_t count;
+ bool assert = true;
+ std::string sensorPath;
+ size_t paraLen = *dataLen;
+ PlatformEventRequest* req;
+ *dataLen = 0;
+
+ if ((paraLen < selSystemEventSizeWith1Bytes) ||
+ (paraLen > selSystemEventSizeWith3Bytes))
+ {
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ if (isFromSystemChannel())
+ { // first byte for SYSTEM Interface is Generator ID
+ // +1 to get common struct
+ req = reinterpret_cast<PlatformEventRequest*>((uint8_t*)request + 1);
+ // Capture the generator ID
+ generatorID = *reinterpret_cast<uint8_t*>(request);
+ // Platform Event usually comes from other firmware, like BIOS.
+ // Unlike BMC sensor, it does not have BMC DBUS sensor path.
+ sensorPath = "System";
+ }
+ else
+ {
+ req = reinterpret_cast<PlatformEventRequest*>(request);
+ // TODO GenratorID for IPMB is combination of RqSA and RqLUN
+ generatorID = 0xff;
+ sensorPath = "IPMB";
+ }
+ // Content of event data field depends on sensor class.
+ // When data0 bit[5:4] is non-zero, valid data counts is 3.
+ // When data0 bit[7:6] is non-zero, valid data counts is 2.
+ if (((req->data[0] & byte3EnableMask) != 0 &&
+ paraLen < selSystemEventSizeWith3Bytes) ||
+ ((req->data[0] & byte2EnableMask) != 0 &&
+ paraLen < selSystemEventSizeWith2Bytes))
+ {
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ // Count bytes of Event Data
+ if ((req->data[0] & byte3EnableMask) != 0)
+ {
+ count = 3;
+ }
+ else if ((req->data[0] & byte2EnableMask) != 0)
+ {
+ count = 2;
+ }
+ else
+ {
+ count = 1;
+ }
+ assert = req->eventDirectionType & directionMask ? false : true;
+ std::vector<uint8_t> eventData(req->data, req->data + count);
+
+ sdbusplus::bus_t dbus(bus);
+ std::string service =
+ ipmi::getService(dbus, ipmiSELAddInterface, ipmiSELPath);
+ sdbusplus::message_t writeSEL = dbus.new_method_call(
+ service.c_str(), ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd");
+ writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert,
+ generatorID);
+ try
+ {
+ dbus.call(writeSEL);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("exception message: {ERROR}", "ERROR", e);
+ return IPMI_CC_UNSPECIFIED_ERROR;
+ }
+ return IPMI_CC_OK;
+}
+
+void registerNetFnSenFunctions()
+{
+ // Handlers with dbus-sdr handler implementation.
+ // Do not register the hander if it dynamic sensors stack is used.
+
+#ifndef FEATURE_DYNAMIC_SENSORS
+
+#ifdef FEATURE_SENSORS_CACHE
+ // Initialize the sensor matches
+ initSensorMatches();
+#endif
+
+ // <Set Sensor Reading and Event Status>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
+ ipmi::Privilege::Operator, ipmiSetSensorReading);
+ // <Get Sensor Reading>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorReading,
+ ipmi::Privilege::User, ipmiSensorGetSensorReading);
+
+ // <Reserve Device SDR Repository>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdReserveDeviceSdrRepository,
+ ipmi::Privilege::User, ipmiSensorReserveSdr);
+
+ // <Get Device SDR Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetDeviceSdrInfo,
+ ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo);
+
+ // <Get Sensor Thresholds>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorThreshold,
+ ipmi::Privilege::User, ipmiSensorGetSensorThresholds);
+
+ // <Set Sensor Thresholds>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdSetSensorThreshold,
+ ipmi::Privilege::User, ipmiSenSetSensorThresholds);
+
+ // <Get Device SDR>
+ ipmi_register_callback(NETFUN_SENSOR, ipmi::sensor_event::cmdGetDeviceSdr,
+ nullptr, ipmi_sen_get_sdr, PRIVILEGE_USER);
+
+#endif
+
+ // Common Handers used by both implementation.
+
+ // <Platform Event Message>
+ ipmi_register_callback(NETFUN_SENSOR, ipmi::sensor_event::cmdPlatformEvent,
+ nullptr, ipmicmdPlatformEvent, PRIVILEGE_OPERATOR);
+
+ // <Get Sensor Type>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor,
+ ipmi::sensor_event::cmdGetSensorType,
+ ipmi::Privilege::User, ipmiGetSensorType);
+
+ return;
+}
diff --git a/sensorhandler.hpp b/sensorhandler.hpp
new file mode 100644
index 0000000..74b1862
--- /dev/null
+++ b/sensorhandler.hpp
@@ -0,0 +1,766 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+
+#include <exception>
+
+/**
+ * @enum device_type
+ * IPMI FRU device types
+ */
+enum device_type
+{
+ IPMI_PHYSICAL_FRU = 0x00,
+ IPMI_LOGICAL_FRU = 0x80,
+};
+
+// Discrete sensor types.
+enum ipmi_sensor_types
+{
+ IPMI_SENSOR_TEMP = 0x01,
+ IPMI_SENSOR_VOLTAGE = 0x02,
+ IPMI_SENSOR_CURRENT = 0x03,
+ IPMI_SENSOR_FAN = 0x04,
+ IPMI_SENSOR_TPM = 0xCC,
+};
+
+/** @brief Custom exception for reading sensors that are not funcitonal.
+ */
+struct SensorFunctionalError : public std::exception
+{
+ const char* what() const noexcept
+ {
+ return "Sensor not functional";
+ }
+};
+
+#define MAX_DBUS_PATH 128
+struct dbus_interface_t
+{
+ uint8_t sensornumber;
+ uint8_t sensortype;
+
+ char bus[MAX_DBUS_PATH];
+ char path[MAX_DBUS_PATH];
+ char interface[MAX_DBUS_PATH];
+};
+
+struct PlatformEventRequest
+{
+ uint8_t eventMessageRevision;
+ uint8_t sensorType;
+ uint8_t sensorNumber;
+ uint8_t eventDirectionType;
+ uint8_t data[3];
+};
+
+static constexpr const char* ipmiSELPath = "/xyz/openbmc_project/Logging/IPMI";
+static constexpr const char* ipmiSELAddInterface =
+ "xyz.openbmc_project.Logging.IPMI";
+static const std::string ipmiSELAddMessage = "IPMI generated SEL Entry";
+
+static constexpr int selSystemEventSizeWith3Bytes = 8;
+static constexpr int selSystemEventSizeWith2Bytes = 7;
+static constexpr int selSystemEventSizeWith1Bytes = 6;
+static constexpr int selIPMBEventSize = 7;
+static constexpr uint8_t directionMask = 0x80;
+static constexpr uint8_t byte3EnableMask = 0x30;
+static constexpr uint8_t byte2EnableMask = 0xC0;
+
+int set_sensor_dbus_state_s(uint8_t, const char*, const char*);
+int set_sensor_dbus_state_y(uint8_t, const char*, const uint8_t);
+int find_openbmc_path(uint8_t, dbus_interface_t*);
+
+ipmi_ret_t ipmi_sen_get_sdr(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_request_t request, ipmi_response_t response,
+ ipmi_data_len_t data_len, ipmi_context_t context);
+
+ipmi::RspType<uint16_t> ipmiSensorReserveSdr();
+
+static const uint16_t FRU_RECORD_ID_START = 256;
+static const uint16_t ENTITY_RECORD_ID_START = 512;
+static const uint8_t SDR_VERSION = 0x51;
+static const uint16_t END_OF_RECORD = 0xFFFF;
+static const uint8_t LENGTH_MASK = 0x1F;
+
+/**
+ * Get SDR Info
+ */
+
+namespace get_sdr_info
+{
+namespace request
+{
+// Note: for some reason the ipmi_request_t appears to be the
+// raw value for this call.
+inline bool get_count(void* req)
+{
+ return (bool)((uint64_t)(req) & 1);
+}
+} // namespace request
+} // namespace get_sdr_info
+
+/**
+ * Get SDR
+ */
+namespace get_sdr
+{
+
+struct GetSdrReq
+{
+ uint8_t reservation_id_lsb;
+ uint8_t reservation_id_msb;
+ uint8_t record_id_lsb;
+ uint8_t record_id_msb;
+ uint8_t offset;
+ uint8_t bytes_to_read;
+} __attribute__((packed));
+
+namespace request
+{
+
+inline uint16_t get_reservation_id(GetSdrReq* req)
+{
+ return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
+};
+
+inline uint16_t get_record_id(GetSdrReq* req)
+{
+ return (req->record_id_lsb + (req->record_id_msb << 8));
+};
+
+} // namespace request
+
+// Response
+struct GetSdrResp
+{
+ uint8_t next_record_id_lsb;
+ uint8_t next_record_id_msb;
+ uint8_t record_data[64];
+} __attribute__((packed));
+
+namespace response
+{
+
+inline void set_next_record_id(uint16_t next, GetSdrResp* resp)
+{
+ resp->next_record_id_lsb = next & 0xff;
+ resp->next_record_id_msb = (next >> 8) & 0xff;
+};
+
+} // namespace response
+
+// Record header
+struct SensorDataRecordHeader
+{
+ uint8_t record_id_lsb;
+ uint8_t record_id_msb;
+ uint8_t sdr_version;
+ uint8_t record_type;
+ uint8_t record_length; // Length not counting the header
+} __attribute__((packed));
+
+namespace header
+{
+
+inline void set_record_id(int id, SensorDataRecordHeader* hdr)
+{
+ hdr->record_id_lsb = (id & 0xFF);
+ hdr->record_id_msb = (id >> 8) & 0xFF;
+};
+
+} // namespace header
+
+enum SensorDataRecordType
+{
+ SENSOR_DATA_FULL_RECORD = 0x1,
+ SENSOR_DATA_COMPACT_RECORD = 0x2,
+ SENSOR_DATA_EVENT_RECORD = 0x3,
+ SENSOR_DATA_ENTITY_RECORD = 0x8,
+ SENSOR_DATA_FRU_RECORD = 0x11,
+ SENSOR_DATA_MGMT_CTRL_LOCATOR = 0x12,
+};
+
+// Record key
+struct SensorDataRecordKey
+{
+ uint8_t owner_id;
+ uint8_t owner_lun;
+ uint8_t sensor_number;
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecordKey
+ *
+ * FRU Device Locator Record(key) - SDR Type 11
+ */
+struct SensorDataFruRecordKey
+{
+ uint8_t deviceAddress;
+ uint8_t fruID;
+ uint8_t accessLun;
+ uint8_t channelNumber;
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecordKey
+ *
+ * Entity Association Record(key) - SDR Type 8
+ */
+struct SensorDataEntityRecordKey
+{
+ uint8_t containerEntityId;
+ uint8_t containerEntityInstance;
+ uint8_t flags;
+ uint8_t entityId1;
+ uint8_t entityInstance1;
+} __attribute__((packed));
+
+namespace key
+{
+
+static constexpr uint8_t listOrRangeBit = 7;
+static constexpr uint8_t linkedBit = 6;
+
+inline void set_owner_id_ipmb(SensorDataRecordKey* key)
+{
+ key->owner_id &= ~0x01;
+};
+
+inline void set_owner_id_system_sw(SensorDataRecordKey* key)
+{
+ key->owner_id |= 0x01;
+};
+
+inline void set_owner_id_bmc(SensorDataRecordKey* key)
+{
+ key->owner_id |= 0x20;
+};
+
+inline void set_owner_id_address(uint8_t addr, SensorDataRecordKey* key)
+{
+ key->owner_id &= 0x01;
+ key->owner_id |= addr << 1;
+};
+
+inline void set_owner_lun(uint8_t lun, SensorDataRecordKey* key)
+{
+ key->owner_lun &= ~0x03;
+ key->owner_lun |= (lun & 0x03);
+};
+
+inline void set_owner_lun_channel(uint8_t channel, SensorDataRecordKey* key)
+{
+ key->owner_lun &= 0x0f;
+ key->owner_lun |= ((channel & 0xf) << 4);
+};
+
+inline void set_flags(bool isList, bool isLinked,
+ SensorDataEntityRecordKey* key)
+{
+ key->flags = 0x00;
+ if (!isList)
+ key->flags |= 1 << listOrRangeBit;
+
+ if (isLinked)
+ key->flags |= 1 << linkedBit;
+};
+
+} // namespace key
+
+/** @struct GetSensorThresholdsResponse
+ *
+ * Response structure for Get Sensor Thresholds command
+ */
+struct GetSensorThresholdsResponse
+{
+ uint8_t validMask; //!< valid mask
+ uint8_t lowerNonCritical; //!< lower non-critical threshold
+ uint8_t lowerCritical; //!< lower critical threshold
+ uint8_t lowerNonRecoverable; //!< lower non-recoverable threshold
+ uint8_t upperNonCritical; //!< upper non-critical threshold
+ uint8_t upperCritical; //!< upper critical threshold
+ uint8_t upperNonRecoverable; //!< upper non-recoverable threshold
+} __attribute__((packed));
+
+// Body - full record
+#define FULL_RECORD_ID_STR_MAX_LENGTH 16
+
+static const int FRU_RECORD_DEVICE_ID_MAX_LENGTH = 16;
+
+struct SensorDataFullRecordBody
+{
+ uint8_t entity_id;
+ uint8_t entity_instance;
+ uint8_t sensor_initialization;
+ uint8_t sensor_capabilities; // no macro support
+ uint8_t sensor_type;
+ uint8_t event_reading_type;
+ uint8_t supported_assertions[2]; // no macro support
+ uint8_t supported_deassertions[2]; // no macro support
+ uint8_t discrete_reading_setting_mask[2]; // no macro support
+ uint8_t sensor_units_1;
+ uint8_t sensor_units_2_base;
+ uint8_t sensor_units_3_modifier;
+ uint8_t linearization;
+ uint8_t m_lsb;
+ uint8_t m_msb_and_tolerance;
+ uint8_t b_lsb;
+ uint8_t b_msb_and_accuracy_lsb;
+ uint8_t accuracy_and_sensor_direction;
+ uint8_t r_b_exponents;
+ uint8_t analog_characteristic_flags; // no macro support
+ uint8_t nominal_reading;
+ uint8_t normal_max;
+ uint8_t normal_min;
+ uint8_t sensor_max;
+ uint8_t sensor_min;
+ uint8_t upper_nonrecoverable_threshold;
+ uint8_t upper_critical_threshold;
+ uint8_t upper_noncritical_threshold;
+ uint8_t lower_nonrecoverable_threshold;
+ uint8_t lower_critical_threshold;
+ uint8_t lower_noncritical_threshold;
+ uint8_t positive_threshold_hysteresis;
+ uint8_t negative_threshold_hysteresis;
+ uint16_t reserved;
+ uint8_t oem_reserved;
+ uint8_t id_string_info;
+ char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataCompactRecord
+ *
+ * Compact Sensor Record(body) - SDR Type 2
+ */
+struct SensorDataCompactRecordBody
+{
+ uint8_t entity_id;
+ uint8_t entity_instance;
+ uint8_t sensor_initialization;
+ uint8_t sensor_capabilities; // no macro support
+ uint8_t sensor_type;
+ uint8_t event_reading_type;
+ uint8_t supported_assertions[2]; // no macro support
+ uint8_t supported_deassertions[2]; // no macro support
+ uint8_t discrete_reading_setting_mask[2]; // no macro support
+ uint8_t sensor_units_1;
+ uint8_t sensor_units_2_base;
+ uint8_t sensor_units_3_modifier;
+ uint8_t record_sharing[2];
+ uint8_t positive_threshold_hysteresis;
+ uint8_t negative_threshold_hysteresis;
+ uint8_t reserved[3];
+ uint8_t oem_reserved;
+ uint8_t id_string_info;
+ char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataEventRecord
+ *
+ * Event Only Sensor Record(body) - SDR Type 3
+ */
+struct SensorDataEventRecordBody
+{
+ uint8_t entity_id;
+ uint8_t entity_instance;
+ uint8_t sensor_type;
+ uint8_t event_reading_type;
+ uint8_t sensor_record_sharing_1;
+ uint8_t sensor_record_sharing_2;
+ uint8_t reserved;
+ uint8_t oem_reserved;
+ uint8_t id_string_info;
+ char id_string[FULL_RECORD_ID_STR_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecordBody
+ *
+ * FRU Device Locator Record(body) - SDR Type 11
+ */
+struct SensorDataFruRecordBody
+{
+ uint8_t reserved;
+ uint8_t deviceType;
+ uint8_t deviceTypeModifier;
+ uint8_t entityID;
+ uint8_t entityInstance;
+ uint8_t oem;
+ uint8_t deviceIDLen;
+ char deviceID[FRU_RECORD_DEVICE_ID_MAX_LENGTH];
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecordBody
+ *
+ * Entity Association Record(body) - SDR Type 8
+ */
+struct SensorDataEntityRecordBody
+{
+ uint8_t entityId2;
+ uint8_t entityInstance2;
+ uint8_t entityId3;
+ uint8_t entityInstance3;
+ uint8_t entityId4;
+ uint8_t entityInstance4;
+} __attribute__((packed));
+
+namespace body
+{
+
+inline void set_entity_instance_number(uint8_t n,
+ SensorDataFullRecordBody* body)
+{
+ body->entity_instance &= 1 << 7;
+ body->entity_instance |= (n & ~(1 << 7));
+};
+inline void set_entity_physical_entity(SensorDataFullRecordBody* body)
+{
+ body->entity_instance &= ~(1 << 7);
+};
+inline void set_entity_logical_container(SensorDataFullRecordBody* body)
+{
+ body->entity_instance |= 1 << 7;
+};
+
+inline void sensor_scanning_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 0;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 0);
+ };
+};
+inline void event_generation_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 1;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 1);
+ }
+};
+inline void init_types_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 2;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 2);
+ }
+};
+inline void init_hyst_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 3;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 3);
+ }
+};
+inline void init_thresh_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 4;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 4);
+ }
+};
+inline void init_events_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 5;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 5);
+ }
+};
+inline void init_scanning_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 6;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 6);
+ }
+};
+inline void init_settable_state(bool enabled, SensorDataFullRecordBody* body)
+{
+ if (enabled)
+ {
+ body->sensor_initialization |= 1 << 7;
+ }
+ else
+ {
+ body->sensor_initialization &= ~(1 << 7);
+ }
+};
+
+inline void set_percentage(SensorDataFullRecordBody* body)
+{
+ body->sensor_units_1 |= 1 << 0;
+};
+inline void unset_percentage(SensorDataFullRecordBody* body)
+{
+ body->sensor_units_1 &= ~(1 << 0);
+};
+inline void set_modifier_operation(uint8_t op, SensorDataFullRecordBody* body)
+{
+ body->sensor_units_1 &= ~(3 << 1);
+ body->sensor_units_1 |= (op & 0x3) << 1;
+};
+inline void set_rate_unit(uint8_t unit, SensorDataFullRecordBody* body)
+{
+ body->sensor_units_1 &= ~(7 << 3);
+ body->sensor_units_1 |= (unit & 0x7) << 3;
+};
+inline void set_analog_data_format(uint8_t format,
+ SensorDataFullRecordBody* body)
+{
+ body->sensor_units_1 &= ~(3 << 6);
+ body->sensor_units_1 |= (format & 0x3) << 6;
+};
+
+inline void set_m(uint16_t m, SensorDataFullRecordBody* body)
+{
+ body->m_lsb = m & 0xff;
+ body->m_msb_and_tolerance &= ~(3 << 6);
+ body->m_msb_and_tolerance |= ((m & (3 << 8)) >> 2);
+};
+inline void set_tolerance(uint8_t tol, SensorDataFullRecordBody* body)
+{
+ body->m_msb_and_tolerance &= ~0x3f;
+ body->m_msb_and_tolerance |= tol & 0x3f;
+};
+
+inline void set_b(uint16_t b, SensorDataFullRecordBody* body)
+{
+ body->b_lsb = b & 0xff;
+ body->b_msb_and_accuracy_lsb &= ~(3 << 6);
+ body->b_msb_and_accuracy_lsb |= ((b & (3 << 8)) >> 2);
+};
+inline void set_accuracy(uint16_t acc, SensorDataFullRecordBody* body)
+{
+ // bottom 6 bits
+ body->b_msb_and_accuracy_lsb &= ~0x3f;
+ body->b_msb_and_accuracy_lsb |= acc & 0x3f;
+ // top 4 bits
+ body->accuracy_and_sensor_direction &= 0x0f;
+ body->accuracy_and_sensor_direction |= ((acc >> 6) & 0xf) << 4;
+};
+inline void set_accuracy_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+ body->accuracy_and_sensor_direction &= ~(3 << 2);
+ body->accuracy_and_sensor_direction |= (exp & 3) << 2;
+};
+inline void set_sensor_dir(uint8_t dir, SensorDataFullRecordBody* body)
+{
+ body->accuracy_and_sensor_direction &= ~(3 << 0);
+ body->accuracy_and_sensor_direction |= (dir & 3);
+};
+
+inline void set_b_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+ body->r_b_exponents &= 0xf0;
+ body->r_b_exponents |= exp & 0x0f;
+};
+inline void set_r_exp(uint8_t exp, SensorDataFullRecordBody* body)
+{
+ body->r_b_exponents &= 0x0f;
+ body->r_b_exponents |= (exp & 0x0f) << 4;
+};
+
+inline void set_id_strlen(uint8_t len, SensorDataFullRecordBody* body)
+{
+ body->id_string_info &= ~(0x1f);
+ body->id_string_info |= len & 0x1f;
+};
+inline void set_id_strlen(uint8_t len, SensorDataEventRecordBody* body)
+{
+ body->id_string_info &= ~(0x1f);
+ body->id_string_info |= len & 0x1f;
+};
+inline uint8_t get_id_strlen(SensorDataFullRecordBody* body)
+{
+ return body->id_string_info & 0x1f;
+};
+inline void set_id_type(uint8_t type, SensorDataFullRecordBody* body)
+{
+ body->id_string_info &= ~(3 << 6);
+ body->id_string_info |= (type & 0x3) << 6;
+};
+inline void set_id_type(uint8_t type, SensorDataEventRecordBody* body)
+{
+ body->id_string_info &= ~(3 << 6);
+ body->id_string_info |= (type & 0x3) << 6;
+};
+
+inline void set_device_id_strlen(uint8_t len, SensorDataFruRecordBody* body)
+{
+ body->deviceIDLen &= ~(LENGTH_MASK);
+ body->deviceIDLen |= len & LENGTH_MASK;
+};
+
+inline uint8_t get_device_id_strlen(SensorDataFruRecordBody* body)
+{
+ return body->deviceIDLen & LENGTH_MASK;
+};
+
+inline void set_readable_mask(uint8_t mask, SensorDataFullRecordBody* body)
+{
+ body->discrete_reading_setting_mask[1] = mask & 0x3F;
+}
+
+} // namespace body
+
+// More types contained in section 43.17 Sensor Unit Type Codes,
+// IPMI spec v2 rev 1.1
+enum SensorUnitTypeCodes
+{
+ SENSOR_UNIT_UNSPECIFIED = 0,
+ SENSOR_UNIT_DEGREES_C = 1,
+ SENSOR_UNIT_VOLTS = 4,
+ SENSOR_UNIT_AMPERES = 5,
+ SENSOR_UNIT_WATTS = 6,
+ SENSOR_UNIT_JOULES = 7,
+ SENSOR_UNIT_RPM = 18,
+ SENSOR_UNIT_METERS = 34,
+ SENSOR_UNIT_REVOLUTIONS = 41,
+};
+
+struct SensorDataFullRecord
+{
+ SensorDataRecordHeader header;
+ SensorDataRecordKey key;
+ SensorDataFullRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataComapactRecord
+ *
+ * Compact Sensor Record - SDR Type 2
+ */
+struct SensorDataCompactRecord
+{
+ SensorDataRecordHeader header;
+ SensorDataRecordKey key;
+ SensorDataCompactRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataEventRecord
+ *
+ * Event Only Sensor Record - SDR Type 3
+ */
+struct SensorDataEventRecord
+{
+ SensorDataRecordHeader header;
+ SensorDataRecordKey key;
+ SensorDataEventRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataFruRecord
+ *
+ * FRU Device Locator Record - SDR Type 11
+ */
+struct SensorDataFruRecord
+{
+ SensorDataRecordHeader header;
+ SensorDataFruRecordKey key;
+ SensorDataFruRecordBody body;
+} __attribute__((packed));
+
+/** @struct SensorDataEntityRecord
+ *
+ * Entity Association Record - SDR Type 8
+ */
+struct SensorDataEntityRecord
+{
+ SensorDataRecordHeader header;
+ SensorDataEntityRecordKey key;
+ SensorDataEntityRecordBody body;
+} __attribute__((packed));
+
+} // namespace get_sdr
+
+namespace ipmi
+{
+
+namespace sensor
+{
+
+/**
+ * @brief Map offset to the corresponding bit in the assertion byte.
+ *
+ * The discrete sensors support up to 14 states. 0-7 offsets are stored in one
+ * byte and offsets 8-14 in the second byte.
+ *
+ * @param[in] offset - offset number.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setOffset(uint8_t offset, ipmi::sensor::GetSensorResponse* resp)
+{
+ if (offset > 7)
+ {
+ resp->discreteReadingSensorStates |= 1 << (offset - 8);
+ }
+ else
+ {
+ resp->thresholdLevelsStates |= 1 << offset;
+ }
+}
+
+/**
+ * @brief Set the reading field in the response.
+ *
+ * @param[in] offset - offset number.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setReading(uint8_t value, ipmi::sensor::GetSensorResponse* resp)
+{
+ resp->reading = value;
+}
+
+/**
+ * @brief Map the value to the assertion bytes. The assertion states are stored
+ * in 2 bytes.
+ *
+ * @param[in] value - value to mapped to the assertion byte.
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void setAssertionBytes(uint16_t value,
+ ipmi::sensor::GetSensorResponse* resp)
+{
+ resp->thresholdLevelsStates = static_cast<uint8_t>(value & 0x00FF);
+ resp->discreteReadingSensorStates = static_cast<uint8_t>(value >> 8);
+}
+
+/**
+ * @brief Set the scanning enabled bit in the response.
+ *
+ * @param[in/out] resp - get sensor reading response.
+ */
+inline void enableScanning(ipmi::sensor::GetSensorResponse* resp)
+{
+ resp->readingOrStateUnavailable = false;
+ resp->scanningEnabled = true;
+ resp->allEventMessagesEnabled = false;
+}
+
+} // namespace sensor
+
+} // namespace ipmi
diff --git a/settings.cpp b/settings.cpp
new file mode 100644
index 0000000..4564fa4
--- /dev/null
+++ b/settings.cpp
@@ -0,0 +1,74 @@
+#include "settings.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace settings
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
+constexpr auto mapperPath = "/xyz/openbmc_project/object_mapper";
+constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+
+Objects::Objects(sdbusplus::bus_t& bus, const std::vector<Interface>& filter) :
+ bus(bus)
+{
+ ipmi::ObjectTree objectTree;
+ try
+ {
+ objectTree = ipmi::getSubTree(bus, filter);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Failed to call the getSubTree method: {ERROR}", "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ for (auto& iter : objectTree)
+ {
+ const auto& path = iter.first;
+ for (auto& interface : iter.second.begin()->second)
+ {
+ auto found = map.find(interface);
+ if (map.end() != found)
+ {
+ auto& paths = found->second;
+ paths.push_back(path);
+ }
+ else
+ {
+ map.emplace(std::move(interface), std::vector<Path>({path}));
+ }
+ }
+ }
+}
+
+Service Objects::service(const Path& path, const Interface& interface) const
+{
+ using Interfaces = std::vector<Interface>;
+ auto mapperCall =
+ bus.new_method_call(mapperService, mapperPath, mapperIntf, "GetObject");
+ mapperCall.append(path);
+ mapperCall.append(Interfaces({interface}));
+
+ std::map<Service, Interfaces> result;
+ try
+ {
+ auto response = bus.call(mapperCall);
+ response.read(result);
+ return result.begin()->first;
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Invalid response from mapper: {ERROR}", "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+} // namespace settings
diff --git a/settings.hpp b/settings.hpp
new file mode 100644
index 0000000..a3505c3
--- /dev/null
+++ b/settings.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+
+#include <string>
+#include <tuple>
+
+namespace settings
+{
+
+using Path = std::string;
+using Service = std::string;
+using Interface = std::string;
+
+/** @class Objects
+ * @brief Fetch paths of settings d-bus objects of interest, upon construction
+ */
+struct Objects
+{
+ public:
+ /** @brief Constructor - fetch settings objects
+ *
+ * @param[in] bus - The Dbus bus object
+ * @param[in] filter - A vector of settings interfaces the caller is
+ * interested in.
+ */
+ Objects(sdbusplus::bus_t& bus, const std::vector<Interface>& filter);
+ Objects(const Objects&) = default;
+ Objects& operator=(const Objects&) = delete;
+ Objects(Objects&&) = delete;
+ Objects& operator=(Objects&&) = delete;
+ ~Objects() = default;
+
+ /** @brief Fetch d-bus service, given a path and an interface. The
+ * service can't be cached because mapper returns unique
+ * service names.
+ *
+ * @param[in] path - The Dbus object
+ * @param[in] interface - The Dbus interface
+ *
+ * @return std::string - the dbus service
+ */
+ Service service(const Path& path, const Interface& interface) const;
+
+ /** @brief map of settings objects */
+ std::map<Interface, std::vector<Path>> map;
+
+ /** @brief The Dbus bus object */
+ sdbusplus::bus_t& bus;
+};
+
+} // namespace settings
diff --git a/softoff/mainapp.cpp b/softoff/mainapp.cpp
new file mode 100644
index 0000000..0791615
--- /dev/null
+++ b/softoff/mainapp.cpp
@@ -0,0 +1,82 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "softoff.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/exception.hpp>
+#include <xyz/openbmc_project/State/Host/error.hpp>
+
+// Return -1 on any errors to ensure we follow the calling targets OnFailure=
+// path
+int main(int, char**)
+{
+ using namespace phosphor::logging;
+
+ // Get a handle to system dbus.
+ auto bus = sdbusplus::bus::new_default();
+
+ // Add systemd object manager.
+ sdbusplus::server::manager_t(bus, SOFTOFF_OBJPATH);
+
+ // Get default event loop
+ auto event = sdeventplus::Event::get_default();
+
+ // Attach the bus to sd_event to service user requests
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ // Claim the bus. Delaying it until sending SMS_ATN may result
+ // in a race condition between this available and IPMI trying to send
+ // message as a response to ack from host.
+ bus.request_name(SOFTOFF_BUSNAME);
+
+ // Create the SoftPowerOff object.
+ phosphor::ipmi::SoftPowerOff powerObj(bus, event.get(), SOFTOFF_OBJPATH);
+
+ // Wait for client requests until this application has processed
+ // at least one successful SoftPowerOff or we timed out
+ while (!powerObj.isCompleted() && !powerObj.isTimerExpired())
+ {
+ try
+ {
+ event.run(std::nullopt);
+ }
+ catch (const sdeventplus::SdEventError& e)
+ {
+ lg2::error("Failure in processing request: {ERROR}", "ERROR", e);
+ return 1;
+ }
+ }
+
+ // Log an error if we timed out after getting Ack for SMS_ATN and before
+ // getting the Host Shutdown response
+ if (powerObj.isTimerExpired() &&
+ (powerObj.responseReceived() ==
+ phosphor::ipmi::Base::SoftPowerOff::HostResponse::SoftOffReceived))
+ {
+ using error =
+ sdbusplus::error::xyz::openbmc_project::state::host::SoftOffTimeout;
+ using errorMetadata = xyz::openbmc_project::state::host::SoftOffTimeout;
+ report<error>(prev_entry<errorMetadata::TIMEOUT_IN_MSEC>());
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/softoff/meson.build b/softoff/meson.build
new file mode 100644
index 0000000..a723c51
--- /dev/null
+++ b/softoff/meson.build
@@ -0,0 +1,34 @@
+softpower_pre = [
+ boost,
+ ipmid_dep,
+ libsystemd_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ sdeventplus_dep,
+ softoff_dbus,
+]
+
+softpower_lib = static_library(
+ 'softpower_lib',
+ 'softoff.cpp',
+ conf_h,
+ dependencies: softpower_pre,
+ include_directories: root_inc,
+)
+
+softpower_dep = declare_dependency(
+ dependencies: softpower_pre,
+ include_directories: root_inc,
+ link_with: softpower_lib,
+)
+
+executable(
+ 'phosphor-softpoweroff',
+ 'mainapp.cpp',
+ implicit_include_directories: false,
+ dependencies: softpower_dep,
+ include_directories: root_inc,
+ install: true,
+ install_dir: get_option('bindir'),
+)
diff --git a/softoff/softoff.cpp b/softoff/softoff.cpp
new file mode 100644
index 0000000..96695b9
--- /dev/null
+++ b/softoff/softoff.cpp
@@ -0,0 +1,136 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "softoff.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+
+#include <chrono>
+namespace phosphor
+{
+namespace ipmi
+{
+
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+void SoftPowerOff::sendHostShutDownCmd()
+{
+ auto ctrlHostPath =
+ std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
+ auto host = ::ipmi::getService(this->bus, CONTROL_HOST_BUSNAME,
+ ctrlHostPath.c_str());
+
+ auto method = bus.new_method_call(host.c_str(), ctrlHostPath.c_str(),
+ CONTROL_HOST_BUSNAME, "Execute");
+
+ method.append(convertForMessage(Host::Command::SoftOff).c_str());
+ try
+ {
+ auto reply = bus.call(method);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Error in call to control host Execute: {ERROR}", "ERROR",
+ e);
+ // TODO openbmc/openbmc#851 - Once available, throw returned error
+ throw std::runtime_error("Error in call to control host Execute");
+ }
+}
+
+// Function called on host control signals
+void SoftPowerOff::hostControlEvent(sdbusplus::message_t& msg)
+{
+ std::string cmdCompleted{};
+ std::string cmdStatus{};
+
+ msg.read(cmdCompleted, cmdStatus);
+
+ lg2::debug(
+ "Host control signal values, command: {COMMAND}, status:{STATUS}",
+ "COMMAND", cmdCompleted, "STATUS", cmdStatus);
+
+ if (Host::convertResultFromString(cmdStatus) == Host::Result::Success)
+ {
+ // Set our internal property indicating we got host attention
+ sdbusplus::server::xyz::openbmc_project::ipmi::internal::SoftPowerOff::
+ responseReceived(HostResponse::SoftOffReceived);
+
+ // Start timer for host shutdown
+ using namespace std::chrono;
+ auto time = duration_cast<microseconds>(
+ seconds(IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS));
+ auto r = startTimer(time);
+ if (r < 0)
+ {
+ lg2::error(
+ "Failure to start Host shutdown wait timer, ERRNO: {ERRNO}",
+ "ERRNO", lg2::hex, -r);
+ }
+ else
+ {
+ lg2::info("Timer started waiting for host to shutdown, "
+ "TIMEOUT_IN_MSEC: {TIMEOUT_IN_MSEC}",
+ "TIMEOUT_IN_MSEC",
+ (duration_cast<milliseconds>(
+ seconds(IPMI_HOST_SHUTDOWN_COMPLETE_TIMEOUT_SECS)))
+ .count());
+ }
+ }
+ else
+ {
+ // An error on the initial attention is not considered an error, just
+ // exit normally and allow remaining shutdown targets to run
+ lg2::info("Timeout on host attention, continue with power down");
+ completed = true;
+ }
+ return;
+}
+
+// Starts a timer
+int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
+{
+ return timer.start(usec);
+}
+
+// Host Response handler
+auto SoftPowerOff::responseReceived(HostResponse response) -> HostResponse
+{
+ using namespace std::chrono;
+
+ if (response == HostResponse::HostShutdown)
+ {
+ // Disable the timer since Host has quiesced and we are
+ // done with soft power off part
+ auto r = timer.stop();
+ if (r < 0)
+ {
+ lg2::error("Failure to STOP the timer, ERRNO: {ERRNO}", "ERRNO",
+ lg2::hex, -r);
+ }
+
+ // This marks the completion of soft power off sequence.
+ completed = true;
+ }
+
+ return sdbusplus::server::xyz::openbmc_project::ipmi::internal::
+ SoftPowerOff::responseReceived(response);
+}
+
+} // namespace ipmi
+} // namespace phosphor
diff --git a/softoff/softoff.hpp b/softoff/softoff.hpp
new file mode 100644
index 0000000..14742e1
--- /dev/null
+++ b/softoff/softoff.hpp
@@ -0,0 +1,145 @@
+#pragma once
+
+#include "config.h"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdbusplus/timer.hpp>
+#include <xyz/openbmc_project/Control/Host/server.hpp>
+#include <xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/server.hpp>
+
+#include <functional>
+namespace phosphor
+{
+namespace ipmi
+{
+
+namespace Base = sdbusplus::server::xyz::openbmc_project::ipmi::internal;
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
+namespace
+{
+using SoftPowerOffInherit = sdbusplus::server::object_t<Base::SoftPowerOff>;
+}
+
+/** @class SoftPowerOff
+ * @brief Responsible for coordinating Host SoftPowerOff operation
+ */
+class SoftPowerOff : public SoftPowerOffInherit
+{
+ public:
+ /** @brief Constructs SoftPowerOff object.
+ *
+ * @param[in] bus - system dbus handler
+ * @param[in] event - sd_event handler
+ * @param[in] objPath - The Dbus path hosting SoftPowerOff function
+ */
+ SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event, const char* objPath) :
+ SoftPowerOffInherit(bus, objPath,
+ SoftPowerOffInherit::action::defer_emit),
+ bus(bus), timer(event),
+ hostControlSignal(
+ bus,
+ sdbusRule::type::signal() + sdbusRule::member("CommandComplete") +
+ sdbusRule::path("/xyz/openbmc_project/control/host0") +
+ sdbusRule::interface(CONTROL_HOST_BUSNAME) +
+ sdbusRule::argN(0, convertForMessage(Host::Command::SoftOff)),
+ std::bind(std::mem_fn(&SoftPowerOff::hostControlEvent), this,
+ std::placeholders::_1))
+ {
+ // Need to announce since we may get the response
+ // very quickly on host shutdown command
+ emit_object_added();
+
+ // The whole purpose of this application is to send a host shutdown
+ // command and watch for the soft power off to go through. We need
+ // the interface added signal emitted before we send the shutdown
+ // command just to attend to lightning fast response from host
+ sendHostShutDownCmd();
+ }
+
+ /** @brief Tells if the objective of this application is completed */
+ inline auto isCompleted()
+ {
+ return completed;
+ }
+
+ /** @brief Tells if the referenced timer is expired or not */
+ inline auto isTimerExpired()
+ {
+ return timer.isExpired();
+ }
+
+ /** @brief overloaded property setter function
+ *
+ * @param[in] value - One of SoftOffReceived / HostShutdown
+ *
+ * @return Success or exception thrown
+ */
+ HostResponse responseReceived(HostResponse value) override;
+
+ /** @brief Using the base class's getter method */
+ using Base::SoftPowerOff::responseReceived;
+
+ /** @brief Calls to start a timer
+ *
+ * @param[in] usec - Time in microseconds
+ *
+ * @return Success or exception thrown
+ */
+ int startTimer(const std::chrono::microseconds& usec);
+
+ private:
+ // Need this to send SMS_ATTN
+ // TODO : Switch over to using mapper service in a different patch
+ static constexpr auto HOST_IPMI_BUS = "org.openbmc.HostIpmi";
+ static constexpr auto HOST_IPMI_OBJ = "/org/openbmc/HostIpmi/1";
+ static constexpr auto HOST_IPMI_INTF = "org.openbmc.HostIpmi";
+
+ /* @brief sdbusplus handle */
+ sdbusplus::bus_t& bus;
+
+ /** @brief Reference to Timer object */
+ sdbusplus::Timer timer;
+
+ /** @brief Marks the end of life of this application.
+ *
+ * This is set to true if host gives appropriate responses
+ * for the sequence of commands.
+ */
+ bool completed = false;
+
+ /** @brief Subscribe to host control signals
+ *
+ * Protocol is to send the host power off request to the host
+ * control interface and then wait for a signal indicating pass/fail
+ **/
+ sdbusplus::bus::match_t hostControlSignal;
+
+ /** @brief Sends host control command to tell host to shut down
+ *
+ * After sending the command, wait for a signal indicating the status
+ * of the command.
+ *
+ * After receiving the initial response, start a timer for 30 minutes
+ * to let host do a clean shutdown of partitions. When the response is
+ * received from the host, it indicates that BMC can do a power off.
+ * If BMC fails to get any response, then a hard power off would
+ * be forced.
+ *
+ * @return - Does not return anything. Error will result in exception
+ * being thrown
+ */
+ void sendHostShutDownCmd();
+
+ /** @brief Callback function on host control signals
+ *
+ * @param[in] msg - Data associated with subscribed signal
+ *
+ */
+ void hostControlEvent(sdbusplus::message_t& msg);
+};
+} // namespace ipmi
+} // namespace phosphor
diff --git a/storageaddsel.cpp b/storageaddsel.cpp
new file mode 100644
index 0000000..2b9866f
--- /dev/null
+++ b/storageaddsel.cpp
@@ -0,0 +1,69 @@
+#include "error-HostEvent.hpp"
+#include "sensorhandler.hpp"
+
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Logging/Entry/server.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+using namespace std;
+using namespace phosphor::logging;
+using namespace sdbusplus::server::xyz::openbmc_project::logging;
+
+std::string readESEL(const char* fileName)
+{
+ std::string content;
+ std::ifstream handle(fileName);
+
+ if (handle.fail())
+ {
+ lg2::error("Failed to open eSEL, file name: {FILENAME}", "FILENAME",
+ fileName);
+ return content;
+ }
+
+ handle.seekg(0, std::ios::end);
+ content.resize(handle.tellg());
+ handle.seekg(0, std::ios::beg);
+ handle.read(&content[0], content.size());
+ handle.close();
+
+ return content;
+}
+
+void createProcedureLogEntry(uint8_t procedureNum)
+{
+ // Read the eSEL data from the file.
+ static constexpr auto eSELFile = "/tmp/esel";
+ auto eSELData = readESEL(eSELFile);
+
+ // Each byte in eSEL is formatted as %02x with a space between bytes and
+ // insert '/0' at the end of the character array.
+ static constexpr auto byteSeparator = 3;
+ std::unique_ptr<char[]> data(
+ new char[(eSELData.size() * byteSeparator) + 1]());
+
+ for (size_t i = 0; i < eSELData.size(); i++)
+ {
+ sprintf(&data[i * byteSeparator], "%02x ", eSELData[i]);
+ }
+ data[eSELData.size() * byteSeparator] = '\0';
+
+ using error = sdbusplus::error::org::open_power::host::MaintenanceProcedure;
+ using metadata = org::open_power::host::MaintenanceProcedure;
+
+ report<error>(metadata::ESEL(data.get()),
+ metadata::PROCEDURE(static_cast<uint32_t>(procedureNum)));
+}
diff --git a/storageaddsel.hpp b/storageaddsel.hpp
new file mode 100644
index 0000000..9183472
--- /dev/null
+++ b/storageaddsel.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <string>
+
+/** @brief Read eSEL data into a string
+ *
+ * @param[in] filename - filename of file containing eSEL
+ *
+ * @return On success return the eSEL data
+ */
+std::string readESEL(const char* filename);
+
+/** @brief Create a log entry with maintenance procedure
+ *
+ * @param[in] procedureNum - procedure number associated with the log entry
+ */
+void createProcedureLogEntry(uint8_t procedureNum);
diff --git a/storagehandler.cpp b/storagehandler.cpp
new file mode 100644
index 0000000..d85673f
--- /dev/null
+++ b/storagehandler.cpp
@@ -0,0 +1,961 @@
+#include "config.h"
+
+#include "fruread.hpp"
+#include "read_fru_data.hpp"
+#include "selutility.hpp"
+#include "sensorhandler.hpp"
+#include "storageaddsel.hpp"
+
+#include <arpa/inet.h>
+#include <systemd/sd-bus.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Logging/SEL/error.hpp>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdio>
+#include <cstring>
+#include <filesystem>
+#include <optional>
+#include <string>
+#include <variant>
+
+void registerNetFnStorageFunctions() __attribute__((constructor));
+
+unsigned int g_sel_time = 0xFFFFFFFF;
+namespace ipmi
+{
+namespace sensor
+{
+extern const IdInfoMap sensors;
+} // namespace sensor
+} // namespace ipmi
+extern const ipmi::sensor::InvObjectIDMap invSensors;
+extern const FruMap frus;
+constexpr uint8_t eventDataSize = 3;
+namespace
+{
+constexpr auto SystemdTimeService = "org.freedesktop.timedate1";
+constexpr auto SystemdTimePath = "/org/freedesktop/timedate1";
+constexpr auto SystemdTimeInterface = "org.freedesktop.timedate1";
+
+constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
+constexpr auto BMC_TIME_PATH = "/xyz/openbmc_project/time/bmc";
+constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
+constexpr auto PROPERTY_ELAPSED = "Elapsed";
+} // namespace
+
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using namespace phosphor::logging;
+using namespace ipmi::fru;
+using namespace xyz::openbmc_project::logging::sel;
+using SELCreated =
+ sdbusplus::error::xyz::openbmc_project::logging::sel::Created;
+
+using SELRecordID = uint16_t;
+using SELEntry = ipmi::sel::SELEventRecordFormat;
+using SELCacheMap = std::map<SELRecordID, SELEntry>;
+
+SELCacheMap selCacheMap __attribute__((init_priority(101)));
+bool selCacheMapInitialized;
+std::unique_ptr<sdbusplus::bus::match_t> selAddedMatch
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> selRemovedMatch
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> selUpdatedMatch
+ __attribute__((init_priority(101)));
+
+static inline uint16_t getLoggingId(const std::string& p)
+{
+ namespace fs = std::filesystem;
+ fs::path entryPath(p);
+ return std::stoul(entryPath.filename().string());
+}
+
+static inline std::string getLoggingObjPath(uint16_t id)
+{
+ return std::string(ipmi::sel::logBasePath) + "/" + std::to_string(id);
+}
+
+std::optional<std::pair<uint16_t, SELEntry>> parseLoggingEntry(
+ const std::string& p)
+{
+ try
+ {
+ auto id = getLoggingId(p);
+ ipmi::sel::GetSELEntryResponse record{};
+ record = ipmi::sel::convertLogEntrytoSEL(p);
+ return std::pair<uint16_t, SELEntry>({id, std::move(record.event)});
+ }
+ catch (const std::exception& e)
+ {
+ fprintf(stderr, "Failed to convert %s to SEL: %s\n", p.c_str(),
+ e.what());
+ }
+ return std::nullopt;
+}
+
+static void selAddedCallback(sdbusplus::message_t& m)
+{
+ sdbusplus::message::object_path objPath;
+ try
+ {
+ m.read(objPath);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to read object path");
+ return;
+ }
+ std::string p = objPath;
+ auto entry = parseLoggingEntry(p);
+ if (entry)
+ {
+ selCacheMap.insert(std::move(*entry));
+ }
+}
+
+static void selRemovedCallback(sdbusplus::message_t& m)
+{
+ sdbusplus::message::object_path objPath;
+ try
+ {
+ m.read(objPath);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to read object path");
+ }
+ try
+ {
+ std::string p = objPath;
+ selCacheMap.erase(getLoggingId(p));
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Invalid logging entry ID");
+ }
+}
+
+static void selUpdatedCallback(sdbusplus::message_t& m)
+{
+ std::string p = m.get_path();
+ auto entry = parseLoggingEntry(p);
+ if (entry)
+ {
+ selCacheMap.insert_or_assign(entry->first, std::move(entry->second));
+ }
+}
+
+void registerSelCallbackHandler()
+{
+ using namespace sdbusplus::bus::match::rules;
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ if (!selAddedMatch)
+ {
+ selAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+ bus, interfacesAdded(ipmi::sel::logWatchPath),
+ std::bind(selAddedCallback, std::placeholders::_1));
+ }
+ if (!selRemovedMatch)
+ {
+ selRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
+ bus, interfacesRemoved(ipmi::sel::logWatchPath),
+ std::bind(selRemovedCallback, std::placeholders::_1));
+ }
+ if (!selUpdatedMatch)
+ {
+ selUpdatedMatch = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ type::signal() + member("PropertiesChanged"s) +
+ interface("org.freedesktop.DBus.Properties"s) +
+ argN(0, ipmi::sel::logEntryIntf),
+ std::bind(selUpdatedCallback, std::placeholders::_1));
+ }
+}
+
+void initSELCache()
+{
+ registerSelCallbackHandler();
+ ipmi::sel::ObjectPaths paths;
+ try
+ {
+ ipmi::sel::readLoggingObjectPaths(paths);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to get logging object paths");
+ return;
+ }
+ for (const auto& p : paths)
+ {
+ auto entry = parseLoggingEntry(p);
+ if (entry)
+ {
+ selCacheMap.insert(std::move(*entry));
+ }
+ }
+ selCacheMapInitialized = true;
+}
+
+/**
+ * @enum Device access mode
+ */
+enum class AccessMode
+{
+ bytes, ///< Device is accessed by bytes
+ words ///< Device is accessed by words
+};
+
+/** @brief implements the get SEL Info command
+ * @returns IPMI completion code plus response data
+ * - selVersion - SEL revision
+ * - entries - Number of log entries in SEL.
+ * - freeSpace - Free Space in bytes.
+ * - addTimeStamp - Most recent addition timestamp
+ * - eraseTimeStamp - Most recent erase timestamp
+ * - operationSupport - Reserve & Delete SEL operations supported
+ */
+
+ipmi::RspType<uint8_t, // SEL revision.
+ uint16_t, // number of log entries in SEL.
+ uint16_t, // free Space in bytes.
+ uint32_t, // most recent addition timestamp
+ uint32_t, // most recent erase timestamp.
+
+ bool, // SEL allocation info supported
+ bool, // reserve SEL supported
+ bool, // partial Add SEL Entry supported
+ bool, // delete SEL supported
+ uint3_t, // reserved
+ bool // overflow flag
+ >
+ ipmiStorageGetSelInfo()
+{
+ uint16_t entries = 0;
+ // Most recent addition timestamp.
+ uint32_t addTimeStamp = ipmi::sel::invalidTimeStamp;
+
+ if (!selCacheMapInitialized)
+ {
+ // In case the initSELCache() fails, try it again
+ initSELCache();
+ }
+ if (!selCacheMap.empty())
+ {
+ entries = static_cast<uint16_t>(selCacheMap.size());
+
+ try
+ {
+ auto objPath = getLoggingObjPath(selCacheMap.rbegin()->first);
+ addTimeStamp = static_cast<uint32_t>(
+ (ipmi::sel::getEntryTimeStamp(objPath).count()));
+ }
+ catch (const InternalFailure& e)
+ {}
+ catch (const std::runtime_error& e)
+ {
+ lg2::error("runtime error: {ERROR}", "ERROR", e);
+ }
+ }
+
+ constexpr uint8_t selVersion = ipmi::sel::selVersion;
+ constexpr uint16_t freeSpace = 0xFFFF;
+ constexpr uint32_t eraseTimeStamp = ipmi::sel::invalidTimeStamp;
+ constexpr uint3_t reserved{0};
+
+ return ipmi::responseSuccess(
+ selVersion, entries, freeSpace, addTimeStamp, eraseTimeStamp,
+ ipmi::sel::operationSupport::getSelAllocationInfo,
+ ipmi::sel::operationSupport::reserveSel,
+ ipmi::sel::operationSupport::partialAddSelEntry,
+ ipmi::sel::operationSupport::deleteSel, reserved,
+ ipmi::sel::operationSupport::overflow);
+}
+
+ipmi_ret_t getSELEntry(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request,
+ ipmi_response_t response, ipmi_data_len_t data_len,
+ ipmi_context_t)
+{
+ if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
+ {
+ *data_len = 0;
+ return IPMI_CC_REQ_DATA_LEN_INVALID;
+ }
+
+ auto requestData =
+ reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request);
+
+ if (requestData->reservationID != 0)
+ {
+ if (!checkSELReservation(requestData->reservationID))
+ {
+ *data_len = 0;
+ return IPMI_CC_INVALID_RESERVATION_ID;
+ }
+ }
+
+ if (!selCacheMapInitialized)
+ {
+ // In case the initSELCache() fails, try it again
+ initSELCache();
+ }
+
+ if (selCacheMap.empty())
+ {
+ *data_len = 0;
+ return IPMI_CC_SENSOR_INVALID;
+ }
+
+ SELCacheMap::const_iterator iter;
+
+ // Check for the requested SEL Entry.
+ if (requestData->selRecordID == ipmi::sel::firstEntry)
+ {
+ iter = selCacheMap.begin();
+ }
+ else if (requestData->selRecordID == ipmi::sel::lastEntry)
+ {
+ if (selCacheMap.size() > 1)
+ {
+ iter = selCacheMap.end();
+ --iter;
+ }
+ else
+ {
+ // Only one entry exists, return the first
+ iter = selCacheMap.begin();
+ }
+ }
+ else
+ {
+ iter = selCacheMap.find(requestData->selRecordID);
+ if (iter == selCacheMap.end())
+ {
+ *data_len = 0;
+ return IPMI_CC_SENSOR_INVALID;
+ }
+ }
+
+ ipmi::sel::GetSELEntryResponse record{0, iter->second};
+ // Identify the next SEL record ID
+ ++iter;
+ if (iter == selCacheMap.end())
+ {
+ record.nextRecordID = ipmi::sel::lastEntry;
+ }
+ else
+ {
+ record.nextRecordID = iter->first;
+ }
+
+ if (requestData->readLength == ipmi::sel::entireRecord)
+ {
+ std::memcpy(response, &record, sizeof(record));
+ *data_len = sizeof(record);
+ }
+ else
+ {
+ if (requestData->offset >= ipmi::sel::selRecordSize ||
+ requestData->readLength > ipmi::sel::selRecordSize)
+ {
+ *data_len = 0;
+ return IPMI_CC_INVALID_FIELD_REQUEST;
+ }
+
+ auto diff = ipmi::sel::selRecordSize - requestData->offset;
+ auto readLength =
+ std::min(diff, static_cast<int>(requestData->readLength));
+
+ uint16_t nextRecordID = record.nextRecordID;
+ std::memcpy(response, &nextRecordID, sizeof(nextRecordID));
+
+ const ipmi::sel::SELEventRecordFormat* evt = &record.event;
+ std::memcpy(static_cast<uint8_t*>(response) + sizeof(nextRecordID),
+ reinterpret_cast<const uint8_t*>(evt) + requestData->offset,
+ readLength);
+ *data_len = sizeof(nextRecordID) + readLength;
+ }
+
+ return IPMI_CC_OK;
+}
+
+/** @brief implements the delete SEL entry command
+ * @request
+ * - reservationID; // reservation ID.
+ * - selRecordID; // SEL record ID.
+ *
+ * @returns ipmi completion code plus response data
+ * - Record ID of the deleted record
+ */
+ipmi::RspType<uint16_t // deleted record ID
+ >
+ deleteSELEntry(uint16_t reservationID, uint16_t selRecordID)
+{
+ namespace fs = std::filesystem;
+
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+
+ // Per the IPMI spec, need to cancel the reservation when a SEL entry is
+ // deleted
+ cancelSELReservation();
+
+ if (!selCacheMapInitialized)
+ {
+ // In case the initSELCache() fails, try it again
+ initSELCache();
+ }
+
+ if (selCacheMap.empty())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ SELCacheMap::const_iterator iter;
+ uint16_t delRecordID = 0;
+
+ if (selRecordID == ipmi::sel::firstEntry)
+ {
+ delRecordID = selCacheMap.begin()->first;
+ }
+ else if (selRecordID == ipmi::sel::lastEntry)
+ {
+ delRecordID = selCacheMap.rbegin()->first;
+ }
+ else
+ {
+ delRecordID = selRecordID;
+ }
+
+ iter = selCacheMap.find(delRecordID);
+ if (iter == selCacheMap.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ std::string service;
+
+ auto objPath = getLoggingObjPath(iter->first);
+ try
+ {
+ service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, objPath);
+ }
+ catch (const std::runtime_error& e)
+ {
+ lg2::error("runtime error: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
+ ipmi::sel::logDeleteIntf, "Delete");
+ try
+ {
+ auto reply = bus.call(methodCall);
+ }
+ catch (const std::exception& e)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(delRecordID);
+}
+
+/** @brief implements the Clear SEL command
+ * @request
+ * - reservationID // Reservation ID.
+ * - clr // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) }
+ * - eraseOperation; // requested operation.
+ *
+ * @returns ipmi completion code plus response data
+ * - erase status
+ */
+
+ipmi::RspType<uint8_t // erase status
+ >
+ clearSEL(uint16_t reservationID, const std::array<char, 3>& clr,
+ uint8_t eraseOperation)
+{
+ static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'};
+ if (clr != clrOk)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (!checkSELReservation(reservationID))
+ {
+ return ipmi::responseInvalidReservationId();
+ }
+
+ /*
+ * Erasure status cannot be fetched from DBUS, so always return erasure
+ * status as `erase completed`.
+ */
+ if (eraseOperation == ipmi::sel::getEraseStatus)
+ {
+ return ipmi::responseSuccess(
+ static_cast<uint8_t>(ipmi::sel::eraseComplete));
+ }
+
+ // Check that initiate erase is correct
+ if (eraseOperation != ipmi::sel::initiateErase)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
+ cancelSELReservation();
+
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ auto service = ipmi::getService(bus, ipmi::sel::logIntf, ipmi::sel::logObj);
+ auto method =
+ bus.new_method_call(service.c_str(), ipmi::sel::logObj,
+ ipmi::sel::logIntf, ipmi::sel::logDeleteAllMethod);
+ try
+ {
+ bus.call_noreply(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Error eraseAll: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess(
+ static_cast<uint8_t>(ipmi::sel::eraseComplete));
+}
+
+/** @brief implements the get SEL time command
+ * @returns IPMI completion code plus response data
+ * -current time
+ */
+ipmi::RspType<uint32_t> // current time
+ ipmiStorageGetSelTime()
+{
+ using namespace std::chrono;
+ uint64_t bmc_time_usec = 0;
+ std::stringstream bmcTime;
+
+ try
+ {
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
+ auto propValue = ipmi::getDbusProperty(
+ bus, service, BMC_TIME_PATH, TIME_INTERFACE, PROPERTY_ELAPSED);
+ bmc_time_usec = std::get<uint64_t>(propValue);
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("exception message: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ lg2::debug("BMC time: {BMC_TIME}", "BMC_TIME",
+ duration_cast<seconds>(microseconds(bmc_time_usec)).count());
+
+ // Time is really long int but IPMI wants just uint32. This works okay until
+ // the number of seconds since 1970 overflows uint32 size.. Still a whole
+ // lot of time here to even think about that.
+ return ipmi::responseSuccess(
+ duration_cast<seconds>(microseconds(bmc_time_usec)).count());
+}
+
+/** @brief implements the set SEL time command
+ * @param selDeviceTime - epoch time
+ * -local time as the number of seconds from 00:00:00, January 1, 1970
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime)
+{
+ using namespace std::chrono;
+ microseconds usec{seconds(selDeviceTime)};
+
+ try
+ {
+ sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
+ bool ntp = std::get<bool>(
+ ipmi::getDbusProperty(bus, SystemdTimeService, SystemdTimePath,
+ SystemdTimeInterface, "NTP"));
+ if (ntp)
+ {
+ return ipmi::responseCommandNotAvailable();
+ }
+
+ auto service = ipmi::getService(bus, TIME_INTERFACE, BMC_TIME_PATH);
+ std::variant<uint64_t> value{(uint64_t)usec.count()};
+
+ // Set bmc time
+ auto method = bus.new_method_call(service.c_str(), BMC_TIME_PATH,
+ DBUS_PROPERTIES, "Set");
+
+ method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
+ auto reply = bus.call(method);
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("exception message: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ return ipmi::responseSuccess();
+}
+
+/** @brief implements the get SEL timezone command
+ * @returns IPMI completion code plus response data
+ * -current timezone
+ */
+ipmi::RspType<int16_t> ipmiStorageGetSelTimeUtcOffset()
+{
+ time_t timep;
+ struct tm* gmTime;
+ struct tm* localTime;
+
+ time(&timep);
+ localTime = localtime(&timep);
+ auto validLocalTime = mktime(localTime);
+ gmTime = gmtime(&timep);
+ auto validGmTime = mktime(gmTime);
+ auto timeEquation = (validLocalTime - validGmTime) / 60;
+
+ return ipmi::responseSuccess(timeEquation);
+}
+
+/** @brief implements the reserve SEL command
+ * @returns IPMI completion code plus response data
+ * - SEL reservation ID.
+ */
+ipmi::RspType<uint16_t> ipmiStorageReserveSel()
+{
+ return ipmi::responseSuccess(reserveSel());
+}
+
+/** @brief implements the Add SEL entry command
+ * @request
+ *
+ * - recordID ID used for SEL Record access
+ * - recordType Record Type
+ * - timeStamp Time when event was logged. LS byte first
+ * - generatorID software ID if event was generated from
+ * system software
+ * - evmRev event message format version
+ * - sensorType sensor type code for service that generated
+ * the event
+ * - sensorNumber number of sensors that generated the event
+ * - eventDir event dir
+ * - eventData event data field contents
+ *
+ * @returns ipmi completion code plus response data
+ * - RecordID of the Added SEL entry
+ */
+ipmi::RspType<uint16_t // recordID of the Added SEL entry
+ >
+ ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType,
+ [[maybe_unused]] uint32_t timeStamp, uint16_t generatorID,
+ [[maybe_unused]] uint8_t evmRev,
+ [[maybe_unused]] uint8_t sensorType, uint8_t sensorNumber,
+ uint8_t eventDir,
+ std::array<uint8_t, eventDataSize> eventData)
+{
+ std::string objpath;
+ static constexpr auto systemRecordType = 0x02;
+#ifdef OPEN_POWER_SUPPORT
+ // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
+ // a maintenance procedure associated with eSEL record.
+ static constexpr auto procedureType = 0xDE;
+#endif
+ cancelSELReservation();
+ if (recordType == systemRecordType)
+ {
+ for (const auto& it : invSensors)
+ {
+ if (it.second.sensorID == sensorNumber)
+ {
+ objpath = it.first;
+ break;
+ }
+ }
+ auto selDataStr = ipmi::sel::toHexStr(eventData);
+
+ bool assert = (eventDir & 0x80) ? false : true;
+
+ recordID = report<SELCreated>(
+ Created::RECORD_TYPE(recordType),
+ Created::GENERATOR_ID(generatorID),
+ Created::SENSOR_DATA(selDataStr.c_str()),
+ Created::EVENT_DIR(assert), Created::SENSOR_PATH(objpath.c_str()));
+ }
+#ifdef OPEN_POWER_SUPPORT
+ else if (recordType == procedureType)
+ {
+ // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
+ // procedure number.
+ createProcedureLogEntry(sensorType);
+ }
+#endif
+
+ return ipmi::responseSuccess(recordID);
+}
+
+bool isFruPresent(ipmi::Context::ptr& ctx, const std::string& fruPath)
+{
+ using namespace ipmi::fru;
+
+ std::string service;
+ boost::system::error_code ec =
+ getService(ctx, invItemInterface, invObjPath + fruPath, service);
+ if (!ec)
+ {
+ bool result;
+ ec = ipmi::getDbusProperty(ctx, service, invObjPath + fruPath,
+ invItemInterface, itemPresentProp, result);
+ if (!ec)
+ {
+ return result;
+ }
+ }
+
+ ipmi::ObjectValueTree managedObjects;
+ ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager",
+ "/xyz/openbmc_project/inventory", managedObjects);
+ if (!ec)
+ {
+ auto connection = managedObjects.find(fruPath);
+ if (connection != managedObjects.end())
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/** @brief implements the get FRU Inventory Area Info command
+ *
+ * @returns IPMI completion code plus response data
+ * - FRU Inventory area size in bytes,
+ * - access bit
+ **/
+ipmi::RspType<uint16_t, // FRU Inventory area size in bytes,
+ uint8_t // access size (bytes / words)
+ >
+ ipmiStorageGetFruInvAreaInfo(ipmi::Context::ptr ctx, uint8_t fruID)
+{
+ auto iter = frus.find(fruID);
+ if (iter == frus.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ auto path = iter->second[0].path;
+ if (!isFruPresent(ctx, path))
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ try
+ {
+ return ipmi::responseSuccess(
+ static_cast<uint16_t>(getFruAreaData(fruID).size()),
+ static_cast<uint8_t>(AccessMode::bytes));
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+/**@brief implements the Read FRU Data command
+ * @param fruDeviceId - FRU device ID. FFh = reserved
+ * @param offset - FRU inventory offset to read
+ * @param readCount - count to read
+ *
+ * @return IPMI completion code plus response data
+ * - returnCount - response data count.
+ * - data - response data
+ */
+ipmi::RspType<uint8_t, // count returned
+ std::vector<uint8_t>> // FRU data
+ ipmiStorageReadFruData(uint8_t fruDeviceId, uint16_t offset,
+ uint8_t readCount)
+{
+ if (fruDeviceId == 0xFF)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ auto iter = frus.find(fruDeviceId);
+ if (iter == frus.end())
+ {
+ return ipmi::responseSensorInvalid();
+ }
+
+ try
+ {
+ const auto& fruArea = getFruAreaData(fruDeviceId);
+ auto size = fruArea.size();
+
+ if (offset >= size)
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ // Write the count of response data.
+ uint8_t returnCount;
+ if ((offset + readCount) <= size)
+ {
+ returnCount = readCount;
+ }
+ else
+ {
+ returnCount = size - offset;
+ }
+
+ std::vector<uint8_t> fruData((fruArea.begin() + offset),
+ (fruArea.begin() + offset + returnCount));
+
+ return ipmi::responseSuccess(returnCount, fruData);
+ }
+ catch (const InternalFailure& e)
+ {
+ lg2::error("Internal Failure: {ERROR}", "ERROR", e);
+ return ipmi::responseUnspecifiedError();
+ }
+}
+
+ipmi::RspType<uint8_t, // SDR version
+ uint16_t, // record count LS first
+ uint16_t, // free space in bytes, LS first
+ uint32_t, // addition timestamp LS first
+ uint32_t, // deletion timestamp LS first
+ uint8_t> // operation Support
+ ipmiGetRepositoryInfo()
+{
+ constexpr uint8_t sdrVersion = 0x51;
+ constexpr uint16_t freeSpace = 0xFFFF;
+ constexpr uint32_t additionTimestamp = 0x0;
+ constexpr uint32_t deletionTimestamp = 0x0;
+ constexpr uint8_t operationSupport = 0;
+
+ // Get SDR count. This returns the total number of SDRs in the device.
+ const auto& entityRecords =
+ ipmi::sensor::EntityInfoMapContainer::getContainer()
+ ->getIpmiEntityRecords();
+ uint16_t records =
+ ipmi::sensor::sensors.size() + frus.size() + entityRecords.size();
+
+ return ipmi::responseSuccess(sdrVersion, records, freeSpace,
+ additionTimestamp, deletionTimestamp,
+ operationSupport);
+}
+
+void registerNetFnStorageFunctions()
+{
+ selCacheMapInitialized = false;
+ initSELCache();
+ // Handlers with dbus-sdr handler implementation.
+ // Do not register the hander if it dynamic sensors stack is used.
+
+#ifndef FEATURE_DYNAMIC_SENSORS
+
+#ifndef FEATURE_DYNAMIC_STORAGES_ONLY
+ // <Get SEL Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+ ipmiStorageGetSelInfo);
+
+ // <Get SEL Timezone>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelTimeUtcOffset,
+ ipmi::Privilege::User,
+ ipmiStorageGetSelTimeUtcOffset);
+
+ // <Get SEL Entry>
+ ipmi_register_callback(NETFUN_STORAGE, ipmi::storage::cmdGetSelEntry,
+ nullptr, getSELEntry, PRIVILEGE_USER);
+
+ // <Delete SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdDeleteSelEntry,
+ ipmi::Privilege::Operator, deleteSELEntry);
+
+ // <Add SEL Entry>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdAddSelEntry,
+ ipmi::Privilege::Operator, ipmiStorageAddSEL);
+
+ // <Clear SEL>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
+ clearSEL);
+
+ // <Get FRU Inventory Area Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetFruInventoryAreaInfo,
+ ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
+
+ // <READ FRU Data>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReadFruData,
+ ipmi::Privilege::Operator, ipmiStorageReadFruData);
+
+#endif // FEATURE_DYNAMIC_STORAGES_ONLY
+
+ // <Get Repository Info>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSdrRepositoryInfo,
+ ipmi::Privilege::User, ipmiGetRepositoryInfo);
+
+ // <Reserve SDR Repository>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReserveSdrRepository,
+ ipmi::Privilege::User, ipmiSensorReserveSdr);
+
+ // <Get SDR>
+ ipmi_register_callback(NETFUN_STORAGE, ipmi::storage::cmdGetSdr, nullptr,
+ ipmi_sen_get_sdr, PRIVILEGE_USER);
+
+#endif
+
+ // Common Handers used by both implementation.
+
+ // <Reserve SEL>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdReserveSel, ipmi::Privilege::User,
+ ipmiStorageReserveSel);
+
+ // <Get SEL Time>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
+ ipmiStorageGetSelTime);
+
+ // <Set SEL Time>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+ ipmi::storage::cmdSetSelTime,
+ ipmi::Privilege::Operator, ipmiStorageSetSelTime);
+
+ ipmi::fru::registerCallbackHandler();
+ return;
+}
diff --git a/subprojects/boost.wrap b/subprojects/boost.wrap
new file mode 100644
index 0000000..5912c6f
--- /dev/null
+++ b/subprojects/boost.wrap
@@ -0,0 +1,9 @@
+[wrap-file]
+directory = boost-1.84.0
+
+source_url = https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.gz
+source_hash = 4d27e9efed0f6f152dc28db6430b9d3dfb40c0345da7342eaa5a987dde57bd95
+source_filename = 1_84_0.tar.gz
+
+[provide]
+boost = boost_dep
diff --git a/subprojects/nlohmann_json.wrap b/subprojects/nlohmann_json.wrap
new file mode 100644
index 0000000..3745380
--- /dev/null
+++ b/subprojects/nlohmann_json.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+revision = HEAD
+url = https://github.com/nlohmann/json.git
+
+[provide]
+nlohmann_json = nlohmann_json_dep
diff --git a/subprojects/phosphor-dbus-interfaces.wrap b/subprojects/phosphor-dbus-interfaces.wrap
index 346aa0c..0bf731e 100644
--- a/subprojects/phosphor-dbus-interfaces.wrap
+++ b/subprojects/phosphor-dbus-interfaces.wrap
@@ -1,3 +1,4 @@
+
[wrap-git]
url = https://github.com/openbmc/phosphor-dbus-interfaces.git
revision = HEAD
diff --git a/subprojects/phosphor-logging.wrap b/subprojects/phosphor-logging.wrap
index 71eee8b..6876a6e 100644
--- a/subprojects/phosphor-logging.wrap
+++ b/subprojects/phosphor-logging.wrap
@@ -1,5 +1,5 @@
[wrap-git]
-url = https://github.com/openbmc/phosphor-logging.git
+url = https://github.com/openbmc/phosphor-logging
revision = HEAD
[provide]
diff --git a/subprojects/sdbusplus.wrap b/subprojects/sdbusplus.wrap
index 7b076d0..42cfcee 100644
--- a/subprojects/sdbusplus.wrap
+++ b/subprojects/sdbusplus.wrap
@@ -1,5 +1,5 @@
[wrap-git]
-url = https://github.com/openbmc/sdbusplus.git
+url = https://github.com/openbmc/sdbusplus
revision = HEAD
[provide]
diff --git a/subprojects/sdeventplus.wrap b/subprojects/sdeventplus.wrap
new file mode 100644
index 0000000..a506404
--- /dev/null
+++ b/subprojects/sdeventplus.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/sdeventplus
+revision = HEAD
+
+[provide]
+sdeventplus = sdeventplus_dep
diff --git a/sys_info_param.cpp b/sys_info_param.cpp
new file mode 100644
index 0000000..79bb29c
--- /dev/null
+++ b/sys_info_param.cpp
@@ -0,0 +1,33 @@
+#include "sys_info_param.hpp"
+
+std::tuple<bool, std::string> SysInfoParamStore::lookup(
+ uint8_t paramSelector) const
+{
+ const auto iterator = params.find(paramSelector);
+ if (iterator == params.end())
+ {
+ return std::make_tuple(false, "");
+ }
+
+ auto& callback = iterator->second;
+ auto s = callback();
+ return std::make_tuple(true, s);
+}
+
+void SysInfoParamStore::update(uint8_t paramSelector, const std::string& s)
+{
+ // Add a callback that captures a copy of the string passed and returns it
+ // when invoked.
+
+ // clang-format off
+ update(paramSelector, [s]() {
+ return s;
+ });
+ // clang-format on
+}
+
+void SysInfoParamStore::update(uint8_t paramSelector,
+ const std::function<std::string()>& callback)
+{
+ params[paramSelector] = callback;
+}
diff --git a/sys_info_param.hpp b/sys_info_param.hpp
new file mode 100644
index 0000000..5f6607d
--- /dev/null
+++ b/sys_info_param.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <string>
+#include <tuple>
+
+/**
+ * Key-value store for string-type system info parameters.
+ */
+class SysInfoParamStoreIntf
+{
+ public:
+ virtual ~SysInfoParamStoreIntf() {}
+
+ /**
+ * Returns true if parameter is found. If and only if s is non-null,
+ * invokes the parameter's callback and writes the value.
+ *
+ * @param[in] paramSelector - the key to lookup.
+ * @return tuple of bool and string, true if parameter is found and
+ * string set accordingly.
+ */
+ virtual std::tuple<bool, std::string> lookup(
+ uint8_t paramSelector) const = 0;
+
+ /**
+ * Update a parameter by its code with a string value.
+ *
+ * @param[in] paramSelector - the key to update.
+ * @param[in] s - the value to set.
+ */
+ virtual void update(uint8_t paramSelector, const std::string& s) = 0;
+
+ /**
+ * Update a parameter by its code with a callback that is called to retrieve
+ * its value whenever called. Callback must be idempotent, as it may be
+ * called multiple times by the host to retrieve the parameter by chunks.
+ *
+ * @param[in] paramSelector - the key to update.
+ * @param[in] callback - the callback to use for parameter retrieval.
+ */
+ virtual void update(uint8_t paramSelector,
+ const std::function<std::string()>& callback) = 0;
+
+ // TODO: Store "read-only" flag for each parameter.
+ // TODO: Function to erase a parameter?
+};
+
+/**
+ * Implement the system info parameters store as a map of callbacks.
+ */
+class SysInfoParamStore : public SysInfoParamStoreIntf
+{
+ public:
+ std::tuple<bool, std::string> lookup(uint8_t paramSelector) const override;
+ void update(uint8_t paramSelector, const std::string& s) override;
+ void update(uint8_t paramSelector,
+ const std::function<std::string()>& callback) override;
+
+ private:
+ std::map<uint8_t, std::function<std::string()>> params;
+};
diff --git a/systemintfcmds.cpp b/systemintfcmds.cpp
new file mode 100644
index 0000000..c7206af
--- /dev/null
+++ b/systemintfcmds.cpp
@@ -0,0 +1,189 @@
+#include "config.h"
+
+#include "systemintfcmds.hpp"
+
+#include "host-cmd-manager.hpp"
+#include "host-interface.hpp"
+
+#include <ipmid-host/cmd.hpp>
+#include <ipmid/api.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <cstring>
+#include <fstream>
+
+void registerNetFnAppFunctions() __attribute__((constructor));
+
+using namespace sdbusplus::server::xyz::openbmc_project::control;
+
+// For accessing Host command manager
+using cmdManagerPtr = std::unique_ptr<phosphor::host::command::Manager>;
+extern cmdManagerPtr& ipmid_get_host_cmd_manager();
+
+//-------------------------------------------------------------------
+// Called by Host post response from Get_Message_Flags
+//-------------------------------------------------------------------
+ipmi::RspType<uint16_t, // id
+ uint8_t, // type
+ uint24_t, // manuf_id
+ uint32_t, // timestamp
+ uint8_t, // netfun
+ uint8_t, // cmd
+ std::array<uint8_t, 4> // data
+ >
+ ipmiAppReadEventBuffer(ipmi::Context::ptr& ctx)
+{
+ // require this to be limited to system interface
+ if (ctx->channel != ipmi::channelSystemIface)
+ {
+ return ipmi::responseInvalidCommand();
+ }
+
+ constexpr uint16_t selOemId = 0x5555;
+ constexpr uint8_t selRecordTypeOem = 0xc0;
+
+ // read manufacturer ID from dev_id file
+ static uint24_t manufId{};
+ if (!manufId)
+ {
+ const char* filename = "/usr/share/ipmi-providers/dev_id.json";
+ std::ifstream devIdFile(filename);
+ if (devIdFile.is_open())
+ {
+ auto data = nlohmann::json::parse(devIdFile, nullptr, false);
+ if (!data.is_discarded())
+ {
+ manufId = data.value("manuf_id", 0);
+ }
+ }
+ }
+
+ constexpr uint32_t timestamp{0};
+
+ // per IPMI spec NetFuntion for OEM
+ constexpr uint8_t netfun = 0x3a;
+
+ // Read from the Command Manager queue. What gets returned is a
+ // pair of <command, data> that can be directly used here
+ const auto& [cmd, data0] = ipmid_get_host_cmd_manager()->getNextCommand();
+ constexpr uint8_t dataUnused = 0xff;
+
+ return ipmi::responseSuccess(
+ selOemId, selRecordTypeOem, manufId, timestamp, netfun, cmd,
+ std::to_array<uint8_t>({data0, dataUnused, dataUnused, dataUnused}));
+}
+
+//---------------------------------------------------------------------
+// Called by Host on seeing a SMS_ATN bit set. Return a hardcoded
+// value of 0x0 to indicate Event Message Buffer is not supported
+//-------------------------------------------------------------------
+ipmi::RspType<uint8_t> ipmiAppGetMessageFlags()
+{
+ // From IPMI spec V2.0 for Get Message Flags Command :
+ // bit:[1] from LSB : 1b = Event Message Buffer Full.
+ // Return as 0 if Event Message Buffer is not supported,
+ // or when the Event Message buffer is disabled.
+ // This path is used to communicate messages to the host
+ // from within the phosphor::host::command::Manager
+ constexpr uint8_t setEventMsgBufferNotSupported = 0x0;
+ return ipmi::responseSuccess(setEventMsgBufferNotSupported);
+}
+
+ipmi::RspType<bool, // Receive Message Queue Interrupt Enabled
+ bool, // Event Message Buffer Full Interrupt Enabled
+ bool, // Event Message Buffer Enabled
+ bool, // System Event Logging Enabled
+ uint1_t, // Reserved
+ bool, // OEM 0 enabled
+ bool, // OEM 1 enabled
+ bool // OEM 2 enabled
+ >
+ ipmiAppGetBMCGlobalEnable()
+{
+ return ipmi::responseSuccess(true, false, false, true, 0, false, false,
+ false);
+}
+
+ipmi::RspType<> ipmiAppSetBMCGlobalEnable(
+ ipmi::Context::ptr ctx, bool receiveMessageQueueInterruptEnabled,
+ bool eventMessageBufferFullInterruptEnabled, bool eventMessageBufferEnabled,
+ bool systemEventLogEnable, uint1_t reserved, bool OEM0Enabled,
+ bool OEM1Enabled, bool OEM2Enabled)
+{
+ ipmi::ChannelInfo chInfo;
+
+ if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess)
+ {
+ lg2::error("Failed to get Channel Info, channel={CHANNEL}", "CHANNEL",
+ ctx->channel);
+ return ipmi::responseUnspecifiedError();
+ }
+
+ if (chInfo.mediumType !=
+ static_cast<uint8_t>(ipmi::EChannelMediumType::systemInterface))
+ {
+ lg2::error("Error - supported only in system interface");
+ return ipmi::responseCommandNotAvailable();
+ }
+
+ // Recv Message Queue and SEL are enabled by default.
+ // Event Message buffer are disabled by default (not supported).
+ // Any request that try to change the mask will be rejected
+ if (!receiveMessageQueueInterruptEnabled || !systemEventLogEnable ||
+ eventMessageBufferFullInterruptEnabled || eventMessageBufferEnabled ||
+ OEM0Enabled || OEM1Enabled || OEM2Enabled || reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ return ipmi::responseSuccess();
+}
+
+namespace
+{
+// Static storage to keep the object alive during process life
+std::unique_ptr<phosphor::host::command::Host> host
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::server::manager_t> objManager
+ __attribute__((init_priority(101)));
+} // namespace
+
+void registerNetFnAppFunctions()
+{
+ // <Read Event Message Buffer>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdReadEventMessageBuffer,
+ ipmi::Privilege::Admin, ipmiAppReadEventBuffer);
+
+ // <Set BMC Global Enables>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetBmcGlobalEnables,
+ ipmi::Privilege::Admin, ipmiAppSetBMCGlobalEnable);
+
+ // <Get BMC Global Enables>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetBmcGlobalEnables,
+ ipmi::Privilege::User, ipmiAppGetBMCGlobalEnable);
+
+ // <Get Message Flags>
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetMessageFlags, ipmi::Privilege::Admin,
+ ipmiAppGetMessageFlags);
+
+ // Create new xyz.openbmc_project.host object on the bus
+ auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
+
+ std::unique_ptr<sdbusplus::asio::connection>& sdbusp =
+ ipmid_get_sdbus_plus_handler();
+
+ // Add sdbusplus ObjectManager.
+ objManager = std::make_unique<sdbusplus::server::manager_t>(
+ *sdbusp, CONTROL_HOST_OBJ_MGR);
+
+ host = std::make_unique<phosphor::host::command::Host>(
+ *sdbusp, objPath.c_str());
+ sdbusp->request_name(CONTROL_HOST_BUSNAME);
+
+ return;
+}
diff --git a/systemintfcmds.hpp b/systemintfcmds.hpp
new file mode 100644
index 0000000..b263a96
--- /dev/null
+++ b/systemintfcmds.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdint.h>
+
+// These are per skiboot ipmi-sel code
+
+// Minor command for soft shurdown
+#define SOFT_OFF 0x00
+// Major command for Any kind of power ops
+#define CMD_POWER 0x04
+// Major command for the heartbeat operation (verify host is alive)
+#define CMD_HEARTBEAT 0xFF
diff --git a/test/dbus-sdr/sensorcommands_unittest.cpp b/test/dbus-sdr/sensorcommands_unittest.cpp
new file mode 100644
index 0000000..bcd2f44
--- /dev/null
+++ b/test/dbus-sdr/sensorcommands_unittest.cpp
@@ -0,0 +1,489 @@
+#include "dbus-sdr/sensorutils.hpp"
+
+#include <cmath>
+
+#include "gtest/gtest.h"
+
+// There is a surprising amount of slop in the math,
+// thanks to all the rounding and conversion.
+// The "x" byte value can drift by up to 2 away, I have seen.
+static constexpr int8_t expectedSlopX = 2;
+
+// Unlike expectedSlopX, this is a ratio, not an integer
+// It scales based on the range of "y"
+static constexpr double expectedSlopY = 0.01;
+
+// The algorithm here was copied from ipmitool
+// sdr_convert_sensor_reading() function
+// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
+double ipmitool_y_from_x(uint8_t x, int m, int k2_rExp, int b, int k1_bExp,
+ bool bSigned)
+{
+ double result;
+
+ // Rename to exactly match names and types (except analog) from ipmitool
+ uint8_t val = x;
+ double k1 = k1_bExp;
+ double k2 = k2_rExp;
+ int analog = bSigned ? 2 : 0;
+
+ // Begin paste here
+ // Only change is to comment out complicated structure in switch statement
+
+ switch (/*sensor->cmn.unit.*/ analog)
+ {
+ case 0:
+ result = (double)(((m * val) + (b * pow(10, k1))) * pow(10, k2));
+ break;
+ case 1:
+ if (val & 0x80)
+ val++;
+ /* Deliberately fall through to case 2. */
+ [[fallthrough]];
+ case 2:
+ result =
+ (double)(((m * (int8_t)val) + (b * pow(10, k1))) * pow(10, k2));
+ break;
+ default:
+ /* Oops! This isn't an analog sensor. */
+ return 0.0;
+ }
+
+ // End paste here
+ // Ignoring linearization curves and postprocessing that follows,
+ // assuming all sensors are perfectly linear
+ return result;
+}
+
+void testValue(int x, double y, int16_t M, int8_t rExp, int16_t B, int8_t bExp,
+ bool bSigned, double yRange)
+{
+ double yRoundtrip;
+ int result;
+
+ // There is intentionally no exception catching here,
+ // because if getSensorAttributes() returned true,
+ // it is a promise that all of these should work.
+ if (bSigned)
+ {
+ int8_t expect = x;
+ int8_t actual =
+ ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+ result = actual;
+ yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+ EXPECT_NEAR(actual, expect, expectedSlopX);
+ }
+ else
+ {
+ uint8_t expect = x;
+ uint8_t actual =
+ ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
+
+ result = actual;
+ yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
+
+ EXPECT_NEAR(actual, expect, expectedSlopX);
+ }
+
+ // Scale the amount of allowed slop in y based on range, so ratio similar
+ double yTolerance = yRange * expectedSlopY;
+ // double yError = std::abs(y - yRoundtrip);
+
+ EXPECT_NEAR(y, yRoundtrip, yTolerance);
+
+ char szFormat[1024];
+ sprintf(szFormat,
+ "Value | xExpect %4d | xResult %4d "
+ "| M %5d | rExp %3d "
+ "| B %5d | bExp %3d | bSigned %1d | y %18.3f | yRoundtrip %18.3f\n",
+ x, result, M, (int)rExp, B, (int)bExp, (int)bSigned, y, yRoundtrip);
+ std::cout << szFormat;
+}
+
+void testBounds(double yMin, double yMax, bool bExpectedOutcome = true)
+{
+ int16_t mValue;
+ int8_t rExp;
+ int16_t bValue;
+ int8_t bExp;
+ bool bSigned;
+ bool result;
+
+ result = ipmi::getSensorAttributes(yMax, yMin, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_EQ(result, bExpectedOutcome);
+
+ if (!result)
+ {
+ return;
+ }
+
+ char szFormat[1024];
+ sprintf(szFormat,
+ "Bounds | yMin %18.3f | yMax %18.3f | M %5d"
+ " | rExp %3d | B %5d | bExp %3d | bSigned %1d\n",
+ yMin, yMax, mValue, (int)rExp, bValue, (int)bExp, (int)bSigned);
+ std::cout << szFormat;
+
+ double y50p = (yMin + yMax) / 2.0;
+
+ // Average the average
+ double y25p = (yMin + y50p) / 2.0;
+ double y75p = (y50p + yMax) / 2.0;
+
+ // This range value is only used for tolerance checking, not computation
+ double yRange = yMax - yMin;
+
+ if (bSigned)
+ {
+ int8_t xMin = -128;
+ int8_t x25p = -64;
+ int8_t x50p = 0;
+ int8_t x75p = 64;
+ int8_t xMax = 127;
+
+ testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+ }
+ else
+ {
+ uint8_t xMin = 0;
+ uint8_t x25p = 64;
+ uint8_t x50p = 128;
+ uint8_t x75p = 192;
+ uint8_t xMax = 255;
+
+ testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
+ testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
+ }
+}
+
+void testRanges(void)
+{
+ // The ranges from the main TEST function
+ testBounds(0x0, 0xFF);
+ testBounds(-128, 127);
+ testBounds(0, 16000);
+ testBounds(0, 20);
+ testBounds(8000, 16000);
+ testBounds(-10, 10);
+ testBounds(0, 277);
+ testBounds(0, 0, false);
+ testBounds(10, 12);
+
+ // Additional test cases recommended to me by hardware people
+ testBounds(-40, 150);
+ testBounds(0, 1);
+ testBounds(0, 2);
+ testBounds(0, 4);
+ testBounds(0, 8);
+ testBounds(35, 65);
+ testBounds(0, 18);
+ testBounds(0, 25);
+ testBounds(0, 80);
+ testBounds(0, 500);
+
+ // Additional sanity checks
+ testBounds(0, 255);
+ testBounds(-255, 0);
+ testBounds(-255, 255);
+ testBounds(0, 1000);
+ testBounds(-1000, 0);
+ testBounds(-1000, 1000);
+ testBounds(0, 255000);
+ testBounds(-128000000, 127000000);
+ testBounds(-50000, 0);
+ testBounds(-40000, 10000);
+ testBounds(-30000, 20000);
+ testBounds(-20000, 30000);
+ testBounds(-10000, 40000);
+ testBounds(0, 50000);
+ testBounds(-1e3, 1e6);
+ testBounds(-1e6, 1e3);
+
+ // Extreme ranges are now possible
+ testBounds(0, 1e10);
+ testBounds(0, 1e11);
+ testBounds(0, 1e12);
+ testBounds(0, 1e13, false);
+ testBounds(-1e10, 0);
+ testBounds(-1e11, 0);
+ testBounds(-1e12, 0);
+ testBounds(-1e13, 0, false);
+ testBounds(-1e9, 1e9);
+ testBounds(-1e10, 1e10);
+ testBounds(-1e11, 1e11);
+ testBounds(-1e12, 1e12, false);
+
+ // Large multiplier but small offset
+ testBounds(1e4, 1e4 + 255);
+ testBounds(1e5, 1e5 + 255);
+ testBounds(1e6, 1e6 + 255);
+ testBounds(1e7, 1e7 + 255);
+ testBounds(1e8, 1e8 + 255);
+ testBounds(1e9, 1e9 + 255);
+ testBounds(1e10, 1e10 + 255, false);
+
+ // Input validation against garbage
+ testBounds(0, INFINITY, false);
+ testBounds(-INFINITY, 0, false);
+ testBounds(-INFINITY, INFINITY, false);
+ testBounds(0, NAN, false);
+ testBounds(NAN, 0, false);
+ testBounds(NAN, NAN, false);
+
+ // Noteworthy binary integers
+ testBounds(0, std::pow(2.0, 32.0) - 1.0);
+ testBounds(0, std::pow(2.0, 32.0));
+ testBounds(0.0 - std::pow(2.0, 31.0), std::pow(2.0, 31.0));
+ testBounds((0.0 - std::pow(2.0, 31.0)) - 1.0, std::pow(2.0, 31.0));
+
+ // Similar but negative (note additional commented-out below)
+ testBounds(-1e1, (-1e1) + 255);
+ testBounds(-1e2, (-1e2) + 255);
+
+ // Ranges of negative numbers (note additional commented-out below)
+ testBounds(-10400, -10000);
+ testBounds(-15000, -14000);
+ testBounds(-10000, -9000);
+ testBounds(-1000, -900);
+ testBounds(-1000, -800);
+ testBounds(-1000, -700);
+ testBounds(-1000, -740);
+
+ // Very small ranges (note additional commented-out below)
+ testBounds(0, 0.1);
+ testBounds(0, 0.01);
+ testBounds(0, 0.001);
+ testBounds(0, 0.0001);
+ testBounds(0, 0.000001, false);
+
+#if 0
+ // TODO(): The algorithm in this module is better than it was before,
+ // but the resulting value of X is still wrong under certain conditions,
+ // such as when the range between min and max is around 255,
+ // and the offset is fairly extreme compared to the multiplier.
+ // Not sure why this is, but these ranges are contrived,
+ // and real-world examples would most likely never be this way.
+ testBounds(-10290, -10000);
+ testBounds(-10280, -10000);
+ testBounds(-10275,-10000);
+ testBounds(-10270,-10000);
+ testBounds(-10265,-10000);
+ testBounds(-10260,-10000);
+ testBounds(-10255,-10000);
+ testBounds(-10250,-10000);
+ testBounds(-10245,-10000);
+ testBounds(-10256,-10000);
+ testBounds(-10512, -10000);
+ testBounds(-11024, -10000);
+
+ // TODO(): This also fails, due to extreme small range, loss of precision
+ testBounds(0, 0.00001);
+
+ // TODO(): Interestingly, if bSigned is forced false,
+ // causing "x" to have range of (0,255) instead of (-128,127),
+ // these test cases change from failing to passing!
+ // Not sure why this is, perhaps a mathematician might know.
+ testBounds(-10300, -10000);
+ testBounds(-1000,-750);
+ testBounds(-1e3, (-1e3) + 255);
+ testBounds(-1e4, (-1e4) + 255);
+ testBounds(-1e5, (-1e5) + 255);
+ testBounds(-1e6, (-1e6) + 255);
+#endif
+}
+
+TEST(sensorutils, TranslateToIPMI)
+{
+ /*bool getSensorAttributes(double maxValue, double minValue, int16_t
+ &mValue, int8_t &rExp, int16_t &bValue, int8_t &bExp, bool &bSigned); */
+ // normal unsigned sensor
+ double maxValue = 0xFF;
+ double minValue = 0x0;
+ int16_t mValue;
+ int8_t rExp;
+ int16_t bValue;
+ int8_t bExp;
+ bool bSigned;
+ bool result;
+
+ uint8_t scaledVal;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, 1);
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+ double expected = 0x50;
+ scaledVal = ipmi::scaleIPMIValueFromDouble(0x50, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // normal signed sensor
+ maxValue = 127;
+ minValue = -128;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+
+ if (result)
+ {
+ EXPECT_EQ(bSigned, true);
+ EXPECT_EQ(mValue, 1);
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+
+ // check negative values
+ expected = 236; // 2s compliment -20
+ scaledVal = ipmi::scaleIPMIValueFromDouble(-20, mValue, rExp, bValue, bExp,
+ bSigned);
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // fan example
+ maxValue = 16000;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor((16000.0 / 0xFF) + 0.5));
+ EXPECT_EQ(rExp, 0);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+
+ // voltage sensor example
+ maxValue = 20;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -3);
+ EXPECT_EQ(bValue, 0);
+ EXPECT_EQ(bExp, 0);
+ }
+ scaledVal = ipmi::scaleIPMIValueFromDouble(12.2, mValue, rExp, bValue, bExp,
+ bSigned);
+
+ expected = 12.2 / (mValue * std::pow(10, rExp));
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // shifted fan example
+ maxValue = 16000;
+ minValue = 8000;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((8000.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -1);
+ EXPECT_EQ(bValue, 8);
+ EXPECT_EQ(bExp, 4);
+ }
+
+ // signed voltage sensor example
+ maxValue = 10;
+ minValue = -10;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, true);
+ EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -3);
+ // Although this seems like a weird magic number,
+ // it is because the range (-128,127) is not symmetrical about zero,
+ // unlike the range (-10,10), so this introduces some distortion.
+ EXPECT_EQ(bValue, 392);
+ EXPECT_EQ(bExp, -1);
+ }
+
+ scaledVal =
+ ipmi::scaleIPMIValueFromDouble(5, mValue, rExp, bValue, bExp, bSigned);
+
+ expected = 5 / (mValue * std::pow(10, rExp));
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // reading = max example
+ maxValue = 277;
+ minValue = 0;
+
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ }
+
+ scaledVal = ipmi::scaleIPMIValueFromDouble(maxValue, mValue, rExp, bValue,
+ bExp, bSigned);
+
+ expected = 0xFF;
+ EXPECT_NEAR(scaledVal, expected, expected * 0.01);
+
+ // 0, 0 failure
+ maxValue = 0;
+ minValue = 0;
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, false);
+
+ // too close *success* (was previously failure!)
+ maxValue = 12;
+ minValue = 10;
+ result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
+ bExp, bSigned);
+ EXPECT_EQ(result, true);
+ if (result)
+ {
+ EXPECT_EQ(bSigned, false);
+ EXPECT_EQ(mValue, floor(((2.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
+ EXPECT_EQ(rExp, -4);
+ EXPECT_EQ(bValue, 1);
+ EXPECT_EQ(bExp, 5);
+ }
+}
+
+TEST(sensorUtils, TestRanges)
+{
+ // Additional test ranges, each running through a series of values,
+ // to make sure the values of "x" and "y" go together and make sense,
+ // for the resulting scaling attributes from each range.
+ // Unlike the TranslateToIPMI test, exact matches of the
+ // getSensorAttributes() results (the coefficients) are not required,
+ // because they are tested through actual use, relating "x" to "y".
+ testRanges();
+}
diff --git a/test/entitymap_json_unittest.cpp b/test/entitymap_json_unittest.cpp
new file mode 100644
index 0000000..5f31e1b
--- /dev/null
+++ b/test/entitymap_json_unittest.cpp
@@ -0,0 +1,223 @@
+#include <ipmid/entity_map_json.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <utility>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace ipmi
+{
+namespace sensor
+{
+
+namespace
+{
+using ::testing::IsEmpty;
+
+TEST(ValidateJson, FailWithNonArrayReturnsEmpty)
+{
+ /* The entity map input json is expected to be an array of objects. */
+ auto j = R"(
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, FailWithMissingFieldReturnsEmpty)
+{
+ /* There are many required fields, let's just validate that if one is
+ * missing, it returns the empty map.
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, AllValidEntryReturnsExpectedMap)
+{
+ /* Boring test where we provide completely valid information and expect the
+ * resulting map contains that information.
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ }
+ ]
+ )"_json;
+
+ auto map = buildJsonEntityMap(j);
+ EXPECT_FALSE(map.find(1) == map.end());
+ auto entry = map.find(1);
+ EXPECT_EQ(entry->first, 1);
+
+ /* TODO: someone could write an equality operator for this object. */
+ EXPECT_EQ(entry->second.containerEntityId, 2);
+ EXPECT_EQ(entry->second.containerEntityInstance, 3);
+ EXPECT_FALSE(entry->second.isList);
+ EXPECT_FALSE(entry->second.isLinked);
+ ContainedEntitiesArray expected = {
+ std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+ std::make_pair(1, 5)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+TEST(ValidateJson, EntryHasInsufficientContainerEntryCountReturnsEmpty)
+{
+ /* The container must have four pairs. (I don't know why, and maybe this
+ * restriction will change).
+ */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesOneInvalidReturnsEmpty)
+{
+ /* If any entry in the file is corrupt, the file is disregarded. */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ },
+ {
+ "id" : 2,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4}
+ ]
+ }
+ ]
+ )"_json;
+
+ EXPECT_THAT(buildJsonEntityMap(j), IsEmpty());
+}
+
+TEST(ValidateJson, ThereAreTwoEntriesBothValidReturnsBoth)
+{
+ /* The map supports more than one entry, just validate this. */
+ auto j = R"(
+ [
+ {
+ "id" : 1,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 2},
+ {"id" : 1, "instance" : 3},
+ {"id" : 1, "instance" : 4},
+ {"id" : 1, "instance" : 5}
+ ]
+ },
+ {
+ "id" : 2,
+ "containerEntityId" : 2,
+ "containerEntityInstance" : 3,
+ "isList" : false,
+ "isLinked" : false,
+ "entities" : [
+ {"id" : 1, "instance" : 6},
+ {"id" : 1, "instance" : 7},
+ {"id" : 1, "instance" : 8},
+ {"id" : 1, "instance" : 9}
+ ]
+ }
+ ]
+ )"_json;
+
+ auto map = buildJsonEntityMap(j);
+ EXPECT_FALSE(map.find(1) == map.end());
+ EXPECT_FALSE(map.find(2) == map.end());
+
+ auto entry = map.find(1);
+ EXPECT_EQ(entry->first, 1);
+ EXPECT_EQ(entry->second.containerEntityId, 2);
+ EXPECT_EQ(entry->second.containerEntityInstance, 3);
+ EXPECT_FALSE(entry->second.isList);
+ EXPECT_FALSE(entry->second.isLinked);
+ ContainedEntitiesArray expected = {
+ std::make_pair(1, 2), std::make_pair(1, 3), std::make_pair(1, 4),
+ std::make_pair(1, 5)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+
+ entry = map.find(2);
+ expected = {std::make_pair(1, 6), std::make_pair(1, 7),
+ std::make_pair(1, 8), std::make_pair(1, 9)};
+ EXPECT_EQ(entry->second.containedEntities, expected);
+}
+
+} // namespace
+} // namespace sensor
+} // namespace ipmi
diff --git a/test/meson.build b/test/meson.build
index 6fcfd04..949a121 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,38 +1,103 @@
-gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
-gmock_dep = dependency('gmock', disabler: true, required: false)
-if not gtest_dep.found() or not gmock_dep.found()
- gtest_proj = import('cmake').subproject('googletest', required: false)
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+gmock = dependency('gmock', disabler: true, required: false)
+if not gtest.found() or not gmock.found()
+ gtest_opts = import('cmake').subproject_options()
+ gtest_opts.add_cmake_defines({'CMAKE_CXX_FLAGS': '-Wno-pedantic'})
+ gtest_proj = import('cmake').subproject(
+ 'googletest',
+ options: gtest_opts,
+ required: false,
+ )
if gtest_proj.found()
- gtest_dep = declare_dependency(
+ gtest = declare_dependency(
dependencies: [
dependency('threads'),
gtest_proj.dependency('gtest'),
gtest_proj.dependency('gtest_main'),
],
)
- gmock_dep = gtest_proj.dependency('gmock')
+ gmock = gtest_proj.dependency('gmock')
else
- assert(
- not get_option('tests').enabled(),
- 'Googletest is required if tests are enabled',
- )
+ assert(not get_option('tests').enabled(), 'Googletest is required')
endif
endif
-test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp']
+test(
+ 'entitymap_json',
+ executable(
+ 'entitymap_json',
+ 'entitymap_json_unittest.cpp',
+ include_directories: root_inc,
+ build_by_default: false,
+ implicit_include_directories: false,
+ dependencies: [
+ entity_map_json_dep,
+ gmock,
+ gtest,
+ nlohmann_json_dep,
+ sdbusplus_dep,
+ ],
+ ),
+)
-tests = ['cipher.cpp']
+# Build/add oemrouter_unittest to test suite
+# Issue #3325
+# test('oemrouter',
+# executable(
+# 'oemrouter',
+# 'oemrouter_unittest.cpp',
+# include_directories: root_inc,
+# build_by_default: false,
+# implicit_include_directories: false,
+# dependencies: [gtest, gmock]
+# ))
-foreach t : tests
- test(
- t,
- executable(
- t.underscorify(),
- t,
- test_sources,
- include_directories: ['..'],
- dependencies: [gtest_dep, gmock_dep, libcrypto_dep],
- ),
- workdir: meson.current_source_dir(),
- )
-endforeach
+# Build/add message packing/unpacking unit tests
+test(
+ 'message',
+ executable(
+ 'message',
+ 'message/pack.cpp',
+ 'message/payload.cpp',
+ 'message/unpack.cpp',
+ include_directories: root_inc,
+ build_by_default: false,
+ override_options: ['b_lundef=true'],
+ implicit_include_directories: false,
+ dependencies: [
+ boost,
+ crypto,
+ gmock,
+ gtest,
+ libsystemd_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ ],
+ ),
+)
+
+# Build/add closesession_unittest to test suite
+test(
+ 'session/closesession',
+ executable(
+ 'session_closesession',
+ 'session/closesession_unittest.cpp',
+ include_directories: root_inc,
+ build_by_default: false,
+ implicit_include_directories: false,
+ dependencies: [gtest, gmock],
+ ),
+)
+
+# Build/add sensorcommands_unittest to test suite
+test(
+ 'dbus-sdr/sensorcommands',
+ executable(
+ 'dbus-sdr_sensorcommands',
+ 'dbus-sdr/sensorcommands_unittest.cpp',
+ include_directories: root_inc,
+ build_by_default: false,
+ implicit_include_directories: false,
+ dependencies: [sensorutils_dep, gtest, gmock],
+ ),
+)
diff --git a/test/message/pack.cpp b/test/message/pack.cpp
new file mode 100644
index 0000000..57bff06
--- /dev/null
+++ b/test/message/pack.cpp
@@ -0,0 +1,497 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <gtest/gtest.h>
+
+// TODO: Add testing of Payload response API
+
+TEST(PackBasics, Uint8)
+{
+ ipmi::message::Payload p;
+ uint8_t v = 4;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint16)
+{
+ ipmi::message::Payload p;
+ uint16_t v = 0x8604;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04, 0x86};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint32)
+{
+ ipmi::message::Payload p;
+ uint32_t v = 0x02008604;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint64)
+{
+ ipmi::message::Payload p;
+ uint64_t v = 0x1122334402008604ull;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22, 0x11};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint24)
+{
+ ipmi::message::Payload p;
+ uint24_t v = 0x112358;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), types::nrFixedBits<decltype(v)> / CHAR_BIT);
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x58, 0x23, 0x11};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Uint3Uint5)
+{
+ // individual bytes are packed low-order-bits first
+ // v1 will occupy [2:0], v2 will occupy [7:3]
+ ipmi::message::Payload p;
+ uint3_t v1 = 0x1;
+ uint5_t v2 = 0x19;
+ p.pack(v1, v2);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), (types::nrFixedBits<decltype(v1)> +
+ types::nrFixedBits<decltype(v2)>) /
+ CHAR_BIT);
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0xc9};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Boolx8)
+{
+ // individual bytes are packed low-order-bits first
+ // [v8, v7, v6, v5, v4, v3, v2, v1]
+ ipmi::message::Payload p;
+ bool v8 = true, v7 = true, v6 = false, v5 = false;
+ bool v4 = true, v3 = false, v2 = false, v1 = true;
+ p.pack(v1, v2, v3, v4, v5, v6, v7, v8);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(uint8_t));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0xc9};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset8)
+{
+ // individual bytes are packed low-order-bits first
+ // a bitset for 8 bits fills the full byte
+ ipmi::message::Payload p;
+ std::bitset<8> v(0xc9);
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() / CHAR_BIT);
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0xc9};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset3Bitset5)
+{
+ // individual bytes are packed low-order-bits first
+ // v1 will occupy [2:0], v2 will occupy [7:3]
+ ipmi::message::Payload p;
+ std::bitset<3> v1(0x1);
+ std::bitset<5> v2(0x19);
+ p.pack(v1, v2);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), (v1.size() + v2.size()) / CHAR_BIT);
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0xc9};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Bitset32)
+{
+ // individual bytes are packed low-order-bits first
+ // v1 will occupy 4 bytes, but in LSByte first order
+ // v1[7:0] v1[15:9] v1[23:16] v1[31:24]
+ ipmi::message::Payload p;
+ std::bitset<32> v(0x02008604);
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() / CHAR_BIT);
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04, 0x86, 0x00, 0x02};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Tuple)
+{
+ // tuples are the new struct, pack a tuple
+ ipmi::message::Payload p;
+ auto v = std::make_tuple(static_cast<uint16_t>(0x8604), 'A');
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(uint16_t) + sizeof(char));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x04, 0x86, 0x41};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Array4xUint8)
+{
+ // an array of bytes will be output verbatim, low-order element first
+ ipmi::message::Payload p;
+ std::array<uint8_t, 4> v = {{0x02, 0x00, 0x86, 0x04}};
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Array4xUint32)
+{
+ // an array of multi-byte values will be output in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::message::Payload p;
+ std::array<uint32_t, 4> v = {
+ {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUint32)
+{
+ // a vector of multi-byte values will be output in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::message::Payload p;
+ std::vector<uint32_t> v = {
+ {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUint8)
+{
+ // a vector of bytes will be output verbatim, low-order element first
+ ipmi::message::Payload p;
+ std::vector<uint8_t> v = {0x02, 0x00, 0x86, 0x04};
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), v.size() * sizeof(v[0]));
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, VectorUnaligned)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(true, std::vector<uint8_t>{1}), 1);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer{0b1});
+}
+
+TEST(PackBasics, StringView)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(std::string_view{"\x24\x30\x11"}), 0);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0x24, 0x30, 0x11}));
+}
+
+TEST(PackBasics, StringViewUnaligned)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(true, std::string_view{"abc"}), 1);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, OptionalEmpty)
+{
+ // an optional will only pack if the value is present
+ ipmi::message::Payload p;
+ std::optional<uint32_t> v;
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), 0);
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, OptionalContainsValue)
+{
+ // an optional will only pack if the value is present
+ ipmi::message::Payload p;
+ std::optional<uint32_t> v(0x04860002);
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(uint32_t));
+ // check that the bytes were correctly packed (in byte order)
+ ipmi::SecureBuffer k = {0x02, 0x00, 0x86, 0x04};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackBasics, Payload)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(true), 0);
+ EXPECT_EQ(p.pack(ipmi::message::Payload({0x24, 0x30})), 0);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1, 0x24, 0x30}));
+}
+
+TEST(PackBasics, PayloadUnaligned)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(true, ipmi::message::Payload({0x24})), 1);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PayloadOtherUnaligned)
+{
+ ipmi::message::Payload p, q;
+ q.appendBits(1, 1);
+ EXPECT_EQ(p.pack(true), 0);
+ EXPECT_EQ(p.pack(q), 1);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PrependPayload)
+{
+ ipmi::message::Payload p;
+ EXPECT_EQ(p.pack(true), 0);
+ EXPECT_EQ(p.prepend(ipmi::message::Payload({0x24, 0x30})), 0);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0x24, 0x30, 0b1}));
+}
+
+TEST(PackBasics, PrependPayloadUnaligned)
+{
+ ipmi::message::Payload p;
+ p.appendBits(1, 1);
+ EXPECT_EQ(p.prepend(ipmi::message::Payload({0x24})), 1);
+ p.drain();
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackBasics, PrependPayloadOtherUnaligned)
+{
+ ipmi::message::Payload p, q;
+ q.appendBits(1, 1);
+ EXPECT_EQ(p.pack(true), 0);
+ EXPECT_EQ(p.prepend(q), 1);
+ EXPECT_EQ(p.raw, ipmi::SecureBuffer({0b1}));
+}
+
+TEST(PackAdvanced, Uints)
+{
+ // all elements will be processed in order, with each multi-byte
+ // element being processed LSByte first
+ // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+ // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+ // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+ ipmi::message::Payload p;
+ uint8_t v1 = 0x02;
+ uint16_t v2 = 0x0604;
+ uint32_t v3 = 0x44332211;
+ uint64_t v4 = 0xccbbaa9988776655ull;
+ p.pack(v1, v2, v3, v4);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v1) + sizeof(v2) + sizeof(v3) + sizeof(v4));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, TupleInts)
+{
+ // all elements will be processed in order, with each multi-byte
+ // element being processed LSByte first
+ // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+ // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+ // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+ ipmi::message::Payload p;
+ uint8_t v1 = 0x02;
+ uint16_t v2 = 0x0604;
+ uint32_t v3 = 0x44332211;
+ uint64_t v4 = 0xccbbaa9988776655ull;
+ auto v = std::make_tuple(v1, v2, v3, v4);
+ p.pack(v);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(v1) + sizeof(v2) + sizeof(v3) + sizeof(v4));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, VariantArray)
+{
+ ipmi::message::Payload p;
+ std::variant<std::array<uint8_t, 2>, uint32_t> variant;
+ auto data = std::array<uint8_t, 2>{2, 4};
+ variant = data;
+
+ p.pack(variant);
+ ASSERT_EQ(p.size(), sizeof(data));
+
+ // check that the bytes were correctly packed packed (LSB first)
+ ipmi::SecureBuffer k = {2, 4};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, BoolsnBitfieldsnFixedIntsOhMy)
+{
+ // each element will be added, filling the low-order bits first
+ // with multi-byte values getting added LSByte first
+ // v1 will occupy k[0][1:0]
+ // v2 will occupy k[0][2]
+ // v3[4:0] will occupy k[0][7:3], v3[6:5] will occupy k[1][1:0]
+ // v4 will occupy k[1][2]
+ // v5 will occupy k[1][7:3]
+ ipmi::message::Payload p;
+ uint2_t v1 = 2; // binary 0b10
+ bool v2 = true; // binary 0b1
+ std::bitset<7> v3(0x73); // binary 0b1110011
+ bool v4 = false; // binary 0b0
+ uint5_t v5 = 27; // binary 0b11011
+ // concat binary: 0b1101101110011110 -> 0xdb9e -> 0x9e 0xdb (LSByte first)
+ p.pack(v1, v2, v3, v4, v5);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(uint16_t));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x9e, 0xdb};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, UnalignedBitPacking)
+{
+ // unaligned multi-byte values will be packed the same as
+ // other bits, effectively building up a large value, low-order
+ // bits first, then outputting a stream of LSByte values
+ // v1 will occupy k[0][1:0]
+ // v2[5:0] will occupy k[0][7:2], v2[7:6] will occupy k[1][1:0]
+ // v3 will occupy k[1][2]
+ // v4[4:0] will occupy k[1][7:3] v4[12:5] will occupy k[2][7:0]
+ // v4[15:13] will occupy k[3][2:0]
+ // v5 will occupy k[3][3]
+ // v6[3:0] will occupy k[3][7:0] v6[11:4] will occupy k[4][7:0]
+ // v6[19:12] will occupy k[5][7:0] v6[27:20] will occupy k[6][7:0]
+ // v6[31:28] will occupy k[7][3:0]
+ // v7 will occupy k[7][7:4]
+ ipmi::message::Payload p;
+ uint2_t v1 = 2; // binary 0b10
+ uint8_t v2 = 0xa5; // binary 0b10100101
+ bool v3 = false; // binary 0b0
+ uint16_t v4 = 0xa55a; // binary 0b1010010101011010
+ bool v5 = true; // binary 0b1
+ uint32_t v6 = 0xdbc3bd3c; // binary 0b11011011110000111011110100111100
+ uint4_t v7 = 9; // binary 0b1001
+ // concat binary:
+ // 0b1001110110111100001110111101001111001101001010101101001010010110
+ // -> 0x9dbc3bd3cd2ad296 -> 0x96 0xd2 0x2a 0xcd 0xd3 0x3b 0xbc 0x9d
+ p.pack(v1, v2, v3, v4, v5, v6, v7);
+ // check that the number of bytes matches
+ ASSERT_EQ(p.size(), sizeof(uint64_t));
+ // check that the bytes were correctly packed (LSB first)
+ ipmi::SecureBuffer k = {0x96, 0xd2, 0x2a, 0xcd, 0xd3, 0x3b, 0xbc, 0x9d};
+ ASSERT_EQ(p.raw, k);
+}
+
+TEST(PackAdvanced, ComplexOptionalTuple)
+{
+ constexpr size_t macSize = 6;
+ // inspired from a real-world case of Get Session Info
+ constexpr uint8_t handle = 0x23; // handle for active session
+ constexpr uint8_t maxSessions = 15; // number of possible active sessions
+ constexpr uint8_t currentSessions = 4; // number of current active sessions
+ std::optional< // only returned for active session
+ std::tuple<uint8_t, // user ID
+ uint8_t, // privilege
+ uint4_t, // channel number
+ uint4_t // protocol (RMCP+)
+ >>
+ activeSession;
+ std::optional< // only returned for channel type LAN
+ std::tuple<uint32_t, // IPv4 address
+ std::array<uint8_t, macSize>, // MAC address
+ uint16_t // port
+ >>
+ lanSession;
+
+ constexpr uint8_t userID = 7;
+ constexpr uint8_t priv = 4;
+ constexpr uint4_t channel = 2;
+ constexpr uint4_t protocol = 1;
+ activeSession.emplace(userID, priv, channel, protocol);
+ constexpr std::array<uint8_t, macSize> macAddr{0};
+ lanSession.emplace(0x0a010105, macAddr, 55327);
+
+ ipmi::message::Payload p;
+ p.pack(handle, maxSessions, currentSessions, activeSession, lanSession);
+ ASSERT_EQ(p.size(),
+ sizeof(handle) + sizeof(maxSessions) + sizeof(currentSessions) +
+ 3 * sizeof(uint8_t) + sizeof(uint32_t) +
+ sizeof(uint8_t) * macSize + sizeof(uint16_t));
+ uint8_t protocol_channel =
+ (static_cast<uint8_t>(protocol) << 4) | static_cast<uint8_t>(channel);
+ ipmi::SecureBuffer k = {
+ handle, maxSessions, currentSessions, userID, priv, protocol_channel,
+ // ip addr
+ 0x05, 0x01, 0x01, 0x0a,
+ // mac addr
+ 0, 0, 0, 0, 0, 0,
+ // port
+ 0x1f, 0xd8};
+ ASSERT_EQ(p.raw, k);
+}
diff --git a/test/message/payload.cpp b/test/message/payload.cpp
new file mode 100644
index 0000000..38977e4
--- /dev/null
+++ b/test/message/payload.cpp
@@ -0,0 +1,436 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include <systemd/sd-journal.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <stdexcept>
+
+#include <gtest/gtest.h>
+
+TEST(Payload, InputSize)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ size_t input_size = i.size();
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ ASSERT_EQ(input_size, p.size());
+}
+
+TEST(Payload, OutputSize)
+{
+ ipmi::message::Payload p;
+ ASSERT_EQ(0, p.size());
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ p.pack(i);
+ ASSERT_EQ(i.size(), p.size());
+ p.pack(i);
+ p.pack(i);
+ ASSERT_EQ(3 * i.size(), p.size());
+}
+
+TEST(Payload, Resize)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p;
+ p.pack(i);
+ p.resize(16);
+ ASSERT_EQ(p.size(), 16);
+}
+
+TEST(Payload, Data)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p;
+ p.pack(i);
+ ASSERT_NE(nullptr, p.data());
+}
+
+TEST(PayloadResponse, Append)
+{
+ std::string s("0123456789abcdef");
+ ipmi::message::Payload p;
+ p.append(s.data(), s.data() + s.size());
+ ASSERT_EQ(s.size(), p.size());
+}
+
+TEST(PayloadResponse, AppendDrain)
+{
+ std::string s("0123456789abcdef");
+ ipmi::message::Payload p;
+ bool b = true;
+ // first pack a lone bit
+ p.pack(b);
+ p.append(s.data(), s.data() + s.size());
+ // append will 'drain' first, padding the lone bit into a full byte
+ ASSERT_EQ(s.size() + 1, p.size());
+}
+
+TEST(PayloadResponse, AppendBits)
+{
+ ipmi::message::Payload p;
+ p.appendBits(3, 0b101);
+ ASSERT_EQ(p.bitStream, 0b101);
+ p.appendBits(4, 0b1100);
+ ASSERT_EQ(p.bitStream, 0b1100101);
+ p.appendBits(1, 0b1);
+ ASSERT_EQ(p.bitStream, 0);
+ ASSERT_EQ(p.bitCount, 0);
+ // appended 8 bits, should be one byte
+ ASSERT_EQ(p.size(), 1);
+ ipmi::SecureBuffer k1 = {0b11100101};
+ ASSERT_EQ(p.raw, k1);
+ p.appendBits(7, 0b1110111);
+ // appended 7 more bits, should still be one byte
+ ASSERT_EQ(p.size(), 1);
+ p.drain();
+ // drain forces padding; should be two bytes now
+ ASSERT_EQ(p.size(), 2);
+ ipmi::SecureBuffer k2 = {0b11100101, 0b01110111};
+ ASSERT_EQ(p.raw, k2);
+}
+
+TEST(PayloadResponse, Drain16Bits)
+{
+ ipmi::message::Payload p;
+ p.bitStream = 0b1011010011001111;
+ p.bitCount = 16;
+ p.drain();
+ ASSERT_EQ(p.size(), 2);
+ ASSERT_EQ(p.bitCount, 0);
+ ASSERT_EQ(p.bitStream, 0);
+ ipmi::SecureBuffer k1 = {0b11001111, 0b10110100};
+ ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15Bits)
+{
+ ipmi::message::Payload p;
+ p.bitStream = 0b101101001100111;
+ p.bitCount = 15;
+ p.drain();
+ ASSERT_EQ(p.size(), 2);
+ ASSERT_EQ(p.bitCount, 0);
+ ASSERT_EQ(p.bitStream, 0);
+ ipmi::SecureBuffer k1 = {0b1100111, 0b1011010};
+ ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadResponse, Drain15BitsWholeBytesOnly)
+{
+ ipmi::message::Payload p;
+ p.bitStream = 0b101101001100111;
+ p.bitCount = 15;
+ p.drain(true);
+ // only the first whole byte should have been 'drained' into p.raw
+ ASSERT_EQ(p.size(), 1);
+ ASSERT_EQ(p.bitCount, 7);
+ ASSERT_EQ(p.bitStream, 0b1011010);
+ ipmi::SecureBuffer k1 = {0b1100111};
+ ASSERT_EQ(p.raw, k1);
+}
+
+TEST(PayloadRequest, Pop)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ const auto& [vb, ve] = p.pop<uint8_t>(4);
+ std::vector<uint8_t> v(vb, ve);
+ std::vector<uint8_t> k = {0xbf, 0x04, 0x86, 0x00};
+ ASSERT_EQ(v, k);
+}
+
+TEST(PayloadRequest, FillBits)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(5);
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0xbf);
+ ASSERT_EQ(p.bitCount, 8);
+ // should still have 5 bits available, no change
+ p.fillBits(5);
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0xbf);
+ ASSERT_EQ(p.bitCount, 8);
+ // discard 5 bits (low order)
+ p.popBits(5);
+ // should add another byte into the stream (high order)
+ p.fillBits(5);
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0x25);
+ ASSERT_EQ(p.bitCount, 11);
+}
+
+TEST(PayloadRequest, FillBitsTooManyBits)
+{
+ ipmi::SecureBuffer i = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(72);
+ ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, FillBitsNotEnoughBytes)
+{
+ ipmi::SecureBuffer i = {1, 2, 3, 4};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(48);
+ ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, PopBits)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(4);
+ uint8_t v = p.popBits(4);
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0x0b);
+ ASSERT_EQ(p.bitCount, 4);
+ ASSERT_EQ(v, 0x0f);
+}
+
+TEST(PayloadRequest, PopBitsNoFillBits)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.popBits(4);
+ ASSERT_TRUE(p.unpackError);
+}
+
+TEST(PayloadRequest, DiscardBits)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(5);
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0xbf);
+ ASSERT_EQ(p.bitCount, 8);
+ p.discardBits();
+ ASSERT_FALSE(p.unpackError);
+ ASSERT_EQ(p.bitStream, 0);
+ ASSERT_EQ(p.bitCount, 0);
+}
+
+TEST(PayloadRequest, FullyUnpacked)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint32_t v1;
+ p.unpack(v1);
+ // still one remaining byte
+ ASSERT_FALSE(p.fullyUnpacked());
+ p.fillBits(3);
+ p.popBits(3);
+ // still five remaining bits
+ ASSERT_FALSE(p.fullyUnpacked());
+ p.fillBits(5);
+ p.popBits(5);
+ // fully unpacked, should be no errors
+ ASSERT_TRUE(p.fullyUnpacked());
+ p.fillBits(4);
+ // fullyUnpacked fails because an attempt to unpack too many bytes
+ ASSERT_FALSE(p.fullyUnpacked());
+}
+
+TEST(PayloadRequest, ResetInternal)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ p.fillBits(4);
+ p.unpackError = true;
+ p.reset();
+ ASSERT_EQ(p.rawIndex, 0);
+ ASSERT_EQ(p.bitStream, 0);
+ ASSERT_EQ(p.bitCount, 0);
+ ASSERT_FALSE(p.unpackError);
+}
+
+TEST(PayloadRequest, ResetUsage)
+{
+ // Payload.reset is used to rewind the unpacking to the initial
+ // state. This is needed so that OEM commands can unpack the group
+ // number or the IANA to determine which handler needs to be called
+ ipmi::SecureBuffer i = {0x04, 0x86};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v1;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint8_t k1 = 0x04;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ // do a reset on the payload
+ p.reset();
+ // unpack a uint16
+ uint16_t v2;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v2), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint16_t k2 = 0x8604;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v2, k2);
+}
+
+TEST(PayloadRequest, PartialPayload)
+{
+ ipmi::SecureBuffer i = {0xbf, 0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v1;
+ ipmi::message::Payload localPayload;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, localPayload), 0);
+ // check that the payload was partially unpacked and not in error
+ ASSERT_FALSE(p.fullyUnpacked());
+ ASSERT_FALSE(p.unpackError);
+ // check that the 'extracted' payload is not fully unpacked
+ ASSERT_FALSE(localPayload.fullyUnpacked());
+ uint8_t k1 = 0xbf;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ uint32_t v2;
+ // unpack using the 'extracted' payload
+ ASSERT_EQ(localPayload.unpack(v2), 0);
+ ASSERT_TRUE(localPayload.fullyUnpacked());
+ uint32_t k2 = 0x02008604;
+ ASSERT_EQ(v2, k2);
+}
+
+std::vector<std::string> logs;
+
+extern "C"
+{
+int sd_journal_sendv(const struct iovec* iov, int /* n */)
+{
+ logs.push_back(std::string((char*)iov[0].iov_base, iov[0].iov_len));
+ return 0;
+}
+}
+
+class PayloadLogging : public testing::Test
+{
+ public:
+ void SetUp()
+ {
+ logs.clear();
+ }
+};
+
+TEST_F(PayloadLogging, TrailingOk)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ }
+ EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingUnchecked)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ }
+ EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingUncheckedUnpacked)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ uint8_t out;
+ p.unpack(out, out);
+ }
+ EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingUncheckedError)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ uint32_t out;
+ p.unpack(out);
+ }
+ EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingChecked)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ EXPECT_FALSE(p.fullyUnpacked());
+ }
+ EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingCheckedUnpacked)
+{
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ uint8_t out;
+ p.unpack(out, out);
+ EXPECT_TRUE(p.fullyUnpacked());
+ }
+ EXPECT_EQ(logs.size(), 0);
+}
+
+TEST_F(PayloadLogging, EnforcingUnpackPayload)
+{
+ {
+ ipmi::message::Payload p;
+ {
+ ipmi::message::Payload q({1, 2});
+ q.trailingOk = false;
+ q.unpack(p);
+ }
+ EXPECT_EQ(logs.size(), 0);
+ }
+ EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingMove)
+{
+ {
+ ipmi::message::Payload p;
+ {
+ ipmi::message::Payload q({1, 2});
+ q.trailingOk = false;
+ p = std::move(q);
+ }
+ EXPECT_EQ(logs.size(), 0);
+ }
+ EXPECT_EQ(logs.size(), 1);
+}
+
+TEST_F(PayloadLogging, EnforcingException)
+{
+ try
+ {
+ ipmi::message::Payload p({1, 2});
+ p.trailingOk = false;
+ throw std::runtime_error("test");
+ }
+ catch (...)
+ {}
+ EXPECT_EQ(logs.size(), 0);
+}
diff --git a/test/message/unpack.cpp b/test/message/unpack.cpp
new file mode 100644
index 0000000..acc6088
--- /dev/null
+++ b/test/message/unpack.cpp
@@ -0,0 +1,897 @@
+/**
+ * Copyright © 2018 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+
+#include <gtest/gtest.h>
+
+TEST(Uints, Uint8)
+{
+ ipmi::SecureBuffer i = {0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint8_t k = 0x04;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint8TooManyBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint8_t k = 0x04;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint8InsufficientBytes)
+{
+ ipmi::SecureBuffer i = {};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v = 0;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint16)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint16_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint16_t k = 0x8604;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint16TooManyBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint16_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint16_t k = 0x8604;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint16InsufficientBytes)
+{
+ ipmi::SecureBuffer i = {0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint16_t v = 0;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint32)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint32_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint32_t k = 0x02008604;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint32TooManyBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint32_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint32_t k = 0x02008604;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint32InsufficientBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint32_t v = 0;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint64)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22, 0x11};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint64_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint64_t k = 0x1122334402008604ull;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint64TooManyBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44,
+ 0x33, 0x22, 0x11, 0x55};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint64_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint64_t k = 0x1122334402008604ull;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Uints, Uint64InsufficientBytes)
+{
+ ipmi::SecureBuffer i = {0x04, 0x86, 0x00, 0x02, 0x44, 0x33, 0x22};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint64_t v = 0;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(Uints, Uint24)
+{
+ ipmi::SecureBuffer i = {0x58, 0x23, 0x11};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint24_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint24_t k = 0x112358;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(FixedInts, Uint24TooManyBytes)
+{
+ ipmi::SecureBuffer i = {0x58, 0x23, 0x11, 0x00};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint24_t v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint24_t k = 0x112358;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(FixedInts, Uint24InsufficientBytes)
+{
+ ipmi::SecureBuffer i = {0x58, 0x23};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint24_t v = 0;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(FixedInts, Uint3Uint5)
+{
+ // individual bytes are unpacked low-order-bits first
+ // v1 will use [2:0], v2 will use [7:3]
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint3_t v1;
+ uint5_t v2;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint3_t k1 = 0x1;
+ uint5_t k2 = 0x19;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+}
+
+TEST(FixedInts, Uint3Uint4TooManyBits)
+{
+ // high order bit should not get unpacked
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint3_t v1;
+ uint4_t v2;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint3_t k1 = 0x1;
+ uint4_t k2 = 0x9;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+}
+
+TEST(FixedInts, Uint3Uint6InsufficientBits)
+{
+ // insufficient bits to unpack v2
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint3_t v1;
+ uint6_t v2;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v1, v2), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ uint3_t k1 = 0x1;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ // check that v2 is zero
+ ASSERT_EQ(v2, 0);
+}
+
+TEST(Bools, Boolx8)
+{
+ // individual bytes are unpacked low-order-bits first
+ // [v8, v7, v6, v5, v4, v3, v2, v1]
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ bool v8, v7, v6, v5;
+ bool v4, v3, v2, v1;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7, v8), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ // check that the bytes were correctly unpacked (LSB first)
+ bool k8 = true, k7 = true, k6 = false, k5 = false;
+ bool k4 = true, k3 = false, k2 = false, k1 = true;
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+ ASSERT_EQ(v5, k5);
+ ASSERT_EQ(v6, k6);
+ ASSERT_EQ(v7, k7);
+ ASSERT_EQ(v8, k8);
+}
+
+TEST(Bools, Boolx8TooManyBits)
+{
+ // high order bit should not get unpacked
+ // individual bytes are unpacked low-order-bits first
+ // [v7, v6, v5, v4, v3, v2, v1]
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ bool v7, v6, v5;
+ bool v4, v3, v2, v1;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that the bytes were correctly unpacked (LSB first)
+ bool k7 = true, k6 = false, k5 = false;
+ bool k4 = true, k3 = false, k2 = false, k1 = true;
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+ ASSERT_EQ(v5, k5);
+ ASSERT_EQ(v6, k6);
+ ASSERT_EQ(v7, k7);
+}
+
+TEST(Bools, Boolx8InsufficientBits)
+{
+ // individual bytes are unpacked low-order-bits first
+ // [v8, v7, v6, v5, v4, v3, v2, v1]
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ bool v9;
+ bool v8, v7, v6, v5;
+ bool v4, v3, v2, v1;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v1, v2, v3, v4, v5, v6, v7, v8, v9), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that the bytes were correctly unpacked (LSB first)
+ bool k8 = true, k7 = true, k6 = false, k5 = false;
+ bool k4 = true, k3 = false, k2 = false, k1 = true;
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+ ASSERT_EQ(v5, k5);
+ ASSERT_EQ(v6, k6);
+ ASSERT_EQ(v7, k7);
+ ASSERT_EQ(v8, k8);
+}
+
+TEST(Bitsets, Bitset8)
+{
+ // individual bytes are unpacked low-order-bits first
+ // a bitset for 8 bits fills the full byte
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<8> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::bitset<8> k(0xc9);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset7TooManyBits)
+{
+ // individual bytes are unpacked low-order-bits first
+ // a bitset for 8 bits fills the full byte
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<7> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::bitset<7> k(0x49);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset9InsufficientBits)
+{
+ // individual bytes are unpacked low-order-bits first
+ // a bitset for 8 bits fills the full byte
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<9> v;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::bitset<9> k(0);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset3Bitset5)
+{
+ // individual bytes are unpacked low-order-bits first
+ // v1 will use [2:0], v2 will use [7:3]
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<3> v1;
+ std::bitset<5> v2;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::bitset<3> k1(0x1);
+ std::bitset<5> k2(0x19);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+}
+
+TEST(Bitsets, Bitset3Bitset4TooManyBits)
+{
+ // high order bit should not get unpacked
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<3> v1;
+ std::bitset<4> v2;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::bitset<3> k1 = 0x1;
+ std::bitset<4> k2 = 0x9;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+}
+
+TEST(Bitsets, Bitset3Bitset6InsufficientBits)
+{
+ // insufficient bits to unpack v2
+ ipmi::SecureBuffer i = {0xc9};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<3> v1;
+ std::bitset<6> v2;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v1, v2), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::bitset<3> k1 = 0x1;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ // check that v2 is zero
+ ASSERT_EQ(v2, 0);
+}
+
+TEST(Bitsets, Bitset32)
+{
+ // individual bytes are unpacked low-order-bits first
+ // v1 will use 4 bytes, but in LSByte first order
+ // v1[7:0] v1[15:9] v1[23:16] v1[31:24]
+ ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<32> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::bitset<32> k(0xc29186b4);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset31TooManyBits)
+{
+ // high order bit should not get unpacked
+ ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<31> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::bitset<31> k(0x429186b4);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Bitsets, Bitset33InsufficientBits)
+{
+ // insufficient bits to unpack v2
+ ipmi::SecureBuffer i = {0xb4, 0x86, 0x91, 0xc2};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::bitset<33> v;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked (comprehends unpack errors)
+ ASSERT_FALSE(p.fullyUnpacked());
+ // check that v is zero
+ ASSERT_EQ(v, 0);
+}
+
+TEST(Arrays, Array4xUint8)
+{
+ // an array of bytes will be read verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint8_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::array<uint8_t, 4> k = {{0x02, 0x00, 0x86, 0x04}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint8TooManyBytes)
+{
+ // last byte should not get unpacked
+ // an array of bytes will be read verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04, 0x22};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint8_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::array<uint8_t, 4> k = {{0x02, 0x00, 0x86, 0x04}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint8InsufficientBytes)
+{
+ // last byte should not get unpacked
+ // an array of bytes will be read verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint8_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ // arrays of uint8_t will be unpacked all at once
+ // so nothing will get unpacked
+ std::array<uint8_t, 4> k = {{0, 0, 0, 0}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32)
+{
+ // an array of multi-byte values will be unpacked in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint32_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::array<uint32_t, 4> k = {
+ {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32TooManyBytes)
+{
+ // last byte should not get unpacked
+ // an array of multi-byte values will be unpacked in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66,
+ 0x44, 0x22, 0x99, 0x77, 0x55, 0x33,
+ 0x78, 0x56, 0x34, 0x12, 0xaa};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint32_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::array<uint32_t, 4> k = {
+ {0x11223344, 0x22446688, 0x33557799, 0x12345678}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Arrays, Array4xUint32InsufficientBytes)
+{
+ // last value should not get unpacked
+ // an array of multi-byte values will be unpacked in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::array<uint32_t, 4> v;
+ // check that the number of bytes matches
+ ASSERT_NE(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ // arrays of uint32_t will be unpacked in a way that looks atomic
+ std::array<uint32_t, 4> k = {{0, 0, 0, 0}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorUint32)
+{
+ // a vector of multi-byte values will be unpacked in order low-order
+ // element first, each multi-byte element in LSByte order
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34, 0x12};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<uint32_t> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::vector<uint32_t> k = {0x11223344, 0x22446688, 0x33557799, 0x12345678};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+// combination of TooManyBytes and InsufficientBytes because
+// vectors will attempt to unpack full <T>s until the end of the input
+TEST(Vectors, VectorUint32NonIntegralBytes)
+{
+ // last value should not get unpacked
+ // a vector of multi-byte values will be unpacked in order low-order
+ // element first, each multi-byte element in LSByte order,
+ // and will attempt to consume all bytes remaining
+ // v[0][7:0] v[0][15:9] v[0][23:16] v[0][31:24]
+ // v[1][7:0] v[1][15:9] v[1][23:16] v[1][31:24]
+ // v[2][7:0] v[2][15:9] v[2][23:16] v[2][31:24]
+ // v[3][7:0] v[3][15:9] v[3][23:16] v[3][31:24]
+ ipmi::SecureBuffer i = {0x44, 0x33, 0x22, 0x11, 0x88, 0x66, 0x44, 0x22,
+ 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<uint32_t> v;
+ // check that the vector unpacks successfully
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ // arrays of uint32_t will be unpacked one at a time, so the
+ // last entry should not get unpacked properly
+ std::vector<uint32_t> k = {0x11223344, 0x22446688, 0x33557799};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorUint8)
+{
+ // a vector of bytes will be unpacked verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<uint8_t> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::vector<uint8_t> k = {0x02, 0x00, 0x86, 0x04};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorEmptyOk)
+{
+ // an empty input vector to show that unpacking elements is okay
+ ipmi::SecureBuffer i{};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<uint32_t> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::vector<uint32_t> k{};
+ // check that the unpacked vector is empty as expected
+ ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorOfTuplesOk)
+{
+ // a vector of bytes will be unpacked verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<std::tuple<uint8_t, uint8_t>> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::vector<std::tuple<uint8_t, uint8_t>> k = {{0x02, 0x00}, {0x86, 0x04}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(Vectors, VectorOfTuplesInsufficientBytes)
+{
+ // a vector of bytes will be unpacked verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04, 0xb4};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::vector<std::tuple<uint8_t, uint8_t>> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was not fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::vector<std::tuple<uint8_t, uint8_t>> k = {{0x02, 0x00}, {0x86, 0x04}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+// Cannot test TooManyBytes or InsufficientBytes for vector<uint8_t>
+// because it will always unpack whatever bytes are remaining
+// TEST(Vectors, VectorUint8TooManyBytes) {}
+// TEST(Vectors, VectorUint8InsufficientBytes) {}
+
+TEST(UnpackAdvanced, OptionalOk)
+{
+ // a vector of bytes will be unpacked verbatim, low-order element first
+ ipmi::SecureBuffer i = {0xbe, 0x02, 0x00, 0x86, 0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::optional<std::tuple<uint8_t, uint32_t>> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ std::optional<std::tuple<uint8_t, uint32_t>> k{{0xbe, 0x04860002}};
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, OptionalInsufficientBytes)
+{
+ // a vector of bytes will be unpacked verbatim, low-order element first
+ ipmi::SecureBuffer i = {0x02, 0x00, 0x86, 0x04};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::optional<std::tuple<uint8_t, uint32_t>> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_FALSE(p.fullyUnpacked());
+ std::optional<std::tuple<uint8_t, uint32_t>> k;
+ // check that the bytes were correctly unpacked (in byte order)
+ ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, Uints)
+{
+ // all elements will be unpacked in order, with each multi-byte
+ // element being processed LSByte first
+ // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+ // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+ // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+ ipmi::SecureBuffer i = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint8_t v1;
+ uint16_t v2;
+ uint32_t v3;
+ uint64_t v4;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2, v3, v4), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint8_t k1 = 0x02;
+ uint16_t k2 = 0x0604;
+ uint32_t k3 = 0x44332211;
+ uint64_t k4 = 0xccbbaa9988776655ull;
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+}
+
+TEST(UnpackAdvanced, TupleInts)
+{
+ // all elements will be unpacked in order, with each multi-byte
+ // element being processed LSByte first
+ // v1[7:0] v2[7:0] v2[15:8] v3[7:0] v3[15:8] v3[23:16] v3[31:24]
+ // v4[7:0] v4[15:8] v4[23:16] v4[31:24]
+ // v4[39:25] v4[47:40] v4[55:48] v4[63:56]
+ ipmi::SecureBuffer i = {0x02, 0x04, 0x06, 0x11, 0x22, 0x33, 0x44, 0x55,
+ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ std::tuple<uint8_t, uint16_t, uint32_t, uint64_t> v;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint8_t k1 = 0x02;
+ uint16_t k2 = 0x0604;
+ uint32_t k3 = 0x44332211;
+ uint64_t k4 = 0xccbbaa9988776655ull;
+ auto k = std::make_tuple(k1, k2, k3, k4);
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v, k);
+}
+
+TEST(UnpackAdvanced, BoolsnBitfieldsnFixedIntsOhMy)
+{
+ // each element will be unpacked, filling the low-order bits first
+ // with multi-byte values getting unpacked LSByte first
+ // v1 will use k[0][1:0]
+ // v2 will use k[0][2]
+ // v3[4:0] will use k[0][7:3], v3[6:5] will use k[1][1:0]
+ // v4 will use k[1][2]
+ // v5 will use k[1][7:3]
+ ipmi::SecureBuffer i = {0x9e, 0xdb};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint2_t v1;
+ bool v2;
+ std::bitset<7> v3;
+ bool v4;
+ uint5_t v5;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint2_t k1 = 2; // binary 0b10
+ bool k2 = true; // binary 0b1
+ std::bitset<7> k3(0x73); // binary 0b1110011
+ bool k4 = false; // binary 0b0
+ uint5_t k5 = 27; // binary 0b11011
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+ ASSERT_EQ(v5, k5);
+}
+
+TEST(UnpackAdvanced, UnalignedBitUnpacking)
+{
+ // unaligned multi-byte values will be unpacked the same as
+ // other bits, effectively reading from a large value, low-order
+ // bits first, then consuming the stream LSByte first
+ // v1 will use k[0][1:0]
+ // v2[5:0] will use k[0][7:2], v2[7:6] will use k[1][1:0]
+ // v3 will use k[1][2]
+ // v4[4:0] will use k[1][7:3] v4[12:5] will use k[2][7:0]
+ // v4[15:13] will use k[3][2:0]
+ // v5 will use k[3][3]
+ // v6[3:0] will use k[3][7:0] v6[11:4] will use k[4][7:0]
+ // v6[19:12] will use k[5][7:0] v6[27:20] will use k[6][7:0]
+ // v6[31:28] will use k[7][3:0]
+ // v7 will use k[7][7:4]
+ ipmi::SecureBuffer i = {0x96, 0xd2, 0x2a, 0xcd, 0xd3, 0x3b, 0xbc, 0x9d};
+ ipmi::message::Payload p(std::forward<ipmi::SecureBuffer>(i));
+ uint2_t v1;
+ uint8_t v2;
+ bool v3;
+ uint16_t v4;
+ bool v5;
+ uint32_t v6;
+ uint4_t v7;
+ // check that the number of bytes matches
+ ASSERT_EQ(p.unpack(v1, v2, v3, v4, v5, v6, v7), 0);
+ // check that the payload was fully unpacked
+ ASSERT_TRUE(p.fullyUnpacked());
+ uint2_t k1 = 2; // binary 0b10
+ uint8_t k2 = 0xa5; // binary 0b10100101
+ bool k3 = false; // binary 0b0
+ uint16_t k4 = 0xa55a; // binary 0b1010010101011010
+ bool k5 = true; // binary 0b1
+ uint32_t k6 = 0xdbc3bd3c; // binary 0b11011011110000111011110100111100
+ uint4_t k7 = 9; // binary 0b1001
+ // check that the bytes were correctly unpacked (LSB first)
+ ASSERT_EQ(v1, k1);
+ ASSERT_EQ(v2, k2);
+ ASSERT_EQ(v3, k3);
+ ASSERT_EQ(v4, k4);
+ ASSERT_EQ(v5, k5);
+ ASSERT_EQ(v6, k6);
+ ASSERT_EQ(v7, k7);
+}
diff --git a/test/oemrouter_unittest.cpp b/test/oemrouter_unittest.cpp
new file mode 100644
index 0000000..f0c0a01
--- /dev/null
+++ b/test/oemrouter_unittest.cpp
@@ -0,0 +1,175 @@
+#include <ipmid/api.h>
+
+#include <include/ipmid/api-types.hpp>
+#include <ipmid/oemrouter.hpp>
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+// Watch for correct singleton behavior.
+static oem::Router* singletonUnderTest;
+
+static ipmid_callback_t wildHandler;
+
+static ipmi_netfn_t lastNetFunction;
+
+// Fake ipmi_register_callback() for this test.
+void ipmi_register_callback(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+ ipmi_context_t context, ipmid_callback_t cb,
+ ipmi_cmd_privilege_t priv)
+{
+ EXPECT_EQ(NETFUN_OEM_GROUP, netfn);
+ EXPECT_EQ(ipmi::cmdWildcard, cmd);
+ EXPECT_EQ(reinterpret_cast<void*>(singletonUnderTest), context);
+ EXPECT_EQ(PRIVILEGE_OEM, priv);
+ lastNetFunction = netfn;
+ wildHandler = cb;
+}
+
+namespace oem
+{
+
+namespace
+{
+void MakeRouter()
+{
+ if (!singletonUnderTest)
+ {
+ singletonUnderTest = mutableRouter();
+ }
+ ASSERT_EQ(singletonUnderTest, mutableRouter());
+}
+
+void ActivateRouter()
+{
+ MakeRouter();
+ singletonUnderTest->activate();
+ ASSERT_EQ(NETFUN_OEM_GROUP, lastNetFunction);
+}
+
+void RegisterWithRouter(Number oen, ipmi_cmd_t cmd, Handler cb)
+{
+ ActivateRouter();
+ singletonUnderTest->registerHandler(oen, cmd, cb);
+}
+
+uint8_t msgPlain[] = {0x56, 0x34, 0x12};
+uint8_t replyPlain[] = {0x56, 0x34, 0x12, 0x31, 0x41};
+uint8_t msgPlus2[] = {0x67, 0x45, 0x23, 0x10, 0x20};
+uint8_t msgBadOen[] = {0x57, 0x34, 0x12};
+
+void RegisterTwoWays(ipmi_cmd_t* nextCmd)
+{
+ Handler f = [](ipmi_cmd_t cmd, [[maybe_unused]] const uint8_t* reqBuf,
+ uint8_t* replyBuf, size_t* dataLen) {
+ // Check inputs
+ EXPECT_EQ(0x78, cmd);
+ EXPECT_EQ(0, *dataLen); // Excludes OEN
+
+ // Generate reply.
+ *dataLen = 2;
+ std::memcpy(replyBuf, replyPlain + 3, *dataLen);
+ return 0;
+ };
+ RegisterWithRouter(0x123456, 0x78, f);
+
+ *nextCmd = ipmi::cmdWildcard;
+ Handler g = [nextCmd](ipmi_cmd_t cmd, const uint8_t* reqBuf,
+ [[maybe_unused]] uint8_t* replyBuf, size_t* dataLen) {
+ // Check inputs
+ EXPECT_EQ(*nextCmd, cmd);
+ EXPECT_EQ(2, *dataLen); // Excludes OEN
+ if (2 != *dataLen)
+ {
+ return 0xE0;
+ }
+ EXPECT_EQ(msgPlus2[3], reqBuf[0]);
+ EXPECT_EQ(msgPlus2[4], reqBuf[1]);
+
+ // Generate reply.
+ *dataLen = 0;
+ return 0;
+ };
+ RegisterWithRouter(0x234567, ipmi::cmdWildcard, g);
+}
+} // namespace
+
+TEST(OemRouterTest, MakeRouterProducesConsistentSingleton)
+{
+ MakeRouter();
+}
+
+TEST(OemRouterTest, ActivateRouterSetsLastNetToOEMGROUP)
+{
+ lastNetFunction = 0;
+ ActivateRouter();
+}
+
+TEST(OemRouterTest, VerifiesSpecificCommandMatches)
+{
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ dataLen = 3;
+ EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply, &dataLen,
+ nullptr));
+ EXPECT_EQ(5, dataLen);
+ EXPECT_EQ(replyPlain[0], reply[0]);
+ EXPECT_EQ(replyPlain[1], reply[1]);
+ EXPECT_EQ(replyPlain[2], reply[2]);
+ EXPECT_EQ(replyPlain[3], reply[3]);
+ EXPECT_EQ(replyPlain[4], reply[4]);
+}
+
+TEST(OemRouterTest, WildCardMatchesTwoRandomCodes)
+{
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ // Check two random command codes.
+ dataLen = 5;
+ cmd = 0x89;
+ EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply, &dataLen,
+ nullptr));
+ EXPECT_EQ(3, dataLen);
+
+ dataLen = 5;
+ cmd = 0x67;
+ EXPECT_EQ(0, wildHandler(NETFUN_OEM_GROUP, cmd, msgPlus2, reply, &dataLen,
+ nullptr));
+ EXPECT_EQ(3, dataLen);
+}
+
+TEST(OemRouterTest, CommandsAreRejectedIfInvalid)
+{
+ ipmi_cmd_t cmd;
+ uint8_t reply[256];
+ size_t dataLen;
+
+ RegisterTwoWays(&cmd);
+
+ // Message too short to include whole OEN?
+ dataLen = 2;
+ EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+ wildHandler(NETFUN_OEM_GROUP, 0x78, msgPlain, reply, &dataLen,
+ nullptr));
+
+ // Wrong specific command?
+ dataLen = 3;
+ EXPECT_EQ(IPMI_CC_INVALID, wildHandler(NETFUN_OEM_GROUP, 0x89, msgPlain,
+ reply, &dataLen, nullptr));
+
+ // Wrong OEN?
+ dataLen = 3;
+ EXPECT_EQ(IPMI_CC_INVALID, wildHandler(NETFUN_OEM_GROUP, 0x78, msgBadOen,
+ reply, &dataLen, nullptr));
+}
+
+} // namespace oem
diff --git a/test/session/closesession_unittest.cpp b/test/session/closesession_unittest.cpp
new file mode 100644
index 0000000..2b184ca
--- /dev/null
+++ b/test/session/closesession_unittest.cpp
@@ -0,0 +1,114 @@
+#include <ipmid/sessionhelper.hpp>
+
+#include <gtest/gtest.h>
+
+TEST(parseSessionInputPayloadTest, ValidObjectPath)
+{
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0;
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+
+ EXPECT_TRUE(
+ parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+ EXPECT_EQ(0x12a4567d, sessionId);
+ EXPECT_EQ(0x8a, sessionHandle);
+}
+
+TEST(parseSessionInputPayloadTest, InvalidObjectPath)
+{
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0;
+ // A valid object path will be like
+ // "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+ // Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ // SessionId : 0X12a4567d
+ // SessionHandle: 0X8a
+ std::string objectPath = "/xyz/openbmc_project/ipmi/session/eth0/12a4567d";
+
+ EXPECT_FALSE(
+ parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+}
+
+TEST(parseSessionInputPayloadTest, NoObjectPath)
+{
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0;
+ std::string objectPath;
+
+ EXPECT_FALSE(
+ parseCloseSessionInputPayload(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ValidSessionId)
+{
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+ uint32_t sessionId = 0x12a4567d;
+ uint8_t sessionHandle = 0;
+
+ EXPECT_TRUE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ValidSessionHandle)
+{
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0x8a;
+
+ EXPECT_TRUE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidSessionId)
+{
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+ uint32_t sessionId = 0x1234b67d;
+ uint8_t sessionHandle = 0;
+
+ EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidSessionHandle)
+{
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0x9b;
+
+ EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, ZeroSessionId_ZeroSessionHandle)
+{
+ std::string objectPath =
+ "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a";
+ uint32_t sessionId = 0;
+ uint8_t sessionHandle = 0;
+
+ EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, InvalidObjectPath)
+{
+ // A valid object path will be like
+ // "/xyz/openbmc_project/ipmi/session/channel/sessionId_sessionHandle"
+ // Ex: "/xyz/openbmc_project/ipmi/session/eth0/12a4567d_8a"
+ // SessionId : 0X12a4567d
+ // SessionHandle: 0X8a
+ std::string objectPath = "/xyz/openbmc_project/ipmi/session/eth0/12a4567d";
+ uint32_t sessionId = 0x12a4567d;
+ uint8_t sessionHandle = 0;
+
+ EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
+
+TEST(isSessionObjectMatchedTest, NoObjectPath)
+{
+ std::string objectPath;
+ uint32_t sessionId = 0x12a4567d;
+ uint8_t sessionHandle = 0x8a;
+
+ EXPECT_FALSE(isSessionObjectMatched(objectPath, sessionId, sessionHandle));
+}
diff --git a/transport/meson.build b/transport/meson.build
new file mode 100644
index 0000000..9e5a241
--- /dev/null
+++ b/transport/meson.build
@@ -0,0 +1,3 @@
+if get_option('transport-implementation') == 'serial'
+ subdir('serialbridge')
+endif
diff --git a/transport/rmcpbridge/.clang-format b/transport/rmcpbridge/.clang-format
new file mode 100644
index 0000000..e5530e6
--- /dev/null
+++ b/transport/rmcpbridge/.clang-format
@@ -0,0 +1,136 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands: Align
+AlignTrailingComments:
+ Kind: Always
+ OverEmptyLines: 1
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: true
+BinPackParameters: true
+BitFieldColonSpacing: None
+BraceWrapping:
+ AfterCaseLabel: true
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterExternBlock: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: true
+ BeforeElse: true
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakAfterAttributes: Never
+BreakAfterReturnType: Automatic
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+BreakInheritanceList: AfterColon
+BreakStringLiterals: false
+BreakTemplateDeclarations: Yes
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+ - Regex: '^[<"](gtest|gmock)'
+ Priority: 7
+ - Regex: '^"config.h"'
+ Priority: -1
+ - Regex: '^".*\.h"'
+ Priority: 1
+ - Regex: '^".*\.hpp"'
+ Priority: 2
+ - Regex: '^<.*\.h>'
+ Priority: 3
+ - Regex: '^<.*\.hpp>'
+ Priority: 4
+ - Regex: '^<.*'
+ Priority: 5
+ - Regex: '.*'
+ Priority: 6
+IndentCaseLabels: true
+IndentExternBlock: NoIndent
+IndentRequiresClause: true
+IndentWidth: 4
+IndentWrappedFunctionNames: true
+InsertNewlineAtEOF: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+LineEnding: LF
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PackConstructorInitializers: BinPack
+PenaltyBreakAssignment: 25
+PenaltyBreakBeforeFirstCallParameter: 50
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 150
+PenaltyIndentedWhitespace: 1
+PointerAlignment: Left
+QualifierAlignment: Left
+ReferenceAlignment: Left
+ReflowComments: true
+RequiresClausePosition: OwnLine
+RequiresExpressionIndentation: Keyword
+SortIncludes: CaseSensitive
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Latest
+TabWidth: 4
+UseTab: Never
+...
+
diff --git a/transport/rmcpbridge/.clang-tidy b/transport/rmcpbridge/.clang-tidy
new file mode 100644
index 0000000..4b6eca0
--- /dev/null
+++ b/transport/rmcpbridge/.clang-tidy
@@ -0,0 +1,7 @@
+Checks: '
+ -*,
+ bugprone-unchecked-optional-access,
+ readability-identifier-naming
+'
+WarningsAsErrors: '*'
+HeaderFilterRegex: '(?!^subprojects).*'
diff --git a/transport/rmcpbridge/.gitignore b/transport/rmcpbridge/.gitignore
new file mode 100644
index 0000000..51ef08e
--- /dev/null
+++ b/transport/rmcpbridge/.gitignore
@@ -0,0 +1,3 @@
+/build*/
+/subprojects/*
+!subprojects/*.wrap
diff --git a/transport/rmcpbridge/LICENSE b/transport/rmcpbridge/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/transport/rmcpbridge/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/transport/rmcpbridge/OWNERS b/transport/rmcpbridge/OWNERS
new file mode 100644
index 0000000..05f2bbe
--- /dev/null
+++ b/transport/rmcpbridge/OWNERS
@@ -0,0 +1,48 @@
+# OWNERS
+# ------
+#
+# The OWNERS file maintains the list of individuals responsible for various
+# parts of this repository, including code review and approval. We use the
+# Gerrit 'owners' plugin, which consumes this file, along with some extra
+# keywords for our own purposes and tooling.
+#
+# For details on the configuration used by 'owners' see:
+# https://gerrit.googlesource.com/plugins/owners/+/refs/heads/master/owners/src/main/resources/Documentation/config.md
+#
+# An OWNERS file must be in the root of a repository but may also be present
+# in any subdirectory. The contents of the subdirectory OWNERS file are
+# combined with parent directories unless 'inherit: false' is set.
+#
+# The owners file is YAML and has [up to] 4 top-level keywords.
+# * owners: A list of individuals who have approval authority on the
+# repository.
+#
+# * reviewers: A list of individuals who have requested review notification
+# on the repository.
+#
+# * matchers: A list of specific file/path matchers for granular 'owners' and
+# 'reviewers'. See 'owners' plugin documentation.
+#
+# * openbmc: A list of openbmc-specific meta-data about owners and reviewers.
+# - name: preferred name of the individual.
+# - email: preferred email address of the individual.
+# - discord: Discord nickname of the individual.
+#
+# It is expected that these 4 sections will be listed in the order above and
+# data within them will be kept sorted.
+
+owners:
+- rushtotom@gmail.com
+- vernon.mauery@gmail.com
+
+reviewers:
+
+matchers:
+
+openbmc:
+- name: Tom Joseph
+ email: rushtotom@gmail.com
+ discord: tomjose
+- name: Vernon Mauery
+ email: vernon.mauery@gmail.com
+ discord: vmauery
diff --git a/transport/rmcpbridge/README.md b/transport/rmcpbridge/README.md
new file mode 100644
index 0000000..89aba20
--- /dev/null
+++ b/transport/rmcpbridge/README.md
@@ -0,0 +1,13 @@
+# phosphor-net-ipmid
+
+## To Build
+
+To build this package, do the following steps:
+
+```sh
+1. ./bootstrap.sh
+2. ./configure ${CONFIGURE_FLAGS}
+3. make
+```
+
+To clean the repository run `./bootstrap.sh clean`.
diff --git a/auth_algo.cpp b/transport/rmcpbridge/auth_algo.cpp
similarity index 100%
rename from auth_algo.cpp
rename to transport/rmcpbridge/auth_algo.cpp
diff --git a/auth_algo.hpp b/transport/rmcpbridge/auth_algo.hpp
similarity index 100%
rename from auth_algo.hpp
rename to transport/rmcpbridge/auth_algo.hpp
diff --git a/comm_module.cpp b/transport/rmcpbridge/comm_module.cpp
similarity index 100%
rename from comm_module.cpp
rename to transport/rmcpbridge/comm_module.cpp
diff --git a/comm_module.hpp b/transport/rmcpbridge/comm_module.hpp
similarity index 100%
rename from comm_module.hpp
rename to transport/rmcpbridge/comm_module.hpp
diff --git a/command/channel_auth.cpp b/transport/rmcpbridge/command/channel_auth.cpp
similarity index 100%
rename from command/channel_auth.cpp
rename to transport/rmcpbridge/command/channel_auth.cpp
diff --git a/command/channel_auth.hpp b/transport/rmcpbridge/command/channel_auth.hpp
similarity index 100%
rename from command/channel_auth.hpp
rename to transport/rmcpbridge/command/channel_auth.hpp
diff --git a/command/guid.cpp b/transport/rmcpbridge/command/guid.cpp
similarity index 100%
rename from command/guid.cpp
rename to transport/rmcpbridge/command/guid.cpp
diff --git a/command/guid.hpp b/transport/rmcpbridge/command/guid.hpp
similarity index 100%
rename from command/guid.hpp
rename to transport/rmcpbridge/command/guid.hpp
diff --git a/command/open_session.cpp b/transport/rmcpbridge/command/open_session.cpp
similarity index 100%
rename from command/open_session.cpp
rename to transport/rmcpbridge/command/open_session.cpp
diff --git a/command/open_session.hpp b/transport/rmcpbridge/command/open_session.hpp
similarity index 100%
rename from command/open_session.hpp
rename to transport/rmcpbridge/command/open_session.hpp
diff --git a/command/payload_cmds.cpp b/transport/rmcpbridge/command/payload_cmds.cpp
similarity index 100%
rename from command/payload_cmds.cpp
rename to transport/rmcpbridge/command/payload_cmds.cpp
diff --git a/command/payload_cmds.hpp b/transport/rmcpbridge/command/payload_cmds.hpp
similarity index 100%
rename from command/payload_cmds.hpp
rename to transport/rmcpbridge/command/payload_cmds.hpp
diff --git a/command/rakp12.cpp b/transport/rmcpbridge/command/rakp12.cpp
similarity index 100%
rename from command/rakp12.cpp
rename to transport/rmcpbridge/command/rakp12.cpp
diff --git a/command/rakp12.hpp b/transport/rmcpbridge/command/rakp12.hpp
similarity index 100%
rename from command/rakp12.hpp
rename to transport/rmcpbridge/command/rakp12.hpp
diff --git a/command/rakp34.cpp b/transport/rmcpbridge/command/rakp34.cpp
similarity index 100%
rename from command/rakp34.cpp
rename to transport/rmcpbridge/command/rakp34.cpp
diff --git a/command/rakp34.hpp b/transport/rmcpbridge/command/rakp34.hpp
similarity index 100%
rename from command/rakp34.hpp
rename to transport/rmcpbridge/command/rakp34.hpp
diff --git a/command/session_cmds.cpp b/transport/rmcpbridge/command/session_cmds.cpp
similarity index 100%
rename from command/session_cmds.cpp
rename to transport/rmcpbridge/command/session_cmds.cpp
diff --git a/command/session_cmds.hpp b/transport/rmcpbridge/command/session_cmds.hpp
similarity index 100%
rename from command/session_cmds.hpp
rename to transport/rmcpbridge/command/session_cmds.hpp
diff --git a/command/sol_cmds.cpp b/transport/rmcpbridge/command/sol_cmds.cpp
similarity index 100%
rename from command/sol_cmds.cpp
rename to transport/rmcpbridge/command/sol_cmds.cpp
diff --git a/command/sol_cmds.hpp b/transport/rmcpbridge/command/sol_cmds.hpp
similarity index 100%
rename from command/sol_cmds.hpp
rename to transport/rmcpbridge/command/sol_cmds.hpp
diff --git a/command_table.cpp b/transport/rmcpbridge/command_table.cpp
similarity index 100%
rename from command_table.cpp
rename to transport/rmcpbridge/command_table.cpp
diff --git a/command_table.hpp b/transport/rmcpbridge/command_table.hpp
similarity index 100%
rename from command_table.hpp
rename to transport/rmcpbridge/command_table.hpp
diff --git a/crypt_algo.cpp b/transport/rmcpbridge/crypt_algo.cpp
similarity index 100%
rename from crypt_algo.cpp
rename to transport/rmcpbridge/crypt_algo.cpp
diff --git a/crypt_algo.hpp b/transport/rmcpbridge/crypt_algo.hpp
similarity index 100%
rename from crypt_algo.hpp
rename to transport/rmcpbridge/crypt_algo.hpp
diff --git a/endian.hpp b/transport/rmcpbridge/endian.hpp
similarity index 100%
rename from endian.hpp
rename to transport/rmcpbridge/endian.hpp
diff --git a/integrity_algo.cpp b/transport/rmcpbridge/integrity_algo.cpp
similarity index 100%
rename from integrity_algo.cpp
rename to transport/rmcpbridge/integrity_algo.cpp
diff --git a/integrity_algo.hpp b/transport/rmcpbridge/integrity_algo.hpp
similarity index 100%
rename from integrity_algo.hpp
rename to transport/rmcpbridge/integrity_algo.hpp
diff --git a/main.cpp b/transport/rmcpbridge/main.cpp
similarity index 100%
rename from main.cpp
rename to transport/rmcpbridge/main.cpp
diff --git a/main.hpp b/transport/rmcpbridge/main.hpp
similarity index 100%
rename from main.hpp
rename to transport/rmcpbridge/main.hpp
diff --git a/transport/rmcpbridge/meson.build b/transport/rmcpbridge/meson.build
new file mode 100644
index 0000000..0e449d4
--- /dev/null
+++ b/transport/rmcpbridge/meson.build
@@ -0,0 +1,120 @@
+project(
+ 'phosphor-net-ipmid',
+ 'cpp',
+ version: '1.0.0',
+ meson_version: '>=1.1.1',
+ default_options: [
+ 'warning_level=3',
+ 'werror=true',
+ 'cpp_std=c++23',
+ 'buildtype=debugoptimized',
+ 'b_lto=true',
+ ],
+)
+
+conf_data = configuration_data()
+conf_data.set('RMCP_PING', get_option('rmcp_ping').allowed())
+conf_data.set('PAM_AUTHENTICATE', get_option('pam_authenticate').allowed())
+
+configure_file(output: 'config.h', configuration: conf_data)
+
+sdbusplus_dep = dependency('sdbusplus')
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+phosphor_logging_dep = dependency('phosphor-logging')
+libsystemd_dep = dependency('libsystemd')
+libcrypto_dep = dependency('libcrypto')
+ipmid_dep = dependency('libipmid')
+userlayer_dep = dependency('libuserlayer')
+channellayer_dep = dependency('libchannellayer')
+
+# Project Arguments
+cpp = meson.get_compiler('cpp')
+if cpp.has_header('CLI/CLI.hpp')
+ cli11_dep = declare_dependency()
+else
+ cli11_dep = dependency('CLI11')
+endif
+
+add_project_arguments(
+ cpp.get_supported_arguments(
+ [
+ '-DBOOST_ERROR_CODE_HEADER_ONLY',
+ '-DBOOST_SYSTEM_NO_DEPRECATED',
+ '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING',
+ '-DBOOST_ASIO_DISABLE_THREADS',
+ '-DBOOST_ALL_NO_LIB',
+ ],
+ ),
+ language: 'cpp',
+)
+
+deps = [
+ cli11_dep,
+ ipmid_dep,
+ userlayer_dep,
+ channellayer_dep,
+ libcrypto_dep,
+ libsystemd_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+]
+
+sources = [
+ 'auth_algo.cpp',
+ 'sessions_manager.cpp',
+ 'message_parsers.cpp',
+ 'message_handler.cpp',
+ 'command_table.cpp',
+ 'command/channel_auth.cpp',
+ 'command/guid.cpp',
+ 'command/open_session.cpp',
+ 'command/rakp12.cpp',
+ 'command/rakp34.cpp',
+ 'command/session_cmds.cpp',
+ 'comm_module.cpp',
+ 'main.cpp',
+ 'integrity_algo.cpp',
+ 'crypt_algo.cpp',
+ 'sd_event_loop.cpp',
+ 'sol/sol_manager.cpp',
+ 'sol/sol_context.cpp',
+ 'command/sol_cmds.cpp',
+ 'command/payload_cmds.cpp',
+ 'sol_module.cpp',
+]
+
+executable(
+ 'netipmid',
+ sources,
+ implicit_include_directories: true,
+ include_directories: ['command', 'sol'],
+ dependencies: deps,
+ install: true,
+ install_dir: get_option('bindir'),
+)
+
+systemd = dependency('systemd')
+systemd_system_unit_dir = systemd.get_variable(
+ 'systemdsystemunitdir',
+ pkgconfig_define: ['prefix', get_option('prefix')],
+)
+
+configure_file(
+ input: 'phosphor-ipmi-net@.service',
+ output: 'phosphor-ipmi-net@.service',
+ copy: true,
+ install_dir: systemd_system_unit_dir,
+)
+
+configure_file(
+ input: 'phosphor-ipmi-net@.socket',
+ output: 'phosphor-ipmi-net@.socket',
+ copy: true,
+ install_dir: systemd_system_unit_dir,
+)
+
+build_tests = get_option('tests')
+if build_tests.allowed()
+ subdir('test')
+endif
diff --git a/transport/rmcpbridge/meson.options b/transport/rmcpbridge/meson.options
new file mode 100644
index 0000000..6f9cbaa
--- /dev/null
+++ b/transport/rmcpbridge/meson.options
@@ -0,0 +1,15 @@
+option('tests', type: 'feature', value: 'enabled', description: 'Build tests')
+
+option(
+ 'rmcp_ping',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable RMCP Ping support',
+)
+
+option(
+ 'pam_authenticate',
+ type: 'feature',
+ value: 'enabled',
+ description: 'Enable Pam Authenticate',
+)
diff --git a/message.hpp b/transport/rmcpbridge/message.hpp
similarity index 100%
rename from message.hpp
rename to transport/rmcpbridge/message.hpp
diff --git a/message_handler.cpp b/transport/rmcpbridge/message_handler.cpp
similarity index 100%
rename from message_handler.cpp
rename to transport/rmcpbridge/message_handler.cpp
diff --git a/message_handler.hpp b/transport/rmcpbridge/message_handler.hpp
similarity index 100%
rename from message_handler.hpp
rename to transport/rmcpbridge/message_handler.hpp
diff --git a/message_parsers.cpp b/transport/rmcpbridge/message_parsers.cpp
similarity index 100%
rename from message_parsers.cpp
rename to transport/rmcpbridge/message_parsers.cpp
diff --git a/message_parsers.hpp b/transport/rmcpbridge/message_parsers.hpp
similarity index 100%
rename from message_parsers.hpp
rename to transport/rmcpbridge/message_parsers.hpp
diff --git a/phosphor-ipmi-net@.service b/transport/rmcpbridge/phosphor-ipmi-net@.service
similarity index 100%
rename from phosphor-ipmi-net@.service
rename to transport/rmcpbridge/phosphor-ipmi-net@.service
diff --git a/phosphor-ipmi-net@.socket b/transport/rmcpbridge/phosphor-ipmi-net@.socket
similarity index 100%
rename from phosphor-ipmi-net@.socket
rename to transport/rmcpbridge/phosphor-ipmi-net@.socket
diff --git a/prng.hpp b/transport/rmcpbridge/prng.hpp
similarity index 100%
rename from prng.hpp
rename to transport/rmcpbridge/prng.hpp
diff --git a/rmcp.hpp b/transport/rmcpbridge/rmcp.hpp
similarity index 100%
rename from rmcp.hpp
rename to transport/rmcpbridge/rmcp.hpp
diff --git a/sd_event_loop.cpp b/transport/rmcpbridge/sd_event_loop.cpp
similarity index 100%
rename from sd_event_loop.cpp
rename to transport/rmcpbridge/sd_event_loop.cpp
diff --git a/sd_event_loop.hpp b/transport/rmcpbridge/sd_event_loop.hpp
similarity index 100%
rename from sd_event_loop.hpp
rename to transport/rmcpbridge/sd_event_loop.hpp
diff --git a/session.hpp b/transport/rmcpbridge/session.hpp
similarity index 100%
rename from session.hpp
rename to transport/rmcpbridge/session.hpp
diff --git a/sessions_manager.cpp b/transport/rmcpbridge/sessions_manager.cpp
similarity index 100%
rename from sessions_manager.cpp
rename to transport/rmcpbridge/sessions_manager.cpp
diff --git a/sessions_manager.hpp b/transport/rmcpbridge/sessions_manager.hpp
similarity index 100%
rename from sessions_manager.hpp
rename to transport/rmcpbridge/sessions_manager.hpp
diff --git a/socket_channel.hpp b/transport/rmcpbridge/socket_channel.hpp
similarity index 100%
rename from socket_channel.hpp
rename to transport/rmcpbridge/socket_channel.hpp
diff --git a/sol/console_buffer.hpp b/transport/rmcpbridge/sol/console_buffer.hpp
similarity index 100%
rename from sol/console_buffer.hpp
rename to transport/rmcpbridge/sol/console_buffer.hpp
diff --git a/sol/sol_context.cpp b/transport/rmcpbridge/sol/sol_context.cpp
similarity index 100%
rename from sol/sol_context.cpp
rename to transport/rmcpbridge/sol/sol_context.cpp
diff --git a/sol/sol_context.hpp b/transport/rmcpbridge/sol/sol_context.hpp
similarity index 100%
rename from sol/sol_context.hpp
rename to transport/rmcpbridge/sol/sol_context.hpp
diff --git a/sol/sol_manager.cpp b/transport/rmcpbridge/sol/sol_manager.cpp
similarity index 100%
rename from sol/sol_manager.cpp
rename to transport/rmcpbridge/sol/sol_manager.cpp
diff --git a/sol/sol_manager.hpp b/transport/rmcpbridge/sol/sol_manager.hpp
similarity index 100%
rename from sol/sol_manager.hpp
rename to transport/rmcpbridge/sol/sol_manager.hpp
diff --git a/sol_module.cpp b/transport/rmcpbridge/sol_module.cpp
similarity index 100%
rename from sol_module.cpp
rename to transport/rmcpbridge/sol_module.cpp
diff --git a/sol_module.hpp b/transport/rmcpbridge/sol_module.hpp
similarity index 100%
rename from sol_module.hpp
rename to transport/rmcpbridge/sol_module.hpp
diff --git a/subprojects/CLI11.wrap b/transport/rmcpbridge/subprojects/CLI11.wrap
similarity index 100%
rename from subprojects/CLI11.wrap
rename to transport/rmcpbridge/subprojects/CLI11.wrap
diff --git a/subprojects/googletest.wrap b/transport/rmcpbridge/subprojects/googletest.wrap
similarity index 100%
rename from subprojects/googletest.wrap
rename to transport/rmcpbridge/subprojects/googletest.wrap
diff --git a/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
new file mode 100644
index 0000000..346aa0c
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-dbus-interfaces.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-dbus-interfaces.git
+revision = HEAD
+
+[provide]
+phosphor-dbus-interfaces = phosphor_dbus_interfaces_dep
diff --git a/subprojects/phosphor-host-ipmid.wrap b/transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
similarity index 100%
rename from subprojects/phosphor-host-ipmid.wrap
rename to transport/rmcpbridge/subprojects/phosphor-host-ipmid.wrap
diff --git a/transport/rmcpbridge/subprojects/phosphor-logging.wrap b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
new file mode 100644
index 0000000..71eee8b
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/phosphor-logging.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/phosphor-logging.git
+revision = HEAD
+
+[provide]
+phosphor-logging = phosphor_logging_dep
diff --git a/transport/rmcpbridge/subprojects/sdbusplus.wrap b/transport/rmcpbridge/subprojects/sdbusplus.wrap
new file mode 100644
index 0000000..7b076d0
--- /dev/null
+++ b/transport/rmcpbridge/subprojects/sdbusplus.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/sdbusplus.git
+revision = HEAD
+
+[provide]
+sdbusplus = sdbusplus_dep
diff --git a/test/cipher.cpp b/transport/rmcpbridge/test/cipher.cpp
similarity index 100%
rename from test/cipher.cpp
rename to transport/rmcpbridge/test/cipher.cpp
diff --git a/transport/rmcpbridge/test/meson.build b/transport/rmcpbridge/test/meson.build
new file mode 100644
index 0000000..6fcfd04
--- /dev/null
+++ b/transport/rmcpbridge/test/meson.build
@@ -0,0 +1,38 @@
+gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
+gmock_dep = dependency('gmock', disabler: true, required: false)
+if not gtest_dep.found() or not gmock_dep.found()
+ gtest_proj = import('cmake').subproject('googletest', required: false)
+ if gtest_proj.found()
+ gtest_dep = declare_dependency(
+ dependencies: [
+ dependency('threads'),
+ gtest_proj.dependency('gtest'),
+ gtest_proj.dependency('gtest_main'),
+ ],
+ )
+ gmock_dep = gtest_proj.dependency('gmock')
+ else
+ assert(
+ not get_option('tests').enabled(),
+ 'Googletest is required if tests are enabled',
+ )
+ endif
+endif
+
+test_sources = ['../integrity_algo.cpp', '../crypt_algo.cpp']
+
+tests = ['cipher.cpp']
+
+foreach t : tests
+ test(
+ t,
+ executable(
+ t.underscorify(),
+ t,
+ test_sources,
+ include_directories: ['..'],
+ dependencies: [gtest_dep, gmock_dep, libcrypto_dep],
+ ),
+ workdir: meson.current_source_dir(),
+ )
+endforeach
diff --git a/transport/serialbridge/meson.build b/transport/serialbridge/meson.build
new file mode 100644
index 0000000..6919c75
--- /dev/null
+++ b/transport/serialbridge/meson.build
@@ -0,0 +1,41 @@
+CLI11_dep = dependency('CLI11')
+
+deps = [
+ dependency('libsystemd'),
+ dependency('systemd'),
+ sdeventplus_dep,
+ stdplus_dep,
+ sdbusplus_dep,
+ phosphor_logging_dep,
+ CLI11_dep,
+]
+
+serialbridged = executable(
+ 'serialbridged',
+ 'serialbridged.cpp',
+ 'serialcmd.cpp',
+ dependencies: deps,
+ install: true,
+ install_dir: get_option('libexecdir'),
+)
+
+# Configure and install systemd unit files
+systemd = dependency('systemd')
+if systemd.found()
+ conf_data = configuration_data()
+ conf_data.set(
+ 'BIN',
+ get_option('prefix') / get_option('libexecdir') / serialbridged.name(),
+ )
+ configure_file(
+ input: 'serialbridge@.service.in',
+ output: 'serialbridge@.service',
+ configuration: conf_data,
+ install: true,
+ install_dir: systemd.get_variable(pkgconfig: 'systemdsystemunitdir'),
+ )
+endif
+
+if not get_option('tests').disabled()
+ subdir('test')
+endif
diff --git a/transport/serialbridge/serialbridge@.service.in b/transport/serialbridge/serialbridge@.service.in
new file mode 100644
index 0000000..a7bfd45
--- /dev/null
+++ b/transport/serialbridge/serialbridge@.service.in
@@ -0,0 +1,18 @@
+[Unit]
+Description=Phosphor IPMI Serial DBus Bridge
+StartLimitBurst=3
+StartLimitIntervalSec=300
+After=phosphor-ipmi-host.service
+
+[Service]
+Restart=always
+RestartSec=10
+TimeoutStartSec=60
+TimeoutStopSec=60
+ExecStartPre=/bin/stty -F /dev/"%i" 115200 litout -crtscts -ixon -echo raw
+ExecStart=@BIN@ -d "%i"
+SyslogIdentifier=serialbridged-%i
+
+[Install]
+WantedBy=multi-user.target
+RequiredBy=
diff --git a/transport/serialbridge/serialbridged.cpp b/transport/serialbridge/serialbridged.cpp
new file mode 100644
index 0000000..b022dc6
--- /dev/null
+++ b/transport/serialbridge/serialbridged.cpp
@@ -0,0 +1,85 @@
+#include "serialcmd.hpp"
+
+#include <systemd/sd-daemon.h>
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/slot.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/source/signal.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/signal.hpp>
+
+namespace serialbridge
+{
+
+using sdeventplus::source::IO;
+using sdeventplus::source::Signal;
+using stdplus::fd::OpenAccess;
+using stdplus::fd::OpenFlag;
+using stdplus::fd::OpenFlags;
+
+int execute(const std::string& channel, const bool& verbose)
+{
+ // Set up DBus and event loop
+ auto event = sdeventplus::Event::get_default();
+ auto bus = sdbusplus::bus::new_default();
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ // Configure basic signal handling
+ auto exit_handler = [&event](Signal&, const struct signalfd_siginfo*) {
+ lg2::error("Interrupted, Exiting\n");
+ event.exit(0);
+ };
+ stdplus::signal::block(SIGINT);
+ Signal sig_init(event, SIGINT, exit_handler);
+ stdplus::signal::block(SIGTERM);
+ Signal sig_term(event, SIGTERM, exit_handler);
+
+ // Open an FD for the UART channel
+ stdplus::ManagedFd uart = stdplus::fd::open(
+ std::format("/dev/{}", channel.c_str()),
+ OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::NonBlock));
+ sdbusplus::slot_t slot(nullptr);
+
+ std::unique_ptr<SerialChannel> serialchannel =
+ std::make_unique<SerialChannel>(verbose);
+
+ // Add a reader to the bus for handling inbound IPMI
+ IO ioSource(event, uart.get(), EPOLLIN | EPOLLET,
+ stdplus::exception::ignore(
+ [&serialchannel, &uart, &bus, &slot](IO&, int, uint32_t) {
+ serialchannel->read(uart, bus, slot);
+ }));
+
+ sd_notify(0, "READY=1");
+ return event.loop();
+}
+
+} // namespace serialbridge
+
+int main(int argc, char* argv[])
+{
+ std::string device;
+ bool verbose = 0;
+
+ // Parse input parameter
+ CLI::App app("Serial IPMI Bridge");
+ app.add_option("-d,--device", device, "select uart device");
+ app.add_option("-v,--verbose", verbose, "enable debug message");
+ CLI11_PARSE(app, argc, argv);
+
+ try
+ {
+ return serialbridge::execute(device, verbose);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("FAILED: {MSG}\n", "MSG", e);
+ return 1;
+ }
+}
diff --git a/transport/serialbridge/serialcmd.cpp b/transport/serialbridge/serialcmd.cpp
new file mode 100644
index 0000000..ec84f5c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.cpp
@@ -0,0 +1,340 @@
+#include "serialcmd.hpp"
+
+#include <fmt/format.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+
+#include <numeric>
+#include <ranges>
+#include <unordered_map>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+static const std::unordered_map<uint8_t, uint8_t> characters = {
+ {bmStart, 0xB0}, /* start */
+ {bmStop, 0xB5}, /* stop */
+ {bmHandshake, 0xB6}, /* packet handshake */
+ {bmEscape, 0xBA}, /* data escape */
+ {0x1B, 0x3B} /* escape */
+};
+
+/**
+ * @brief Calculate IPMI checksum
+ */
+uint8_t SerialChannel::calculateChecksum(std::span<uint8_t> data)
+{
+ uint8_t checksum;
+
+ checksum = std::accumulate(data.begin(), data.end(), 0);
+ checksum = (~checksum) + 1;
+
+ // return checksum
+ return checksum;
+}
+
+/**
+ * @brief Return unescaped character for the given one
+ */
+uint8_t SerialChannel::getUnescapedCharacter(uint8_t c)
+{
+ auto search =
+ std::find_if(characters.begin(), characters.end(),
+ [c](const auto& map_set) { return map_set.second == c; });
+
+ if (search == characters.end())
+ {
+ return c;
+ }
+
+ return search->first;
+}
+
+/**
+ * @brief Process IPMI Serial Request State Machine
+ */
+int SerialChannel::consumeIpmiSerialPacket(
+ std::span<uint8_t>& escapedDataBytes,
+ std::vector<uint8_t>& unescapedDataBytes)
+{
+ unescapedDataBytes.reserve(escapedDataBytes.size());
+
+ for (auto c : escapedDataBytes)
+ {
+ if (c == bmStart) // START
+ {
+ msgState = MsgState::msgInProgress;
+ }
+ else if (msgState == MsgState::msgIdle)
+ {
+ continue;
+ }
+ else if (msgState == MsgState::msgInEscape)
+ {
+ uint8_t unescapedCharacter;
+ unescapedCharacter = getUnescapedCharacter(c);
+
+ if (unescapedCharacter == c)
+ {
+ // error, then reset
+ msgState = MsgState::msgIdle;
+ unescapedDataBytes.clear();
+ continue;
+ }
+
+ unescapedDataBytes.push_back(unescapedCharacter);
+ msgState = MsgState::msgInProgress;
+ }
+ else if (c == bmEscape)
+ {
+ msgState = MsgState::msgInEscape;
+ continue;
+ }
+ else if (c == bmStop) // STOP
+ {
+ msgState = MsgState::msgIdle;
+ return true;
+ }
+ else if (c == bmHandshake) // Handshake
+ {
+ unescapedDataBytes.clear();
+ continue;
+ }
+ else if (msgState == MsgState::msgInProgress)
+ {
+ unescapedDataBytes.push_back(c);
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief Encapsluate response to avoid escape character
+ */
+uint8_t SerialChannel::processEscapedCharacter(std::vector<uint8_t>& buffer,
+ const std::vector<uint8_t>& data)
+{
+ uint8_t checksum = 0;
+
+ std::ranges::for_each(data.begin(), data.end(),
+ [&buffer, &checksum](const auto& c) {
+ auto search = characters.find(c);
+ if (search != characters.end())
+ {
+ buffer.push_back(bmEscape);
+ buffer.push_back(search->second);
+ }
+ else
+ {
+ buffer.push_back(c);
+ }
+
+ checksum += c;
+ });
+
+ return checksum;
+}
+
+/**
+ * @brief Write function
+ */
+int SerialChannel::write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr,
+ uint8_t seq, sdbusplus::message_t&& m)
+{
+ std::span<uint8_t> out;
+ uint8_t checksum;
+
+ try
+ {
+ if (m.is_method_error())
+ {
+ // Extra copy to workaround lack of `const sd_bus_error` constructor
+ auto error = *m.get_error();
+ throw sdbusplus::exception::SdBusError(&error, "ipmid response");
+ }
+
+ uint8_t netFn = 0xff;
+ uint8_t lun = 0xff;
+ uint8_t cmd = 0xff;
+ uint8_t cc = 0xff;
+ std::vector<uint8_t> data;
+
+ m.read(std::tie(netFn, lun, cmd, cc, data));
+
+ uint8_t netFnLun = (netFn << netFnShift) | (lun & lunMask);
+ uint8_t seqLun = (seq << netFnShift) | (lun & lunMask);
+
+ std::vector<uint8_t> connectionHeader = {rqAddr, netFnLun};
+ std::vector<uint8_t> messageHeader = {rsAddr, seqLun, cmd, cc};
+
+ // Reserve the buffer size to avoid relloc and copy
+ responseBuffer.clear();
+ responseBuffer.reserve(
+ sizeof(struct IpmiSerialHeader) + 2 * data.size() +
+ 4); // 4 for bmStart & bmStop & 2 checksums
+
+ // bmStart
+ responseBuffer.push_back(bmStart);
+
+ // Assemble connection header and checksum
+ checksum = processEscapedCharacter(responseBuffer, connectionHeader);
+ responseBuffer.push_back(-checksum); // checksum1
+
+ // Assemble response message and checksum
+ checksum = processEscapedCharacter(responseBuffer, messageHeader);
+ checksum +=
+ processEscapedCharacter(responseBuffer, std::vector<uint8_t>(data));
+ responseBuffer.push_back(-checksum); // checksum2
+
+ // bmStop
+ responseBuffer.push_back(bmStop);
+
+ out = std::span<uint8_t>(responseBuffer.begin(), responseBuffer.end());
+
+ if (verbose)
+ {
+ lg2::info(
+ "Write serial request message with len={LEN}, netfn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", responseBuffer.size(), "NETFN", netFn, "LUN", lun, "CMD",
+ cmd, "SEQ", seq);
+
+ std::string msgData = "Tx: ";
+ for (auto c : responseBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ stdplus::fd::writeExact(uart, out);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("IPMI Response failure: {MSG}", "MSG", e);
+
+ return -1;
+ }
+
+ return out.size();
+}
+
+/**
+ * @brief Read function
+ */
+void SerialChannel::read(stdplus::Fd& uart, sdbusplus::bus_t& bus,
+ sdbusplus::slot_t& outstanding)
+{
+ std::array<uint8_t, ipmiSerialMaxBufferSize> buffer;
+ auto ipmiSerialPacket = stdplus::fd::read(uart, buffer);
+
+ if (ipmiSerialPacket.empty())
+ {
+ return;
+ }
+
+ if (outstanding)
+ {
+ lg2::error("Canceling outstanding request \n");
+ outstanding = sdbusplus::slot_t(nullptr);
+ }
+
+ // process ipmi serial packet
+ if (!consumeIpmiSerialPacket(ipmiSerialPacket, requestBuffer))
+ {
+ lg2::info("Wait for more data ... \n");
+ return;
+ }
+
+ // validate ipmi serial packet length
+ if (requestBuffer.size() <
+ (sizeof(struct IpmiSerialHeader) + ipmiSerialChecksumSize))
+ {
+ lg2::error("Invalid request length, ignoring \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum1
+ if (calculateChecksum(std::span<uint8_t>(requestBuffer.begin(),
+ ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 1 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ // validate checksum2
+ if (calculateChecksum(std::span<uint8_t>(
+ &requestBuffer[ipmiSerialConnectionHeaderLength],
+ requestBuffer.size() - ipmiSerialConnectionHeaderLength)))
+ {
+ lg2::error("Invalid request checksum 2 \n");
+ requestBuffer.clear();
+ return;
+ }
+
+ auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
+ "/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server", "execute");
+
+ std::map<std::string, std::variant<int>> options;
+ struct IpmiSerialHeader* header =
+ reinterpret_cast<struct IpmiSerialHeader*>(requestBuffer.data());
+
+ uint8_t rsAddr = header->rsAddr;
+ uint8_t netFn = header->rsNetFnLUN >> netFnShift;
+ uint8_t lun = header->rsNetFnLUN & lunMask;
+ uint8_t rqAddr = header->rqAddr;
+ uint8_t seq = header->rqSeqLUN >> netFnShift;
+ uint8_t cmd = header->cmd;
+
+ std::span reqSpan{requestBuffer.begin(),
+ requestBuffer.end() -
+ ipmiSerialChecksumSize}; // remove checksum 2
+ m.append(netFn, lun, cmd, reqSpan.subspan(sizeof(IpmiSerialHeader)),
+ options);
+
+ if (verbose)
+ {
+ lg2::info("Read serial request message with len={LEN}, netFn={NETFN}, "
+ "lun={LUN}, cmd={CMD}, seq={SEQ}",
+ "LEN", requestBuffer.size(), "NETFN", netFn, "LUN", lun,
+ "CMD", cmd, "SEQ", seq);
+
+ std::string msgData = "Rx: ";
+ for (auto c : requestBuffer)
+ {
+ msgData += std::format("{:#x} ", c);
+ }
+ lg2::info(msgData.c_str());
+ }
+
+ outstanding = m.call_async(stdplus::exception::ignore(
+ [&outstanding, this, &uart, _rsAddr{rsAddr}, _rqAddr{rqAddr},
+ _seq{seq}](sdbusplus::message_t&& m) {
+ outstanding = sdbusplus::slot_t(nullptr);
+
+ if (write(uart, _rsAddr, _rqAddr, _seq, std::move(m)) < 0)
+ {
+ lg2::error(
+ "Occur an error while attempting to send the response.");
+ }
+ }));
+
+ requestBuffer.clear();
+
+ return;
+}
+
+} // namespace serialbridge
diff --git a/transport/serialbridge/serialcmd.hpp b/transport/serialbridge/serialcmd.hpp
new file mode 100644
index 0000000..985921c
--- /dev/null
+++ b/transport/serialbridge/serialcmd.hpp
@@ -0,0 +1,64 @@
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/fd/intf.hpp>
+
+namespace serialbridge
+{
+
+static constexpr auto bmStart = 0xA0;
+static constexpr auto bmStop = 0xA5;
+static constexpr auto bmHandshake = 0xA6;
+static constexpr auto bmEscape = 0xAA;
+
+static constexpr auto ipmiSerialConnectionHeaderLength = 3;
+static constexpr auto ipmiSerialChecksumSize = 1;
+static constexpr auto ipmiSerialMaxBufferSize = 256;
+
+/**
+ * @brief IPMI Serial Message Structure
+ */
+struct IpmiSerialHeader
+{
+ uint8_t rsAddr;
+ uint8_t rsNetFnLUN;
+ uint8_t checksum1;
+ uint8_t rqAddr;
+ uint8_t rqSeqLUN;
+ uint8_t cmd;
+} __attribute__((packed));
+
+class SerialChannel
+{
+ public:
+ static constexpr uint8_t netFnShift = 2;
+ static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
+
+ SerialChannel(bool debug) : verbose(debug), msgState(MsgState::msgIdle) {};
+
+ int write(stdplus::Fd& uart, uint8_t rsAddr, uint8_t rqAddr, uint8_t seq,
+ sdbusplus::message_t&& m);
+ void read(stdplus::Fd& serial, sdbusplus::bus_t& bus,
+ sdbusplus::slot_t& outstanding);
+ uint8_t calculateChecksum(std::span<uint8_t> data);
+ uint8_t getUnescapedCharacter(uint8_t c);
+ int consumeIpmiSerialPacket(std::span<uint8_t>& escapedDataBytes,
+ std::vector<uint8_t>& unescapedDataBytes);
+ uint8_t processEscapedCharacter(std::vector<uint8_t>& buffer,
+ const std::vector<uint8_t>& data);
+
+ private:
+ bool verbose;
+ enum class MsgState
+ {
+ msgIdle = 0,
+ msgInProgress,
+ msgInEscape,
+ };
+ MsgState msgState;
+ std::vector<uint8_t> requestBuffer;
+ std::vector<uint8_t> responseBuffer;
+};
+
+} // namespace serialbridge
diff --git a/transport/serialbridge/test/meson.build b/transport/serialbridge/test/meson.build
new file mode 100644
index 0000000..39835d2
--- /dev/null
+++ b/transport/serialbridge/test/meson.build
@@ -0,0 +1,44 @@
+gtest = dependency('gtest', main: true, disabler: true, required: false)
+gmock = dependency('gmock', disabler: true, required: false)
+if not gtest.found() or not gmock.found()
+ gtest_opts = import('cmake').subproject_options()
+ gtest_opts.add_cmake_defines({'CMAKE_CXX_FLAGS': '-Wno-pedantic'})
+ gtest_proj = import('cmake').subproject(
+ 'googletest',
+ options: gtest_opts,
+ required: false,
+ )
+ if gtest_proj.found()
+ gtest = declare_dependency(
+ dependencies: [
+ dependency('threads'),
+ gtest_proj.dependency('gtest'),
+ gtest_proj.dependency('gtest_main'),
+ ],
+ )
+ gmock = gtest_proj.dependency('gmock')
+ else
+ assert(not get_option('tests').enabled(), 'Googletest is required')
+ endif
+endif
+
+# Build/add serial_unittest to test suite
+test(
+ 'transport_serial',
+ executable(
+ 'transport_serial_unittest',
+ 'serial_unittest.cpp',
+ '../serialcmd.cpp',
+ include_directories: root_inc,
+ build_by_default: false,
+ implicit_include_directories: false,
+ dependencies: [
+ sdbusplus_dep,
+ stdplus_dep,
+ phosphor_logging_dep,
+ sdeventplus_dep,
+ gtest,
+ gmock,
+ ],
+ ),
+)
diff --git a/transport/serialbridge/test/serial_unittest.cpp b/transport/serialbridge/test/serial_unittest.cpp
new file mode 100644
index 0000000..3fb0227
--- /dev/null
+++ b/transport/serialbridge/test/serial_unittest.cpp
@@ -0,0 +1,84 @@
+#include <transport/serialbridge/serialcmd.hpp>
+
+#include <gtest/gtest.h>
+
+namespace serialbridge
+{
+
+/**
+ * @brief Table of special characters
+ */
+std::unordered_map<uint8_t, uint8_t> testsets = {
+ {bmStart, 0xB0}, /* start */
+ {bmStop, 0xB5}, /* stop */
+ {bmHandshake, 0xB6}, /* packet handshake */
+ {bmEscape, 0xBA}, /* data escape */
+ {0x1B, 0x3B} /* escape */
+};
+
+TEST(TestSpecialCharact, getUnescapedCharact)
+{
+ uint8_t c;
+ auto channel = std::make_shared<SerialChannel>(0);
+
+ for (const auto& set : testsets)
+ {
+ c = channel->getUnescapedCharacter(set.second);
+ ASSERT_EQ(c, set.first);
+ }
+}
+
+TEST(TestSpecialCharact, processEscapedCharacter)
+{
+ std::vector<uint8_t> buffer;
+ uint8_t unescaped = 0xd0;
+ auto channel = std::make_shared<SerialChannel>(0);
+
+ channel->processEscapedCharacter(buffer, std::vector<uint8_t>{bmStart});
+
+ ASSERT_EQ(buffer.at(0), bmEscape);
+ ASSERT_EQ(buffer.at(1), testsets.at(bmStart));
+
+ buffer.clear();
+ channel->processEscapedCharacter(buffer, std::vector<uint8_t>{unescaped});
+
+ ASSERT_EQ(buffer.at(0), unescaped);
+}
+
+TEST(TestChecksum, calculateChecksum)
+{
+ std::array<uint8_t, 5> dataBytes{0x01, 0x10, 0x60, 0xf0, 0x50};
+ auto channel = std::make_shared<SerialChannel>(0);
+
+ uint8_t checksum =
+ channel->calculateChecksum(std::span<uint8_t>(dataBytes));
+
+ checksum += (~checksum) + 1;
+ ASSERT_EQ(checksum, 0);
+}
+
+TEST(TestIpmiSerialPacket, consumeIpmiSerialPacket)
+{
+ std::vector<uint8_t> dataBytes{bmStart, 0x20, 0x18, 0xc8, 0x81,
+ 0xc, 0x46, 0x01, 0x2c, bmStop};
+ std::vector<uint8_t> dataBytesSplit1{bmStart, 0x20, 0x18, 0xc8};
+ std::vector<uint8_t> dataBytesSplit2{0x81, 0xc, 0x46, 0x01, 0x2c, bmStop};
+ std::span<uint8_t> input(dataBytes);
+ std::span<uint8_t> input1(dataBytesSplit1);
+ std::span<uint8_t> input2(dataBytesSplit2);
+ std::vector<uint8_t> output;
+
+ auto channel = std::make_shared<SerialChannel>(0);
+
+ auto result = channel->consumeIpmiSerialPacket(input, output);
+
+ ASSERT_EQ(result, true);
+
+ output.clear();
+ result = channel->consumeIpmiSerialPacket(input1, output);
+ ASSERT_EQ(result, false);
+ result = channel->consumeIpmiSerialPacket(input2, output);
+ ASSERT_EQ(result, true);
+}
+
+} // namespace serialbridge
diff --git a/transportconstants.hpp b/transportconstants.hpp
new file mode 100644
index 0000000..3f865c6
--- /dev/null
+++ b/transportconstants.hpp
@@ -0,0 +1,160 @@
+#pragma once
+
+#include <ipmid/api-types.hpp>
+#include <stdplus/zstring_view.hpp>
+
+#include <cstdint>
+
+namespace ipmi
+{
+namespace transport
+{
+
+using stdplus::operator""_zsv;
+
+// D-Bus Network Daemon definitions
+constexpr auto PATH_ROOT = "/xyz/openbmc_project/network"_zsv;
+constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
+constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP";
+constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create";
+constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress";
+constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor";
+constexpr auto INTF_NEIGHBOR_CREATE_STATIC =
+ "xyz.openbmc_project.Network.Neighbor.CreateStatic";
+constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
+constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create";
+
+/** @brief IPMI LAN Parameters */
+enum class LanParam : uint8_t
+{
+ SetStatus = 0,
+ AuthSupport = 1,
+ AuthEnables = 2,
+ IP = 3,
+ IPSrc = 4,
+ MAC = 5,
+ SubnetMask = 6,
+ Gateway1 = 12,
+ Gateway1MAC = 13,
+ VLANId = 20,
+ CiphersuiteSupport = 22,
+ CiphersuiteEntries = 23,
+ cipherSuitePrivilegeLevels = 24,
+ IPFamilySupport = 50,
+ IPFamilyEnables = 51,
+ IPv6Status = 55,
+ IPv6StaticAddresses = 56,
+ IPv6DynamicAddresses = 59,
+ IPv6RouterControl = 64,
+ IPv6StaticRouter1IP = 65,
+ IPv6StaticRouter1MAC = 66,
+ IPv6StaticRouter1PrefixLength = 67,
+ IPv6StaticRouter1PrefixValue = 68,
+};
+
+/** @brief IPMI IP Origin Types */
+enum class IPSrc : uint8_t
+{
+ Unspecified = 0,
+ Static = 1,
+ DHCP = 2,
+ BIOS = 3,
+ BMC = 4,
+};
+
+/** @brief IPMI Set Status */
+enum class SetStatus : uint8_t
+{
+ Complete = 0,
+ InProgress = 1,
+ Commit = 2,
+};
+
+/** @brief IPMI Family Suport Bits */
+namespace IPFamilySupportFlag
+{
+constexpr uint8_t IPv6Only = 0;
+constexpr uint8_t DualStack = 1;
+constexpr uint8_t IPv6Alerts = 2;
+} // namespace IPFamilySupportFlag
+
+/** @brief IPMI IPFamily Enables Flag */
+enum class IPFamilyEnables : uint8_t
+{
+ IPv4Only = 0,
+ IPv6Only = 1,
+ DualStack = 2,
+};
+
+/** @brief IPMI IPv6 Dyanmic Status Bits */
+namespace IPv6StatusFlag
+{
+constexpr uint8_t DHCP = 0;
+constexpr uint8_t SLAAC = 1;
+}; // namespace IPv6StatusFlag
+
+/** @brief IPMI IPv6 Source */
+enum class IPv6Source : uint8_t
+{
+ Static = 0,
+ SLAAC = 1,
+ DHCP = 2,
+};
+
+/** @brief IPMI IPv6 Address Status */
+enum class IPv6AddressStatus : uint8_t
+{
+ Active = 0,
+ Disabled = 1,
+};
+
+namespace IPv6RouterControlFlag
+{
+constexpr uint8_t Static = 0;
+constexpr uint8_t Dynamic = 1;
+}; // namespace IPv6RouterControlFlag
+
+// LAN Handler specific response codes
+constexpr Cc ccParamNotSupported = 0x80;
+constexpr Cc ccParamSetLocked = 0x81;
+constexpr Cc ccParamReadOnly = 0x82;
+
+// VLANs are a 12-bit value
+constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
+constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000;
+
+// Arbitrary v4 Address Limits
+constexpr uint8_t MAX_IPV4_ADDRESSES = 2;
+
+// Arbitrary v6 Address Limits to prevent too much output in ipmitool
+constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15;
+constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15;
+
+// Prefix length limits of phosphor-networkd
+constexpr uint8_t MIN_IPV4_PREFIX_LENGTH = 1;
+constexpr uint8_t MAX_IPV4_PREFIX_LENGTH = 32;
+constexpr uint8_t MIN_IPV6_PREFIX_LENGTH = 1;
+constexpr uint8_t MAX_IPV6_PREFIX_LENGTH = 128;
+
+/** @enum SolConfParam
+ *
+ * using for Set/Get SOL configuration parameters command.
+ */
+enum class SolConfParam : uint8_t
+{
+ Progress, //!< Set In Progress.
+ Enable, //!< SOL Enable.
+ Authentication, //!< SOL Authentication.
+ Accumulate, //!< Character Accumulate Interval & Send Threshold.
+ Retry, //!< SOL Retry.
+ NonVbitrate, //!< SOL non-volatile bit rate.
+ Vbitrate, //!< SOL volatile bit rate.
+ Channel, //!< SOL payload channel.
+ Port, //!< SOL payload port.
+};
+
+constexpr uint8_t ipmiCCParamNotSupported = 0x80;
+constexpr uint8_t ipmiCCWriteReadParameter = 0x82;
+
+} // namespace transport
+} // namespace ipmi
diff --git a/transporthandler.cpp b/transporthandler.cpp
new file mode 100644
index 0000000..7218da9
--- /dev/null
+++ b/transporthandler.cpp
@@ -0,0 +1,1741 @@
+#include "transporthandler.hpp"
+
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <stdplus/net/addr/subnet.hpp>
+#include <stdplus/raw.hpp>
+
+#include <array>
+#include <fstream>
+
+using phosphor::logging::elog;
+using sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using sdbusplus::error::xyz::openbmc_project::common::InvalidArgument;
+using sdbusplus::server::xyz::openbmc_project::network::EthernetInterface;
+using sdbusplus::server::xyz::openbmc_project::network::IP;
+using sdbusplus::server::xyz::openbmc_project::network::Neighbor;
+
+namespace cipher
+{
+
+std::vector<uint8_t> getCipherList()
+{
+ std::vector<uint8_t> cipherList;
+
+ std::ifstream jsonFile(cipher::configFile);
+ if (!jsonFile.is_open())
+ {
+ lg2::error("Channel Cipher suites file not found");
+ elog<InternalFailure>();
+ }
+
+ auto data = Json::parse(jsonFile, nullptr, false);
+ if (data.is_discarded())
+ {
+ lg2::error("Parsing channel cipher suites JSON failed");
+ elog<InternalFailure>();
+ }
+
+ // Byte 1 is reserved
+ cipherList.push_back(0x00);
+
+ for (const auto& record : data)
+ {
+ cipherList.push_back(record.value(cipher, 0));
+ }
+
+ return cipherList;
+}
+} // namespace cipher
+
+namespace ipmi
+{
+namespace transport
+{
+
+/** @brief Valid address origins for IPv4 */
+const std::unordered_set<IP::AddressOrigin> originsV4 = {
+ IP::AddressOrigin::Static,
+ IP::AddressOrigin::DHCP,
+};
+
+static constexpr uint8_t oemCmdStart = 192;
+
+// Checks if the ifname is part of the networkd path
+// This assumes the path came from the network subtree PATH_ROOT
+bool ifnameInPath(std::string_view ifname, std::string_view path)
+{
+ constexpr auto rs = PATH_ROOT.size() + 1; // ROOT + separator
+ const auto is = rs + ifname.size(); // ROOT + sep + ifname
+ return path.size() > rs && path.substr(rs).starts_with(ifname) &&
+ (path.size() == is || path[is] == '/');
+}
+
+std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus_t& bus,
+ uint8_t channel)
+{
+ auto ifname = getChannelName(channel);
+ if (ifname.empty())
+ {
+ return std::nullopt;
+ }
+
+ // Enumerate all VLAN + ETHERNET interfaces
+ std::vector<std::string> interfaces = {INTF_VLAN, INTF_ETHERNET};
+ ipmi::ObjectTree objs =
+ ipmi::getSubTree(bus, interfaces, std::string{PATH_ROOT});
+
+ ChannelParams params;
+ for (const auto& [path, impls] : objs)
+ {
+ if (!ifnameInPath(ifname, path))
+ {
+ continue;
+ }
+ for (const auto& [service, intfs] : impls)
+ {
+ bool vlan = false;
+ bool ethernet = false;
+ for (const auto& intf : intfs)
+ {
+ if (intf == INTF_VLAN)
+ {
+ vlan = true;
+ }
+ else if (intf == INTF_ETHERNET)
+ {
+ ethernet = true;
+ }
+ }
+ if (params.service.empty() && (vlan || ethernet))
+ {
+ params.service = service;
+ }
+ if (params.ifPath.empty() && !vlan && ethernet)
+ {
+ params.ifPath = path;
+ }
+ if (params.logicalPath.empty() && vlan)
+ {
+ params.logicalPath = path;
+ }
+ }
+ }
+
+ // We must have a path for the underlying interface
+ if (params.ifPath.empty())
+ {
+ return std::nullopt;
+ }
+ // We don't have a VLAN so the logical path is the same
+ if (params.logicalPath.empty())
+ {
+ params.logicalPath = params.ifPath;
+ }
+
+ params.id = channel;
+ params.ifname = std::move(ifname);
+ return params;
+}
+
+ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel)
+{
+ auto params = maybeGetChannelParams(bus, channel);
+ if (!params)
+ {
+ lg2::error("Failed to get channel params: {CHANNEL}", "CHANNEL",
+ channel);
+ elog<InternalFailure>();
+ }
+ return std::move(*params);
+}
+
+/** @brief Get / Set the Property value from phosphor-networkd EthernetInterface
+ */
+template <typename T>
+static T getEthProp(sdbusplus::bus_t& bus, const ChannelParams& params,
+ const std::string& prop)
+{
+ return std::get<T>(getDbusProperty(bus, params.service, params.logicalPath,
+ INTF_ETHERNET, prop));
+}
+template <typename T>
+static void setEthProp(sdbusplus::bus_t& bus, const ChannelParams& params,
+ const std::string& prop, const T& t)
+{
+ return setDbusProperty(bus, params.service, params.logicalPath,
+ INTF_ETHERNET, prop, t);
+}
+
+/** @brief Determines the MAC of the ethernet interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @return The configured mac address
+ */
+stdplus::EtherAddr getMACProperty(sdbusplus::bus_t& bus,
+ const ChannelParams& params)
+{
+ auto prop = getDbusProperty(bus, params.service, params.ifPath, INTF_MAC,
+ "MACAddress");
+ return stdplus::fromStr<stdplus::EtherAddr>(std::get<std::string>(prop));
+}
+
+/** @brief Sets the system value for MAC address on the given interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] mac - MAC address to apply
+ */
+void setMACProperty(sdbusplus::bus_t& bus, const ChannelParams& params,
+ stdplus::EtherAddr mac)
+{
+ setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress",
+ stdplus::toStr(mac));
+}
+
+void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& path)
+{
+ if (path.empty())
+ {
+ return;
+ }
+ try
+ {
+ auto req = bus.new_method_call(service.c_str(), path.c_str(),
+ ipmi::DELETE_INTERFACE, "Delete");
+ bus.call_noreply(req);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ if (strcmp(e.name(),
+ "xyz.openbmc_project.Common.Error.InternalFailure") != 0 &&
+ strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0)
+ {
+ // We want to rethrow real errors
+ throw;
+ }
+ }
+}
+
+/** @brief Sets the address info configured for the interface
+ * If a previous address path exists then it will be removed
+ * before the new address is added.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] address - The address of the new IP
+ * @param[in] prefix - The prefix of the new IP
+ */
+template <int family>
+void createIfAddr(sdbusplus::bus_t& bus, const ChannelParams& params,
+ typename AddrFamily<family>::addr address, uint8_t prefix)
+{
+ auto newreq =
+ bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
+ INTF_IP_CREATE, "IP");
+ std::string protocol =
+ sdbusplus::common::xyz::openbmc_project::network::convertForMessage(
+ AddrFamily<family>::protocol);
+ stdplus::ToStrHandle<stdplus::ToStr<typename AddrFamily<family>::addr>> tsh;
+ newreq.append(protocol, tsh(address), prefix, "");
+ bus.call_noreply(newreq);
+}
+
+/** @brief Trivial helper for getting the IPv4 address from getIfAddrs()
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @return The address and prefix if found
+ */
+auto getIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+ std::optional<IfAddr<AF_INET>> ifaddr4 = std::nullopt;
+ IP::AddressOrigin src;
+
+ try
+ {
+ src = std::get<bool>(
+ getDbusProperty(bus, params.service, params.logicalPath,
+ INTF_ETHERNET, "DHCP4"))
+ ? IP::AddressOrigin::DHCP
+ : IP::AddressOrigin::Static;
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to get IPv4 source");
+ return ifaddr4;
+ }
+
+ for (uint8_t i = 0; i < MAX_IPV4_ADDRESSES; ++i)
+ {
+ ifaddr4 = getIfAddr<AF_INET>(bus, params, i, originsV4);
+ if (ifaddr4 && src == ifaddr4->origin)
+ {
+ break;
+ }
+ else
+ {
+ ifaddr4 = std::nullopt;
+ }
+ }
+ return ifaddr4;
+}
+
+/** @brief Reconfigures the IPv4 address info configured for the interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] address - The new address if specified
+ * @param[in] prefix - The new address prefix if specified
+ */
+void reconfigureIfAddr4(sdbusplus::bus_t& bus, const ChannelParams& params,
+ std::optional<stdplus::In4Addr> address,
+ std::optional<uint8_t> prefix)
+{
+ auto ifaddr = getIfAddr4(bus, params);
+ if (!ifaddr && !address)
+ {
+ lg2::error("Missing address for IPv4 assignment");
+ elog<InternalFailure>();
+ }
+ uint8_t fallbackPrefix = AddrFamily<AF_INET>::defaultPrefix;
+ auto addr = stdplus::In4Addr{};
+ if (ifaddr)
+ {
+ addr = ifaddr->address;
+ fallbackPrefix = ifaddr->prefix;
+ deleteObjectIfExists(bus, params.service, ifaddr->path);
+ }
+ addr = address.value_or(addr);
+ if (addr != stdplus::In4Addr{})
+ {
+ createIfAddr<AF_INET>(bus, params, addr,
+ prefix.value_or(fallbackPrefix));
+ }
+}
+
+template <int family>
+std::optional<IfNeigh<family>> findGatewayNeighbor(sdbusplus::bus_t& bus,
+ const ChannelParams& params,
+ ObjectLookupCache& neighbors)
+{
+ auto gateway = getGatewayProperty<family>(bus, params);
+ if (!gateway)
+ {
+ return std::nullopt;
+ }
+
+ return findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+}
+
+template <int family>
+std::optional<IfNeigh<family>> getGatewayNeighbor(sdbusplus::bus_t& bus,
+ const ChannelParams& params)
+{
+ ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+ return findGatewayNeighbor<family>(bus, params, neighbors);
+}
+
+template <int family>
+void reconfigureGatewayMAC(sdbusplus::bus_t& bus, const ChannelParams& params,
+ stdplus::EtherAddr mac)
+{
+ auto gateway = getGatewayProperty<family>(bus, params);
+ if (!gateway)
+ {
+ lg2::error("Tried to set Gateway MAC without Gateway");
+ elog<InternalFailure>();
+ }
+
+ ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+ auto neighbor =
+ findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+ if (neighbor)
+ {
+ deleteObjectIfExists(bus, params.service, neighbor->path);
+ }
+
+ createNeighbor<family>(bus, params, *gateway, mac);
+}
+
+/** @brief Deconfigures the IPv6 address info configured for the interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] idx - The address index to operate on
+ */
+void deconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+ uint8_t idx)
+{
+ auto ifaddr = getIfAddr<AF_INET6>(bus, params, idx, originsV6Static);
+ if (ifaddr)
+ {
+ deleteObjectIfExists(bus, params.service, ifaddr->path);
+ }
+}
+
+/** @brief Reconfigures the IPv6 address info configured for the interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] idx - The address index to operate on
+ * @param[in] address - The new address
+ * @param[in] prefix - The new address prefix
+ */
+void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+ uint8_t idx, stdplus::In6Addr address, uint8_t prefix)
+{
+ deconfigureIfAddr6(bus, params, idx);
+ createIfAddr<AF_INET6>(bus, params, address, prefix);
+}
+
+/** @brief Converts the AddressOrigin into an IPv6Source
+ *
+ * @param[in] origin - The DBus Address Origin to convert
+ * @return The IPv6Source version of the origin
+ */
+IPv6Source originToSourceType(IP::AddressOrigin origin)
+{
+ switch (origin)
+ {
+ case IP::AddressOrigin::Static:
+ return IPv6Source::Static;
+ case IP::AddressOrigin::DHCP:
+ return IPv6Source::DHCP;
+ case IP::AddressOrigin::SLAAC:
+ return IPv6Source::SLAAC;
+ default:
+ {
+ auto originStr = sdbusplus::common::xyz::openbmc_project::network::
+ convertForMessage(origin);
+ lg2::error("Invalid IP::AddressOrigin conversion to IPv6Source, "
+ "origin: {ORIGIN}",
+ "ORIGIN", originStr);
+ elog<InternalFailure>();
+ }
+ }
+}
+
+/** @brief Packs the IPMI message response with IPv6 address data
+ *
+ * @param[out] ret - The IPMI response payload to be packed
+ * @param[in] channel - The channel id corresponding to an ethernet interface
+ * @param[in] set - The set selector for determining address index
+ * @param[in] origins - Set of valid origins for address filtering
+ */
+void getLanIPv6Address(message::Payload& ret, uint8_t channel, uint8_t set,
+ const std::unordered_set<IP::AddressOrigin>& origins)
+{
+ auto source = IPv6Source::Static;
+ bool enabled = false;
+ stdplus::In6Addr addr{};
+ uint8_t prefix{};
+ auto status = IPv6AddressStatus::Disabled;
+
+ auto ifaddr = channelCall<getIfAddr<AF_INET6>>(channel, set, origins);
+ if (ifaddr)
+ {
+ source = originToSourceType(ifaddr->origin);
+ enabled = (origins == originsV6Static);
+ addr = ifaddr->address;
+ prefix = ifaddr->prefix;
+ status = IPv6AddressStatus::Active;
+ }
+
+ ret.pack(set);
+ ret.pack(types::enum_cast<uint4_t>(source), uint3_t{}, enabled);
+ ret.pack(stdplus::raw::asView<char>(addr));
+ ret.pack(prefix);
+ ret.pack(types::enum_cast<uint8_t>(status));
+}
+
+/** @brief Gets the vlan ID configured on the interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @return VLAN id or the standard 0 for no VLAN
+ */
+uint16_t getVLANProperty(sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+ // VLAN devices will always have a separate logical object
+ if (params.ifPath == params.logicalPath)
+ {
+ return 0;
+ }
+
+ auto vlan = std::get<uint32_t>(getDbusProperty(
+ bus, params.service, params.logicalPath, INTF_VLAN, "Id"));
+ if ((vlan & VLAN_VALUE_MASK) != vlan)
+ {
+ lg2::error("networkd returned an invalid vlan: {VLAN} "
+ "(CH={CHANNEL}, IF={IFNAME})",
+ "CHANNEL", params.id, "IFNAME", params.ifname, "VLAN", vlan);
+ elog<InternalFailure>();
+ }
+ return vlan;
+}
+
+/** @brief Deletes all of the possible configuration parameters for a channel
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ */
+void deconfigureChannel(sdbusplus::bus_t& bus, ChannelParams& params)
+{
+ // Delete all objects associated with the interface
+ ObjectTree objs =
+ ipmi::getSubTree(bus, std::vector<std::string>{DELETE_INTERFACE},
+ std::string{PATH_ROOT});
+ for (const auto& [path, impls] : objs)
+ {
+ if (!ifnameInPath(params.ifname, path))
+ {
+ continue;
+ }
+ for (const auto& [service, intfs] : impls)
+ {
+ deleteObjectIfExists(bus, service, path);
+ }
+ // Update params to reflect the deletion of vlan
+ if (path == params.logicalPath)
+ {
+ params.logicalPath = params.ifPath;
+ }
+ }
+
+ // Clear out any settings on the lower physical interface
+ setEthProp(bus, params, "DHCP4", false);
+ setEthProp(bus, params, "DHCP6", false);
+ setEthProp(bus, params, "IPv6AcceptRA", false);
+}
+
+/** @brief Creates a new VLAN on the specified interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] vlan - The id of the new vlan
+ */
+void createVLAN(sdbusplus::bus_t& bus, ChannelParams& params, uint16_t vlan)
+{
+ if (vlan == 0)
+ {
+ return;
+ }
+
+ auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT.c_str(),
+ INTF_VLAN_CREATE, "VLAN");
+ req.append(params.ifname, static_cast<uint32_t>(vlan));
+ auto reply = bus.call(req);
+ sdbusplus::message::object_path newPath;
+ reply.read(newPath);
+ params.logicalPath = std::move(newPath);
+}
+
+/** @brief Performs the necessary reconfiguration to change the VLAN
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] vlan - The new vlan id to use
+ */
+void reconfigureVLAN(sdbusplus::bus_t& bus, ChannelParams& params,
+ uint16_t vlan)
+{
+ // Unfortunatetly we don't have built-in functions to migrate our interface
+ // customizations to new VLAN interfaces, or have some kind of decoupling.
+ // We therefore must retain all of our old information, setup the new VLAN
+ // configuration, then restore the old info.
+
+ // Save info from the old logical interface
+ bool dhcp4 = getEthProp<bool>(bus, params, "DHCP4");
+ bool dhcp6 = getEthProp<bool>(bus, params, "DHCP6");
+ bool ra = getEthProp<bool>(bus, params, "IPv6AcceptRA");
+ ObjectLookupCache ips(bus, params, INTF_IP);
+ auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips);
+ std::vector<IfAddr<AF_INET6>> ifaddrs6;
+ for (uint8_t i = 0; i < MAX_IPV6_STATIC_ADDRESSES; ++i)
+ {
+ auto ifaddr6 =
+ findIfAddr<AF_INET6>(bus, params, i, originsV6Static, ips);
+ if (!ifaddr6)
+ {
+ break;
+ }
+ ifaddrs6.push_back(std::move(*ifaddr6));
+ }
+ ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+ auto neighbor4 = findGatewayNeighbor<AF_INET>(bus, params, neighbors);
+ auto neighbor6 = findGatewayNeighbor<AF_INET6>(bus, params, neighbors);
+
+ deconfigureChannel(bus, params);
+ createVLAN(bus, params, vlan);
+
+ // Re-establish the saved settings
+ setEthProp(bus, params, "DHCP4", dhcp4);
+ setEthProp(bus, params, "DHCP6", dhcp6);
+ setEthProp(bus, params, "IPv6AcceptRA", ra);
+ if (ifaddr4)
+ {
+ createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix);
+ }
+ for (const auto& ifaddr6 : ifaddrs6)
+ {
+ createIfAddr<AF_INET6>(bus, params, ifaddr6.address, ifaddr6.prefix);
+ }
+ if (neighbor4)
+ {
+ createNeighbor<AF_INET>(bus, params, neighbor4->ip, neighbor4->mac);
+ }
+ if (neighbor6)
+ {
+ createNeighbor<AF_INET6>(bus, params, neighbor6->ip, neighbor6->mac);
+ }
+}
+
+// We need to store this value so it can be returned to the client
+// It is volatile so safe to store in daemon memory.
+static std::unordered_map<uint8_t, SetStatus> setStatus;
+
+// Until we have good support for fixed versions of IPMI tool
+// we need to return the VLAN id for disabled VLANs. The value is only
+// used for verification that a disable operation succeeded and will only
+// be sent if our system indicates that vlans are disabled.
+static std::unordered_map<uint8_t, uint16_t> lastDisabledVlan;
+
+/** @brief Gets the set status for the channel if it exists
+ * Otherise populates and returns the default value.
+ *
+ * @param[in] channel - The channel id corresponding to an ethernet interface
+ * @return A reference to the SetStatus for the channel
+ */
+SetStatus& getSetStatus(uint8_t channel)
+{
+ auto it = setStatus.find(channel);
+ if (it != setStatus.end())
+ {
+ return it->second;
+ }
+ return setStatus[channel] = SetStatus::Complete;
+}
+
+/** @brief Unpacks the trivially copyable type from the message */
+template <typename T>
+static T unpackT(message::Payload& req)
+{
+ std::array<uint8_t, sizeof(T)> bytes;
+ if (req.unpack(bytes) != 0)
+ {
+ throw ccReqDataLenInvalid;
+ }
+ return stdplus::raw::copyFrom<T>(bytes);
+}
+
+/** @brief Ensure the message is fully unpacked */
+static void unpackFinal(message::Payload& req)
+{
+ if (!req.fullyUnpacked())
+ {
+ throw ccReqDataTruncated;
+ }
+}
+
+/**
+ * Define placeholder command handlers for the OEM Extension bytes for the Set
+ * LAN Configuration Parameters and Get LAN Configuration Parameters
+ * commands. Using "weak" linking allows the placeholder setLanOem/getLanOem
+ * functions below to be overridden.
+ * To create handlers for your own proprietary command set:
+ * Create/modify a phosphor-ipmi-host Bitbake append file within your Yocto
+ * recipe
+ * Create C++ file(s) that define IPMI handler functions matching the
+ * function names below (i.e. setLanOem). The default name for the
+ * transport IPMI commands is transporthandler_oem.cpp.
+ * Add:
+ * EXTRA_OEMESON:append = "-Dtransport-oem=enabled"
+ * Create a do_configure:prepend()/do_install:append() method in your
+ * bbappend file to copy the file to the build directory.
+ * Add:
+ * PROJECT_SRC_DIR := "${THISDIR}/${PN}"
+ * # Copy the "strong" functions into the working directory, overriding the
+ * # placeholder functions.
+ * do_configure:prepend(){
+ * cp -f ${PROJECT_SRC_DIR}/transporthandler_oem.cpp ${S}
+ * }
+ *
+ * # Clean up after complilation has completed
+ * do_install:append(){
+ * rm -f ${S}/transporthandler_oem.cpp
+ * }
+ *
+ */
+
+/**
+ * Define the placeholder OEM commands as having weak linkage. Create
+ * setLanOem, and getLanOem functions in the transporthandler_oem.cpp
+ * file. The functions defined there must not have the "weak" attribute
+ * applied to them.
+ */
+RspType<> setLanOem(uint8_t channel, uint8_t parameter, message::Payload& req)
+ __attribute__((weak));
+RspType<message::Payload> getLanOem(uint8_t channel, uint8_t parameter,
+ uint8_t set, uint8_t block)
+ __attribute__((weak));
+
+RspType<> setLanOem(uint8_t, uint8_t, message::Payload& req)
+{
+ req.trailingOk = true;
+ return response(ccParamNotSupported);
+}
+
+RspType<message::Payload> getLanOem(uint8_t, uint8_t, uint8_t, uint8_t)
+{
+ return response(ccParamNotSupported);
+}
+
+/**
+ * @brief is a valid LAN channel.
+ *
+ * This function checks whether the input channel is a valid LAN channel or not.
+ *
+ * @param[in] channel: the channel number.
+ * @return nullopt if the channel is invalid, false if the channel is not a LAN
+ * channel, true if the channel is a LAN channel.
+ **/
+std::optional<bool> isLanChannel(uint8_t channel)
+{
+ ChannelInfo chInfo;
+ auto cc = getChannelInfo(channel, chInfo);
+ if (cc != ccSuccess)
+ {
+ return std::nullopt;
+ }
+
+ return chInfo.mediumType ==
+ static_cast<uint8_t>(EChannelMediumType::lan8032);
+}
+
+RspType<> setLanInt(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1,
+ uint8_t parameter, message::Payload& req)
+{
+ const uint8_t channel = convertCurrentChannelNum(
+ static_cast<uint8_t>(channelBits), ctx->channel);
+ if (reserved1 || !isValidChannel(channel))
+ {
+ lg2::error("Set Lan - Invalid field in request");
+ req.trailingOk = true;
+ return responseInvalidFieldRequest();
+ }
+
+ if (!isLanChannel(channel).value_or(false))
+ {
+ lg2::error("Set Lan - Not a LAN channel");
+ return responseInvalidFieldRequest();
+ }
+
+ switch (static_cast<LanParam>(parameter))
+ {
+ case LanParam::SetStatus:
+ {
+ uint2_t flag;
+ uint6_t rsvd;
+ if (req.unpack(flag, rsvd) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ if (rsvd)
+ {
+ return responseInvalidFieldRequest();
+ }
+ auto status = static_cast<SetStatus>(static_cast<uint8_t>(flag));
+ switch (status)
+ {
+ case SetStatus::Complete:
+ {
+ getSetStatus(channel) = status;
+ return responseSuccess();
+ }
+ case SetStatus::InProgress:
+ {
+ auto& storedStatus = getSetStatus(channel);
+ if (storedStatus == SetStatus::InProgress)
+ {
+ return response(ccParamSetLocked);
+ }
+ storedStatus = status;
+ return responseSuccess();
+ }
+ case SetStatus::Commit:
+ if (getSetStatus(channel) != SetStatus::InProgress)
+ {
+ return responseInvalidFieldRequest();
+ }
+ return responseSuccess();
+ }
+ return response(ccParamNotSupported);
+ }
+ case LanParam::AuthSupport:
+ {
+ req.trailingOk = true;
+ return response(ccParamReadOnly);
+ }
+ case LanParam::AuthEnables:
+ {
+ req.trailingOk = true;
+ return response(ccParamReadOnly);
+ }
+ case LanParam::IP:
+ {
+ if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+ {
+ return responseCommandNotAvailable();
+ }
+ auto ip = unpackT<stdplus::In4Addr>(req);
+ unpackFinal(req);
+ channelCall<reconfigureIfAddr4>(channel, ip, std::nullopt);
+ return responseSuccess();
+ }
+ case LanParam::IPSrc:
+ {
+ uint4_t flag;
+ uint4_t rsvd;
+ if (req.unpack(flag, rsvd) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ if (rsvd)
+ {
+ return responseInvalidFieldRequest();
+ }
+ switch (static_cast<IPSrc>(static_cast<uint8_t>(flag)))
+ {
+ case IPSrc::DHCP:
+ // The IPSrc IPMI command is only for IPv4
+ // management. Modifying IPv6 state is done using
+ // a completely different Set LAN Configuration
+ // subcommand.
+ channelCall<setEthProp<bool>>(channel, "DHCP4", true);
+ return responseSuccess();
+ case IPSrc::Unspecified:
+ case IPSrc::Static:
+ channelCall<setEthProp<bool>>(channel, "DHCP4", false);
+ return responseSuccess();
+ case IPSrc::BIOS:
+ case IPSrc::BMC:
+ return responseInvalidFieldRequest();
+ }
+ return response(ccParamNotSupported);
+ }
+ case LanParam::MAC:
+ {
+ auto mac = unpackT<stdplus::EtherAddr>(req);
+ unpackFinal(req);
+ channelCall<setMACProperty>(channel, mac);
+ return responseSuccess();
+ }
+ case LanParam::SubnetMask:
+ {
+ if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+ {
+ return responseCommandNotAvailable();
+ }
+ auto pfx = stdplus::maskToPfx(unpackT<stdplus::In4Addr>(req));
+ unpackFinal(req);
+ channelCall<reconfigureIfAddr4>(channel, std::nullopt, pfx);
+ return responseSuccess();
+ }
+ case LanParam::Gateway1:
+ {
+ if (channelCall<getEthProp<bool>>(channel, "DHCP4"))
+ {
+ return responseCommandNotAvailable();
+ }
+ auto gateway = unpackT<stdplus::In4Addr>(req);
+ unpackFinal(req);
+ channelCall<setGatewayProperty<AF_INET>>(channel, gateway);
+ return responseSuccess();
+ }
+ case LanParam::Gateway1MAC:
+ {
+ auto gatewayMAC = unpackT<stdplus::EtherAddr>(req);
+ unpackFinal(req);
+ channelCall<reconfigureGatewayMAC<AF_INET>>(channel, gatewayMAC);
+ return responseSuccess();
+ }
+ case LanParam::VLANId:
+ {
+ uint12_t vlanData;
+ uint3_t rsvd;
+ bool vlanEnable;
+
+ if (req.unpack(vlanData, rsvd, vlanEnable) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+
+ if (rsvd)
+ {
+ return responseInvalidFieldRequest();
+ }
+
+ uint16_t vlan = static_cast<uint16_t>(vlanData);
+
+ if (!vlanEnable)
+ {
+ lastDisabledVlan[channel] = vlan;
+ vlan = 0;
+ }
+ else if (vlan == 0 || vlan == VLAN_VALUE_MASK)
+ {
+ return responseInvalidFieldRequest();
+ }
+
+ channelCall<reconfigureVLAN>(channel, vlan);
+ return responseSuccess();
+ }
+ case LanParam::CiphersuiteSupport:
+ case LanParam::CiphersuiteEntries:
+ case LanParam::IPFamilySupport:
+ {
+ req.trailingOk = true;
+ return response(ccParamReadOnly);
+ }
+ case LanParam::IPFamilyEnables:
+ {
+ uint8_t enables;
+ if (req.unpack(enables) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ switch (static_cast<IPFamilyEnables>(enables))
+ {
+ case IPFamilyEnables::DualStack:
+ return responseSuccess();
+ case IPFamilyEnables::IPv4Only:
+ case IPFamilyEnables::IPv6Only:
+ return response(ccParamNotSupported);
+ }
+ return response(ccParamNotSupported);
+ }
+ case LanParam::IPv6Status:
+ {
+ req.trailingOk = true;
+ return response(ccParamReadOnly);
+ }
+ case LanParam::IPv6StaticAddresses:
+ {
+ uint8_t set;
+ uint7_t rsvd;
+ bool enabled;
+ uint8_t prefix;
+ uint8_t status;
+ if (req.unpack(set, rsvd, enabled) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ auto ip = unpackT<stdplus::In6Addr>(req);
+ if (req.unpack(prefix, status) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ if (rsvd)
+ {
+ return responseInvalidFieldRequest();
+ }
+ if (enabled)
+ {
+ if (prefix < MIN_IPV6_PREFIX_LENGTH ||
+ prefix > MAX_IPV6_PREFIX_LENGTH)
+ {
+ return responseParmOutOfRange();
+ }
+ channelCall<reconfigureIfAddr6>(channel, set, ip, prefix);
+ }
+ else
+ {
+ channelCall<deconfigureIfAddr6>(channel, set);
+ }
+ return responseSuccess();
+ }
+ case LanParam::IPv6DynamicAddresses:
+ {
+ req.trailingOk = true;
+ return response(ccParamReadOnly);
+ }
+ case LanParam::IPv6RouterControl:
+ {
+ std::bitset<8> control;
+ constexpr uint8_t reservedRACCBits = 0xfc;
+ if (req.unpack(control) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ if (std::bitset<8> expected(
+ control & std::bitset<8>(reservedRACCBits));
+ expected.any())
+ {
+ return response(ccParamNotSupported);
+ }
+
+ bool enableRA = control[IPv6RouterControlFlag::Dynamic];
+ channelCall<setEthProp<bool>>(channel, "IPv6AcceptRA", enableRA);
+ channelCall<setEthProp<bool>>(channel, "DHCP6", enableRA);
+ return responseSuccess();
+ }
+ case LanParam::IPv6StaticRouter1IP:
+ {
+ auto gateway = unpackT<stdplus::In6Addr>(req);
+ unpackFinal(req);
+ channelCall<setGatewayProperty<AF_INET6>>(channel, gateway);
+ return responseSuccess();
+ }
+ case LanParam::IPv6StaticRouter1MAC:
+ {
+ auto mac = unpackT<stdplus::EtherAddr>(req);
+ unpackFinal(req);
+ channelCall<reconfigureGatewayMAC<AF_INET6>>(channel, mac);
+ return responseSuccess();
+ }
+ case LanParam::IPv6StaticRouter1PrefixLength:
+ {
+ uint8_t prefix;
+ if (req.unpack(prefix) != 0)
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+ if (prefix != 0)
+ {
+ return responseInvalidFieldRequest();
+ }
+ return responseSuccess();
+ }
+ case LanParam::IPv6StaticRouter1PrefixValue:
+ {
+ unpackT<stdplus::In6Addr>(req);
+ unpackFinal(req);
+ // Accept any prefix value since our prefix length has to be 0
+ return responseSuccess();
+ }
+ case LanParam::cipherSuitePrivilegeLevels:
+ {
+ uint8_t rsvd;
+ std::array<uint4_t, ipmi::maxCSRecords> cipherSuitePrivs;
+
+ if (req.unpack(rsvd, cipherSuitePrivs))
+ {
+ return responseReqDataLenInvalid();
+ }
+ unpackFinal(req);
+
+ if (rsvd)
+ {
+ return responseInvalidFieldRequest();
+ }
+
+ uint8_t resp =
+ getCipherConfigObject(csPrivFileName, csPrivDefaultFileName)
+ .setCSPrivilegeLevels(channel, cipherSuitePrivs);
+ if (!resp)
+ {
+ return responseSuccess();
+ }
+ else
+ {
+ req.trailingOk = true;
+ return response(resp);
+ }
+ }
+ }
+
+ if (parameter >= oemCmdStart)
+ {
+ return setLanOem(channel, parameter, req);
+ }
+
+ req.trailingOk = true;
+ return response(ccParamNotSupported);
+}
+
+RspType<> setLan(Context::ptr ctx, uint4_t channelBits, uint4_t reserved1,
+ uint8_t parameter, message::Payload& req)
+{
+ try
+ {
+ return setLanInt(ctx, channelBits, reserved1, parameter, req);
+ }
+ catch (ipmi::Cc cc)
+ {
+ return response(cc);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ if (std::string_view{InvalidArgument::errName} == e.name())
+ {
+ return responseInvalidFieldRequest();
+ }
+ throw;
+ }
+}
+
+RspType<message::Payload> getLan(Context::ptr ctx, uint4_t channelBits,
+ uint3_t reserved, bool revOnly,
+ uint8_t parameter, uint8_t set, uint8_t block)
+{
+ message::Payload ret;
+ constexpr uint8_t current_revision = 0x11;
+ ret.pack(current_revision);
+
+ if (revOnly)
+ {
+ return responseSuccess(std::move(ret));
+ }
+
+ const uint8_t channel = convertCurrentChannelNum(
+ static_cast<uint8_t>(channelBits), ctx->channel);
+ if (reserved || !isValidChannel(channel))
+ {
+ lg2::error("Get Lan - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ if (!isLanChannel(channel).value_or(false))
+ {
+ lg2::error("Set Lan - Not a LAN channel");
+ return responseInvalidFieldRequest();
+ }
+
+ static std::vector<uint8_t> cipherList;
+ static bool listInit = false;
+ if (!listInit)
+ {
+ try
+ {
+ cipherList = cipher::getCipherList();
+ listInit = true;
+ }
+ catch (const std::exception& e)
+ {}
+ }
+
+ switch (static_cast<LanParam>(parameter))
+ {
+ case LanParam::SetStatus:
+ {
+ SetStatus status;
+ try
+ {
+ status = setStatus.at(channel);
+ }
+ catch (const std::out_of_range&)
+ {
+ status = SetStatus::Complete;
+ }
+ ret.pack(types::enum_cast<uint2_t>(status), uint6_t{});
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::AuthSupport:
+ {
+ std::bitset<6> support;
+ ret.pack(support, uint2_t{});
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::AuthEnables:
+ {
+ std::bitset<6> enables;
+ ret.pack(enables, uint2_t{}); // Callback
+ ret.pack(enables, uint2_t{}); // User
+ ret.pack(enables, uint2_t{}); // Operator
+ ret.pack(enables, uint2_t{}); // Admin
+ ret.pack(enables, uint2_t{}); // OEM
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IP:
+ {
+ auto ifaddr = channelCall<getIfAddr4>(channel);
+ stdplus::In4Addr addr{};
+ if (ifaddr)
+ {
+ addr = ifaddr->address;
+ }
+ ret.pack(stdplus::raw::asView<char>(addr));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPSrc:
+ {
+ auto src = channelCall<getEthProp<bool>>(channel, "DHCP4")
+ ? IPSrc::DHCP
+ : IPSrc::Static;
+ ret.pack(types::enum_cast<uint4_t>(src), uint4_t{});
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::MAC:
+ {
+ auto mac = channelCall<getMACProperty>(channel);
+ ret.pack(stdplus::raw::asView<char>(mac));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::SubnetMask:
+ {
+ auto ifaddr = channelCall<getIfAddr4>(channel);
+ uint8_t prefix = AddrFamily<AF_INET>::defaultPrefix;
+ if (ifaddr)
+ {
+ prefix = ifaddr->prefix;
+ }
+ auto netmask = stdplus::pfxToMask<stdplus::In4Addr>(prefix);
+ ret.pack(stdplus::raw::asView<char>(netmask));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::Gateway1:
+ {
+ auto gateway = channelCall<getGatewayProperty<AF_INET>>(channel);
+ ret.pack(stdplus::raw::asView<char>(
+ gateway.value_or(stdplus::In4Addr{})));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::Gateway1MAC:
+ {
+ stdplus::EtherAddr mac{};
+ auto neighbor = channelCall<getGatewayNeighbor<AF_INET>>(channel);
+ if (neighbor)
+ {
+ mac = neighbor->mac;
+ }
+ ret.pack(stdplus::raw::asView<char>(mac));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::VLANId:
+ {
+ uint16_t vlan = channelCall<getVLANProperty>(channel);
+ if (vlan != 0)
+ {
+ vlan |= VLAN_ENABLE_FLAG;
+ }
+ else
+ {
+ vlan = lastDisabledVlan[channel];
+ }
+ ret.pack(vlan);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::CiphersuiteSupport:
+ {
+ if (getChannelSessionSupport(channel) ==
+ EChannelSessSupported::none)
+ {
+ return responseInvalidFieldRequest();
+ }
+ if (!listInit)
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(static_cast<uint8_t>(cipherList.size() - 1));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::CiphersuiteEntries:
+ {
+ if (getChannelSessionSupport(channel) ==
+ EChannelSessSupported::none)
+ {
+ return responseInvalidFieldRequest();
+ }
+ if (!listInit)
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(cipherList);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPFamilySupport:
+ {
+ std::bitset<8> support;
+ support[IPFamilySupportFlag::IPv6Only] = 0;
+ support[IPFamilySupportFlag::DualStack] = 1;
+ support[IPFamilySupportFlag::IPv6Alerts] = 1;
+ ret.pack(support);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPFamilyEnables:
+ {
+ ret.pack(static_cast<uint8_t>(IPFamilyEnables::DualStack));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6Status:
+ {
+ ret.pack(MAX_IPV6_STATIC_ADDRESSES);
+ ret.pack(MAX_IPV6_DYNAMIC_ADDRESSES);
+ std::bitset<8> support;
+ support[IPv6StatusFlag::DHCP] = 1;
+ support[IPv6StatusFlag::SLAAC] = 1;
+ ret.pack(support);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6StaticAddresses:
+ {
+ if (set >= MAX_IPV6_STATIC_ADDRESSES)
+ {
+ return responseParmOutOfRange();
+ }
+ getLanIPv6Address(ret, channel, set, originsV6Static);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6DynamicAddresses:
+ {
+ if (set >= MAX_IPV6_DYNAMIC_ADDRESSES)
+ {
+ return responseParmOutOfRange();
+ }
+ getLanIPv6Address(ret, channel, set, originsV6Dynamic);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6RouterControl:
+ {
+ std::bitset<8> control;
+ control[IPv6RouterControlFlag::Dynamic] =
+ channelCall<getEthProp<bool>>(channel, "IPv6AcceptRA");
+ control[IPv6RouterControlFlag::Static] = 1;
+ ret.pack(control);
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6StaticRouter1IP:
+ {
+ stdplus::In6Addr gateway{};
+ if (!channelCall<getEthProp<bool>>(channel, "IPv6AcceptRA"))
+ {
+ gateway =
+ channelCall<getGatewayProperty<AF_INET6>>(channel).value_or(
+ stdplus::In6Addr{});
+ }
+ ret.pack(stdplus::raw::asView<char>(gateway));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6StaticRouter1MAC:
+ {
+ stdplus::EtherAddr mac{};
+ auto neighbor = channelCall<getGatewayNeighbor<AF_INET6>>(channel);
+ if (neighbor)
+ {
+ mac = neighbor->mac;
+ }
+ ret.pack(stdplus::raw::asView<char>(mac));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6StaticRouter1PrefixLength:
+ {
+ ret.pack(uint8_t{0});
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::IPv6StaticRouter1PrefixValue:
+ {
+ ret.pack(stdplus::raw::asView<char>(stdplus::In6Addr{}));
+ return responseSuccess(std::move(ret));
+ }
+ case LanParam::cipherSuitePrivilegeLevels:
+ {
+ std::array<uint4_t, ipmi::maxCSRecords> csPrivilegeLevels;
+
+ uint8_t resp =
+ getCipherConfigObject(csPrivFileName, csPrivDefaultFileName)
+ .getCSPrivilegeLevels(channel, csPrivilegeLevels);
+ if (!resp)
+ {
+ constexpr uint8_t reserved1 = 0x00;
+ ret.pack(reserved1, csPrivilegeLevels);
+ return responseSuccess(std::move(ret));
+ }
+ else
+ {
+ return response(resp);
+ }
+ }
+ }
+
+ if (parameter >= oemCmdStart)
+ {
+ return getLanOem(channel, parameter, set, block);
+ }
+
+ return response(ccParamNotSupported);
+}
+
+constexpr const char* solInterface = "xyz.openbmc_project.Ipmi.SOL";
+constexpr const char* solPath = "/xyz/openbmc_project/ipmi/sol/";
+constexpr const uint16_t solDefaultPort = 623;
+
+RspType<> setSolConfParams(Context::ptr ctx, uint4_t channelBits,
+ uint4_t /*reserved*/, uint8_t parameter,
+ message::Payload& req)
+{
+ const uint8_t channel = convertCurrentChannelNum(
+ static_cast<uint8_t>(channelBits), ctx->channel);
+
+ if (!isValidChannel(channel))
+ {
+ lg2::error("Set Sol Config - Invalid channel in request");
+ return responseInvalidFieldRequest();
+ }
+
+ std::string solService{};
+ std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel);
+
+ if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService))
+ {
+ lg2::error("Set Sol Config - Invalid solInterface, service: {SERVICE}, "
+ "object path: {OBJPATH}, interface: {INTERFACE}",
+ "SERVICE", solService, "OBJPATH", solPathWitheEthName,
+ "INTERFACE", solInterface);
+ return responseInvalidFieldRequest();
+ }
+
+ switch (static_cast<SolConfParam>(parameter))
+ {
+ case SolConfParam::Progress:
+ {
+ uint8_t progress;
+ if (req.unpack(progress) != 0 || !req.fullyUnpacked())
+ {
+ return responseReqDataLenInvalid();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Progress", progress))
+ {
+ return responseUnspecifiedError();
+ }
+ break;
+ }
+ case SolConfParam::Enable:
+ {
+ bool enable;
+ uint7_t reserved2;
+
+ if (req.unpack(enable, reserved2) != 0 || !req.fullyUnpacked())
+ {
+ return responseReqDataLenInvalid();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Enable", enable))
+ {
+ return responseUnspecifiedError();
+ }
+ break;
+ }
+ case SolConfParam::Authentication:
+ {
+ uint4_t privilegeBits{};
+ uint2_t reserved2{};
+ bool forceAuth = false;
+ bool forceEncrypt = false;
+
+ if (req.unpack(privilegeBits, reserved2, forceAuth, forceEncrypt) !=
+ 0 ||
+ !req.fullyUnpacked())
+ {
+ return responseReqDataLenInvalid();
+ }
+
+ uint8_t privilege = static_cast<uint8_t>(privilegeBits);
+ if (privilege < static_cast<uint8_t>(Privilege::User) ||
+ privilege > static_cast<uint8_t>(Privilege::Oem))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Privilege", privilege))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "ForceEncryption",
+ forceEncrypt))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "ForceAuthentication",
+ forceAuth))
+ {
+ return responseUnspecifiedError();
+ }
+ break;
+ }
+ case SolConfParam::Accumulate:
+ {
+ uint8_t interval;
+ uint8_t threshold;
+ if (req.unpack(interval, threshold) != 0 || !req.fullyUnpacked())
+ {
+ return responseReqDataLenInvalid();
+ }
+
+ if (threshold == 0)
+ {
+ return responseInvalidFieldRequest();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "AccumulateIntervalMS",
+ interval))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Threshold", threshold))
+ {
+ return responseUnspecifiedError();
+ }
+ break;
+ }
+ case SolConfParam::Retry:
+ {
+ uint3_t countBits;
+ uint5_t reserved2;
+ uint8_t interval;
+
+ if (req.unpack(countBits, reserved2, interval) != 0 ||
+ !req.fullyUnpacked())
+ {
+ return responseReqDataLenInvalid();
+ }
+
+ uint8_t count = static_cast<uint8_t>(countBits);
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "RetryCount", count))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::setDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "RetryIntervalMS",
+ interval))
+ {
+ return responseUnspecifiedError();
+ }
+ break;
+ }
+ case SolConfParam::Port:
+ {
+ return response(ipmiCCWriteReadParameter);
+ }
+ case SolConfParam::NonVbitrate:
+ case SolConfParam::Vbitrate:
+ case SolConfParam::Channel:
+ default:
+ return response(ipmiCCParamNotSupported);
+ }
+ return responseSuccess();
+}
+
+RspType<message::Payload> getSolConfParams(
+ Context::ptr ctx, uint4_t channelBits, uint3_t /*reserved*/, bool revOnly,
+ uint8_t parameter, uint8_t /*set*/, uint8_t /*block*/)
+{
+ message::Payload ret;
+ constexpr uint8_t current_revision = 0x11;
+ ret.pack(current_revision);
+ if (revOnly)
+ {
+ return responseSuccess(std::move(ret));
+ }
+
+ const uint8_t channel = convertCurrentChannelNum(
+ static_cast<uint8_t>(channelBits), ctx->channel);
+
+ if (!isValidChannel(channel))
+ {
+ lg2::error("Get Sol Config - Invalid channel in request");
+ return responseInvalidFieldRequest();
+ }
+
+ std::string solService{};
+ std::string solPathWitheEthName = solPath + ipmi::getChannelName(channel);
+
+ if (ipmi::getService(ctx, solInterface, solPathWitheEthName, solService))
+ {
+ lg2::error("Set Sol Config - Invalid solInterface, service: {SERVICE}, "
+ "object path: {OBJPATH}, interface: {INTERFACE}",
+ "SERVICE", solService, "OBJPATH", solPathWitheEthName,
+ "INTERFACE", solInterface);
+ return responseInvalidFieldRequest();
+ }
+
+ switch (static_cast<SolConfParam>(parameter))
+ {
+ case SolConfParam::Progress:
+ {
+ uint8_t progress;
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Progress", progress))
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(progress);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Enable:
+ {
+ bool enable{};
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Enable", enable))
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(enable, uint7_t{});
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Authentication:
+ {
+ // 4bits, cast when pack
+ uint8_t privilege;
+ bool forceAuth = false;
+ bool forceEncrypt = false;
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Privilege", privilege))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "ForceAuthentication",
+ forceAuth))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "ForceEncryption",
+ forceEncrypt))
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(uint4_t{privilege}, uint2_t{}, forceAuth, forceEncrypt);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Accumulate:
+ {
+ uint8_t interval{}, threshold{};
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "AccumulateIntervalMS",
+ interval))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "Threshold", threshold))
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(interval, threshold);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Retry:
+ {
+ // 3bits, cast when cast
+ uint8_t count{};
+ uint8_t interval{};
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "RetryCount", count))
+ {
+ return responseUnspecifiedError();
+ }
+
+ if (ipmi::getDbusProperty(ctx, solService, solPathWitheEthName,
+ solInterface, "RetryIntervalMS",
+ interval))
+ {
+ return responseUnspecifiedError();
+ }
+ ret.pack(uint3_t{count}, uint5_t{}, interval);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Port:
+ {
+ auto port = solDefaultPort;
+ ret.pack(static_cast<uint16_t>(port));
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Channel:
+ {
+ ret.pack(channel);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::NonVbitrate:
+ {
+ uint64_t baudRate;
+ uint8_t encodedBitRate = 0;
+ if (ipmi::getDbusProperty(
+ ctx, "xyz.openbmc_project.Console.default",
+ "/xyz/openbmc_project/console/default",
+ "xyz.openbmc_project.Console.UART", "Baud", baudRate))
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ switch (baudRate)
+ {
+ case 9600:
+ encodedBitRate = 0x06;
+ break;
+ case 19200:
+ encodedBitRate = 0x07;
+ break;
+ case 38400:
+ encodedBitRate = 0x08;
+ break;
+ case 57600:
+ encodedBitRate = 0x09;
+ break;
+ case 115200:
+ encodedBitRate = 0x0a;
+ break;
+ default:
+ break;
+ }
+ ret.pack(encodedBitRate);
+ return responseSuccess(std::move(ret));
+ }
+ case SolConfParam::Vbitrate:
+ default:
+ return response(ipmiCCParamNotSupported);
+ }
+
+ return response(ccParamNotSupported);
+}
+
+} // namespace transport
+} // namespace ipmi
+
+void registerNetFnTransportFunctions() __attribute__((constructor));
+
+void registerNetFnTransportFunctions()
+{
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+ ipmi::transport::cmdSetLanConfigParameters,
+ ipmi::Privilege::Admin, ipmi::transport::setLan);
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+ ipmi::transport::cmdGetLanConfigParameters,
+ ipmi::Privilege::Operator, ipmi::transport::getLan);
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+ ipmi::transport::cmdSetSolConfigParameters,
+ ipmi::Privilege::Admin,
+ ipmi::transport::setSolConfParams);
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnTransport,
+ ipmi::transport::cmdGetSolConfigParameters,
+ ipmi::Privilege::User,
+ ipmi::transport::getSolConfParams);
+}
diff --git a/transporthandler.hpp b/transporthandler.hpp
new file mode 100644
index 0000000..de1bb66
--- /dev/null
+++ b/transporthandler.hpp
@@ -0,0 +1,452 @@
+#pragma once
+
+#include "app/channel.hpp"
+#include "transportconstants.hpp"
+#include "user_channel/cipher_mgmt.hpp"
+
+#include <ipmid/api-types.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/message.hpp>
+#include <ipmid/message/types.hpp>
+#include <ipmid/types.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <stdplus/net/addr/ether.hpp>
+#include <stdplus/net/addr/ip.hpp>
+#include <stdplus/str/conv.hpp>
+#include <stdplus/zstring_view.hpp>
+#include <user_channel/channel_layer.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
+#include <xyz/openbmc_project/Network/IP/server.hpp>
+#include <xyz/openbmc_project/Network/Neighbor/server.hpp>
+
+#include <cinttypes>
+#include <functional>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+namespace ipmi
+{
+namespace transport
+{
+
+/** @brief The dbus parameters for the interface corresponding to a channel
+ * This helps reduce the number of mapper lookups we need for each
+ * query and simplifies finding the VLAN interface if needed.
+ */
+struct ChannelParams
+{
+ /** @brief The channel ID */
+ int id;
+ /** @brief channel name for the interface */
+ std::string ifname;
+ /** @brief Name of the service on the bus */
+ std::string service;
+ /** @brief Lower level adapter path that is guaranteed to not be a VLAN */
+ std::string ifPath;
+ /** @brief Logical adapter path used for address assignment */
+ std::string logicalPath;
+};
+
+/** @brief Determines the ethernet interface name corresponding to a channel
+ * Tries to map a VLAN object first so that the address information
+ * is accurate. Otherwise it gets the standard ethernet interface.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] channel - The channel id corresponding to an ethernet interface
+ * @return Ethernet interface service and object path if it exists
+ */
+std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus_t& bus,
+ uint8_t channel);
+
+/** @brief A trivial helper around maybeGetChannelParams() that throws an
+ * exception when it is unable to acquire parameters for the channel.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] channel - The channel id corresponding to an ethernet interface
+ * @return Ethernet interface service and object path
+ */
+ChannelParams getChannelParams(sdbusplus::bus_t& bus, uint8_t channel);
+
+/** @brief Trivializes using parameter getter functions by providing a bus
+ * and channel parameters automatically.
+ *
+ * @param[in] channel - The channel id corresponding to an ethernet interface
+ * ...
+ */
+template <auto func, typename... Args>
+auto channelCall(uint8_t channel, Args&&... args)
+{
+ sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+ auto params = getChannelParams(bus, channel);
+ return std::invoke(func, bus, params, std::forward<Args>(args)...);
+}
+
+/** @brief Generic paramters for different address families */
+template <int family>
+struct AddrFamily
+{};
+
+/** @brief Parameter specialization for IPv4 */
+template <>
+struct AddrFamily<AF_INET>
+{
+ using addr = stdplus::In4Addr;
+ static constexpr auto protocol =
+ sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv4;
+ static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
+ static constexpr uint8_t defaultPrefix = 32;
+ static constexpr char propertyGateway[] = "DefaultGateway";
+};
+
+/** @brief Parameter specialization for IPv6 */
+template <>
+struct AddrFamily<AF_INET6>
+{
+ using addr = stdplus::In6Addr;
+ static constexpr auto protocol =
+ sdbusplus::server::xyz::openbmc_project::network::IP::Protocol::IPv6;
+ static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
+ static constexpr uint8_t defaultPrefix = 128;
+ static constexpr char propertyGateway[] = "DefaultGateway6";
+};
+
+/** @brief Interface Neighbor configuration parameters */
+template <int family>
+struct IfNeigh
+{
+ std::string path;
+ typename AddrFamily<family>::addr ip;
+ stdplus::EtherAddr mac;
+};
+
+/** @brief Interface IP Address configuration parameters */
+template <int family>
+struct IfAddr
+{
+ std::string path;
+ typename AddrFamily<family>::addr address;
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin origin;
+ uint8_t prefix;
+};
+
+/** @brief Valid address origins for IPv6 */
+static inline const std::unordered_set<
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
+ originsV6Static = {sdbusplus::server::xyz::openbmc_project::network::IP::
+ AddressOrigin::Static};
+static inline const std::unordered_set<
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>
+ originsV6Dynamic = {
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
+ DHCP,
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin::
+ SLAAC,
+};
+
+/** @brief A lazy lookup mechanism for iterating over object properties stored
+ * in DBus. This will only perform the object lookup when needed, and
+ * retains a cache of previous lookups to speed up future iterations.
+ */
+class ObjectLookupCache
+{
+ public:
+ using PropertiesCache = std::unordered_map<std::string, PropertyMap>;
+
+ /** @brief Creates a new ObjectLookupCache for the interface on the bus
+ * NOTE: The inputs to this object must outlive the object since
+ * they are only referenced by it.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] intf - The interface we are looking up
+ */
+ ObjectLookupCache(sdbusplus::bus_t& bus, const ChannelParams& params,
+ const char* intf) :
+ bus(bus), params(params), intf(intf),
+ objs(getAllDbusObjects(bus, params.logicalPath, intf, ""))
+ {}
+
+ class iterator : public ObjectTree::const_iterator
+ {
+ public:
+ using value_type = PropertiesCache::value_type;
+
+ iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) :
+ ObjectTree::const_iterator(it), container(container),
+ ret(container.cache.end())
+ {}
+ value_type& operator*()
+ {
+ ret = container.get(ObjectTree::const_iterator::operator*().first);
+ return *ret;
+ }
+ value_type* operator->()
+ {
+ return &operator*();
+ }
+
+ private:
+ ObjectLookupCache& container;
+ PropertiesCache::iterator ret;
+ };
+
+ iterator begin() noexcept
+ {
+ return iterator(objs.begin(), *this);
+ }
+
+ iterator end() noexcept
+ {
+ return iterator(objs.end(), *this);
+ }
+
+ private:
+ sdbusplus::bus_t& bus;
+ const ChannelParams& params;
+ const char* const intf;
+ const ObjectTree objs;
+ PropertiesCache cache;
+
+ /** @brief Gets a cached copy of the object properties if possible
+ * Otherwise performs a query on DBus to look them up
+ *
+ * @param[in] path - The object path to lookup
+ * @return An iterator for the specified object path + properties
+ */
+ PropertiesCache::iterator get(const std::string& path)
+ {
+ auto it = cache.find(path);
+ if (it != cache.end())
+ {
+ return it;
+ }
+ auto properties = getAllDbusProperties(bus, params.service, path, intf);
+ return cache.insert({path, std::move(properties)}).first;
+ }
+};
+
+/** @brief Searches the ip object lookup cache for an address matching
+ * the input parameters. NOTE: The index lacks stability across address
+ * changes since the network daemon has no notion of stable indicies.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] idx - The index of the desired address on the interface
+ * @param[in] origins - The allowed origins for the address objects
+ * @param[in] ips - The object lookup cache holding all of the address info
+ * @return The address and prefix if it was found
+ */
+template <int family>
+std::optional<IfAddr<family>> findIfAddr(
+ [[maybe_unused]] sdbusplus::bus_t& bus,
+ [[maybe_unused]] const ChannelParams& params, uint8_t idx,
+ const std::unordered_set<
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
+ origins,
+ ObjectLookupCache& ips)
+{
+ for (const auto& [path, properties] : ips)
+ {
+ try
+ {
+ typename AddrFamily<family>::addr addr;
+ addr = stdplus::fromStr<typename AddrFamily<family>::addr>(
+ std::get<std::string>(properties.at("Address")));
+
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin
+ origin = sdbusplus::server::xyz::openbmc_project::network::IP::
+ convertAddressOriginFromString(
+ std::get<std::string>(properties.at("Origin")));
+ if (origins.find(origin) == origins.end())
+ {
+ continue;
+ }
+
+ if (idx > 0)
+ {
+ idx--;
+ continue;
+ }
+
+ IfAddr<family> ifaddr;
+ ifaddr.path = path;
+ ifaddr.address = addr;
+ ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength"));
+ ifaddr.origin = origin;
+
+ return ifaddr;
+ }
+ catch (...)
+ {
+ continue;
+ }
+ }
+
+ return std::nullopt;
+}
+/** @brief Trivial helper around findIfAddr that simplifies calls
+ * for one off lookups. Don't use this if you intend to do multiple
+ * lookups at a time.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] idx - The index of the desired address on the interface
+ * @param[in] origins - The allowed origins for the address objects
+ * @return The address and prefix if it was found
+ */
+template <int family>
+auto getIfAddr(
+ sdbusplus::bus_t& bus, const ChannelParams& params, uint8_t idx,
+ const std::unordered_set<
+ sdbusplus::server::xyz::openbmc_project::network::IP::AddressOrigin>&
+ origins)
+{
+ ObjectLookupCache ips(bus, params, INTF_IP);
+ return findIfAddr<family>(bus, params, idx, origins, ips);
+}
+
+/** @brief Reconfigures the IPv6 address info configured for the interface
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] idx - The address index to operate on
+ * @param[in] address - The new address
+ * @param[in] prefix - The new address prefix
+ */
+void reconfigureIfAddr6(sdbusplus::bus_t& bus, const ChannelParams& params,
+ uint8_t idx, stdplus::In6Addr address, uint8_t prefix);
+
+/** @brief Retrieves the current gateway for the address family on the system
+ * NOTE: The gateway is per channel instead of the system wide one.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @return An address representing the gateway address if it exists
+ */
+template <int family>
+std::optional<typename AddrFamily<family>::addr> getGatewayProperty(
+ sdbusplus::bus_t& bus, const ChannelParams& params)
+{
+ auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
+ auto gatewayStr = std::get<std::string>(
+ getDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
+ AddrFamily<family>::propertyGateway));
+ if (gatewayStr.empty())
+ {
+ return std::nullopt;
+ }
+ return stdplus::fromStr<typename AddrFamily<family>::addr>(gatewayStr);
+}
+
+template <int family>
+std::optional<IfNeigh<family>> findStaticNeighbor(
+ sdbusplus::bus_t&, const ChannelParams&,
+ typename AddrFamily<family>::addr ip, ObjectLookupCache& neighbors)
+{
+ using sdbusplus::server::xyz::openbmc_project::network::Neighbor;
+ const auto state =
+ sdbusplus::common::xyz::openbmc_project::network::convertForMessage(
+ Neighbor::State::Permanent);
+ for (const auto& [path, neighbor] : neighbors)
+ {
+ try
+ {
+ typename AddrFamily<family>::addr neighIP;
+ neighIP = stdplus::fromStr<typename AddrFamily<family>::addr>(
+ std::get<std::string>(neighbor.at("IPAddress")));
+
+ if (neighIP != ip)
+ {
+ continue;
+ }
+ if (state != std::get<std::string>(neighbor.at("State")))
+ {
+ continue;
+ }
+
+ IfNeigh<family> ret;
+ ret.path = path;
+ ret.ip = ip;
+ ret.mac = stdplus::fromStr<stdplus::EtherAddr>(
+ std::get<std::string>(neighbor.at("MACAddress")));
+
+ return ret;
+ }
+ catch (...)
+ {
+ continue;
+ }
+ }
+
+ return std::nullopt;
+}
+
+template <int family>
+void createNeighbor(sdbusplus::bus_t& bus, const ChannelParams& params,
+ typename AddrFamily<family>::addr address,
+ stdplus::EtherAddr mac)
+{
+ auto newreq =
+ bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
+ INTF_NEIGHBOR_CREATE_STATIC, "Neighbor");
+ stdplus::ToStrHandle<stdplus::ToStr<stdplus::EtherAddr>> macToStr;
+ stdplus::ToStrHandle<stdplus::ToStr<typename AddrFamily<family>::addr>>
+ addrToStr;
+ newreq.append(addrToStr(address), macToStr(mac));
+ bus.call_noreply(newreq);
+}
+
+/** @brief Deletes the dbus object. Ignores empty objects or objects that are
+ * missing from the bus.
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] service - The name of the service
+ * @param[in] path - The path of the object to delete
+ */
+void deleteObjectIfExists(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& path);
+
+/** @brief Sets the value for the default gateway of the channel
+ *
+ * @param[in] bus - The bus object used for lookups
+ * @param[in] params - The parameters for the channel
+ * @param[in] gateway - Gateway address to apply
+ */
+template <int family>
+void setGatewayProperty(sdbusplus::bus_t& bus, const ChannelParams& params,
+ typename AddrFamily<family>::addr address)
+{
+ // Save the old gateway MAC address if it exists so we can recreate it
+ auto gateway = getGatewayProperty<family>(bus, params);
+ std::optional<IfNeigh<family>> neighbor;
+ if (gateway)
+ {
+ ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+ neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+ }
+
+ auto objPath = "/xyz/openbmc_project/network/" + params.ifname;
+ setDbusProperty(bus, params.service, objPath, INTF_ETHERNET,
+ AddrFamily<family>::propertyGateway,
+ stdplus::toStr(address));
+
+ // Restore the gateway MAC if we had one
+ if (neighbor)
+ {
+ deleteObjectIfExists(bus, params.service, neighbor->path);
+ createNeighbor<family>(bus, params, address, neighbor->mac);
+ }
+}
+
+} // namespace transport
+} // namespace ipmi
diff --git a/user_channel/channel_layer.cpp b/user_channel/channel_layer.cpp
new file mode 100644
index 0000000..26bc3fa
--- /dev/null
+++ b/user_channel/channel_layer.cpp
@@ -0,0 +1,152 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "channel_layer.hpp"
+
+#include "channel_mgmt.hpp"
+#include "cipher_mgmt.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace ipmi
+{
+
+bool doesDeviceExist(const uint8_t chNum)
+{
+ // TODO: This is not the reliable way to find the device
+ // associated with ethernet interface as the channel number to
+ // eth association is not done. Need to revisit later
+ struct stat fileStat = {};
+ std::string devName("/sys/class/net/" + getChannelName(chNum));
+
+ if (stat(devName.data(), &fileStat) != 0)
+ {
+ lg2::debug("Ethernet device not found");
+ return false;
+ }
+
+ return true;
+}
+
+bool isValidPrivLimit(const uint8_t privLimit)
+{
+ // Callback privilege is deprecated in OpenBMC
+ // At present, "OEM Privilege" is not used in OpenBMC
+ return ((privLimit > PRIVILEGE_CALLBACK) && (privLimit < PRIVILEGE_OEM));
+}
+
+bool isValidAccessMode(const uint8_t accessMode)
+{
+ return (accessMode <= static_cast<uint8_t>(EChannelAccessMode::shared));
+}
+
+bool isValidChannel(const uint8_t chNum)
+{
+ return getChannelConfigObject().isValidChannel(chNum);
+}
+
+bool isValidAuthType(const uint8_t chNum, const EAuthType& authType)
+{
+ return getChannelConfigObject().isValidAuthType(chNum, authType);
+}
+
+EChannelSessSupported getChannelSessionSupport(const uint8_t chNum)
+{
+ return getChannelConfigObject().getChannelSessionSupport(chNum);
+}
+
+int getChannelActiveSessions(const uint8_t chNum)
+{
+ return getChannelConfigObject().getChannelActiveSessions(chNum);
+}
+
+size_t getChannelMaxTransferSize(uint8_t chNum)
+{
+ return getChannelConfigObject().getChannelMaxTransferSize(chNum);
+}
+
+Cc ipmiChannelInit()
+{
+ getChannelConfigObject();
+ getCipherConfigObject(csPrivFileName, csPrivDefaultFileName);
+ return ccSuccess;
+}
+
+Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo)
+{
+ return getChannelConfigObject().getChannelInfo(chNum, chInfo);
+}
+
+Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData)
+{
+ return getChannelConfigObject().getChannelAccessData(chNum, chAccessData);
+}
+
+Cc setChannelAccessData(const uint8_t chNum, const ChannelAccess& chAccessData,
+ const uint8_t setFlag)
+{
+ return getChannelConfigObject().setChannelAccessData(chNum, chAccessData,
+ setFlag);
+}
+
+Cc getChannelAccessPersistData(const uint8_t chNum, ChannelAccess& chAccessData)
+{
+ return getChannelConfigObject().getChannelAccessPersistData(
+ chNum, chAccessData);
+}
+
+Cc setChannelAccessPersistData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag)
+{
+ return getChannelConfigObject().setChannelAccessPersistData(
+ chNum, chAccessData, setFlag);
+}
+
+Cc getChannelAuthTypeSupported(const uint8_t chNum, uint8_t& authTypeSupported)
+{
+ return getChannelConfigObject().getChannelAuthTypeSupported(
+ chNum, authTypeSupported);
+}
+
+Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+ EAuthType& authType)
+{
+ return getChannelConfigObject().getChannelEnabledAuthType(
+ chNum, priv, authType);
+}
+
+std::string getChannelName(const uint8_t chNum)
+{
+ return getChannelConfigObject().getChannelName(chNum);
+}
+
+uint8_t getChannelByName(const std::string& chName)
+{
+ return getChannelConfigObject().getChannelByName(chName);
+}
+
+bool isValidPayloadType(const PayloadType payloadType)
+{
+ return (
+ payloadType == PayloadType::IPMI || payloadType == PayloadType::SOL ||
+ payloadType == PayloadType::OPEN_SESSION_REQUEST ||
+ payloadType == PayloadType::OPEN_SESSION_RESPONSE ||
+ payloadType == PayloadType::RAKP1 ||
+ payloadType == PayloadType::RAKP2 ||
+ payloadType == PayloadType::RAKP3 || payloadType == PayloadType::RAKP4);
+}
+} // namespace ipmi
diff --git a/user_channel/channel_layer.hpp b/user_channel/channel_layer.hpp
new file mode 100644
index 0000000..37d2b90
--- /dev/null
+++ b/user_channel/channel_layer.hpp
@@ -0,0 +1,374 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <openssl/crypto.h>
+
+#include <ipmid/api.hpp>
+
+#include <array>
+#include <string>
+
+namespace ipmi
+{
+
+static constexpr uint8_t maxIpmiChannels = 16;
+static constexpr uint8_t currentChNum = 0xE;
+static constexpr uint8_t invalidChannel = 0xff;
+static constexpr const uint8_t ccActionNotSupportedForChannel = 0x82;
+static constexpr const uint8_t ccAccessModeNotSupportedForChannel = 0x83;
+
+/**
+ * @array of privilege levels
+ */
+extern const std::array<std::string, PRIVILEGE_OEM + 1> privList;
+
+/**
+ * @enum Channel Protocol Type (refer spec sec 6.4)
+ */
+enum class EChannelProtocolType : uint8_t
+{
+ na = 0x00,
+ ipmbV10 = 0x01,
+ icmbV11 = 0x02,
+ reserved = 0x03,
+ ipmiSmbus = 0x04,
+ kcs = 0x05,
+ smic = 0x06,
+ bt10 = 0x07,
+ bt15 = 0x08,
+ tMode = 0x09,
+ oem = 0x1C,
+};
+
+/**
+ * @enum Channel Medium Type (refer spec sec 6.5)
+ */
+enum class EChannelMediumType : uint8_t
+{
+ reserved = 0x00,
+ ipmb = 0x01,
+ icmbV10 = 0x02,
+ icmbV09 = 0x03,
+ lan8032 = 0x04,
+ serial = 0x05,
+ otherLan = 0x06,
+ pciSmbus = 0x07,
+ smbusV11 = 0x08,
+ smbusV20 = 0x09,
+ usbV1x = 0x0A,
+ usbV2x = 0x0B,
+ systemInterface = 0x0C,
+ oem = 0x60,
+ unknown = 0x82,
+};
+
+/**
+ * @enum Channel Session Type (refer spec sec 22.24 -
+ * response data byte 5)
+ */
+enum class EChannelSessSupported : uint8_t
+{
+ none = 0,
+ single = 1,
+ multi = 2,
+ any = 3,
+};
+
+/**
+ * @enum Channel Access Mode (refer spec sec 6.6)
+ */
+enum class EChannelAccessMode : uint8_t
+{
+ disabled = 0,
+ preboot = 1,
+ alwaysAvail = 2,
+ shared = 3,
+};
+
+/**
+ * @enum Authentication Types (refer spec sec 13.6 - IPMI
+ * Session Header)
+ */
+enum class EAuthType : uint8_t
+{
+ none = (1 << 0x0),
+ md2 = (1 << 0x1),
+ md5 = (1 << 0x2),
+ reserved = (1 << 0x3),
+ straightPasswd = (1 << 0x4),
+ oem = (1 << 0x5),
+};
+
+// TODO: Remove duplicate 'PayloadType' definition from netipmid's message.hpp
+// to phosphor-ipmi-host/include
+/**
+ * @enum Payload Types (refer spec sec 13.27.3)
+ */
+enum class PayloadType : uint8_t
+{
+ IPMI = 0x00,
+ SOL = 0x01,
+ OPEN_SESSION_REQUEST = 0x10,
+ OPEN_SESSION_RESPONSE = 0x11,
+ RAKP1 = 0x12,
+ RAKP2 = 0x13,
+ RAKP3 = 0x14,
+ RAKP4 = 0x15,
+ INVALID = 0xFF,
+};
+
+/**
+ * @enum Access mode for channel access set/get (refer spec
+ * sec 22.22 - request byte 2[7:6])
+ */
+typedef enum
+{
+ doNotSet = 0x00,
+ nvData = 0x01,
+ activeData = 0x02,
+ reserved = 0x03,
+} EChannelActionType;
+
+/**
+ * @enum Access set flag to determine changes that has to be updated
+ * in channel access data configuration.
+ */
+enum AccessSetFlag
+{
+ setAccessMode = (1 << 0),
+ setUserAuthEnabled = (1 << 1),
+ setMsgAuthEnabled = (1 << 2),
+ setAlertingEnabled = (1 << 3),
+ setPrivLimit = (1 << 4),
+};
+
+/** @struct ChannelAccess
+ *
+ * Structure to store channel access related information, defined in IPMI
+ * specification and used in Get / Set channel access (refer spec sec 22.22
+ * & 22.23)
+ */
+struct ChannelAccess
+{
+ uint8_t accessMode;
+ bool userAuthDisabled;
+ bool perMsgAuthDisabled;
+ bool alertingDisabled;
+ uint8_t privLimit;
+};
+
+/** @struct ChannelInfo
+ *
+ * Structure to store data about channel information, which identifies each
+ * channel type and information as defined in IPMI specification. (refer spec
+ * sec 22.22 & 22.23)
+ */
+struct ChannelInfo
+{
+ uint8_t mediumType;
+ uint8_t protocolType;
+ uint8_t sessionSupported;
+ bool isIpmi; // Is session IPMI
+ // This is used in Get LAN Configuration parameter.
+ // This holds the supported AuthTypes for a given channel.
+ uint8_t authTypeSupported;
+};
+
+/** @brief determines valid channel
+ *
+ * @param[in] chNum- channel number
+ *
+ * @return true if valid, false otherwise
+ */
+bool isValidChannel(const uint8_t chNum);
+
+/** @brief determines whether channel device exist
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return true if valid, false otherwise
+ */
+bool doesDeviceExist(const uint8_t chNum);
+
+/** @brief determines whether privilege limit is valid
+ *
+ * @param[in] privLimit - Privilege limit
+ *
+ * @return true if valid, false otherwise
+ */
+bool isValidPrivLimit(const uint8_t privLimit);
+
+/** @brief determines whether access mode is valid
+ *
+ * @param[in] accessMode - Access mode
+ *
+ * @return true if valid, false otherwise
+ */
+bool isValidAccessMode(const uint8_t accessMode);
+
+/** @brief determines valid authentication type based on channel number
+ *
+ * @param[in] chNum - channel number
+ * @param[in] authType - authentication type
+ *
+ * @return true if valid, false otherwise
+ */
+bool isValidAuthType(const uint8_t chNum, const EAuthType& authType);
+
+/** @brief determines supported session type of a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return EChannelSessSupported - supported session type
+ */
+EChannelSessSupported getChannelSessionSupport(const uint8_t chNum);
+
+/** @brief determines number of active sessions on a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return numer of active sessions
+ */
+int getChannelActiveSessions(const uint8_t chNum);
+
+/** @brief determines maximum transfer size for a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return maximum bytes that can be transferred on this channel
+ */
+size_t getChannelMaxTransferSize(uint8_t chNum);
+
+/** @brief initializes channel management
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiChannelInit();
+
+/** @brief provides channel info details
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chInfo - channel info details
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo);
+
+/** @brief provides channel access data
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chAccessData -channel access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData);
+
+/** @brief provides function to convert current channel number (0xE)
+ *
+ * @param[in] chNum - channel number as requested in commands.
+ * @param[in] devChannel - channel number as provided by device (not 0xE)
+ *
+ * @return same channel number or proper channel number for current channel
+ * number (0xE).
+ */
+static inline uint8_t convertCurrentChannelNum(const uint8_t chNum,
+ const uint8_t devChannel)
+{
+ if (chNum == currentChNum)
+ {
+ return devChannel;
+ }
+ return chNum;
+}
+
+/** @brief to set channel access data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] chAccessData - channel access data
+ * @param[in] setFlag - flag to indicate updatable fields
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc setChannelAccessData(const uint8_t chNum, const ChannelAccess& chAccessData,
+ const uint8_t setFlag);
+
+/** @brief to get channel access data persistent data
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chAccessData - channel access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAccessPersistData(const uint8_t chNum,
+ ChannelAccess& chAccessData);
+
+/** @brief to set channel access data persistent data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] chAccessData - channel access data
+ * @param[in] setFlag - flag to indicate updatable fields
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc setChannelAccessPersistData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag);
+
+/** @brief provides supported authentication type for the channel
+ *
+ * @param[in] chNum - channel number
+ * @param[out] authTypeSupported - supported authentication type
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc getChannelAuthTypeSupported(const uint8_t chNum, uint8_t& authTypeSupported);
+
+/** @brief provides enabled authentication type for the channel
+ *
+ * @param[in] chNum - channel number
+ * @param[in] priv - privilege
+ * @param[out] authType - enabled authentication type
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+ EAuthType& authType);
+
+/** @brief Retrieves the LAN channel name from the IPMI channel number
+ *
+ * @param[in] chNum - IPMI channel number
+ *
+ * @return the LAN channel name (i.e. eth0)
+ */
+std::string getChannelName(const uint8_t chNum);
+
+/** @brief Retrieves the LAN channel number from the IPMI channel name
+ *
+ * @param[in] chName - IPMI channel name (i.e. eth0)
+ *
+ * @return the LAN channel number
+ */
+uint8_t getChannelByName(const std::string& chName);
+
+/** @brief determines whether payload type is valid
+ *
+ * @param[in] payload type - Payload Type
+ *
+ * @return true if valid, false otherwise
+ */
+bool isValidPayloadType(const PayloadType payloadType);
+
+} // namespace ipmi
diff --git a/user_channel/channel_mgmt.cpp b/user_channel/channel_mgmt.cpp
new file mode 100644
index 0000000..8594d65
--- /dev/null
+++ b/user_channel/channel_mgmt.cpp
@@ -0,0 +1,1445 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "channel_mgmt.hpp"
+
+#include "user_layer.hpp"
+
+#include <ifaddrs.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/object.hpp>
+
+#include <cerrno>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <unordered_map>
+
+namespace ipmi
+{
+
+static constexpr const char* channelAccessDefaultFilename =
+ "/usr/share/ipmi-providers/channel_access.json";
+static constexpr const char* channelConfigDefaultFilename =
+ "/usr/share/ipmi-providers/channel_config.json";
+static constexpr const char* channelNvDataFilename =
+ "/var/lib/ipmi/channel_access_nv.json";
+static constexpr const char* channelVolatileDataFilename =
+ "/run/ipmi/channel_access_volatile.json";
+
+// TODO: Get the service name dynamically..
+static constexpr const char* networkIntfServiceName =
+ "xyz.openbmc_project.Network";
+static constexpr const char* networkIntfObjectBasePath =
+ "/xyz/openbmc_project/network";
+static constexpr const char* networkChConfigIntfName =
+ "xyz.openbmc_project.Channel.ChannelAccess";
+static constexpr const char* privilegePropertyString = "MaxPrivilege";
+static constexpr const char* dBusPropertiesInterface =
+ "org.freedesktop.DBus.Properties";
+static constexpr const char* propertiesChangedSignal = "PropertiesChanged";
+static constexpr const char* interfaceAddedSignal = "InterfacesAdded";
+static constexpr const char* interfaceRemovedSignal = "InterfacesRemoved";
+
+// STRING DEFINES: Should sync with key's in JSON
+static constexpr const char* nameString = "name";
+static constexpr const char* isValidString = "is_valid";
+static constexpr const char* activeSessionsString = "active_sessions";
+static constexpr const char* maxTransferSizeString = "max_transfer_size";
+static constexpr const char* channelInfoString = "channel_info";
+static constexpr const char* mediumTypeString = "medium_type";
+static constexpr const char* protocolTypeString = "protocol_type";
+static constexpr const char* sessionSupportedString = "session_supported";
+static constexpr const char* isIpmiString = "is_ipmi";
+static constexpr const char* isManagementNIC = "is_management_nic";
+static constexpr const char* accessModeString = "access_mode";
+static constexpr const char* userAuthDisabledString = "user_auth_disabled";
+static constexpr const char* perMsgAuthDisabledString = "per_msg_auth_disabled";
+static constexpr const char* alertingDisabledString = "alerting_disabled";
+static constexpr const char* privLimitString = "priv_limit";
+
+// Default values
+static constexpr const char* defaultChannelName = "RESERVED";
+static constexpr const uint8_t defaultMediumType =
+ static_cast<uint8_t>(EChannelMediumType::reserved);
+static constexpr const uint8_t defaultProtocolType =
+ static_cast<uint8_t>(EChannelProtocolType::reserved);
+static constexpr const uint8_t defaultSessionSupported =
+ static_cast<uint8_t>(EChannelSessSupported::none);
+static constexpr const uint8_t defaultAuthType =
+ static_cast<uint8_t>(EAuthType::none);
+static constexpr const bool defaultIsIpmiState = false;
+static constexpr size_t smallChannelSize = 64;
+
+std::unique_ptr<sdbusplus::bus::match_t> chPropertiesSignal
+ __attribute__((init_priority(101)));
+
+std::unique_ptr<sdbusplus::bus::match_t> chInterfaceAddedSignal
+ __attribute__((init_priority(101)));
+
+std::unique_ptr<sdbusplus::bus::match_t> chInterfaceRemovedSignal
+ __attribute__((init_priority(101)));
+
+// String mappings use in JSON config file
+static std::unordered_map<std::string, EChannelMediumType> mediumTypeMap = {
+ {"reserved", EChannelMediumType::reserved},
+ {"ipmb", EChannelMediumType::ipmb},
+ {"icmb-v1.0", EChannelMediumType::icmbV10},
+ {"icmb-v0.9", EChannelMediumType::icmbV09},
+ {"lan-802.3", EChannelMediumType::lan8032},
+ {"serial", EChannelMediumType::serial},
+ {"other-lan", EChannelMediumType::otherLan},
+ {"pci-smbus", EChannelMediumType::pciSmbus},
+ {"smbus-v1.0", EChannelMediumType::smbusV11},
+ {"smbus-v2.0", EChannelMediumType::smbusV20},
+ {"usb-1x", EChannelMediumType::usbV1x},
+ {"usb-2x", EChannelMediumType::usbV2x},
+ {"system-interface", EChannelMediumType::systemInterface},
+ {"oem", EChannelMediumType::oem},
+ {"unknown", EChannelMediumType::unknown}};
+
+static std::unordered_map<EInterfaceIndex, std::string> interfaceMap = {
+ {interfaceKCS, "SMS"},
+ {interfaceLAN1, "eth0"},
+ {interfaceUnknown, "unknown"}};
+
+static std::unordered_map<std::string, EChannelProtocolType> protocolTypeMap = {
+ {"na", EChannelProtocolType::na},
+ {"ipmb-1.0", EChannelProtocolType::ipmbV10},
+ {"icmb-2.0", EChannelProtocolType::icmbV11},
+ {"reserved", EChannelProtocolType::reserved},
+ {"ipmi-smbus", EChannelProtocolType::ipmiSmbus},
+ {"kcs", EChannelProtocolType::kcs},
+ {"smic", EChannelProtocolType::smic},
+ {"bt-10", EChannelProtocolType::bt10},
+ {"bt-15", EChannelProtocolType::bt15},
+ {"tmode", EChannelProtocolType::tMode},
+ {"oem", EChannelProtocolType::oem}};
+
+static std::array<std::string, 4> accessModeList = {
+ "disabled", "pre-boot", "always_available", "shared"};
+
+static std::array<std::string, 4> sessionSupportList = {
+ "session-less", "single-session", "multi-session", "session-based"};
+
+const std::array<std::string, PRIVILEGE_OEM + 1> privList = {
+ "priv-reserved", "priv-callback", "priv-user",
+ "priv-operator", "priv-admin", "priv-oem"};
+
+std::string ChannelConfig::getChannelName(const uint8_t chNum)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::error("Invalid channel number: {CHANNEL_ID}", "CHANNEL_ID", chNum);
+ throw std::invalid_argument("Invalid channel number");
+ }
+
+ return channelData[chNum].chName;
+}
+
+int ChannelConfig::convertToChannelNumberFromChannelName(
+ const std::string& chName)
+{
+ for (const auto& it : channelData)
+ {
+ if (it.chName == chName)
+ {
+ return it.chID;
+ }
+ }
+ lg2::error("Invalid channel name: {CHANNEL}", "CHANNEL", chName);
+ throw std::invalid_argument("Invalid channel name");
+
+ return -1;
+}
+
+std::string ChannelConfig::getChannelNameFromPath(const std::string& path)
+{
+ const size_t length = strlen(networkIntfObjectBasePath);
+ if (((length + 1) >= path.size()) ||
+ path.compare(0, length, networkIntfObjectBasePath))
+ {
+ lg2::error("Invalid object path: {PATH}", "PATH", path);
+ throw std::invalid_argument("Invalid object path");
+ }
+ std::string chName(path, length + 1);
+ return chName;
+}
+
+void ChannelConfig::processChAccessPropChange(
+ const std::string& path, const DbusChObjProperties& chProperties)
+{
+ // Get interface name from path. ex: '/xyz/openbmc_project/network/eth0'
+ std::string chName;
+ try
+ {
+ chName = getChannelNameFromPath(path);
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Exception: {MSG}", "MSG", e.what());
+ return;
+ }
+
+ // Get the MaxPrivilege property value from the signal
+ std::string intfPrivStr;
+ std::string propName;
+ for (const auto& prop : chProperties)
+ {
+ if (prop.first == privilegePropertyString)
+ {
+ propName = privilegePropertyString;
+ intfPrivStr = std::get<std::string>(prop.second);
+ break;
+ }
+ }
+
+ if (propName != privilegePropertyString)
+ {
+ lg2::error("Unknown signal caught.");
+ return;
+ }
+
+ if (intfPrivStr.empty())
+ {
+ lg2::error("Invalid privilege string for intf {INTF}", "INTF", chName);
+ return;
+ }
+
+ uint8_t intfPriv = 0;
+ int chNum;
+ try
+ {
+ intfPriv = static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
+ chNum = convertToChannelNumberFromChannelName(chName);
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Exception: {MSG}", "MSG", e.what());
+ return;
+ }
+
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+ // skip updating the values, if this property change originated from IPMI.
+ if (signalFlag & (1 << chNum))
+ {
+ signalFlag &= ~(1 << chNum);
+ lg2::debug("Request originated from IPMI so ignoring signal");
+ return;
+ }
+
+ // Update both volatile & Non-volatile, if there is mismatch.
+ // as property change other than IPMI, has to update both volatile &
+ // non-volatile data.
+ checkAndReloadVolatileData();
+ checkAndReloadNVData();
+ if (channelData[chNum].chAccess.chNonVolatileData.privLimit != intfPriv)
+ {
+ // Update NV data
+ channelData[chNum].chAccess.chNonVolatileData.privLimit = intfPriv;
+ if (writeChannelPersistData() != 0)
+ {
+ lg2::error("Failed to update the persist data file");
+ return;
+ }
+
+ // Update Volatile data
+ if (channelData[chNum].chAccess.chVolatileData.privLimit != intfPriv)
+ {
+ channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
+ if (writeChannelVolatileData() != 0)
+ {
+ lg2::error("Failed to update the volatile data file");
+ return;
+ }
+ }
+ }
+
+ return;
+}
+
+ChannelConfig& getChannelConfigObject()
+{
+ static ChannelConfig channelConfig;
+ return channelConfig;
+}
+
+ChannelConfig::~ChannelConfig()
+{
+ if (signalHndlrObjectState)
+ {
+ chPropertiesSignal.reset();
+ chInterfaceAddedSignal.reset();
+ chInterfaceRemovedSignal.reset();
+ sigHndlrLock.unlock();
+ }
+}
+
+ChannelConfig::ChannelConfig() : bus(ipmid_get_sd_bus_connection())
+{
+ std::ofstream mutexCleanUpFile;
+ mutexCleanUpFile.open(ipmiChMutexCleanupLockFile,
+ std::ofstream::out | std::ofstream::app);
+ if (!mutexCleanUpFile.good())
+ {
+ lg2::debug("Unable to open mutex cleanup file");
+ return;
+ }
+ mutexCleanUpFile.close();
+ mutexCleanupLock =
+ boost::interprocess::file_lock(ipmiChMutexCleanupLockFile);
+ if (mutexCleanupLock.try_lock())
+ {
+ boost::interprocess::named_recursive_mutex::remove(ipmiChannelMutex);
+ channelMutex =
+ std::make_unique<boost::interprocess::named_recursive_mutex>(
+ boost::interprocess::open_or_create, ipmiChannelMutex);
+ mutexCleanupLock.lock_sharable();
+ }
+ else
+ {
+ mutexCleanupLock.lock_sharable();
+ channelMutex =
+ std::make_unique<boost::interprocess::named_recursive_mutex>(
+ boost::interprocess::open_or_create, ipmiChannelMutex);
+ }
+
+ initChannelPersistData();
+
+ sigHndlrLock = boost::interprocess::file_lock(channelNvDataFilename);
+ // Register it for single object and single process either netipmid /
+ // host-ipmid
+ if (chPropertiesSignal == nullptr && sigHndlrLock.try_lock())
+ {
+ lg2::debug("Registering channel signal handler.");
+ chPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::path_namespace(
+ networkIntfObjectBasePath) +
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::member(propertiesChangedSignal) +
+ sdbusplus::bus::match::rules::interface(
+ dBusPropertiesInterface) +
+ sdbusplus::bus::match::rules::argN(0, networkChConfigIntfName),
+ [&](sdbusplus::message_t& msg) {
+ DbusChObjProperties props;
+ std::string iface;
+ std::string path = msg.get_path();
+ msg.read(iface, props);
+ processChAccessPropChange(path, props);
+ });
+ signalHndlrObjectState = true;
+
+ chInterfaceAddedSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::member(interfaceAddedSignal) +
+ sdbusplus::bus::match::rules::argNpath(
+ 0, std::string(networkIntfObjectBasePath) + "/"),
+ [&](sdbusplus::message_t&) { initChannelPersistData(); });
+
+ chInterfaceRemovedSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::member(interfaceRemovedSignal) +
+ sdbusplus::bus::match::rules::argNpath(
+ 0, std::string(networkIntfObjectBasePath) + "/"),
+ [&](sdbusplus::message_t&) { initChannelPersistData(); });
+ }
+}
+
+bool ChannelConfig::isValidChannel(const uint8_t chNum)
+{
+ if (chNum >= maxIpmiChannels)
+ {
+ lg2::debug("Invalid channel ID - Out of range");
+ return false;
+ }
+
+ if (channelData[chNum].isChValid == false)
+ {
+ lg2::debug("Channel is not valid");
+ }
+
+ return channelData[chNum].isChValid;
+}
+
+EChannelSessSupported ChannelConfig::getChannelSessionSupport(
+ const uint8_t chNum)
+{
+ EChannelSessSupported chSessSupport =
+ (EChannelSessSupported)channelData[chNum].chInfo.sessionSupported;
+ return chSessSupport;
+}
+
+bool ChannelConfig::isValidAuthType(const uint8_t chNum,
+ const EAuthType& authType)
+{
+ if ((authType < EAuthType::md2) || (authType > EAuthType::oem))
+ {
+ lg2::debug("Invalid authentication type");
+ return false;
+ }
+
+ uint8_t authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
+ if (!(authTypeSupported & (1 << static_cast<uint8_t>(authType))))
+ {
+ lg2::debug("Authentication type is not supported.");
+ return false;
+ }
+
+ return true;
+}
+
+int ChannelConfig::getChannelActiveSessions(const uint8_t chNum)
+{
+ // TODO: TEMPORARY FIX
+ // Channels active session count is managed separately
+ // by monitoring channel session which includes LAN and
+ // RAKP layer changes. This will be updated, once the
+ // authentication part is implemented.
+ return channelData[chNum].activeSessCount;
+}
+
+size_t ChannelConfig::getChannelMaxTransferSize(uint8_t chNum)
+{
+ return channelData[chNum].maxTransferSize;
+}
+
+Cc ChannelConfig::getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ std::copy_n(reinterpret_cast<uint8_t*>(&channelData[chNum].chInfo),
+ sizeof(channelData[chNum].chInfo),
+ reinterpret_cast<uint8_t*>(&chInfo));
+ return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAccessData(const uint8_t chNum,
+ ChannelAccess& chAccessData)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Session-less channel doesn't have access data.");
+ return ccActionNotSupportedForChannel;
+ }
+
+ if (checkAndReloadVolatileData() != 0)
+ {
+ return ccUnspecifiedError;
+ }
+
+ std::copy_n(
+ reinterpret_cast<uint8_t*>(&channelData[chNum].chAccess.chVolatileData),
+ sizeof(channelData[chNum].chAccess.chVolatileData),
+ reinterpret_cast<uint8_t*>(&chAccessData));
+
+ return ccSuccess;
+}
+
+Cc ChannelConfig::setChannelAccessData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Session-less channel doesn't have access data.");
+ return ccActionNotSupportedForChannel;
+ }
+
+ if ((setFlag & setAccessMode) &&
+ (!isValidAccessMode(chAccessData.accessMode)))
+ {
+ lg2::debug("Invalid access mode specified");
+ return ccAccessModeNotSupportedForChannel;
+ }
+ if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
+ {
+ lg2::debug("Invalid privilege limit specified");
+ return ccInvalidFieldRequest;
+ }
+
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ if (checkAndReloadVolatileData() != 0)
+ {
+ return ccUnspecifiedError;
+ }
+
+ if (setFlag & setAccessMode)
+ {
+ channelData[chNum].chAccess.chVolatileData.accessMode =
+ chAccessData.accessMode;
+ }
+ if (setFlag & setUserAuthEnabled)
+ {
+ channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
+ chAccessData.userAuthDisabled;
+ }
+ if (setFlag & setMsgAuthEnabled)
+ {
+ channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
+ chAccessData.perMsgAuthDisabled;
+ }
+ if (setFlag & setAlertingEnabled)
+ {
+ channelData[chNum].chAccess.chVolatileData.alertingDisabled =
+ chAccessData.alertingDisabled;
+ }
+ if (setFlag & setPrivLimit)
+ {
+ channelData[chNum].chAccess.chVolatileData.privLimit =
+ chAccessData.privLimit;
+ }
+
+ // Write Volatile data to file
+ if (writeChannelVolatileData() != 0)
+ {
+ lg2::debug("Failed to update the channel volatile data");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAccessPersistData(const uint8_t chNum,
+ ChannelAccess& chAccessData)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Session-less channel doesn't have access data.");
+ return ccActionNotSupportedForChannel;
+ }
+
+ if (checkAndReloadNVData() != 0)
+ {
+ return ccUnspecifiedError;
+ }
+
+ std::copy_n(reinterpret_cast<uint8_t*>(
+ &channelData[chNum].chAccess.chNonVolatileData),
+ sizeof(channelData[chNum].chAccess.chNonVolatileData),
+ reinterpret_cast<uint8_t*>(&chAccessData));
+
+ return ccSuccess;
+}
+
+Cc ChannelConfig::setChannelAccessPersistData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Session-less channel doesn't have access data.");
+ return ccActionNotSupportedForChannel;
+ }
+
+ if ((setFlag & setAccessMode) &&
+ (!isValidAccessMode(chAccessData.accessMode)))
+ {
+ lg2::debug("Invalid access mode specified");
+ return ccAccessModeNotSupportedForChannel;
+ }
+ if ((setFlag & setPrivLimit) && (!isValidPrivLimit(chAccessData.privLimit)))
+ {
+ lg2::debug("Invalid privilege limit specified");
+ return ccInvalidFieldRequest;
+ }
+
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ if (checkAndReloadNVData() != 0)
+ {
+ return ccUnspecifiedError;
+ }
+
+ if (setFlag & setAccessMode)
+ {
+ channelData[chNum].chAccess.chNonVolatileData.accessMode =
+ chAccessData.accessMode;
+ }
+ if (setFlag & setUserAuthEnabled)
+ {
+ channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
+ chAccessData.userAuthDisabled;
+ }
+ if (setFlag & setMsgAuthEnabled)
+ {
+ channelData[chNum].chAccess.chNonVolatileData.perMsgAuthDisabled =
+ chAccessData.perMsgAuthDisabled;
+ }
+ if (setFlag & setAlertingEnabled)
+ {
+ channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
+ chAccessData.alertingDisabled;
+ }
+ if (setFlag & setPrivLimit)
+ {
+ // Send Update to network channel config interfaces over dbus
+ std::string privStr = convertToPrivLimitString(chAccessData.privLimit);
+ std::string networkIntfObj = std::string(networkIntfObjectBasePath) +
+ "/" + channelData[chNum].chName;
+ try
+ {
+ if (0 != setDbusProperty(networkIntfServiceName, networkIntfObj,
+ networkChConfigIntfName,
+ privilegePropertyString, privStr))
+ {
+ lg2::debug("Network interface '{INTERFACE}' does not exist",
+ "INTERFACE", channelData[chNum].chName);
+ return ccUnspecifiedError;
+ }
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Exception: Network interface does not exist");
+ return ccInvalidFieldRequest;
+ }
+ signalFlag |= (1 << chNum);
+ channelData[chNum].chAccess.chNonVolatileData.privLimit =
+ chAccessData.privLimit;
+ }
+
+ // Write persistent data to file
+ if (writeChannelPersistData() != 0)
+ {
+ lg2::debug("Failed to update the presist data file");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelAuthTypeSupported(const uint8_t chNum,
+ uint8_t& authTypeSupported)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ authTypeSupported = channelData[chNum].chInfo.authTypeSupported;
+ return ccSuccess;
+}
+
+Cc ChannelConfig::getChannelEnabledAuthType(
+ const uint8_t chNum, const uint8_t priv, EAuthType& authType)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Invalid channel");
+ return ccInvalidFieldRequest;
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Sessionless channel doesn't have access data.");
+ return ccInvalidFieldRequest;
+ }
+
+ if (!isValidPrivLimit(priv))
+ {
+ lg2::debug("Invalid privilege specified.");
+ return ccInvalidFieldRequest;
+ }
+
+ // TODO: Hardcoded for now. Need to implement.
+ authType = EAuthType::none;
+
+ return ccSuccess;
+}
+
+std::time_t ChannelConfig::getUpdatedFileTime(const std::string& fileName)
+{
+ struct stat fileStat;
+ if (stat(fileName.c_str(), &fileStat) != 0)
+ {
+ lg2::debug("Error in getting last updated time stamp");
+ return -EIO;
+ }
+ return fileStat.st_mtime;
+}
+
+EChannelAccessMode ChannelConfig::convertToAccessModeIndex(
+ const std::string& mode)
+{
+ auto iter = std::find(accessModeList.begin(), accessModeList.end(), mode);
+ if (iter == accessModeList.end())
+ {
+ lg2::error("Invalid access mode: {MODE_STR}", "MODE_STR", mode);
+ throw std::invalid_argument("Invalid access mode.");
+ }
+
+ return static_cast<EChannelAccessMode>(
+ std::distance(accessModeList.begin(), iter));
+}
+
+std::string ChannelConfig::convertToAccessModeString(const uint8_t value)
+{
+ if (accessModeList.size() <= value)
+ {
+ lg2::error("Invalid access mode: {MODE_IDX}", "MODE_IDX", value);
+ throw std::invalid_argument("Invalid access mode.");
+ }
+
+ return accessModeList.at(value);
+}
+
+CommandPrivilege ChannelConfig::convertToPrivLimitIndex(
+ const std::string& value)
+{
+ auto iter = std::find(privList.begin(), privList.end(), value);
+ if (iter == privList.end())
+ {
+ lg2::error("Invalid privilege: {PRIV_STR}", "PRIV_STR", value);
+ throw std::invalid_argument("Invalid privilege.");
+ }
+
+ return static_cast<CommandPrivilege>(std::distance(privList.begin(), iter));
+}
+
+std::string ChannelConfig::convertToPrivLimitString(const uint8_t value)
+{
+ if (privList.size() <= value)
+ {
+ lg2::error("Invalid privilege: {PRIV_IDX.", "PRIV_IDX", value);
+ throw std::invalid_argument("Invalid privilege.");
+ }
+
+ return privList.at(value);
+}
+
+EChannelSessSupported ChannelConfig::convertToSessionSupportIndex(
+ const std::string& value)
+{
+ auto iter =
+ std::find(sessionSupportList.begin(), sessionSupportList.end(), value);
+ if (iter == sessionSupportList.end())
+ {
+ lg2::error("Invalid session supported: {SESS_STR}", "SESS_STR", value);
+ throw std::invalid_argument("Invalid session supported.");
+ }
+
+ return static_cast<EChannelSessSupported>(
+ std::distance(sessionSupportList.begin(), iter));
+}
+
+EChannelMediumType ChannelConfig::convertToMediumTypeIndex(
+ const std::string& value)
+{
+ std::unordered_map<std::string, EChannelMediumType>::iterator it =
+ mediumTypeMap.find(value);
+ if (it == mediumTypeMap.end())
+ {
+ lg2::error("Invalid medium type: {MEDIUM_STR}", "MEDIUM_STR", value);
+ throw std::invalid_argument("Invalid medium type.");
+ }
+
+ return static_cast<EChannelMediumType>(it->second);
+}
+
+EChannelProtocolType ChannelConfig::convertToProtocolTypeIndex(
+ const std::string& value)
+{
+ std::unordered_map<std::string, EChannelProtocolType>::iterator it =
+ protocolTypeMap.find(value);
+ if (it == protocolTypeMap.end())
+ {
+ lg2::error("Invalid protocol type: {PROTO_STR}", "PROTO_STR", value);
+ throw std::invalid_argument("Invalid protocol type.");
+ }
+
+ return static_cast<EChannelProtocolType>(it->second);
+}
+
+Json ChannelConfig::readJsonFile(const std::string& configFile)
+{
+ std::ifstream jsonFile(configFile);
+ if (!jsonFile.good())
+ {
+ lg2::info("JSON file '{FILE_NAME}' not found", "FILE_NAME", configFile);
+ return nullptr;
+ }
+
+ Json data = nullptr;
+ try
+ {
+ data = Json::parse(jsonFile, nullptr, false);
+ }
+ catch (const Json::parse_error& e)
+ {
+ lg2::debug("Corrupted channel config: {MSG}", "MSG", e.what());
+ throw std::runtime_error("Corrupted channel config file");
+ }
+
+ return data;
+}
+
+int ChannelConfig::writeJsonFile(const std::string& configFile,
+ const Json& jsonData)
+{
+ const std::string tmpFile = configFile + "_tmp";
+ int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0)
+ {
+ lg2::error("Error in creating json file '{FILE_NAME}'", "FILE_NAME",
+ tmpFile);
+ return -EIO;
+ }
+ const auto& writeData = jsonData.dump();
+ if (write(fd, writeData.c_str(), writeData.size()) !=
+ static_cast<ssize_t>(writeData.size()))
+ {
+ close(fd);
+ lg2::error("Error in writing configuration file '{FILE_NAME}'",
+ "FILE_NAME", tmpFile);
+ return -EIO;
+ }
+ close(fd);
+
+ if (std::rename(tmpFile.c_str(), configFile.c_str()) != 0)
+ {
+ lg2::error("Error in renaming temporary data file '{FILE_NAME}'",
+ "FILE_NAME", tmpFile);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void ChannelConfig::setDefaultChannelConfig(const uint8_t chNum,
+ const std::string& chName)
+{
+ channelData[chNum].chName = chName;
+ channelData[chNum].chID = chNum;
+ channelData[chNum].isChValid = false;
+ channelData[chNum].activeSessCount = 0;
+ channelData[chNum].isManagementNIC = false;
+
+ channelData[chNum].chInfo.mediumType = defaultMediumType;
+ channelData[chNum].chInfo.protocolType = defaultProtocolType;
+ channelData[chNum].chInfo.sessionSupported = defaultSessionSupported;
+ channelData[chNum].chInfo.isIpmi = defaultIsIpmiState;
+ channelData[chNum].chInfo.authTypeSupported = defaultAuthType;
+}
+
+uint8_t ChannelConfig::getManagementNICID()
+{
+ static bool idFound = false;
+ static uint8_t id = 0;
+
+ if (idFound)
+ {
+ return id;
+ }
+
+ for (uint8_t chIdx = 0; chIdx < maxIpmiChannels; chIdx++)
+ {
+ if (channelData[chIdx].isManagementNIC)
+ {
+ id = chIdx;
+ idFound = true;
+ break;
+ }
+ }
+
+ if (!idFound)
+ {
+ id = static_cast<uint8_t>(EChannelID::chanLan1);
+ idFound = true;
+ }
+ return id;
+}
+
+int ChannelConfig::loadChannelConfig()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ Json data = readJsonFile(channelConfigDefaultFilename);
+ if (data.empty())
+ {
+ lg2::debug("Error in opening IPMI Channel data file");
+ return -EIO;
+ }
+
+ channelData.fill(ChannelProperties{});
+
+ // Collect the list of NIC interfaces connected to the BMC. Use this
+ // information to only add IPMI channels that have active NIC interfaces.
+ struct ifaddrs *ifaddr = nullptr, *ifa = nullptr;
+ if (int err = getifaddrs(&ifaddr); err < 0)
+ {
+ lg2::debug("Unable to acquire network interfaces");
+ return -EIO;
+ }
+
+ for (int chNum = 0; chNum < maxIpmiChannels; chNum++)
+ {
+ try
+ {
+ std::string chKey = std::to_string(chNum);
+ Json jsonChData = data[chKey].get<Json>();
+ if (jsonChData.is_null())
+ {
+ // If user didn't want to configure specific channel (say
+ // reserved channel), then load that index with default values.
+ setDefaultChannelConfig(chNum, defaultChannelName);
+ continue;
+ }
+ Json jsonChInfo = jsonChData[channelInfoString].get<Json>();
+ if (jsonChInfo.is_null())
+ {
+ lg2::error("Invalid/corrupted channel config file");
+ freeifaddrs(ifaddr);
+ return -EBADMSG;
+ }
+
+ bool channelFound = true;
+ // Confirm the LAN channel is present
+ if (jsonChInfo[mediumTypeString].get<std::string>() == "lan-802.3")
+ {
+ channelFound = false;
+ for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next)
+ {
+ if (jsonChData[nameString].get<std::string>() ==
+ ifa->ifa_name)
+ {
+ channelFound = true;
+ break;
+ }
+ }
+ }
+ ChannelProperties& chData = channelData[chNum];
+ chData.chID = chNum;
+ chData.chName = jsonChData[nameString].get<std::string>();
+ chData.isChValid = channelFound &&
+ jsonChData[isValidString].get<bool>();
+ chData.activeSessCount = jsonChData.value(activeSessionsString, 0);
+ chData.maxTransferSize =
+ jsonChData.value(maxTransferSizeString, smallChannelSize);
+ if (jsonChData.count(isManagementNIC) != 0)
+ {
+ chData.isManagementNIC =
+ jsonChData[isManagementNIC].get<bool>();
+ }
+
+ std::string medTypeStr =
+ jsonChInfo[mediumTypeString].get<std::string>();
+ chData.chInfo.mediumType =
+ static_cast<uint8_t>(convertToMediumTypeIndex(medTypeStr));
+ std::string protoTypeStr =
+ jsonChInfo[protocolTypeString].get<std::string>();
+ chData.chInfo.protocolType =
+ static_cast<uint8_t>(convertToProtocolTypeIndex(protoTypeStr));
+ std::string sessStr =
+ jsonChInfo[sessionSupportedString].get<std::string>();
+ chData.chInfo.sessionSupported =
+ static_cast<uint8_t>(convertToSessionSupportIndex(sessStr));
+ chData.chInfo.isIpmi = jsonChInfo[isIpmiString].get<bool>();
+ chData.chInfo.authTypeSupported = defaultAuthType;
+ }
+ catch (const Json::exception& e)
+ {
+ lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+ freeifaddrs(ifaddr);
+
+ return -EBADMSG;
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+ freeifaddrs(ifaddr);
+ return -EBADMSG;
+ }
+ }
+ freeifaddrs(ifaddr);
+
+ return 0;
+}
+
+int ChannelConfig::readChannelVolatileData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ Json data = readJsonFile(channelVolatileDataFilename);
+ if (data == nullptr)
+ {
+ lg2::debug("Error in opening IPMI Channel data file");
+ return -EIO;
+ }
+ try
+ {
+ // Fill in global structure
+ for (auto it = data.begin(); it != data.end(); ++it)
+ {
+ std::string chKey = it.key();
+ uint8_t chNum = std::stoi(chKey, nullptr, 10);
+ if (chNum >= maxIpmiChannels)
+ {
+ lg2::debug("Invalid channel access entry in config file");
+ throw std::out_of_range("Out of range - channel number");
+ }
+ Json jsonChData = it.value();
+ if (!jsonChData.is_null())
+ {
+ std::string accModeStr =
+ jsonChData[accessModeString].get<std::string>();
+ channelData[chNum].chAccess.chVolatileData.accessMode =
+ static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
+ channelData[chNum].chAccess.chVolatileData.userAuthDisabled =
+ jsonChData[userAuthDisabledString].get<bool>();
+ channelData[chNum].chAccess.chVolatileData.perMsgAuthDisabled =
+ jsonChData[perMsgAuthDisabledString].get<bool>();
+ channelData[chNum].chAccess.chVolatileData.alertingDisabled =
+ jsonChData[alertingDisabledString].get<bool>();
+ std::string privStr =
+ jsonChData[privLimitString].get<std::string>();
+ channelData[chNum].chAccess.chVolatileData.privLimit =
+ static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
+ }
+ else
+ {
+ lg2::error(
+ "Invalid/corrupted volatile channel access file '{FILE}'",
+ "FILE", channelVolatileDataFilename);
+ throw std::runtime_error(
+ "Corrupted volatile channel access file");
+ }
+ }
+ }
+ catch (const Json::exception& e)
+ {
+ lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+ throw std::runtime_error("Corrupted volatile channel access file");
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+ throw std::runtime_error("Corrupted volatile channel access file");
+ }
+
+ // Update the timestamp
+ voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
+ return 0;
+}
+
+int ChannelConfig::readChannelPersistData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ Json data = readJsonFile(channelNvDataFilename);
+ if (data == nullptr)
+ {
+ lg2::debug("Error in opening IPMI Channel data file");
+ return -EIO;
+ }
+ try
+ {
+ // Fill in global structure
+ for (auto it = data.begin(); it != data.end(); ++it)
+ {
+ std::string chKey = it.key();
+ uint8_t chNum = std::stoi(chKey, nullptr, 10);
+ if (chNum >= maxIpmiChannels)
+ {
+ lg2::debug("Invalid channel access entry in config file");
+ throw std::out_of_range("Out of range - channel number");
+ }
+ Json jsonChData = it.value();
+ if (!jsonChData.is_null())
+ {
+ std::string accModeStr =
+ jsonChData[accessModeString].get<std::string>();
+ channelData[chNum].chAccess.chNonVolatileData.accessMode =
+ static_cast<uint8_t>(convertToAccessModeIndex(accModeStr));
+ channelData[chNum].chAccess.chNonVolatileData.userAuthDisabled =
+ jsonChData[userAuthDisabledString].get<bool>();
+ channelData[chNum]
+ .chAccess.chNonVolatileData.perMsgAuthDisabled =
+ jsonChData[perMsgAuthDisabledString].get<bool>();
+ channelData[chNum].chAccess.chNonVolatileData.alertingDisabled =
+ jsonChData[alertingDisabledString].get<bool>();
+ std::string privStr =
+ jsonChData[privLimitString].get<std::string>();
+ channelData[chNum].chAccess.chNonVolatileData.privLimit =
+ static_cast<uint8_t>(convertToPrivLimitIndex(privStr));
+ }
+ else
+ {
+ lg2::error("Invalid/corrupted nv channel access file {FILE}",
+ "FILE", channelNvDataFilename);
+ throw std::runtime_error("Corrupted nv channel access file");
+ }
+ }
+ }
+ catch (const Json::exception& e)
+ {
+ lg2::debug("Json Exception caught: {MSG}", "MSG", e.what());
+ throw std::runtime_error("Corrupted nv channel access file");
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+ throw std::runtime_error("Corrupted nv channel access file");
+ }
+
+ // Update the timestamp
+ nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
+ return 0;
+}
+
+int ChannelConfig::writeChannelVolatileData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+ Json outData;
+
+ try
+ {
+ for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+ {
+ if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+ {
+ Json jsonObj;
+ std::string chKey = std::to_string(chNum);
+ std::string accModeStr = convertToAccessModeString(
+ channelData[chNum].chAccess.chVolatileData.accessMode);
+ jsonObj[accessModeString] = accModeStr;
+ jsonObj[userAuthDisabledString] =
+ channelData[chNum].chAccess.chVolatileData.userAuthDisabled;
+ jsonObj[perMsgAuthDisabledString] =
+ channelData[chNum]
+ .chAccess.chVolatileData.perMsgAuthDisabled;
+ jsonObj[alertingDisabledString] =
+ channelData[chNum].chAccess.chVolatileData.alertingDisabled;
+ std::string privStr = convertToPrivLimitString(
+ channelData[chNum].chAccess.chVolatileData.privLimit);
+ jsonObj[privLimitString] = privStr;
+
+ outData[chKey] = jsonObj;
+ }
+ }
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+ return -EINVAL;
+ }
+
+ if (writeJsonFile(channelVolatileDataFilename, outData) != 0)
+ {
+ lg2::debug("Error in write JSON data to file");
+ return -EIO;
+ }
+
+ // Update the timestamp
+ voltFileLastUpdatedTime = getUpdatedFileTime(channelVolatileDataFilename);
+ return 0;
+}
+
+int ChannelConfig::writeChannelPersistData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+ Json outData;
+
+ try
+ {
+ for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+ {
+ if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+ {
+ Json jsonObj;
+ std::string chKey = std::to_string(chNum);
+ std::string accModeStr = convertToAccessModeString(
+ channelData[chNum].chAccess.chNonVolatileData.accessMode);
+ jsonObj[accessModeString] = accModeStr;
+ jsonObj[userAuthDisabledString] =
+ channelData[chNum]
+ .chAccess.chNonVolatileData.userAuthDisabled;
+ jsonObj[perMsgAuthDisabledString] =
+ channelData[chNum]
+ .chAccess.chNonVolatileData.perMsgAuthDisabled;
+ jsonObj[alertingDisabledString] =
+ channelData[chNum]
+ .chAccess.chNonVolatileData.alertingDisabled;
+ std::string privStr = convertToPrivLimitString(
+ channelData[chNum].chAccess.chNonVolatileData.privLimit);
+ jsonObj[privLimitString] = privStr;
+
+ outData[chKey] = jsonObj;
+ }
+ }
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::error("Corrupted config: {MSG}", "MSG", e.what());
+ return -EINVAL;
+ }
+
+ if (writeJsonFile(channelNvDataFilename, outData) != 0)
+ {
+ lg2::debug("Error in write JSON data to file");
+ return -EIO;
+ }
+
+ // Update the timestamp
+ nvFileLastUpdatedTime = getUpdatedFileTime(channelNvDataFilename);
+ return 0;
+}
+
+int ChannelConfig::checkAndReloadNVData()
+{
+ std::time_t updateTime = getUpdatedFileTime(channelNvDataFilename);
+ int ret = 0;
+ if (updateTime != nvFileLastUpdatedTime || updateTime == -EIO)
+ {
+ try
+ {
+ ret = readChannelPersistData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Exception caught in readChannelPersistData: {MSG}",
+ "MSG", e.what());
+ ret = -EIO;
+ }
+ }
+ return ret;
+}
+
+int ChannelConfig::checkAndReloadVolatileData()
+{
+ std::time_t updateTime = getUpdatedFileTime(channelVolatileDataFilename);
+ int ret = 0;
+ if (updateTime != voltFileLastUpdatedTime || updateTime == -EIO)
+ {
+ try
+ {
+ ret = readChannelVolatileData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Exception caught in readChannelVolatileData: {MSG}",
+ "MSG", e.what());
+ ret = -EIO;
+ }
+ }
+ return ret;
+}
+
+int ChannelConfig::setDbusProperty(
+ const std::string& service, const std::string& objPath,
+ const std::string& interface, const std::string& property,
+ const DbusVariant& value)
+{
+ try
+ {
+ auto method =
+ bus.new_method_call(service.c_str(), objPath.c_str(),
+ "org.freedesktop.DBus.Properties", "Set");
+
+ method.append(interface, property, value);
+
+ auto reply = bus.call(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug(
+ "set-property {SERVICE}:{OBJPATH}/{INTERFACE}.{PROP} failed: {MSG}",
+ "SERVICE", service, "OBJPATH", objPath, "INTERFACE", interface,
+ "PROP", property, "MSG", e);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int ChannelConfig::syncNetworkChannelConfig()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+ bool isUpdated = false;
+ for (uint8_t chNum = 0; chNum < maxIpmiChannels; chNum++)
+ {
+ if (getChannelSessionSupport(chNum) != EChannelSessSupported::none)
+ {
+ std::string intfPrivStr;
+ uint8_t intfPriv = 0;
+ try
+ {
+ std::string networkIntfObj =
+ std::string(networkIntfObjectBasePath) + "/" +
+ channelData[chNum].chName;
+ auto propValue = ipmi::getDbusProperty(
+ bus, networkIntfServiceName, networkIntfObj,
+ networkChConfigIntfName, privilegePropertyString);
+
+ intfPrivStr = std::get<std::string>(propValue);
+ intfPriv =
+ static_cast<uint8_t>(convertToPrivLimitIndex(intfPrivStr));
+ }
+ catch (const std::bad_variant_access& e)
+ {
+ lg2::debug("Network interface '{INTERFACE}' does not exist",
+ "INTERFACE", channelData[chNum].chName);
+ continue;
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Network interface '{INTERFACE}' does not exist",
+ "INTERFACE", channelData[chNum].chName);
+ continue;
+ }
+ catch (const std::invalid_argument& e)
+ {
+ lg2::debug("exception: Invalid privilege");
+ continue;
+ }
+
+ if (channelData[chNum].chAccess.chNonVolatileData.privLimit !=
+ intfPriv)
+ {
+ isUpdated = true;
+ channelData[chNum].chAccess.chNonVolatileData.privLimit =
+ intfPriv;
+ channelData[chNum].chAccess.chVolatileData.privLimit = intfPriv;
+ }
+ }
+ }
+
+ if (isUpdated)
+ {
+ // Write persistent data to file
+ if (writeChannelPersistData() != 0)
+ {
+ lg2::debug("Failed to update the persistent data file");
+ return -EIO;
+ }
+ // Write Volatile data to file
+ if (writeChannelVolatileData() != 0)
+ {
+ lg2::debug("Failed to update the channel volatile data");
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+void ChannelConfig::initChannelPersistData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ channelLock{*channelMutex};
+
+ /* Always read the channel config */
+ if (loadChannelConfig() != 0)
+ {
+ lg2::error("Failed to read channel config file");
+ throw std::ios_base::failure("Failed to load channel configuration");
+ }
+
+ /* Populate the channel persist data */
+ if (readChannelPersistData() != 0)
+ {
+ // Copy default NV data to RW location
+ std::filesystem::copy_file(channelAccessDefaultFilename,
+ channelNvDataFilename);
+
+ // Load the channel access NV data
+ if (readChannelPersistData() != 0)
+ {
+ lg2::error("Failed to read channel access NV data");
+ throw std::ios_base::failure(
+ "Failed to read channel access NV configuration");
+ }
+ }
+
+ // First check the volatile data file
+ // If not present, load the default values
+ if (readChannelVolatileData() != 0)
+ {
+ // Copy default volatile data to temporary location
+ // NV file(channelNvDataFilename) must have created by now.
+ std::filesystem::copy_file(channelNvDataFilename,
+ channelVolatileDataFilename);
+
+ // Load the channel access volatile data
+ if (readChannelVolatileData() != 0)
+ {
+ lg2::error("Failed to read channel access volatile data");
+ throw std::ios_base::failure(
+ "Failed to read channel access volatile configuration");
+ }
+ }
+
+ // Synchronize the channel config(priv) with network channel
+ // configuration(priv) over dbus
+ if (syncNetworkChannelConfig() != 0)
+ {
+ lg2::error(
+ "Failed to synchronize data with network channel config over dbus");
+ throw std::ios_base::failure(
+ "Failed to synchronize data with network channel config over dbus");
+ }
+
+ lg2::debug("Successfully completed channel data initialization.");
+ return;
+}
+
+} // namespace ipmi
diff --git a/user_channel/channel_mgmt.hpp b/user_channel/channel_mgmt.hpp
new file mode 100644
index 0000000..5ec5a16
--- /dev/null
+++ b/user_channel/channel_mgmt.hpp
@@ -0,0 +1,428 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include "channel_layer.hpp"
+#include "ipmid/api-types.hpp"
+
+#include <boost/interprocess/sync/file_lock.hpp>
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <variant>
+
+namespace ipmi
+{
+
+using Json = nlohmann::json;
+
+using DbusVariant = std::variant<std::vector<std::string>, std::string, bool>;
+
+using DbusChObjProperties = std::vector<std::pair<std::string, DbusVariant>>;
+
+static constexpr const char* ipmiChannelMutex = "ipmi_channel_mutex";
+static constexpr const char* ipmiChMutexCleanupLockFile =
+ "/run/ipmi/ipmi_channel_mutex_cleanup";
+
+/** @struct ChannelAccessData
+ *
+ * Structure to store both non-volatile and volatile channel access information
+ * as used by IPMI specification (refer spec sec 22.22 to 22.24)
+ */
+struct ChannelAccessData
+{
+ ChannelAccess chNonVolatileData;
+ ChannelAccess chVolatileData;
+};
+
+/** @struct ChannelProperties
+ *
+ * Structure for channel information - base structure to get all information
+ * about the channel.(refer spec sec 22.22 to 22.24)
+ */
+struct ChannelProperties
+{
+ std::string chName;
+ uint8_t chID;
+ bool isChValid;
+ uint8_t activeSessCount;
+ ChannelInfo chInfo;
+ ChannelAccessData chAccess;
+ size_t maxTransferSize;
+ bool isManagementNIC;
+};
+
+class ChannelConfig;
+
+ChannelConfig& getChannelConfigObject();
+
+class ChannelConfig
+{
+ public:
+ ChannelConfig(const ChannelConfig&) = delete;
+ ChannelConfig& operator=(const ChannelConfig&) = delete;
+ ChannelConfig(ChannelConfig&&) = delete;
+ ChannelConfig& operator=(ChannelConfig&&) = delete;
+
+ ~ChannelConfig();
+ ChannelConfig();
+
+ /** @brief determines valid channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return true if valid, false otherwise
+ */
+ bool isValidChannel(const uint8_t chNum);
+
+ /** @brief determines valid authentication type
+ *
+ * @param[in] chNum - channel number
+ * @param[in] authType - authentication type
+ *
+ * @return true if valid, false otherwise
+ */
+ bool isValidAuthType(const uint8_t chNum, const EAuthType& authType);
+
+ /** @brief function to get channel name from channel number
+ *
+ * @param[in] chNum - channel number index
+ *
+ * @return network channel interface name
+ */
+ std::string getChannelName(const uint8_t chNum);
+
+ /** @brief function to get channel number from channel name
+ *
+ * @param[in] chName - channel name
+ *
+ * @return network channel interface number
+ */
+
+ uint8_t getChannelByName(const std::string& chName)
+ {
+ return convertToChannelNumberFromChannelName(chName);
+ }
+
+ /** @brief determines supported session type of a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return EChannelSessSupported - supported session type
+ */
+ EChannelSessSupported getChannelSessionSupport(const uint8_t chNum);
+
+ /** @brief determines number of active sessions on a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return numer of active sessions
+ */
+ int getChannelActiveSessions(const uint8_t chNum);
+
+ /** @brief determines maximum transfer size for a channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return maximum bytes that can be transferred on this channel
+ */
+ size_t getChannelMaxTransferSize(uint8_t chNum);
+
+ /** @brief provides channel info details
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chInfo - channel info details
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getChannelInfo(const uint8_t chNum, ChannelInfo& chInfo);
+
+ /** @brief provides channel access data
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chAccessData - channel access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getChannelAccessData(const uint8_t chNum, ChannelAccess& chAccessData);
+
+ /** @brief to set channel access data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] chAccessData - channel access data
+ * @param[in] setFlag - flag to indicate updatable fields
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setChannelAccessData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag);
+
+ /** @brief to get channel access data persistent data
+ *
+ * @param[in] chNum - channel number
+ * @param[out] chAccessData - channel access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getChannelAccessPersistData(const uint8_t chNum,
+ ChannelAccess& chAccessData);
+
+ /** @brief to set channel access data persistent data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] chAccessData - channel access data
+ * @param[in] setFlag - flag to indicate updatable fields
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setChannelAccessPersistData(const uint8_t chNum,
+ const ChannelAccess& chAccessData,
+ const uint8_t setFlag);
+
+ /** @brief provides supported authentication type for the channel
+ *
+ * @param[in] chNum - channel number
+ * @param[out] authTypeSupported - supported authentication type
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getChannelAuthTypeSupported(const uint8_t chNum,
+ uint8_t& authTypeSupported);
+
+ /** @brief provides enabled authentication type for the channel
+ *
+ * @param[in] chNum - channel number
+ * @param[in] priv - privilege
+ * @param[out] authType - enabled authentication type
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv,
+ EAuthType& authType);
+
+ /** @brief conver to channel privilege from system privilege
+ *
+ * @param[in] value - privilege value
+ *
+ * @return Channel privilege
+ */
+ CommandPrivilege convertToPrivLimitIndex(const std::string& value);
+
+ /** @brief function to write persistent channel configuration to config file
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int writeChannelPersistData();
+
+ /** @brief function to write volatile channel configuration to config file
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int writeChannelVolatileData();
+
+ /** @brief Returns the IPMI channel ID authorized to push IPMI privilege
+ * changes to phosphor-user-manager. Any channel access changes made on
+ * any other channel are ignored.
+ *
+ * @return IPMI channel ID as defined in channel_config.json
+ */
+ uint8_t getManagementNICID();
+
+ private:
+ uint32_t signalFlag = 0;
+ std::unique_ptr<boost::interprocess::named_recursive_mutex> channelMutex{
+ nullptr};
+ std::array<ChannelProperties, maxIpmiChannels> channelData;
+ std::time_t nvFileLastUpdatedTime;
+ std::time_t voltFileLastUpdatedTime;
+ boost::interprocess::file_lock mutexCleanupLock;
+ sdbusplus::bus_t bus;
+ bool signalHndlrObjectState = false;
+ boost::interprocess::file_lock sigHndlrLock;
+
+ /** @brief function to initialize persistent channel configuration
+ *
+ */
+ void initChannelPersistData();
+
+ /** @brief function to set default channel configuration based on channel
+ * number
+ *
+ * @param[in] chNum - channel number
+ * @param[in] chName - channel name
+ */
+ void setDefaultChannelConfig(const uint8_t chNum,
+ const std::string& chName);
+
+ /** @brief function to load all channel configuration
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int loadChannelConfig();
+
+ /** @brief function to read persistent channel data
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int readChannelPersistData();
+
+ /** @brief function to read volatile channel data
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int readChannelVolatileData();
+
+ /** @brief function to check and reload persistent channel data
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int checkAndReloadNVData();
+
+ /** @brief function to check and reload volatile channel data
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int checkAndReloadVolatileData();
+
+ /** @brief function to sync channel privilege with system network channel
+ * privilege
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int syncNetworkChannelConfig();
+
+ /** @brief function to set D-Bus property value
+ *
+ * @param[in] service - service name
+ * @param[in] objPath - object path
+ * @param[in] interface - interface
+ * @param[in] property - property name
+ * @param[in] value - property value
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int setDbusProperty(const std::string& service, const std::string& objPath,
+ const std::string& interface,
+ const std::string& property, const DbusVariant& value);
+
+ /** @brief function to read json config file
+ *
+ * @param[in] configFile - configuration file name
+ *
+ * @return Json object
+ */
+ Json readJsonFile(const std::string& configFile);
+
+ /** @brief function to write json config file
+ *
+ * @param[in] configFile - configuration file name
+ * @param[in] jsonData - json object
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int writeJsonFile(const std::string& configFile, const Json& jsonData);
+
+ /** @brief function to convert system access mode to Channel access mode
+ * type
+ *
+ * @param[in] mode - access mode in string
+ *
+ * @return Channel access mode.
+ */
+ EChannelAccessMode convertToAccessModeIndex(const std::string& mode);
+
+ /** @brief function to convert access mode value to string
+ *
+ * @param[in] value - acess mode value
+ *
+ * @return access mode in string
+ */
+ std::string convertToAccessModeString(const uint8_t value);
+
+ /** @brief function to convert privilege value to string
+ *
+ * @param[in] value - privilege value
+ *
+ * @return privilege in string
+ */
+ std::string convertToPrivLimitString(const uint8_t value);
+
+ /** @brief function to convert session support string to value type
+ *
+ * @param[in] value - session support type in string
+ *
+ * @return support session type
+ */
+ EChannelSessSupported convertToSessionSupportIndex(
+ const std::string& value);
+
+ /** @brief function to convert medium type string to value type
+ *
+ * @param[in] value - medium type in string
+ *
+ * @return channel medium type
+ */
+ EChannelMediumType convertToMediumTypeIndex(const std::string& value);
+
+ /** @brief function to convert protocol type string to value type
+ *
+ * @param[in] value - protocol type in string
+ *
+ * @return channel protocol type
+ */
+ EChannelProtocolType convertToProtocolTypeIndex(const std::string& value);
+
+ /** @brief function to convert channel name to the IPMI channel number.
+ *
+ * @param[in] chName - the channel name defined in the JSON input file
+ * (i.e. LAN1)
+ *
+ * @return IPMI channel number
+ */
+ int convertToChannelNumberFromChannelName(const std::string& chName);
+
+ /** @brief function to handle Channel access property update through the
+ * D-Bus handler.
+ *
+ * @param[in] path - D-Bus path to the network element (i.e. eth0)
+ * @param[in] chProperties - D-Bus channel properties
+ */
+ void processChAccessPropChange(const std::string& path,
+ const DbusChObjProperties& chProperties);
+
+ /** @brief function to retrieve last modification time for the named file
+ *
+ * @param[in] fileName - the name of the file for which to acquire
+ * timestamp data
+ *
+ * @return time the file was last modified
+ */
+ std::time_t getUpdatedFileTime(const std::string& fileName);
+
+ /** @brief function to convert the DBus path to a network channel name
+ *
+ * @param[in] path - The DBus path to the device
+ *
+ * @return network channel name (i.e. eth0)
+ */
+ std::string getChannelNameFromPath(const std::string& path);
+};
+
+} // namespace ipmi
diff --git a/user_channel/channelcommands.cpp b/user_channel/channelcommands.cpp
new file mode 100644
index 0000000..cb4da6b
--- /dev/null
+++ b/user_channel/channelcommands.cpp
@@ -0,0 +1,420 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "channel_layer.hpp"
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <regex>
+
+namespace ipmi
+{
+
+/** @brief implements the set channel access command
+ * @ param ctx - context pointer
+ * @ param channel - channel number
+ * @ param reserved - skip 4 bits
+ * @ param accessMode - access mode for IPMI messaging
+ * @ param usrAuth - user level authentication (enable/disable)
+ * @ param msgAuth - per message authentication (enable/disable)
+ * @ param alertDisabled - PEF alerting (enable/disable)
+ * @ param chanAccess - channel access
+ * @ param channelPrivLimit - channel privilege limit
+ * @ param reserved - skip 3 bits
+ * @ param channelPrivMode - channel priviledge mode
+ *
+ * @ returns IPMI completion code
+ **/
+RspType<> ipmiSetChannelAccess(
+ Context::ptr ctx, uint4_t channel, uint4_t reserved1, uint3_t accessMode,
+ bool usrAuth, bool msgAuth, bool alertDisabled, uint2_t chanAccess,
+ uint4_t channelPrivLimit, uint2_t reserved2, uint2_t channelPrivMode)
+{
+ if (reserved1 || reserved2 ||
+ !isValidPrivLimit(static_cast<uint8_t>(channelPrivLimit)))
+ {
+ lg2::debug("Set channel access - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ const uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+ if ((getChannelSessionSupport(chNum) == EChannelSessSupported::none) ||
+ (!isValidChannel(chNum)))
+ {
+ lg2::debug("Set channel access - No support on channel: {CHANNEL}",
+ "CHANNEL", chNum);
+ return response(ccActionNotSupportedForChannel);
+ }
+
+ ChannelAccess chActData;
+ ChannelAccess chNVData;
+ uint8_t setActFlag = 0;
+ uint8_t setNVFlag = 0;
+ Cc compCode;
+
+ // cannot static cast directly from uint2_t to enum; must go via int
+ uint8_t channelAccessAction = static_cast<uint8_t>(chanAccess);
+ switch (static_cast<EChannelActionType>(channelAccessAction))
+ {
+ case doNotSet:
+ break;
+ case nvData:
+ chNVData.accessMode = static_cast<uint8_t>(accessMode);
+ chNVData.userAuthDisabled = usrAuth;
+ chNVData.perMsgAuthDisabled = msgAuth;
+ chNVData.alertingDisabled = alertDisabled;
+ setNVFlag |= (setAccessMode | setUserAuthEnabled |
+ setMsgAuthEnabled | setAlertingEnabled);
+ break;
+
+ case activeData:
+ chActData.accessMode = static_cast<uint8_t>(accessMode);
+ chActData.userAuthDisabled = usrAuth;
+ chActData.perMsgAuthDisabled = msgAuth;
+ chActData.alertingDisabled = alertDisabled;
+ setActFlag |= (setAccessMode | setUserAuthEnabled |
+ setMsgAuthEnabled | setAlertingEnabled);
+ break;
+
+ case reserved:
+ default:
+ lg2::debug("Set channel access - Invalid access set mode");
+ return response(ccAccessModeNotSupportedForChannel);
+ }
+
+ // cannot static cast directly from uint2_t to enum; must go via int
+ uint8_t channelPrivAction = static_cast<uint8_t>(channelPrivMode);
+ switch (static_cast<EChannelActionType>(channelPrivAction))
+ {
+ case doNotSet:
+ break;
+ case nvData:
+ chNVData.privLimit = static_cast<uint8_t>(channelPrivLimit);
+ setNVFlag |= setPrivLimit;
+ break;
+ case activeData:
+ chActData.privLimit = static_cast<uint8_t>(channelPrivLimit);
+
+ setActFlag |= setPrivLimit;
+ break;
+ case reserved:
+ default:
+ lg2::debug("Set channel access - Invalid access priv mode");
+ return response(ccAccessModeNotSupportedForChannel);
+ }
+
+ if (setNVFlag != 0)
+ {
+ compCode = setChannelAccessPersistData(chNum, chNVData, setNVFlag);
+ if (compCode != ccSuccess)
+ {
+ lg2::debug("Set channel access - Failed to set access data");
+ return response(compCode);
+ }
+ }
+
+ if (setActFlag != 0)
+ {
+ compCode = setChannelAccessData(chNum, chActData, setActFlag);
+ if (compCode != ccSuccess)
+ {
+ lg2::debug("Set channel access - Failed to set access data");
+ return response(compCode);
+ }
+ }
+
+ return responseSuccess();
+}
+
+/** @brief implements the get channel access command
+ * @ param ctx - context pointer
+ * @ param channel - channel number
+ * @ param reserved1 - skip 4 bits
+ * @ param reserved2 - skip 6 bits
+ * @ param accessMode - get access mode
+ *
+ * @returns ipmi completion code plus response data
+ * - accessMode - get access mode
+ * - usrAuthDisabled - user level authentication status
+ * - msgAuthDisabled - message level authentication status
+ * - alertDisabled - alerting status
+ * - reserved - skip 2 bits
+ * - privLimit - channel privilege limit
+ * - reserved - skip 4 bits
+ * */
+ipmi ::RspType<uint3_t, // access mode,
+ bool, // user authentication status,
+ bool, // message authentication status,
+ bool, // alerting status,
+ uint2_t, // reserved,
+
+ uint4_t, // channel privilege,
+ uint4_t // reserved
+ >
+ ipmiGetChannelAccess(Context::ptr ctx, uint4_t channel, uint4_t reserved1,
+ uint6_t reserved2, uint2_t accessSetMode)
+{
+ if (reserved1 || reserved2)
+ {
+ lg2::debug("Get channel access - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ if ((types::enum_cast<EChannelActionType>(accessSetMode) == doNotSet) ||
+ (types::enum_cast<EChannelActionType>(accessSetMode) == reserved))
+ {
+ lg2::debug("Get channel access - Invalid Access mode");
+ return responseInvalidFieldRequest();
+ }
+
+ const uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+ if ((getChannelSessionSupport(chNum) == EChannelSessSupported::none) ||
+ (!isValidChannel(chNum)))
+ {
+ lg2::debug("Get channel access - No support on channel: {CHANNEL}",
+ "CHANNEL", chNum);
+ return response(ccActionNotSupportedForChannel);
+ }
+
+ ChannelAccess chAccess = {};
+
+ Cc compCode = ipmi::ccUnspecifiedError;
+
+ if (types::enum_cast<EChannelActionType>(accessSetMode) == nvData)
+ {
+ compCode = getChannelAccessPersistData(chNum, chAccess);
+ }
+ else if (types::enum_cast<EChannelActionType>(accessSetMode) == activeData)
+ {
+ compCode = getChannelAccessData(chNum, chAccess);
+ }
+
+ if (compCode != ccSuccess)
+ {
+ return response(compCode);
+ }
+
+ constexpr uint2_t reservedOut1 = 0;
+ constexpr uint4_t reservedOut2 = 0;
+
+ return responseSuccess(
+ types::enum_cast<uint3_t>(chAccess.accessMode),
+ chAccess.userAuthDisabled, chAccess.perMsgAuthDisabled,
+ chAccess.alertingDisabled, reservedOut1,
+ types::enum_cast<uint4_t>(chAccess.privLimit), reservedOut2);
+}
+
+/** @brief implements the get channel info command
+ * @ param ctx - context pointer
+ * @ param channel - channel number
+ * @ param reserved - skip 4 bits
+ *
+ * @returns ipmi completion code plus response data
+ * - chNum - the channel number for this request
+ * - mediumType - see Table 6-3, Channel Medium Type Numbers
+ * - protocolType - Table 6-2, Channel Protocol Type Numbers
+ * - activeSessionCount - number of active sessions
+ * - sessionType - channel support for sessions
+ * - vendorId - vendor for this channel protocol (IPMI - 7154)
+ * - auxChInfo - auxiliary info for channel
+ * */
+RspType<uint4_t, // chNum
+ uint4_t, // reserved
+ uint7_t, // mediumType
+ bool, // reserved
+ uint5_t, // protocolType
+ uint3_t, // reserved
+ uint6_t, // activeSessionCount
+ uint2_t, // sessionType
+ uint24_t, // Vendor IANA
+ uint16_t // aux info
+ >
+ ipmiGetChannelInfo(Context::ptr ctx, uint4_t channel, uint4_t reserved)
+{
+ if (reserved)
+ {
+ lg2::debug("Get channel access - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Get channel Info - No support on channel: {CHANNEL}",
+ "CHANNEL", chNum);
+ return responseInvalidFieldRequest();
+ }
+
+ ChannelInfo chInfo;
+ Cc compCode = getChannelInfo(chNum, chInfo);
+ if (compCode != ccSuccess)
+ {
+ lg2::error("Failed to get channel info, channel: {CHANNEL}, "
+ "errno: {ERRNO}",
+ "CHANNEL", chNum, "ERRNO", compCode);
+ return response(compCode);
+ }
+
+ constexpr uint4_t reserved1 = 0;
+ constexpr bool reserved2 = false;
+ constexpr uint3_t reserved3 = 0;
+ uint8_t mediumType = chInfo.mediumType;
+ uint8_t protocolType = chInfo.protocolType;
+ uint2_t sessionType = chInfo.sessionSupported;
+ uint6_t activeSessionCount = getChannelActiveSessions(chNum);
+ // IPMI Spec: The IPMI Enterprise Number is: 7154 (decimal)
+ constexpr uint24_t vendorId = 7154;
+ constexpr uint16_t auxChInfo = 0;
+
+ return responseSuccess(chNum, reserved1, mediumType, reserved2,
+ protocolType, reserved3, activeSessionCount,
+ sessionType, vendorId, auxChInfo);
+}
+
+namespace
+{
+constexpr uint16_t standardPayloadBit(PayloadType p)
+{
+ return (1 << static_cast<size_t>(p));
+}
+
+constexpr uint16_t sessionPayloadBit(PayloadType p)
+{
+ constexpr size_t sessionShift =
+ static_cast<size_t>(PayloadType::OPEN_SESSION_REQUEST);
+ return ((1 << static_cast<size_t>(p)) >> sessionShift);
+}
+} // namespace
+
+/** @brief implements get channel payload support command
+ * @ param ctx - ipmi context pointer
+ * @ param chNum - channel number
+ * @ param reserved - skip 4 bits
+ *
+ * @ returns IPMI completion code plus response data
+ * - stdPayloadType - bitmask of supported standard payload types
+ * - sessSetupPayloadType - bitmask of supported session setup payload types
+ * - OEMPayloadType - bitmask of supported OEM payload types
+ * - reserved - 2 bytes of 0
+ **/
+RspType<uint16_t, // stdPayloadType
+ uint16_t, // sessSetupPayloadType
+ uint16_t, // OEMPayloadType
+ uint16_t // reserved
+ >
+ ipmiGetChannelPayloadSupport(Context::ptr ctx, uint4_t channel,
+ uint4_t reserved)
+{
+ uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+ if (!doesDeviceExist(chNum) || !isValidChannel(chNum) || reserved)
+ {
+ lg2::debug("Get channel payload - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ // Session support is available in active LAN channels.
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Get channel payload - No support on channel");
+ return response(ccActionNotSupportedForChannel);
+ }
+ constexpr uint16_t stdPayloadType = standardPayloadBit(PayloadType::IPMI) |
+ standardPayloadBit(PayloadType::SOL);
+ constexpr uint16_t sessSetupPayloadType =
+ sessionPayloadBit(PayloadType::OPEN_SESSION_REQUEST) |
+ sessionPayloadBit(PayloadType::OPEN_SESSION_RESPONSE) |
+ sessionPayloadBit(PayloadType::RAKP1) |
+ sessionPayloadBit(PayloadType::RAKP2) |
+ sessionPayloadBit(PayloadType::RAKP3) |
+ sessionPayloadBit(PayloadType::RAKP4);
+ constexpr uint16_t OEMPayloadType = 0;
+ constexpr uint16_t rspRsvd = 0;
+ return responseSuccess(stdPayloadType, sessSetupPayloadType, OEMPayloadType,
+ rspRsvd);
+}
+
+/** @brief implements the get channel payload version command
+ * @param ctx - IPMI context pointer (for channel)
+ * @param chNum - channel number to get info about
+ * @param reserved - skip 4 bits
+ * @param payloadTypeNum - to get payload type info
+
+ * @returns IPMI completion code plus response data
+ * - formatVersion - BCD encoded format version info
+ */
+
+RspType<uint8_t> // formatVersion
+ ipmiGetChannelPayloadVersion(Context::ptr ctx, uint4_t chNum,
+ uint4_t reserved, uint8_t payloadTypeNum)
+{
+ uint8_t channel =
+ convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx->channel);
+ constexpr uint8_t payloadTypeNotSupported = 0x80;
+
+ if (reserved || !isValidChannel(channel))
+ {
+ lg2::debug("Get channel payload version - Invalid field in request");
+ return responseInvalidFieldRequest();
+ }
+
+ if (getChannelSessionSupport(channel) == EChannelSessSupported::none)
+ {
+ lg2::debug("Get channel payload version - No support on channel");
+ return response(payloadTypeNotSupported);
+ }
+
+ if (!isValidPayloadType(static_cast<PayloadType>(payloadTypeNum)))
+ {
+ lg2::error("Get channel payload version - Payload type unavailable");
+
+ return response(payloadTypeNotSupported);
+ }
+
+ // BCD encoded version representation - 1.0
+ constexpr uint8_t formatVersion = 0x10;
+
+ return responseSuccess(formatVersion);
+}
+
+void registerChannelFunctions() __attribute__((constructor));
+void registerChannelFunctions()
+{
+ ipmiChannelInit();
+
+ registerHandler(prioOpenBmcBase, netFnApp, app::cmdSetChannelAccess,
+ Privilege::Admin, ipmiSetChannelAccess);
+
+ registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelAccess,
+ Privilege::User, ipmiGetChannelAccess);
+
+ registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelInfoCommand,
+ Privilege::User, ipmiGetChannelInfo);
+
+ registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadSupport,
+ Privilege::User, ipmiGetChannelPayloadSupport);
+
+ registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadVersion,
+ Privilege::User, ipmiGetChannelPayloadVersion);
+}
+
+} // namespace ipmi
diff --git a/user_channel/cipher_mgmt.cpp b/user_channel/cipher_mgmt.cpp
new file mode 100644
index 0000000..c954f65
--- /dev/null
+++ b/user_channel/cipher_mgmt.cpp
@@ -0,0 +1,244 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "cipher_mgmt.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <filesystem>
+#include <fstream>
+
+namespace ipmi
+{
+
+using Json = nlohmann::json;
+namespace fs = std::filesystem;
+
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+ const std::string& csDefaultFileName)
+{
+ static CipherConfig cipherConfig(csFileName, csDefaultFileName);
+ return cipherConfig;
+}
+
+CipherConfig::CipherConfig(const std::string& csFileName,
+ const std::string& csDefaultFileName) :
+ cipherSuitePrivFileName(csFileName),
+ cipherSuiteDefaultPrivFileName(csDefaultFileName)
+{
+ loadCSPrivilegesToMap();
+}
+
+void CipherConfig::loadCSPrivilegesToMap()
+{
+ if (!fs::exists(cipherSuiteDefaultPrivFileName))
+ {
+ lg2::error("CS privilege levels default file does not exist...");
+ }
+ else
+ {
+ // read default privileges
+ Json data = readCSPrivilegeLevels(cipherSuiteDefaultPrivFileName);
+
+ // load default privileges
+ updateCSPrivilegesMap(data);
+
+ // check for user-saved privileges
+ if (fs::exists(cipherSuitePrivFileName))
+ {
+ data = readCSPrivilegeLevels(cipherSuitePrivFileName);
+ if (data != nullptr)
+ {
+ // update map with user-saved privileges by merging (overriding)
+ // values from the defaults
+ updateCSPrivilegesMap(data);
+ }
+ }
+ }
+}
+
+void CipherConfig::updateCSPrivilegesMap(const Json& jsonData)
+{
+ for (uint8_t chNum = 0; chNum < ipmi::maxIpmiChannels; chNum++)
+ {
+ std::string chKey = "Channel" + std::to_string(chNum);
+ for (uint8_t csNum = 0; csNum < maxCSRecords; csNum++)
+ {
+ auto csKey = "CipherID" + std::to_string(csNum);
+
+ if (jsonData.find(chKey) != jsonData.end())
+ {
+ csPrivilegeMap[{chNum, csNum}] = convertToPrivLimitIndex(
+ static_cast<std::string>(jsonData[chKey][csKey]));
+ }
+ }
+ }
+}
+
+Json CipherConfig::readCSPrivilegeLevels(const std::string& csFileName)
+{
+ std::ifstream jsonFile(csFileName);
+ if (!jsonFile.good())
+ {
+ lg2::error("JSON file not found");
+ return nullptr;
+ }
+
+ Json data = nullptr;
+ try
+ {
+ data = Json::parse(jsonFile, nullptr, false);
+ }
+ catch (const Json::parse_error& e)
+ {
+ lg2::error(
+ "Corrupted cipher suite privilege levels config file: {ERROR}",
+ "ERROR", e);
+ }
+
+ return data;
+}
+
+int CipherConfig::writeCSPrivilegeLevels(const Json& jsonData)
+{
+ std::string tmpFile =
+ static_cast<std::string>(cipherSuitePrivFileName) + "_tmpXXXXXX";
+
+ std::vector<char> tmpRandomFile(tmpFile.length() + 1);
+ strncpy(tmpRandomFile.data(), tmpFile.c_str(), tmpFile.length() + 1);
+
+ int fd = mkstemp(tmpRandomFile.data());
+ fchmod(fd, 0644);
+
+ if (fd < 0)
+ {
+ lg2::error("Error opening CS privilege level config file: {FILE_NAME}",
+ "FILE_NAME", tmpFile);
+ return -EIO;
+ }
+ const auto& writeData = jsonData.dump();
+ if (write(fd, writeData.c_str(), writeData.size()) !=
+ static_cast<ssize_t>(writeData.size()))
+ {
+ close(fd);
+ lg2::error("Error writing CS privilege level config file: {FILE_NAME}",
+ "FILE_NAME", tmpFile);
+ unlink(tmpRandomFile.data());
+ return -EIO;
+ }
+ close(fd);
+
+ if (std::rename(tmpRandomFile.data(), cipherSuitePrivFileName.c_str()))
+ {
+ lg2::error("Error renaming CS privilege level config file: {FILE_NAME}",
+ "FILE_NAME", tmpFile);
+ unlink(tmpRandomFile.data());
+ return -EIO;
+ }
+
+ return 0;
+}
+
+uint4_t CipherConfig::convertToPrivLimitIndex(const std::string& value)
+{
+ auto iter = std::find(ipmi::privList.begin(), ipmi::privList.end(), value);
+ if (iter == privList.end())
+ {
+ lg2::error("Invalid privilege: {PRIV_STR}", "PRIV_STR", value);
+ return ccUnspecifiedError;
+ }
+
+ return static_cast<uint4_t>(std::distance(ipmi::privList.begin(), iter));
+}
+
+std::string CipherConfig::convertToPrivLimitString(const uint4_t& value)
+{
+ return ipmi::privList.at(static_cast<size_t>(value));
+}
+
+ipmi::Cc CipherConfig::getCSPrivilegeLevels(
+ uint8_t chNum, std::array<uint4_t, maxCSRecords>& csPrivilegeLevels)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::error("Invalid channel number: {CHANNEL}", "CHANNEL", chNum);
+ return ccInvalidFieldRequest;
+ }
+
+ for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
+ {
+ csPrivilegeLevels[csNum] = csPrivilegeMap[{chNum, csNum}];
+ }
+ return ccSuccess;
+}
+
+ipmi::Cc CipherConfig::setCSPrivilegeLevels(
+ uint8_t chNum, const std::array<uint4_t, maxCSRecords>& requestData)
+{
+ if (!isValidChannel(chNum))
+ {
+ lg2::error("Invalid channel number: {CHANNEL}", "CHANNEL", chNum);
+ return ccInvalidFieldRequest;
+ }
+
+ Json jsonData;
+ if (!fs::exists(cipherSuitePrivFileName))
+ {
+ lg2::info("CS privilege levels user settings file does not "
+ "exist. Creating...");
+ }
+ else
+ {
+ jsonData = readCSPrivilegeLevels(cipherSuitePrivFileName);
+ if (jsonData == nullptr)
+ {
+ return ccUnspecifiedError;
+ }
+ }
+
+ Json privData;
+ std::string csKey;
+ constexpr auto privMaxValue = static_cast<uint8_t>(ipmi::Privilege::Oem);
+ for (size_t csNum = 0; csNum < maxCSRecords; ++csNum)
+ {
+ csKey = "CipherID" + std::to_string(csNum);
+ auto priv = static_cast<uint8_t>(requestData[csNum]);
+
+ if (priv > privMaxValue)
+ {
+ return ccInvalidFieldRequest;
+ }
+ privData[csKey] = convertToPrivLimitString(priv);
+ }
+
+ std::string chKey = "Channel" + std::to_string(chNum);
+ jsonData[chKey] = privData;
+
+ if (writeCSPrivilegeLevels(jsonData))
+ {
+ lg2::error("Error in setting CS Privilege Levels.");
+ return ccUnspecifiedError;
+ }
+
+ updateCSPrivilegesMap(jsonData);
+ return ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/user_channel/cipher_mgmt.hpp b/user_channel/cipher_mgmt.hpp
new file mode 100644
index 0000000..0bd6b1d
--- /dev/null
+++ b/user_channel/cipher_mgmt.hpp
@@ -0,0 +1,132 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include "channel_layer.hpp"
+
+#include <ipmid/api-types.hpp>
+#include <ipmid/message/types.hpp>
+#include <nlohmann/json.hpp>
+
+#include <map>
+
+namespace ipmi
+{
+static const std::string csPrivDefaultFileName =
+ "/usr/share/ipmi-providers/cs_privilege_levels.json";
+
+static const std::string csPrivFileName =
+ "/var/lib/ipmi/cs_privilege_levels.json";
+
+static const size_t maxCSRecords = 16;
+
+using ChannelNumCipherIDPair = std::pair<uint8_t, uint8_t>;
+using privMap = std::map<ChannelNumCipherIDPair, uint4_t>;
+
+/** @class CipherConfig
+ * @brief Class to provide cipher suite functionalities
+ */
+class CipherConfig
+{
+ public:
+ ~CipherConfig() = default;
+ explicit CipherConfig(const std::string& csFileName,
+ const std::string& csDefaultFileName);
+ CipherConfig() = delete;
+
+ /** @brief function to get cipher suite privileges from config file
+ *
+ * @param[in] chNum - channel number for which we want to get cipher suite
+ * privilege levels
+ *
+ * @param[in] csPrivilegeLevels - gets filled by cipher suite privilege
+ * levels
+ *
+ * @return 0 for success, non zero value for failure
+ */
+ ipmi::Cc getCSPrivilegeLevels(
+ uint8_t chNum, std::array<uint4_t, maxCSRecords>& csPrivilegeLevels);
+
+ /** @brief function to set/update cipher suite privileges in config file
+ *
+ * @param[in] chNum - channel number for which we want to update cipher
+ * suite privilege levels
+ *
+ * @param[in] csPrivilegeLevels - cipher suite privilege levels to update
+ * in config file
+ *
+ * @return 0 for success, non zero value for failure
+ */
+ ipmi::Cc setCSPrivilegeLevels(
+ uint8_t chNum,
+ const std::array<uint4_t, maxCSRecords>& csPrivilegeLevels);
+
+ private:
+ std::string cipherSuitePrivFileName, cipherSuiteDefaultPrivFileName;
+
+ privMap csPrivilegeMap;
+
+ /** @brief function to read json config file
+ *
+ * @return nlohmann::json object
+ */
+ nlohmann::json readCSPrivilegeLevels(const std::string& csFileName);
+
+ /** @brief function to write json config file
+ *
+ * @param[in] jsonData - json object
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int writeCSPrivilegeLevels(const nlohmann::json& jsonData);
+
+ /** @brief convert to cipher suite privilege from string to value
+ *
+ * @param[in] value - privilege value
+ *
+ * @return cipher suite privilege index
+ */
+ uint4_t convertToPrivLimitIndex(const std::string& value);
+
+ /** @brief function to convert privilege value to string
+ *
+ * @param[in] value - privilege value
+ *
+ * @return privilege in string
+ */
+ std::string convertToPrivLimitString(const uint4_t& value);
+
+ /** @brief function to load CS Privilege Levels from json file/files to map
+ *
+ */
+ void loadCSPrivilegesToMap();
+
+ /** @brief function to update CS privileges map from json object data,
+ * jsonData
+ *
+ */
+ void updateCSPrivilegesMap(const nlohmann::json& jsonData);
+};
+
+/** @brief function to create static CipherConfig object
+ *
+ * @param[in] csFileName - user setting cipher suite privilege file name
+ * @param[in] csDefaultFileName - default cipher suite privilege file name
+ *
+ * @return static CipherConfig object
+ */
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+ const std::string& csDefaultFileName);
+} // namespace ipmi
diff --git a/user_channel/file.hpp b/user_channel/file.hpp
new file mode 100644
index 0000000..5be3a18
--- /dev/null
+++ b/user_channel/file.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <stdio.h>
+
+#include <filesystem>
+namespace phosphor
+{
+namespace user
+{
+
+namespace fs = std::filesystem;
+
+/** @class File
+ * @brief Responsible for handling file pointer
+ * Needed by putspent(3)
+ */
+class File
+{
+ private:
+ /** @brief handler for operating on file */
+ FILE* fp = nullptr;
+
+ /** @brief File name. Needed in the case where the temp
+ * needs to be removed
+ */
+ const std::string& name;
+
+ /** @brief Should the file be removed at exit */
+ bool removeOnExit = false;
+
+ public:
+ File() = delete;
+ File(const File&) = delete;
+ File& operator=(const File&) = delete;
+ File(File&&) = delete;
+ File& operator=(File&&) = delete;
+
+ /** @brief Opens file and uses it to do file operation
+ *
+ * @param[in] name - File name
+ * @param[in] mode - File open mode
+ * @param[in] removeOnExit - File to be removed at exit or no
+ */
+ File(const std::string& name, const std::string& mode,
+ bool removeOnExit = false) : name(name), removeOnExit(removeOnExit)
+ {
+ fp = fopen(name.c_str(), mode.c_str());
+ }
+
+ /** @brief Opens file using provided file descriptor
+ *
+ * @param[in] fd - File descriptor
+ * @param[in] name - File name
+ * @param[in] mode - File open mode
+ * @param[in] removeOnExit - File to be removed at exit or no
+ */
+ File(int fd, const std::string& name, const std::string& mode,
+ bool removeOnExit = false) : name(name), removeOnExit(removeOnExit)
+ {
+ fp = fdopen(fd, mode.c_str());
+ }
+
+ ~File()
+ {
+ if (fp)
+ {
+ fclose(fp);
+ }
+
+ // Needed for exception safety
+ if (removeOnExit && fs::exists(name))
+ {
+ fs::remove(name);
+ }
+ }
+
+ auto operator()()
+ {
+ return fp;
+ }
+};
+
+} // namespace user
+} // namespace phosphor
diff --git a/user_channel/meson.build b/user_channel/meson.build
new file mode 100644
index 0000000..701c86a
--- /dev/null
+++ b/user_channel/meson.build
@@ -0,0 +1,101 @@
+user_channel_inc = include_directories('.')
+
+channellayer_pre = declare_dependency(
+ include_directories: [root_inc, user_channel_inc],
+ dependencies: [
+ crypto,
+ ipmid_dep,
+ libsystemd_dep,
+ nlohmann_json_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ ],
+)
+
+channellayer_src = ['channel_layer.cpp', 'channel_mgmt.cpp', 'cipher_mgmt.cpp']
+
+channellayer_lib = library(
+ 'channellayer',
+ channellayer_src,
+ implicit_include_directories: false,
+ dependencies: channellayer_pre,
+ version: meson.project_version(),
+ install: true,
+ install_dir: get_option('libdir'),
+ override_options: ['b_lundef=false'],
+)
+
+channellayer_dep = declare_dependency(
+ link_with: channellayer_lib,
+ dependencies: channellayer_pre,
+)
+
+import('pkgconfig').generate(
+ channellayer_lib,
+ name: 'libchannellayer',
+ version: meson.project_version(),
+ description: 'libchannellayer',
+)
+
+if get_option('libuserlayer').allowed()
+ userlayer_pre = declare_dependency(
+ include_directories: [root_inc, user_channel_inc],
+ dependencies: [
+ channellayer_dep,
+ crypto,
+ ipmid_dep,
+ libsystemd_dep,
+ nlohmann_json_dep,
+ pam,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ ],
+ )
+
+ userlayer_src = ['user_layer.cpp', 'user_mgmt.cpp', 'passwd_mgr.cpp']
+
+ userlayer_lib = library(
+ 'userlayer',
+ userlayer_src,
+ implicit_include_directories: false,
+ dependencies: userlayer_pre,
+ version: meson.project_version(),
+ install: true,
+ install_dir: get_option('libdir'),
+ override_options: ['b_lundef=false'],
+ )
+
+ userlayer_dep = declare_dependency(
+ link_with: userlayer_lib,
+ dependencies: userlayer_pre,
+ )
+
+ usercmds_pre = declare_dependency(
+ include_directories: [root_inc, user_channel_inc],
+ dependencies: [
+ phosphor_logging_dep,
+ ipmid_dep,
+ userlayer_dep,
+ channellayer_dep,
+ ],
+ )
+
+ usercmds_lib = library(
+ 'usercmds',
+ 'usercommands.cpp',
+ implicit_include_directories: false,
+ dependencies: usercmds_pre,
+ install: true,
+ install_dir: get_option('libdir') / 'ipmid-providers',
+ version: meson.project_version(),
+ override_options: ['b_lundef=false'],
+ )
+
+ import('pkgconfig').generate(
+ userlayer_lib,
+ name: 'libuserlayer',
+ version: meson.project_version(),
+ description: 'libuserlayer',
+ )
+
+endif
diff --git a/user_channel/passwd_mgr.cpp b/user_channel/passwd_mgr.cpp
new file mode 100644
index 0000000..929a83c
--- /dev/null
+++ b/user_channel/passwd_mgr.cpp
@@ -0,0 +1,603 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "passwd_mgr.hpp"
+
+#include "file.hpp"
+#include "shadowlock.hpp"
+
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+
+namespace ipmi
+{
+
+static const char* passwdFileName = "/etc/ipmi_pass";
+static const char* encryptKeyFileName = "/etc/key_file";
+static const size_t maxKeySize = 8;
+
+constexpr mode_t modeMask =
+ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
+
+#define META_PASSWD_SIG "=OPENBMC="
+
+/*
+ * Meta data struct for encrypted password file
+ */
+struct MetaPassStruct
+{
+ char signature[10];
+ unsigned char reseved[2];
+ size_t hashSize;
+ size_t ivSize;
+ size_t dataSize;
+ size_t padSize;
+ size_t macSize;
+};
+
+PasswdMgr::PasswdMgr()
+{
+ restrictFilesPermission();
+ initPasswordMap();
+}
+
+void PasswdMgr::restrictFilesPermission(void)
+{
+ struct stat st = {};
+ // Restrict file permission to owner read & write
+ if (stat(passwdFileName, &st) == 0)
+ {
+ if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
+ {
+ if (chmod(passwdFileName, S_IRUSR | S_IWUSR) == -1)
+ {
+ lg2::debug("Error setting chmod for ipmi_pass file");
+ }
+ }
+ }
+
+ if (stat(encryptKeyFileName, &st) == 0)
+ {
+ if ((st.st_mode & modeMask) != (S_IRUSR | S_IWUSR))
+ {
+ if (chmod(encryptKeyFileName, S_IRUSR | S_IWUSR) == -1)
+ {
+ lg2::debug("Error setting chmod for ipmi_pass file");
+ }
+ }
+ }
+}
+
+SecureString PasswdMgr::getPasswdByUserName(const std::string& userName)
+{
+ checkAndReload();
+ auto iter = passwdMapList.find(userName);
+ if (iter == passwdMapList.end())
+ {
+ return SecureString();
+ }
+ return iter->second;
+}
+
+int PasswdMgr::updateUserEntry(const std::string& userName,
+ const std::string& newUserName)
+{
+ std::time_t updatedTime = getUpdatedFileTime();
+ // Check file time stamp to know passwdMapList is up-to-date.
+ // If not up-to-date, then updatePasswdSpecialFile will read and
+ // check the user entry existance.
+ if (fileLastUpdatedTime == updatedTime && updatedTime != -EIO)
+ {
+ if (passwdMapList.find(userName) == passwdMapList.end())
+ {
+ lg2::debug("User not found");
+ return 0;
+ }
+ }
+
+ // Write passwdMap to Encryted file
+ if (updatePasswdSpecialFile(userName, newUserName) != 0)
+ {
+ lg2::debug("Passwd file update failed");
+ return -EIO;
+ }
+
+ lg2::debug("Passwd file updated successfully");
+ return 0;
+}
+
+void PasswdMgr::checkAndReload(void)
+{
+ std::time_t updatedTime = getUpdatedFileTime();
+ if (fileLastUpdatedTime != updatedTime && updatedTime != -1)
+ {
+ lg2::debug("Reloading password map list");
+ passwdMapList.clear();
+ initPasswordMap();
+ }
+}
+
+int PasswdMgr::encryptDecryptData(
+ bool doEncrypt, const EVP_CIPHER* cipher, uint8_t* key, size_t keyLen,
+ uint8_t* iv, size_t ivLen, uint8_t* inBytes, size_t inBytesLen,
+ uint8_t* mac, size_t* macLen, unsigned char* outBytes, size_t* outBytesLen)
+{
+ if (cipher == nullptr || key == nullptr || iv == nullptr ||
+ inBytes == nullptr || outBytes == nullptr || mac == nullptr ||
+ inBytesLen == 0 || (size_t)EVP_CIPHER_key_length(cipher) > keyLen ||
+ (size_t)EVP_CIPHER_iv_length(cipher) > ivLen)
+ {
+ lg2::debug("Error Invalid Inputs");
+ return -EINVAL;
+ }
+
+ if (!doEncrypt)
+ {
+ // verify MAC before decrypting the data.
+ std::array<uint8_t, EVP_MAX_MD_SIZE> calMac;
+ size_t calMacLen = calMac.size();
+ // calculate MAC for the encrypted message.
+ if (nullptr ==
+ HMAC(EVP_sha256(), key, keyLen, inBytes, inBytesLen, calMac.data(),
+ reinterpret_cast<unsigned int*>(&calMacLen)))
+ {
+ lg2::debug("Error: Failed to calculate MAC");
+ return -EIO;
+ }
+ if (!((calMacLen == *macLen) &&
+ (std::memcmp(calMac.data(), mac, calMacLen) == 0)))
+ {
+ lg2::debug("Authenticated message doesn't match");
+ return -EBADMSG;
+ }
+ }
+
+ std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ctx(
+ EVP_CIPHER_CTX_new(), ::EVP_CIPHER_CTX_free);
+
+ if (!ctx)
+ {
+ lg2::debug("Error: EVP_CIPHER_CTX is NULL");
+ return -ENOMEM;
+ }
+
+ EVP_CIPHER_CTX_set_padding(ctx.get(), 1);
+
+ // Set key & IV
+ int retval = EVP_CipherInit_ex(ctx.get(), cipher, nullptr, key, iv,
+ static_cast<int>(doEncrypt));
+ if (!retval)
+ {
+ lg2::debug("EVP_CipherInit_ex failed: {RET_VAL}", "RET_VAL", retval);
+ return -EIO;
+ }
+
+ int outLen = 0, outEVPLen = 0;
+ if ((retval = EVP_CipherUpdate(ctx.get(), outBytes + outLen, &outEVPLen,
+ inBytes, inBytesLen)))
+ {
+ outLen += outEVPLen;
+ if ((retval =
+ EVP_CipherFinal(ctx.get(), outBytes + outLen, &outEVPLen)))
+ {
+ outLen += outEVPLen;
+ *outBytesLen = outLen;
+ }
+ else
+ {
+ lg2::debug("EVP_CipherFinal fails: {RET_VAL}", "RET_VAL", retval);
+ return -EIO;
+ }
+ }
+ else
+ {
+ lg2::debug("EVP_CipherUpdate fails: {RET_VAL}", "RET_VAL", retval);
+ return -EIO;
+ }
+
+ if (doEncrypt)
+ {
+ // Create MAC for the encrypted message
+ if (nullptr == HMAC(EVP_sha256(), key, keyLen, outBytes, *outBytesLen,
+ mac, reinterpret_cast<unsigned int*>(macLen)))
+ {
+ lg2::debug("Failed to create authentication");
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+void PasswdMgr::initPasswordMap(void)
+{
+ // TODO phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
+ SecureString dataBuf;
+
+ if (readPasswdFileData(dataBuf) != 0)
+ {
+ lg2::debug("Error in reading the encrypted pass file");
+ return;
+ }
+
+ if (dataBuf.size() != 0)
+ {
+ // populate the user list with password
+ char* outPtr = dataBuf.data();
+ char* nToken = nullptr;
+ char* linePtr = strtok_r(outPtr, "\n", &nToken);
+ size_t lineSize = 0;
+ while (linePtr != nullptr)
+ {
+ size_t userEPos = 0;
+ SecureString lineStr(linePtr);
+ if ((userEPos = lineStr.find(":")) != std::string::npos)
+ {
+ lineSize = lineStr.size();
+ passwdMapList.emplace(
+ lineStr.substr(0, userEPos),
+ lineStr.substr(userEPos + 1, lineSize - (userEPos + 1)));
+ }
+ linePtr = strtok_r(nullptr, "\n", &nToken);
+ }
+ }
+
+ // Update the timestamp
+ fileLastUpdatedTime = getUpdatedFileTime();
+ return;
+}
+
+int PasswdMgr::readPasswdFileData(SecureString& outBytes)
+{
+ std::array<uint8_t, maxKeySize> keyBuff;
+ std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
+ if (!keyFile.is_open())
+ {
+ lg2::debug("Error in opening encryption key file");
+ return -EIO;
+ }
+ keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
+ if (keyFile.fail())
+ {
+ lg2::debug("Error in reading encryption key file");
+ return -EIO;
+ }
+
+ std::ifstream passwdFile(passwdFileName, std::ios::in | std::ios::binary);
+ if (!passwdFile.is_open())
+ {
+ lg2::debug("Error in opening ipmi password file");
+ return -EIO;
+ }
+
+ // calculate file size and read the data
+ passwdFile.seekg(0, std::ios::end);
+ ssize_t fileSize = passwdFile.tellg();
+ passwdFile.seekg(0, std::ios::beg);
+ std::vector<uint8_t> input(fileSize);
+ passwdFile.read(reinterpret_cast<char*>(input.data()), fileSize);
+ if (passwdFile.fail())
+ {
+ lg2::debug("Error in reading encryption key file");
+ return -EIO;
+ }
+
+ // verify the signature first
+ MetaPassStruct* metaData = reinterpret_cast<MetaPassStruct*>(input.data());
+ if (std::strncmp(metaData->signature, META_PASSWD_SIG,
+ sizeof(metaData->signature)))
+ {
+ lg2::debug("Error signature mismatch in password file");
+ return -EBADMSG;
+ }
+
+ size_t inBytesLen = metaData->dataSize + metaData->padSize;
+ // If data is empty i.e no password map then return success
+ if (inBytesLen == 0)
+ {
+ lg2::debug("Empty password file");
+ return 0;
+ }
+
+ // compute the key needed to decrypt
+ std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
+ auto keyLen = key.size();
+ if (nullptr == HMAC(EVP_sha256(), keyBuff.data(), keyBuff.size(),
+ input.data() + sizeof(*metaData), metaData->hashSize,
+ key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
+ {
+ lg2::debug("Failed to create MAC for authentication");
+ return -EIO;
+ }
+
+ // decrypt the data
+ uint8_t* iv = input.data() + sizeof(*metaData) + metaData->hashSize;
+ size_t ivLen = metaData->ivSize;
+ uint8_t* inBytes = iv + ivLen;
+ uint8_t* mac = inBytes + inBytesLen;
+ size_t macLen = metaData->macSize;
+
+ size_t outBytesLen = 0;
+ // Resize to actual data size
+ outBytes.resize(inBytesLen + EVP_MAX_BLOCK_LENGTH, '\0');
+ if (encryptDecryptData(false, EVP_aes_128_cbc(), key.data(), keyLen, iv,
+ ivLen, inBytes, inBytesLen, mac, &macLen,
+ reinterpret_cast<unsigned char*>(outBytes.data()),
+ &outBytesLen) != 0)
+ {
+ lg2::debug("Error in decryption");
+ return -EIO;
+ }
+ // Resize the vector to outBytesLen
+ outBytes.resize(outBytesLen);
+
+ OPENSSL_cleanse(key.data(), keyLen);
+ OPENSSL_cleanse(iv, ivLen);
+
+ return 0;
+}
+
+int PasswdMgr::updatePasswdSpecialFile(const std::string& userName,
+ const std::string& newUserName)
+{
+ // TODO phosphor-host-ipmid#170 phosphor::user::shadow::Lock lock{};
+
+ size_t bytesWritten = 0;
+ size_t inBytesLen = 0;
+ size_t isUsrFound = false;
+ const EVP_CIPHER* cipher = EVP_aes_128_cbc();
+ SecureString dataBuf;
+
+ // Read the encrypted file and get the file data
+ // Check user existance and return if not exist.
+ if (readPasswdFileData(dataBuf) != 0)
+ {
+ lg2::debug("Error in reading the encrypted pass file");
+ return -EIO;
+ }
+
+ if (dataBuf.size() != 0)
+ {
+ inBytesLen = dataBuf.size() + newUserName.size() +
+ EVP_CIPHER_block_size(cipher);
+ }
+
+ SecureString inBytes(inBytesLen, '\0');
+ if (inBytesLen != 0)
+ {
+ char* outPtr = reinterpret_cast<char*>(dataBuf.data());
+ char* nToken = nullptr;
+ char* linePtr = strtok_r(outPtr, "\n", &nToken);
+ while (linePtr != nullptr)
+ {
+ size_t userEPos = 0;
+
+ SecureString lineStr(linePtr);
+ if ((userEPos = lineStr.find(":")) != std::string::npos)
+ {
+ if (userName.compare(lineStr.substr(0, userEPos)) == 0)
+ {
+ isUsrFound = true;
+ if (!newUserName.empty())
+ {
+ bytesWritten += std::snprintf(
+ &inBytes[0] + bytesWritten,
+ (inBytesLen - bytesWritten), "%s%s\n",
+ newUserName.c_str(),
+ lineStr.substr(userEPos, lineStr.size()).data());
+ }
+ }
+ else
+ {
+ bytesWritten += std::snprintf(&inBytes[0] + bytesWritten,
+ (inBytesLen - bytesWritten),
+ "%s\n", lineStr.data());
+ }
+ }
+ linePtr = strtok_r(nullptr, "\n", &nToken);
+ }
+ inBytesLen = bytesWritten;
+ }
+ if (!isUsrFound)
+ {
+ lg2::debug("User doesn't exist");
+ return 0;
+ }
+
+ // Read the key buff from key file
+ std::array<uint8_t, maxKeySize> keyBuff;
+ std::ifstream keyFile(encryptKeyFileName, std::ios::in | std::ios::binary);
+ if (!keyFile.good())
+ {
+ lg2::debug("Error in opening encryption key file");
+ return -EIO;
+ }
+ keyFile.read(reinterpret_cast<char*>(keyBuff.data()), keyBuff.size());
+ if (keyFile.fail())
+ {
+ lg2::debug("Error in reading encryption key file");
+ return -EIO;
+ }
+ keyFile.close();
+
+ // Read the original passwd file mode
+ struct stat st = {};
+ if (stat(passwdFileName, &st) != 0)
+ {
+ lg2::debug("Error in getting password file fstat()");
+ return -EIO;
+ }
+
+ // Create temporary file for write
+ std::string pwdFile(passwdFileName);
+ std::vector<char> tempFileName(pwdFile.begin(), pwdFile.end());
+ std::vector<char> fileTemplate = {'_', '_', 'X', 'X', 'X',
+ 'X', 'X', 'X', '\0'};
+ tempFileName.insert(tempFileName.end(), fileTemplate.begin(),
+ fileTemplate.end());
+ int fd = mkstemp((char*)tempFileName.data());
+ if (fd == -1)
+ {
+ lg2::debug("Error creating temp file");
+ return -EIO;
+ }
+
+ std::string strTempFileName(tempFileName.data());
+ // Open the temp file for writing from provided fd
+ // By "true", remove it at exit if still there.
+ // This is needed to cleanup the temp file at exception
+ phosphor::user::File temp(fd, strTempFileName, "w", true);
+ if ((temp)() == nullptr)
+ {
+ close(fd);
+ lg2::debug("Error creating temp file");
+ return -EIO;
+ }
+
+ // Set the file mode as read-write for owner only
+ if (fchmod(fileno((temp)()), S_IRUSR | S_IWUSR) < 0)
+ {
+ lg2::debug("Error setting fchmod for temp file");
+ return -EIO;
+ }
+
+ const EVP_MD* digest = EVP_sha256();
+ size_t hashLen = EVP_MD_block_size(digest);
+ std::vector<uint8_t> hash(hashLen);
+ size_t ivLen = EVP_CIPHER_iv_length(cipher);
+ std::vector<uint8_t> iv(ivLen);
+ std::array<uint8_t, EVP_MAX_KEY_LENGTH> key;
+ size_t keyLen = key.size();
+ std::array<uint8_t, EVP_MAX_MD_SIZE> mac;
+ size_t macLen = mac.size();
+
+ // Create random hash and generate hash key which will be used for
+ // encryption.
+ if (RAND_bytes(hash.data(), hashLen) != 1)
+ {
+ lg2::debug("Hash genertion failed, bailing out");
+ return -EIO;
+ }
+ if (nullptr ==
+ HMAC(digest, keyBuff.data(), keyBuff.size(), hash.data(), hashLen,
+ key.data(), reinterpret_cast<unsigned int*>(&keyLen)))
+ {
+ lg2::debug("Failed to create MAC for authentication");
+ return -EIO;
+ }
+
+ // Generate IV values
+ if (RAND_bytes(iv.data(), ivLen) != 1)
+ {
+ lg2::debug("UV genertion failed, bailing out");
+ return -EIO;
+ }
+
+ // Encrypt the input data
+ std::vector<uint8_t> outBytes(inBytesLen + EVP_MAX_BLOCK_LENGTH);
+ size_t outBytesLen = 0;
+ if (inBytesLen != 0)
+ {
+ if (encryptDecryptData(
+ true, EVP_aes_128_cbc(), key.data(), keyLen, iv.data(), ivLen,
+ reinterpret_cast<unsigned char*>(inBytes.data()), inBytesLen,
+ mac.data(), &macLen, outBytes.data(), &outBytesLen) != 0)
+ {
+ lg2::debug("Error while encrypting the data");
+ return -EIO;
+ }
+ outBytes[outBytesLen] = 0;
+ }
+ OPENSSL_cleanse(key.data(), keyLen);
+
+ // Update the meta password structure.
+ MetaPassStruct metaData = {META_PASSWD_SIG, {0, 0}, 0, 0, 0, 0, 0};
+ metaData.hashSize = hashLen;
+ metaData.ivSize = ivLen;
+ metaData.dataSize = bytesWritten;
+ metaData.padSize = outBytesLen - bytesWritten;
+ metaData.macSize = macLen;
+
+ if (fwrite(&metaData, 1, sizeof(metaData), (temp)()) != sizeof(metaData))
+ {
+ lg2::debug("Error in writing meta data");
+ return -EIO;
+ }
+
+ if (fwrite(&hash[0], 1, hashLen, (temp)()) != hashLen)
+ {
+ lg2::debug("Error in writing hash data");
+ return -EIO;
+ }
+
+ if (fwrite(&iv[0], 1, ivLen, (temp)()) != ivLen)
+ {
+ lg2::debug("Error in writing IV data");
+ return -EIO;
+ }
+
+ if (fwrite(&outBytes[0], 1, outBytesLen, (temp)()) != outBytesLen)
+ {
+ lg2::debug("Error in writing encrypted data");
+ return -EIO;
+ }
+
+ if (fwrite(&mac[0], 1, macLen, (temp)()) != macLen)
+ {
+ lg2::debug("Error in writing MAC data");
+ return -EIO;
+ }
+
+ if (fflush((temp)()))
+ {
+ lg2::debug("File fflush error while writing entries to special file");
+ return -EIO;
+ }
+
+ OPENSSL_cleanse(iv.data(), ivLen);
+
+ // Rename the tmp file to actual file
+ if (std::rename(strTempFileName.data(), passwdFileName) != 0)
+ {
+ lg2::debug("Failed to rename tmp file to ipmi-pass");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+std::time_t PasswdMgr::getUpdatedFileTime()
+{
+ struct stat fileStat = {};
+ if (stat(passwdFileName, &fileStat) != 0)
+ {
+ lg2::debug("Error - Getting passwd file time stamp");
+ return -EIO;
+ }
+ return fileStat.st_mtime;
+}
+
+} // namespace ipmi
diff --git a/user_channel/passwd_mgr.hpp b/user_channel/passwd_mgr.hpp
new file mode 100644
index 0000000..8932ac5
--- /dev/null
+++ b/user_channel/passwd_mgr.hpp
@@ -0,0 +1,133 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <openssl/evp.h>
+
+#include <ipmid/types.hpp>
+
+#include <ctime>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace ipmi
+{
+
+class PasswdMgr
+{
+ public:
+ ~PasswdMgr() = default;
+ PasswdMgr(const PasswdMgr&) = delete;
+ PasswdMgr& operator=(const PasswdMgr&) = delete;
+ PasswdMgr(PasswdMgr&&) = delete;
+ PasswdMgr& operator=(PasswdMgr&&) = delete;
+
+ /** @brief Constructs user password list
+ *
+ */
+ PasswdMgr();
+
+ /** @brief Get password for the user
+ *
+ * @param[in] userName - user name
+ *
+ * @return password string. will return empty string, if unable to locate
+ * the user
+ */
+ SecureString getPasswdByUserName(const std::string& userName);
+
+ /** @brief Update / clear username and password entry for the specified
+ * user
+ *
+ * @param[in] userName - user name that has to be renamed / deleted
+ * @param[in] newUserName - new user name. If empty, userName will be
+ * deleted.
+ *
+ * @return error response
+ */
+ int updateUserEntry(const std::string& userName,
+ const std::string& newUserName);
+
+ private:
+ using UserName = std::string;
+ using Password = SecureString;
+ std::unordered_map<UserName, Password> passwdMapList;
+ std::time_t fileLastUpdatedTime;
+
+ /** @brief restrict file permission
+ *
+ */
+ void restrictFilesPermission(void);
+ /** @brief check timestamp and reload password map if required
+ *
+ */
+ void checkAndReload(void);
+ /** @brief initializes passwdMapList by reading the encrypted file
+ *
+ * Initializes the passwordMapList members after decrypting the
+ * password file. passwordMapList will be used further in IPMI
+ * authentication.
+ */
+ void initPasswordMap(void);
+
+ /** @brief Function to read the encrypted password file data
+ *
+ * @param[out] outBytes - vector to hold decrypted password file data
+ *
+ * @return error response
+ */
+ int readPasswdFileData(SecureString& outBytes);
+ /** @brief Updates special password file by clearing the password entry
+ * for the user specified.
+ *
+ * @param[in] userName - user name that has to be renamed / deleted
+ * @param[in] newUserName - new user name. If empty, userName will be
+ * deleted.
+ *
+ * @return error response
+ */
+ int updatePasswdSpecialFile(const std::string& userName,
+ const std::string& newUserName);
+ /** @brief encrypts or decrypt the data provided
+ *
+ * @param[in] doEncrypt - do encrypt if set to true, else do decrypt.
+ * @param[in] cipher - cipher to be used
+ * @param[in] key - pointer to the key
+ * @param[in] keyLen - Length of the key to be used
+ * @param[in] iv - pointer to initialization vector
+ * @param[in] ivLen - Length of the iv
+ * @param[in] inBytes - input data to be encrypted / decrypted
+ * @param[in] inBytesLen - input size to be encrypted / decrypted
+ * @param[in] mac - message authentication code - to figure out corruption
+ * @param[in] macLen - size of MAC
+ * @param[in] outBytes - ptr to store output bytes
+ * @param[in] outBytesLen - outbut data length.
+ *
+ * @return error response
+ */
+ int encryptDecryptData(
+ bool doEncrypt, const EVP_CIPHER* cipher, uint8_t* key, size_t keyLen,
+ uint8_t* iv, size_t ivLen, uint8_t* inBytes, size_t inBytesLen,
+ uint8_t* mac, size_t* macLen, uint8_t* outBytes, size_t* outBytesLen);
+
+ /** @brief returns updated file time of passwd file entry.
+ *
+ * @return timestamp or -1 for error.
+ */
+ std::time_t getUpdatedFileTime();
+};
+
+} // namespace ipmi
diff --git a/user_channel/shadowlock.hpp b/user_channel/shadowlock.hpp
new file mode 100644
index 0000000..1dc5f3b
--- /dev/null
+++ b/user_channel/shadowlock.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <shadow.h>
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+namespace phosphor
+{
+namespace user
+{
+namespace shadow
+{
+
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+using namespace phosphor::logging;
+
+/** @class Lock
+ * @brief Responsible for locking and unlocking /etc/shadow
+ */
+class Lock
+{
+ public:
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ Lock(Lock&&) = delete;
+ Lock& operator=(Lock&&) = delete;
+
+ /** @brief Default constructor that just locks the shadow file */
+ Lock()
+ {
+ if (!lckpwdf())
+ {
+ lg2::error("Locking Shadow failed");
+ elog<InternalFailure>();
+ }
+ }
+ ~Lock()
+ {
+ if (!ulckpwdf())
+ {
+ lg2::error("Un-Locking Shadow failed");
+ elog<InternalFailure>();
+ }
+ }
+};
+
+} // namespace shadow
+} // namespace user
+} // namespace phosphor
diff --git a/user_channel/user_layer.cpp b/user_channel/user_layer.cpp
new file mode 100644
index 0000000..5a2e7e0
--- /dev/null
+++ b/user_channel/user_layer.cpp
@@ -0,0 +1,226 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "user_layer.hpp"
+
+#include "passwd_mgr.hpp"
+#include "user_mgmt.hpp"
+
+namespace
+{
+ipmi::PasswdMgr passwdMgr;
+}
+
+namespace ipmi
+{
+
+Cc ipmiUserInit()
+{
+ getUserAccessObject();
+ return ccSuccess;
+}
+
+SecureString ipmiUserGetPassword(const std::string& userName)
+{
+ return passwdMgr.getPasswdByUserName(userName);
+}
+
+Cc ipmiClearUserEntryPassword(const std::string& userName)
+{
+ if (passwdMgr.updateUserEntry(userName, "") != 0)
+ {
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+Cc ipmiRenameUserEntryPassword(const std::string& userName,
+ const std::string& newUserName)
+{
+ if (passwdMgr.updateUserEntry(userName, newUserName) != 0)
+ {
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+bool ipmiUserIsValidUserId(const uint8_t userId)
+{
+ return UserAccess::isValidUserId(userId);
+}
+
+bool ipmiUserIsValidPrivilege(const uint8_t priv)
+{
+ return UserAccess::isValidPrivilege(priv);
+}
+
+uint8_t ipmiUserGetUserId(const std::string& userName)
+{
+ return getUserAccessObject().getUserId(userName);
+}
+
+Cc ipmiUserSetUserName(const uint8_t userId, const char* userName)
+{
+ std::string newUser(userName, 0, ipmiMaxUserName);
+ return getUserAccessObject().setUserName(userId, newUser);
+}
+
+Cc ipmiUserSetUserName(const uint8_t userId, const std::string& userName)
+{
+ std::string newUser(userName, 0, ipmiMaxUserName);
+ return getUserAccessObject().setUserName(userId, newUser);
+}
+
+Cc ipmiUserGetUserName(const uint8_t userId, std::string& userName)
+{
+ return getUserAccessObject().getUserName(userId, userName);
+}
+
+Cc ipmiUserSetUserPassword(const uint8_t userId, const char* userPassword)
+{
+ return getUserAccessObject().setUserPassword(userId, userPassword);
+}
+
+Cc ipmiSetSpecialUserPassword(const std::string& userName,
+ const SecureString& userPassword)
+{
+ return getUserAccessObject().setSpecialUserPassword(userName, userPassword);
+}
+
+Cc ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers,
+ uint8_t& fixedUsers)
+{
+ maxChUsers = ipmiMaxUsers;
+ UsersTbl* userData = getUserAccessObject().getUsersTblPtr();
+ enabledUsers = 0;
+ fixedUsers = 0;
+ // user index 0 is reserved, starts with 1
+ for (size_t count = 1; count <= ipmiMaxUsers; ++count)
+ {
+ if (userData->user[count].userEnabled)
+ {
+ enabledUsers++;
+ }
+ if (userData->user[count].fixedUserName)
+ {
+ fixedUsers++;
+ }
+ }
+ return ccSuccess;
+}
+
+Cc ipmiUserUpdateEnabledState(const uint8_t userId, const bool& state)
+{
+ return getUserAccessObject().setUserEnabledState(userId, state);
+}
+
+Cc ipmiUserCheckEnabled(const uint8_t userId, bool& state)
+{
+ if (!UserAccess::isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+ state = userInfo->userEnabled;
+ return ccSuccess;
+}
+
+Cc ipmiUserGetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ PrivAccess& privAccess)
+{
+ if (!UserAccess::isValidChannel(chNum))
+ {
+ return ccInvalidFieldRequest;
+ }
+ if (!UserAccess::isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+ privAccess.privilege = userInfo->userPrivAccess[chNum].privilege;
+ privAccess.ipmiEnabled = userInfo->userPrivAccess[chNum].ipmiEnabled;
+ privAccess.linkAuthEnabled =
+ userInfo->userPrivAccess[chNum].linkAuthEnabled;
+ privAccess.accessCallback = userInfo->userPrivAccess[chNum].accessCallback;
+ return ccSuccess;
+}
+
+Cc ipmiUserSetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ const PrivAccess& privAccess,
+ const bool& otherPrivUpdates)
+{
+ UserPrivAccess userPrivAccess;
+ userPrivAccess.privilege = privAccess.privilege;
+ if (otherPrivUpdates)
+ {
+ userPrivAccess.ipmiEnabled = privAccess.ipmiEnabled;
+ userPrivAccess.linkAuthEnabled = privAccess.linkAuthEnabled;
+ userPrivAccess.accessCallback = privAccess.accessCallback;
+ }
+ return getUserAccessObject().setUserPrivilegeAccess(
+ userId, chNum, userPrivAccess, otherPrivUpdates);
+}
+
+bool ipmiUserPamAuthenticate(std::string_view userName,
+ std::string_view userPassword)
+{
+ return pamUserCheckAuthenticate(userName, userPassword);
+}
+
+Cc ipmiUserSetUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+ const uint8_t userId,
+ const PayloadAccess& payloadAccess)
+{
+ if (!UserAccess::isValidChannel(chNum))
+ {
+ return ccInvalidFieldRequest;
+ }
+ if (!UserAccess::isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+
+ return getUserAccessObject().setUserPayloadAccess(chNum, operation, userId,
+ payloadAccess);
+}
+
+Cc ipmiUserGetUserPayloadAccess(const uint8_t chNum, const uint8_t userId,
+ PayloadAccess& payloadAccess)
+{
+ if (!UserAccess::isValidChannel(chNum))
+ {
+ return ccInvalidFieldRequest;
+ }
+ if (!UserAccess::isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+
+ UserInfo* userInfo = getUserAccessObject().getUserInfo(userId);
+
+ payloadAccess.stdPayloadEnables1 =
+ userInfo->payloadAccess[chNum].stdPayloadEnables1;
+ payloadAccess.stdPayloadEnables2Reserved =
+ userInfo->payloadAccess[chNum].stdPayloadEnables2Reserved;
+ payloadAccess.oemPayloadEnables1 =
+ userInfo->payloadAccess[chNum].oemPayloadEnables1;
+ payloadAccess.oemPayloadEnables2Reserved =
+ userInfo->payloadAccess[chNum].oemPayloadEnables2Reserved;
+
+ return ccSuccess;
+}
+
+} // namespace ipmi
diff --git a/user_channel/user_layer.hpp b/user_channel/user_layer.hpp
new file mode 100644
index 0000000..ad215e3
--- /dev/null
+++ b/user_channel/user_layer.hpp
@@ -0,0 +1,270 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+
+#include <bitset>
+#include <string>
+
+namespace ipmi
+{
+
+// TODO: Has to be replaced with proper channel number assignment logic
+/**
+ * @enum Channel Id
+ */
+enum class EChannelID : uint8_t
+{
+ chanLan1 = 0x01
+};
+
+static constexpr uint8_t invalidUserId = 0xFF;
+static constexpr uint8_t reservedUserId = 0x0;
+static constexpr uint8_t ipmiMaxUserName = 16;
+static constexpr uint8_t ipmiMaxUsers = 15;
+static constexpr uint8_t ipmiMaxChannels = 16;
+static constexpr uint8_t maxIpmi20PasswordSize = 20;
+static constexpr uint8_t maxIpmi15PasswordSize = 16;
+static constexpr uint8_t payloadsPerByte = 8;
+
+/** @struct PrivAccess
+ *
+ * User privilege related access data as per IPMI specification.(refer spec
+ * sec 22.26)
+ */
+struct PrivAccess
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint8_t privilege:4;
+ uint8_t ipmiEnabled:1;
+ uint8_t linkAuthEnabled:1;
+ uint8_t accessCallback:1;
+ uint8_t reserved:1;
+#endif
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t reserved:1;
+ uint8_t accessCallback:1;
+ uint8_t linkAuthEnabled:1;
+ uint8_t ipmiEnabled:1;
+ uint8_t privilege:4;
+#endif
+} __attribute__((packed));
+
+/** @struct UserPayloadAccess
+ *
+ * Structure to denote payload access restrictions applicable for a
+ * given user and channel. (refer spec sec 24.6)
+ */
+struct PayloadAccess
+{
+ std::bitset<payloadsPerByte> stdPayloadEnables1;
+ std::bitset<payloadsPerByte> stdPayloadEnables2Reserved;
+ std::bitset<payloadsPerByte> oemPayloadEnables1;
+ std::bitset<payloadsPerByte> oemPayloadEnables2Reserved;
+};
+
+/** @brief initializes user management
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserInit();
+
+/** @brief The ipmi get user password layer call
+ *
+ * @param[in] userName - user name
+ *
+ * @return password or empty string
+ */
+SecureString ipmiUserGetPassword(const std::string& userName);
+
+/** @brief The IPMI call to clear password entry associated with specified
+ * username
+ *
+ * @param[in] userName - user name to be removed
+ *
+ * @return 0 on success, non-zero otherwise.
+ */
+Cc ipmiClearUserEntryPassword(const std::string& userName);
+
+/** @brief The IPMI call to reuse password entry for the renamed user
+ * to another one
+ *
+ * @param[in] userName - user name which has to be renamed
+ * @param[in] newUserName - new user name
+ *
+ * @return 0 on success, non-zero otherwise.
+ */
+Cc ipmiRenameUserEntryPassword(const std::string& userName,
+ const std::string& newUserName);
+
+/** @brief determines valid userId
+ *
+ * @param[in] userId - user id
+ *
+ * @return true if valid, false otherwise
+ */
+bool ipmiUserIsValidUserId(const uint8_t userId);
+
+/** @brief determines valid privilege level
+ *
+ * @param[in] priv - privilege level
+ *
+ * @return true if valid, false otherwise
+ */
+bool ipmiUserIsValidPrivilege(const uint8_t priv);
+
+/** @brief get user id corresponding to the user name
+ *
+ * @param[in] userName - user name
+ *
+ * @return userid. Will return 0xff if no user id found
+ */
+uint8_t ipmiUserGetUserId(const std::string& userName);
+
+/** @brief set's user name
+ * This API is deprecated
+ */
+Cc ipmiUserSetUserName(const uint8_t userId, const char* userName)
+ __attribute__((deprecated));
+
+/** @brief set's user name
+ *
+ * @param[in] userId - user id
+ * @param[in] userName - user name
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserName(const uint8_t userId, const std::string& userName);
+
+/** @brief set user password
+ *
+ * @param[in] userId - user id
+ * @param[in] userPassword - New Password
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserPassword(const uint8_t userId, const char* userPassword);
+
+/** @brief set special user password (non-ipmi accounts)
+ *
+ * @param[in] userName - user name
+ * @param[in] userPassword - New Password
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiSetSpecialUserPassword(const std::string& userName,
+ const SecureString& userPassword);
+
+/** @brief get user name
+ *
+ * @param[in] userId - user id
+ * @param[out] userName - user name
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetUserName(const uint8_t userId, std::string& userName);
+
+/** @brief provides available fixed, max, and enabled user counts
+ *
+ * @param[out] maxChUsers - max channel users
+ * @param[out] enabledUsers - enabled user count
+ * @param[out] fixedUsers - fixed user count
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers,
+ uint8_t& fixedUsers);
+
+/** @brief function to update user enabled state
+ *
+ * @param[in] userId - user id
+ *..@param[in] state - state of the user to be updated, true - user enabled.
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserUpdateEnabledState(const uint8_t userId, const bool& state);
+
+/** @brief determines whether user is enabled
+ *
+ * @param[in] userId - user id
+ *..@param[out] state - state of the user
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserCheckEnabled(const uint8_t userId, bool& state);
+
+/** @brief provides user privilege access data
+ *
+ * @param[in] userId - user id
+ * @param[in] chNum - channel number
+ * @param[out] privAccess - privilege access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ PrivAccess& privAccess);
+
+/** @brief sets user privilege access data
+ *
+ * @param[in] userId - user id
+ * @param[in] chNum - channel number
+ * @param[in] privAccess - privilege access data
+ * @param[in] otherPrivUpdate - flags to indicate other fields update
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ const PrivAccess& privAccess,
+ const bool& otherPrivUpdate);
+
+/** @brief check for user pam authentication. This is to determine, whether user
+ * is already locked out for failed login attempt
+ *
+ * @param[in] username - username
+ * @param[in] password - password
+ *
+ * @return status
+ */
+bool ipmiUserPamAuthenticate(std::string_view userName,
+ std::string_view userPassword);
+
+/** @brief sets user payload access data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] operation - ENABLE / DISABLE operation
+ * @param[in] userId - user id
+ * @param[in] payloadAccess - payload access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserSetUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+ const uint8_t userId,
+ const PayloadAccess& payloadAccess);
+
+/** @brief provides user payload access data
+ *
+ * @param[in] chNum - channel number
+ * @param[in] userId - user id
+ * @param[out] payloadAccess - payload access data
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+Cc ipmiUserGetUserPayloadAccess(const uint8_t chNum, const uint8_t userId,
+ PayloadAccess& payloadAccess);
+
+} // namespace ipmi
diff --git a/user_channel/user_mgmt.cpp b/user_channel/user_mgmt.cpp
new file mode 100644
index 0000000..29262cb
--- /dev/null
+++ b/user_channel/user_mgmt.cpp
@@ -0,0 +1,1748 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "user_mgmt.hpp"
+
+#include "channel_layer.hpp"
+#include "channel_mgmt.hpp"
+
+#include <security/pam_appl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <boost/interprocess/sync/scoped_lock.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/User/Common/error.hpp>
+
+#include <cerrno>
+#include <fstream>
+#include <regex>
+#include <variant>
+
+namespace ipmi
+{
+
+// TODO: Move D-Bus & Object Manager related stuff, to common files
+// D-Bus property related
+static constexpr const char* dBusPropertiesInterface =
+ "org.freedesktop.DBus.Properties";
+static constexpr const char* getAllPropertiesMethod = "GetAll";
+static constexpr const char* propertiesChangedSignal = "PropertiesChanged";
+static constexpr const char* setPropertiesMethod = "Set";
+
+// Object Manager related
+static constexpr const char* dBusObjManager =
+ "org.freedesktop.DBus.ObjectManager";
+static constexpr const char* getManagedObjectsMethod = "GetManagedObjects";
+// Object Manager signals
+static constexpr const char* intfAddedSignal = "InterfacesAdded";
+static constexpr const char* intfRemovedSignal = "InterfacesRemoved";
+
+static constexpr const char* ipmiUserMutex = "ipmi_usr_mutex";
+static constexpr const char* ipmiMutexCleanupLockFile =
+ "/run/ipmi/ipmi_usr_mutex_cleanup";
+static constexpr const char* ipmiUserSignalLockFile =
+ "/run/ipmi/ipmi_usr_signal_mutex";
+static constexpr const char* ipmiUserDataFile = "/var/lib/ipmi/ipmi_user.json";
+static constexpr const char* ipmiGrpName = "ipmi";
+static constexpr size_t privNoAccess = 0xF;
+static constexpr size_t privMask = 0xF;
+
+// User manager related
+static constexpr const char* userMgrService =
+ "xyz.openbmc_project.User.Manager";
+static constexpr const char* userMgrObjBasePath = "/xyz/openbmc_project/user";
+static constexpr const char* userObjBasePath = "/xyz/openbmc_project/user";
+static constexpr const char* userMgrInterface =
+ "xyz.openbmc_project.User.Manager";
+static constexpr const char* usersInterface =
+ "xyz.openbmc_project.User.Attributes";
+static constexpr const char* deleteUserInterface =
+ "xyz.openbmc_project.Object.Delete";
+
+static constexpr const char* createUserMethod = "CreateUser";
+static constexpr const char* deleteUserMethod = "Delete";
+static constexpr const char* renameUserMethod = "RenameUser";
+// User manager signal memebers
+static constexpr const char* userRenamedSignal = "UserRenamed";
+// Mgr interface properties
+static constexpr const char* allPrivProperty = "AllPrivileges";
+static constexpr const char* allGrpProperty = "AllGroups";
+// User interface properties
+static constexpr const char* userPrivProperty = "UserPrivilege";
+static constexpr const char* userGrpProperty = "UserGroups";
+static constexpr const char* userEnabledProperty = "UserEnabled";
+
+static std::array<std::string, (PRIVILEGE_OEM + 1)> ipmiPrivIndex = {
+ "priv-reserved", // PRIVILEGE_RESERVED - 0
+ "priv-callback", // PRIVILEGE_CALLBACK - 1
+ "priv-user", // PRIVILEGE_USER - 2
+ "priv-operator", // PRIVILEGE_OPERATOR - 3
+ "priv-admin", // PRIVILEGE_ADMIN - 4
+ "priv-custom" // PRIVILEGE_OEM - 5
+};
+
+using namespace phosphor::logging;
+using Json = nlohmann::json;
+
+using PrivAndGroupType = std::variant<std::string, std::vector<std::string>>;
+
+using NoResource =
+ sdbusplus::error::xyz::openbmc_project::user::common::NoResource;
+
+using InternalFailure =
+ sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
+
+std::unique_ptr<sdbusplus::bus::match_t> userUpdatedSignal
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> userMgrRenamedSignal
+ __attribute__((init_priority(101)));
+std::unique_ptr<sdbusplus::bus::match_t> userPropertiesSignal
+ __attribute__((init_priority(101)));
+
+void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+ const std::string& objPath, const std::string& interface,
+ const std::string& property,
+ const DbusUserPropVariant& value)
+{
+ try
+ {
+ auto method =
+ bus.new_method_call(service.c_str(), objPath.c_str(),
+ dBusPropertiesInterface, setPropertiesMethod);
+ method.append(interface, property, value);
+ bus.call(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to set {PROPERTY}, path: {PATH}, "
+ "interface: {INTERFACE}",
+ "PROPERTY", property, "PATH", objPath, "INTERFACE",
+ interface);
+ throw;
+ }
+}
+
+UserAccess& getUserAccessObject()
+{
+ static UserAccess userAccess;
+ return userAccess;
+}
+
+int getUserNameFromPath(const std::string& path, std::string& userName)
+{
+ sdbusplus::message::object_path objPath(path);
+ userName.assign(objPath.filename());
+ return 0;
+}
+
+void userUpdateHelper(UserAccess& usrAccess, const UserUpdateEvent& userEvent,
+ const std::string& userName, const std::string& priv,
+ const bool& enabled, const std::string& newUserName)
+{
+ UsersTbl* userData = usrAccess.getUsersTblPtr();
+ if (userEvent == UserUpdateEvent::userCreated)
+ {
+ if (usrAccess.addUserEntry(userName, priv, enabled) == false)
+ {
+ return;
+ }
+ }
+ else
+ {
+ // user index 0 is reserved, starts with 1
+ size_t usrIndex = 1;
+ for (; usrIndex <= ipmiMaxUsers; ++usrIndex)
+ {
+ std::string curName(
+ reinterpret_cast<char*>(userData->user[usrIndex].userName), 0,
+ ipmiMaxUserName);
+ if (userName == curName)
+ {
+ break; // found the entry
+ }
+ }
+ if (usrIndex > ipmiMaxUsers)
+ {
+ lg2::debug("User not found for signal, user name: {USER_NAME}, "
+ "user event: {USER_EVENT}",
+ "USER_NAME", userName, "USER_EVENT", userEvent);
+ return;
+ }
+ switch (userEvent)
+ {
+ case UserUpdateEvent::userDeleted:
+ {
+ usrAccess.deleteUserIndex(usrIndex);
+ break;
+ }
+ case UserUpdateEvent::userPrivUpdated:
+ {
+ uint8_t userPriv =
+ static_cast<uint8_t>(
+ UserAccess::convertToIPMIPrivilege(priv)) &
+ privMask;
+ // Update all channels privileges, only if it is not equivalent
+ // to getUsrMgmtSyncIndex()
+ if (userData->user[usrIndex]
+ .userPrivAccess[UserAccess::getUsrMgmtSyncIndex()]
+ .privilege != userPriv)
+ {
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels;
+ ++chIndex)
+ {
+ userData->user[usrIndex]
+ .userPrivAccess[chIndex]
+ .privilege = userPriv;
+ }
+ }
+ break;
+ }
+ case UserUpdateEvent::userRenamed:
+ {
+ std::fill(
+ static_cast<uint8_t*>(userData->user[usrIndex].userName),
+ static_cast<uint8_t*>(userData->user[usrIndex].userName) +
+ sizeof(userData->user[usrIndex].userName),
+ 0);
+ std::strncpy(
+ reinterpret_cast<char*>(userData->user[usrIndex].userName),
+ newUserName.c_str(), ipmiMaxUserName);
+ ipmiRenameUserEntryPassword(userName, newUserName);
+ break;
+ }
+ case UserUpdateEvent::userStateUpdated:
+ {
+ userData->user[usrIndex].userEnabled = enabled;
+ break;
+ }
+ default:
+ {
+ lg2::error("Unhandled user event: {USER_EVENT}", "USER_EVENT",
+ userEvent);
+ return;
+ }
+ }
+ }
+ usrAccess.writeUserData();
+ lg2::debug("User event handled successfully, user name: {USER_NAME}, "
+ "user event: {USER_EVENT}",
+ "USER_NAME", userName.c_str(), "USER_EVENT", userEvent);
+
+ return;
+}
+
+void userUpdatedSignalHandler(UserAccess& usrAccess, sdbusplus::message_t& msg)
+{
+ static sdbusplus::bus_t bus(ipmid_get_sd_bus_connection());
+ std::string signal = msg.get_member();
+ std::string userName, priv, newUserName;
+ std::vector<std::string> groups;
+ bool enabled = false;
+ UserUpdateEvent userEvent = UserUpdateEvent::reservedEvent;
+ if (signal == intfAddedSignal)
+ {
+ DbusUserObjPath objPath;
+ DbusUserObjValue objValue;
+ msg.read(objPath, objValue);
+ getUserNameFromPath(objPath.str, userName);
+ if (usrAccess.getUserObjProperties(objValue, groups, priv, enabled) !=
+ 0)
+ {
+ return;
+ }
+ if (std::find(groups.begin(), groups.end(), ipmiGrpName) ==
+ groups.end())
+ {
+ return;
+ }
+ userEvent = UserUpdateEvent::userCreated;
+ }
+ else if (signal == intfRemovedSignal)
+ {
+ DbusUserObjPath objPath;
+ std::vector<std::string> interfaces;
+ msg.read(objPath, interfaces);
+ getUserNameFromPath(objPath.str, userName);
+ userEvent = UserUpdateEvent::userDeleted;
+ }
+ else if (signal == userRenamedSignal)
+ {
+ msg.read(userName, newUserName);
+ userEvent = UserUpdateEvent::userRenamed;
+ }
+ else if (signal == propertiesChangedSignal)
+ {
+ getUserNameFromPath(msg.get_path(), userName);
+ }
+ else
+ {
+ lg2::error("Unknown user update signal: {SIGNAL}", "SIGNAL", signal);
+ return;
+ }
+
+ if (signal.empty() || userName.empty() ||
+ (signal == userRenamedSignal && newUserName.empty()))
+ {
+ lg2::error("Invalid inputs received");
+ return;
+ }
+
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*(usrAccess.userMutex)};
+ usrAccess.checkAndReloadUserData();
+
+ if (signal == propertiesChangedSignal)
+ {
+ std::string intfName;
+ DbusUserObjProperties chProperties;
+ msg.read(intfName, chProperties); // skip reading 3rd argument.
+ for (const auto& prop : chProperties)
+ {
+ userEvent = UserUpdateEvent::reservedEvent;
+ std::string member = prop.first;
+ if (member == userPrivProperty)
+ {
+ priv = std::get<std::string>(prop.second);
+ userEvent = UserUpdateEvent::userPrivUpdated;
+ }
+ else if (member == userGrpProperty)
+ {
+ groups = std::get<std::vector<std::string>>(prop.second);
+ userEvent = UserUpdateEvent::userGrpUpdated;
+ }
+ else if (member == userEnabledProperty)
+ {
+ enabled = std::get<bool>(prop.second);
+ userEvent = UserUpdateEvent::userStateUpdated;
+ }
+ // Process based on event type.
+ if (userEvent == UserUpdateEvent::userGrpUpdated)
+ {
+ if (std::find(groups.begin(), groups.end(), ipmiGrpName) ==
+ groups.end())
+ {
+ // remove user from ipmi user list.
+ userUpdateHelper(usrAccess, UserUpdateEvent::userDeleted,
+ userName, priv, enabled, newUserName);
+ }
+ else
+ {
+ DbusUserObjProperties properties;
+ try
+ {
+ auto method = bus.new_method_call(
+ userMgrService, msg.get_path(),
+ dBusPropertiesInterface, getAllPropertiesMethod);
+ method.append(usersInterface);
+ auto reply = bus.call(method);
+ reply.read(properties);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path: {PATH}",
+ "METHOD", getAllPropertiesMethod, "PATH",
+ msg.get_path());
+ return;
+ }
+ usrAccess.getUserProperties(properties, groups, priv,
+ enabled);
+ // add user to ipmi user list.
+ userUpdateHelper(usrAccess, UserUpdateEvent::userCreated,
+ userName, priv, enabled, newUserName);
+ }
+ }
+ else if (userEvent != UserUpdateEvent::reservedEvent)
+ {
+ userUpdateHelper(usrAccess, userEvent, userName, priv, enabled,
+ newUserName);
+ }
+ }
+ }
+ else if (userEvent != UserUpdateEvent::reservedEvent)
+ {
+ userUpdateHelper(usrAccess, userEvent, userName, priv, enabled,
+ newUserName);
+ }
+ return;
+}
+
+UserAccess::~UserAccess()
+{
+ if (signalHndlrObject)
+ {
+ userUpdatedSignal.reset();
+ userMgrRenamedSignal.reset();
+ userPropertiesSignal.reset();
+ sigHndlrLock.unlock();
+ }
+}
+
+UserAccess::UserAccess() : bus(ipmid_get_sd_bus_connection())
+{
+ std::ofstream mutexCleanUpFile;
+ mutexCleanUpFile.open(ipmiMutexCleanupLockFile,
+ std::ofstream::out | std::ofstream::app);
+ if (!mutexCleanUpFile.good())
+ {
+ lg2::debug("Unable to open mutex cleanup file");
+ return;
+ }
+ mutexCleanUpFile.close();
+ mutexCleanupLock = boost::interprocess::file_lock(ipmiMutexCleanupLockFile);
+ if (mutexCleanupLock.try_lock())
+ {
+ boost::interprocess::named_recursive_mutex::remove(ipmiUserMutex);
+ }
+ mutexCleanupLock.lock_sharable();
+ userMutex = std::make_unique<boost::interprocess::named_recursive_mutex>(
+ boost::interprocess::open_or_create, ipmiUserMutex);
+
+ cacheUserDataFile();
+ getSystemPrivAndGroups();
+}
+
+UserInfo* UserAccess::getUserInfo(const uint8_t userId)
+{
+ checkAndReloadUserData();
+ return &usersTbl.user[userId];
+}
+
+void UserAccess::setUserInfo(const uint8_t userId, UserInfo* userInfo)
+{
+ checkAndReloadUserData();
+ std::copy(reinterpret_cast<uint8_t*>(userInfo),
+ reinterpret_cast<uint8_t*>(userInfo) + sizeof(*userInfo),
+ reinterpret_cast<uint8_t*>(&usersTbl.user[userId]));
+ writeUserData();
+}
+
+bool UserAccess::isValidChannel(const uint8_t chNum)
+{
+ return (chNum < ipmiMaxChannels);
+}
+
+bool UserAccess::isValidUserId(const uint8_t userId)
+{
+ return ((userId <= ipmiMaxUsers) && (userId != reservedUserId));
+}
+
+bool UserAccess::isValidPrivilege(const uint8_t priv)
+{
+ // Callback privilege is deprecated in OpenBMC
+ return isValidPrivLimit(priv);
+}
+
+uint8_t UserAccess::getUsrMgmtSyncIndex()
+{
+ // Identify the IPMI channel used to assign system user privilege levels
+ // in phosphor-user-manager. The default value is IPMI Channel 1. To
+ // assign a different channel add:
+ // "is_management_nic" : true
+ // into the channel_config.json file describing the assignment of the IPMI
+ // channels. It is only necessary to add the string above to ONE record in
+ // the channel_config.json file. All other records will be automatically
+ // assigned a "false" value.
+ return getChannelConfigObject().getManagementNICID();
+}
+
+CommandPrivilege UserAccess::convertToIPMIPrivilege(const std::string& value)
+{
+ auto iter = std::find(ipmiPrivIndex.begin(), ipmiPrivIndex.end(), value);
+ if (iter == ipmiPrivIndex.end())
+ {
+ if (value == "")
+ {
+ return static_cast<CommandPrivilege>(privNoAccess);
+ }
+ lg2::error("Error in converting to IPMI privilege: {PRIV}", "PRIV",
+ value);
+ throw std::out_of_range("Out of range - convertToIPMIPrivilege");
+ }
+ else
+ {
+ return static_cast<CommandPrivilege>(
+ std::distance(ipmiPrivIndex.begin(), iter));
+ }
+}
+
+std::string UserAccess::convertToSystemPrivilege(const CommandPrivilege& value)
+{
+ if (value == static_cast<CommandPrivilege>(privNoAccess))
+ {
+ return "";
+ }
+ try
+ {
+ return ipmiPrivIndex.at(value);
+ }
+ catch (const std::out_of_range& e)
+ {
+ lg2::error("Error in converting to system privilege: {PRIV}", "PRIV",
+ value);
+ throw std::out_of_range("Out of range - convertToSystemPrivilege");
+ }
+}
+
+bool UserAccess::isValidUserName(const std::string& userName)
+{
+ if (userName.empty())
+ {
+ lg2::error("userName is empty");
+ return false;
+ }
+ if (!std::regex_match(userName.c_str(),
+ std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
+ {
+ lg2::error("Unsupported characters in user name");
+ return false;
+ }
+ if (userName == "root")
+ {
+ lg2::error("Invalid user name - root");
+ return false;
+ }
+ std::map<DbusUserObjPath, DbusUserObjValue> properties;
+ try
+ {
+ auto method =
+ bus.new_method_call(userMgrService, userMgrObjBasePath,
+ dBusObjManager, getManagedObjectsMethod);
+ auto reply = bus.call(method);
+ reply.read(properties);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+ getManagedObjectsMethod, "PATH", userMgrObjBasePath);
+ return false;
+ }
+
+ sdbusplus::message::object_path tempUserPath(userObjBasePath);
+ tempUserPath /= userName;
+ std::string usersPath(tempUserPath);
+
+ if (properties.find(usersPath) != properties.end())
+ {
+ lg2::debug("Username {USER_NAME} already exists", "USER_NAME",
+ userName);
+ return false;
+ }
+
+ return true;
+}
+
+/** @brief Information exchanged by pam module and application.
+ *
+ * @param[in] numMsg - length of the array of pointers,msg.
+ *
+ * @param[in] msg - pointer to an array of pointers to pam_message structure
+ *
+ * @param[out] resp - struct pam response array
+ *
+ * @param[in] appdataPtr - member of pam_conv structure
+ *
+ * @return the response in pam response structure.
+ */
+
+static int pamFunctionConversation(int numMsg, const struct pam_message** msg,
+ struct pam_response** resp, void* appdataPtr)
+{
+ if (appdataPtr == nullptr)
+ {
+ return PAM_CONV_ERR;
+ }
+
+ if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG)
+ {
+ return PAM_CONV_ERR;
+ }
+
+ for (int i = 0; i < numMsg; ++i)
+ {
+ /* Ignore all PAM messages except prompting for hidden input */
+ if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF)
+ {
+ continue;
+ }
+
+ /* Assume PAM is only prompting for the password as hidden input */
+ /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */
+
+ char* appPass = reinterpret_cast<char*>(appdataPtr);
+ size_t appPassSize = std::strlen(appPass);
+
+ if (appPassSize >= PAM_MAX_RESP_SIZE)
+ {
+ return PAM_CONV_ERR;
+ }
+
+ char* pass = reinterpret_cast<char*>(malloc(appPassSize + 1));
+ if (pass == nullptr)
+ {
+ return PAM_BUF_ERR;
+ }
+
+ void* ptr =
+ calloc(static_cast<size_t>(numMsg), sizeof(struct pam_response));
+ if (ptr == nullptr)
+ {
+ free(pass);
+ return PAM_BUF_ERR;
+ }
+
+ std::strncpy(pass, appPass, appPassSize + 1);
+
+ *resp = reinterpret_cast<pam_response*>(ptr);
+ resp[i]->resp = pass;
+
+ return PAM_SUCCESS;
+ }
+
+ return PAM_CONV_ERR;
+}
+
+/** @brief Updating the PAM password
+ *
+ * @param[in] username - username in string
+ *
+ * @param[in] password - new password in string
+ *
+ * @return status
+ */
+
+int pamUpdatePasswd(const char* username, const char* password)
+{
+ const struct pam_conv localConversation = {pamFunctionConversation,
+ const_cast<char*>(password)};
+ pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
+
+ int retval =
+ pam_start("passwd", username, &localConversation, &localAuthHandle);
+
+ if (retval != PAM_SUCCESS)
+ {
+ return retval;
+ }
+
+ retval = pam_chauthtok(localAuthHandle, PAM_SILENT);
+ if (retval != PAM_SUCCESS)
+ {
+ pam_end(localAuthHandle, retval);
+ return retval;
+ }
+
+ return pam_end(localAuthHandle, PAM_SUCCESS);
+}
+
+bool pamUserCheckAuthenticate(std::string_view username,
+ std::string_view password)
+{
+ const struct pam_conv localConversation = {
+ pamFunctionConversation, const_cast<char*>(password.data())};
+
+ pam_handle_t* localAuthHandle = nullptr; // this gets set by pam_start
+
+ if (pam_start("dropbear", username.data(), &localConversation,
+ &localAuthHandle) != PAM_SUCCESS)
+ {
+ lg2::error("User Authentication Failure");
+ return false;
+ }
+
+ int retval = pam_authenticate(localAuthHandle,
+ PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
+
+ if (retval != PAM_SUCCESS)
+ {
+ lg2::debug("pam_authenticate returned failure: {ERROR}", "ERROR",
+ retval);
+
+ pam_end(localAuthHandle, retval);
+ return false;
+ }
+
+ if (pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK) !=
+ PAM_SUCCESS)
+ {
+ pam_end(localAuthHandle, PAM_SUCCESS);
+ return false;
+ }
+
+ if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS)
+ {
+ return false;
+ }
+ return true;
+}
+
+Cc UserAccess::setSpecialUserPassword(const std::string& userName,
+ const SecureString& userPassword)
+{
+ if (pamUpdatePasswd(userName.c_str(), userPassword.c_str()) != PAM_SUCCESS)
+ {
+ lg2::debug("Failed to update password");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+Cc UserAccess::setUserPassword(const uint8_t userId, const char* userPassword)
+{
+ std::string userName;
+ if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+ {
+ lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+ userId);
+ return ccParmOutOfRange;
+ }
+
+ ipmi::SecureString passwd;
+ passwd.assign(reinterpret_cast<const char*>(userPassword), 0,
+ maxIpmi20PasswordSize);
+ int retval = pamUpdatePasswd(userName.c_str(), passwd.c_str());
+
+ switch (retval)
+ {
+ case PAM_SUCCESS:
+ {
+ return ccSuccess;
+ }
+ case PAM_AUTHTOK_ERR:
+ {
+ lg2::debug("Bad authentication token");
+ return ccInvalidFieldRequest;
+ }
+ default:
+ {
+ lg2::debug("Failed to update password, user Id: {USER_ID}",
+ "USER_ID", userId);
+ return ccUnspecifiedError;
+ }
+ }
+}
+
+Cc UserAccess::setUserEnabledState(const uint8_t userId,
+ const bool& enabledState)
+{
+ if (!isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ UserInfo* userInfo = getUserInfo(userId);
+ std::string userName;
+ userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+ ipmiMaxUserName);
+ if (userName.empty())
+ {
+ lg2::debug("User name not set / invalid");
+ return ccUnspecifiedError;
+ }
+ if (userInfo->userEnabled != enabledState)
+ {
+ sdbusplus::message::object_path tempUserPath(userObjBasePath);
+ tempUserPath /= userName;
+ std::string userPath(tempUserPath);
+ setDbusProperty(bus, userMgrService, userPath, usersInterface,
+ userEnabledProperty, enabledState);
+ userInfo->userEnabled = enabledState;
+ try
+ {
+ writeUserData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::debug("Write user data failed");
+ return ccUnspecifiedError;
+ }
+ }
+ return ccSuccess;
+}
+
+Cc UserAccess::setUserPayloadAccess(
+ const uint8_t chNum, const uint8_t operation, const uint8_t userId,
+ const PayloadAccess& payloadAccess)
+{
+ constexpr uint8_t enable = 0x0;
+ constexpr uint8_t disable = 0x1;
+
+ if (!isValidChannel(chNum))
+ {
+ return ccInvalidFieldRequest;
+ }
+ if (!isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ if (operation != enable && operation != disable)
+ {
+ return ccInvalidFieldRequest;
+ }
+ // Check operation & payloadAccess if required.
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ UserInfo* userInfo = getUserInfo(userId);
+
+ if (operation == enable)
+ {
+ userInfo->payloadAccess[chNum].stdPayloadEnables1 |=
+ payloadAccess.stdPayloadEnables1;
+
+ userInfo->payloadAccess[chNum].oemPayloadEnables1 |=
+ payloadAccess.oemPayloadEnables1;
+ }
+ else
+ {
+ userInfo->payloadAccess[chNum].stdPayloadEnables1 &=
+ ~(payloadAccess.stdPayloadEnables1);
+
+ userInfo->payloadAccess[chNum].oemPayloadEnables1 &=
+ ~(payloadAccess.oemPayloadEnables1);
+ }
+
+ try
+ {
+ writeUserData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Write user data failed");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+Cc UserAccess::setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ const UserPrivAccess& privAccess,
+ const bool& otherPrivUpdates)
+{
+ if (!isValidChannel(chNum))
+ {
+ return ccInvalidFieldRequest;
+ }
+ if (!isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ UserInfo* userInfo = getUserInfo(userId);
+ std::string userName;
+ userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+ ipmiMaxUserName);
+ if (userName.empty())
+ {
+ lg2::debug("User name not set / invalid");
+ return ccUnspecifiedError;
+ }
+ std::string priv = convertToSystemPrivilege(
+ static_cast<CommandPrivilege>(privAccess.privilege));
+ uint8_t syncIndex = getUsrMgmtSyncIndex();
+ if (chNum == syncIndex &&
+ privAccess.privilege != userInfo->userPrivAccess[syncIndex].privilege)
+ {
+ sdbusplus::message::object_path tempUserPath(userObjBasePath);
+ tempUserPath /= userName;
+ std::string userPath(tempUserPath);
+ setDbusProperty(bus, userMgrService, userPath, usersInterface,
+ userPrivProperty, priv);
+ }
+ userInfo->userPrivAccess[chNum].privilege = privAccess.privilege;
+
+ if (otherPrivUpdates)
+ {
+ userInfo->userPrivAccess[chNum].ipmiEnabled = privAccess.ipmiEnabled;
+ userInfo->userPrivAccess[chNum].linkAuthEnabled =
+ privAccess.linkAuthEnabled;
+ userInfo->userPrivAccess[chNum].accessCallback =
+ privAccess.accessCallback;
+ }
+ try
+ {
+ writeUserData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::debug("Write user data failed");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+uint8_t UserAccess::getUserId(const std::string& userName)
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ checkAndReloadUserData();
+ // user index 0 is reserved, starts with 1
+ size_t usrIndex = 1;
+ for (; usrIndex <= ipmiMaxUsers; ++usrIndex)
+ {
+ std::string curName(
+ reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), 0,
+ ipmiMaxUserName);
+ if (userName == curName)
+ {
+ break; // found the entry
+ }
+ }
+ if (usrIndex > ipmiMaxUsers)
+ {
+ lg2::debug("Username {USER_NAME} not found", "USER_NAME", userName);
+ return invalidUserId;
+ }
+
+ return usrIndex;
+}
+
+Cc UserAccess::getUserName(const uint8_t userId, std::string& userName)
+{
+ if (!isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+ UserInfo* userInfo = getUserInfo(userId);
+ userName.assign(reinterpret_cast<char*>(userInfo->userName), 0,
+ ipmiMaxUserName);
+ return ccSuccess;
+}
+
+bool UserAccess::isIpmiInAvailableGroupList()
+{
+ if (std::find(availableGroups.begin(), availableGroups.end(),
+ ipmiGrpName) != availableGroups.end())
+ {
+ return true;
+ }
+ if (availableGroups.empty())
+ {
+ // available groups shouldn't be empty, re-query
+ getSystemPrivAndGroups();
+ if (std::find(availableGroups.begin(), availableGroups.end(),
+ ipmiGrpName) != availableGroups.end())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+Cc UserAccess::setUserName(const uint8_t userId, const std::string& userName)
+{
+ if (!isValidUserId(userId))
+ {
+ return ccParmOutOfRange;
+ }
+
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ std::string oldUser;
+ getUserName(userId, oldUser);
+
+ if (oldUser == userName)
+ {
+ // requesting to set the same user name, return success.
+ return ccSuccess;
+ }
+
+ bool validUser = isValidUserName(userName);
+ UserInfo* userInfo = getUserInfo(userId);
+ if (userName.empty() && !oldUser.empty())
+ {
+ // Delete existing user
+ sdbusplus::message::object_path tempUserPath(userObjBasePath);
+ tempUserPath /= oldUser;
+ std::string userPath(tempUserPath);
+ try
+ {
+ auto method =
+ bus.new_method_call(userMgrService, userPath.c_str(),
+ deleteUserInterface, deleteUserMethod);
+ auto reply = bus.call(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path:{PATH}", "METHOD",
+ deleteUserMethod, "PATH", userPath);
+ return ccUnspecifiedError;
+ }
+ deleteUserIndex(userId);
+ }
+ else if (oldUser.empty() && !userName.empty() && validUser)
+ {
+ try
+ {
+ if (!isIpmiInAvailableGroupList())
+ {
+ return ccUnspecifiedError;
+ }
+ // Create new user
+ auto method =
+ bus.new_method_call(userMgrService, userMgrObjBasePath,
+ userMgrInterface, createUserMethod);
+ method.append(userName.c_str(), availableGroups,
+ ipmiPrivIndex[PRIVILEGE_USER], false);
+ auto reply = bus.call(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+ createUserMethod, "PATH", userMgrObjBasePath);
+ return ccUnspecifiedError;
+ }
+
+ std::memset(userInfo->userName, 0, sizeof(userInfo->userName));
+ std::memcpy(userInfo->userName,
+ static_cast<const void*>(userName.data()), userName.size());
+ userInfo->userInSystem = true;
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+ {
+ userInfo->userPrivAccess[chIndex].privilege =
+ static_cast<uint8_t>(PRIVILEGE_USER);
+ }
+ }
+ else if (oldUser != userName && validUser)
+ {
+ try
+ {
+ // User rename
+ auto method =
+ bus.new_method_call(userMgrService, userMgrObjBasePath,
+ userMgrInterface, renameUserMethod);
+ method.append(oldUser.c_str(), userName.c_str());
+ auto reply = bus.call(method);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+ renameUserMethod, "PATH", userMgrObjBasePath);
+ return ccUnspecifiedError;
+ }
+ std::fill(static_cast<uint8_t*>(userInfo->userName),
+ static_cast<uint8_t*>(userInfo->userName) +
+ sizeof(userInfo->userName),
+ 0);
+
+ std::memset(userInfo->userName, 0, sizeof(userInfo->userName));
+ std::memcpy(userInfo->userName,
+ static_cast<const void*>(userName.data()), userName.size());
+
+ ipmiRenameUserEntryPassword(oldUser, userName);
+ userInfo->userInSystem = true;
+ }
+ else if (!validUser)
+ {
+ return ccInvalidFieldRequest;
+ }
+ try
+ {
+ writeUserData();
+ }
+ catch (const std::exception& e)
+ {
+ lg2::debug("Write user data failed");
+ return ccUnspecifiedError;
+ }
+ return ccSuccess;
+}
+
+static constexpr const char* jsonUserName = "user_name";
+static constexpr const char* jsonPriv = "privilege";
+static constexpr const char* jsonIpmiEnabled = "ipmi_enabled";
+static constexpr const char* jsonLinkAuthEnabled = "link_auth_enabled";
+static constexpr const char* jsonAccCallbk = "access_callback";
+static constexpr const char* jsonUserEnabled = "user_enabled";
+static constexpr const char* jsonUserInSys = "user_in_system";
+static constexpr const char* jsonFixedUser = "fixed_user_name";
+static constexpr const char* payloadEnabledStr = "payload_enabled";
+static constexpr const char* stdPayloadStr = "std_payload";
+static constexpr const char* oemPayloadStr = "OEM_payload";
+
+/** @brief to construct a JSON object from the given payload access details.
+ *
+ * @param[in] stdPayload - stdPayloadEnables1 in a 2D-array. (input)
+ * @param[in] oemPayload - oemPayloadEnables1 in a 2D-array. (input)
+ *
+ * @details Sample output JSON object format :
+ * "payload_enabled":{
+ * "OEM_payload0":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "OEM_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload0":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * "std_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>],
+ * }
+ */
+static const Json constructJsonPayloadEnables(
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ stdPayload,
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ oemPayload)
+{
+ Json jsonPayloadEnabled;
+
+ for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++)
+ {
+ std::ostringstream stdPayloadStream;
+ std::ostringstream oemPayloadStream;
+
+ stdPayloadStream << stdPayloadStr << payloadNum;
+ oemPayloadStream << oemPayloadStr << payloadNum;
+
+ jsonPayloadEnabled.push_back(Json::object_t::value_type(
+ stdPayloadStream.str(), stdPayload[payloadNum]));
+
+ jsonPayloadEnabled.push_back(Json::object_t::value_type(
+ oemPayloadStream.str(), oemPayload[payloadNum]));
+ }
+ return jsonPayloadEnabled;
+}
+
+void UserAccess::readPayloadAccessFromUserInfo(
+ const UserInfo& userInfo,
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& stdPayload,
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& oemPayload)
+{
+ for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++)
+ {
+ for (auto chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+ {
+ stdPayload[payloadNum][chIndex] =
+ userInfo.payloadAccess[chIndex].stdPayloadEnables1[payloadNum];
+
+ oemPayload[payloadNum][chIndex] =
+ userInfo.payloadAccess[chIndex].oemPayloadEnables1[payloadNum];
+ }
+ }
+}
+
+void UserAccess::updatePayloadAccessInUserInfo(
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ stdPayload,
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&,
+ UserInfo& userInfo)
+{
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+ {
+ // Ensure that reserved/unsupported payloads are marked to zero.
+ userInfo.payloadAccess[chIndex].stdPayloadEnables1.reset();
+ userInfo.payloadAccess[chIndex].oemPayloadEnables1.reset();
+ userInfo.payloadAccess[chIndex].stdPayloadEnables2Reserved.reset();
+ userInfo.payloadAccess[chIndex].oemPayloadEnables2Reserved.reset();
+ // Update SOL status as it is the only supported payload currently.
+ userInfo.payloadAccess[chIndex]
+ .stdPayloadEnables1[static_cast<uint8_t>(ipmi::PayloadType::SOL)] =
+ stdPayload[static_cast<uint8_t>(ipmi::PayloadType::SOL)][chIndex];
+ }
+}
+
+void UserAccess::readUserData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+
+ std::ifstream iUsrData(ipmiUserDataFile, std::ios::in | std::ios::binary);
+ if (!iUsrData.good())
+ {
+ lg2::error("Error in reading IPMI user data file");
+ throw std::ios_base::failure("Error opening IPMI user data file");
+ }
+
+ Json jsonUsersTbl = Json::array();
+ jsonUsersTbl = Json::parse(iUsrData, nullptr, false);
+
+ if (jsonUsersTbl.size() != ipmiMaxUsers)
+ {
+ lg2::error("Error in reading IPMI user data file - User count issues");
+ throw std::runtime_error(
+ "Corrupted IPMI user data file - invalid user count");
+ }
+
+ // user index 0 is reserved, starts with 1
+ for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+ {
+ Json userInfo = jsonUsersTbl[usrIndex - 1]; // json array starts with 0.
+ if (userInfo.is_null())
+ {
+ lg2::error("Error in reading IPMI user data file - "
+ "user info corrupted");
+ throw std::runtime_error(
+ "Corrupted IPMI user data file - invalid user info");
+ }
+ std::string userName = userInfo[jsonUserName].get<std::string>();
+ std::strncpy(reinterpret_cast<char*>(usersTbl.user[usrIndex].userName),
+ userName.c_str(), ipmiMaxUserName);
+
+ std::vector<std::string> privilege =
+ userInfo[jsonPriv].get<std::vector<std::string>>();
+ std::vector<bool> ipmiEnabled =
+ userInfo[jsonIpmiEnabled].get<std::vector<bool>>();
+ std::vector<bool> linkAuthEnabled =
+ userInfo[jsonLinkAuthEnabled].get<std::vector<bool>>();
+ std::vector<bool> accessCallback =
+ userInfo[jsonAccCallbk].get<std::vector<bool>>();
+
+ // Payload Enables Processing.
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+ stdPayload = {};
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+ oemPayload = {};
+ try
+ {
+ const auto jsonPayloadEnabled = userInfo.at(payloadEnabledStr);
+ for (auto payloadNum = 0; payloadNum < payloadsPerByte;
+ payloadNum++)
+ {
+ std::ostringstream stdPayloadStream;
+ std::ostringstream oemPayloadStream;
+
+ stdPayloadStream << stdPayloadStr << payloadNum;
+ oemPayloadStream << oemPayloadStr << payloadNum;
+
+ stdPayload[payloadNum] =
+ jsonPayloadEnabled[stdPayloadStream.str()]
+ .get<std::array<bool, ipmiMaxChannels>>();
+ oemPayload[payloadNum] =
+ jsonPayloadEnabled[oemPayloadStream.str()]
+ .get<std::array<bool, ipmiMaxChannels>>();
+
+ if (stdPayload[payloadNum].size() != ipmiMaxChannels ||
+ oemPayload[payloadNum].size() != ipmiMaxChannels)
+ {
+ lg2::error("Error in reading IPMI user data file - "
+ "payload properties corrupted");
+ throw std::runtime_error(
+ "Corrupted IPMI user data file - payload properties");
+ }
+ }
+ }
+ catch (const Json::out_of_range& e)
+ {
+ // Key not found in 'userInfo'; possibly an old JSON file. Use
+ // default values for all payloads, and SOL payload default is true.
+ stdPayload[static_cast<uint8_t>(ipmi::PayloadType::SOL)].fill(true);
+ }
+
+ if (privilege.size() != ipmiMaxChannels ||
+ ipmiEnabled.size() != ipmiMaxChannels ||
+ linkAuthEnabled.size() != ipmiMaxChannels ||
+ accessCallback.size() != ipmiMaxChannels)
+ {
+ lg2::error("Error in reading IPMI user data file - "
+ "properties corrupted");
+ throw std::runtime_error(
+ "Corrupted IPMI user data file - properties");
+ }
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+ {
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege =
+ static_cast<uint8_t>(
+ convertToIPMIPrivilege(privilege[chIndex]));
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled =
+ ipmiEnabled[chIndex];
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled =
+ linkAuthEnabled[chIndex];
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback =
+ accessCallback[chIndex];
+ }
+ updatePayloadAccessInUserInfo(stdPayload, oemPayload,
+ usersTbl.user[usrIndex]);
+ usersTbl.user[usrIndex].userEnabled =
+ userInfo[jsonUserEnabled].get<bool>();
+ usersTbl.user[usrIndex].userInSystem =
+ userInfo[jsonUserInSys].get<bool>();
+ usersTbl.user[usrIndex].fixedUserName =
+ userInfo[jsonFixedUser].get<bool>();
+ }
+
+ lg2::debug("User data read from IPMI data file");
+ iUsrData.close();
+ // Update the timestamp
+ fileLastUpdatedTime = getUpdatedFileTime();
+ return;
+}
+
+void UserAccess::writeUserData()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+
+ Json jsonUsersTbl = Json::array();
+ // user index 0 is reserved, starts with 1
+ for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+ {
+ Json jsonUserInfo;
+ jsonUserInfo[jsonUserName] = std::string(
+ reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), 0,
+ ipmiMaxUserName);
+ std::vector<std::string> privilege(ipmiMaxChannels);
+ std::vector<bool> ipmiEnabled(ipmiMaxChannels);
+ std::vector<bool> linkAuthEnabled(ipmiMaxChannels);
+ std::vector<bool> accessCallback(ipmiMaxChannels);
+
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+ stdPayload;
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>
+ oemPayload;
+
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++)
+ {
+ privilege[chIndex] =
+ convertToSystemPrivilege(static_cast<CommandPrivilege>(
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege));
+ ipmiEnabled[chIndex] =
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled;
+ linkAuthEnabled[chIndex] =
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled;
+ accessCallback[chIndex] =
+ usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback;
+ }
+ jsonUserInfo[jsonPriv] = privilege;
+ jsonUserInfo[jsonIpmiEnabled] = ipmiEnabled;
+ jsonUserInfo[jsonLinkAuthEnabled] = linkAuthEnabled;
+ jsonUserInfo[jsonAccCallbk] = accessCallback;
+ jsonUserInfo[jsonUserEnabled] = usersTbl.user[usrIndex].userEnabled;
+ jsonUserInfo[jsonUserInSys] = usersTbl.user[usrIndex].userInSystem;
+ jsonUserInfo[jsonFixedUser] = usersTbl.user[usrIndex].fixedUserName;
+
+ readPayloadAccessFromUserInfo(usersTbl.user[usrIndex], stdPayload,
+ oemPayload);
+ Json jsonPayloadEnabledInfo =
+ constructJsonPayloadEnables(stdPayload, oemPayload);
+ jsonUserInfo[payloadEnabledStr] = jsonPayloadEnabledInfo;
+
+ jsonUsersTbl.push_back(jsonUserInfo);
+ }
+
+ static std::string tmpFile{std::string(ipmiUserDataFile) + "_tmp"};
+ int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0)
+ {
+ lg2::error("Error in creating temporary IPMI user data file");
+ throw std::ios_base::failure(
+ "Error in creating temporary IPMI user data file");
+ }
+ const auto& writeStr = jsonUsersTbl.dump();
+ if (write(fd, writeStr.c_str(), writeStr.size()) !=
+ static_cast<ssize_t>(writeStr.size()))
+ {
+ close(fd);
+ lg2::error("Error in writing temporary IPMI user data file");
+ throw std::ios_base::failure(
+ "Error in writing temporary IPMI user data file");
+ }
+ close(fd);
+
+ if (std::rename(tmpFile.c_str(), ipmiUserDataFile) != 0)
+ {
+ lg2::error("Error in renaming temporary IPMI user data file");
+ throw std::runtime_error("Error in renaming IPMI user data file");
+ }
+ // Update the timestamp
+ fileLastUpdatedTime = getUpdatedFileTime();
+ return;
+}
+
+bool UserAccess::addUserEntry(const std::string& userName,
+ const std::string& sysPriv, const bool& enabled)
+{
+ UsersTbl* userData = getUsersTblPtr();
+ size_t freeIndex = 0xFF;
+ // user index 0 is reserved, starts with 1
+ for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex)
+ {
+ std::string curName(
+ reinterpret_cast<char*>(userData->user[usrIndex].userName), 0,
+ ipmiMaxUserName);
+ if (userName == curName)
+ {
+ lg2::debug("Username {USER_NAME} exists", "USER_NAME", userName);
+ return false; // user name exists.
+ }
+
+ if ((!userData->user[usrIndex].userInSystem) &&
+ (userData->user[usrIndex].userName[0] == '\0') &&
+ (freeIndex == 0xFF))
+ {
+ freeIndex = usrIndex;
+ }
+ }
+ if (freeIndex == 0xFF)
+ {
+ lg2::error("No empty slots found");
+ return false;
+ }
+ std::strncpy(reinterpret_cast<char*>(userData->user[freeIndex].userName),
+ userName.c_str(), ipmiMaxUserName);
+ uint8_t priv =
+ static_cast<uint8_t>(UserAccess::convertToIPMIPrivilege(sysPriv)) &
+ privMask;
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+ {
+ userData->user[freeIndex].userPrivAccess[chIndex].privilege = priv;
+ userData->user[freeIndex].userPrivAccess[chIndex].ipmiEnabled = true;
+ userData->user[freeIndex].userPrivAccess[chIndex].linkAuthEnabled =
+ true;
+ userData->user[freeIndex].userPrivAccess[chIndex].accessCallback = true;
+ }
+ userData->user[freeIndex].userInSystem = true;
+ userData->user[freeIndex].userEnabled = enabled;
+
+ return true;
+}
+
+void UserAccess::deleteUserIndex(const size_t& usrIdx)
+{
+ UsersTbl* userData = getUsersTblPtr();
+
+ std::string userName(
+ reinterpret_cast<char*>(userData->user[usrIdx].userName), 0,
+ ipmiMaxUserName);
+ ipmiClearUserEntryPassword(userName);
+ std::fill(static_cast<uint8_t*>(userData->user[usrIdx].userName),
+ static_cast<uint8_t*>(userData->user[usrIdx].userName) +
+ sizeof(userData->user[usrIdx].userName),
+ 0);
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+ {
+ userData->user[usrIdx].userPrivAccess[chIndex].privilege = privNoAccess;
+ userData->user[usrIdx].userPrivAccess[chIndex].ipmiEnabled = false;
+ userData->user[usrIdx].userPrivAccess[chIndex].linkAuthEnabled = false;
+ userData->user[usrIdx].userPrivAccess[chIndex].accessCallback = false;
+ }
+ userData->user[usrIdx].userInSystem = false;
+ userData->user[usrIdx].userEnabled = false;
+ return;
+}
+
+void UserAccess::checkAndReloadUserData()
+{
+ std::timespec updateTime = getUpdatedFileTime();
+ if ((updateTime.tv_sec != fileLastUpdatedTime.tv_sec ||
+ updateTime.tv_nsec != fileLastUpdatedTime.tv_nsec) ||
+ (updateTime.tv_sec == 0 && updateTime.tv_nsec == 0))
+ {
+ std::fill(reinterpret_cast<uint8_t*>(&usersTbl),
+ reinterpret_cast<uint8_t*>(&usersTbl) + sizeof(usersTbl), 0);
+ readUserData();
+ }
+ return;
+}
+
+UsersTbl* UserAccess::getUsersTblPtr()
+{
+ // reload data before using it.
+ checkAndReloadUserData();
+ return &usersTbl;
+}
+
+void UserAccess::getSystemPrivAndGroups()
+{
+ std::map<std::string, PrivAndGroupType> properties;
+ try
+ {
+ auto method = bus.new_method_call(userMgrService, userMgrObjBasePath,
+ dBusPropertiesInterface,
+ getAllPropertiesMethod);
+ method.append(userMgrInterface);
+
+ auto reply = bus.call(method);
+ reply.read(properties);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+ getAllPropertiesMethod, "PATH", userMgrObjBasePath);
+ return;
+ }
+ for (const auto& t : properties)
+ {
+ auto key = t.first;
+ if (key == allPrivProperty)
+ {
+ availablePrivileges = std::get<std::vector<std::string>>(t.second);
+ }
+ else if (key == allGrpProperty)
+ {
+ availableGroups = std::get<std::vector<std::string>>(t.second);
+ }
+ }
+ // TODO: Implement Supported Privilege & Groups verification logic
+ return;
+}
+
+std::timespec UserAccess::getUpdatedFileTime()
+{
+ struct stat fileStat;
+ if (stat(ipmiUserDataFile, &fileStat) != 0)
+ {
+ lg2::debug("Error in getting last updated time stamp");
+ return std::timespec{0, 0};
+ }
+ return fileStat.st_mtim;
+}
+
+void UserAccess::getUserProperties(const DbusUserObjProperties& properties,
+ std::vector<std::string>& usrGrps,
+ std::string& usrPriv, bool& usrEnabled)
+{
+ for (const auto& t : properties)
+ {
+ std::string key = t.first;
+ if (key == userPrivProperty)
+ {
+ usrPriv = std::get<std::string>(t.second);
+ }
+ else if (key == userGrpProperty)
+ {
+ usrGrps = std::get<std::vector<std::string>>(t.second);
+ }
+ else if (key == userEnabledProperty)
+ {
+ usrEnabled = std::get<bool>(t.second);
+ }
+ }
+ return;
+}
+
+int UserAccess::getUserObjProperties(const DbusUserObjValue& userObjs,
+ std::vector<std::string>& usrGrps,
+ std::string& usrPriv, bool& usrEnabled)
+{
+ auto usrObj = userObjs.find(usersInterface);
+ if (usrObj != userObjs.end())
+ {
+ getUserProperties(usrObj->second, usrGrps, usrPriv, usrEnabled);
+ return 0;
+ }
+ return -EIO;
+}
+
+void UserAccess::cacheUserDataFile()
+{
+ boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex>
+ userLock{*userMutex};
+ try
+ {
+ readUserData();
+ }
+ catch (const std::ios_base::failure& e)
+ { // File is empty, create it for the first time
+ std::fill(reinterpret_cast<uint8_t*>(&usersTbl),
+ reinterpret_cast<uint8_t*>(&usersTbl) + sizeof(usersTbl), 0);
+ // user index 0 is reserved, starts with 1
+ for (size_t userIndex = 1; userIndex <= ipmiMaxUsers; ++userIndex)
+ {
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex)
+ {
+ usersTbl.user[userIndex].userPrivAccess[chIndex].privilege =
+ privNoAccess;
+ usersTbl.user[userIndex]
+ .payloadAccess[chIndex]
+ .stdPayloadEnables1[static_cast<uint8_t>(
+ ipmi::PayloadType::SOL)] = true;
+ }
+ }
+ writeUserData();
+ }
+ // Create lock file if it does not exist
+ int fd = open(ipmiUserSignalLockFile, O_CREAT | O_TRUNC | O_SYNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0)
+ {
+ lg2::error("Error in creating IPMI user signal lock file");
+ throw std::ios_base::failure(
+ "Error in creating temporary IPMI user signal lock file");
+ }
+ close(fd);
+
+ sigHndlrLock = boost::interprocess::file_lock(ipmiUserSignalLockFile);
+ // Register it for single object and single process either netipmid /
+ // host-ipmid
+ if (userUpdatedSignal == nullptr && sigHndlrLock.try_lock())
+ {
+ lg2::debug("Registering signal handler");
+ userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::interface(dBusObjManager) +
+ sdbusplus::bus::match::rules::path(userMgrObjBasePath),
+ [&](sdbusplus::message_t& msg) {
+ userUpdatedSignalHandler(*this, msg);
+ });
+ userMgrRenamedSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::interface(userMgrInterface) +
+ sdbusplus::bus::match::rules::path(userMgrObjBasePath),
+ [&](sdbusplus::message_t& msg) {
+ userUpdatedSignalHandler(*this, msg);
+ });
+ userPropertiesSignal = std::make_unique<sdbusplus::bus::match_t>(
+ bus,
+ sdbusplus::bus::match::rules::type::signal() +
+ sdbusplus::bus::match::rules::path_namespace(userObjBasePath) +
+ sdbusplus::bus::match::rules::interface(
+ dBusPropertiesInterface) +
+ sdbusplus::bus::match::rules::member(propertiesChangedSignal) +
+ sdbusplus::bus::match::rules::argN(0, usersInterface),
+ [&](sdbusplus::message_t& msg) {
+ userUpdatedSignalHandler(*this, msg);
+ });
+ signalHndlrObject = true;
+ }
+ std::map<DbusUserObjPath, DbusUserObjValue> managedObjs;
+ try
+ {
+ auto method =
+ bus.new_method_call(userMgrService, userMgrObjBasePath,
+ dBusObjManager, getManagedObjectsMethod);
+ auto reply = bus.call(method);
+ reply.read(managedObjs);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD",
+ getManagedObjectsMethod, "PATH", userMgrObjBasePath);
+ return;
+ }
+ bool updateRequired = false;
+ UsersTbl* userData = &usersTbl;
+ // user index 0 is reserved, starts with 1
+ for (size_t usrIdx = 1; usrIdx <= ipmiMaxUsers; ++usrIdx)
+ {
+ if ((userData->user[usrIdx].userInSystem) &&
+ (userData->user[usrIdx].userName[0] != '\0'))
+ {
+ std::vector<std::string> usrGrps;
+ std::string usrPriv;
+
+ std::string userName(
+ reinterpret_cast<char*>(userData->user[usrIdx].userName), 0,
+ ipmiMaxUserName);
+ sdbusplus::message::object_path tempUserPath(userObjBasePath);
+ tempUserPath /= userName;
+ std::string usersPath(tempUserPath);
+
+ auto usrObj = managedObjs.find(usersPath);
+ if (usrObj != managedObjs.end())
+ {
+ bool usrEnabled = false;
+
+ // User exist. Lets check and update other fileds
+ getUserObjProperties(usrObj->second, usrGrps, usrPriv,
+ usrEnabled);
+ if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) ==
+ usrGrps.end())
+ {
+ updateRequired = true;
+ // Group "ipmi" is removed so lets remove user in IPMI
+ deleteUserIndex(usrIdx);
+ }
+ else
+ {
+ // Group "ipmi" is present so lets update other properties
+ // in IPMI
+ uint8_t priv = UserAccess::convertToIPMIPrivilege(usrPriv) &
+ privMask;
+ // Update all channels priv, only if it is not equivalent to
+ // getUsrMgmtSyncIndex()
+ if (userData->user[usrIdx]
+ .userPrivAccess[getUsrMgmtSyncIndex()]
+ .privilege != priv)
+ {
+ updateRequired = true;
+ for (size_t chIndex = 0; chIndex < ipmiMaxChannels;
+ ++chIndex)
+ {
+ userData->user[usrIdx]
+ .userPrivAccess[chIndex]
+ .privilege = priv;
+ }
+ }
+ if (userData->user[usrIdx].userEnabled != usrEnabled)
+ {
+ updateRequired = true;
+ userData->user[usrIdx].userEnabled = usrEnabled;
+ }
+ }
+
+ // We are done with this obj. lets delete from MAP
+ managedObjs.erase(usrObj);
+ }
+ else
+ {
+ updateRequired = true;
+ deleteUserIndex(usrIdx);
+ }
+ }
+ }
+
+ // Walk through remnaining managedObj users list
+ // Add them to ipmi data base
+ for (const auto& usrObj : managedObjs)
+ {
+ std::vector<std::string> usrGrps;
+ std::string usrPriv, userName;
+ bool usrEnabled = false;
+ std::string usrObjPath = std::string(usrObj.first);
+ if (getUserNameFromPath(usrObj.first.str, userName) != 0)
+ {
+ lg2::error("Error in user object path");
+ continue;
+ }
+ getUserObjProperties(usrObj.second, usrGrps, usrPriv, usrEnabled);
+ // Add 'ipmi' group users
+ if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) !=
+ usrGrps.end())
+ {
+ updateRequired = true;
+ // CREATE NEW USER
+ if (true != addUserEntry(userName, usrPriv, usrEnabled))
+ {
+ break;
+ }
+ }
+ }
+
+ if (updateRequired)
+ {
+ // All userData slots update done. Lets write the data
+ writeUserData();
+ }
+
+ return;
+}
+} // namespace ipmi
diff --git a/user_channel/user_mgmt.hpp b/user_channel/user_mgmt.hpp
new file mode 100644
index 0000000..74166c2
--- /dev/null
+++ b/user_channel/user_mgmt.hpp
@@ -0,0 +1,406 @@
+/*.
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include "user_layer.hpp"
+
+#include <boost/interprocess/sync/file_lock.hpp>
+#include <boost/interprocess/sync/named_recursive_mutex.hpp>
+#include <ipmid/api.hpp>
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+#include <ctime>
+#include <variant>
+
+namespace ipmi
+{
+
+using DbusUserPropVariant =
+ std::variant<std::vector<std::string>, std::string, bool>;
+
+using DbusUserObjPath = sdbusplus::message::object_path;
+
+using DbusUserObjProperties =
+ std::vector<std::pair<std::string, DbusUserPropVariant>>;
+
+using DbusUserObjValue = std::map<std::string, DbusUserObjProperties>;
+
+/**
+ * @enum User update events.
+ */
+enum class UserUpdateEvent
+{
+ reservedEvent,
+ userCreated,
+ userDeleted,
+ userRenamed,
+ userGrpUpdated,
+ userPrivUpdated,
+ userStateUpdated
+};
+
+/** @struct UserPrivAccess
+ *
+ * Structure for user privilege access (refer spec sec 22.22)
+ */
+struct UserPrivAccess
+{
+ uint8_t privilege;
+ bool ipmiEnabled;
+ bool linkAuthEnabled;
+ bool accessCallback;
+};
+
+/** @struct UserInfo
+ *
+ * Structure for user related information
+ */
+struct UserInfo
+{
+ uint8_t userName[ipmiMaxUserName];
+ UserPrivAccess userPrivAccess[ipmiMaxChannels];
+ bool userEnabled;
+ bool userInSystem;
+ bool fixedUserName;
+ PayloadAccess payloadAccess[ipmiMaxChannels];
+};
+
+/** @struct UsersTbl
+ *
+ * Structure for array of user related information
+ */
+struct UsersTbl
+{
+ //+1 to map with UserId directly. UserId 0 is reserved.
+ UserInfo user[ipmiMaxUsers + 1];
+};
+
+/** @brief PAM User Authentication check
+ *
+ * @param[in] username - username in string
+ * @param[in] password - password in string
+ *
+ * @return status
+ */
+bool pamUserCheckAuthenticate(std::string_view username,
+ std::string_view password);
+
+class UserAccess;
+
+UserAccess& getUserAccessObject();
+
+class UserAccess
+{
+ public:
+ UserAccess(const UserAccess&) = delete;
+ UserAccess& operator=(const UserAccess&) = delete;
+ UserAccess(UserAccess&&) = delete;
+ UserAccess& operator=(UserAccess&&) = delete;
+
+ ~UserAccess();
+ UserAccess();
+
+ /** @brief determines valid channel
+ *
+ * @param[in] chNum - channel number
+ *
+ * @return true if valid, false otherwise
+ */
+ static bool isValidChannel(const uint8_t chNum);
+
+ /** @brief determines valid userId
+ *
+ * @param[in] userId - user id
+ *
+ * @return true if valid, false otherwise
+ */
+ static bool isValidUserId(const uint8_t userId);
+
+ /** @brief determines valid user privilege
+ *
+ * @param[in] priv - Privilege
+ *
+ * @return true if valid, false otherwise
+ */
+ static bool isValidPrivilege(const uint8_t priv);
+
+ /** @brief determines sync index to be mapped with common-user-management
+ *
+ * @return Index which will be used as sync index
+ */
+ static uint8_t getUsrMgmtSyncIndex();
+
+ /** @brief Converts system privilege to IPMI privilege
+ *
+ * @param[in] value - Privilege in string
+ *
+ * @return CommandPrivilege - IPMI privilege type
+ */
+ static CommandPrivilege convertToIPMIPrivilege(const std::string& value);
+
+ /** @brief Converts IPMI privilege to system privilege
+ *
+ * @param[in] value - IPMI privilege
+ *
+ * @return System privilege in string
+ */
+ static std::string convertToSystemPrivilege(const CommandPrivilege& value);
+
+ /** @brief determines whether user name is valid
+ *
+ * @param[in] userNameInChar - user name
+ *
+ * @return true if valid, false otherwise
+ */
+ bool isValidUserName(const std::string& userName);
+
+ /** @brief determines whether ipmi is in available groups list
+ *
+ * @return true if ipmi group is present, false otherwise
+ */
+ bool isIpmiInAvailableGroupList();
+
+ /** @brief provides user id of the user
+ *
+ * @param[in] userName - user name
+ *
+ * @return user id of the user, else invalid user id (0xFF), if user not
+ * found
+ */
+ uint8_t getUserId(const std::string& userName);
+
+ /** @brief provides user information
+ *
+ * @param[in] userId - user id
+ *
+ * @return UserInfo for the specified user id
+ */
+ UserInfo* getUserInfo(const uint8_t userId);
+
+ /** @brief sets user information
+ *
+ * @param[in] userId - user id
+ * @param[in] userInfo - user information
+ *
+ */
+ void setUserInfo(const uint8_t userId, UserInfo* userInfo);
+
+ /** @brief provides user name
+ *
+ * @param[in] userId - user id
+ * @param[out] userName - user name
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc getUserName(const uint8_t userId, std::string& userName);
+
+ /** @brief to set user name
+ *
+ * @param[in] userId - user id
+ * @param[in] userName - user name
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setUserName(const uint8_t userId, const std::string& userName);
+
+ /** @brief to set user enabled state
+ *
+ * @param[in] userId - user id
+ * @param[in] enabledState - enabled state of the user
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setUserEnabledState(const uint8_t userId, const bool& enabledState);
+
+ /** @brief to set user password
+ *
+ * @param[in] userId - user id
+ * @param[in] userPassword - new password of the user
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setUserPassword(const uint8_t userId, const char* userPassword);
+
+ /** @brief to set special user password
+ *
+ * @param[in] userName - user name
+ * @param[in] userPassword - new password of the user
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setSpecialUserPassword(const std::string& userName,
+ const SecureString& userPassword);
+
+ /** @brief to set user privilege and access details
+ *
+ * @param[in] userId - user id
+ * @param[in] chNum - channel number
+ * @param[in] privAccess - privilege access
+ * @param[in] otherPrivUpdates - other privilege update flag to update ipmi
+ * enable, link authentication and access callback
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum,
+ const UserPrivAccess& privAccess,
+ const bool& otherPrivUpdates);
+
+ /** @brief to get user payload access details from userInfo entry.
+ *
+ * @param[in] userInfo - userInfo entry in usersTbl.
+ * @param[out] stdPayload - stdPayloadEnables1 in a 2D-array.
+ * @param[out] oemPayload - oemPayloadEnables1 in a 2D-array.
+ *
+ * @details Update the given 2D-arrays using the payload access details
+ * available in the given userInfo entry (from usersTbl).
+ * This 2D-array will be mapped to a JSON object (which will be written to
+ * a JSON file subsequently).
+ */
+ void readPayloadAccessFromUserInfo(
+ const UserInfo& userInfo,
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ stdPayload,
+ std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ oemPayload);
+
+ /** @brief to update user payload access details in userInfo entry.
+ *
+ * @param[in] stdPayload - stdPayloadEnables1 in a 2D-array.
+ * @param[in] oemPayload - oemPayloadEnables1 in a 2D-array.
+ * @param[out] userInfo - userInfo entry in usersTbl.
+ *
+ * @details Update user payload access details of a given userInfo
+ * entry (in usersTbl) with the information provided in given 2D-arrays.
+ * This 2D-array was created out of a JSON object (which was created by
+ * parsing a JSON file).
+ */
+ void updatePayloadAccessInUserInfo(
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ stdPayload,
+ const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>&
+ oemPayload,
+ UserInfo& userInfo);
+
+ /** @brief to set user payload access details
+ *
+ * @param[in] chNum - channel number
+ * @param[in] operation - Enable / Disable
+ * @param[in] userId - user id
+ * @param[in] payloadAccess - payload access
+ *
+ * @return ccSuccess for success, others for failure.
+ */
+ Cc setUserPayloadAccess(const uint8_t chNum, const uint8_t operation,
+ const uint8_t userId,
+ const PayloadAccess& payloadAccess);
+
+ /** @brief reads user management related data from configuration file
+ *
+ */
+ void readUserData();
+
+ /** @brief writes user management related data to configuration file
+ *
+ */
+ void writeUserData();
+
+ /** @brief Funtion which checks and reload configuration file data if
+ * needed.
+ *
+ */
+ void checkAndReloadUserData();
+
+ /** @brief provides user details from D-Bus user property data
+ *
+ * @param[in] properties - D-Bus user property
+ * @param[out] usrGrps - user group details
+ * @param[out] usrPriv - user privilege
+ * @param[out] usrEnabled - enabled state of the user.
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ void getUserProperties(const DbusUserObjProperties& properties,
+ std::vector<std::string>& usrGrps,
+ std::string& usrPriv, bool& usrEnabled);
+
+ /** @brief provides user details from D-Bus user object data
+ *
+ * @param[in] userObjs - D-Bus user object
+ * @param[out] usrGrps - user group details
+ * @param[out] usrPriv - user privilege
+ * @param[out] usrEnabled - enabled state of the user.
+ *
+ * @return 0 for success, -errno for failure.
+ */
+ int getUserObjProperties(const DbusUserObjValue& userObjs,
+ std::vector<std::string>& usrGrps,
+ std::string& usrPriv, bool& usrEnabled);
+
+ /** @brief function to add user entry information to the configuration
+ *
+ * @param[in] userName - user name
+ * @param[in] priv - privilege of the user
+ * @param[in] enabled - enabled state of the user
+ *
+ * @return true for success, false for failure
+ */
+ bool addUserEntry(const std::string& userName, const std::string& priv,
+ const bool& enabled);
+
+ /** @brief function to delete user entry based on user index
+ *
+ * @param[in] usrIdx - user index
+ *
+ */
+ void deleteUserIndex(const size_t& usrIdx);
+
+ /** @brief function to get users table
+ *
+ */
+ UsersTbl* getUsersTblPtr();
+
+ std::unique_ptr<boost::interprocess::named_recursive_mutex> userMutex{
+ nullptr};
+
+ private:
+ UsersTbl usersTbl;
+ std::vector<std::string> availablePrivileges;
+ std::vector<std::string> availableGroups;
+ sdbusplus::bus_t bus;
+ std::timespec fileLastUpdatedTime;
+ bool signalHndlrObject = false;
+ boost::interprocess::file_lock sigHndlrLock;
+ boost::interprocess::file_lock mutexCleanupLock;
+
+ /** @brief function to get user configuration file timestamp
+ *
+ * @return time stamp or -EIO for failure
+ */
+ std::timespec getUpdatedFileTime();
+
+ /** @brief function to available system privileges and groups
+ *
+ */
+ void getSystemPrivAndGroups();
+
+ /** @brief function to init user data from configuration & D-Bus objects
+ * and to register for signals
+ *
+ */
+ void cacheUserDataFile();
+};
+
+} // namespace ipmi
diff --git a/user_channel/usercommands.cpp b/user_channel/usercommands.cpp
new file mode 100644
index 0000000..3e1b3ca
--- /dev/null
+++ b/user_channel/usercommands.cpp
@@ -0,0 +1,679 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "usercommands.hpp"
+
+#include "channel_layer.hpp"
+#include "user_layer.hpp"
+
+#include <security/pam_appl.h>
+
+#include <ipmid/api.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <regex>
+
+namespace ipmi
+{
+
+static constexpr uint8_t enableOperation = 0x00;
+static constexpr uint8_t disableOperation = 0x01;
+
+/** @brief implements the set user access command
+ * @param ctx - IPMI context pointer (for channel)
+ * @param channel - channel number
+ * @param ipmiEnabled - indicates ipmi messaging state
+ * @param linkAuthEnabled - indicates link authentication state
+ * @param accessCallback - indicates callback state
+ * @param bitsUpdate - indicates update request
+ * @param userId - user id
+ * @param reserved1 - skip 2 bits
+ * @param privilege - user privilege
+ * @param reserved2 - skip 4 bits
+ * @param sessionLimit - optional - unused for now
+ *
+ * @returns ipmi completion code
+ */
+ipmi::RspType<> ipmiSetUserAccess(
+ ipmi::Context::ptr ctx, uint4_t channel, uint1_t ipmiEnabled,
+ uint1_t linkAuthEnabled, uint1_t accessCallback, uint1_t bitsUpdate,
+
+ uint6_t userId, uint2_t reserved1,
+
+ uint4_t privilege, uint4_t reserved2,
+
+ std::optional<uint8_t> sessionLimit)
+{
+ uint8_t sessLimit = sessionLimit.value_or(0);
+ if (reserved1 || reserved2 || sessLimit ||
+ !ipmiUserIsValidPrivilege(static_cast<uint8_t>(privilege)))
+ {
+ lg2::debug("Set user access - Invalid field in request");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+ if (!isValidChannel(chNum))
+ {
+ lg2::debug("Set user access - Invalid channel request");
+ return ipmi::response(invalidChannel);
+ }
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Set user access - No support on channel");
+ return ipmi::response(ccActionNotSupportedForChannel);
+ }
+ if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+ {
+ lg2::debug("Set user access - Parameter out of range");
+ return ipmi::responseParmOutOfRange();
+ }
+
+ PrivAccess privAccess = {};
+ if (bitsUpdate)
+ {
+ privAccess.ipmiEnabled = static_cast<uint8_t>(ipmiEnabled);
+ privAccess.linkAuthEnabled = static_cast<uint8_t>(linkAuthEnabled);
+ privAccess.accessCallback = static_cast<uint8_t>(accessCallback);
+ }
+ privAccess.privilege = static_cast<uint8_t>(privilege);
+ return ipmi::response(
+ ipmiUserSetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
+ privAccess, static_cast<bool>(bitsUpdate)));
+}
+
+/** @brief implements the set user access command
+ * @param ctx - IPMI context pointer (for channel)
+ * @param channel - channel number
+ * @param reserved1 - skip 4 bits
+ * @param userId - user id
+ * @param reserved2 - skip 2 bits
+ *
+ * @returns ipmi completion code plus response data
+ * - maxChUsers - max channel users
+ * - reserved1 - skip 2 bits
+ * - enabledUsers - enabled users count
+ * - enabledStatus - enabled status
+ * - fixedUsers - fixed users count
+ * - reserved2 - skip 2 bits
+ * - privilege - user privilege
+ * - ipmiEnabled - ipmi messaging state
+ * - linkAuthEnabled - link authenticatin state
+ * - accessCallback - callback state
+ * - reserved - skip 1 bit
+ */
+ipmi::RspType<uint6_t, // max channel users
+ uint2_t, // reserved1
+
+ uint6_t, // enabled users count
+ uint2_t, // enabled status
+
+ uint6_t, // fixed users count
+ uint2_t, // reserved2
+
+ uint4_t, // privilege
+ uint1_t, // ipmi messaging state
+ uint1_t, // link authentication state
+ uint1_t, // access callback state
+ uint1_t // reserved3
+ >
+ ipmiGetUserAccess(ipmi::Context::ptr ctx, uint4_t channel,
+ uint4_t reserved1,
+
+ uint6_t userId, uint2_t reserved2)
+{
+ uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+ if (reserved1 || reserved2 || !isValidChannel(chNum))
+ {
+ lg2::debug("Get user access - Invalid field in request");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ lg2::debug("Get user access - No support on channel");
+ return ipmi::response(ccActionNotSupportedForChannel);
+ }
+ if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+ {
+ lg2::debug("Get user access - Parameter out of range");
+ return ipmi::responseParmOutOfRange();
+ }
+
+ uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
+ ipmi::Cc retStatus;
+ retStatus = ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
+ if (retStatus != ccSuccess)
+ {
+ return ipmi::response(retStatus);
+ }
+
+ bool enabledState = false;
+ retStatus =
+ ipmiUserCheckEnabled(static_cast<uint8_t>(userId), enabledState);
+ if (retStatus != ccSuccess)
+ {
+ return ipmi::response(retStatus);
+ }
+
+ uint2_t enabledStatus = enabledState ? userIdEnabledViaSetPassword
+ : userIdDisabledViaSetPassword;
+ PrivAccess privAccess{};
+ retStatus = ipmiUserGetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
+ privAccess);
+ if (retStatus != ccSuccess)
+ {
+ return ipmi::response(retStatus);
+ }
+ constexpr uint2_t res2Bits = 0;
+ return ipmi::responseSuccess(
+ static_cast<uint6_t>(maxChUsers), res2Bits,
+
+ static_cast<uint6_t>(enabledUsers), enabledStatus,
+
+ static_cast<uint6_t>(fixedUsers), res2Bits,
+
+ static_cast<uint4_t>(privAccess.privilege),
+ static_cast<uint1_t>(privAccess.ipmiEnabled),
+ static_cast<uint1_t>(privAccess.linkAuthEnabled),
+ static_cast<uint1_t>(privAccess.accessCallback),
+ static_cast<uint1_t>(privAccess.reserved));
+}
+
+/** @brief implementes the get user name command
+ * @param[in] ctx - ipmi command context
+ * @param[in] userId - 6-bit user ID
+ * @param[in] reserved - 2-bits reserved
+ * @param[in] name - 16-byte array for username
+
+ * @returns ipmi response
+ */
+ipmi::RspType<> ipmiSetUserName(
+ [[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id, uint2_t reserved,
+ const std::array<uint8_t, ipmi::ipmiMaxUserName>& name)
+{
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ uint8_t userId = static_cast<uint8_t>(id);
+ if (!ipmiUserIsValidUserId(userId))
+ {
+ lg2::debug("Set user name - Invalid user id");
+ return ipmi::responseParmOutOfRange();
+ }
+
+ size_t nameLen = strnlen(reinterpret_cast<const char*>(name.data()),
+ ipmi::ipmiMaxUserName);
+ const std::string strUserName(reinterpret_cast<const char*>(name.data()),
+ nameLen);
+
+ ipmi::Cc res = ipmiUserSetUserName(userId, strUserName);
+ return ipmi::response(res);
+}
+
+/** @brief implementes the get user name command
+ * @param[in] ctx - ipmi command context
+ * @param[in] userId - 6-bit user ID
+ * @param[in] reserved - 2-bits reserved
+
+ * @returns ipmi response with 16-byte username
+ */
+ipmi::RspType<std::array<uint8_t, ipmi::ipmiMaxUserName>> // user name
+ ipmiGetUserName([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
+ uint2_t reserved)
+{
+ if (reserved)
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ uint8_t userId = static_cast<uint8_t>(id);
+ std::string userName;
+ if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+ { // Invalid User ID
+ lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+ userId);
+ return ipmi::responseParmOutOfRange();
+ }
+ // copy the std::string into a fixed array
+ if (userName.size() > ipmi::ipmiMaxUserName)
+ {
+ return ipmi::responseUnspecifiedError();
+ }
+ std::array<uint8_t, ipmi::ipmiMaxUserName> userNameFixed;
+ std::fill(userNameFixed.begin(), userNameFixed.end(), 0);
+ std::copy(userName.begin(), userName.end(), userNameFixed.begin());
+ return ipmi::responseSuccess(std::move(userNameFixed));
+}
+
+/** @brief implementes the get user name command
+ * @param[in] ctx - ipmi command context
+ * @param[in] userId - 6-bit user ID
+ * @param[in] reserved - 2-bits reserved
+
+ * @returns ipmi response with 16-byte username
+ */
+ipmi::RspType<> // user name
+ ipmiSetUserPassword([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
+ bool reserved1, bool pwLen20, uint2_t operation,
+ uint6_t reserved2, SecureBuffer& userPassword)
+{
+ if (reserved1 || reserved2)
+ {
+ lg2::debug("Invalid data field in request");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ static constexpr uint2_t opDisableUser = 0x00;
+ static constexpr uint2_t opEnableUser = 0x01;
+ static constexpr uint2_t opSetPassword = 0x02;
+ static constexpr uint2_t opTestPassword = 0x03;
+
+ // If set / test password operation then password size has to be 16 or 20
+ // bytes based on the password size bit
+ if (((operation == opSetPassword) || (operation == opTestPassword)) &&
+ ((pwLen20 && (userPassword.size() != maxIpmi20PasswordSize)) ||
+ (!pwLen20 && (userPassword.size() != maxIpmi15PasswordSize))))
+ {
+ lg2::debug("Invalid Length");
+ return ipmi::responseReqDataLenInvalid();
+ }
+
+ size_t passwordLength = userPassword.size();
+
+ uint8_t userId = static_cast<uint8_t>(id);
+ std::string userName;
+ if (ipmiUserGetUserName(userId, userName) != ccSuccess)
+ {
+ lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
+ userId);
+ return ipmi::responseParmOutOfRange();
+ }
+
+ if (operation == opSetPassword)
+ {
+ // turn the non-nul terminated SecureBuffer into a SecureString
+ SecureString password(
+ reinterpret_cast<const char*>(userPassword.data()), passwordLength);
+ ipmi::Cc res = ipmiUserSetUserPassword(userId, password.data());
+ return ipmi::response(res);
+ }
+ else if (operation == opEnableUser || operation == opDisableUser)
+ {
+ ipmi::Cc res =
+ ipmiUserUpdateEnabledState(userId, static_cast<bool>(operation));
+ return ipmi::response(res);
+ }
+ else if (operation == opTestPassword)
+ {
+ SecureString password = ipmiUserGetPassword(userName);
+ // extend with zeros, if needed
+ if (password.size() < passwordLength)
+ {
+ password.resize(passwordLength, '\0');
+ }
+ SecureString testPassword(
+ reinterpret_cast<const char*>(userPassword.data()), passwordLength);
+ // constant time string compare: always compare exactly as many bytes
+ // as the length of the input, resizing the actual password to match,
+ // maintaining a knowledge if the sizes differed originally
+ static const std::array<char, maxIpmi20PasswordSize> empty = {'\0'};
+ size_t cmpLen = testPassword.size();
+ bool pwLenDiffers = password.size() != cmpLen;
+ const char* cmpPassword = nullptr;
+ if (pwLenDiffers)
+ {
+ cmpPassword = empty.data();
+ }
+ else
+ {
+ cmpPassword = password.data();
+ }
+ bool pwBad = CRYPTO_memcmp(cmpPassword, testPassword.data(), cmpLen);
+ pwBad |= pwLenDiffers;
+ if (pwBad)
+ {
+ lg2::debug("Test password failed, user Id: {USER_ID}", "USER_ID",
+ userId);
+ return ipmi::response(ipmiCCPasswdFailMismatch);
+ }
+ return ipmi::responseSuccess();
+ }
+ return ipmi::responseInvalidFieldRequest();
+}
+
+/** @brief implements the get channel authentication command
+ * @param ctx - IPMI context pointer (for channel)
+ * @param extData - get IPMI 2.0 extended data
+ * @param reserved1 - skip 3 bits
+ * @param chNum - channel number to get info about
+ * @param reserved2 - skip 4 bits
+ * @param privLevel - requested privilege level
+
+ * @returns ipmi completion code plus response data
+ * - channel number
+ * - rmcpAuthTypes - RMCP auth types (IPMI 1.5)
+ * - reserved1
+ * - extDataSupport - true for IPMI 2.0 extensions
+ * - anonymousLogin - true for anonymous login enabled
+ * - nullUsers - true for null user names enabled
+ * - nonNullUsers - true for non-null usernames enabled
+ * - userAuth - false for user authentication enabled
+ * - perMessageAuth - false for per message authentication enabled
+ * - KGStatus - true for Kg required for authentication
+ * - reserved2
+ * - rmcp - RMCP (IPMI 1.5) connection support
+ * - rmcpp - RMCP+ (IPMI 2.0) connection support
+ * - reserved3
+ * - oemID - OEM IANA of any OEM auth support
+ * - oemAuxillary - OEM data for auth
+ */
+ipmi::RspType<uint8_t, // channel number
+ uint6_t, // rmcpAuthTypes
+ bool, // reserved1
+ bool, // extDataSupport
+ bool, // anonymousLogin
+ bool, // nullUsers
+ bool, // nonNullUsers
+ bool, // userAuth
+ bool, // perMessageAuth
+ bool, // KGStatus
+ uint2_t, // reserved2
+ bool, // rmcp
+ bool, // rmcpp
+ uint6_t, // reserved3
+ uint24_t, // oemID
+ uint8_t // oemAuxillary
+ >
+ ipmiGetChannelAuthenticationCapabilities(
+ ipmi::Context::ptr ctx, uint4_t chNum, uint3_t reserved1,
+ [[maybe_unused]] bool extData, uint4_t privLevel, uint4_t reserved2)
+{
+ uint8_t channel =
+ convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx->channel);
+
+ if (reserved1 || reserved2 || !isValidChannel(channel) ||
+ !isValidPrivLimit(static_cast<uint8_t>(privLevel)))
+ {
+ lg2::debug("Get channel auth capabilities - Invalid field in request");
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if (getChannelSessionSupport(channel) == EChannelSessSupported::none)
+ {
+ lg2::debug("Get channel auth capabilities - No support on channel");
+ return ipmi::response(ccActionNotSupportedForChannel);
+ }
+
+ constexpr bool extDataSupport = true; // true for IPMI 2.0 extensions
+ constexpr bool reserved3 = false;
+ constexpr uint6_t rmcpAuthTypes = 0; // IPMI 1.5 auth types - not supported
+ constexpr uint2_t reserved4 = 0;
+ constexpr bool KGStatus = false; // Not supporting now.
+ constexpr bool perMessageAuth = false; // Per message auth - enabled
+ constexpr bool userAuth = false; // User authentication - enabled
+ constexpr bool nullUsers = false; // Null user names - not supported
+ constexpr bool anonymousLogin = false; // Anonymous login - not supported
+ constexpr uint6_t reserved5 = 0;
+ constexpr bool rmcpp = true; // IPMI 2.0 - supported
+ constexpr bool rmcp = false; // IPMI 1.5 - not supported
+ constexpr uint24_t oemID = 0;
+ constexpr uint8_t oemAuxillary = 0;
+
+ bool nonNullUsers = 0;
+ uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
+ ipmi::ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
+ nonNullUsers = enabledUsers > 0;
+
+ return ipmi::responseSuccess(
+ channel, rmcpAuthTypes, reserved3, extDataSupport, anonymousLogin,
+ nullUsers, nonNullUsers, userAuth, perMessageAuth, KGStatus, reserved4,
+ rmcp, rmcpp, reserved5, oemID, oemAuxillary);
+}
+
+/** @brief implements the set user payload access command.
+ * @param ctx - IPMI context pointer (for channel)
+ * @param channel - channel number (4 bits)
+ * @param reserved1 - skip 4 bits
+ * @param userId - user id (6 bits)
+ * @param operation - access ENABLE /DISABLE. (2 bits)
+ * @param stdPayload0 - IPMI - reserved. (1 bit)
+ * @param stdPayload1 - SOL. (1 bit)
+ * @param stdPayload2 - (1 bit)
+ * @param stdPayload3 - (1 bit)
+ * @param stdPayload4 - (1 bit)
+ * @param stdPayload5 - (1 bit)
+ * @param stdPayload6 - (1 bit)
+ * @param stdPayload7 - (1 bit)
+ * @param stdPayloadEnables2Reserved - (8 bits)
+ * @param oemPayload0 - (1 bit)
+ * @param oemPayload1 - (1 bit)
+ * @param oemPayload2 - (1 bit)
+ * @param oemPayload3 - (1 bit)
+ * @param oemPayload4 - (1 bit)
+ * @param oemPayload5 - (1 bit)
+ * @param oemPayload6 - (1 bit)
+ * @param oemPayload7 - (1 bit)
+ * @param oemPayloadEnables2Reserved - (8 bits)
+ *
+ * @returns IPMI completion code
+ */
+ipmi::RspType<> ipmiSetUserPayloadAccess(
+ ipmi::Context::ptr ctx,
+
+ uint4_t channel, uint4_t reserved,
+
+ uint6_t userId, uint2_t operation,
+
+ bool stdPayload0ipmiReserved, bool stdPayload1SOL, bool stdPayload2,
+ bool stdPayload3, bool stdPayload4, bool stdPayload5, bool stdPayload6,
+ bool stdPayload7,
+
+ uint8_t stdPayloadEnables2Reserved,
+
+ bool oemPayload0, bool oemPayload1, bool oemPayload2, bool oemPayload3,
+ bool oemPayload4, bool oemPayload5, bool oemPayload6, bool oemPayload7,
+
+ uint8_t oemPayloadEnables2Reserved)
+{
+ auto chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+ // Validate the reserved args. Only SOL payload is supported as on date.
+ if (reserved || stdPayload0ipmiReserved || stdPayload2 || stdPayload3 ||
+ stdPayload4 || stdPayload5 || stdPayload6 || stdPayload7 ||
+ oemPayload0 || oemPayload1 || oemPayload2 || oemPayload3 ||
+ oemPayload4 || oemPayload5 || oemPayload6 || oemPayload7 ||
+ stdPayloadEnables2Reserved || oemPayloadEnables2Reserved ||
+ !isValidChannel(chNum))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+
+ if ((operation != enableOperation && operation != disableOperation))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ return ipmi::response(ccActionNotSupportedForChannel);
+ }
+ if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ PayloadAccess payloadAccess = {};
+ payloadAccess.stdPayloadEnables1[1] = stdPayload1SOL;
+
+ return ipmi::response(ipmiUserSetUserPayloadAccess(
+ chNum, static_cast<uint8_t>(operation), static_cast<uint8_t>(userId),
+ payloadAccess));
+}
+
+/** @brief implements the get user payload access command
+ * This command returns information about user payload enable settings
+ * that were set using the 'Set User Payload Access' Command.
+ *
+ * @param ctx - IPMI context pointer (for channel)
+ * @param channel - channel number
+ * @param reserved1 - skip 4 bits
+ * @param userId - user id
+ * @param reserved2 - skip 2 bits
+ *
+ * @returns IPMI completion code plus response data
+ * - stdPayload0ipmiReserved - IPMI payload (reserved).
+ * - stdPayload1SOL - SOL payload
+ * - stdPayload2
+ * - stdPayload3
+ * - stdPayload4
+ * - stdPayload5
+ * - stdPayload6
+ * - stdPayload7
+
+ * - stdPayloadEnables2Reserved - Reserved.
+
+ * - oemPayload0
+ * - oemPayload1
+ * - oemPayload2
+ * - oemPayload3
+ * - oemPayload4
+ * - oemPayload5
+ * - oemPayload6
+ * - oemPayload7
+
+ * - oemPayloadEnables2Reserved - Reserved
+ */
+ipmi::RspType<bool, // stdPayload0ipmiReserved
+ bool, // stdPayload1SOL
+ bool, // stdPayload2
+ bool, // stdPayload3
+ bool, // stdPayload4
+ bool, // stdPayload5
+ bool, // stdPayload6
+ bool, // stdPayload7
+
+ uint8_t, // stdPayloadEnables2Reserved
+
+ bool, // oemPayload0
+ bool, // oemPayload1
+ bool, // oemPayload2
+ bool, // oemPayload3
+ bool, // oemPayload4
+ bool, // oemPayload5
+ bool, // oemPayload6
+ bool, // oemPayload7
+
+ uint8_t // oemPayloadEnables2Reserved
+ >
+ ipmiGetUserPayloadAccess(ipmi::Context::ptr ctx,
+
+ uint4_t channel, uint4_t reserved1,
+
+ uint6_t userId, uint2_t reserved2)
+{
+ uint8_t chNum =
+ convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel);
+
+ if (reserved1 || reserved2 || !isValidChannel(chNum))
+ {
+ return ipmi::responseInvalidFieldRequest();
+ }
+ if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
+ {
+ return ipmi::response(ccActionNotSupportedForChannel);
+ }
+ if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
+ {
+ return ipmi::responseParmOutOfRange();
+ }
+
+ ipmi::Cc retStatus;
+ PayloadAccess payloadAccess = {};
+ retStatus = ipmiUserGetUserPayloadAccess(
+ chNum, static_cast<uint8_t>(userId), payloadAccess);
+ if (retStatus != ccSuccess)
+ {
+ return ipmi::response(retStatus);
+ }
+ constexpr uint8_t res8bits = 0;
+ return ipmi::responseSuccess(
+ payloadAccess.stdPayloadEnables1.test(0),
+ payloadAccess.stdPayloadEnables1.test(1),
+ payloadAccess.stdPayloadEnables1.test(2),
+ payloadAccess.stdPayloadEnables1.test(3),
+ payloadAccess.stdPayloadEnables1.test(4),
+ payloadAccess.stdPayloadEnables1.test(5),
+ payloadAccess.stdPayloadEnables1.test(6),
+ payloadAccess.stdPayloadEnables1.test(7),
+
+ res8bits,
+
+ payloadAccess.oemPayloadEnables1.test(0),
+ payloadAccess.oemPayloadEnables1.test(1),
+ payloadAccess.oemPayloadEnables1.test(2),
+ payloadAccess.oemPayloadEnables1.test(3),
+ payloadAccess.oemPayloadEnables1.test(4),
+ payloadAccess.oemPayloadEnables1.test(5),
+ payloadAccess.oemPayloadEnables1.test(6),
+ payloadAccess.oemPayloadEnables1.test(7),
+
+ res8bits);
+}
+
+void registerUserIpmiFunctions() __attribute__((constructor));
+void registerUserIpmiFunctions()
+{
+ post_work([]() { ipmiUserInit(); });
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetUserAccessCommand,
+ ipmi::Privilege::Admin, ipmiSetUserAccess);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetUserAccessCommand,
+ ipmi::Privilege::Admin, ipmiGetUserAccess);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetUserNameCommand,
+ ipmi::Privilege::Admin, ipmiGetUserName);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetUserName, ipmi::Privilege::Admin,
+ ipmiSetUserName);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetUserPasswordCommand,
+ ipmi::Privilege::Admin, ipmiSetUserPassword);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetChannelAuthCapabilities,
+ ipmi::Privilege::Callback,
+ ipmiGetChannelAuthenticationCapabilities);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdSetUserPayloadAccess,
+ ipmi::Privilege::Admin, ipmiSetUserPayloadAccess);
+
+ ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
+ ipmi::app::cmdGetUserPayloadAccess,
+ ipmi::Privilege::Operator, ipmiGetUserPayloadAccess);
+
+ return;
+}
+} // namespace ipmi
diff --git a/user_channel/usercommands.hpp b/user_channel/usercommands.hpp
new file mode 100644
index 0000000..a1fb7b9
--- /dev/null
+++ b/user_channel/usercommands.hpp
@@ -0,0 +1,36 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include <cstdint>
+
+namespace ipmi
+{
+
+/**
+ * @enum IPMI set password return codes (refer spec sec 22.30)
+ */
+enum ipmi_set_password_return_codes : uint8_t
+{
+ ipmiCCPasswdFailMismatch = 0x80,
+ ipmiCCPasswdFailWrongSize = 0x81,
+};
+
+static constexpr uint8_t userIdEnabledViaSetPassword = 0x1;
+static constexpr uint8_t userIdDisabledViaSetPassword = 0x2;
+
+void registerUserIpmiFunctions();
+} // namespace ipmi
diff --git a/whitelist-filter.cpp b/whitelist-filter.cpp
new file mode 100644
index 0000000..7fe4e22
--- /dev/null
+++ b/whitelist-filter.cpp
@@ -0,0 +1,253 @@
+#include <ipmiallowlist.hpp>
+#include <ipmid/api.hpp>
+#include <ipmid/utils.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <settings.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
+
+#include <algorithm>
+#include <array>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::error::xyz::openbmc_project::common;
+
+namespace ipmi
+{
+
+// put the filter provider in an unnamed namespace
+namespace
+{
+
+/** @class AllowlistFilter
+ *
+ * Class that implements an IPMI message filter based
+ * on incoming interface and a restriction mode setting
+ */
+class AllowlistFilter
+{
+ public:
+ AllowlistFilter();
+ ~AllowlistFilter() = default;
+ AllowlistFilter(const AllowlistFilter&) = delete;
+ AllowlistFilter(AllowlistFilter&&) = delete;
+ AllowlistFilter& operator=(const AllowlistFilter&) = delete;
+ AllowlistFilter& operator=(AllowlistFilter&&) = delete;
+
+ private:
+ void postInit();
+ void cacheRestrictedMode(const std::vector<std::string>& devices);
+ void handleRestrictedModeChange(
+ sdbusplus::message_t& m,
+ const std::map<std::string, size_t>& deviceList);
+ ipmi::Cc filterMessage(ipmi::message::Request::ptr request);
+
+ std::vector<bool> restrictedMode;
+ std::shared_ptr<sdbusplus::asio::connection> bus;
+ std::unique_ptr<settings::Objects> objects;
+ std::unique_ptr<sdbusplus::bus::match_t> modeChangeMatch;
+
+ static constexpr const char restrictionModeIntf[] =
+ "xyz.openbmc_project.Control.Security.RestrictionMode";
+};
+
+AllowlistFilter::AllowlistFilter()
+{
+ bus = getSdBus();
+
+ lg2::info("Loading allowlist filter");
+ ipmi::registerFilter(ipmi::prioOpenBmcBase,
+ [this](ipmi::message::Request::ptr request) {
+ return filterMessage(request);
+ });
+
+ // wait until io->run is going to fetch RestrictionMode
+ post_work([this]() { postInit(); });
+}
+
+/** @brief Get RestrictionMode of the devices which has RestrictionMode support
+ * enabled
+ * @param[in] devices - vector of devices object path
+ * @returns void.
+ */
+
+void AllowlistFilter::cacheRestrictedMode(
+ const std::vector<std::string>& devices)
+{
+ using namespace sdbusplus::server::xyz::openbmc_project::control::security;
+ std::string restrictionModeSetting;
+ std::string restrictionModeService;
+
+ for (auto& dev : devices)
+ {
+ try
+ {
+ restrictionModeSetting = dev;
+ restrictionModeService =
+ objects->service(restrictionModeSetting, restrictionModeIntf);
+ }
+ catch (const std::out_of_range& e)
+ {
+ lg2::error(
+ "Could not look up restriction mode interface from cache");
+ return;
+ }
+
+ std::string mode;
+ try
+ {
+ auto propValue = ipmi::getDbusProperty(
+ *bus, restrictionModeService, restrictionModeSetting,
+ restrictionModeIntf, "RestrictionMode");
+ mode = std::get<std::string>(propValue);
+ }
+ catch (const std::exception& e)
+ {
+ lg2::error("Error in RestrictionMode Get");
+ // Fail-safe to true.
+ size_t index = std::distance(&*std::begin(devices), &dev);
+ restrictedMode[index] = true;
+ }
+
+ auto restrictionMode = RestrictionMode::convertModesFromString(mode);
+
+ bool restrictMode =
+ (restrictionMode == RestrictionMode::Modes::Allowlist);
+ restrictedMode.emplace_back(restrictMode);
+
+ lg2::info("Set restrictedMode = {RESTRICTED_MODE}", "RESTRICTED_MODE",
+ restrictMode);
+ }
+}
+
+/** @brief Update RestrictionMode if any changes in RestrictionMode
+ * @param[in] m - sdbusplus message. Using this to get Updated Mode dbus path
+ * @param[in] deviceList - map to store devices path and their index
+ * @returns void.
+ */
+
+void AllowlistFilter::handleRestrictedModeChange(
+ sdbusplus::message_t& m, const std::map<std::string, size_t>& deviceList)
+{
+ using namespace sdbusplus::server::xyz::openbmc_project::control::security;
+ std::string intf;
+ std::vector<std::pair<std::string, ipmi::Value>> propertyList;
+ m.read(intf, propertyList);
+
+ std::string path = m.get_path();
+ size_t hostId = 0;
+ auto it = deviceList.find(path);
+
+ if (it == deviceList.end())
+ {
+ lg2::error("Key not found in deviceList ");
+ }
+ else
+ {
+ hostId = it->second;
+ }
+
+ for (const auto& property : propertyList)
+ {
+ if (property.first == "RestrictionMode")
+ {
+ RestrictionMode::Modes restrictionMode =
+ RestrictionMode::convertModesFromString(
+ std::get<std::string>(property.second));
+ bool restrictMode =
+ (restrictionMode == RestrictionMode::Modes::Allowlist);
+ restrictedMode[hostId] = restrictMode;
+
+ lg2::info("Updated restrictedMode = {RESTRICTED_MODE}",
+ "RESTRICTED_MODE", restrictMode);
+ }
+ }
+}
+
+/** @brief Get and Update RestrictionModes of supported devices
+ * @param[in] void
+ * @returns void.
+ */
+
+void AllowlistFilter::postInit()
+{
+ objects = std::make_unique<settings::Objects>(
+ *bus, std::vector<settings::Interface>({restrictionModeIntf}));
+ if (!objects)
+ {
+ lg2::error(
+ "Failed to create settings object; defaulting to restricted mode");
+ return;
+ }
+
+ std::vector<std::string> devices;
+ try
+ {
+ devices = objects->map.at(restrictionModeIntf);
+ }
+ catch (const std::out_of_range& e)
+ {
+ lg2::error("Could not look up restriction mode interface from cache");
+ return;
+ }
+
+ // Initialize restricted mode
+ cacheRestrictedMode(devices);
+ // Wait for changes on Restricted mode
+ std::map<std::string, size_t> deviceList;
+
+ for (size_t index = 0; index < devices.size(); index++)
+ {
+ deviceList.emplace(devices[index], index);
+ }
+
+ std::string filterStr;
+ std::string devicesDbusPath{"/xyz/openbmc_project/control"};
+
+ filterStr = sdbusplus::bus::match::rules::propertiesChangedNamespace(
+ devicesDbusPath, restrictionModeIntf);
+
+ modeChangeMatch = std::make_unique<sdbusplus::bus::match_t>(
+ *bus, filterStr, [this, deviceList](sdbusplus::message_t& m) {
+ handleRestrictedModeChange(m, deviceList);
+ });
+}
+
+/** @brief Filter IPMI messages with RestrictedMode
+ * @param[in] request - IPMI messahe request
+ * @returns IPMI completion code success or error.
+ */
+
+ipmi::Cc AllowlistFilter::filterMessage(ipmi::message::Request::ptr request)
+{
+ /* Getting hostIdx for all IPMI devices like hosts, debugcard and other
+ devices from ipmi::message::Request and call postInit() to get the
+ restriction mode for all the IPMI commands */
+
+ size_t hostIdx = request->ctx->hostIdx;
+
+ if (request->ctx->channel == ipmi::channelSystemIface &&
+ restrictedMode[hostIdx])
+ {
+ if (!std::binary_search(
+ allowlist.cbegin(), allowlist.cend(),
+ std::make_pair(request->ctx->netFn, request->ctx->cmd)))
+ {
+ lg2::error("Net function not allowlisted, "
+ "NetFn: {NETFN}, Cmd: {CMD}",
+ "NETFN", lg2::hex, request->ctx->netFn, "CMD", lg2::hex,
+ request->ctx->cmd);
+
+ return ipmi::ccInsufficientPrivilege;
+ }
+ }
+ return ipmi::ccSuccess;
+}
+
+// instantiate the AllowlistFilter when this shared object is loaded
+AllowlistFilter allowlistFilter;
+
+} // namespace
+
+} // namespace ipmi
diff --git a/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml
new file mode 100644
index 0000000..9e34734
--- /dev/null
+++ b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff.interface.yaml
@@ -0,0 +1,35 @@
+description: >
+ Implement the Soft Power Off function. On receiving the SMS_ATTN from BMC,
+ Host will respond with a GetMessageFlags command and the BMC will respond
+ with a static data indicating that Event Message Buffer is full. Host then
+ sends 'ReadEvent' command and BMC responds with an architected packet
+ mentioning that the type is SOFT_OFF. Host then goes ahead and starts to
+ quiesce. Once that is done, Host will send a hard power off command to BMC
+ and then BMC will issue a hard power off.
+
+properties:
+ - name: ResponseReceived
+ type: enum[self.HostResponse]
+ default: NotApplicable
+ description: >
+ When the response is received for 'SMS_ATN', this is set to
+ 'SoftOffReceived' and is set to 'HostShutdown' when Host sends a Power
+ Off request.
+
+enumerations:
+ - name: HostResponse
+ description: >
+ Possible response types from Host for a Soft Power Off function.
+ values:
+ - name: NotApplicable
+ description: >
+ Default initial value.
+ - name: SoftOffReceived
+ description: >
+ Host has received the SMS_ATN from BMC indicating that Host
+ needs to do a Soft Power Off.
+ - name: HostShutdown
+ description: >
+ Host has sufficiently quiesced and acknowledged the shutdown
+ request such that the hardware shutdown sequence can safely be
+ performed.
diff --git a/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build
new file mode 100644
index 0000000..56c2019
--- /dev/null
+++ b/xyz/openbmc_project/Ipmi/Internal/SoftPowerOff/meson.build
@@ -0,0 +1,69 @@
+sdbuspp_prog = find_program('sdbus++')
+
+domain = 'xyz.openbmc_project.Ipmi.Internal.SoftPowerOff'
+if_yaml_file = files('../SoftPowerOff.interface.yaml')
+
+if_cpp = custom_target(
+ 'server.cpp',
+ output: 'server.cpp',
+ input: if_yaml_file,
+ capture: true,
+ command: [sdbuspp_prog, '-r', root, 'interface', 'server-cpp', domain],
+)
+
+if_hpp = custom_target(
+ 'server.hpp',
+ output: 'server.hpp',
+ input: if_yaml_file,
+ capture: true,
+ command: [sdbuspp_prog, '-r', root, 'interface', 'server-header', domain],
+ install: true,
+ install_dir: get_option('includedir') / 'xyz/openbmc_project/Ipmi/Internal/SoftPowerOff',
+)
+
+if_common_hpp = custom_target(
+ 'common.hpp',
+ output: 'common.hpp',
+ input: if_yaml_file,
+ capture: true,
+ command: [sdbuspp_prog, '-r', root, 'interface', 'common-header', domain],
+ install: true,
+ install_dir: get_option('includedir') / 'xyz/openbmc_project/Ipmi/Internal/SoftPowerOff',
+)
+
+softoff_dbus_deps = [
+ dependency('phosphor-dbus-interfaces'),
+ dependency('sdbusplus'),
+]
+
+softoff_dbus_lib = library(
+ 'softoff-dbus',
+ [if_cpp, if_hpp, if_common_hpp],
+ implicit_include_directories: false,
+ include_directories: root_inc,
+ version: meson.project_version(),
+ dependencies: softoff_dbus_deps,
+ override_options: ['b_lundef=false'],
+ install: true,
+)
+
+softoff_dbus = declare_dependency(
+ dependencies: softoff_dbus_deps,
+ sources: [if_hpp, if_common_hpp],
+ link_with: softoff_dbus_lib,
+)
+
+softoff_dbus_reqs = []
+foreach dep : softoff_dbus_deps
+ if dep.type_name() == 'pkgconfig'
+ softoff_dbus_reqs += dep
+ endif
+endforeach
+
+import('pkgconfig').generate(
+ name: 'softoff-dbus',
+ description: 'SoftPowerOff DBus Bindings',
+ version: meson.project_version(),
+ libraries: softoff_dbus,
+ requires: softoff_dbus_reqs,
+)