diff options
82 files changed, 7856 insertions, 4288 deletions
@@ -66,14 +66,14 @@ Makefile /entity-gen.cpp # test related -/test/sample_unittest /test/*.log /test/*.trs /test-suite.log *-1.0-coverage* -test/*.gcda -test/*.gcno +*.gcda +*.gcno softoff/test +test/*_unittest # ignore vim swap files .*.sw* diff --git a/MAINTAINERS b/MAINTAINERS index 7849e57..b4f1e57 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -64,7 +64,6 @@ START OF MAINTAINERS LIST M: Vernon Mauery <vernon.mauery@linux.intel.com> <vmauery!> M: Tom Joseph <tomjose@linux.vnet.ibm.com> <tomjoseph!> -M: Emily Shaffer <emilyshaffer@google.com> <nasamuffin!> R: Adriana Kobylak <anoo@us.ibm.com> <anoo!> R: Deepak Kodihalli <dkodihal@linux.vnet.ibm.com> <dkodihal!> R: Ratan Gupta <ratagupt@linux.vnet.ibm.com> <rgupta!> diff --git a/Makefile.am b/Makefile.am index 864eb93..82fdd5d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,6 +14,12 @@ endif providersdir = ${libdir}/ipmid-providers providers_LTLIBRARIES = +if FEATURE_IPMI_WHITELIST +IPMI_WHITELIST_SOURCE = ipmiwhitelist.cpp +else +IPMI_WHITELIST_SOURCE = +endif + bin_PROGRAMS = \ ipmid @@ -25,11 +31,10 @@ ipmid_SOURCES = \ libipmi20_BUILT_LIST = \ sensor-gen.cpp \ inventory-sensor-gen.cpp \ - fru-read-gen.cpp \ - entity-gen.cpp + fru-read-gen.cpp BUILT_SOURCES = \ - ipmiwhitelist.cpp \ + $(IPMI_WHITELIST_SOURCE) \ $(libipmi20_BUILT_LIST) CLEANFILES = $(BUILT_SOURCES) @@ -70,19 +75,22 @@ ipmid_LDFLAGS = \ ipmiwhitelist.cpp: ${srcdir}/generate_whitelist.sh $(WHITELIST_CONF) $(SHELL) $^ > $@ -sensor-gen.cpp: +sensor-gen.cpp: scripts/writesensor.mako.cpp scripts/sensor_gen.py @SENSOR_YAML_GEN@ $(AM_V_GEN)@SENSORGEN@ -o $(top_builddir) generate-cpp -inventory-sensor-gen.cpp: +inventory-sensor-gen.cpp: scripts/inventorysensor.mako.cpp scripts/inventory-sensor.py @INVSENSOR_YAML_GEN@ $(AM_V_GEN)@INVSENSORGEN@ -o $(top_builddir) generate-cpp -fru-read-gen.cpp: +fru-read-gen.cpp: scripts/readfru.mako.cpp scripts/fru_gen.py @FRU_YAML_GEN@ $(AM_V_GEN)@FRUGEN@ -o $(top_builddir) generate-cpp -entity-gen.cpp: - $(AM_V_GEN)@ENTITYGEN@ -o $(top_builddir) generate-cpp - providers_LTLIBRARIES += libipmi20.la +if FEATURE_TRANSPORT_OEM +libipmi20_la_TRANSPORTOEM = transporthandler_oem.cpp +else +libipmi20_la_TRANSPORTOEM = +endif + libipmi20_la_SOURCES = \ app/channel.cpp \ app/watchdog.cpp \ @@ -90,6 +98,7 @@ libipmi20_la_SOURCES = \ apphandler.cpp \ sys_info_param.cpp \ sensorhandler.cpp \ + entity_map_json.cpp \ storagehandler.cpp \ chassishandler.cpp \ dcmihandler.cpp \ @@ -103,6 +112,7 @@ libipmi20_la_SOURCES = \ read_fru_data.cpp \ sensordatahandler.cpp \ user_channel/channelcommands.cpp \ + $(libipmi20_la_TRANSPORTOEM) \ $(libipmi20_BUILT_LIST) check_PROGRAMS = @@ -123,6 +133,7 @@ libipmi20_la_LDFLAGS = \ -version-info 0:0:0 -shared libipmi20_la_CXXFLAGS = $(COMMON_CXX) +if FEATURE_LIBUSERLAYER providers_LTLIBRARIES += libusercmds.la libusercmds_la_LIBADD = \ libipmid/libipmid.la \ @@ -135,6 +146,7 @@ libusercmds_la_LDFLAGS = \ $(libmapper_LIBS) \ -version-info 0:0:0 -shared libusercmds_la_CXXFLAGS = $(COMMON_CXX) +endif providers_LTLIBRARIES += libsysintfcmds.la libsysintfcmds_la_LIBADD = \ @@ -152,6 +164,7 @@ libsysintfcmds_la_LDFLAGS = \ -version-info 0:0:0 -shared libsysintfcmds_la_CXXFLAGS = $(COMMON_CXX) +if FEATURE_IPMI_WHITELIST libwhitelistdir = ${libdir}/ipmid-providers libwhitelist_LTLIBRARIES = libwhitelist.la libwhitelist_la_SOURCES = \ @@ -164,6 +177,7 @@ libwhitelist_la_LDFLAGS = \ -version-info 0:0:0 -shared libwhitelist_la_CXXFLAGS = $(COMMON_CXX) nodist_libwhitelist_la_SOURCES = ipmiwhitelist.cpp +endif nobase_include_HEADERS = \ user_channel/channel_layer.hpp \ diff --git a/app/channel.cpp b/app/channel.cpp index 6ea25c4..51e953d 100644 --- a/app/channel.cpp +++ b/app/channel.cpp @@ -1,6 +1,5 @@ #include "channel.hpp" -#include "transporthandler.hpp" #include "user_channel/channel_layer.hpp" #include <arpa/inet.h> @@ -97,26 +96,44 @@ std::pair<std::vector<uint8_t>, std::vector<uint8_t>> getCipherRecords() } // namespace cipher -ipmi_ret_t getChannelCipherSuites(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) +/** @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; - auto requestData = - reinterpret_cast<const GetChannelCipherRequest*>(request); + uint8_t rspChannel = ipmi::convertCurrentChannelNum( + static_cast<uint8_t>(channelNumber), ctx->channel); - if (*data_len < sizeof(GetChannelCipherRequest)) + if (!ipmi::isValidChannel(rspChannel) || reserved1 != 0 || reserved2 != 0) { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; + return ipmi::responseInvalidFieldRequest(); + } + if (!ipmi::isValidPayloadType(static_cast<ipmi::PayloadType>(payloadType))) + { + log<level::DEBUG>("Get channel cipher suites - Invalid payload type"); + constexpr uint8_t ccPayloadTypeNotSupported = 0x80; + return ipmi::response(ccPayloadTypeNotSupported); } - - *data_len = 0; if (!recordInit) { @@ -128,41 +145,37 @@ ipmi_ret_t getChannelCipherSuites(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } catch (const std::exception& e) { - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } - const auto& records = (cipher::listCipherSuite == - (requestData->listIndex & cipher::listTypeMask)) - ? cipherRecords - : supportedAlgorithms; + 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))) + { + log<level::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. - auto index = - static_cast<size_t>(requestData->listIndex & cipher::listIndexMask); // Calculate the number of record data bytes to be returned. - auto start = std::min(index * cipher::respSize, records.size()); - auto end = - std::min((index * cipher::respSize) + cipher::respSize, records.size()); + 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; - auto responseData = reinterpret_cast<GetChannelCipherRespHeader*>(response); - responseData->channelNumber = cipher::defaultChannelNumber; - - if (!size) - { - *data_len = sizeof(GetChannelCipherRespHeader); - } - else - { - std::copy_n(records.data() + start, size, - static_cast<uint8_t*>(response) + 1); - *data_len = size + sizeof(GetChannelCipherRespHeader); - } + std::vector<uint8_t> rspRecords; + std::copy_n(records.data() + start, size, std::back_inserter(rspRecords)); - return IPMI_CC_OK; + return ipmi::responseSuccess(rspChannel, rspRecords); } template <typename... ArgTypes> diff --git a/app/channel.hpp b/app/channel.hpp index 7004ddd..8e5accb 100644 --- a/app/channel.hpp +++ b/app/channel.hpp @@ -1,6 +1,6 @@ #include "nlohmann/json.hpp" -#include <ipmid/api.h> +#include <ipmid/api.hpp> /** @brief The set channel access IPMI command. * @@ -53,31 +53,31 @@ ipmi_ret_t ipmi_app_channel_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_data_len_t data_len, ipmi_context_t context); -/** @brief Implementation of get channel cipher suites command +/** @brief this command is used to look up what authentication, integrity, + * confidentiality algorithms are supported. * - * @param[in] netfn - Net Function - * @param[in] cmd - Command - * @param[in] request - Request pointer - * @param[in,out] response - Response pointer - * @param[in,out] data_len - Data Length - * @param[in] context - Context + * @ param ctx - context pointer + * @ param channelNumber - channel number + * @ param payloadType - payload type + * @ param listIndex - list index + * @ param algoSelectBit - list algorithms * - * @return IPMI_CC_OK on success, non-zero otherwise. - */ -ipmi_ret_t getChannelCipherSuites(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); + * @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 defaultChannelNumber = 1; -static constexpr auto listTypeMask = 0x80; static constexpr auto listCipherSuite = 0x80; -static constexpr auto listIndexMask = 0x3F; -static constexpr auto respSize = 16; using Json = nlohmann::json; static constexpr auto configFile = "/usr/share/ipmi-providers/cipher_list.json"; @@ -92,23 +92,3 @@ static constexpr auto conf = "confidentiality"; static constexpr auto confTag = 0x80; } // namespace cipher - -/** @struct GetChannelCipherRequest - * - * IPMI payload for Get Channel Cipher Suites command request - */ -struct GetChannelCipherRequest -{ - uint8_t channelNumber; //!< Channel Number - uint8_t payloadType; //!< Payload type number - uint8_t listIndex; //!< List Index -} __attribute__((packed)); - -/** @struct GetChannelCipherRespHeader - * - * IPMI payload for Get Channel Cipher Suites command response header - */ -struct GetChannelCipherRespHeader -{ - uint8_t channelNumber; //!< Channel Number -} __attribute__((packed)); diff --git a/app/watchdog.cpp b/app/watchdog.cpp index b1eea1c..03c373e 100644 --- a/app/watchdog.cpp +++ b/app/watchdog.cpp @@ -4,6 +4,7 @@ #include <endian.h> +#include <bitset> #include <cstdint> #include <ipmid/api.hpp> #include <phosphor-logging/elog-errors.hpp> @@ -68,13 +69,11 @@ ipmi::RspType<> ipmiAppResetWatchdogTimer() { const std::string e_str = std::string("wd_reset: ") + e.what(); log<level::ERR>(e_str.c_str()); - reportError(); return ipmi::responseUnspecifiedError(); } catch (...) { log<level::ERR>("wd_reset: Unknown Error"); - reportError(); return ipmi::responseUnspecifiedError(); } } @@ -82,7 +81,12 @@ ipmi::RspType<> ipmiAppResetWatchdogTimer() static constexpr uint8_t wd_dont_stop = 0x1 << 6; static constexpr uint8_t wd_timeout_action_mask = 0x3; -static constexpr uint8_t wdTimerUseMask = 0x7; +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 { @@ -168,85 +172,106 @@ WatchdogService::TimerUse ipmiTimerUseToWdTimerUse(IpmiTimerUse ipmiTimerUse) } } -struct wd_set_req -{ - uint8_t timer_use; - uint8_t timer_action; - uint8_t pretimeout; // (seconds) - uint8_t expire_flags; - uint16_t initial_countdown; // Little Endian (deciseconds) -} __attribute__((packed)); -static_assert(sizeof(wd_set_req) == 6, "wd_set_req has invalid size."); -static_assert(sizeof(wd_set_req) <= MAX_IPMI_BUFFER, - "wd_get_res can't fit in request buffer."); - -ipmi_ret_t ipmi_app_watchdog_set(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) +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) { - // Extract the request data - if (*data_len < sizeof(wd_set_req)) + if ((timerUse == wdTimerUseResTimer1) || + (timerUse == wdTimerUseResTimer2) || + (timerUse == wdTimerUseResTimer3) || + (timeoutAction > wdTimeoutActionMax) || + (preTimeoutInterrupt == wdTimeoutInterruptTimer) || + (reserved | reserved1 | reserved2 | + expFlagValue.test(wdExpirationFlagReservedBit0) | + expFlagValue.test(wdExpirationFlagReservedBit6) | + expFlagValue.test(wdExpirationFlagReservedBit7))) { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; + return ipmi::responseInvalidFieldRequest(); } - wd_set_req req; - memcpy(&req, request, sizeof(req)); - req.initial_countdown = le16toh(req.initial_countdown); - *data_len = 0; + + 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 (!(req.timer_use & wd_dont_stop)) + if (!(dontStopTimer)) { wd_service.setEnabled(false); } // Set the action based on the request - const auto ipmi_action = - static_cast<IpmiAction>(req.timer_action & wd_timeout_action_mask); + 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 = - static_cast<IpmiTimerUse>(req.timer_use & wdTimerUseMask); + static_cast<IpmiTimerUse>(static_cast<uint8_t>(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 = req.initial_countdown * 100; + const uint64_t interval = initialCountdown * 100; wd_service.setInterval(interval); - wd_service.setTimeRemaining(interval); + wd_service.resetTimeRemaining(false); // Mark as initialized so that future resets behave correctly wd_service.setInitialized(true); lastCallSuccessful = true; - return IPMI_CC_OK; + return ipmi::responseSuccess(); } catch (const std::domain_error&) { - return IPMI_CC_INVALID_FIELD_REQUEST; + return ipmi::responseInvalidFieldRequest(); } catch (const InternalFailure& e) { reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } catch (const std::exception& e) { const std::string e_str = std::string("wd_set: ") + e.what(); log<level::ERR>(e_str.c_str()); - reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } catch (...) { log<level::ERR>("wd_set: Unknown Error"); - reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } @@ -320,30 +345,37 @@ IpmiTimerUse wdTimerUseToIpmiTimerUse(WatchdogService::TimerUse wdTimerUse) } } -struct wd_get_res -{ - uint8_t timer_use; - uint8_t timer_action; - uint8_t pretimeout; - uint8_t expire_flags; - uint16_t initial_countdown; // Little Endian (deciseconds) - uint16_t present_countdown; // Little Endian (deciseconds) -} __attribute__((packed)); -static_assert(sizeof(wd_get_res) == 8, "wd_get_res has invalid size."); -static_assert(sizeof(wd_get_res) <= MAX_IPMI_BUFFER, - "wd_get_res can't fit in response buffer."); - -static constexpr uint8_t wd_dont_log = 0x1 << 7; static constexpr uint8_t wd_running = 0x1 << 6; -ipmi_ret_t ipmi_app_watchdog_get(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) +/**@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() { - // Assume we will fail and send no data outside the return code - *data_len = 0; + uint16_t presentCountdown = 0; + uint8_t pretimeout = 0; try { @@ -351,50 +383,58 @@ ipmi_ret_t ipmi_app_watchdog_get(ipmi_netfn_t netfn, ipmi_cmd_t cmd, WatchdogService::Properties wd_prop = wd_service.getProperties(); // Build and return the response - wd_get_res res; - res.timer_use = wd_dont_log; - res.timer_action = - static_cast<uint8_t>(wdActionToIpmiAction(wd_prop.expireAction)); - // Interval and timeRemaining need converted from milli -> deci seconds - res.initial_countdown = htole16(wd_prop.interval / 100); + 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) { - res.timer_use |= wd_running; - res.present_countdown = htole16(wd_prop.timeRemaining / 100); + presentCountdown = htole16(wd_prop.timeRemaining / 100); } else { - res.present_countdown = res.initial_countdown; + if (wd_prop.expiredTimerUse == WatchdogService::TimerUse::Reserved) + { + presentCountdown = initialCountdown; + } + else + { + presentCountdown = 0; + // Automatically clear it whenever a timer expiration occurs. + timerNotLogFlags = false; + } } - res.timer_use |= - static_cast<uint8_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse)); - // TODO: Do something about having pretimeout support - res.pretimeout = 0; - res.expire_flags = 0; - memcpy(response, &res, sizeof(res)); - *data_len = sizeof(res); + pretimeout = 0; + lastCallSuccessful = true; - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint3_t>(wdTimerUseToIpmiTimerUse(wd_prop.timerUse)), 0, + wd_prop.enabled, timerNotLogFlags, + static_cast<uint3_t>(wdActionToIpmiAction(wd_prop.expireAction)), 0, + timerPreTimeoutInterrupt, 0, pretimeout, timerUseExpirationFlags, + initialCountdown, presentCountdown); } catch (const InternalFailure& e) { reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } catch (const std::exception& e) { const std::string e_str = std::string("wd_get: ") + e.what(); log<level::ERR>(e_str.c_str()); - reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } catch (...) { log<level::ERR>("wd_get: Unknown Error"); - reportError(); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } diff --git a/app/watchdog.hpp b/app/watchdog.hpp index 3f91f4f..fa53ac7 100644 --- a/app/watchdog.hpp +++ b/app/watchdog.hpp @@ -6,35 +6,40 @@ */ ipmi::RspType<> ipmiAppResetWatchdogTimer(); -/** @brief The SET watchdog IPMI command. +/**@brief The setWatchdogTimer ipmi command. * - * @param[in] netfn - * @param[in] cmd - * @param[in] request - * @param[in,out] response - * @param[out] data_len - * @param[in] context + * @param + * - timerUse + * - dontStopTimer + * - dontLog + * - timerAction + * - pretimeout + * - expireFlags + * - initialCountdown * - * @return IPMI_CC_OK on success, an IPMI error code otherwise. - */ -ipmi_ret_t ipmi_app_watchdog_set(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); + * @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 GET watchdog IPMI command. - * @param[in] netfn - * @param[in] cmd - * @param[in] request - * @param[in,out] response - * @param[out] data_len - * @param[in] context +/**@brief The getWatchdogTimer ipmi command. * - * @return IPMI_CC_OK on success, an IPMI error code otherwise. - */ -ipmi_ret_t ipmi_app_watchdog_get(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); + * @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 index 284964d..3534e89 100644 --- a/app/watchdog_service.cpp +++ b/app/watchdog_service.cpp @@ -16,8 +16,6 @@ using phosphor::logging::elog; using phosphor::logging::entry; using phosphor::logging::level; using phosphor::logging::log; -using sdbusplus::message::variant_ns::get; -using sdbusplus::message::variant_ns::variant; using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using sdbusplus::xyz::openbmc_project::State::server::convertForMessage; using sdbusplus::xyz::openbmc_project::State::server::Watchdog; @@ -72,18 +70,22 @@ WatchdogService::Properties WatchdogService::getProperties() } try { - std::map<std::string, variant<bool, uint64_t, std::string>> properties; + std::map<std::string, std::variant<bool, uint64_t, std::string>> + properties; response.read(properties); Properties wd_prop; - wd_prop.initialized = get<bool>(properties.at("Initialized")); - wd_prop.enabled = get<bool>(properties.at("Enabled")); + wd_prop.initialized = std::get<bool>(properties.at("Initialized")); + wd_prop.enabled = std::get<bool>(properties.at("Enabled")); wd_prop.expireAction = Watchdog::convertActionFromString( - get<std::string>(properties.at("ExpireAction"))); + std::get<std::string>(properties.at("ExpireAction"))); wd_prop.timerUse = Watchdog::convertTimerUseFromString( - get<std::string>(properties.at("CurrentTimerUse"))); + std::get<std::string>(properties.at("CurrentTimerUse"))); + wd_prop.expiredTimerUse = Watchdog::convertTimerUseFromString( + std::get<std::string>(properties.at("ExpiredTimerUse"))); - wd_prop.interval = get<uint64_t>(properties.at("Interval")); - wd_prop.timeRemaining = get<uint64_t>(properties.at("TimeRemaining")); + 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) @@ -121,9 +123,9 @@ T WatchdogService::getProperty(const std::string& key) } try { - variant<T> value; + std::variant<T> value; response.read(value); - return get<T>(value); + return std::get<T>(value); } catch (const std::exception& e) { @@ -145,7 +147,7 @@ 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, variant<T>(val)); + request.append(wd_intf, key, std::variant<T>(val)); auto response = bus.call(request); if (response.is_method_error()) { @@ -153,7 +155,8 @@ void WatchdogService::setProperty(const std::string& key, const T& val) if (wasValid) { // Retry the request once in case the cached service was stale - return setProperty(key, val); + setProperty(key, val); + return; } log<level::ERR>("WatchdogService: Method error setting property", entry("PROPERTY=%s", key.c_str())); @@ -186,12 +189,12 @@ void WatchdogService::setTimerUse(TimerUse timerUse) setProperty("CurrentTimerUse", convertForMessage(timerUse)); } -void WatchdogService::setInterval(uint64_t interval) +void WatchdogService::setExpiredTimerUse(TimerUse timerUse) { - setProperty("Interval", interval); + setProperty("ExpiredTimerUse", convertForMessage(timerUse)); } -void WatchdogService::setTimeRemaining(uint64_t timeRemaining) +void WatchdogService::setInterval(uint64_t interval) { - setProperty("TimeRemaining", timeRemaining); + setProperty("Interval", interval); } diff --git a/app/watchdog_service.hpp b/app/watchdog_service.hpp index 0cf1c74..141bdb7 100644 --- a/app/watchdog_service.hpp +++ b/app/watchdog_service.hpp @@ -35,6 +35,7 @@ class WatchdogService bool enabled; Action expireAction; TimerUse timerUse; + TimerUse expiredTimerUse; uint64_t interval; uint64_t timeRemaining; }; @@ -78,18 +79,18 @@ class WatchdogService */ void setTimerUse(TimerUse timerUse); - /** @brief Sets the value of the interval property on the host watchdog + /** @brief Sets the value of the ExpiredTimerUse property on the host + * watchdog * - * @param[in] interval - The new interval value + * @param[in] timerUse - The new timerUse value */ - void setInterval(uint64_t interval); + void setExpiredTimerUse(TimerUse timerUse); - /** @brief Sets the value of the timeRemaining property on the host - * watchdog + /** @brief Sets the value of the interval property on the host watchdog * - * @param[in] timeRemaining - The new timeRemaining value + * @param[in] interval - The new interval value */ - void setTimeRemaining(uint64_t timeRemaining); + void setInterval(uint64_t interval); private: /** @brief sdbusplus handle */ diff --git a/apphandler.cpp b/apphandler.cpp index 8be092c..2430e8e 100644 --- a/apphandler.cpp +++ b/apphandler.cpp @@ -1,6 +1,12 @@ #include <arpa/inet.h> +#include <fcntl.h> #include <limits.h> +#include <linux/i2c-dev.h> +#include <linux/i2c.h> #include <mapper.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> #include <systemd/sd-bus.h> #include <unistd.h> @@ -14,6 +20,8 @@ #include <filesystem> #include <fstream> #include <ipmid/api.hpp> +#include <ipmid/sessiondef.hpp> +#include <ipmid/sessionhelper.hpp> #include <ipmid/types.hpp> #include <ipmid/utils.hpp> #include <memory> @@ -23,7 +31,6 @@ #include <sdbusplus/message/types.hpp> #include <string> #include <sys_info_param.hpp> -#include <transporthandler.hpp> #include <tuple> #include <vector> #include <xyz/openbmc_project/Common/error.hpp> @@ -36,10 +43,6 @@ extern sd_bus* bus; constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC"; constexpr auto bmc_state_property = "CurrentBMCState"; -constexpr auto bmc_interface = "xyz.openbmc_project.Inventory.Item.Bmc"; -constexpr auto bmc_guid_interface = "xyz.openbmc_project.Common.UUID"; -constexpr auto bmc_guid_property = "UUID"; -constexpr auto bmc_guid_len = 16; static constexpr auto redundancyIntf = "xyz.openbmc_project.Software.RedundancyPriority"; @@ -57,7 +60,35 @@ using Activation = sdbusplus::xyz::openbmc_project::Software::server::Activation; using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC; namespace fs = std::filesystem; -namespace variant_ns = sdbusplus::message::variant_ns; + +#ifdef ENABLE_I2C_WHITELIST_CHECK +typedef struct +{ + uint8_t busId; + uint8_t slaveAddr; + uint8_t slaveAddrMask; + std::vector<uint8_t> data; + std::vector<uint8_t> dataMask; +} i2cMasterWRWhitelist; + +static std::vector<i2cMasterWRWhitelist>& getWRWhitelist() +{ + static std::vector<i2cMasterWRWhitelist> wrWhitelist; + return wrWhitelist; +} + +static constexpr const char* i2cMasterWRWhitelistFile = + "/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* slaveAddrStr = "slaveAddr"; +static constexpr const char* slaveAddrMaskStr = "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 maxIPMIWriteReadSize = 144; /** * @brief Returns the Version info from primary s/w object @@ -70,16 +101,14 @@ namespace variant_ns = sdbusplus::message::variant_ns; * @return On success returns the Version info from primary s/w object. * */ -std::string getActiveSoftwareVersionInfo() +std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx) { - auto busp = getSdBus(); - std::string revision{}; ipmi::ObjectTree objectTree; try { objectTree = - ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf); + ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf); } catch (sdbusplus::exception::SdBusError& e) { @@ -93,9 +122,9 @@ std::string getActiveSoftwareVersionInfo() for (auto& softObject : objectTree) { auto service = - ipmi::getService(*busp, redundancyIntf, softObject.first); + ipmi::getService(*ctx->bus, redundancyIntf, softObject.first); auto objValueTree = - ipmi::getManagedObjects(*busp, service, softwareRoot); + ipmi::getManagedObjects(*ctx->bus, service, softwareRoot); auto minPriority = 0xFF; for (const auto& objIter : objValueTree) @@ -106,14 +135,14 @@ std::string getActiveSoftwareVersionInfo() auto& redundancyPriorityProps = intfMap.at(redundancyIntf); auto& versionProps = intfMap.at(versionIntf); auto& activationProps = intfMap.at(activationIntf); - auto priority = variant_ns::get<uint8_t>( - redundancyPriorityProps.at("Priority")); + auto priority = + std::get<uint8_t>(redundancyPriorityProps.at("Priority")); auto purpose = - variant_ns::get<std::string>(versionProps.at("Purpose")); - auto activation = variant_ns::get<std::string>( - activationProps.at("Activation")); + std::get<std::string>(versionProps.at("Purpose")); + auto activation = + std::get<std::string>(activationProps.at("Activation")); auto version = - variant_ns::get<std::string>(versionProps.at("Version")); + std::get<std::string>(versionProps.at("Version")); if ((Version::convertVersionPurposeFromString(purpose) == Version::VersionPurpose::BMC) && (Activation::convertActivationsFromString(activation) == @@ -154,9 +183,23 @@ bool getCurrentBmcState() ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first, bmc_state_interface, bmc_state_property); - return variant_ns::holds_alternative<std::string>(variant) && - BMC::convertBMCStateFromString( - variant_ns::get<std::string>(variant)) == BMC::BMCState::Ready; + 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 @@ -198,12 +241,6 @@ enum class PowerState : uint8_t static constexpr uint8_t stateChanged = 0x80; -struct ACPIState -{ - uint8_t sysACPIState; - uint8_t devACPIState; -} __attribute__((packed)); - std::map<ACPIPowerState::ACPI, PowerState> dbusToIPMI = { {ACPIPowerState::ACPI::S0_G0_D0, PowerState::s0G0D0}, {ACPIPowerState::ACPI::S1_D1, PowerState::s1D1}, @@ -259,41 +296,32 @@ bool isValidACPIState(acpi_state::PowerStateType type, uint8_t state) } } // namespace acpi_state -ipmi_ret_t ipmi_app_set_acpi_power_state(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) +/** @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); - ipmi_ret_t rc = IPMI_CC_OK; sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; auto value = acpi_state::ACPIPowerState::ACPI::Unknown; - auto* req = reinterpret_cast<acpi_state::ACPIState*>(request); - - if (*data_len != sizeof(acpi_state::ACPIState)) - { - log<level::ERR>("set_acpi invalid len"); - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - *data_len = 0; - - if (req->sysACPIState & acpi_state::stateChanged) + if (sysAcpiState & acpi_state::stateChanged) { // set system power state - s = req->sysACPIState & ~acpi_state::stateChanged; + s = sysAcpiState & ~acpi_state::stateChanged; if (!acpi_state::isValidACPIState( acpi_state::PowerStateType::sysPowerState, s)) { log<level::ERR>("set_acpi_power sys invalid input", entry("S=%x", s)); - return IPMI_CC_PARM_OUT_OF_RANGE; + return ipmi::responseParmOutOfRange(); } // valid input @@ -324,7 +352,7 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::ERR>("Failed in set ACPI system property", entry("EXCEPTION=%s", e.what())); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } } @@ -333,16 +361,16 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::DEBUG>("Do not change system power state"); } - if (req->devACPIState & acpi_state::stateChanged) + if (devAcpiState & acpi_state::stateChanged) { // set device power state - s = req->devACPIState & ~acpi_state::stateChanged; + s = devAcpiState & ~acpi_state::stateChanged; if (!acpi_state::isValidACPIState( acpi_state::PowerStateType::devPowerState, s)) { log<level::ERR>("set_acpi_power dev invalid input", entry("S=%x", s)); - return IPMI_CC_PARM_OUT_OF_RANGE; + return ipmi::responseParmOutOfRange(); } // valid input @@ -373,7 +401,7 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::ERR>("Failed in set ACPI device property", entry("EXCEPTION=%s", e.what())); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } } @@ -381,24 +409,26 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::DEBUG>("Do not change device power state"); } - - return rc; + return ipmi::responseSuccess(); } -ipmi_ret_t ipmi_app_get_acpi_power_state(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) +/** + * @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() { - ipmi_ret_t rc = IPMI_CC_OK; - - auto* res = reinterpret_cast<acpi_state::ACPIState*>(response); + uint8_t sysAcpiState; + uint8_t devAcpiState; sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; - *data_len = 0; - try { auto acpiObject = ipmi::getDbusObject(bus, acpi_state::acpiInterface); @@ -407,26 +437,22 @@ ipmi_ret_t ipmi_app_get_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface, acpi_state::sysACPIProp); auto sysACPI = acpi_state::ACPIPowerState::convertACPIFromString( - variant_ns::get<std::string>(sysACPIVal)); - res->sysACPIState = - static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI)); + 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( - variant_ns::get<std::string>(devACPIVal)); - res->devACPIState = - static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI)); - - *data_len = sizeof(acpi_state::ACPIState); + std::get<std::string>(devACPIVal)); + devAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI)); } catch (const InternalFailure& e) { - log<level::ERR>("Failed in get ACPI property"); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - return rc; + + return ipmi::responseSuccess(sysAcpiState, devAcpiState); } typedef struct @@ -524,16 +550,31 @@ int convertVersion(std::string s, Revision& rev) return 0; } -auto ipmiAppGetDeviceId() -> 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 - > +/* @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(ipmi::Context::ptr ctx) { int r = -1; Revision rev = {0}; @@ -549,6 +590,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID 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); @@ -557,7 +599,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID { try { - auto version = getActiveSoftwareVersionInfo(); + auto version = getActiveSoftwareVersionInfo(ctx); r = convertVersion(version, rev); } catch (const std::exception& e) @@ -596,6 +638,9 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID devId.prodId = data.value("prod_id", 0); devId.aux = data.value("aux", 0); + // Set the availablitity of the BMC. + defaultActivationSetting = data.value("availability", true); + // Don't read the file every time if successful dev_id_initialized = true; } @@ -614,7 +659,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID // Set availability to the actual current BMC state devId.fw[0] &= ipmiDevIdFw1Mask; - if (!getCurrentBmcState()) + if (!getCurrentBmcStateWithFallback(defaultActivationSetting)) { devId.fw[0] |= (1 << ipmiDevIdStateShift); } @@ -651,120 +696,75 @@ auto ipmiAppGetSelfTestResults() -> ipmi::RspType<uint8_t, uint8_t> return ipmi::responseSuccess(notImplemented, zero); } -ipmi_ret_t ipmi_app_get_device_guid(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) +static constexpr size_t uuidBinaryLength = 16; +static std::array<uint8_t, uuidBinaryLength> rfc4122ToIpmi(std::string rfc4122) { - const char* objname = "/org/openbmc/control/chassis0"; - const char* iface = "org.freedesktop.DBus.Properties"; - const char* chassis_iface = "org.openbmc.control.Chassis"; - sd_bus_message* reply = NULL; - sd_bus_error error = SD_BUS_ERROR_NULL; - int r = 0; - char* uuid = NULL; - char* busname = NULL; - + 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 - - const int resp_size = 16; // Response is 16 hex bytes per IPMI Spec - uint8_t resp_uuid[resp_size]; // Array to hold the formatted response - // Point resp end of array to save in reverse order - int resp_loc = resp_size - 1; - int i = 0; - char* tokptr = NULL; - char* id_octet = NULL; - size_t total_uuid_size = 0; - // 1 byte of resp is built from 2 chars of uuid. - constexpr size_t max_uuid_size = 2 * resp_size; - - // Status code. - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; - - // Call Get properties method with the interface and property name - r = mapper_get_service(bus, objname, &busname); - if (r < 0) - { - log<level::ERR>("Failed to get bus name", entry("BUS=%s", objname), - entry("ERRNO=0x%X", -r)); - goto finish; - } - r = sd_bus_call_method(bus, busname, objname, iface, "Get", &error, &reply, - "ss", chassis_iface, "uuid"); - if (r < 0) - { - log<level::ERR>("Failed to call Get Method", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_UNSPECIFIED_ERROR; - goto finish; - } - - r = sd_bus_message_read(reply, "v", "s", &uuid); - if (r < 0 || uuid == NULL) + constexpr size_t uuidHexLength = (2 * uuidBinaryLength); + constexpr size_t uuidRfc4122Length = (uuidHexLength + 4); + std::array<uint8_t, uuidBinaryLength> uuid; + if (rfc4122.size() == uuidRfc4122Length) { - log<level::ERR>("Failed to get a response", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'), + rfc4122.end()); } - - // Traverse the UUID - // Get the UUID octects separated by dash - id_octet = strtok_r(uuid, "-", &tokptr); - - if (id_octet == NULL) + if (rfc4122.size() != uuidHexLength) { - // Error - log<level::ERR>("Unexpected UUID format", entry("UUID=%s", uuid)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), + Argument::ARGUMENT_VALUE(rfc4122.c_str())); } - - while (id_octet != NULL) + for (size_t ind = 0; ind < uuidHexLength; ind += 2) { - // Calculate the octet string size since it varies - // Divide it by 2 for the array size since 1 byte is built from 2 chars - int tmp_size = strlen(id_octet) / 2; - - // Check if total UUID size has been exceeded - if ((total_uuid_size += strlen(id_octet)) > max_uuid_size) + char v[3]; + v[0] = rfc4122[ind]; + v[1] = rfc4122[ind + 1]; + v[2] = 0; + size_t err; + long b; + try { - // Error - UUID too long to store - log<level::ERR>("UUID too long", entry("UUID=%s", uuid)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + b = std::stoul(v, &err, 16); } - - for (i = 0; i < tmp_size; i++) + catch (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) { - // Holder of the 2 chars that will become a byte - char tmp_array[3] = {0}; - strncpy(tmp_array, id_octet, 2); // 2 chars at a time - - int resp_byte = strtoul(tmp_array, NULL, 16); // Convert to hex byte - // Copy end to first - std::memcpy((void*)&resp_uuid[resp_loc], &resp_byte, 1); - resp_loc--; - id_octet += 2; // Finished with the 2 chars, advance + elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), + Argument::ARGUMENT_VALUE(rfc4122.c_str())); } - id_octet = strtok_r(NULL, "-", &tokptr); // Get next octet + 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 - // Data length - *data_len = resp_size; + // 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); - // Pack the actual response - std::memcpy(response, &resp_uuid, *data_len); + sd_id128_t bmcUuid; + // create the UUID from /etc/machine-id via the systemd API + sd_id128_get_machine_app_specific(bmcUuidAppId, &bmcUuid); -finish: - sd_bus_error_free(&error); - reply = sd_bus_message_unref(reply); - free(busname); + char bmcUuidCstr[SD_ID128_STRING_MAX]; + std::string systemUuid = sd_id128_to_string(bmcUuid, bmcUuidCstr); - return rc; + std::array<uint8_t, uuidBinaryLength> uuid = rfc4122ToIpmi(systemUuid); + return ipmi::responseSuccess(uuid); } auto ipmiAppGetBtCapabilities() @@ -782,210 +782,524 @@ auto ipmiAppGetBtCapabilities() outputBufferSize, transactionTime, nrRetries); } -ipmi_ret_t ipmi_app_get_sys_guid(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) - +auto ipmiAppGetSystemGuid() -> ipmi::RspType<std::array<uint8_t, 16>> { - ipmi_ret_t rc = IPMI_CC_OK; - sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + static constexpr auto bmcInterface = + "xyz.openbmc_project.Inventory.Item.Bmc"; + static constexpr auto uuidInterface = "xyz.openbmc_project.Common.UUID"; + static constexpr auto uuidProperty = "UUID"; + ipmi::Value propValue; try { // Get the Inventory object implementing BMC interface - ipmi::DbusObjectInfo bmcObject = - ipmi::getDbusObject(bus, bmc_interface); + auto busPtr = getSdBus(); + auto objectInfo = ipmi::getDbusObject(*busPtr, bmcInterface); // Read UUID property value from bmcObject // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 - auto variant = - ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first, - bmc_guid_interface, bmc_guid_property); - std::string guidProp = variant_ns::get<std::string>(variant); - - // Erase "-" characters from the property value - guidProp.erase(std::remove(guidProp.begin(), guidProp.end(), '-'), - guidProp.end()); + propValue = + ipmi::getDbusProperty(*busPtr, objectInfo.second, objectInfo.first, + uuidInterface, uuidProperty); + } + catch (const InternalFailure& e) + { + log<level::ERR>("Failed in reading BMC UUID property", + entry("INTERFACE=%s", uuidInterface), + entry("PROPERTY=%s", uuidProperty)); + return ipmi::responseUnspecifiedError(); + } + std::array<uint8_t, 16> uuid; + std::string rfc4122Uuid = std::get<std::string>(propValue); + try + { + // convert to IPMI format + uuid = rfc4122ToIpmi(rfc4122Uuid); + } + catch (const InvalidArgument& e) + { + log<level::ERR>("Failed in parsing BMC UUID property", + entry("INTERFACE=%s", uuidInterface), + entry("PROPERTY=%s", uuidProperty), + entry("VALUE=%s", rfc4122Uuid.c_str())); + return ipmi::responseUnspecifiedError(); + } + return ipmi::responseSuccess(uuid); +} - auto guidPropLen = guidProp.length(); - // Validate UUID data - // Divide by 2 as 1 byte is built from 2 chars - if ((guidPropLen <= 0) || ((guidPropLen / 2) != bmc_guid_len)) +/** + * @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)) { - log<level::ERR>("Invalid UUID property value", - entry("UUID_LENGTH=%d", guidPropLen)); - return IPMI_CC_RESPONSE_ERROR; + ipmi::setDbusProperty( + *busp, service, obj, session::sessionIntf, "State", + static_cast<uint8_t>(session::State::tearDownInProgress)); + return ipmi::ccSuccess; } + } + catch (std::exception& e) + { + log<level::ERR>("Failed in getting session state property", + entry("service=%s", service.c_str()), + entry("object path=%s", obj.c_str()), + entry("interface=%s", session::sessionIntf)); + return ipmi::ccUnspecifiedError; + } - // Convert data in RFC4122(MSB) format to LSB format - // Get 2 characters at a time as 1 byte is built from 2 chars and - // convert to hex byte - // TODO: Data printed for GUID command is not as per the - // GUID format defined in IPMI specification 2.0 section 20.8 - // Ticket raised: https://sourceforge.net/p/ipmitool/bugs/501/ - uint8_t respGuid[bmc_guid_len]; - for (size_t i = 0, respLoc = (bmc_guid_len - 1); - i < guidPropLen && respLoc >= 0; i += 2, respLoc--) - { - auto value = static_cast<uint8_t>( - std::stoi(guidProp.substr(i, 2).c_str(), NULL, 16)); - respGuid[respLoc] = value; - } + return ipmi::ccInvalidFieldRequest; +} - *data_len = bmc_guid_len; - std::memcpy(response, &respGuid, bmc_guid_len); +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); } - catch (const InternalFailure& e) + + if (reqSessionId == session::sessionZero && + reqSessionHandle == session::invalidSessionHandle) { - log<level::ERR>("Failed in reading BMC UUID property", - entry("INTERFACE=%s", bmc_interface), - entry("PROPERTY_INTERFACE=%s", bmc_guid_interface), - entry("PROPERTY=%s", bmc_guid_property)); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::response(session::ccInvalidSessionHandle); } - return rc; -} -static std::unique_ptr<SysInfoParamStore> sysInfoParamStore; + if (reqSessionId != session::sessionZero && + reqSessionHandle != session::defaultSessionHandle) + { + return ipmi::response(ipmi::ccInvalidFieldRequest); + } -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) + try { - perror("System info parameter: system name"); + 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)); + } + } } - return hostname; + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch object from dbus", + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::responseUnspecifiedError(); + } + + return ipmi::responseInvalidFieldRequest(); } -struct IpmiSysInfoResp +uint8_t getTotalSessionCount() { - uint8_t paramRevision; - uint8_t setSelector; - union + uint8_t count = 0, ch = 1; + + while (ch < ipmi::maxIpmiChannels && + count < session::maxNetworkInstanceSupported) { - struct + ipmi::ChannelInfo chInfo; + ipmi::getChannelInfo(ch, chInfo); + if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) == + ipmi::EChannelMediumType::lan8032) { - uint8_t encoding; - uint8_t stringLen; - uint8_t stringData0[14]; - } __attribute__((packed)); - uint8_t stringDataN[16]; - uint8_t byteData; - }; -} __attribute__((packed)); + count++; + } + ch++; + } + return count * session::maxSessionCountPerChannel; +} /** - * Split a string into (up to) 16-byte chunks as expected in response for get - * system info parameter. + * @brief get session info request data. * - * @param[in] fullString: Input string to be split - * @param[in] chunkIndex: Index of the chunk to be written out - * @param[in,out] chunk: Output data buffer; must have 14 byte capacity if - * chunk_index = 0 and 16-byte capacity otherwise - * @return the number of bytes written into the output buffer, or -EINVAL for - * invalid arguments. - */ -static int splitStringParam(const std::string& fullString, int chunkIndex, - uint8_t* chunk) + * 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) { - constexpr int maxChunk = 255; - constexpr int smallChunk = 14; - constexpr int chunkSize = 16; - if (chunkIndex > maxChunk || chunk == nullptr) + if ((sessionIndex > session::maxSessionCountPerChannel) && + (sessionIndex < session::searchSessionByHandle)) { - return -EINVAL; + 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(std::shared_ptr<sdbusplus::asio::connection>& busp, + const std::string& service, const std::string& objPath, + uint8_t& sessionState) +{ + try + { + sessionState = std::get<uint8_t>(ipmi::getDbusProperty( + *busp, service, objPath, session::sessionIntf, "State")); + } + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch state property ", + entry("SERVICE=%s", service.c_str()), + entry("OBJECTPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::ccUnspecifiedError; + } + + return ipmi::ccSuccess; +} + +static constexpr uint8_t macAddrLen = 6; +struct GetSessionInfoRes +{ + uint8_t sessionHandle; + uint8_t totalSessionCount; + uint8_t activeSessionCount; + uint8_t userID; + uint8_t privLevel; + uint8_t channelNumber; + uint32_t remoteIpAddr; + std::array<uint8_t, macAddrLen> macAddr = {0}; + uint16_t remotePort; +}; + +uint8_t + fillGetSessionInfoRes(std::shared_ptr<sdbusplus::asio::connection>& busp, + const std::string& service, + const std::string& objPath, + struct GetSessionInfoRes& resp, uint8_t& sessionState) +{ try { - std::string output; - if (chunkIndex == 0) + ipmi::PropertyMap sessionProps = ipmi::getAllDbusProperties( + *busp, service, objPath, session::sessionIntf); + + sessionState = std::get<uint8_t>(sessionProps.at("State")); + if (sessionState == static_cast<uint8_t>(session::State::active)) { - // Output must have 14 byte capacity. - output = fullString.substr(0, smallChunk); + resp.sessionHandle = + std::get<uint8_t>(sessionProps["SessionHandle"]); + resp.userID = std::get<uint8_t>(sessionProps["UserID"]); + resp.privLevel = + std::get<uint8_t>(sessionProps["CurrentPrivilege"]); + resp.channelNumber = std::get<uint8_t>(sessionProps["ChannelNum"]); + resp.remoteIpAddr = + std::get<uint32_t>(sessionProps["RemoteIPAddr"]); + resp.remotePort = std::get<uint16_t>(sessionProps["RemotePort"]); } - else + } + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch state property ", + entry("SERVICE=%s", service.c_str()), + entry("OBJECTPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::ccUnspecifiedError; + } + + return ipmi::ccSuccess; +} + +ipmi::RspType< + uint8_t, // session handle, + uint8_t, // total session count + uint8_t, // active session count + std::optional<std::tuple<uint8_t, // user ID + uint8_t, // privilege level + uint8_t, // channel number + uint32_t, // remote ip address, + std::array<uint8_t, macAddrLen>, // mac address + uint16_t // remote port + >>> + 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); + } + struct GetSessionInfoRes res = {0}; + res.totalSessionCount = getTotalSessionCount(); + res.activeSessionCount = 0; + auto busp = getSdBus(); + + try + { + uint8_t index = 0; + ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects( + *busp, session::sessionManagerRootPath, session::sessionIntf); + + for (auto& objectTreeItr : objectTree) { - // Output must have 16 byte capacity. - output = fullString.substr((chunkIndex * chunkSize) - 2, chunkSize); + uint32_t sessionId = 0; + uint8_t sessionHandle = session::defaultSessionHandle; + 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(busp, service, objectPath, sessionState); + if (completionCode) + { + return ipmi::response(completionCode); + } + + if (sessionState == static_cast<uint8_t>(session::State::active)) + { + res.activeSessionCount++; + } + + if (index != sessionIndex && reqSessionId != sessionId && + reqSessionHandle != sessionHandle) + { + continue; + } + + completionCode = + fillGetSessionInfoRes(busp, service, objectPath, res, state); + + if (completionCode) + { + return ipmi::response(completionCode); + } } + } + + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch object from dbus", + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::responseUnspecifiedError(); + } - std::memcpy(chunk, output.c_str(), output.length()); - return output.length(); + if (state == static_cast<uint8_t>(session::State::active)) + { + return ipmi::responseSuccess( + res.sessionHandle, res.totalSessionCount, res.activeSessionCount, + std::make_tuple(res.userID, res.privLevel, res.channelNumber, + res.remoteIpAddr, res.macAddr, res.remotePort)); } - catch (const std::out_of_range& e) + else if (state == static_cast<uint8_t>(session::State::tearDownInProgress)) { - // The position was beyond the end. - return -EINVAL; + res.sessionHandle = 0; + return ipmi::responseSuccess(res.sessionHandle, res.totalSessionCount, + res.activeSessionCount, std::nullopt); } + + return ipmi::responseInvalidFieldRequest(); } -/** - * Packs the Get Sys Info Request Item into the response. - * - * @param[in] paramString - the parameter. - * @param[in] setSelector - the selector - * @param[in,out] resp - the System info response. - * @return The number of bytes packed or failure from splitStringParam(). - */ -static int packGetSysInfoResp(const std::string& paramString, - uint8_t setSelector, IpmiSysInfoResp* resp) +static std::unique_ptr<SysInfoParamStore> sysInfoParamStore; + +static std::string sysInfoReadSystemName() { - uint8_t* dataBuffer = resp->stringDataN; - resp->setSelector = setSelector; - if (resp->setSelector == 0) // First chunk has only 14 bytes. + // Use the BMC hostname as the "System Name." + char hostname[HOST_NAME_MAX + 1] = {}; + if (gethostname(hostname, HOST_NAME_MAX) != 0) { - resp->encoding = 0; - resp->stringLen = paramString.length(); - dataBuffer = resp->stringData0; + perror("System info parameter: system name"); } - return splitStringParam(paramString, resp->setSelector, dataBuffer); + return hostname; } -ipmi_ret_t ipmi_app_get_system_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t dataLen, - ipmi_context_t context) +static constexpr uint8_t revisionOnly = 0x80; +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 setComplete = 0x0; +static constexpr uint8_t setInProgress = 0x1; +static constexpr uint8_t commitWrite = 0x2; +static uint8_t transferStatus = setComplete; + +static constexpr uint8_t configDataOverhead = 2; + +// For EFI based system, 256 bytes is recommended. +static constexpr size_t maxBytesPerParameter = 256; + +namespace ipmi { - IpmiSysInfoResp resp = {}; - size_t respLen = 0; - uint8_t* const reqData = static_cast<uint8_t*>(request); - std::string paramString; - bool found; - std::tuple<bool, std::string> ret; - constexpr int minRequestSize = 4; - constexpr int paramSelector = 1; - constexpr uint8_t revisionOnly = 0x80; - const uint8_t paramRequested = reqData[paramSelector]; - int rc; +constexpr Cc ccParmNotSupported = 0x80; +constexpr Cc ccSetInProgressActive = 0x81; +constexpr Cc ccSystemInfoParameterSetReadOnly = 0x82; - if (*dataLen < minRequestSize) +static inline auto responseParmNotSupported() +{ + return response(ccParmNotSupported); +} +static inline auto responseSetInProgressActive() +{ + return response(ccSetInProgressActive); +} +static inline auto responseSystemInfoParameterSetReadOnly() +{ + return response(ccSystemInfoParameterSetReadOnly); +} +} // namespace ipmi + +ipmi::RspType<uint8_t, // Parameter revision + std::optional<uint8_t>, // data1 / setSelector / ProgressStatus + std::optional<std::vector<uint8_t>>> // data2-17 + ipmiAppGetSystemInfo(uint8_t getRevision, uint8_t paramSelector, + uint8_t setSelector, uint8_t BlockSelector) +{ + if (getRevision & revisionOnly) { - return IPMI_CC_REQ_DATA_LEN_INVALID; + return ipmi::responseSuccess(paramRevision, std::nullopt, std::nullopt); } - *dataLen = 0; // default to 0. - - // Parameters revision as of IPMI spec v2.0 rev. 1.1 (Feb 11, 2014 E6) - resp.paramRevision = 0x11; - if (reqData[0] & revisionOnly) // Get parameter revision only + if (paramSelector == 0) { - respLen = 1; - goto writeResponse; + return ipmi::responseSuccess(paramRevision, transferStatus, + std::nullopt); } - // The "Set In Progress" parameter can be used for rollback of parameter - // data and is not implemented. - if (paramRequested == 0) + if (BlockSelector != 0) // 00h if parameter does not require a block number { - resp.byteData = 0; - respLen = 2; - goto writeResponse; + return ipmi::responseParmNotSupported(); } if (sysInfoParamStore == nullptr) @@ -996,30 +1310,323 @@ ipmi_ret_t ipmi_app_get_system_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } // Parameters other than Set In Progress are assumed to be strings. - ret = sysInfoParamStore->lookup(paramRequested); - found = std::get<0>(ret); - paramString = std::get<1>(ret); + std::tuple<bool, std::string> ret = + sysInfoParamStore->lookup(paramSelector); + bool found = std::get<0>(ret); if (!found) { - return IPMI_CC_SYSTEM_INFO_PARAMETER_NOT_SUPPORTED; + return ipmi::responseParmNotSupported(); + } + 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 thunk } - // TODO: Cache each parameter across multiple calls, until the whole string - // has been read out. Otherwise, it's possible for a parameter to change - // between requests for its chunks, returning chunks incoherent with each - // other. For now, the parameter store is simply required to have only - // idempotent callbacks. - rc = packGetSysInfoResp(paramString, reqData[2], &resp); - if (rc == -EINVAL) + else { - return IPMI_CC_RESPONSE_ERROR; + 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 == 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) + { + phosphor::logging::log<phosphor::logging::level::ERR>( + "illegal SetInProgress status"); + return ipmi::responseInvalidFieldRequest(); + } + + transferStatus = data1 & progressMask; + return ipmi::responseSuccess(); + } + + if (configData.size() > configParameterLength) + { + return ipmi::responseInvalidFieldRequest(); } - respLen = sizeof(resp); // Write entire string data chunk in response. + 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 = ""; + } -writeResponse: - std::memcpy(response, &resp, sizeof(resp)); - *dataLen = respLen; - return IPMI_CC_OK; + uint8_t setSelector = data1; + size_t count = 0; + if (setSelector == 0) // First chunk has only 14 bytes. + { + size_t stringLen = configData.at(1); // string length + // maxBytesPerParamter is 256. It will always be greater than stringLen + // (unit8_t) if maxBytes changes in future, then following line is + // needed. + // stringLen = std::min(stringLen, maxBytesPerParameter); + 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 populateI2CMasterWRWhitelist() +{ + nlohmann::json data = nullptr; + std::ifstream jsonFile(i2cMasterWRWhitelistFile); + + if (!jsonFile.good()) + { + log<level::WARNING>("i2c white list file not found!", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile)); + return false; + } + + try + { + data = nlohmann::json::parse(jsonFile, nullptr, false); + } + catch (nlohmann::json::parse_error& e) + { + log<level::ERR>("Corrupted i2c white list config file", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile), + entry("MSG: %s", e.what())); + 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<i2cMasterWRWhitelist>& whitelist = getWRWhitelist(); + for (const auto& it : filters.items()) + { + nlohmann::json filter = it.value(); + if (filter.is_null()) + { + log<level::ERR>( + "Corrupted I2C master write read whitelist config file", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile)); + 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()) + { + log<level::ERR>("I2C master write read whitelist filter " + "mismatch for command & mask size"); + return false; + } + whitelist.push_back( + {static_cast<uint8_t>(std::stoul( + filter[busIdStr].get<std::string>(), nullptr, base_16)), + static_cast<uint8_t>( + std::stoul(filter[slaveAddrStr].get<std::string>(), + nullptr, base_16)), + static_cast<uint8_t>( + std::stoul(filter[slaveAddrMaskStr].get<std::string>(), + nullptr, base_16)), + writeData, writeDataMask}); + } + if (whitelist.size() != filters.size()) + { + log<level::ERR>( + "I2C master write read whitelist filter size mismatch"); + return false; + } + } + catch (std::exception& e) + { + log<level::ERR>("I2C master write read whitelist unexpected exception", + entry("ERROR=%s", e.what())); + return false; + } + return true; +} + +static inline bool isWriteDataWhitelisted(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 isCmdWhitelisted(uint8_t busId, uint8_t slaveAddr, + std::vector<uint8_t>& writeData) +{ + std::vector<i2cMasterWRWhitelist>& whiteList = getWRWhitelist(); + for (const auto& wlEntry : whiteList) + { + if ((busId == wlEntry.busId) && + ((slaveAddr | wlEntry.slaveAddrMask) == + (wlEntry.slaveAddr | wlEntry.slaveAddrMask))) + { + 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 (isWriteDataWhitelisted(wlEntry.data, dataMask, writeData)) + { + return true; + } + } + } + return false; +} +#else +static bool populateI2CMasterWRWhitelist() +{ + log<level::INFO>( + "I2C_WHITELIST_CHECK is disabled, do not populate whitelist"); + return true; +} +#endif // ENABLE_I2C_WHITELIST_CHECK + +/** @brief implements master 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 slaveAddr - slave 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>> + ipmiMasterWriteRead(bool isPrivateBus, uint3_t busId, uint4_t channelNum, + bool reserved, uint7_t slaveAddr, uint8_t readCount, + std::vector<uint8_t> writeData) +{ + if (readCount > maxIPMIWriteReadSize) + { + log<level::ERR>("Master write read command: Read count exceeds limit"); + return ipmi::responseParmOutOfRange(); + } + const size_t writeCount = writeData.size(); + if (!readCount && !writeCount) + { + log<level::ERR>("Master write read command: Read & write count are 0"); + return ipmi::responseInvalidFieldRequest(); + } +#ifdef ENABLE_I2C_WHITELIST_CHECK + if (!isCmdWhitelisted(static_cast<uint8_t>(busId), + static_cast<uint8_t>(slaveAddr), writeData)) + { + log<level::ERR>("Master write read request blocked!", + entry("BUS=%d", static_cast<uint8_t>(busId)), + entry("ADDR=0x%x", static_cast<uint8_t>(slaveAddr))); + return ipmi::responseInvalidFieldRequest(); + } +#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>(slaveAddr), + writeData, readBuf); + if (ret != ipmi::ccSuccess) + { + return ipmi::response(ret); + } + return ipmi::responseSuccess(readBuf); } void register_netfn_app_functions() @@ -1039,13 +1646,23 @@ void register_netfn_app_functions() ipmi::app::cmdResetWatchdogTimer, ipmi::Privilege::Operator, ipmiAppResetWatchdogTimer); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetSessionInfo, ipmi::Privilege::User, + ipmiAppGetSessionInfo); + // <Set Watchdog Timer> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_WD, NULL, - ipmi_app_watchdog_set, PRIVILEGE_OPERATOR); + 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_register_callback(NETFUN_APP, IPMI_CMD_GET_WD, NULL, - ipmi_app_watchdog_get, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetWatchdogTimer, ipmi::Privilege::User, + ipmiGetWatchdogTimer); // <Get Self Test Results> ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, @@ -1053,27 +1670,48 @@ void register_netfn_app_functions() ipmi::Privilege::User, ipmiAppGetSelfTestResults); // <Get Device GUID> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_DEVICE_GUID, NULL, - ipmi_app_get_device_guid, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetDeviceGuid, ipmi::Privilege::User, + ipmiAppGetDeviceGuid); // <Set ACPI Power State> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_ACPI, NULL, - ipmi_app_set_acpi_power_state, PRIVILEGE_ADMIN); - + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdSetAcpiPowerState, + ipmi::Privilege::Admin, ipmiSetAcpiPowerState); // <Get ACPI Power State> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_ACPI, NULL, - ipmi_app_get_acpi_power_state, PRIVILEGE_ADMIN); + 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 Master write read whitelist + if (populateI2CMasterWRWhitelist()) + { + // Note: For security reasons, registering master 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, ipmiMasterWriteRead); + } // <Get System GUID Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYS_GUID, NULL, - ipmi_app_get_sys_guid, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetSystemGuid, ipmi::Privilege::User, + ipmiAppGetSystemGuid); // <Get Channel Cipher Suites Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHAN_CIPHER_SUITES, NULL, - getChannelCipherSuites, PRIVILEGE_CALLBACK); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetChannelCipherSuites, + ipmi::Privilege::None, getChannelCipherSuites); // <Get System Info Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYSTEM_INFO, NULL, - ipmi_app_get_system_info, PRIVILEGE_USER); + 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 index d4dd8e8..4f03121 100644 --- a/apphandler.hpp +++ b/apphandler.hpp @@ -19,7 +19,6 @@ enum ipmi_netfn_app_cmds IPMI_CMD_SET_CHAN_ACCESS = 0x40, IPMI_CMD_GET_CHANNEL_ACCESS = 0x41, IPMI_CMD_GET_CHAN_INFO = 0x42, - IPMI_CMD_GET_CHAN_CIPHER_SUITES = 0x54, IPMI_CMD_SET_SYSTEM_INFO = 0x58, IPMI_CMD_GET_SYSTEM_INFO = 0x59, }; diff --git a/chassishandler.cpp b/chassishandler.cpp index f8a93b0..6e1341c 100644 --- a/chassishandler.cpp +++ b/chassishandler.cpp @@ -2,8 +2,6 @@ #include "chassishandler.hpp" -#include "settings.hpp" - #include <arpa/inet.h> #include <endian.h> #include <limits.h> @@ -26,6 +24,7 @@ #include <sdbusplus/message/types.hpp> #include <sdbusplus/server/object.hpp> #include <sdbusplus/timer.hpp> +#include <settings.hpp> #include <sstream> #include <string> #include <xyz/openbmc_project/Common/error.hpp> @@ -44,6 +43,8 @@ std::unique_ptr<phosphor::Timer> identifyTimer __attribute__((init_priority(101))); +static ChassisIDState chassisIDState = ChassisIDState::reserved; + constexpr size_t SIZE_MAC = 18; constexpr size_t SIZE_BOOT_OPTION = (uint8_t) BootOptionResponseSize::OPAL_NETWORK_SETTINGS; // Maximum size of the boot @@ -100,33 +101,14 @@ const static constexpr char chassisSMDevAddrProp[] = "SMDeviceAddress"; const static constexpr char chassisBridgeDevAddrProp[] = "BridgeDeviceAddress"; static constexpr uint8_t chassisCapFlagMask = 0x0f; static constexpr uint8_t chassisCapAddrMask = 0xfe; - -typedef struct -{ - uint8_t cap_flags; - uint8_t fru_info_dev_addr; - uint8_t sdr_dev_addr; - uint8_t sel_dev_addr; - uint8_t system_management_dev_addr; - uint8_t bridge_dev_addr; -} __attribute__((packed)) ipmi_chassis_cap_t; - -typedef struct -{ - uint8_t cur_power_state; - uint8_t last_power_event; - uint8_t misc_power_state; - uint8_t front_panel_button_cap_status; -} __attribute__((packed)) ipmi_get_chassis_status_t; - -/** - * @struct Get POH counter command response data - */ -struct GetPOHCountResponse -{ - uint8_t minPerCount; ///< Minutes per count - uint8_t counterReading[4]; ///< Counter reading -} __attribute__((packed)); +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::xyz::openbmc_project::State::server; @@ -136,7 +118,6 @@ namespace fs = std::filesystem; using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; using namespace sdbusplus::xyz::openbmc_project::Control::Boot::server; -namespace variant_ns = sdbusplus::message::variant_ns; namespace chassis { @@ -152,8 +133,18 @@ sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection()); namespace cache { -settings::Objects objects(dbus, - {bootModeIntf, bootSourceIntf, powerRestoreIntf}); +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, bootSourceIntf, + powerRestoreIntf}); + } + return *objectsPtr; +} } // namespace cache } // namespace internal @@ -216,19 +207,19 @@ int getHostNetworkData(get_sys_boot_options_response_t* respptr) macObjectInfo.first, MAC_INTERFACE, "MACAddress"); - auto ipAddress = variant_ns::get<std::string>(properties["Address"]); + auto ipAddress = std::get<std::string>(properties["Address"]); - auto gateway = variant_ns::get<std::string>(properties["Gateway"]); + auto gateway = std::get<std::string>(properties["Gateway"]); - auto prefix = variant_ns::get<uint8_t>(properties["PrefixLength"]); + auto prefix = std::get<uint8_t>(properties["PrefixLength"]); uint8_t isStatic = - (variant_ns::get<std::string>(properties["Origin"]) == + (std::get<std::string>(properties["Origin"]) == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") ? 1 : 0; - auto MACAddress = variant_ns::get<std::string>(variant); + 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. @@ -266,11 +257,10 @@ int getHostNetworkData(get_sys_boot_options_response_t* respptr) std::memcpy(respptr->data + ADDRTYPE_OFFSET, &isStatic, sizeof(isStatic)); - uint8_t addressFamily = - (variant_ns::get<std::string>(properties["Type"]) == - "xyz.openbmc_project.Network.IP.Protocol.IPv4") - ? AF_INET - : AF_INET6; + 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 @@ -504,39 +494,29 @@ uint32_t getPOHCounter() ipmi::getDbusProperty(bus, service, chassisStateObj.first, chassisPOHStateIntf, pOHCounterProperty); - return variant_ns::get<uint32_t>(propValue); -} - -ipmi_ret_t ipmi_chassis_wildcard(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) -{ - // Status code. - ipmi_ret_t rc = IPMI_CC_INVALID; - *data_len = 0; - return rc; + return std::get<uint32_t>(propValue); } -ipmi_ret_t ipmi_get_chassis_cap(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) +/** @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<uint8_t, // chassis capabilities flag + 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() { - // sd_bus error - ipmi_ret_t rc = IPMI_CC_OK; - - ipmi_chassis_cap_t chassis_cap{}; - - if (*data_len != 0) - { - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - *data_len = sizeof(ipmi_chassis_cap_t); - + ipmi::PropertyMap properties; try { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; @@ -555,120 +535,135 @@ ipmi_ret_t ipmi_get_chassis_cap(ipmi_netfn_t netfn, ipmi_cmd_t cmd, // interfaces). // [0] -1b = Chassis provides intrusion (physical security) sensor. // set to default value 0x0. - ipmi::Value variant = ipmi::getDbusProperty( - bus, chassisCapObject.second, chassisCapObject.first, - chassisCapIntf, chassisCapFlagsProp); - chassis_cap.cap_flags = variant_ns::get<uint8_t>(variant); - - variant = ipmi::getDbusProperty(bus, chassisCapObject.second, - chassisCapObject.first, chassisCapIntf, - chassisFRUDevAddrProp); - // Chassis FRU info Device Address. - chassis_cap.fru_info_dev_addr = variant_ns::get<uint8_t>(variant); - - variant = ipmi::getDbusProperty(bus, chassisCapObject.second, - chassisCapObject.first, chassisCapIntf, - chassisSDRDevAddrProp); - // Chassis SDR Device Address. - chassis_cap.sdr_dev_addr = variant_ns::get<uint8_t>(variant); - - variant = ipmi::getDbusProperty(bus, chassisCapObject.second, - chassisCapObject.first, chassisCapIntf, - chassisSELDevAddrProp); - // Chassis SEL Device Address. - chassis_cap.sel_dev_addr = variant_ns::get<uint8_t>(variant); - - variant = ipmi::getDbusProperty(bus, chassisCapObject.second, - chassisCapObject.first, chassisCapIntf, - chassisSMDevAddrProp); - // Chassis System Management Device Address. - chassis_cap.system_management_dev_addr = - variant_ns::get<uint8_t>(variant); - - variant = ipmi::getDbusProperty(bus, chassisCapObject.second, - chassisCapObject.first, chassisCapIntf, - chassisBridgeDevAddrProp); - // Chassis Bridge Device Address. - chassis_cap.bridge_dev_addr = variant_ns::get<uint8_t>(variant); - uint8_t* respP = reinterpret_cast<uint8_t*>(response); - uint8_t* chassisP = reinterpret_cast<uint8_t*>(&chassis_cap); - std::copy(chassisP, chassisP + *data_len, respP); + + properties = + ipmi::getAllDbusProperties(bus, chassisCapObject.second, + chassisCapObject.first, chassisCapIntf); } catch (std::exception& e) { - log<level::ERR>(e.what()); - rc = IPMI_CC_UNSPECIFIED_ERROR; - *data_len = 0; - return rc; + log<level::ERR>("Failed to fetch Chassis Capability properties", + entry("ERROR=%s", e.what())); + return ipmi::responseUnspecifiedError(); } - return rc; + uint8_t* chassisCapFlags = + std::get_if<uint8_t>(&properties[chassisCapFlagsProp]); + if (chassisCapFlags == nullptr) + { + log<level::ERR>("Error to get chassis capability flags"); + return ipmi::responseUnspecifiedError(); + } + uint8_t* chassisFRUInfoDevAddr = + std::get_if<uint8_t>(&properties[chassisFRUDevAddrProp]); + if (chassisFRUInfoDevAddr == nullptr) + { + log<level::ERR>("Error to get chassis FRU info device address"); + return ipmi::responseUnspecifiedError(); + } + uint8_t* chassisSDRDevAddr = + std::get_if<uint8_t>(&properties[chassisSDRDevAddrProp]); + if (chassisSDRDevAddr == nullptr) + { + log<level::ERR>("Error to get chassis SDR device address"); + return ipmi::responseUnspecifiedError(); + } + uint8_t* chassisSELDevAddr = + std::get_if<uint8_t>(&properties[chassisSELDevAddrProp]); + if (chassisSELDevAddr == nullptr) + { + log<level::ERR>("Error to get chassis SEL device address"); + return ipmi::responseUnspecifiedError(); + } + uint8_t* chassisSMDevAddr = + std::get_if<uint8_t>(&properties[chassisSMDevAddrProp]); + if (chassisSMDevAddr == nullptr) + { + log<level::ERR>("Error to get chassis SM device address"); + return ipmi::responseUnspecifiedError(); + } + uint8_t* chassisBridgeDevAddr = + std::get_if<uint8_t>(&properties[chassisBridgeDevAddrProp]); + if (chassisBridgeDevAddr == nullptr) + { + log<level::ERR>("Error to get chassis bridge device address"); + return ipmi::responseUnspecifiedError(); + } + + return ipmi::responseSuccess(*chassisCapFlags, *chassisFRUInfoDevAddr, + *chassisSDRDevAddr, *chassisSELDevAddr, + *chassisSMDevAddr, *chassisBridgeDevAddr); } -ipmi_ret_t ipmi_set_chassis_cap(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_ret_t rc = IPMI_CC_OK; +/** @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, - if (*data_len != sizeof(ipmi_chassis_cap_t)) - { - log<level::ERR>("Unsupported request length", - entry("LEN=0x%x", *data_len)); - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } + uint8_t fruDeviceAddr, - ipmi_chassis_cap_t* chassisCap = static_cast<ipmi_chassis_cap_t*>(request); + uint8_t sdrDeviceAddr, - *data_len = 0; + uint8_t selDeviceAddr, + + uint8_t smDeviceAddr, + + uint8_t bridgeDeviceAddr) +{ // check input data - if (0 != (chassisCap->cap_flags & ~chassisCapFlagMask)) + if (reserved1 != 0) { - log<level::ERR>("Unsupported request parameter(CAP Flags)", - entry("REQ=0x%x", chassisCap->cap_flags)); - return IPMI_CC_INVALID_FIELD_REQUEST; + log<level::ERR>("Unsupported request parameter"); + return ipmi::responseInvalidFieldRequest(); } - if (0 != (chassisCap->fru_info_dev_addr & ~chassisCapAddrMask)) + if ((fruDeviceAddr & ~chassisCapAddrMask) != 0) { log<level::ERR>("Unsupported request parameter(FRU Addr)", - entry("REQ=0x%x", chassisCap->fru_info_dev_addr)); - return IPMI_CC_INVALID_FIELD_REQUEST; + entry("REQ=0x%x", fruDeviceAddr)); + return ipmi::responseInvalidFieldRequest(); } - - if (0 != (chassisCap->sdr_dev_addr & ~chassisCapAddrMask)) + if ((sdrDeviceAddr & ~chassisCapAddrMask) != 0) { log<level::ERR>("Unsupported request parameter(SDR Addr)", - entry("REQ=0x%x", chassisCap->sdr_dev_addr)); - return IPMI_CC_INVALID_FIELD_REQUEST; + entry("REQ=0x%x", sdrDeviceAddr)); + return ipmi::responseInvalidFieldRequest(); } - if (0 != (chassisCap->sel_dev_addr & ~chassisCapAddrMask)) + if ((selDeviceAddr & ~chassisCapAddrMask) != 0) { log<level::ERR>("Unsupported request parameter(SEL Addr)", - entry("REQ=0x%x", chassisCap->sel_dev_addr)); - return IPMI_CC_INVALID_FIELD_REQUEST; + entry("REQ=0x%x", selDeviceAddr)); + return ipmi::responseInvalidFieldRequest(); } - if (0 != (chassisCap->system_management_dev_addr & ~chassisCapAddrMask)) + if ((smDeviceAddr & ~chassisCapAddrMask) != 0) { - log<level::ERR>( - "Unsupported request parameter(SM Addr)", - entry("REQ=0x%x", chassisCap->system_management_dev_addr)); - return IPMI_CC_INVALID_FIELD_REQUEST; + log<level::ERR>("Unsupported request parameter(SM Addr)", + entry("REQ=0x%x", smDeviceAddr)); + return ipmi::responseInvalidFieldRequest(); } - if (0 != (chassisCap->bridge_dev_addr & ~chassisCapAddrMask)) + if ((bridgeDeviceAddr & ~chassisCapAddrMask) != 0) { log<level::ERR>("Unsupported request parameter(Bridge Addr)", - entry("REQ=0x%x", chassisCap->bridge_dev_addr)); - return IPMI_CC_INVALID_FIELD_REQUEST; + entry("REQ=0x%x", bridgeDeviceAddr)); + return ipmi::responseInvalidFieldRequest(); } + uint8_t capFlags = (static_cast<uint8_t>(intrusion)) | + ((static_cast<uint8_t>(fpLockout)) << 1); try { sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); @@ -677,39 +672,34 @@ ipmi_ret_t ipmi_set_chassis_cap(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisCapFlagsProp, chassisCap->cap_flags); + chassisCapFlagsProp, capFlags); ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisFRUDevAddrProp, - chassisCap->fru_info_dev_addr); + chassisFRUDevAddrProp, fruDeviceAddr); ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisSDRDevAddrProp, chassisCap->sdr_dev_addr); + chassisSDRDevAddrProp, sdrDeviceAddr); ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisSELDevAddrProp, chassisCap->sel_dev_addr); + chassisSELDevAddrProp, selDeviceAddr); ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisSMDevAddrProp, - chassisCap->system_management_dev_addr); + chassisSMDevAddrProp, smDeviceAddr); ipmi::setDbusProperty(bus, chassisCapObject.second, chassisCapObject.first, chassisCapIntf, - chassisBridgeDevAddrProp, - chassisCap->bridge_dev_addr); + chassisBridgeDevAddrProp, bridgeDeviceAddr); } catch (std::exception& e) { log<level::ERR>(e.what()); - rc = IPMI_CC_UNSPECIFIED_ERROR; - return rc; + return ipmi::responseUnspecifiedError(); } - - return rc; + return ipmi::responseSuccess(); } //------------------------------------------ @@ -770,6 +760,38 @@ int initiate_state_transition(State::Host::Transition transition) return rc; } +//------------------------------------------ +// Set Enabled property to inform NMI source +// handling to trigger a NMI_OUT BSOD. +//------------------------------------------ +int setNmiProperty(const bool value) +{ + constexpr const char* nmiSourceObjPath = + "/xyz/openbmc_project/Chassis/Control/NMISource"; + constexpr const char* nmiSourceIntf = + "xyz.openbmc_project.Chassis.Control.NMISource"; + std::string bmcSourceSignal = "xyz.openbmc_project.Chassis.Control." + "NMISource.BMCSourceSignal.ChassisCmd"; + std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); + + try + { + auto service = ipmi::getService(*busp, nmiSourceIntf, nmiSourceObjPath); + ipmi::setDbusProperty(*busp, service, nmiSourceObjPath, nmiSourceIntf, + "BMCSource", bmcSourceSignal); + ipmi::setDbusProperty(*busp, service, nmiSourceObjPath, nmiSourceIntf, + "Enabled", value); + } + catch (std::exception& e) + { + log<level::ERR>("Failed to trigger NMI_OUT", + entry("EXCEPTION=%s", e.what())); + return -1; + } + + return 0; +} + namespace power_policy { @@ -777,175 +799,264 @@ using namespace sdbusplus::xyz::openbmc_project::Control::Power::server; using IpmiValue = uint8_t; using DbusValue = RestorePolicy::Policy; -std::map<DbusValue, IpmiValue> dbusToIpmi = { +const std::map<DbusValue, IpmiValue> dbusToIpmi = { {RestorePolicy::Policy::AlwaysOff, 0x00}, {RestorePolicy::Policy::Restore, 0x01}, {RestorePolicy::Policy::AlwaysOn, 0x02}}; static constexpr uint8_t noChange = 0x03; static constexpr uint8_t allSupport = 0x01 | 0x02 | 0x04; -static constexpr uint8_t policyBitMask = 0x07; -static constexpr uint8_t setPolicyReqLen = 1; + +/* 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) + { + log<level::ERR>( + "Failed to fetch pgood property", entry("ERROR=%s", e.what()), + entry("PATH=%s", objects.map.at(powerRestoreIntf).front().c_str()), + entry("INTERFACE=%s", powerRestoreIntf)); + 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"); + powerGood = std::get<std::string>(powerState) == + "xyz.openbmc_project.State.Chassis.PowerState.On"; + } + 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) + { + log<level::ERR>("Failed to fetch pgood property", + entry("ERROR=%s", e.what())); + 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) + { + log<level::ERR>("Failed to fetch PFail property", + entry("ERROR=%s", e.what()), + entry("PATH=%s", powerControlObj), + entry("INTERFACE=%s", powerControlIntf)); + } + return acFail; +} } // namespace power_policy +static std::optional<bool> getButtonEnabled(const std::string& buttonPath, + const std::string& buttonIntf) +{ + std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); + bool buttonDisabled = false; + try + { + auto service = ipmi::getService(*busp, buttonIntf, buttonPath); + ipmi::Value enabled = ipmi::getDbusProperty(*busp, service, buttonPath, + buttonIntf, "Enabled"); + buttonDisabled = !std::get<bool>(enabled); + } + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Fail to get button Enabled property", + entry("PATH=%s", buttonPath.c_str()), + entry("ERROR=%s", e.what())); + return std::nullopt; + } + return std::make_optional(buttonDisabled); +} + //---------------------------------------------------------------------- // Get Chassis Status commands //---------------------------------------------------------------------- -ipmi_ret_t ipmi_get_chassis_status(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<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() { - const char* objname = "/org/openbmc/control/power0"; - const char* intf = "org.openbmc.control.Power"; - - sd_bus* bus = NULL; - sd_bus_message* reply = NULL; - int r = 0; - int pgood = 0; - char* busname = NULL; - ipmi_ret_t rc = IPMI_CC_OK; - ipmi_get_chassis_status_t chassis_status{}; - - uint8_t s = 0; - using namespace chassis::internal; - using namespace chassis::internal::cache; - using namespace power_policy; - - const auto& powerRestoreSetting = objects.map.at(powerRestoreIntf).front(); - auto method = dbus.new_method_call( - objects.service(powerRestoreSetting, powerRestoreIntf).c_str(), - powerRestoreSetting.c_str(), ipmi::PROP_INTF, "Get"); - method.append(powerRestoreIntf, "PowerRestorePolicy"); - auto resp = dbus.call(method); - if (resp.is_method_error()) + std::optional<uint2_t> restorePolicy = + power_policy::getPowerRestorePolicy(); + std::optional<bool> powerGood = power_policy::getPowerStatus(); + if (!restorePolicy || !powerGood) { - log<level::ERR>("Error in PowerRestorePolicy Get"); - report<InternalFailure>(); - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - sdbusplus::message::variant<std::string> result; - resp.read(result); - auto powerRestore = RestorePolicy::convertPolicyFromString( - variant_ns::get<std::string>(result)); - - *data_len = 4; - - bus = ipmid_get_sd_bus_connection(); - - r = mapper_get_service(bus, objname, &busname); - if (r < 0) - { - log<level::ERR>("Failed to get bus name", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_UNSPECIFIED_ERROR; - goto finish; - } - - r = sd_bus_get_property(bus, busname, objname, intf, "pgood", NULL, &reply, - "i"); - if (r < 0) - { - log<level::ERR>("Failed to call sd_bus_get_property", - entry("PROPERTY=%s", "pgood"), entry("ERRNO=0x%X", -r), - entry("BUS=%s", busname), entry("PATH=%s", objname), - entry("INTERFACE=%s", intf)); - rc = IPMI_CC_UNSPECIFIED_ERROR; - goto finish; - } - - r = sd_bus_message_read(reply, "i", &pgood); - if (r < 0) - { - log<level::ERR>("Failed to read sensor:", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_UNSPECIFIED_ERROR; - goto finish; - } - - s = dbusToIpmi.at(powerRestore); - - // Current Power State - // [7] reserved - // [6..5] power restore policy - // 00b = chassis stays powered off after AC/mains returns - // 01b = after AC returns, power is restored to the state that was - // in effect when AC/mains was lost. - // 10b = chassis always powers up after AC/mains returns - // 11b = unknow - // Set to 00b, by observing the hardware behavior. - // Do we need to define a dbus property to identify the restore - // policy? - - // [4] power control fault - // 1b = controller attempted to turn system power on or off, but - // system did not enter desired state. - // Set to 0b, since We don't support it.. - - // [3] power fault - // 1b = fault detected in main power subsystem. - // set to 0b. for we don't support it. - - // [2] 1b = interlock (chassis is presently shut down because a chassis - // panel interlock switch is active). (IPMI 1.5) - // set to 0b, for we don't support it. - - // [1] power overload - // 1b = system shutdown because of power overload condition. - // set to 0b, for we don't support it. - - // [0] power is on - // 1b = system power is on - // 0b = system power is off(soft-off S4/S5, or mechanical off) - - chassis_status.cur_power_state = ((s & 0x3) << 5) | (pgood & 0x1); - - // Last Power Event - // [7..5] – reserved - // [4] – 1b = last ‘Power is on’ state was entered via IPMI command - // [3] – 1b = last power down caused by power fault - // [2] – 1b = last power down caused by a power interlock being activated - // [1] – 1b = last power down caused by a Power overload - // [0] – 1b = AC failed - // set to 0x0, for we don't support these fields. - - chassis_status.last_power_event = 0; - - // Misc. Chassis State - // [7] – reserved - // [6] – 1b = Chassis Identify command and state info supported (Optional) - // 0b = Chassis Identify command support unspecified via this command. - // (The Get Command Support command , if implemented, would still - // indicate support for the Chassis Identify command) - // [5..4] – Chassis Identify State. Mandatory when bit[6] =1b, reserved - // (return - // as 00b) otherwise. Returns the present chassis identify state. - // Refer to the Chassis Identify command for more info. - // 00b = chassis identify state = Off - // 01b = chassis identify state = Temporary(timed) On - // 10b = chassis identify state = Indefinite On - // 11b = reserved - // [3] – 1b = Cooling/fan fault detected - // [2] – 1b = Drive Fault - // [1] – 1b = Front Panel Lockout active (power off and reset via chassis - // push-buttons disabled.) - // [0] – 1b = Chassis Intrusion active - // set to 0, for we don't support them. - chassis_status.misc_power_state = 0; // Front Panel Button Capabilities and disable/enable status(Optional) - // set to 0, for we don't support them. - chassis_status.front_panel_button_cap_status = 0; - - // Pack the actual response - std::memcpy(response, &chassis_status, *data_len); + std::optional<bool> powerButtonReading = + getButtonEnabled(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; + } -finish: - free(busname); - reply = sd_bus_message_unref(reply); + std::optional<bool> resetButtonReading = + getButtonEnabled(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; + } - return rc; + bool powerDownAcFailed = power_policy::getACFailStatus(); + + // 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 chassisIntrusionActive = 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 = static_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); } //------------------------------------------------------------- @@ -1018,25 +1129,16 @@ void indicate_no_softoff_needed() std::ofstream(path.c_str()); } -//---------------------------------------------------------------------- -// Chassis Control commands -//---------------------------------------------------------------------- -ipmi_ret_t ipmi_chassis_control(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) +/** @brief Implementation of chassis control command + * + * @param - chassisControl command byte + * + * @return Success or InvalidFieldRequest. + */ +ipmi::RspType<> ipmiChassisControl(uint8_t chassisControl) { - // Error from power off. int rc = 0; - - // No response for this command. - *data_len = 0; - - // Catch the actual operaton by peeking into request buffer - uint8_t chassis_ctrl_cmd = *(uint8_t*)request; - - switch (chassis_ctrl_cmd) + switch (chassisControl) { case CMD_POWER_ON: rc = initiate_state_transition(State::Host::Transition::On); @@ -1096,15 +1198,20 @@ ipmi_ret_t ipmi_chassis_control(ipmi_netfn_t netfn, ipmi_cmd_t cmd, rc = initiate_state_transition(State::Host::Transition::Off); break; + case CMD_PULSE_DIAGNOSTIC_INTR: + rc = setNmiProperty(true); + break; + default: { log<level::ERR>("Invalid Chassis Control command", - entry("CMD=0x%X", chassis_ctrl_cmd)); - rc = -1; + entry("CMD=0x%X", chassisControl)); + return ipmi::responseInvalidFieldRequest(); } } - return ((rc < 0) ? IPMI_CC_INVALID : IPMI_CC_OK); + return ((rc < 0) ? ipmi::responseUnspecifiedError() + : ipmi::responseSuccess()); } /** @brief Return D-Bus connection string to enclosure identify LED object @@ -1159,7 +1266,7 @@ void enclosureIdentifyLed(bool flag) dbus.new_method_call(connection.c_str(), identify_led_object_name, "org.freedesktop.DBus.Properties", "Set"); led.append("xyz.openbmc_project.Led.Group", "Asserted", - sdbusplus::message::variant<bool>(flag)); + std::variant<bool>(flag)); auto ledReply = dbus.call(led); if (ledReply.is_method_error()) { @@ -1175,6 +1282,7 @@ void enclosureIdentifyLedOff() { try { + chassisIDState = ChassisIDState::off; enclosureIdentifyLed(false); } catch (const InternalFailure& e) @@ -1207,6 +1315,7 @@ ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval, identifyTimer->stop(); try { + chassisIDState = ChassisIDState::temporaryOn; enclosureIdentifyLed(true); } catch (const InternalFailure& e) @@ -1217,6 +1326,7 @@ ipmi::RspType<> ipmiChassisIdentify(std::optional<uint8_t> interval, if (forceIdentify) { + chassisIDState = ChassisIDState::indefiniteOn; return ipmi::responseSuccess(); } // start the timer @@ -1243,10 +1353,13 @@ 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}}; @@ -1254,10 +1367,13 @@ 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}}; @@ -1271,8 +1387,8 @@ static ipmi_ret_t setBootSource(const Source::Sources& source) { using namespace chassis::internal; using namespace chassis::internal::cache; - sdbusplus::message::variant<std::string> property = - convertForMessage(source); + std::variant<std::string> property = convertForMessage(source); + settings::Objects& objects = getObjects(); auto bootSetting = settings::boot::setting(objects, bootSourceIntf); const auto& bootSourceSetting = std::get<settings::Path>(bootSetting); auto method = dbus.new_method_call( @@ -1297,7 +1413,8 @@ static ipmi_ret_t setBootMode(const Mode::Modes& mode) { using namespace chassis::internal; using namespace chassis::internal::cache; - sdbusplus::message::variant<std::string> property = convertForMessage(mode); + std::variant<std::string> property = convertForMessage(mode); + settings::Objects& objects = getObjects(); auto bootSetting = settings::boot::setting(objects, bootModeIntf); const auto& bootModeSetting = std::get<settings::Path>(bootSetting); auto method = dbus.new_method_call( @@ -1347,6 +1464,7 @@ ipmi_ret_t ipmi_chassis_get_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, try { + settings::Objects& objects = getObjects(); auto bootSetting = settings::boot::setting(objects, bootSourceIntf); const auto& bootSourceSetting = std::get<settings::Path>(bootSetting); @@ -1364,10 +1482,10 @@ ipmi_ret_t ipmi_chassis_get_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, *data_len = 0; return IPMI_CC_UNSPECIFIED_ERROR; } - sdbusplus::message::variant<std::string> result; + std::variant<std::string> result; reply.read(result); - auto bootSource = Source::convertSourcesFromString( - variant_ns::get<std::string>(result)); + auto bootSource = + Source::convertSourcesFromString(std::get<std::string>(result)); bootSetting = settings::boot::setting(objects, bootModeIntf); const auto& bootModeSetting = std::get<settings::Path>(bootSetting); @@ -1384,8 +1502,8 @@ ipmi_ret_t ipmi_chassis_get_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } reply.read(result); - auto bootMode = Mode::convertModesFromString( - variant_ns::get<std::string>(result)); + auto bootMode = + Mode::convertModesFromString(std::get<std::string>(result)); bootOption = sourceDbusToIpmi.at(bootSource); if ((Mode::Modes::Regular == bootMode) && @@ -1407,6 +1525,7 @@ ipmi_ret_t ipmi_chassis_get_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } catch (InternalFailure& e) { + cache::objectsPtr.reset(); report<InternalFailure>(); *data_len = 0; return IPMI_CC_UNSPECIFIED_ERROR; @@ -1489,6 +1608,8 @@ ipmi_ret_t ipmi_chassis_set_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, (reqptr->data[0] & SET_PARM_BOOT_FLAGS_PERMANENT) == SET_PARM_BOOT_FLAGS_PERMANENT; + settings::Objects& objects = getObjects(); + auto bootSetting = settings::boot::setting(objects, bootSourceIntf); oneTimeEnabled = @@ -1548,9 +1669,17 @@ ipmi_ret_t ipmi_chassis_set_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, setBootSource(Source::Sources::Default); } } + if ((modeIpmiToDbus.end() == modeItr) && + (sourceIpmiToDbus.end() == sourceItr)) + { + // return error if boot option is not supported + *data_len = 0; + return IPMI_CC_INVALID_FIELD_REQUEST; + } } catch (InternalFailure& e) { + objectsPtr.reset(); report<InternalFailure>(); *data_len = 0; return IPMI_CC_UNSPECIFIED_ERROR; @@ -1588,74 +1717,57 @@ ipmi_ret_t ipmi_chassis_set_sys_boot_options(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return rc; } -ipmi_ret_t ipmiGetPOHCounter(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) +/** @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 - ipmi_ret_t rc = IPMI_CC_OK; - - auto resptr = reinterpret_cast<GetPOHCountResponse*>(response); - try { - auto pohCounter = getPOHCounter(); - resptr->counterReading[0] = pohCounter; - resptr->counterReading[1] = pohCounter >> 8; - resptr->counterReading[2] = pohCounter >> 16; - resptr->counterReading[3] = pohCounter >> 24; + return ipmi::responseSuccess(static_cast<uint8_t>(poh::minutesPerCount), + getPOHCounter()); } catch (std::exception& e) { log<level::ERR>(e.what()); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - - resptr->minPerCount = poh::minutesPerCount; - *data_len = sizeof(GetPOHCountResponse); - - return rc; } -ipmi_ret_t ipmi_chassis_set_power_restore_policy( - 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<uint3_t, // policy support + uint5_t // reserved + > + ipmiChassisSetPowerRestorePolicy(boost::asio::yield_context yield, + uint3_t policy, uint5_t reserved) { - auto* reqptr = reinterpret_cast<uint8_t*>(request); - auto* resptr = reinterpret_cast<uint8_t*>(response); - uint8_t reqPolicy = 0; - power_policy::DbusValue value = power_policy::RestorePolicy::Policy::AlwaysOff; - if (*data_len != power_policy::setPolicyReqLen) + if (reserved || (policy > power_policy::noChange)) { - phosphor::logging::log<level::ERR>("Unsupported request length", - entry("LEN=0x%x", *data_len)); - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; + phosphor::logging::log<level::ERR>( + "Reserved request parameter", + entry("REQ=0x%x", static_cast<int>(policy))); + return ipmi::responseInvalidFieldRequest(); } - if (*reqptr > power_policy::noChange) - { - phosphor::logging::log<level::ERR>("Reserved request parameter", - entry("REQ=0x%x", *reqptr)); - *data_len = 0; - return IPMI_CC_PARM_OUT_OF_RANGE; - } - - reqPolicy = *reqptr & power_policy::policyBitMask; - if (reqPolicy == power_policy::noChange) + if (policy == power_policy::noChange) { // just return the supported policy - *resptr = power_policy::allSupport; - *data_len = power_policy::setPolicyReqLen; - return IPMI_CC_OK; + return ipmi::responseSuccess(power_policy::allSupport, reserved); } for (auto const& it : power_policy::dbusToIpmi) { - if (it.second == reqPolicy) + if (it.second == policy) { value = it.first; break; @@ -1664,57 +1776,51 @@ ipmi_ret_t ipmi_chassis_set_power_restore_policy( try { + settings::Objects& objects = chassis::internal::cache::getObjects(); const settings::Path& powerRestoreSetting = - chassis::internal::cache::objects.map - .at(chassis::internal::powerRestoreIntf) - .front(); - sdbusplus::message::variant<std::string> property = - convertForMessage(value); - - auto method = chassis::internal::dbus.new_method_call( - chassis::internal::cache::objects + objects.map.at(chassis::internal::powerRestoreIntf).front(); + std::variant<std::string> property = convertForMessage(value); + + auto sdbusp = getSdBus(); + boost::system::error_code ec; + sdbusp->yield_method_call<void>( + yield, ec, + objects .service(powerRestoreSetting, chassis::internal::powerRestoreIntf) .c_str(), - powerRestoreSetting.c_str(), ipmi::PROP_INTF, "Set"); - - method.append(chassis::internal::powerRestoreIntf, "PowerRestorePolicy", - property); - auto reply = chassis::internal::dbus.call(method); - if (reply.is_method_error()) + powerRestoreSetting, ipmi::PROP_INTF, "Set", + chassis::internal::powerRestoreIntf, "PowerRestorePolicy", + property); + if (ec) { phosphor::logging::log<level::ERR>("Unspecified Error"); - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } catch (InternalFailure& e) { + chassis::internal::cache::objectsPtr.reset(); report<InternalFailure>(); - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - *resptr = power_policy::allSupport; - *data_len = power_policy::setPolicyReqLen; - return IPMI_CC_OK; + return ipmi::responseSuccess(power_policy::allSupport, reserved); } void register_netfn_chassis_functions() { createIdentifyTimer(); - // <Wildcard Command> - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_WILDCARD, NULL, - ipmi_chassis_wildcard, PRIVILEGE_USER); - // Get Chassis Capabilities - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_GET_CHASSIS_CAP, NULL, - ipmi_get_chassis_cap, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdGetChassisCapabilities, + ipmi::Privilege::User, ipmiGetChassisCap); // Set Chassis Capabilities - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_SET_CHASSIS_CAP, NULL, - ipmi_set_chassis_cap, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdSetChassisCapabilities, + ipmi::Privilege::User, ipmiSetChassisCap); // <Get System Boot Options> ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_GET_SYS_BOOT_OPTIONS, NULL, @@ -1722,12 +1828,14 @@ void register_netfn_chassis_functions() PRIVILEGE_OPERATOR); // <Get Chassis Status> - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_CHASSIS_STATUS, NULL, - ipmi_get_chassis_status, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdGetChassisStatus, + ipmi::Privilege::User, ipmiGetChassisStatus); // <Chassis Control> - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_CHASSIS_CONTROL, NULL, - ipmi_chassis_control, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdChassisControl, + ipmi::Privilege::Operator, ipmiChassisControl); // <Chassis Identify> ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, @@ -1739,11 +1847,13 @@ void register_netfn_chassis_functions() ipmi_chassis_set_sys_boot_options, PRIVILEGE_OPERATOR); // <Get POH Counter> - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_GET_POH_COUNTER, NULL, - ipmiGetPOHCounter, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdGetPohCounter, + ipmi::Privilege::User, ipmiGetPOHCounter); // <Set Power Restore Policy> - ipmi_register_callback(NETFUN_CHASSIS, IPMI_CMD_SET_RESTORE_POLICY, NULL, - ipmi_chassis_set_power_restore_policy, - PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnChassis, + ipmi::chassis::cmdSetPowerRestorePolicy, + ipmi::Privilege::Operator, + ipmiChassisSetPowerRestorePolicy); } diff --git a/chassishandler.hpp b/chassishandler.hpp index 49b5ef8..dcaf06c 100644 --- a/chassishandler.hpp +++ b/chassishandler.hpp @@ -14,8 +14,6 @@ enum ipmi_netfn_chassis_cmds IPMI_CMD_CHASSIS_CONTROL = 0x02, IPMI_CMD_CHASSIS_IDENTIFY = 0x04, IPMI_CMD_SET_CHASSIS_CAP = 0x05, - // Set Power Restore Policy - IPMI_CMD_SET_RESTORE_POLICY = 0x06, // Get capability bits IPMI_CMD_SET_SYS_BOOT_OPTIONS = 0x08, IPMI_CMD_GET_SYS_BOOT_OPTIONS = 0x09, @@ -58,3 +56,11 @@ enum class BootOptionResponseSize : size_t BOOT_FLAGS = 5, OPAL_NETWORK_SETTINGS = 50 }; + +enum class ChassisIDState : uint8_t +{ + off = 0x0, + temporaryOn = 0x1, + indefiniteOn = 0x2, + reserved = 0x3 +}; diff --git a/configure.ac b/configure.ac index 2bb011f..29f58e2 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,28 @@ AM_PROG_AR AC_PROG_INSTALL AC_PROG_MAKE_SET +# Add an option to enable/disable safe mode in boot flags +AC_ARG_ENABLE([boot-flag-safe-mode-support], + AS_HELP_STRING([--disable-boot-flag-safe-mode-support], [Disable safe mode option in boot flags. [default=enable]]) +) +AS_IF([test "x$enable_boot_flag_safe_mode_support" != "xno"], + AC_MSG_NOTICE([Enabling safe mode option in boot flags]) + [cpp_flags="-DENABLE_BOOT_FLAG_SAFE_MODE_SUPPORT"] + AC_SUBST([CPPFLAGS], [$cpp_flags]), + AC_MSG_WARN([Disabling safe mode option in boot flags]) +) + +# Add an option to enable/disable i2c master write read command white list checking +AC_ARG_ENABLE([i2c-whitelist-check], + AS_HELP_STRING([--disable-i2c-whitelist-check], [Disable I2C master write read command white list check. [default=enable]]) +) +AS_IF([test "x$enable_i2c_whitelist_check" != "xno"], + AC_MSG_NOTICE([Enabling I2C master write read command white list check]) + [cpp_flags="-DENABLE_I2C_WHITELIST_CHECK"] + AC_SUBST([CPPFLAGS], [$cpp_flags]), + AC_MSG_WARN([Disabling I2C master write read command white list check]) +) + # softoff dir specific ones AC_ARG_ENABLE([softoff], AS_HELP_STRING([--enable-softoff], [Builds soft power off]) @@ -103,20 +125,24 @@ if test -z "$WHITELIST_CONF"; then WHITELIST_CONF=${srcdir}/host-ipmid-whitelist.conf fi -AS_IF([test "x$SENSOR_YAML_GEN" == "x"], [SENSOR_YAML_GEN="sensor-example.yaml"]) +AS_IF([test "x$SENSOR_YAML_GEN" == "x"], [SENSOR_YAML_GEN="$srcdir/scripts/sensor-example.yaml"]) SENSORGEN="$PYTHON ${srcdir}/scripts/sensor_gen.py -i $SENSOR_YAML_GEN" +AC_SUBST(SENSOR_YAML_GEN) AC_SUBST(SENSORGEN) -AS_IF([test "x$INVSENSOR_YAML_GEN" == "x"], [INVSENSOR_YAML_GEN="inventory-sensor-example.yaml"]) +AS_IF([test "x$INVSENSOR_YAML_GEN" == "x"], [INVSENSOR_YAML_GEN="$srcdir/scripts/inventory-sensor-example.yaml"]) INVSENSORGEN="$PYTHON ${srcdir}/scripts/inventory-sensor.py -i $INVSENSOR_YAML_GEN" +AC_SUBST(INVSENSOR_YAML_GEN) AC_SUBST(INVSENSORGEN) -AS_IF([test "x$FRU_YAML_GEN" == "x"], [FRU_YAML_GEN="fru-read-example.yaml"]) +AS_IF([test "x$FRU_YAML_GEN" == "x"], [FRU_YAML_GEN="$srcdir/scripts/fru-read-example.yaml"]) FRUGEN="$PYTHON $srcdir/scripts/fru_gen.py -i $FRU_YAML_GEN" +AC_SUBST(FRU_YAML_GEN) AC_SUBST(FRUGEN) -AS_IF([test "x$ENTITY_YAML_GEN" == "x"], [ENTITY_YAML_GEN="entity-example.yaml"]) +AS_IF([test "x$ENTITY_YAML_GEN" == "x"], [ENTITY_YAML_GEN="$srcdir/scripts/entity-example.yaml"]) ENTITYGEN="$PYTHON $srcdir/scripts/entity_gen.py -i $ENTITY_YAML_GEN" +AC_SUBST(ENTITY_YAML_GEN) AC_SUBST(ENTITYGEN) AC_DEFINE(CALLOUT_FWD_ASSOCIATION, "callout", [The name of the callout's forward association.]) @@ -188,6 +214,58 @@ AC_ARG_VAR(HOST_IPMI_LIB_PATH, [The file path to search for libraries.]) AS_IF([test "x$HOST_IPMI_LIB_PATH" == "x"], [HOST_IPMI_LIB_PATH="/usr/lib/ipmid-providers/"]) AC_DEFINE_UNQUOTED([HOST_IPMI_LIB_PATH], ["$HOST_IPMI_LIB_PATH"], [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. +AC_ARG_ENABLE([update-functional-on-fail], + AS_HELP_STRING( + [--enable-update-functional-on-fail], + [Check functional property to skip reading from faulty sensors.] + ) +) + +AC_ARG_VAR(UPDATE_FUNCTIONAL_ON_FAIL, [Check functional property to skip reading from faulty sensors.]) +AS_IF( + [test "x$enable_update_functional_on_fail" == "xyes"], + [UPDATE_FUNCTIONAL_ON_FAIL="yes"] + AC_DEFINE_UNQUOTED( + [UPDATE_FUNCTIONAL_ON_FAIL], + ["$UPDATE_FUNCTIONAL_ON_FAIL"], + [Check functional property to skip reading from faulty sensors.] + ) +) + +# When disable-libuserlayer flag is set, libuserlayer won't be included in the build. +AC_ARG_ENABLE([libuserlayer], + AS_HELP_STRING([--disable-libuserlayer], [Set a flag to exclude libuserlayer]) +) +AM_CONDITIONAL(FEATURE_LIBUSERLAYER, [test "x$enable_libuserlayer" != "xno"]) + +# When enable-transport-oem flag is set, 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. +AC_ARG_ENABLE([transport_oem], + [ --enable-transport-oem Enable/disable OEM Parameter extensions], + [case "${enableval}" in + yes) transport_oem=true ;; + no) transport_oem=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-transport_oem]) ;; + esac],[transport_oem=false] + ) +AM_CONDITIONAL([FEATURE_TRANSPORT_OEM], [test x$transport_oem = xtrue]) + +# IPMI whitelist mechanism is not needed by everyone; offer a way to disable it +AC_ARG_ENABLE([ipmi-whitelist], + [ --enable-ipmi-whitelist Enable/disable IPMI whitelist filtering], + [case "${enableval}" in + yes) ipmi_whitelist=true ;; + no) ipmi_whitelist=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-ipmi-whitelist]) ;; + esac],[ipmi_whitelist=true] + ) +AM_CONDITIONAL([FEATURE_IPMI_WHITELIST], [test x$ipmi_whitelist = xtrue]) + # Create configured output AC_CONFIG_FILES([ Makefile diff --git a/dcmihandler.cpp b/dcmihandler.cpp index 3188585..f8498f5 100644 --- a/dcmihandler.cpp +++ b/dcmihandler.cpp @@ -13,7 +13,7 @@ #include <phosphor-logging/elog-errors.hpp> #include <phosphor-logging/log.hpp> #include <sdbusplus/bus.hpp> -#include <sdbusplus/message/types.hpp> +#include <variant> #include <xyz/openbmc_project/Common/error.hpp> using namespace phosphor::logging; @@ -37,8 +37,8 @@ constexpr auto DCMI_OPTION_60_43_MASK = 0x02; constexpr auto DCMI_OPTION_12_MASK = 0x01; constexpr auto DCMI_ACTIVATE_DHCP_MASK = 0x01; constexpr auto DCMI_ACTIVATE_DHCP_REPLY = 0x00; -constexpr auto DCMI_SET_CONF_PARAM_REQ_PACKET_MAX_SIZE = 0x05; -constexpr auto DCMI_SET_CONF_PARAM_REQ_PACKET_MIN_SIZE = 0x04; +constexpr auto DCMI_SET_CONF_PARAM_REQ_PACKET_MAX_SIZE = 0x04; +constexpr auto DCMI_SET_CONF_PARAM_REQ_PACKET_MIN_SIZE = 0x03; constexpr auto DHCP_TIMING1 = 0x04; // 4 sec constexpr auto DHCP_TIMING2_UPPER = 0x00; // 2 min constexpr auto DHCP_TIMING2_LOWER = 0x78; @@ -54,7 +54,6 @@ constexpr auto SENSOR_VALUE_PROP = "Value"; constexpr auto SENSOR_SCALE_PROP = "Scale"; using namespace phosphor::logging; -namespace variant_ns = sdbusplus::message::variant_ns; namespace dcmi { @@ -86,10 +85,10 @@ uint32_t getPcap(sdbusplus::bus::bus& bus) log<level::ERR>("Error in getPcap prop"); elog<InternalFailure>(); } - sdbusplus::message::variant<uint32_t> pcap; + std::variant<uint32_t> pcap; reply.read(pcap); - return variant_ns::get<uint32_t>(pcap); + return std::get<uint32_t>(pcap); } bool getPcapEnabled(sdbusplus::bus::bus& bus) @@ -107,10 +106,10 @@ bool getPcapEnabled(sdbusplus::bus::bus& bus) log<level::ERR>("Error in getPcapEnabled prop"); elog<InternalFailure>(); } - sdbusplus::message::variant<bool> pcapEnabled; + std::variant<bool> pcapEnabled; reply.read(pcapEnabled); - return variant_ns::get<bool>(pcapEnabled); + return std::get<bool>(pcapEnabled); } void setPcap(sdbusplus::bus::bus& bus, const uint32_t powerCap) @@ -121,7 +120,7 @@ void setPcap(sdbusplus::bus::bus& bus, const uint32_t powerCap) "org.freedesktop.DBus.Properties", "Set"); method.append(PCAP_INTERFACE, POWER_CAP_PROP); - method.append(sdbusplus::message::variant<uint32_t>(powerCap)); + method.append(std::variant<uint32_t>(powerCap)); auto reply = bus.call(method); @@ -140,7 +139,7 @@ void setPcapEnable(sdbusplus::bus::bus& bus, bool enabled) "org.freedesktop.DBus.Properties", "Set"); method.append(PCAP_INTERFACE, POWER_CAP_ENABLE_PROP); - method.append(sdbusplus::message::variant<bool>(enabled)); + method.append(std::variant<bool>(enabled)); auto reply = bus.call(method); @@ -206,10 +205,10 @@ std::string readAssetTag() elog<InternalFailure>(); } - sdbusplus::message::variant<std::string> assetTag; + std::variant<std::string> assetTag; reply.read(assetTag); - return variant_ns::get<std::string>(assetTag); + return std::get<std::string>(assetTag); } void writeAssetTag(const std::string& assetTag) @@ -226,7 +225,7 @@ void writeAssetTag(const std::string& assetTag) (objectTree.begin()->first).c_str(), dcmi::propIntf, "Set"); method.append(dcmi::assetTagIntf); method.append(dcmi::assetTagProp); - method.append(sdbusplus::message::variant<std::string>(assetTag)); + method.append(std::variant<std::string>(assetTag)); auto reply = bus.call(method); if (reply.is_method_error()) @@ -244,7 +243,7 @@ std::string getHostName(void) auto value = ipmi::getDbusProperty(bus, service, networkConfigObj, networkConfigIntf, hostNameProp); - return variant_ns::get<std::string>(value); + return std::get<std::string>(value); } bool getDHCPEnabled() @@ -258,7 +257,7 @@ bool getDHCPEnabled() auto value = ipmi::getDbusProperty(bus, service, ethernetObj.first, ethernetIntf, "DHCPEnabled"); - return variant_ns::get<bool>(value); + return std::get<bool>(value); } bool getDHCPOption(std::string prop) @@ -268,7 +267,7 @@ bool getDHCPOption(std::string prop) auto service = ipmi::getService(bus, dhcpIntf, dhcpObj); auto value = ipmi::getDbusProperty(bus, service, dhcpObj, dhcpIntf, prop); - return variant_ns::get<bool>(value); + return std::get<bool>(value); } void setDHCPOption(std::string prop, bool value) @@ -311,18 +310,10 @@ ipmi_ret_t getPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_INVALID; } - auto requestData = - reinterpret_cast<const dcmi::GetPowerLimitRequest*>(request); std::vector<uint8_t> outPayload(sizeof(dcmi::GetPowerLimitResponse)); auto responseData = reinterpret_cast<dcmi::GetPowerLimitResponse*>(outPayload.data()); - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } - sdbusplus::bus::bus sdbus{ipmid_get_sd_bus_connection()}; uint32_t pcapValue = 0; bool pcapEnable = false; @@ -338,8 +329,6 @@ ipmi_ret_t getPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; - /* * Exception action if power limit is exceeded and cannot be controlled * with the correction time limit is hardcoded to Hard Power Off system @@ -381,15 +370,6 @@ ipmi_ret_t setPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto requestData = reinterpret_cast<const dcmi::SetPowerLimitRequest*>(request); - std::vector<uint8_t> outPayload(sizeof(dcmi::SetPowerLimitResponse)); - auto responseData = - reinterpret_cast<dcmi::SetPowerLimitResponse*>(outPayload.data()); - - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } sdbusplus::bus::bus sdbus{ipmid_get_sd_bus_connection()}; @@ -407,10 +387,7 @@ ipmi_ret_t setPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::INFO>("Set Power Cap", entry("POWERCAP=%u", requestData->powerLimit)); - responseData->groupID = dcmi::groupExtId; - memcpy(response, outPayload.data(), outPayload.size()); - *data_len = outPayload.size(); - + *data_len = 0; return IPMI_CC_OK; } @@ -427,15 +404,6 @@ ipmi_ret_t applyPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto requestData = reinterpret_cast<const dcmi::ApplyPowerLimitRequest*>(request); - std::vector<uint8_t> outPayload(sizeof(dcmi::ApplyPowerLimitResponse)); - auto responseData = - reinterpret_cast<dcmi::ApplyPowerLimitResponse*>(outPayload.data()); - - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } sdbusplus::bus::bus sdbus{ipmid_get_sd_bus_connection()}; @@ -453,10 +421,7 @@ ipmi_ret_t applyPowerLimit(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::INFO>("Set Power Cap Enable", entry("POWERCAPENABLE=%u", requestData->powerLimitAction)); - responseData->groupID = dcmi::groupExtId; - memcpy(response, outPayload.data(), outPayload.size()); - *data_len = outPayload.size(); - + *data_len = 0; return IPMI_CC_OK; } @@ -470,12 +435,6 @@ ipmi_ret_t getAssetTag(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto responseData = reinterpret_cast<dcmi::GetAssetTagResponse*>(outPayload.data()); - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } - // Verify offset to read and number of bytes to read are not exceeding the // range. if ((requestData->offset > dcmi::assetTagMaxOffset) || @@ -498,8 +457,6 @@ ipmi_ret_t getAssetTag(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; - // Return if the asset tag is not populated. if (!assetTag.size()) { @@ -545,12 +502,6 @@ ipmi_ret_t setAssetTag(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto responseData = reinterpret_cast<dcmi::SetAssetTagResponse*>(outPayload.data()); - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } - // Verify offset to read and number of bytes to read are not exceeding the // range. if ((requestData->offset > dcmi::assetTagMaxOffset) || @@ -581,7 +532,6 @@ ipmi_ret_t setAssetTag(ipmi_netfn_t netfn, ipmi_cmd_t cmd, dcmi::writeAssetTag(assetTag); - responseData->groupID = dcmi::groupExtId; responseData->tagLength = assetTag.size(); memcpy(response, outPayload.data(), outPayload.size()); *data_len = outPayload.size(); @@ -607,8 +557,7 @@ ipmi_ret_t getMgmntCtrlIdStr(ipmi_netfn_t netfn, ipmi_cmd_t cmd, *data_len = 0; - if (requestData->groupID != dcmi::groupExtId || - requestData->bytes > dcmi::maxBytes || + if (requestData->bytes > dcmi::maxBytes || requestData->offset + requestData->bytes > dcmi::maxCtrlIdStrLen) { return IPMI_CC_INVALID_FIELD_REQUEST; @@ -630,7 +579,6 @@ ipmi_ret_t getMgmntCtrlIdStr(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto responseStr = hostName.substr(requestData->offset, requestData->bytes); auto responseStrLen = std::min(static_cast<std::size_t>(requestData->bytes), responseStr.length() + 1); - responseData->groupID = dcmi::groupExtId; responseData->strLen = hostName.length(); std::copy(begin(responseStr), end(responseStr), responseData->data); @@ -651,8 +599,7 @@ ipmi_ret_t setMgmntCtrlIdStr(ipmi_netfn_t netfn, ipmi_cmd_t cmd, *data_len = 0; - if (requestData->groupID != dcmi::groupExtId || - requestData->bytes > dcmi::maxBytes || + if (requestData->bytes > dcmi::maxBytes || requestData->offset + requestData->bytes > dcmi::maxCtrlIdStrLen + 1 || (requestData->offset + requestData->bytes == dcmi::maxCtrlIdStrLen + 1 && @@ -701,7 +648,6 @@ ipmi_ret_t setMgmntCtrlIdStr(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; responseData->offset = requestData->offset + requestData->bytes; *data_len = sizeof(*responseData); return IPMI_CC_OK; @@ -768,12 +714,6 @@ ipmi_ret_t getDCMICapabilities(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_INVALID_FIELD_REQUEST; } - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } - auto responseData = reinterpret_cast<dcmi::GetDCMICapResponse*>(response); // For each capabilities in a parameter fill the data from @@ -808,7 +748,6 @@ ipmi_ret_t getDCMICapabilities(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } } - responseData->groupID = dcmi::groupExtId; responseData->major = DCMI_SPEC_MAJOR_VERSION; responseData->minor = DCMI_SPEC_MINOR_VERSION; responseData->paramRevision = DCMI_PARAMETER_REVISION; @@ -834,16 +773,15 @@ Temperature readTemp(const std::string& dbusService, sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; auto result = ipmi::getAllDbusProperties( bus, dbusService, dbusPath, "xyz.openbmc_project.Sensor.Value"); - auto temperature = sdbusplus::message::variant_ns::visit( - ipmi::VariantToDoubleVisitor(), result.at("Value")); + 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 = sdbusplus::message::variant_ns::visit( - ipmi::VariantToDoubleVisitor(), findFactor->second); + factor = std::visit(ipmi::VariantToDoubleVisitor(), findFactor->second); } double scale = std::pow(10, factor); @@ -997,13 +935,6 @@ ipmi_ret_t getTempReadings(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_INVALID_FIELD_REQUEST; } - if (requestData->groupID != dcmi::groupExtId) - { - log<level::ERR>("Invalid Group ID", - entry("GROUP_ID=%d", requestData->groupID)); - return IPMI_CC_INVALID_FIELD_REQUEST; - } - if (requestData->sensorType != dcmi::temperatureSensorType) { log<level::ERR>("Invalid sensor type", @@ -1036,7 +967,6 @@ ipmi_ret_t getTempReadings(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; size_t payloadSize = temps.size() * sizeof(dcmi::temp_readings::Response); if (!temps.empty()) { @@ -1084,8 +1014,8 @@ int64_t getPowerReading(sdbusplus::bus::bus& bus) // Read the sensor value and scale properties auto properties = ipmi::getAllDbusProperties(bus, service, objectPath, SENSOR_VALUE_INTF); - auto value = variant_ns::get<int64_t>(properties[SENSOR_VALUE_PROP]); - auto scale = variant_ns::get<int64_t>(properties[SENSOR_SCALE_PROP]); + auto value = std::get<int64_t>(properties[SENSOR_VALUE_PROP]); + auto scale = std::get<int64_t>(properties[SENSOR_SCALE_PROP]); // Power reading needs to be scaled with the Scale value using the // formula Value * 10^Scale. @@ -1106,19 +1036,15 @@ ipmi_ret_t setDCMIConfParams(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { auto requestData = reinterpret_cast<const dcmi::SetConfParamsRequest*>(request); - auto responseData = - reinterpret_cast<dcmi::SetConfParamsResponse*>(response); - if (requestData->groupID != dcmi::groupExtId || - *data_len < DCMI_SET_CONF_PARAM_REQ_PACKET_MIN_SIZE || + if (*data_len < DCMI_SET_CONF_PARAM_REQ_PACKET_MIN_SIZE || *data_len > DCMI_SET_CONF_PARAM_REQ_PACKET_MAX_SIZE) { - log<level::ERR>("Invalid Group ID or Invalid Requested Packet size", - entry("GROUP_ID=%d", requestData->groupID), + log<level::ERR>("Invalid Requested Packet size", entry("PACKET SIZE=%d", *data_len)); + *data_len = 0; return IPMI_CC_INVALID_FIELD_REQUEST; } - *data_len = 0; try @@ -1170,9 +1096,6 @@ ipmi_ret_t setDCMIConfParams(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::ERR>(e.what()); return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; - *data_len = sizeof(dcmi::SetConfParamsResponse); - return IPMI_CC_OK; } @@ -1188,11 +1111,9 @@ ipmi_ret_t getDCMIConfParams(ipmi_netfn_t netfn, ipmi_cmd_t cmd, responseData->data[0] = 0x00; - if (requestData->groupID != dcmi::groupExtId || - *data_len != sizeof(dcmi::GetConfParamsRequest)) + if (*data_len != sizeof(dcmi::GetConfParamsRequest)) { - log<level::ERR>("Invalid Group ID or Invalid Requested Packet size", - entry("GROUP_ID=%d", requestData->groupID), + log<level::ERR>("Invalid Requested Packet size", entry("PACKET SIZE=%d", *data_len)); return IPMI_CC_INVALID_FIELD_REQUEST; } @@ -1242,7 +1163,6 @@ ipmi_ret_t getDCMIConfParams(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; responseData->major = DCMI_SPEC_MAJOR_VERSION; responseData->minor = DCMI_SPEC_MINOR_VERSION; responseData->paramRevision = DCMI_CONFIG_PARAMETER_REVISION; @@ -1262,17 +1182,9 @@ ipmi_ret_t getPowerReading(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } ipmi_ret_t rc = IPMI_CC_OK; - auto requestData = - reinterpret_cast<const dcmi::GetPowerReadingRequest*>(request); auto responseData = reinterpret_cast<dcmi::GetPowerReadingResponse*>(response); - if (requestData->groupID != dcmi::groupExtId) - { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; - } - sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; int64_t power = 0; try @@ -1286,7 +1198,6 @@ ipmi_ret_t getPowerReading(ipmi_netfn_t netfn, ipmi_cmd_t cmd, entry("PROPERTY=%s", SENSOR_VALUE_PROP)); return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; // TODO: openbmc/openbmc#2819 // Minimum, Maximum, Average power, TimeFrame, TimeStamp, @@ -1427,13 +1338,6 @@ ipmi_ret_t getSensorInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_INVALID_FIELD_REQUEST; } - if (requestData->groupID != dcmi::groupExtId) - { - log<level::ERR>("Invalid Group ID", - entry("GROUP_ID=%d", requestData->groupID)); - return IPMI_CC_INVALID_FIELD_REQUEST; - } - if (requestData->sensorType != dcmi::temperatureSensorType) { log<level::ERR>("Invalid sensor type", @@ -1475,7 +1379,6 @@ ipmi_ret_t getSensorInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_UNSPECIFIED_ERROR; } - responseData->groupID = dcmi::groupExtId; size_t payloadSize = sensors.size() * sizeof(dcmi::sensor_info::Response); if (!sensors.empty()) { diff --git a/dcmihandler.hpp b/dcmihandler.hpp index a188c25..4f35bc6 100644 --- a/dcmihandler.hpp +++ b/dcmihandler.hpp @@ -129,9 +129,8 @@ static constexpr size_t maxCtrlIdStrLen = 63; */ struct GetAssetTagRequest { - uint8_t groupID; //!< Group extension identification. - uint8_t offset; //!< Offset to read. - uint8_t bytes; //!< Number of bytes to read. + uint8_t offset; //!< Offset to read. + uint8_t bytes; //!< Number of bytes to read. } __attribute__((packed)); /** @struct GetAssetTagResponse @@ -140,7 +139,6 @@ struct GetAssetTagRequest */ struct GetAssetTagResponse { - uint8_t groupID; //!< Group extension identification. uint8_t tagLength; //!< Total asset tag length. } __attribute__((packed)); @@ -150,9 +148,8 @@ struct GetAssetTagResponse */ struct SetAssetTagRequest { - uint8_t groupID; //!< Group extension identification. - uint8_t offset; //!< Offset to write. - uint8_t bytes; //!< Number of bytes to write. + uint8_t offset; //!< Offset to write. + uint8_t bytes; //!< Number of bytes to write. } __attribute__((packed)); /** @struct SetAssetTagResponse @@ -161,7 +158,6 @@ struct SetAssetTagRequest */ struct SetAssetTagResponse { - uint8_t groupID; //!< Group extension identification. uint8_t tagLength; //!< Total asset tag length. } __attribute__((packed)); @@ -211,23 +207,12 @@ uint32_t getPcap(sdbusplus::bus::bus& bus); */ bool getPcapEnabled(sdbusplus::bus::bus& bus); -/** @struct GetPowerLimitRequest - * - * DCMI payload for Get Power Limit command request. - */ -struct GetPowerLimitRequest -{ - uint8_t groupID; //!< Group extension identification. - uint16_t reserved; //!< Reserved -} __attribute__((packed)); - /** @struct GetPowerLimitResponse * * DCMI payload for Get Power Limit command response. */ struct GetPowerLimitResponse { - uint8_t groupID; //!< Group extension identification. uint16_t reserved; //!< Reserved. uint8_t exceptionAction; //!< Exception action. uint16_t powerLimit; //!< Power limit requested in watts. @@ -249,7 +234,6 @@ void setPcap(sdbusplus::bus::bus& bus, const uint32_t powerCap); */ struct SetPowerLimitRequest { - uint8_t groupID; //!< Group extension identification. uint16_t reserved; //!< Reserved uint8_t reserved1; //!< Reserved uint8_t exceptionAction; //!< Exception action. @@ -259,15 +243,6 @@ struct SetPowerLimitRequest uint16_t samplingPeriod; //!< Statistics sampling period in seconds. } __attribute__((packed)); -/** @struct SetPowerLimitResponse - * - * DCMI payload for Set Power Limit command response. - */ -struct SetPowerLimitResponse -{ - uint8_t groupID; //!< Group extension identification. -} __attribute__((packed)); - /** @brief Enable or disable the power capping * * @param[in] bus - dbus connection @@ -281,29 +256,18 @@ void setPcapEnable(sdbusplus::bus::bus& bus, bool enabled); */ struct ApplyPowerLimitRequest { - uint8_t groupID; //!< Group extension identification. uint8_t powerLimitAction; //!< Power limit activation uint16_t reserved; //!< Reserved } __attribute__((packed)); -/** @struct ApplyPowerLimitResponse - * - * DCMI payload for Acticate/Deactivate Power Limit command response. - */ -struct ApplyPowerLimitResponse -{ - uint8_t groupID; //!< Group extension identification. -} __attribute__((packed)); - /** @struct GetMgmntCtrlIdStrRequest * * DCMI payload for Get Management Controller Identifier String cmd request. */ struct GetMgmntCtrlIdStrRequest { - uint8_t groupID; //!< Group extension identification. - uint8_t offset; //!< Offset to read. - uint8_t bytes; //!< Number of bytes to read. + uint8_t offset; //!< Offset to read. + uint8_t bytes; //!< Number of bytes to read. } __attribute__((packed)); /** @struct GetMgmntCtrlIdStrResponse @@ -312,9 +276,8 @@ struct GetMgmntCtrlIdStrRequest */ struct GetMgmntCtrlIdStrResponse { - uint8_t groupID; //!< Group extension identification. - uint8_t strLen; //!< ID string length. - char data[]; //!< ID string + uint8_t strLen; //!< ID string length. + char data[]; //!< ID string } __attribute__((packed)); /** @struct SetMgmntCtrlIdStrRequest @@ -323,10 +286,9 @@ struct GetMgmntCtrlIdStrResponse */ struct SetMgmntCtrlIdStrRequest { - uint8_t groupID; //!< Group extension identification. - uint8_t offset; //!< Offset to write. - uint8_t bytes; //!< Number of bytes to read. - char data[]; //!< ID string + uint8_t offset; //!< Offset to write. + uint8_t bytes; //!< Number of bytes to read. + char data[]; //!< ID string } __attribute__((packed)); /** @struct GetMgmntCtrlIdStrResponse @@ -335,8 +297,7 @@ struct SetMgmntCtrlIdStrRequest */ struct SetMgmntCtrlIdStrResponse { - uint8_t groupID; //!< Group extension identification. - uint8_t offset; //!< Last Offset Written. + uint8_t offset; //!< Last Offset Written. } __attribute__((packed)); /** @enum DCMICapParameters @@ -357,8 +318,7 @@ enum class DCMICapParameters */ struct GetDCMICapRequest { - uint8_t groupID; //!< Group extension identification. - uint8_t param; //!< Capability parameter selector. + uint8_t param; //!< Capability parameter selector. } __attribute__((packed)); /** @struct GetDCMICapRequest @@ -367,7 +327,6 @@ struct GetDCMICapRequest */ struct GetDCMICapResponse { - uint8_t groupID; //!< Group extension identification. uint8_t major; //!< DCMI Specification Conformance - major ver uint8_t minor; //!< DCMI Specification Conformance - minor ver uint8_t paramRevision; //!< Parameter Revision = 02h @@ -406,7 +365,6 @@ using DCMICaps = std::map<DCMICapParameters, DCMICapEntry>; */ struct GetTempReadingsRequest { - uint8_t groupID; //!< Group extension identification. uint8_t sensorType; //!< Type of the sensor uint8_t entityId; //!< Entity ID uint8_t entityInstance; //!< Entity Instance (0 means all instances) @@ -419,7 +377,6 @@ struct GetTempReadingsRequest */ struct GetTempReadingsResponseHdr { - uint8_t groupID; //!< Group extension identification. uint8_t numInstances; //!< No. of instances for requested id uint8_t numDataSets; //!< No. of sets of temperature data } __attribute__((packed)); @@ -526,7 +483,6 @@ int64_t getPowerReading(sdbusplus::bus::bus& bus); */ struct GetPowerReadingRequest { - uint8_t groupID; //!< Group extension identification. uint8_t mode; //!< Mode uint8_t modeAttribute; //!< Mode Attributes } __attribute__((packed)); @@ -538,7 +494,6 @@ struct GetPowerReadingRequest */ struct GetPowerReadingResponse { - uint8_t groupID; //!< Group extension identification. uint16_t currentPower; //!< Current power in watts uint16_t minimumPower; //!< Minimum power over sampling duration //!< in watts @@ -558,7 +513,6 @@ struct GetPowerReadingResponse */ struct GetSensorInfoRequest { - uint8_t groupID; //!< Group extension identification. uint8_t sensorType; //!< Type of the sensor uint8_t entityId; //!< Entity ID uint8_t entityInstance; //!< Entity Instance (0 means all instances) @@ -571,7 +525,6 @@ struct GetSensorInfoRequest */ struct GetSensorInfoResponseHdr { - uint8_t groupID; //!< Group extension identification. uint8_t numInstances; //!< No. of instances for requested id uint8_t numRecords; //!< No. of record ids in the response } __attribute__((packed)); @@ -594,23 +547,12 @@ enum class DCMIConfigParameters : uint8_t */ struct SetConfParamsRequest { - uint8_t groupID; //!< Group extension identification. uint8_t paramSelect; //!< Parameter selector. uint8_t setSelect; //!< Set Selector (use 00h for parameters that only //!< have one set). uint8_t data[]; //!< Configuration parameter data. } __attribute__((packed)); -/** @struct SetConfParamsResponse - * - * DCMI Set DCMI Configuration Parameters Command response. - * Refer DCMI specification Version 1.1 Section 6.1.2 - */ -struct SetConfParamsResponse -{ - uint8_t groupID; //!< Group extension identification. -} __attribute__((packed)); - /** @struct GetConfParamsRequest * * DCMI Get DCMI Configuration Parameters Command. @@ -618,7 +560,6 @@ struct SetConfParamsResponse */ struct GetConfParamsRequest { - uint8_t groupID; //!< Group extension identification. uint8_t paramSelect; //!< Parameter selector. uint8_t setSelect; //!< Set Selector. Selects a given set of parameters //!< under a given Parameter selector value. 00h if @@ -632,7 +573,6 @@ struct GetConfParamsRequest */ struct GetConfParamsResponse { - uint8_t groupID; //!< Group extension identification. uint8_t major; //!< DCMI Spec Conformance - major ver = 01h. uint8_t minor; //!< DCMI Spec Conformance - minor ver = 05h. uint8_t paramRevision; //!< Parameter Revision = 01h. diff --git a/docs/configuration.md b/docs/configuration.md index 61caaf1..4e833d9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,4 +1,4 @@ -#Device ID Configuration# +# Device ID Configuration There is a default dev_id.json file provided by meta-phosphor/common/recipes-phosphor/ipmi/phosphor-ipmi-host.bb diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..8ccb3ec --- /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/testing.md b/docs/testing.md new file mode 100644 index 0000000..f5ed04b --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,471 @@ +# 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/entity_map_json.cpp b/entity_map_json.cpp new file mode 100644 index 0000000..7b4e0bf --- /dev/null +++ b/entity_map_json.cpp @@ -0,0 +1,119 @@ +#include "entity_map_json.hpp" + +#include <exception> +#include <fstream> +#include <ipmid/types.hpp> +#include <memory> +#include <nlohmann/json.hpp> +#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/entity_map_json.hpp b/entity_map_json.hpp new file mode 100644 index 0000000..3ab4ff2 --- /dev/null +++ b/entity_map_json.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include <ipmid/types.hpp> +#include <memory> +#include <nlohmann/json.hpp> + +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/globalhandler.cpp b/globalhandler.cpp index 27409c2..c192cbb 100644 --- a/globalhandler.cpp +++ b/globalhandler.cpp @@ -32,9 +32,11 @@ void resetBMC() convertForMessage(BMC::Transition::Reboot)); } -ipmi_ret_t ipmi_global_reset(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) +/** @brief implements cold and warm reset commands + * @param - None + * @returns IPMI completion code. + */ +ipmi::RspType<> ipmiGlobalReset() { try { @@ -43,24 +45,19 @@ ipmi_ret_t ipmi_global_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd, catch (std::exception& e) { log<level::ERR>(e.what()); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } // Status code. - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; - return rc; + return ipmi::responseSuccess(); } void register_netfn_global_functions() { - // Cold Reset - ipmi_register_callback(NETFUN_APP, IPMI_CMD_COLD_RESET, NULL, - ipmi_global_reset, PRIVILEGE_ADMIN); - - // <Warm Reset> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_WARM_RESET, NULL, - ipmi_global_reset, PRIVILEGE_ADMIN); + // Cold Reset + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdColdReset, ipmi::Privilege::Admin, + ipmiGlobalReset); return; } diff --git a/globalhandler.hpp b/globalhandler.hpp index 23d3b3e..078b170 100644 --- a/globalhandler.hpp +++ b/globalhandler.hpp @@ -6,5 +6,4 @@ enum ipmi_global_control_cmds : uint8_t { IPMI_CMD_COLD_RESET = 0x02, - IPMI_CMD_WARM_RESET = 0x03, }; diff --git a/host-cmd-manager.cpp b/host-cmd-manager.cpp index 9efb787..f3aba7f 100644 --- a/host-cmd-manager.cpp +++ b/host-cmd-manager.cpp @@ -33,7 +33,6 @@ using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; namespace sdbusRule = sdbusplus::bus::match::rules; -namespace variant_ns = sdbusplus::message::variant_ns; Manager::Manager(sdbusplus::bus::bus& bus) : bus(bus), timer(std::bind(&Manager::hostTimeout, this)), @@ -183,7 +182,7 @@ void Manager::clearQueueOnPowerOn(sdbusplus::message::message& msg) } auto& requestedState = - variant_ns::get<std::string>(properties.at(HOST_TRANS_PROP)); + std::get<std::string>(properties.at(HOST_TRANS_PROP)); if (server::Host::convertTransitionFromString(requestedState) == server::Host::Transition::On) diff --git a/host-interface.cpp b/host-interface.cpp index 69ab376..1f9bfcb 100644 --- a/host-interface.cpp +++ b/host-interface.cpp @@ -54,7 +54,7 @@ void Host::execute(Base::Host::Command command) std::placeholders::_1, std::placeholders::_2)); - return ipmid_send_cmd_to_host(std::move(cmd)); + ipmid_send_cmd_to_host(std::move(cmd)); } // Called into by Command Manager diff --git a/host-ipmid-whitelist.conf b/host-ipmid-whitelist.conf index 961214d..5397115 100644 --- a/host-ipmid-whitelist.conf +++ b/host-ipmid-whitelist.conf @@ -26,7 +26,9 @@ 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> diff --git a/include/Makefile.am b/include/Makefile.am index b7c303a..08824c4 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,6 +1,8 @@ nobase_include_HEADERS = \ ipmid/api.hpp \ ipmid/api-types.hpp \ + ipmid/sessiondef.hpp \ + ipmid/sessionhelper.hpp \ ipmid/filter.hpp \ ipmid/handler.hpp \ ipmid/message.hpp \ diff --git a/include/ipmid/api.hpp b/include/ipmid/api.hpp index f4cbf13..6e33470 100644 --- a/include/ipmid/api.hpp +++ b/include/ipmid/api.hpp @@ -47,7 +47,7 @@ std::shared_ptr<sdbusplus::asio::connection> getSdBus(); template <typename WorkFn> static inline void post_work(WorkFn work) { - getIoContext()->post(std::forward<WorkFn>(work)); + boost::asio::post(*getIoContext(), std::forward<WorkFn>(work)); } enum class SignalResponse : int diff --git a/include/ipmid/handler.hpp b/include/ipmid/handler.hpp index 1421c3d..b6c6c0f 100644 --- a/include/ipmid/handler.hpp +++ b/include/ipmid/handler.hpp @@ -153,6 +153,7 @@ class IpmiHandler final : public HandlerBase 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) { @@ -178,45 +179,47 @@ class IpmiHandler final : public HandlerBase * parameter selector. All the remaining data can be extracted using * the Payload class and the unpack API available to the Payload class. */ - std::optional<InputArgsType> inputArgs; - if constexpr (std::tuple_size<InputArgsType>::value > 0) + ResultType result; + try { - 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) + std::optional<InputArgsType> inputArgs; + if constexpr (std::tuple_size<InputArgsType>::value > 0) { - inputArgs.emplace(std::tuple_cat(std::forward_as_tuple(request), - std::move(unpackArgs))); + 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 special parameters were requested (but others were) - inputArgs.emplace(std::move(unpackArgs)); + // no parameters were requested + inputArgs = std::move(unpackArgs); } - } - else - { - // no parameters were requested - inputArgs = std::move(unpackArgs); - } - ResultType result; - try - { + // execute the registered callback function and get the // ipmi::RspType<> result = std::apply(handler_, *inputArgs); @@ -264,6 +267,7 @@ class IpmiHandler final : public HandlerBase }; #ifdef ALLOW_DEPRECATED_API +static constexpr size_t maxLegacyBufferSize = 64 * 1024; /** * @brief Legacy IPMI handler class * @@ -307,17 +311,17 @@ class IpmiHandler<ipmid_callback_t> final : public HandlerBase executeCallback(message::Request::ptr request) override { message::Response::ptr response = request->makeResponse(); - size_t len = request->payload.size(); // allocate a big response buffer here - response->payload.resize( - getChannelMaxTransferSize(request->ctx->channel)); + 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(), response->payload.data(), - &len, handlerCtx); + ccRet = + handler_(request->ctx->netFn, request->ctx->cmd, + request->payload.data() + request->payload.rawIndex, + response->payload.data(), &len, handlerCtx); } catch (const std::exception& e) { @@ -396,16 +400,17 @@ class IpmiHandler<oem::Handler> final : public HandlerBase executeCallback(message::Request::ptr request) override { message::Response::ptr response = request->makeResponse(); - size_t len = request->payload.size(); // allocate a big response buffer here - response->payload.resize( - getChannelMaxTransferSize(request->ctx->channel)); + 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(), - response->payload.data(), &len); + ccRet = + handler_(request->ctx->cmd, + request->payload.data() + request->payload.rawIndex, + response->payload.data(), &len); } catch (const std::exception& e) { diff --git a/include/ipmid/message.hpp b/include/ipmid/message.hpp index 030618f..c828e3c 100644 --- a/include/ipmid/message.hpp +++ b/include/ipmid/message.hpp @@ -18,10 +18,12 @@ #include <algorithm> #include <boost/asio/spawn.hpp> #include <cstdint> +#include <exception> #include <ipmid/api-types.hpp> #include <ipmid/message/types.hpp> #include <memory> #include <phosphor-logging/log.hpp> +#include <sdbusplus/asio/connection.hpp> #include <tuple> #include <utility> #include <vector> @@ -33,23 +35,33 @@ struct Context { using ptr = std::shared_ptr<Context>; - Context() = default; - - Context(NetFn netFn, Cmd cmd, int channel, int userId, Privilege priv, - boost::asio::yield_context* yield = nullptr) : - netFn(netFn), - cmd(cmd), channel(channel), userId(userId), priv(priv), yield(yield) + 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, + Cmd cmd, int channel, int userId, uint32_t sessionId, + Privilege priv, int rqSA, boost::asio::yield_context& yield) : + bus(bus), + netFn(netFn), cmd(cmd), channel(channel), userId(userId), + sessionId(sessionId), priv(priv), rqSA(rqSA), yield(yield) { } + std::shared_ptr<sdbusplus::asio::connection> bus; // normal IPMI context (what call is this, from whence it came...) - NetFn netFn = 0; - Cmd cmd = 0; - int channel = 0; - int userId = 0; - Privilege priv = Privilege::None; - // if non-null, use this to do blocking asynchronous asio calls - boost::asio::yield_context* yield = nullptr; + NetFn netFn; + Cmd cmd; + int channel; + int userId; + uint32_t sessionId; + Privilege priv; + // srcAddr is only set on IPMB requests because + // Platform Event Message needs it to determine the incoming format + int rqSA; + boost::asio::yield_context yield; }; namespace message @@ -99,15 +111,15 @@ struct Payload Payload(Payload&&) = default; Payload& operator=(Payload&&) = default; - explicit Payload(std::vector<uint8_t>&& data) : - raw(std::move(data)), unpackCheck(false) + explicit Payload(std::vector<uint8_t>&& data) : raw(std::move(data)) { } ~Payload() { using namespace phosphor::logging; - if (trailingOk && !unpackCheck && !fullyUnpacked()) + if (raw.size() != 0 && std::uncaught_exceptions() == 0 && !trailingOk && + !unpackCheck && !unpackError) { log<level::ERR>("Failed to check request for full unpack"); } @@ -252,6 +264,27 @@ struct Payload 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 *****************************************************************/ @@ -445,8 +478,8 @@ struct Payload size_t bitCount = 0; std::vector<uint8_t> raw; size_t rawIndex = 0; - bool trailingOk = false; - bool unpackCheck = true; + bool trailingOk = true; + bool unpackCheck = false; bool unpackError = false; }; @@ -513,6 +546,21 @@ struct Response 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; @@ -560,19 +608,20 @@ struct Request int unpack(Args&&... args) { int unpackRet = payload.unpack(std::forward<Args>(args)...); - if (unpackRet == ipmi::ccSuccess) + if (unpackRet != ipmi::ccSuccess) + { + // not all bits were consumed by requested parameters + return ipmi::ccReqDataLenInvalid; + } + if (!payload.trailingOk) { - if (!payload.trailingOk) + if (!payload.fullyUnpacked()) { - if (!payload.fullyUnpacked()) - { - // not all bits were consumed by requested parameters - return ipmi::ccReqDataLenInvalid; - } - payload.unpackCheck = false; + // not all bits were consumed by requested parameters + return ipmi::ccReqDataLenInvalid; } } - return unpackRet; + return ipmi::ccSuccess; } /** diff --git a/include/ipmid/message/pack.hpp b/include/ipmid/message/pack.hpp index 18863c4..598e650 100644 --- a/include/ipmid/message/pack.hpp +++ b/include/ipmid/message/pack.hpp @@ -20,6 +20,7 @@ #include <memory> #include <optional> #include <phosphor-logging/log.hpp> +#include <string_view> #include <tuple> #include <utility> #include <variant> @@ -239,6 +240,26 @@ 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 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; @@ -259,6 +280,22 @@ struct PackSingle<std::variant<T...>> } }; +/** @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 diff --git a/include/ipmid/message/unpack.hpp b/include/ipmid/message/unpack.hpp index 94f80f1..d9ccba4 100644 --- a/include/ipmid/message/unpack.hpp +++ b/include/ipmid/message/unpack.hpp @@ -99,26 +99,29 @@ struct UnpackSingle } return 0; } - else + else if constexpr (utility::is_tuple<T>::value) { - 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) { - 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 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"); } } }; @@ -289,18 +292,21 @@ struct UnpackSingle<std::vector<T>> { static int op(Payload& p, std::vector<T>& t) { - int ret = 0; while (p.rawIndex < p.raw.size()) { t.emplace_back(); - ret = UnpackSingle<T>::op(p, t.back()); - if (ret) + if (UnpackSingle<T>::op(p, t.back())) { t.pop_back(); break; } } - return ret; + // 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; } }; @@ -324,13 +330,9 @@ 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; - t = p; - // reset the unpacking flags so it can be properly checked - t.trailingOk = false; - t.unpackCheck = true; - t.unpackError = false; return 0; } }; diff --git a/include/ipmid/sessiondef.hpp b/include/ipmid/sessiondef.hpp new file mode 100644 index 0000000..ac63f8f --- /dev/null +++ b/include/ipmid/sessiondef.hpp @@ -0,0 +1,52 @@ +/* + * 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 + +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..a96f037 --- /dev/null +++ b/include/ipmid/sessionhelper.hpp @@ -0,0 +1,88 @@ +#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 index 57c5873..3e64cb4 100644 --- a/include/ipmid/types.hpp +++ b/include/ipmid/types.hpp @@ -5,6 +5,7 @@ #include <map> #include <sdbusplus/server.hpp> #include <string> +#include <variant> namespace ipmi { @@ -15,9 +16,8 @@ using DbusInterface = std::string; using DbusObjectInfo = std::pair<DbusObjectPath, DbusService>; using DbusProperty = std::string; -using Value = sdbusplus::message::variant<bool, uint8_t, int16_t, uint16_t, - int32_t, uint32_t, int64_t, uint64_t, - double, std::string>; +using Value = std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + int64_t, uint64_t, double, std::string>; using PropertyMap = std::map<DbusProperty, Value>; @@ -100,7 +100,15 @@ struct GetReadingResponse constexpr auto inventoryRoot = "/xyz/openbmc_project/inventory"; -using GetSensorResponse = std::array<uint8_t, sizeof(GetReadingResponse)>; +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>; @@ -215,12 +223,7 @@ using EntityInfoMap = std::map<Id, EntityInfo>; namespace network { -using ChannelEthMap = std::map<int, std::string>; - constexpr auto MAC_ADDRESS_FORMAT = "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"; -constexpr auto IP_ADDRESS_FORMAT = "%u.%u.%u.%u"; -constexpr auto PREFIX_FORMAT = "%hhd"; -constexpr auto ADDR_TYPE_FORMAT = "%hhx"; constexpr auto IPV4_ADDRESS_SIZE_BYTE = 4; constexpr auto IPV6_ADDRESS_SIZE_BYTE = 16; @@ -228,20 +231,6 @@ 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"; -constexpr auto MAC_ADDRESS_SIZE_BYTE = 6; -constexpr auto VLAN_SIZE_BYTE = 2; -constexpr auto IPSRC_SIZE_BYTE = 1; -constexpr auto BITS_32 = 32; -constexpr auto MASK_32_BIT = 0xFFFFFFFF; -constexpr auto VLAN_ID_MASK = 0x00000FFF; -constexpr auto VLAN_ENABLE_MASK = 0x8000; - -enum class IPOrigin : uint8_t -{ - UNSPECIFIED = 0, - STATIC = 1, - DHCP = 2, -}; - } // namespace network + } // namespace ipmi diff --git a/include/ipmid/utility.hpp b/include/ipmid/utility.hpp index 79c76f7..0c39e92 100644 --- a/include/ipmid/utility.hpp +++ b/include/ipmid/utility.hpp @@ -214,6 +214,13 @@ 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 index 9ef1488..3515eb6 100644 --- a/include/ipmid/utils.hpp +++ b/include/ipmid/utils.hpp @@ -1,6 +1,7 @@ #pragma once #include <chrono> +#include <ipmid/api-types.hpp> #include <ipmid/types.hpp> #include <optional> #include <sdbusplus/server.hpp> @@ -112,20 +113,6 @@ DbusObjectInfo getDbusObject(sdbusplus::bus::bus& bus, const std::string& subtreePath = ROOT, const std::string& match = {}); -/** @brief Get the ipObject of first dbus IP object of Non-LinkLocalIPAddress - * type from the given subtree, if not available gets IP object of - * LinkLocalIPAddress type. - * @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 getIPObject(sdbusplus::bus::bus& bus, - const std::string& interface, - const std::string& subtreePath, - const std::string& match); - /** @brief Gets the value associated with the given object * and the interface. * @param[in] bus - DBUS Bus Object. @@ -250,59 +237,13 @@ void callDbusMethod(sdbusplus::bus::bus& bus, const std::string& service, } // namespace method_no_args -namespace network -{ - -constexpr auto ROOT = "/xyz/openbmc_project/network"; -constexpr auto SERVICE = "xyz.openbmc_project.Network"; -constexpr auto IP_TYPE = "ipv4"; -constexpr auto IPV4_PREFIX = "169.254"; -constexpr auto IPV6_PREFIX = "fe80"; -constexpr auto IP_INTERFACE = "xyz.openbmc_project.Network.IP"; -constexpr auto MAC_INTERFACE = "xyz.openbmc_project.Network.MACAddress"; -constexpr auto SYSTEMCONFIG_INTERFACE = - "xyz.openbmc_project.Network.SystemConfiguration"; -constexpr auto ETHERNET_INTERFACE = - "xyz.openbmc_project.Network.EthernetInterface"; -constexpr auto IP_CREATE_INTERFACE = "xyz.openbmc_project.Network.IP.Create"; -constexpr auto VLAN_CREATE_INTERFACE = - "xyz.openbmc_project.Network.VLAN.Create"; -constexpr auto VLAN_INTERFACE = "xyz.openbmc_project.Network.VLAN"; - -/* @brief converts the given subnet into prefix notation. - * @param[in] addressFamily - IP address family(AF_INET/AF_INET6). - * @param[in] mask - Subnet Mask. - * @returns prefix. +/** @brief Perform the low-level i2c bus write-read. + * @param[in] i2cBus - i2c bus device node name, such as /dev/i2c-2. + * @param[in] slaveAddr - i2c device slave address. + * @param[in] writeData - The data written to i2c device. + * @param[out] readBuf - Data read from the i2c device. */ -uint8_t toPrefix(int addressFamily, const std::string& subnetMask); - -/** @brief Sets the ip on the system. - * @param[in] bus - DBUS Bus Object. - * @param[in] service - Dbus service name. - * @param[in] objPath - Dbus object path. - * @param[in] protocolType - Protocol type - * @param[in] ipaddress - IPaddress. - * @param[in] prefix - Prefix length. - */ -void createIP(sdbusplus::bus::bus& bus, const std::string& service, - const std::string& objPath, const std::string& protocolType, - const std::string& ipaddress, uint8_t prefix); - -/** @brief Creates the VLAN on the given interface. - * @param[in] bus - DBUS Bus Object. - * @param[in] service - Dbus service name. - * @param[in] objPath - Dbus object path. - * @param[in] interface - EthernetInterface. - * @param[in] vlanID - Vlan ID. - */ -void createVLAN(sdbusplus::bus::bus& bus, const std::string& service, - const std::string& objPath, const std::string& interface, - uint32_t vlanID); - -/** @brief Gets the vlan id from the given object path. - * @param[in] path - Dbus object path. - */ -uint32_t getVLAN(const std::string& path); - -} // namespace network +ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t slaveAddr, + std::vector<uint8_t> writeData, + std::vector<uint8_t>& readBuf); } // namespace ipmi diff --git a/ipmi_fru_info_area.cpp b/ipmi_fru_info_area.cpp index 47a8a8e..b220f02 100644 --- a/ipmi_fru_info_area.cpp +++ b/ipmi_fru_info_area.cpp @@ -2,9 +2,11 @@ #include <algorithm> #include <ctime> +#include <iomanip> #include <map> #include <numeric> #include <phosphor-logging/elog.hpp> +#include <sstream> namespace ipmi { @@ -13,12 +15,12 @@ namespace fru using namespace phosphor::logging; // Property variables -static constexpr auto partNumber = "PartNumber"; -static constexpr auto serialNumber = "SerialNumber"; +static constexpr auto partNumber = "Part Number"; +static constexpr auto serialNumber = "Serial Number"; static constexpr auto manufacturer = "Manufacturer"; -static constexpr auto buildDate = "BuildDate"; +static constexpr auto buildDate = "Mfg Date"; static constexpr auto model = "Model"; -static constexpr auto prettyName = "PrettyName"; +static constexpr auto prettyName = "Name"; static constexpr auto version = "Version"; static constexpr auto type = "Type"; @@ -193,6 +195,29 @@ void appendData(const Property& key, const PropertyMap& propMap, } } +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 std::mktime(&time); +} + /** * @brief Appends Build Date * @@ -205,9 +230,7 @@ void appendMfgDate(const PropertyMap& propMap, FruAreaData& data) auto iter = propMap.find(buildDate); if ((iter != propMap.end()) && (iter->second.size() > 0)) { - tm time = {}; - strptime(iter->second.c_str(), "%F - %H:%M:%S", &time); - time_t raw = mktime(&time); + std::time_t raw = timeStringToRaw(iter->second); // From FRU Spec: // "Mfg. Date / Time diff --git a/ipmid-new.cpp b/ipmid-new.cpp index 2a2d4a6..abd0a10 100644 --- a/ipmid-new.cpp +++ b/ipmid-new.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <any> +#include <boost/algorithm/string.hpp> #include <dcmihandler.hpp> #include <exception> #include <filesystem> @@ -232,17 +233,18 @@ message::Response::ptr executeIpmiCommandCommon( { // filter the command first; a non-null message::Response::ptr // means that the message has been rejected for some reason - message::Response::ptr response = filterIpmiCommand(request); - if (response) - { - return response; - } + 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) + { + return filterResponse; + } HandlerTuple& chosen = cmdIter->second; if (request->ctx->priv < std::get<Privilege>(chosen)) { @@ -256,6 +258,11 @@ message::Response::ptr executeIpmiCommandCommon( cmdIter = handlers.find(wildcard); if (cmdIter != handlers.end()) { + // only return the filter response if the command is found + if (filterResponse) + { + return filterResponse; + } HandlerTuple& chosen = cmdIter->second; if (request->ctx->priv < std::get<Privilege>(chosen)) { @@ -270,39 +277,34 @@ message::Response::ptr executeIpmiCommandCommon( message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request) { // look up the group for this request - Group group; - if (0 != request->payload.unpack(group)) + uint8_t bytes; + if (0 != request->payload.unpack(bytes)) { return errorResponse(request, ccReqDataLenInvalid); } - // The handler will need to unpack group as well; we just need it for lookup - request->payload.reset(); + auto group = static_cast<Group>(bytes); message::Response::ptr response = executeIpmiCommandCommon(groupHandlerMap, group, request); - // if the handler should add the group; executeIpmiCommandCommon does not - if (response->cc != ccSuccess && response->payload.size() == 0) - { - response->pack(group); - } + 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 - Iana iana; - if (0 != request->payload.unpack(iana)) + uint24_t bytes; + if (0 != request->payload.unpack(bytes)) { return errorResponse(request, ccReqDataLenInvalid); } - request->payload.reset(); + auto iana = static_cast<Iana>(bytes); message::Response::ptr response = executeIpmiCommandCommon(oemHandlerMap, iana, request); - // if the handler should add the iana; executeIpmiCommandCommon does not - if (response->cc != ccSuccess && response->payload.size() == 0) - { - response->pack(iana); - } + ipmi::message::Payload prefix; + prefix.pack(bytes); + response->prepend(prefix); return response; } @@ -320,21 +322,240 @@ message::Response::ptr executeIpmiCommand(message::Request::ptr 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) + { + log<level::ERR>("Error getting dbus owner", + entry("INTERFACE=%s", name.c_str())); + return; + } + // start after ipmiDbusChannelPrefix (after the '.') + std::string chName = + name.substr(std::strlen(ipmiDbusChannelMatch) + 1); + try + { + uint8_t channel = getChannelByName(chName); + uniqueNameToChannelNumber[nameOwner] = channel; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + }, + "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", + name); +} + +void doListNames(boost::asio::io_service& io, sdbusplus::asio::connection& conn) +{ + conn.async_method_call( + [&io, &conn](const boost::system::error_code ec, + std::vector<std::string> busNames) { + if (ec) + { + log<level::ERR>("Error getting dbus names"); + 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::message& 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; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + } +}; + +} // anonymous namespace + +static constexpr const char intraBmcName[] = "INTRABMC"; +uint8_t channelFromMessage(sdbusplus::message::message& 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, NetFn netFn, uint8_t lun, +auto executionEntry(boost::asio::yield_context yield, + sdbusplus::message::message& m, NetFn netFn, uint8_t lun, Cmd cmd, std::vector<uint8_t>& data, std::map<std::string, ipmi::Value>& options) { - auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0, - ipmi::Privilege::Admin, &yield); + const auto dbusResponse = + [netFn, lun, cmd](Cc cc, const std::vector<uint8_t>& 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; + 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 + log<level::ERR>("ERROR determining source IPMI channel", + entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", 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) + { + log<level::ERR>("ERROR determining IPMI session credentials", + entry("CHANNEL=%u", channel), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", 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); + } + } + } + } + // check to see if the requested priv/username is valid + log<level::DEBUG>("Set up ipmi context", entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd), + entry("CHANNEL=%u", channel), entry("USERID=%u", userId), + entry("SESSIONID=0x%X", sessionId), + entry("PRIVILEGE=%u", static_cast<uint8_t>(privilege)), + entry("RQSA=%x", rqSA)); + + auto ctx = + std::make_shared<ipmi::Context>(getSdBus(), netFn, cmd, channel, userId, + sessionId, privilege, rqSA, yield); auto request = std::make_shared<ipmi::message::Request>( ctx, std::forward<std::vector<uint8_t>>(data)); message::Response::ptr response = executeIpmiCommand(request); - // Responses in IPMI require a bit set. So there ya go... - netFn |= 0x01; - return std::make_tuple(netFn, lun, cmd, response->cc, - response->payload.raw); + return dbusResponse(response->cc, response->payload.raw); } /** @struct IpmiProvider @@ -531,29 +752,43 @@ Router* mutableRouter() /* legacy alternative to executionEntry */ void handleLegacyIpmiCommand(sdbusplus::message::message& m) { - unsigned char seq, netFn, lun, cmd; - std::vector<uint8_t> data; - - m.read(seq, netFn, lun, cmd, data); - - auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0, - ipmi::Privilege::Admin); - auto request = std::make_shared<ipmi::message::Request>( - ctx, std::forward<std::vector<uint8_t>>(data)); - ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request); - - // Responses in IPMI require a bit set. So there ya go... - netFn |= 0x01; - - const char *dest, *path; - constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi"; - - dest = m.get_sender(); - path = m.get_path(); - getSdBus()->async_method_call([](boost::system::error_code ec) {}, dest, - path, DBUS_INTF, "sendMessage", seq, netFn, - lun, cmd, response->cc, - response->payload.raw); + // make a copy so the next two moves don't wreak havoc on the stack + sdbusplus::message::message b{m}; + boost::asio::spawn(*getIoContext(), [b = std::move(b)]( + boost::asio::yield_context yield) { + sdbusplus::message::message m{std::move(b)}; + unsigned char seq, netFn, lun, cmd; + std::vector<uint8_t> 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, cmd, 0, 0, 0, ipmi::Privilege::Admin, 0, yield); + auto request = std::make_shared<ipmi::message::Request>( + ctx, std::forward<std::vector<uint8_t>>(data)); + ipmi::message::Response::ptr response = + ipmi::executeIpmiCommand(request); + + // Responses in IPMI require a bit set. So there ya go... + netFn |= 0x01; + + const char *dest, *path; + constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi"; + + dest = m.get_sender(); + path = m.get_path(); + boost::system::error_code ec; + bus->yield_method_call(yield, ec, dest, path, DBUS_INTF, "sendMessage", + seq, netFn, lun, cmd, response->cc, + response->payload.raw); + if (ec) + { + log<level::ERR>("Failed to send response to requestor", + entry("ERROR=%s", ec.message().c_str()), + entry("SENDER=%s", dest), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + } + }); } #endif /* ALLOW_DEPRECATED_API */ @@ -591,7 +826,6 @@ int main(int argc, char* argv[]) } auto sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus); setSdBus(sdbusp); - sdbusp->request_name("xyz.openbmc_project.Ipmi.Host"); // 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 @@ -608,13 +842,6 @@ int main(int argc, char* argv[]) std::forward_list<ipmi::IpmiProvider> providers = ipmi::loadProviders(HOST_IPMI_LIB_PATH); - // 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(); - #ifdef ALLOW_DEPRECATED_API // listen on deprecated signal interface for kcs/bt commands constexpr const char* FILTER = "type='signal',interface='org.openbmc." @@ -623,17 +850,36 @@ int main(int argc, char* argv[]) handleLegacyIpmiCommand); #endif /* ALLOW_DEPRECATED_API */ + // set up bus name watching to match channels with bus names + sdbusplus::bus::match::match nameOwnerChanged( + *sdbusp, + sdbusplus::bus::match::rules::nameOwnerChanged() + + sdbusplus::bus::match::rules::arg0namespace( + ipmi::ipmiDbusChannelMatch), + ipmi::nameChangeHandler); + ipmi::doListNames(*io, *sdbusp); + + int exitCode = 0; // set up boost::asio signal handling std::function<SignalResponse(int)> stopAsioRunLoop = - [&io](int signalNumber) { + [&io, &exitCode](int signalNumber) { log<level::INFO>("Received signal; quitting", entry("SIGNAL=%d", 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 @@ -644,5 +890,5 @@ int main(int argc, char* argv[]) // unload the provider libraries providers.clear(); - return 0; + std::exit(exitCode); } diff --git a/libipmid/utils.cpp b/libipmid/utils.cpp index 3ff79ed..ed493d6 100644 --- a/libipmid/utils.cpp +++ b/libipmid/utils.cpp @@ -1,6 +1,12 @@ #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 <algorithm> #include <chrono> @@ -15,7 +21,6 @@ namespace ipmi using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; -namespace variant_ns = sdbusplus::message::variant_ns; namespace network { @@ -93,43 +98,6 @@ DbusObjectInfo getDbusObject(sdbusplus::bus::bus& bus, return make_pair(found->first, std::move(found->second.begin()->first)); } -DbusObjectInfo getIPObject(sdbusplus::bus::bus& bus, - const std::string& interface, - const std::string& serviceRoot, - const std::string& match) -{ - auto objectTree = getAllDbusObjects(bus, serviceRoot, interface, match); - - if (objectTree.empty()) - { - log<level::ERR>("No Object has implemented the IP interface", - entry("INTERFACE=%s", interface.c_str())); - elog<InternalFailure>(); - } - - DbusObjectInfo objectInfo; - - for (auto& object : objectTree) - { - auto variant = ipmi::getDbusProperty( - bus, object.second.begin()->first, object.first, - ipmi::network::IP_INTERFACE, "Address"); - - objectInfo = std::make_pair(object.first, object.second.begin()->first); - - // if LinkLocalIP found look for Non-LinkLocalIP - if (ipmi::network::isLinkLocalIP(variant_ns::get<std::string>(variant))) - { - continue; - } - else - { - break; - } - } - return objectInfo; -} - Value getDbusProperty(sdbusplus::bus::bus& bus, const std::string& service, const std::string& objPath, const std::string& interface, const std::string& property, @@ -432,114 +400,62 @@ void callDbusMethod(sdbusplus::bus::bus& bus, const std::string& service, } } // namespace method_no_args - -namespace network -{ - -bool isLinkLocalIP(const std::string& address) +ipmi::Cc i2cWriteRead(std::string i2cBus, const uint8_t slaveAddr, + std::vector<uint8_t> writeData, + std::vector<uint8_t>& readBuf) { - return address.find(IPV4_PREFIX) == 0 || address.find(IPV6_PREFIX) == 0; -} - -void createIP(sdbusplus::bus::bus& bus, const std::string& service, - const std::string& objPath, const std::string& protocolType, - const std::string& ipaddress, uint8_t prefix) -{ - std::string gateway = ""; - - auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(), - IP_CREATE_INTERFACE, "IP"); - - busMethod.append(protocolType, ipaddress, prefix, gateway); - - auto reply = bus.call(busMethod); - - if (reply.is_method_error()) + // Open the i2c device, for low-level combined data write/read + int i2cDev = ::open(i2cBus.c_str(), O_RDWR | O_CLOEXEC); + if (i2cDev < 0) { - log<level::ERR>("Failed to execute method", entry("METHOD=%s", "IP"), - entry("PATH=%s", objPath.c_str())); - elog<InternalFailure>(); + log<level::ERR>("Failed to open i2c bus", + phosphor::logging::entry("BUS=%s", i2cBus.c_str())); + return ipmi::ccInvalidFieldRequest; } -} - -void createVLAN(sdbusplus::bus::bus& bus, const std::string& service, - const std::string& objPath, const std::string& interfaceName, - uint32_t vlanID) -{ - auto busMethod = bus.new_method_call(service.c_str(), objPath.c_str(), - VLAN_CREATE_INTERFACE, "VLAN"); - busMethod.append(interfaceName, vlanID); - - auto reply = bus.call(busMethod); - - if (reply.is_method_error()) + const size_t writeCount = writeData.size(); + const size_t readCount = readBuf.size(); + int msgCount = 0; + i2c_msg i2cmsg[2] = {0}; + if (writeCount) { - log<level::ERR>("Failed to execute method", entry("METHOD=%s", "VLAN"), - entry("PATH=%s", objPath.c_str())); - elog<InternalFailure>(); + // Data will be writtern to the slave address + i2cmsg[msgCount].addr = slaveAddr; + i2cmsg[msgCount].flags = 0x00; + i2cmsg[msgCount].len = writeCount; + i2cmsg[msgCount].buf = writeData.data(); + msgCount++; } -} - -uint8_t toPrefix(int addressFamily, const std::string& subnetMask) -{ - if (addressFamily == AF_INET6) + if (readCount) { - return 0; + // Data will be read into the buffer from the slave address + i2cmsg[msgCount].addr = slaveAddr; + i2cmsg[msgCount].flags = I2C_M_RD; + i2cmsg[msgCount].len = readCount; + i2cmsg[msgCount].buf = readBuf.data(); + msgCount++; } - uint32_t buff{}; + i2c_rdwr_ioctl_data msgReadWrite = {0}; + msgReadWrite.msgs = i2cmsg; + msgReadWrite.nmsgs = msgCount; - auto rc = inet_pton(addressFamily, subnetMask.c_str(), &buff); - if (rc <= 0) - { - log<level::ERR>("inet_pton failed:", - entry("SUBNETMASK=%s", subnetMask.c_str())); - return 0; - } + // Perform the combined write/read + int ret = ::ioctl(i2cDev, I2C_RDWR, &msgReadWrite); + ::close(i2cDev); - buff = be32toh(buff); - // total no of bits - total no of leading zero == total no of ones - if (((sizeof(buff) * 8) - (__builtin_ctz(buff))) == - __builtin_popcount(buff)) + if (ret < 0) { - return __builtin_popcount(buff); + log<level::ERR>("I2C WR Failed!", + phosphor::logging::entry("RET=%d", ret)); + return ipmi::ccUnspecifiedError; } - else + if (readCount) { - log<level::ERR>("Invalid Mask", - entry("SUBNETMASK=%s", subnetMask.c_str())); - return 0; + readBuf.resize(msgReadWrite.msgs[msgCount - 1].len); } -} - -uint32_t getVLAN(const std::string& path) -{ - // Path would be look like - // /xyz/openbmc_project/network/eth0_443/ipv4 - - uint32_t vlanID = 0; - try - { - auto intfObjectPath = path.substr(0, path.find(IP_TYPE) - 1); - - auto intfName = intfObjectPath.substr(intfObjectPath.rfind("/") + 1); - auto index = intfName.find("_"); - if (index != std::string::npos) - { - auto str = intfName.substr(index + 1); - vlanID = std::stoul(str); - } - } - catch (std::exception& e) - { - log<level::ERR>("Exception occurred during getVLAN", - entry("PATH=%s", path.c_str()), - entry("EXCEPTION=%s", e.what())); - } - return vlanID; + return ipmi::ccSuccess; } -} // namespace network } // namespace ipmi diff --git a/oemrouter.cpp b/oemrouter.cpp deleted file mode 100644 index 00f66f1..0000000 --- a/oemrouter.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include <cstdio> -#include <cstring> -#include <ipmid/oemrouter.hpp> -#include <map> -#include <utility> - -namespace oem -{ - -using Key = std::pair<Number, ipmi_cmd_t>; - -// Private implementation of OemRouter Interface. -class RouterImpl : public Router -{ - public: - RouterImpl() - { - } - - // Implement OemRouter Interface. - void activate() override; - void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override; - - // Actual message routing function. - ipmi_ret_t routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf, - uint8_t* replyBuf, size_t* dataLen); - - private: - std::map<Key, Handler> handlers; -}; - -// Static global instance for simplicity. -static RouterImpl* globalRouterImpl; - -// TODO Refactor ipmid to avoid need for singleton here. -Router* mutableRouter() -{ - if (!globalRouterImpl) - { - globalRouterImpl = new RouterImpl; - } - return globalRouterImpl; -} - -ipmi_ret_t RouterImpl::routeMsg(ipmi_cmd_t cmd, const uint8_t* reqBuf, - uint8_t* replyBuf, size_t* dataLen) -{ - // Not entirely clear we can route reply without complete OEM group. - // TODO: consider adding a way to suppress malformed replies. - if (*dataLen < groupMagicSize) - { - std::fprintf(stderr, "NetFn:[0x2E], OEM:[%zu bytes?], Cmd:[%#04X]\n", - *dataLen, cmd); - (*dataLen) = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - // Find registered handler or reject request. - auto number = toOemNumber(reqBuf); - auto cmdKey = std::make_pair(number, cmd); - - auto iter = handlers.find(cmdKey); - if (iter == handlers.end()) - { - auto wildKey = std::make_pair(number, IPMI_CMD_WILDCARD); - iter = handlers.find(wildKey); - if (iter == handlers.end()) - { - std::fprintf(stderr, - "No Registered handler for NetFn:[0x2E], " - "OEM:[%#08X], Cmd:[%#04X]\n", - number, cmd); - *dataLen = groupMagicSize; - return IPMI_CC_INVALID; - } -#ifdef __IPMI_DEBUG__ - std::fprintf(stderr, - "Wildcard NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n", - number, cmd); -#endif - } - else - { -#ifdef __IPMI_DEBUG__ - std::fprintf(stderr, "Match NetFn:[0x2E], OEM:[%#08X], Cmd:[%#04X]\n", - number, cmd); -#endif - } - - // Copy OEMGroup here, by analogy to IPMI CC code at netfn router; - // OemHandler should deal only with optional following data bytes. - std::memcpy(replyBuf, reqBuf, groupMagicSize); - - size_t oemDataLen = *dataLen - groupMagicSize; - Handler& handler = iter->second; - - auto rc = handler(cmd, reqBuf + groupMagicSize, replyBuf + groupMagicSize, - &oemDataLen); - - // Add OEMGroup bytes to nominal reply. - *dataLen = oemDataLen + groupMagicSize; - return rc; -} - -// Function suitable for use as ipmi_netfn_router() call-back. -// Translates call-back pointer args to more specific types. -ipmi_ret_t ipmi_oem_wildcard_handler(ipmi_netfn_t /* netfn */, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t dataLen, - ipmi_context_t context) -{ - // View requests & responses as byte sequences. - const uint8_t* reqBuf = static_cast<uint8_t*>(request); - uint8_t* replyBuf = static_cast<uint8_t*>(response); - - // View context as router object, defaulting nullptr to global object. - auto router = static_cast<RouterImpl*>(context ? context : mutableRouter()); - - // Send message parameters to dispatcher. - return router->routeMsg(cmd, reqBuf, replyBuf, dataLen); -} - -// Enable message routing to begin. -void RouterImpl::activate() -{ - // Register netfn 0x2e OEM Group, any (wildcard) command. - std::printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_OEM_GROUP, - IPMI_CMD_WILDCARD); - ipmi_register_callback(NETFUN_OEM_GROUP, IPMI_CMD_WILDCARD, this, - ipmi_oem_wildcard_handler, PRIVILEGE_OEM); -} - -void RouterImpl::registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) -{ - auto cmdKey = std::make_pair(oen, cmd); - auto iter = handlers.find(cmdKey); - if (iter == handlers.end()) - { - // Add handler if key not already taken. - handlers.emplace(cmdKey, handler); - } - else - { - std::fprintf(stderr, - "ERROR : Duplicate registration for NetFn:[0x2E], " - "OEM:[%#08X], Cmd:[%#04X]\n", - oen, cmd); - } -} - -} // namespace oem diff --git a/read_fru_data.cpp b/read_fru_data.cpp index 1f99e20..270accf 100644 --- a/read_fru_data.cpp +++ b/read_fru_data.cpp @@ -17,14 +17,13 @@ namespace ipmi namespace fru { -namespace variant_ns = sdbusplus::message::variant_ns; - using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; std::unique_ptr<sdbusplus::bus::match_t> matchPtr __attribute__((init_priority(101))); +static constexpr auto XYZ_PREFIX = "/xyz/openbmc_project/"; static constexpr auto INV_INTF = "xyz.openbmc_project.Inventory.Manager"; static constexpr auto OBJ_PATH = "/xyz/openbmc_project/inventory"; static constexpr auto PROP_INTF = "org.freedesktop.DBus.Properties"; @@ -51,21 +50,38 @@ ipmi::PropertyMap readAllProperties(const std::string& intf, { ipmi::PropertyMap properties; sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; - auto service = ipmi::getService(bus, INV_INTF, OBJ_PATH); - std::string objPath = OBJ_PATH + path; + std::string service; + std::string objPath; + + // Is the path the full dbus path? + if (path.find(XYZ_PREFIX) != std::string::npos) + { + service = ipmi::getService(bus, intf, path); + objPath = path; + } + else + { + service = ipmi::getService(bus, INV_INTF, OBJ_PATH); + objPath = OBJ_PATH + path; + } + auto method = bus.new_method_call(service.c_str(), objPath.c_str(), PROP_INTF, "GetAll"); method.append(intf); - auto reply = bus.call(method); - if (reply.is_method_error()) + try + { + auto reply = bus.call(method); + reply.read(properties); + } + catch (const sdbusplus::exception::SdBusError& e) { // If property is not found simply return empty value - log<level::ERR>("Error in reading property values from inventory", + log<level::ERR>("Error in reading property values", + entry("EXCEPTION=%s", e.what()), entry("INTERFACE=%s", intf.c_str()), entry("PATH=%s", objPath.c_str())); - return properties; } - reply.read(properties); + return properties; } @@ -140,9 +156,9 @@ FruInventoryData readDataFromInventory(const FRUId& fruNum) if (iter != allProp.end()) { data[properties.second.section].emplace( - properties.first, - std::move(variant_ns::get<std::string>( - allProp[properties.first]))); + properties.second.property, + std::move( + std::get<std::string>(allProp[properties.first]))); } } } diff --git a/scripts/entity-example.yaml b/scripts/entity-example.md index 2db14cc..98dbade 100755..100644 --- a/scripts/entity-example.yaml +++ b/scripts/entity-example.md @@ -1,3 +1,28 @@ +If your platform requires the entity container map, you can provide a json file of the format: + +``` +[ + { + "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: + +``` # This record has: # Container Entity Id and Container Entity Instance = (0x13, 0x81) # Contained Entity Id and Contained Entity Instance = (0x0A, 0x1), @@ -125,3 +150,4 @@ entityInstance3: 0xD entityId4: 0x20 entityInstance4: 0xF +``` diff --git a/scripts/entity_gen.py b/scripts/entity_gen.py deleted file mode 100755 index 057821b..0000000 --- a/scripts/entity_gen.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -import yaml -import argparse -from mako.template import Template - - -def generate_cpp(entity_yaml, output_dir): - with open(os.path.join(script_dir, entity_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, - "writeentity.mako.cpp")) - - output_cpp = os.path.join(output_dir, "entity-gen.cpp") - with open(output_cpp, 'w') as fd: - fd.write(t.render(entityDict=ifile)) - - -def main(): - - valid_commands = { - 'generate-cpp': generate_cpp - } - parser = argparse.ArgumentParser( - description="IPMI Entity record parser and code generator") - - parser.add_argument( - '-i', '--entity_yaml', dest='entity_yaml', - default='example.yaml', help='input entity 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(os.path.join(script_dir, args.entity_yaml)))): - sys.exit("Can not find input yaml file " + args.entity_yaml) - - function = valid_commands[args.command] - function(args.entity_yaml, args.outputdir) - - -if __name__ == '__main__': - script_dir = os.path.dirname(os.path.realpath(__file__)) - main() diff --git a/scripts/fru_gen.py b/scripts/fru_gen.py index f6111b7..a8d148a 100755 --- a/scripts/fru_gen.py +++ b/scripts/fru_gen.py @@ -8,7 +8,7 @@ from mako.template import Template def generate_cpp(inventory_yaml, output_dir): - with open(os.path.join(script_dir, inventory_yaml), 'r') as f: + with open(inventory_yaml, 'r') as f: ifile = yaml.safe_load(f) if not isinstance(ifile, dict): ifile = {} @@ -48,7 +48,7 @@ def main(): args = parser.parse_args() - if (not (os.path.isfile(os.path.join(script_dir, args.inventory_yaml)))): + if (not (os.path.isfile(args.inventory_yaml))): sys.exit("Can not find input yaml file " + args.inventory_yaml) function = valid_commands[args.command] diff --git a/scripts/inventory-sensor-example.yaml b/scripts/inventory-sensor-example.yaml index 7ff78fb..7ff78fb 100755..100644 --- a/scripts/inventory-sensor-example.yaml +++ b/scripts/inventory-sensor-example.yaml diff --git a/scripts/inventory-sensor.py b/scripts/inventory-sensor.py index 77222f5..2dd1e8d 100755 --- a/scripts/inventory-sensor.py +++ b/scripts/inventory-sensor.py @@ -8,7 +8,7 @@ from mako.template import Template def generate_cpp(sensor_yaml, output_dir): - with open(os.path.join(script_dir, sensor_yaml), 'r') as f: + with open(sensor_yaml, 'r') as f: ifile = yaml.safe_load(f) if not isinstance(ifile, dict): ifile = {} @@ -48,7 +48,7 @@ def main(): args = parser.parse_args() - if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))): + if (not (os.path.isfile(args.sensor_yaml))): sys.exit("Can not find input yaml file " + args.sensor_yaml) function = valid_commands[args.command] diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml index 4a1472d..c0fbe1e 100755..100644 --- a/scripts/sensor-example.yaml +++ b/scripts/sensor-example.yaml @@ -154,3 +154,41 @@ #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 + deassert: 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 + deassert: true + type: bool + Prereqs: + 7: + assert: true + deassert: false + type: bool diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py index 822a00a..6f59021 100755 --- a/scripts/sensor_gen.py +++ b/scripts/sensor_gen.py @@ -8,7 +8,7 @@ from mako.template import Template def generate_cpp(sensor_yaml, output_dir): - with open(os.path.join(script_dir, sensor_yaml), 'r') as f: + with open(sensor_yaml, 'r') as f: ifile = yaml.safe_load(f) if not isinstance(ifile, dict): ifile = {} @@ -48,7 +48,7 @@ def main(): args = parser.parse_args() - if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))): + if (not (os.path.isfile(args.sensor_yaml))): sys.exit("Can not find input yaml file " + args.sensor_yaml) function = valid_commands[args.command] diff --git a/scripts/writeentity.mako.cpp b/scripts/writeentity.mako.cpp deleted file mode 100644 index 4cfc82b..0000000 --- a/scripts/writeentity.mako.cpp +++ /dev/null @@ -1,34 +0,0 @@ -## 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 EntityInfoMap entities = { -% for key in entityDict.iterkeys(): -{${key},{ -<% - entity = entityDict[key] - containerEntityId = entity["containerEntityId"] - containerEntityInstance = entity["containerEntityInstance"] - isList = entity["isList"] - isLinked = entity["isLinked"] - entityId1 = entity["entityId1"] - entityInstance1 = entity["entityInstance1"] - entityId2 = entity["entityId2"] - entityInstance2 = entity["entityInstance2"] - entityId3 = entity["entityId3"] - entityInstance3 = entity["entityInstance3"] - entityId4 = entity["entityId4"] - entityInstance4 = entity["entityInstance4"] -%> - ${containerEntityId},${containerEntityInstance},${isList},${isLinked},{ - std::make_pair(${entityId1}, ${entityInstance1}), - std::make_pair(${entityId2}, ${entityInstance2}), - std::make_pair(${entityId3}, ${entityInstance3}), - std::make_pair(${entityId4}, ${entityInstance4}) } - -}}, -% endfor -}; diff --git a/scripts/writesensor.mako.cpp b/scripts/writesensor.mako.cpp index 559f0f9..c740f6f 100644 --- a/scripts/writesensor.mako.cpp +++ b/scripts/writesensor.mako.cpp @@ -27,7 +27,9 @@ interfaceDict = {} #include "sensordatahandler.hpp" #include <ipmid/types.hpp> -using namespace ipmi::sensor; + +namespace ipmi { +namespace sensor { extern const IdInfoMap sensors = { % for key in sensorDict.iterkeys(): @@ -74,67 +76,69 @@ extern const IdInfoMap sensors = { ${updateFunc},${getFunc},Mutability(${mutability}),${sensorNameFunc},{ % for interface,properties in interfaces.items(): {"${interface}",{ - % for dbus_property,property_value in properties.items(): - {"${dbus_property}",{ + % if properties: + % for dbus_property,property_value in properties.items(): + {"${dbus_property}",{ <% try: preReq = property_value["Prereqs"] except KeyError, e: preReq = dict() %>\ - { - % for preOffset,preValues in preReq.items(): - { ${preOffset},{ - % for name,value in preValues.items(): - % if name == "type": + { + % for preOffset,preValues in preReq.items(): + { ${preOffset},{ + % for name,value in preValues.items(): + % if name == "type": <% continue %>\ - % endif + % endif <% value = str(value).lower() %>\ - ${value}, + ${value}, + % endfor + } + }, % endfor - } }, - % endfor - }, - { - % for offset,values in property_value["Offsets"].items(): - { ${offset},{ - % if offset == 0xFF: - }}, + { + % for offset,values in property_value["Offsets"].items(): + { ${offset},{ + % if offset == 0xFF: + }}, <% continue %>\ - % endif + % endif <% valueType = values["type"] %>\ <% try: skip = values["skipOn"] if skip == "assert": - skipVal = "SkipAssertion::ASSERT" + skipVal = "SkipAssertion::ASSERT" elif skip == "deassert": - skipVal = "SkipAssertion::DEASSERT" + skipVal = "SkipAssertion::DEASSERT" else: - assert "Unknown skip value " + str(skip) + assert "Unknown skip value " + str(skip) except KeyError, e: skipVal = "SkipAssertion::NONE" %>\ - ${skipVal}, - % for name,value in values.items(): - % if name == "type" or name == "skipOn": + ${skipVal}, + % for name,value in values.items(): + % if name == "type" or name == "skipOn": <% continue %>\ - % endif - % if valueType == "string": - std::string("${value}"), - % elif valueType == "bool": + % endif + % if valueType == "string": + std::string("${value}"), + % elif valueType == "bool": <% value = str(value).lower() %>\ - ${value}, - % else: - ${value}, - % endif - % endfor - } - }, + ${value}, + % else: + ${value}, + % endif + % endfor + } + }, + % endfor + }}}, % endfor - }}}, - % endfor + % endif }}, % endfor }, @@ -143,3 +147,5 @@ except KeyError, e: % endfor }; +} // namespace sensor +} // namespace ipmi diff --git a/selutility.cpp b/selutility.cpp index 97c2b2b..cd3b6a6 100644 --- a/selutility.cpp +++ b/selutility.cpp @@ -58,8 +58,7 @@ GetSELEntryResponse elog<InternalFailure>(); } - record.recordID = static_cast<uint16_t>( - sdbusplus::message::variant_ns::get<uint32_t>(iterId->second)); + record.recordID = static_cast<uint16_t>(std::get<uint32_t>(iterId->second)); // Read Timestamp from the log entry. static constexpr auto propTimeStamp = "Timestamp"; @@ -71,7 +70,7 @@ GetSELEntryResponse } std::chrono::milliseconds chronoTimeStamp( - sdbusplus::message::variant_ns::get<uint64_t>(iterTimeStamp->second)); + std::get<uint64_t>(iterTimeStamp->second)); record.timeStamp = static_cast<uint32_t>( std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp) .count()); @@ -100,7 +99,7 @@ GetSELEntryResponse static constexpr auto deassertEvent = 0x80; // Evaluate if the event is assertion or deassertion event - if (sdbusplus::message::variant_ns::get<bool>(iterResolved->second)) + if (std::get<bool>(iterResolved->second)) { record.eventType = deassertEvent | iter->second.eventReadingType; } @@ -118,8 +117,9 @@ GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; - static constexpr auto assocIntf = "org.openbmc.Associations"; - static constexpr auto assocProp = "associations"; + static constexpr auto assocIntf = + "xyz.openbmc_project.Association.Definitions"; + static constexpr auto assocProp = "Associations"; auto service = ipmi::getService(bus, assocIntf, objPath); @@ -139,10 +139,10 @@ GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath) using AssociationList = std::vector<std::tuple<std::string, std::string, std::string>>; - sdbusplus::message::variant<AssociationList> list; + std::variant<AssociationList> list; reply.read(list); - auto& assocs = sdbusplus::message::variant_ns::get<AssociationList>(list); + auto& assocs = std::get<AssociationList>(list); /* * Check if the log entry has any callout associations, if there is a @@ -201,11 +201,10 @@ std::chrono::seconds getEntryTimeStamp(const std::string& objPath) elog<InternalFailure>(); } - sdbusplus::message::variant<uint64_t> timeStamp; + std::variant<uint64_t> timeStamp; reply.read(timeStamp); - std::chrono::milliseconds chronoTimeStamp( - sdbusplus::message::variant_ns::get<uint64_t>(timeStamp)); + std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp)); return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp); } diff --git a/selutility.hpp b/selutility.hpp index 806f937..49ec1b7 100644 --- a/selutility.hpp +++ b/selutility.hpp @@ -28,32 +28,26 @@ using Id = uint32_t; using Timestamp = uint64_t; using Message = std::string; using AdditionalData = std::vector<std::string>; -using PropertyType = sdbusplus::message::variant<Resolved, Id, Timestamp, - Message, AdditionalData>; +using PropertyType = + std::variant<Resolved, Id, Timestamp, Message, AdditionalData>; static constexpr auto selVersion = 0x51; static constexpr auto invalidTimeStamp = 0xFFFFFFFF; -static constexpr auto operationSupport = 0x0A; - -/** @struct GetSELInfoResponse - * - * IPMI payload for Get SEL Info command response. - */ -struct GetSELInfoResponse -{ - uint8_t selVersion; //!< SEL revision. - uint16_t entries; //!< Number of log entries in SEL. - uint16_t freeSpace; //!< Free Space in bytes. - uint32_t addTimeStamp; //!< Most recent addition timestamp. - uint32_t eraseTimeStamp; //!< Most recent erase timestamp. - uint8_t operationSupport; //!< Operation support. -} __attribute__((packed)); 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. @@ -86,33 +80,10 @@ struct GetSELEntryResponse uint8_t eventData3; //!< Event Data 3. } __attribute__((packed)); -/** @struct DeleteSELEntryRequest - * - * IPMI payload for Delete SEL Entry command request. - */ -struct DeleteSELEntryRequest -{ - uint16_t reservationID; //!< Reservation ID. - uint16_t selRecordID; //!< SEL Record ID. -} __attribute__((packed)); - static constexpr auto initiateErase = 0xAA; static constexpr auto getEraseStatus = 0x00; static constexpr auto eraseComplete = 0x01; -/** @struct ClearSELRequest - * - * IPMI payload for Clear SEL command request. - */ -struct ClearSELRequest -{ - uint16_t reservationID; //!< Reservation ID. - uint8_t charC; //!< Char 'C'(0x43h). - uint8_t charL; //!< Char 'L'(0x4Ch). - uint8_t charR; //!< Char 'R'(0x52h). - uint8_t eraseOperation; //!< Erase operation. -} __attribute__((packed)); - /** @brief Convert logging entry to SEL * * @param[in] objPath - DBUS object path of the logging entry. diff --git a/sensordatahandler.cpp b/sensordatahandler.cpp index 1de1540..0d1182d 100644 --- a/sensordatahandler.cpp +++ b/sensordatahandler.cpp @@ -17,8 +17,6 @@ namespace ipmi namespace sensor { -namespace variant_ns = sdbusplus::message::variant_ns; - using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; @@ -134,7 +132,6 @@ GetSensorResponse mapDbusToAssertion(const Info& sensorInfo, { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; GetSensorResponse response{}; - auto responseData = reinterpret_cast<GetReadingResponse*>(response.data()); auto service = ipmi::getService(bus, interface, path); @@ -151,7 +148,7 @@ GetSensorResponse mapDbusToAssertion(const Info& sensorInfo, { if (propValue == value.second.assert) { - setOffset(value.first, responseData); + setOffset(value.first, &response); break; } } @@ -171,7 +168,6 @@ GetSensorResponse eventdata2(const Info& sensorInfo) { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; GetSensorResponse response{}; - auto responseData = reinterpret_cast<GetReadingResponse*>(response.data()); auto service = ipmi::getService(bus, sensorInfo.sensorInterface, sensorInfo.sensorPath); @@ -190,7 +186,7 @@ GetSensorResponse eventdata2(const Info& sensorInfo) { if (propValue == value.second.assert) { - setReading(value.first, responseData); + setReading(value.first, &response); break; } } @@ -324,6 +320,15 @@ ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo) 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) @@ -340,8 +345,7 @@ ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo) { return IPMI_CC_OK; } - result = - result && variant_ns::get<bool>(value.second.assert); + result = result && std::get<bool>(value.second.assert); valid = true; } else if (deassertionSet.test(value.first)) @@ -351,8 +355,7 @@ ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo) { return IPMI_CC_OK; } - result = - result && variant_ns::get<bool>(value.second.deassert); + result = result && std::get<bool>(value.second.deassert); valid = true; } } @@ -361,13 +364,11 @@ ipmi_ret_t assertion(const SetSensorReadingReq& cmdData, const Info& sensorInfo) { if (assertionSet.test(value.first)) { - result = - result && variant_ns::get<bool>(value.second.assert); + result = result && std::get<bool>(value.second.assert); } else if (deassertionSet.test(value.first)) { - result = - result && variant_ns::get<bool>(value.second.deassert); + result = result && std::get<bool>(value.second.deassert); } } if (valid) diff --git a/sensordatahandler.hpp b/sensordatahandler.hpp index 6d80f9a..99c3ae9 100644 --- a/sensordatahandler.hpp +++ b/sensordatahandler.hpp @@ -1,5 +1,7 @@ #pragma once +#include "config.h" + #include "sensorhandler.hpp" #include <cmath> @@ -13,8 +15,6 @@ namespace ipmi namespace sensor { -namespace variant_ns = sdbusplus::message::variant_ns; - using Assertion = uint16_t; using Deassertion = uint16_t; using AssertionSet = std::pair<Assertion, Deassertion>; @@ -156,7 +156,6 @@ GetSensorResponse readingAssertion(const Info& sensorInfo) { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; GetSensorResponse response{}; - auto responseData = reinterpret_cast<GetReadingResponse*>(response.data()); auto service = ipmi::getService(bus, sensorInfo.sensorInterface, sensorInfo.sensorPath); @@ -166,8 +165,7 @@ GetSensorResponse readingAssertion(const Info& sensorInfo) sensorInfo.propertyInterfaces.begin()->first, sensorInfo.propertyInterfaces.begin()->second.begin()->first); - setAssertionBytes(static_cast<uint16_t>(variant_ns::get<T>(propValue)), - responseData); + setAssertionBytes(static_cast<uint16_t>(std::get<T>(propValue)), &response); return response; } @@ -184,26 +182,51 @@ template <typename T> GetSensorResponse readingData(const Info& sensorInfo) { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + GetSensorResponse response{}; - auto responseData = reinterpret_cast<GetReadingResponse*>(response.data()); - enableScanning(responseData); + 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 = variant_ns::get<T>(propValue) * + double value = std::get<T>(propValue) * std::pow(10, sensorInfo.scale - sensorInfo.exponentR); auto rawData = static_cast<uint8_t>((value - sensorInfo.scaledOffset) / sensorInfo.coefficientM); - - setReading(rawData, responseData); + setReading(rawData, &response); return response; } @@ -252,7 +275,7 @@ ipmi_ret_t readingAssertion(const SetSensorReadingReq& cmdData, for (const auto& property : interface->second) { msg.append(property.first); - sdbusplus::message::variant<T> value = + std::variant<T> value = (cmdData.assertOffset8_14 << 8) | cmdData.assertOffset0_7; msg.append(value); } @@ -283,7 +306,7 @@ ipmi_ret_t readingData(const SetSensorReadingReq& cmdData, for (const auto& property : interface->second) { msg.append(property.first); - sdbusplus::message::variant<T> value = raw_value; + std::variant<T> value = raw_value; msg.append(value); } return updateToDbus(msg); diff --git a/sensorhandler.cpp b/sensorhandler.cpp index 48546af..1cffd55 100644 --- a/sensorhandler.cpp +++ b/sensorhandler.cpp @@ -1,5 +1,8 @@ +#include "config.h" + #include "sensorhandler.hpp" +#include "entity_map_json.hpp" #include "fruread.hpp" #include <mapper.h> @@ -24,16 +27,21 @@ static constexpr uint8_t BMCSlaveAddress = 0x20; extern int updateSensorRecordFromSSRAESC(const void*); extern sd_bus* bus; -extern const ipmi::sensor::IdInfoMap sensors; + +namespace ipmi +{ +namespace sensor +{ +extern const IdInfoMap sensors; +} // namespace sensor +} // namespace ipmi + extern const FruMap frus; -extern const ipmi::sensor::EntityInfoMap entities; using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; -namespace variant_ns = sdbusplus::message::variant_ns; - void register_netfn_sen_functions() __attribute__((constructor)); struct sensorTypemap_t @@ -71,13 +79,6 @@ struct sensor_data_t uint8_t sennum; } __attribute__((packed)); -struct sensorreadingresp_t -{ - uint8_t value; - uint8_t operation; - uint8_t indication[2]; -} __attribute__((packed)); - int get_bus_for_path(const char* path, char** busname) { return mapper_get_service(bus, path, busname); @@ -90,8 +91,8 @@ int find_openbmc_path(uint8_t num, dbus_interface_t* interface) { int rc; - const auto& sensor_it = sensors.find(num); - if (sensor_it == sensors.end()) + 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; @@ -324,16 +325,54 @@ bool isAnalogSensor(const std::string& interface) return (analogSensorInterfaces.count(interface)); } -ipmi_ret_t setSensorReading(void* request) +/** +@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) { - ipmi::sensor::SetSensorReadingReq cmdData = - *(static_cast<ipmi::sensor::SetSensorReadingReq*>(request)); + log<level::DEBUG>("IPMI SET_SENSOR", + entry("SENSOR_NUM=0x%02x", sensorNumber)); + + 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 = sensors.find(cmdData.number); - if (iter == sensors.end()) + const auto iter = ipmi::sensor::sensors.find(sensorNumber); + if (iter == ipmi::sensor::sensors.end()) { - return IPMI_CC_SENSOR_INVALID; + updateSensorRecordFromSSRAESC(&sensorNumber); + return ipmi::responseSuccess(); } try @@ -342,92 +381,104 @@ ipmi_ret_t setSensorReading(void* request) (iter->second.mutability & ipmi::sensor::Mutability::Write)) { log<level::ERR>("Sensor Set operation is not allowed", - entry("SENSOR_NUM=%d", cmdData.number)); - return IPMI_CC_ILLEGAL_COMMAND; + entry("SENSOR_NUM=%d", sensorNumber)); + return ipmi::responseIllegalCommand(); } - return iter->second.updateFunc(cmdData, iter->second); + auto ipmiRC = iter->second.updateFunc(cmdData, iter->second); + return ipmi::response(ipmiRC); } catch (InternalFailure& e) { log<level::ERR>("Set sensor failed", - entry("SENSOR_NUM=%d", cmdData.number)); + entry("SENSOR_NUM=%d", sensorNumber)); commit<InternalFailure>(); + return ipmi::responseUnspecifiedError(); } catch (const std::runtime_error& e) { log<level::ERR>(e.what()); + return ipmi::responseUnspecifiedError(); } - - return IPMI_CC_UNSPECIFIED_ERROR; } -ipmi_ret_t ipmi_sen_set_sensor(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) +/** @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(uint8_t sensorNum) { - auto reqptr = static_cast<sensor_data_t*>(request); - - log<level::DEBUG>("IPMI SET_SENSOR", - entry("SENSOR_NUM=0x%02x", reqptr->sennum)); - - /* - * This would support the Set Sensor Reading command for the presence - * and functional state of Processor, Core & DIMM. For the remaining - * sensors the existing support is invoked. - */ - auto ipmiRC = setSensorReading(request); - - if (ipmiRC == IPMI_CC_SENSOR_INVALID) + if (sensorNum == 0xFF) { - updateSensorRecordFromSSRAESC(reqptr); - ipmiRC = IPMI_CC_OK; + return ipmi::responseInvalidFieldRequest(); } - *data_len = 0; - return ipmiRC; -} - -ipmi_ret_t ipmi_sen_get_sensor_reading(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) -{ - auto reqptr = static_cast<sensor_data_t*>(request); - auto resp = static_cast<sensorreadingresp_t*>(response); - ipmi::sensor::GetSensorResponse getResponse{}; - static constexpr auto scanningEnabledBit = 6; - - const auto iter = sensors.find(reqptr->sennum); - if (iter == sensors.end()) + const auto iter = ipmi::sensor::sensors.find(sensorNum); + if (iter == ipmi::sensor::sensors.end()) { - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } if (ipmi::sensor::Mutability::Read != (iter->second.mutability & ipmi::sensor::Mutability::Read)) { - return IPMI_CC_ILLEGAL_COMMAND; + return ipmi::responseIllegalCommand(); } try { - getResponse = iter->second.getFunc(iter->second); - *data_len = getResponse.size(); - std::memcpy(resp, getResponse.data(), *data_len); - resp->operation = 1 << scanningEnabledBit; - return IPMI_CC_OK; + 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); + } +#ifdef UPDATE_FUNCTIONAL_ON_FAIL + catch (const SensorFunctionalError& e) + { + return ipmi::responseResponseError(); } +#endif catch (const std::exception& e) { - *data_len = getResponse.size(); - std::memcpy(resp, getResponse.data(), *data_len); - return IPMI_CC_OK; + // 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 getSensorThresholds(uint8_t sensorNum, - get_sdr::GetSensorThresholdsResponse* response) +get_sdr::GetSensorThresholdsResponse getSensorThresholds(uint8_t sensorNum) { + get_sdr::GetSensorThresholdsResponse resp; constexpr auto warningThreshIntf = "xyz.openbmc_project.Sensor.Threshold.Warning"; constexpr auto criticalThreshIntf = @@ -435,7 +486,7 @@ void getSensorThresholds(uint8_t sensorNum, sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; - const auto iter = sensors.find(sensorNum); + const auto iter = ipmi::sensor::sensors.find(sensorNum); const auto info = iter->second; auto service = ipmi::getService(bus, info.sensorInterface, info.sensorPath); @@ -443,76 +494,85 @@ void getSensorThresholds(uint8_t sensorNum, auto warnThresholds = ipmi::getAllDbusProperties( bus, service, info.sensorPath, warningThreshIntf); - double warnLow = variant_ns::visit(ipmi::VariantToDoubleVisitor(), - warnThresholds["WarningLow"]); - double warnHigh = variant_ns::visit(ipmi::VariantToDoubleVisitor(), - warnThresholds["WarningHigh"]); + double warnLow = std::visit(ipmi::VariantToDoubleVisitor(), + warnThresholds["WarningLow"]); + double warnHigh = std::visit(ipmi::VariantToDoubleVisitor(), + warnThresholds["WarningHigh"]); if (warnLow != 0) { warnLow *= std::pow(10, info.scale - info.exponentR); - response->lowerNonCritical = static_cast<uint8_t>( + resp.lowerNonCritical = static_cast<uint8_t>( (warnLow - info.scaledOffset) / info.coefficientM); - response->validMask |= static_cast<uint8_t>( + resp.validMask |= static_cast<uint8_t>( ipmi::sensor::ThresholdMask::NON_CRITICAL_LOW_MASK); } if (warnHigh != 0) { warnHigh *= std::pow(10, info.scale - info.exponentR); - response->upperNonCritical = static_cast<uint8_t>( + resp.upperNonCritical = static_cast<uint8_t>( (warnHigh - info.scaledOffset) / info.coefficientM); - response->validMask |= static_cast<uint8_t>( + resp.validMask |= static_cast<uint8_t>( ipmi::sensor::ThresholdMask::NON_CRITICAL_HIGH_MASK); } auto critThresholds = ipmi::getAllDbusProperties( bus, service, info.sensorPath, criticalThreshIntf); - double critLow = variant_ns::visit(ipmi::VariantToDoubleVisitor(), - critThresholds["CriticalLow"]); - double critHigh = variant_ns::visit(ipmi::VariantToDoubleVisitor(), - critThresholds["CriticalHigh"]); + double critLow = std::visit(ipmi::VariantToDoubleVisitor(), + critThresholds["CriticalLow"]); + double critHigh = std::visit(ipmi::VariantToDoubleVisitor(), + critThresholds["CriticalHigh"]); if (critLow != 0) { critLow *= std::pow(10, info.scale - info.exponentR); - response->lowerCritical = static_cast<uint8_t>( + resp.lowerCritical = static_cast<uint8_t>( (critLow - info.scaledOffset) / info.coefficientM); - response->validMask |= static_cast<uint8_t>( + resp.validMask |= static_cast<uint8_t>( ipmi::sensor::ThresholdMask::CRITICAL_LOW_MASK); } if (critHigh != 0) { critHigh *= std::pow(10, info.scale - info.exponentR); - response->upperCritical = static_cast<uint8_t>( + resp.upperCritical = static_cast<uint8_t>( (critHigh - info.scaledOffset) / info.coefficientM); - response->validMask |= static_cast<uint8_t>( + resp.validMask |= static_cast<uint8_t>( ipmi::sensor::ThresholdMask::CRITICAL_HIGH_MASK); } + + return resp; } -ipmi_ret_t ipmi_sen_get_sensor_thresholds(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) +/** @brief implements the get sensor thresholds command + * @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(uint8_t sensorNum) { constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; - if (*data_len != sizeof(uint8_t)) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - auto sensorNum = *(reinterpret_cast<const uint8_t*>(request)); - *data_len = 0; - - const auto iter = sensors.find(sensorNum); - if (iter == sensors.end()) + const auto iter = ipmi::sensor::sensors.find(sensorNum); + if (iter == ipmi::sensor::sensors.end()) { - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } const auto info = iter->second; @@ -522,82 +582,75 @@ ipmi_ret_t ipmi_sen_get_sensor_thresholds(ipmi_netfn_t netfn, ipmi_cmd_t cmd, info.propertyInterfaces.end()) { // return with valid mask as 0 - return IPMI_CC_OK; + return ipmi::responseSuccess(); } - auto responseData = - reinterpret_cast<get_sdr::GetSensorThresholdsResponse*>(response); - + get_sdr::GetSensorThresholdsResponse resp{}; try { - getSensorThresholds(sensorNum, responseData); + resp = getSensorThresholds(sensorNum); } catch (std::exception& e) { // Mask if the property is not present - responseData->validMask = 0; } - *data_len = sizeof(get_sdr::GetSensorThresholdsResponse); - return IPMI_CC_OK; + return ipmi::responseSuccess(resp.validMask, resp.lowerNonCritical, + resp.lowerCritical, resp.lowerNonRecoverable, + resp.upperNonCritical, resp.upperCritical, + resp.upperNonRecoverable); } -ipmi_ret_t ipmi_sen_wildcard(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) +/** @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) { - ipmi_ret_t rc = IPMI_CC_INVALID; - - printf("IPMI S/E Wildcard Netfn:[0x%X], Cmd:[0x%X]\n", netfn, cmd); - *data_len = 0; - - return rc; -} + uint8_t sdrCount; + // multiple LUNs not supported. + constexpr uint8_t lunsAndDynamicPopulation = 1; + constexpr uint8_t getSdrCount = 0x01; + constexpr uint8_t getSensorCount = 0x00; -ipmi_ret_t ipmi_sen_get_sdr_info(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) -{ - auto resp = static_cast<get_sdr_info::GetSdrInfoResp*>(response); - if (request == nullptr || - get_sdr_info::request::get_count(request) == false) + if (count.value_or(0) == getSdrCount) { - // Get Sensor Count - resp->count = sensors.size() + frus.size() + entities.size(); + // 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 { - resp->count = 1; + return ipmi::responseInvalidCommandOnLun(); } - // Multiple LUNs not supported. - namespace response = get_sdr_info::response; - response::set_lun_present(0, &(resp->luns_and_dynamic_population)); - response::set_lun_not_present(1, &(resp->luns_and_dynamic_population)); - response::set_lun_not_present(2, &(resp->luns_and_dynamic_population)); - response::set_lun_not_present(3, &(resp->luns_and_dynamic_population)); - response::set_static_population(&(resp->luns_and_dynamic_population)); - - *data_len = SDR_INFO_RESP_SIZE; - - return IPMI_CC_OK; + return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation); } -ipmi_ret_t ipmi_sen_reserve_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) +/** @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. - const uint16_t reservation_id = 1; - *(uint16_t*)response = reservation_id; - *data_len = sizeof(uint16_t); + constexpr uint16_t reservationID = 1; - printf("Created new IPMI SDR reservation ID %d\n", *(uint16_t*)response); - return IPMI_CC_OK; + return ipmi::responseSuccess(reservationID); } void setUnitFieldsForObject(const ipmi::sensor::Info* info, @@ -740,9 +793,13 @@ ipmi_ret_t ipmi_fru_get_sdr(ipmi_request_t request, ipmi_response_t response, { // 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 = - (entities.size()) ? entities.begin()->first + ENTITY_RECORD_ID_START - : END_OF_RECORD; + (entityRecords.size()) + ? entityRecords.begin()->first + ENTITY_RECORD_ID_START + : END_OF_RECORD; get_sdr::response::set_next_record_id(next_record_id, resp); } else @@ -777,13 +834,16 @@ ipmi_ret_t ipmi_entity_get_sdr(ipmi_request_t request, ipmi_response_t response, get_sdr::SensorDataEntityRecord record{}; auto dataLength = 0; - auto entity = entities.begin(); + 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 = entities.find(entityRecordID); - if (entity == entities.end()) + entity = entityRecords.find(entityRecordID); + if (entity == entityRecords.end()) { return IPMI_CC_SENSOR_INVALID; } @@ -810,7 +870,7 @@ ipmi_ret_t ipmi_entity_get_sdr(ipmi_request_t request, ipmi_response_t response, record.body.entityId4 = entity->second.containedEntities[3].first; record.body.entityInstance4 = entity->second.containedEntities[3].second; - if (++entity == entities.end()) + if (++entity == entityRecords.end()) { get_sdr::response::set_next_record_id(END_OF_RECORD, resp); // last record @@ -847,101 +907,98 @@ ipmi_ret_t ipmi_sen_get_sdr(ipmi_netfn_t netfn, ipmi_cmd_t cmd, get_sdr::GetSdrReq* req = (get_sdr::GetSdrReq*)request; get_sdr::GetSdrResp* resp = (get_sdr::GetSdrResp*)response; get_sdr::SensorDataFullRecord record = {0}; - if (req != NULL) - { - // Note: we use an iterator so we can provide the next ID at the end of - // the call. - auto 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 = sensors.find(recordID); - if (sensor == sensors.end()) - { - return IPMI_CC_SENSOR_INVALID; - } - } - } + // 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); - uint8_t sensor_id = sensor->first; - - /* Header */ - 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(get_sdr::SensorDataFullRecord); - - /* 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)) + // 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) { - get_sdr::body::init_settable_state(true, &(record.body)); + return ipmi_entity_get_sdr(request, response, data_len); } - - // Set the type-specific details given the DBus interface - ret = populate_record_from_dbus(&(record.body), &(sensor->second), - data_len); - - if (++sensor == sensors.end()) + else if (recordID >= FRU_RECORD_ID_START && + recordID < ENTITY_RECORD_ID_START) { - // 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); + return ipmi_fru_get_sdr(request, response, data_len); } else { - get_sdr::response::set_next_record_id(sensor->first, resp); + sensor = ipmi::sensor::sensors.find(recordID); + if (sensor == ipmi::sensor::sensors.end()) + { + return IPMI_CC_SENSOR_INVALID; + } } + } - if (req->offset > sizeof(record)) - { - return IPMI_CC_PARM_OUT_OF_RANGE; - } + uint8_t sensor_id = sensor->first; + + /* Header */ + 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(get_sdr::SensorDataFullRecord); + + /* 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)); + } - // 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); + // Set the type-specific details given the DBus interface + ret = + populate_record_from_dbus(&(record.body), &(sensor->second), data_len); - std::memcpy(resp->record_data, - reinterpret_cast<uint8_t*>(&record) + req->offset, - *data_len); + 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; - // data_len should include the LSB and MSB: - *data_len += - sizeof(resp->next_record_id_lsb) + sizeof(resp->next_record_id_msb); + 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<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; } @@ -1037,10 +1094,6 @@ ipmi_ret_t ipmicmdPlatformEvent(ipmi_netfn_t netfn, ipmi_cmd_t cmd, void register_netfn_sen_functions() { - // <Wildcard Command> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_WILDCARD, nullptr, - ipmi_sen_wildcard, PRIVILEGE_USER); - // <Platform Event Message> ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_PLATFORM_EVENT, nullptr, ipmicmdPlatformEvent, PRIVILEGE_OPERATOR); @@ -1049,29 +1102,32 @@ void register_netfn_sen_functions() ipmi_sen_get_sensor_type, PRIVILEGE_USER); // <Set Sensor Reading and Event Status> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_SET_SENSOR, nullptr, - ipmi_sen_set_sensor, PRIVILEGE_OPERATOR); - + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, + ipmi::sensor_event::cmdSetSensorReadingAndEvtSts, + ipmi::Privilege::Operator, ipmiSetSensorReading); // <Get Sensor Reading> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_GET_SENSOR_READING, nullptr, - ipmi_sen_get_sensor_reading, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, + ipmi::sensor_event::cmdGetSensorReading, + ipmi::Privilege::User, ipmiSensorGetSensorReading); // <Reserve Device SDR Repository> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_RESERVE_DEVICE_SDR_REPO, - nullptr, ipmi_sen_reserve_sdr, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, + ipmi::sensor_event::cmdReserveDeviceSdrRepository, + ipmi::Privilege::User, ipmiSensorReserveSdr); // <Get Device SDR Info> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_GET_DEVICE_SDR_INFO, nullptr, - ipmi_sen_get_sdr_info, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, + ipmi::sensor_event::cmdGetDeviceSdrInfo, + ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo); // <Get Device SDR> ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_GET_DEVICE_SDR, nullptr, ipmi_sen_get_sdr, PRIVILEGE_USER); // <Get Sensor Thresholds> - ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_GET_SENSOR_THRESHOLDS, - nullptr, ipmi_sen_get_sensor_thresholds, - PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, + ipmi::sensor_event::cmdGetSensorThreshold, + ipmi::Privilege::User, ipmiSensorGetSensorThresholds); return; } diff --git a/sensorhandler.hpp b/sensorhandler.hpp index 954a3ed..8780235 100644 --- a/sensorhandler.hpp +++ b/sensorhandler.hpp @@ -2,6 +2,7 @@ #include <stdint.h> +#include <exception> #include <ipmid/api.hpp> #include <ipmid/types.hpp> @@ -38,6 +39,16 @@ enum ipmi_sensor_types 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 { @@ -79,11 +90,7 @@ 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_ret_t ipmi_sen_reserve_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; @@ -106,34 +113,6 @@ inline bool get_count(void* req) return (bool)((uint64_t)(req)&1); } } // namespace request - -namespace response -{ -#define SDR_INFO_RESP_SIZE 2 -inline void set_lun_present(int lun, uint8_t* resp) -{ - *resp |= 1 << lun; -} -inline void set_lun_not_present(int lun, uint8_t* resp) -{ - *resp &= ~(1 << lun); -} -inline void set_dynamic_population(uint8_t* resp) -{ - *resp |= 1 << 7; -} -inline void set_static_population(uint8_t* resp) -{ - *resp &= ~(1 << 7); -} -} // namespace response - -struct GetSdrInfoResp -{ - uint8_t count; - uint8_t luns_and_dynamic_population; -}; - } // namespace get_sdr_info /** @@ -155,7 +134,7 @@ struct GetSdrReq namespace request { -inline uint8_t get_reservation_id(GetSdrReq* req) +inline uint16_t get_reservation_id(GetSdrReq* req) { return (req->reservation_id_lsb + (req->reservation_id_msb << 8)); }; @@ -665,15 +644,15 @@ namespace sensor * @param[in] offset - offset number. * @param[in/out] resp - get sensor reading response. */ -inline void setOffset(uint8_t offset, ipmi::sensor::GetReadingResponse* resp) +inline void setOffset(uint8_t offset, ipmi::sensor::GetSensorResponse* resp) { if (offset > 7) { - resp->assertOffset8_14 |= 1 << (offset - 8); + resp->discreteReadingSensorStates |= 1 << (offset - 8); } else { - resp->assertOffset0_7 |= 1 << offset; + resp->thresholdLevelsStates |= 1 << offset; } } @@ -683,7 +662,7 @@ inline void setOffset(uint8_t offset, ipmi::sensor::GetReadingResponse* resp) * @param[in] offset - offset number. * @param[in/out] resp - get sensor reading response. */ -inline void setReading(uint8_t value, ipmi::sensor::GetReadingResponse* resp) +inline void setReading(uint8_t value, ipmi::sensor::GetSensorResponse* resp) { resp->reading = value; } @@ -696,10 +675,10 @@ inline void setReading(uint8_t value, ipmi::sensor::GetReadingResponse* resp) * @param[in/out] resp - get sensor reading response. */ inline void setAssertionBytes(uint16_t value, - ipmi::sensor::GetReadingResponse* resp) + ipmi::sensor::GetSensorResponse* resp) { - resp->assertOffset0_7 = static_cast<uint8_t>(value & 0x00FF); - resp->assertOffset8_14 = static_cast<uint8_t>(value >> 8); + resp->thresholdLevelsStates = static_cast<uint8_t>(value & 0x00FF); + resp->discreteReadingSensorStates = static_cast<uint8_t>(value >> 8); } /** @@ -707,9 +686,11 @@ inline void setAssertionBytes(uint16_t value, * * @param[in/out] resp - get sensor reading response. */ -inline void enableScanning(ipmi::sensor::GetReadingResponse* resp) +inline void enableScanning(ipmi::sensor::GetSensorResponse* resp) { - resp->operation = 1 << 6; + resp->readingOrStateUnavailable = false; + resp->scanningEnabled = true; + resp->allEventMessagesEnabled = false; } } // namespace sensor diff --git a/settings.cpp b/settings.cpp index e8b8997..2fa2511 100644 --- a/settings.cpp +++ b/settings.cpp @@ -11,7 +11,6 @@ namespace settings using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::Error; -namespace variant_ns = sdbusplus::message::variant_ns; constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper"; constexpr auto mapperPath = "/xyz/openbmc_project/object_mapper"; @@ -130,9 +129,9 @@ std::tuple<Path, OneTimeEnabled> setting(const Objects& objects, elog<InternalFailure>(); } - sdbusplus::message::variant<bool> enabled; + std::variant<bool> enabled; reply.read(enabled); - auto oneTimeEnabled = variant_ns::get<bool>(enabled); + auto oneTimeEnabled = std::get<bool>(enabled); const Path& setting = oneTimeEnabled ? oneTimeSetting : regularSetting; return std::make_tuple(setting, oneTimeEnabled); } diff --git a/storagehandler.cpp b/storagehandler.cpp index 18f1357..5b9726a 100644 --- a/storagehandler.cpp +++ b/storagehandler.cpp @@ -19,17 +19,24 @@ #include <ipmid/utils.hpp> #include <phosphor-logging/elog-errors.hpp> #include <phosphor-logging/log.hpp> -#include <sdbusplus/message/types.hpp> #include <sdbusplus/server.hpp> #include <string> +#include <variant> #include <xyz/openbmc_project/Common/error.hpp> void register_netfn_storage_functions() __attribute__((constructor)); unsigned int g_sel_time = 0xFFFFFFFF; -extern const ipmi::sensor::IdInfoMap sensors; -extern const FruMap frus; +namespace ipmi +{ +namespace sensor +{ +extern const IdInfoMap sensors; +} // namespace sensor +} // namespace ipmi +extern const FruMap frus; +constexpr uint8_t eventDataSize = 3; namespace { constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime"; @@ -37,13 +44,6 @@ constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host"; constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"; constexpr auto PROPERTY_ELAPSED = "Elapsed"; -const char* getTimeString(const uint64_t& usecSinceEpoch) -{ - using namespace std::chrono; - system_clock::time_point tp{microseconds(usecSinceEpoch)}; - auto t = system_clock::to_time_t(tp); - return std::ctime(&t); -} } // namespace namespace cache @@ -62,8 +62,6 @@ ipmi::sel::ObjectPaths paths; } // namespace cache -namespace variant_ns = sdbusplus::message::variant_ns; - using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using namespace phosphor::logging; @@ -78,36 +76,34 @@ enum class AccessMode words ///< Device is accessed by words }; -ipmi_ret_t ipmi_storage_wildcard(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) -{ - // Status code. - ipmi_ret_t rc = IPMI_CC_INVALID; - *data_len = 0; - return rc; -} +/** @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_ret_t getSELInfo(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<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() { - if (*data_len != 0) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - std::vector<uint8_t> outPayload(sizeof(ipmi::sel::GetSELInfoResponse)); - auto responseData = - reinterpret_cast<ipmi::sel::GetSELInfoResponse*>(outPayload.data()); - - responseData->selVersion = ipmi::sel::selVersion; - // Last erase timestamp is not available from log manager. - responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp; - responseData->operationSupport = ipmi::sel::operationSupport; + uint16_t entries = 0; + // Most recent addition timestamp. + uint32_t addTimeStamp = ipmi::sel::invalidTimeStamp; try { @@ -121,16 +117,13 @@ ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, // as 0. } - responseData->entries = 0; - responseData->addTimeStamp = ipmi::sel::invalidTimeStamp; - if (!cache::paths.empty()) { - responseData->entries = static_cast<uint16_t>(cache::paths.size()); + entries = static_cast<uint16_t>(cache::paths.size()); try { - responseData->addTimeStamp = static_cast<uint32_t>( + addTimeStamp = static_cast<uint32_t>( (ipmi::sel::getEntryTimeStamp(cache::paths.back()).count())); } catch (InternalFailure& e) @@ -142,10 +135,18 @@ ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } } - std::memcpy(response, outPayload.data(), outPayload.size()); - *data_len = outPayload.size(); + 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_CC_OK; + 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 netfn, ipmi_cmd_t cmd, @@ -269,24 +270,24 @@ ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, return IPMI_CC_OK; } -ipmi_ret_t deleteSELEntry(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) +/** @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) { - if (*data_len != sizeof(ipmi::sel::DeleteSELEntryRequest)) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } namespace fs = std::filesystem; - auto requestData = - reinterpret_cast<const ipmi::sel::DeleteSELEntryRequest*>(request); - if (!checkSELReservation(requestData->reservationID)) + if (!checkSELReservation(reservationID)) { - *data_len = 0; - return IPMI_CC_INVALID_RESERVATION_ID; + return ipmi::responseInvalidReservationId(); } // Per the IPMI spec, need to cancel the reservation when a SEL entry is @@ -301,27 +302,25 @@ ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { // readLoggingObjectPaths will throw exception if there are no error // log entries. - *data_len = 0; - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } if (cache::paths.empty()) { - *data_len = 0; - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } ipmi::sel::ObjectPaths::const_iterator iter; uint16_t delRecordID = 0; - if (requestData->selRecordID == ipmi::sel::firstEntry) + if (selRecordID == ipmi::sel::firstEntry) { iter = cache::paths.begin(); fs::path path(*iter); delRecordID = static_cast<uint16_t>( std::stoul(std::string(path.filename().c_str()))); } - else if (requestData->selRecordID == ipmi::sel::lastEntry) + else if (selRecordID == ipmi::sel::lastEntry) { iter = cache::paths.end(); fs::path path(*iter); @@ -331,15 +330,14 @@ ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, else { std::string objPath = std::string(ipmi::sel::logBasePath) + "/" + - std::to_string(requestData->selRecordID); + std::to_string(selRecordID); iter = std::find(cache::paths.begin(), cache::paths.end(), objPath); if (iter == cache::paths.end()) { - *data_len = 0; - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } - delRecordID = requestData->selRecordID; + delRecordID = selRecordID; } sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; @@ -352,8 +350,7 @@ ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, catch (const std::runtime_error& e) { log<level::ERR>(e.what()); - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } auto methodCall = bus.new_method_call(service.c_str(), (*iter).c_str(), @@ -361,55 +358,49 @@ ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd, auto reply = bus.call(methodCall); if (reply.is_method_error()) { - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } // Invalidate the cache of dbus entry objects. cache::paths.clear(); - std::memcpy(response, &delRecordID, sizeof(delRecordID)); - *data_len = sizeof(delRecordID); - return IPMI_CC_OK; + return ipmi::responseSuccess(delRecordID); } -ipmi_ret_t clearSEL(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) -{ - if (*data_len != sizeof(ipmi::sel::ClearSELRequest)) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - auto requestData = - reinterpret_cast<const ipmi::sel::ClearSELRequest*>(request); +/** @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 + */ - if (!checkSELReservation(requestData->reservationID)) +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) { - *data_len = 0; - return IPMI_CC_INVALID_RESERVATION_ID; + return ipmi::responseInvalidFieldRequest(); } - if (requestData->charC != 'C' || requestData->charL != 'L' || - requestData->charR != 'R') + if (!checkSELReservation(reservationID)) { - *data_len = 0; - return IPMI_CC_INVALID_FIELD_REQUEST; + return ipmi::responseInvalidReservationId(); } - uint8_t eraseProgress = ipmi::sel::eraseComplete; - /* * Erasure status cannot be fetched from DBUS, so always return erasure * status as `erase completed`. */ - if (requestData->eraseOperation == ipmi::sel::getEraseStatus) + if (eraseOperation == ipmi::sel::getEraseStatus) { - std::memcpy(response, &eraseProgress, sizeof(eraseProgress)); - *data_len = sizeof(eraseProgress); - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint8_t>(ipmi::sel::eraseComplete)); } // Per the IPMI spec, need to cancel any reservation when the SEL is cleared @@ -431,24 +422,21 @@ ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, auto reply = bus.call(mapperCall); if (reply.is_method_error()) { - std::memcpy(response, &eraseProgress, sizeof(eraseProgress)); - *data_len = sizeof(eraseProgress); - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint8_t>(ipmi::sel::eraseComplete)); } reply.read(objectPaths); if (objectPaths.empty()) { - std::memcpy(response, &eraseProgress, sizeof(eraseProgress)); - *data_len = sizeof(eraseProgress); - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint8_t>(ipmi::sel::eraseComplete)); } } catch (const sdbusplus::exception::SdBusError& e) { - std::memcpy(response, &eraseProgress, sizeof(eraseProgress)); - *data_len = sizeof(eraseProgress); - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint8_t>(ipmi::sel::eraseComplete)); } std::string service; @@ -461,8 +449,7 @@ ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, catch (const std::runtime_error& e) { log<level::ERR>(e.what()); - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } for (const auto& iter : objectPaths) @@ -473,40 +460,32 @@ ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request, auto reply = bus.call(methodCall); if (reply.is_method_error()) { - *data_len = 0; - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } // Invalidate the cache of dbus entry objects. cache::paths.clear(); - std::memcpy(response, &eraseProgress, sizeof(eraseProgress)); - *data_len = sizeof(eraseProgress); - return IPMI_CC_OK; + return ipmi::responseSuccess( + static_cast<uint8_t>(ipmi::sel::eraseComplete)); } -ipmi_ret_t ipmi_storage_get_sel_time(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) +/** @brief implements the get SEL time command + * @returns IPMI completion code plus response data + * -current time + */ +ipmi::RspType<uint32_t> // current time + ipmiStorageGetSelTime() { - if (*data_len != 0) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - using namespace std::chrono; uint64_t host_time_usec = 0; - uint32_t resp = 0; std::stringstream hostTime; try { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); - sdbusplus::message::variant<uint64_t> value; + std::variant<uint64_t> value; // Get host time auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH, @@ -519,64 +498,48 @@ ipmi_ret_t ipmi_storage_get_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::ERR>("Error getting time", entry("SERVICE=%s", service.c_str()), entry("PATH=%s", HOST_TIME_PATH)); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } reply.read(value); - host_time_usec = variant_ns::get<uint64_t>(value); + host_time_usec = std::get<uint64_t>(value); } catch (InternalFailure& e) { log<level::ERR>(e.what()); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - catch (const std::runtime_error& e) + catch (const std::exception& e) { log<level::ERR>(e.what()); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - hostTime << "Host time:" << getTimeString(host_time_usec); + hostTime << "Host time:" + << duration_cast<seconds>(microseconds(host_time_usec)).count(); log<level::DEBUG>(hostTime.str().c_str()); // 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. - resp = duration_cast<seconds>(microseconds(host_time_usec)).count(); - resp = htole32(resp); - - // From the IPMI Spec 2.0, response should be a 32-bit value - *data_len = sizeof(resp); - - // Pack the actual response - std::memcpy(response, &resp, *data_len); - - return IPMI_CC_OK; + return ipmi::responseSuccess( + duration_cast<seconds>(microseconds(host_time_usec)).count()); } -ipmi_ret_t ipmi_storage_set_sel_time(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) +/** @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) { - if (*data_len != sizeof(uint32_t)) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } using namespace std::chrono; - ipmi_ret_t rc = IPMI_CC_OK; - uint32_t secs = *static_cast<uint32_t*>(request); - *data_len = 0; - - secs = le32toh(secs); - microseconds usec{seconds(secs)}; + microseconds usec{seconds(selDeviceTime)}; try { sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH); - sdbusplus::message::variant<uint64_t> value{usec.count()}; + std::variant<uint64_t> value{usec.count()}; // Set host time auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH, @@ -589,257 +552,243 @@ ipmi_ret_t ipmi_storage_set_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::ERR>("Error setting time", entry("SERVICE=%s", service.c_str()), entry("PATH=%s", HOST_TIME_PATH)); - rc = IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } catch (InternalFailure& e) { log<level::ERR>(e.what()); - rc = IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - catch (const std::runtime_error& e) + catch (const std::exception& e) { log<level::ERR>(e.what()); - rc = IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - return rc; + return ipmi::responseSuccess(); } -ipmi_ret_t ipmi_storage_reserve_sel(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) +/** @brief implements the reserve SEL command + * @returns IPMI completion code plus response data + * - SEL reservation ID. + */ +ipmi::RspType<uint16_t> ipmiStorageReserveSel() { - if (*data_len != 0) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - ipmi_ret_t rc = IPMI_CC_OK; - unsigned short selResID = reserveSel(); - - *data_len = sizeof(selResID); - - // Pack the actual response - std::memcpy(response, &selResID, *data_len); - - return rc; + return ipmi::responseSuccess(reserveSel()); } -ipmi_ret_t ipmi_storage_add_sel(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) +/** @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, uint32_t timeStamp, + uint16_t generatorID, uint8_t evmRev, uint8_t sensorType, + uint8_t sensorNumber, uint8_t eventDir, + std::array<uint8_t, eventDataSize> eventData) { - if (*data_len != sizeof(ipmi_add_sel_request_t)) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - ipmi_ret_t rc = IPMI_CC_OK; - ipmi_add_sel_request_t* p = (ipmi_add_sel_request_t*)request; - uint16_t recordid; - // Per the IPMI spec, need to cancel the reservation when a SEL entry is // added cancelSELReservation(); - - recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2]; - - *data_len = sizeof(recordid); - - // Pack the actual response - std::memcpy(response, &p->eventdata[1], 2); - // 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; - if (p->recordtype == procedureType) + if (recordType == procedureType) { // In the OEM record type 0xDE, byte 11 in the SEL record indicate the // procedure number. - createProcedureLogEntry(p->sensortype); + createProcedureLogEntry(sensorType); } - return rc; + return ipmi::responseSuccess(recordID); } -// Read FRU info area -ipmi_ret_t ipmi_storage_get_fru_inv_area_info( - 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) +/** @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(uint8_t fruID) { - ipmi_ret_t rc = IPMI_CC_OK; - const FruInvenAreaInfoRequest* reqptr = - reinterpret_cast<const FruInvenAreaInfoRequest*>(request); - auto iter = frus.find(reqptr->fruID); + auto iter = frus.find(fruID); if (iter == frus.end()) { - *data_len = 0; - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } try { - const auto& fruArea = getFruAreaData(reqptr->fruID); - auto size = static_cast<uint16_t>(fruArea.size()); - FruInvenAreaInfoResponse resp; - resp.sizems = size >> 8; - resp.sizels = size; - resp.access = static_cast<uint8_t>(AccessMode::bytes); - - *data_len = sizeof(resp); - - // Pack the actual response - std::memcpy(response, &resp, *data_len); + return ipmi::responseSuccess( + static_cast<uint16_t>(getFruAreaData(fruID).size()), + static_cast<uint8_t>(AccessMode::bytes)); } catch (const InternalFailure& e) { - rc = IPMI_CC_UNSPECIFIED_ERROR; - *data_len = 0; log<level::ERR>(e.what()); + return ipmi::responseUnspecifiedError(); } - return rc; } -// Read FRU data -ipmi_ret_t ipmi_storage_read_fru_data(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) +/**@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) { - ipmi_ret_t rc = IPMI_CC_OK; - const ReadFruDataRequest* reqptr = - reinterpret_cast<const ReadFruDataRequest*>(request); - auto resptr = reinterpret_cast<ReadFruDataResponse*>(response); + if (fruDeviceId == 0xFF) + { + return ipmi::responseInvalidFieldRequest(); + } - auto iter = frus.find(reqptr->fruID); + auto iter = frus.find(fruDeviceId); if (iter == frus.end()) { - *data_len = 0; - return IPMI_CC_SENSOR_INVALID; + return ipmi::responseSensorInvalid(); } - auto offset = - static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS); try { - const auto& fruArea = getFruAreaData(reqptr->fruID); + const auto& fruArea = getFruAreaData(fruDeviceId); auto size = fruArea.size(); if (offset >= size) { - return IPMI_CC_PARM_OUT_OF_RANGE; + return ipmi::responseParmOutOfRange(); } // Write the count of response data. - if ((offset + reqptr->count) <= size) + uint8_t returnCount; + if ((offset + readCount) <= size) { - resptr->count = reqptr->count; + returnCount = readCount; } else { - resptr->count = size - offset; + returnCount = size - offset; } - std::copy((fruArea.begin() + offset), - (fruArea.begin() + offset + resptr->count), resptr->data); + std::vector<uint8_t> fruData((fruArea.begin() + offset), + (fruArea.begin() + offset + returnCount)); - *data_len = resptr->count + 1; // additional one byte for count + return ipmi::responseSuccess(returnCount, fruData); } catch (const InternalFailure& e) { - rc = IPMI_CC_UNSPECIFIED_ERROR; - *data_len = 0; log<level::ERR>(e.what()); + return ipmi::responseUnspecifiedError(); } - return rc; } -ipmi_ret_t ipmi_get_repository_info(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<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 auto sdrVersion = 0x51; - auto responseData = reinterpret_cast<GetRepositoryInfoResponse*>(response); - - std::memset(responseData, 0, sizeof(GetRepositoryInfoResponse)); - responseData->sdrVersion = sdrVersion; + 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; - uint16_t records = frus.size() + sensors.size(); - responseData->recordCountMs = records >> 8; - responseData->recordCountLs = records; + uint16_t records = frus.size() + ipmi::sensor::sensors.size(); - responseData->freeSpace[0] = 0xFF; - responseData->freeSpace[1] = 0xFF; - - *data_len = sizeof(GetRepositoryInfoResponse); - - return IPMI_CC_OK; + return ipmi::responseSuccess(sdrVersion, records, freeSpace, + additionTimestamp, deletionTimestamp, + operationSupport); } void register_netfn_storage_functions() { - // <Wildcard Command> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, - ipmi_storage_wildcard, PRIVILEGE_USER); - // <Get SEL Info> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL, - getSELInfo, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User, + ipmiStorageGetSelInfo); // <Get SEL Time> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME, NULL, - ipmi_storage_get_sel_time, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdGetSelTime, ipmi::Privilege::User, + ipmiStorageGetSelTime); // <Set SEL Time> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME, NULL, - ipmi_storage_set_sel_time, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdSetSelTime, + ipmi::Privilege::Operator, ipmiStorageSetSelTime); // <Reserve SEL> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL, - ipmi_storage_reserve_sel, PRIVILEGE_USER); - + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdReserveSel, ipmi::Privilege::User, + ipmiStorageReserveSel); // <Get SEL Entry> ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, getSELEntry, PRIVILEGE_USER); // <Delete SEL Entry> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_DELETE_SEL, NULL, - deleteSELEntry, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdDeleteSelEntry, + ipmi::Privilege::Operator, deleteSELEntry); // <Add SEL Entry> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL, - ipmi_storage_add_sel, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdAddSelEntry, + ipmi::Privilege::Operator, ipmiStorageAddSEL); + // <Clear SEL> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL, NULL, clearSEL, - PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdClearSel, ipmi::Privilege::Operator, + clearSEL); + // <Get FRU Inventory Area Info> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_FRU_INV_AREA_INFO, NULL, - ipmi_storage_get_fru_inv_area_info, - PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdGetFruInventoryAreaInfo, + ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo); - // <Add READ FRU Data - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL, - ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR); + // <READ FRU Data> + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdReadFruData, + ipmi::Privilege::Operator, ipmiStorageReadFruData); // <Get Repository Info> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO, - nullptr, ipmi_get_repository_info, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdGetSdrRepositoryInfo, + ipmi::Privilege::User, ipmiGetRepositoryInfo); // <Reserve SDR Repository> - ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, nullptr, - ipmi_sen_reserve_sdr, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage, + ipmi::storage::cmdReserveSdrRepository, + ipmi::Privilege::User, ipmiSensorReserveSdr); // <Get SDR> ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr, diff --git a/storagehandler.hpp b/storagehandler.hpp index bcc6c7d..6c91905 100644 --- a/storagehandler.hpp +++ b/storagehandler.hpp @@ -21,69 +21,3 @@ enum ipmi_netfn_storage_cmds IPMI_CMD_SET_SEL_TIME = 0x49, }; - -struct ipmi_add_sel_request_t -{ - - uint8_t recordid[2]; - uint8_t recordtype; - uint8_t timestamp[4]; - uint8_t generatorid[2]; - uint8_t evmrev; - uint8_t sensortype; - uint8_t sensornumber; - uint8_t eventdir; - uint8_t eventdata[3]; -}; - -/** - * @struct Read FRU Data command request data - */ -struct ReadFruDataRequest -{ - uint8_t fruID; ///< FRU Device ID. FFh = reserved - uint8_t offsetLS; ///< FRU Inventory Offset to read, LS Byte - uint8_t offsetMS; ///< FRU Inventory Offset ro read, MS Byte - uint8_t count; ///< Count to read -} __attribute__((packed)); - -/** - * @struct Read FRU Data command response data - */ -struct ReadFruDataResponse -{ - uint8_t count; ///< Response data Count. - uint8_t data[]; ///< Response data. -} __attribute__((packed)); - -/** - * @struct Get FRU inventory area info command request data - */ -struct FruInvenAreaInfoRequest -{ - uint8_t fruID; ///< FRU Device ID. FFH = reserved. -} __attribute__((packed)); - -/** - * @struct Get FRU inventory area info command response - */ -struct FruInvenAreaInfoResponse -{ - uint8_t sizels; ///< Fru Inventory area size in bytes, LS Byte - uint8_t sizems; ///< Fru Inventory are size in bytes, MS Byte - uint8_t access; ///< 0b Devices is accessed by bytes, 1b - by words -} __attribute__((packed)); - -/** - * @struct Get Repository info command response - */ -struct GetRepositoryInfoResponse -{ - uint8_t sdrVersion; //< SDR version - uint8_t recordCountLs; //< Record count LS byte - uint8_t recordCountMs; //< Record count MS bte - uint8_t freeSpace[2]; //< Free space in bytes, LS first - uint8_t additionTimestamp[4]; //< Most recent addition timestamp LS first - uint8_t deletionTimestamp[4]; //< Most recent deletion timestamp LS first - uint8_t operationSupport; //< Operation support -} __attribute__((packed)); diff --git a/systemintfcmds.cpp b/systemintfcmds.cpp index d298abd..689b465 100644 --- a/systemintfcmds.cpp +++ b/systemintfcmds.cpp @@ -17,17 +17,6 @@ using namespace sdbusplus::xyz::openbmc_project::Control::server; using cmdManagerPtr = std::unique_ptr<phosphor::host::command::Manager>; extern cmdManagerPtr& ipmid_get_host_cmd_manager(); -// global enables -// bit0 - Message Receive Queue enable -// bit1 - Enable Event Message Buffer Full Interrupt -// bit2 - Enable Event Message Buffer -// bit3 - Enable System Event Logging -// bit4 - reserved -// bit5-7 - OEM 0~2 enables -static constexpr uint8_t selEnable = 0x08; -static constexpr uint8_t recvMsgQueueEnable = 0x01; -static constexpr uint8_t globalEnablesDefault = selEnable | recvMsgQueueEnable; - //------------------------------------------------------------------- // Called by Host post response from Get_Message_Flags //------------------------------------------------------------------- @@ -71,71 +60,68 @@ ipmi_ret_t ipmi_app_read_event(ipmi_netfn_t netfn, ipmi_cmd_t cmd, // Called by Host on seeing a SMS_ATN bit set. Return a hardcoded // value of 0x2 indicating we need Host read some data. //------------------------------------------------------------------- -ipmi_ret_t ipmi_app_get_msg_flags(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<uint8_t> ipmiAppGetMessageFlags() { - // Generic return from IPMI commands. - ipmi_ret_t rc = IPMI_CC_OK; - // 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. - // For now, it is not supported. - - uint8_t set_event_msg_buffer_full = 0x0; - *data_len = sizeof(set_event_msg_buffer_full); - - // Pack the actual response - std::memcpy(response, &set_event_msg_buffer_full, *data_len); - - return rc; + // This path is used to communicate messages to the host + // from within the phosphor::host::command::Manager + constexpr uint8_t setEventMsgBufferFull = 0x2; + return ipmi::responseSuccess(setEventMsgBufferFull); } -ipmi_ret_t ipmi_app_get_bmc_global_enables(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<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() { - ipmi_ret_t rc = IPMI_CC_OK; - if (0 != *data_len) - { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - *data_len = sizeof(globalEnablesDefault); - *reinterpret_cast<uint8_t*>(response) = globalEnablesDefault; - return rc; + return ipmi::responseSuccess(true, false, false, true, 0, false, false, + false); } -ipmi_ret_t ipmi_app_set_bmc_global_enables(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<> ipmiAppSetBMCGlobalEnable( + ipmi::Context::ptr ctx, bool receiveMessageQueueInterruptEnabled, + bool eventMessageBufferFullInterruptEnabled, bool eventMessageBufferEnabled, + bool systemEventLogEnable, uint1_t reserved, bool OEM0Enabled, + bool OEM1Enabled, bool OEM2Enabled) { - ipmi_ret_t rc = IPMI_CC_OK; + ipmi::ChannelInfo chInfo; - uint8_t reqMask = *reinterpret_cast<uint8_t*>(request); - if (sizeof(reqMask) != *data_len) + if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess) { - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; + phosphor::logging::log<phosphor::logging::level::ERR>( + "Failed to get Channel Info", + phosphor::logging::entry("CHANNEL=%d", ctx->channel)); + return ipmi::responseUnspecifiedError(); + } + + if (chInfo.mediumType != + static_cast<uint8_t>(ipmi::EChannelMediumType::systemInterface)) + { + phosphor::logging::log<phosphor::logging::level::ERR>( + "Error - supported only in system interface"); + return ipmi::responseCommandNotAvailable(); } - *data_len = 0; // 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 (reqMask != (selEnable | recvMsgQueueEnable)) + if (!receiveMessageQueueInterruptEnabled || !systemEventLogEnable || + eventMessageBufferFullInterruptEnabled || eventMessageBufferEnabled || + OEM0Enabled || OEM1Enabled || OEM2Enabled || reserved) { - return IPMI_CC_INVALID_FIELD_REQUEST; + return ipmi::responseInvalidFieldRequest(); } - return rc; + + return ipmi::responseSuccess(); } namespace @@ -155,16 +141,19 @@ void register_netfn_app_functions() ipmi_app_read_event, SYSTEM_INTERFACE); // <Set BMC Global Enables> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_BMC_GLOBAL_ENABLES, NULL, - ipmi_app_set_bmc_global_enables, SYSTEM_INTERFACE); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdSetBmcGlobalEnables, + ipmi::Privilege::Admin, ipmiAppSetBMCGlobalEnable); // <Get BMC Global Enables> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_BMC_GLOBAL_ENABLES, NULL, - ipmi_app_get_bmc_global_enables, SYSTEM_INTERFACE); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetBmcGlobalEnables, + ipmi::Privilege::User, ipmiAppGetBMCGlobalEnable); // <Get Message Flags> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_MSG_FLAGS, NULL, - ipmi_app_get_msg_flags, SYSTEM_INTERFACE); + 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'; diff --git a/test/Makefile.am b/test/Makefile.am index e510230..0faeda7 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -26,6 +26,11 @@ AM_LDFLAGS = $(GTEST_MAIN_LIBS) $(OESDK_TESTCASE_FLAGS) check_PROGRAMS = TESTS = $(check_PROGRAMS) +entitymap_json_unittest_SOURCES = entitymap_json_unittest.cpp +entitymap_json_unittest_LDADD = $(top_builddir)/entity_map_json.o -lgmock + +check_PROGRAMS += entitymap_json_unittest + # Build/add sample_unittest to test suite sample_unittest_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS) sample_unittest_CXXFLAGS = $(PTHREAD_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) \ @@ -67,3 +72,21 @@ message_unittest_SOURCES = \ %reldir%/message/unpack.cpp \ %reldir%/message/pack.cpp check_PROGRAMS += %reldir%/message_unittest + +# Build/add closesession_unittest to test suite +session_unittest_CPPFLAGS = \ + -Igtest \ + $(GTEST_CPPFLAGS) \ + $(AM_CPPFLAGS) +session_unittest_CXXFLAGS = \ + $(PTHREAD_CFLAGS) \ + $(CODE_COVERAGE_CXXFLAGS) \ + $(CODE_COVERAGE_CFLAGS) +session_unittest_LDFLAGS = \ + -lgtest_main \ + -lgtest \ + -pthread \ + $(OESDK_TESTCASE_FLAGS) \ + $(CODE_COVERAGE_LDFLAGS) +session_unittest_SOURCES = %reldir%/session/closesession_unittest.cpp +check_PROGRAMS += %reldir%/session_unittest diff --git a/test/entitymap_json_unittest.cpp b/test/entitymap_json_unittest.cpp new file mode 100644 index 0000000..a380a9e --- /dev/null +++ b/test/entitymap_json_unittest.cpp @@ -0,0 +1,223 @@ +#include "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/message/pack.cpp b/test/message/pack.cpp index d0a738a..9e88f2b 100644 --- a/test/message/pack.cpp +++ b/test/message/pack.cpp @@ -235,6 +235,27 @@ TEST(PackBasics, VectorUint8) 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, std::vector<uint8_t>{0b1}); +} + +TEST(PackBasics, StringView) +{ + ipmi::message::Payload p; + EXPECT_EQ(p.pack(std::string_view{"\x24\x30\x11"}), 0); + EXPECT_EQ(p.raw, std::vector<uint8_t>({0x24, 0x30, 0x11})); +} + +TEST(PackBasics, StringViewUnaligned) +{ + ipmi::message::Payload p; + EXPECT_EQ(p.pack(true, std::string_view{"abc"}), 1); + EXPECT_EQ(p.raw, std::vector<uint8_t>({0b1})); +} + TEST(PackBasics, OptionalEmpty) { // an optional will only pack if the value is present @@ -261,6 +282,56 @@ TEST(PackBasics, OptionalContainsValue) 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, std::vector<uint8_t>({0b1, 0x24, 0x30})); +} + +TEST(PackBasics, PayloadUnaligned) +{ + ipmi::message::Payload p; + EXPECT_EQ(p.pack(true, ipmi::message::Payload({0x24})), 1); + EXPECT_EQ(p.raw, std::vector<uint8_t>({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, std::vector<uint8_t>({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, std::vector<uint8_t>({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, std::vector<uint8_t>({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, std::vector<uint8_t>({0b1})); +} + TEST(PackAdvanced, Uints) { // all elements will be processed in order, with each multi-byte diff --git a/test/message/payload.cpp b/test/message/payload.cpp index 9d20ff0..56d8d41 100644 --- a/test/message/payload.cpp +++ b/test/message/payload.cpp @@ -13,8 +13,13 @@ * 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> @@ -306,3 +311,132 @@ TEST(PayloadRequest, PartialPayload) uint32_t k2 = 0x02008604; ASSERT_EQ(v2, k2); } + +std::vector<std::string> logs; + +extern "C" { +int sd_journal_send(const char* format, ...) +{ + logs.push_back(format); + return 0; +} + +int sd_journal_send_with_location(const char* file, const char* line, + const char* func, const char* format, ...) +{ + logs.push_back(format); + 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 index 611a5fe..7d69218 100644 --- a/test/message/unpack.cpp +++ b/test/message/unpack.cpp @@ -660,8 +660,8 @@ TEST(Vectors, VectorUint32NonIntegralBytes) 0x99, 0x77, 0x55, 0x33, 0x78, 0x56, 0x34}; ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(i)); std::vector<uint32_t> v; - // check that the number of bytes matches - ASSERT_NE(p.unpack(v), 0); + // 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 @@ -686,6 +686,51 @@ TEST(Vectors, VectorUint8) ASSERT_EQ(v, k); } +TEST(Vectors, VectorEmptyOk) +{ + // an empty input vector to show that unpacking elements is okay + std::vector<uint8_t> i{}; + ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(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 + std::vector<uint8_t> i = {0x02, 0x00, 0x86, 0x04}; + ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(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 + std::vector<uint8_t> i = {0x02, 0x00, 0x86, 0x04, 0xb4}; + ipmi::message::Payload p(std::forward<std::vector<uint8_t>>(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) {} @@ -716,7 +761,7 @@ TEST(UnpackAdvanced, OptionalInsufficientBytes) 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 = {{0, 0}}; + std::optional<std::tuple<uint8_t, uint32_t>> k; // check that the bytes were correctly unpacked (in byte order) ASSERT_EQ(v, k); } 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/transporthandler.cpp b/transporthandler.cpp index 59d933a..a3b3c35 100644 --- a/transporthandler.cpp +++ b/transporthandler.cpp @@ -1,1002 +1,1948 @@ -#include "transporthandler.hpp" - #include "app/channel.hpp" -#include "user_channel/channel_layer.hpp" #include <arpa/inet.h> +#include <netinet/ether.h> -#include <chrono> -#include <filesystem> +#include <array> +#include <bitset> +#include <cinttypes> +#include <cstdint> +#include <cstring> #include <fstream> +#include <functional> #include <ipmid/api.hpp> +#include <ipmid/message.hpp> +#include <ipmid/message/types.hpp> +#include <ipmid/types.hpp> #include <ipmid/utils.hpp> +#include <optional> #include <phosphor-logging/elog-errors.hpp> +#include <phosphor-logging/elog.hpp> #include <phosphor-logging/log.hpp> -#include <sdbusplus/message/types.hpp> -#include <sdbusplus/timer.hpp> +#include <sdbusplus/bus.hpp> +#include <sdbusplus/exception.hpp> #include <string> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <user_channel/channel_layer.hpp> +#include <utility> +#include <vector> #include <xyz/openbmc_project/Common/error.hpp> +#include <xyz/openbmc_project/Network/IP/server.hpp> +#include <xyz/openbmc_project/Network/Neighbor/server.hpp> + +using phosphor::logging::commit; +using phosphor::logging::elog; +using phosphor::logging::entry; +using phosphor::logging::level; +using phosphor::logging::log; +using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; +using sdbusplus::xyz::openbmc_project::Network::server::IP; +using sdbusplus::xyz::openbmc_project::Network::server::Neighbor; -#define SYSTEMD_NETWORKD_DBUS 1 +namespace cipher +{ -#ifdef SYSTEMD_NETWORKD_DBUS -#include <mapper.h> -#include <systemd/sd-bus.h> -#endif +std::vector<uint8_t> getCipherList() +{ + std::vector<uint8_t> cipherList; -// timer for network changes -std::unique_ptr<phosphor::Timer> networkTimer = nullptr; + std::ifstream jsonFile(cipher::configFile); + if (!jsonFile.is_open()) + { + log<level::ERR>("Channel Cipher suites file not found"); + elog<InternalFailure>(); + } -const int SIZE_MAC = 18; // xx:xx:xx:xx:xx:xx -constexpr auto ipv4Protocol = "xyz.openbmc_project.Network.IP.Protocol.IPv4"; + auto data = Json::parse(jsonFile, nullptr, false); + if (data.is_discarded()) + { + log<level::ERR>("Parsing channel cipher suites JSON failed"); + elog<InternalFailure>(); + } + + // Byte 1 is reserved + cipherList.push_back(0x00); -std::map<int, std::unique_ptr<struct ChannelConfig_t>> channelConfig; + for (const auto& record : data) + { + cipherList.push_back(record.value(cipher, 0)); + } -using namespace phosphor::logging; -using namespace sdbusplus::xyz::openbmc_project::Common::Error; + return cipherList; +} +} // namespace cipher -namespace fs = std::filesystem; -namespace variant_ns = sdbusplus::message::variant_ns; +namespace ipmi +{ +namespace transport +{ -void register_netfn_transport_functions() __attribute__((constructor)); +// 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 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; + +// D-Bus Network Daemon definitions +constexpr auto PATH_ROOT = "/xyz/openbmc_project/network"; +constexpr auto PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config"; + +constexpr auto INTF_SYSTEMCONFIG = + "xyz.openbmc_project.Network.SystemConfiguration"; +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 Generic paramters for different address families */ +template <int family> +struct AddrFamily +{ +}; -struct ChannelConfig_t* getChannelConfig(int channel) +/** @brief Parameter specialization for IPv4 */ +template <> +struct AddrFamily<AF_INET> { - auto item = channelConfig.find(channel); - if (item == channelConfig.end()) - { - channelConfig[channel] = std::make_unique<struct ChannelConfig_t>(); - } + using addr = in_addr; + static constexpr auto protocol = 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 = in6_addr; + static constexpr auto protocol = IP::Protocol::IPv6; + static constexpr size_t maxStrLen = INET6_ADDRSTRLEN; + static constexpr uint8_t defaultPrefix = 128; + static constexpr char propertyGateway[] = "DefaultGateway6"; +}; + +/** @brief Valid address origins for IPv4 */ +const std::unordered_set<IP::AddressOrigin> originsV4 = { + IP::AddressOrigin::Static, + IP::AddressOrigin::DHCP, +}; + +/** @brief Valid address origins for IPv6 */ +const std::unordered_set<IP::AddressOrigin> originsV6Static = { + IP::AddressOrigin::Static}; +const std::unordered_set<IP::AddressOrigin> originsV6Dynamic = { + IP::AddressOrigin::DHCP, + IP::AddressOrigin::SLAAC, +}; + +/** @brief Interface IP Address configuration parameters */ +template <int family> +struct IfAddr +{ + std::string path; + typename AddrFamily<family>::addr address; + IP::AddressOrigin origin; + uint8_t prefix; +}; + +/** @brief Interface Neighbor configuration parameters */ +template <int family> +struct IfNeigh +{ + std::string path; + typename AddrFamily<family>::addr ip; + ether_addr mac; +}; - return channelConfig[channel].get(); +/** @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, + IPFamilySupport = 50, + IPFamilyEnables = 51, + IPv6Status = 55, + IPv6StaticAddresses = 56, + IPv6DynamicAddresses = 59, + IPv6RouterControl = 64, + IPv6StaticRouter1IP = 65, + IPv6StaticRouter1MAC = 66, + IPv6StaticRouter1PrefixLength = 67, + IPv6StaticRouter1PrefixValue = 68, +}; + +static constexpr uint8_t oemCmdStart = 192; +static constexpr uint8_t oemCmdEnd = 255; + +/** @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 + +/** @brief A trivial helper used to determine if two PODs are equal + * + * @params[in] a - The first object to compare + * @params[in] b - The second object to compare + * @return True if the objects are the same bytewise + */ +template <typename T> +bool equal(const T& a, const T& b) +{ + static_assert(std::is_trivially_copyable_v<T>); + return std::memcmp(&a, &b, sizeof(T)) == 0; } -// Helper Function to get IP Address/NetMask/Gateway/MAC Address from Network -// Manager or Cache based on Set-In-Progress State -ipmi_ret_t getNetworkData(uint8_t lan_param, uint8_t* data, int channel) +/** @brief Copies bytes from an array into a trivially copyable container + * + * @params[out] t - The container receiving the data + * @params[in] bytes - The data to copy + */ +template <size_t N, typename T> +void copyInto(T& t, const std::array<uint8_t, N>& bytes) { - ipmi_ret_t rc = IPMI_CC_OK; - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + static_assert(std::is_trivially_copyable_v<T>); + static_assert(N == sizeof(T)); + std::memcpy(&t, bytes.data(), bytes.size()); +} + +/** @brief Gets a generic view of the bytes in the input container + * + * @params[in] t - The data to reference + * @return A string_view referencing the bytes in the container + */ +template <typename T> +std::string_view dataRef(const T& t) +{ + static_assert(std::is_trivially_copyable_v<T>); + return {reinterpret_cast<const char*>(&t), sizeof(T)}; +} - auto ethdevice = ipmi::getChannelName(channel); - // if ethdevice is an empty string they weren't expecting this channel. - if (ethdevice.empty()) +/** @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::bus& bus, + uint8_t channel) +{ + auto ifname = getChannelName(channel); + if (ifname.empty()) { - // TODO: return error from getNetworkData() - return IPMI_CC_INVALID_FIELD_REQUEST; + return std::nullopt; } - auto ethIP = ethdevice + "/" + ipmi::network::IP_TYPE; - auto channelConf = getChannelConfig(channel); - try + // Enumerate all VLAN + ETHERNET interfaces + auto req = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, + "GetSubTree"); + req.append(PATH_ROOT, 0, + std::vector<std::string>{INTF_VLAN, INTF_ETHERNET}); + auto reply = bus.call(req); + ObjectTree objs; + reply.read(objs); + + ChannelParams params; + for (const auto& [path, impls] : objs) { - switch (static_cast<LanParam>(lan_param)) + if (path.find(ifname) == path.npos) + { + continue; + } + for (const auto& [service, intfs] : impls) { - case LanParam::IP: + bool vlan = false; + bool ethernet = false; + for (const auto& intf : intfs) { - std::string ipaddress; - if (channelConf->lan_set_in_progress == SET_COMPLETE) + if (intf == INTF_VLAN) { - try - { - auto ipObjectInfo = - ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - auto properties = ipmi::getAllDbusProperties( - bus, ipObjectInfo.second, ipObjectInfo.first, - ipmi::network::IP_INTERFACE); - - ipaddress = - variant_ns::get<std::string>(properties["Address"]); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do. - } + vlan = true; } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) + else if (intf == INTF_ETHERNET) { - ipaddress = channelConf->ipaddr; + ethernet = true; } - - inet_pton(AF_INET, ipaddress.c_str(), - reinterpret_cast<void*>(data)); } - break; - - case LanParam::IPSRC: + if (params.service.empty() && (vlan || ethernet)) + { + params.service = service; + } + if (params.ifPath.empty() && !vlan && ethernet) + { + params.ifPath = path; + } + if (params.logicalPath.empty() && vlan) { - std::string networkInterfacePath; + params.logicalPath = path; + } + } + } - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - ipmi::ObjectTree ancestorMap; - // if the system is having ip object,then - // get the IP object. - auto ipObject = ipmi::getDbusObject( - bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - // Get the parent interface of the IP object. - try - { - ipmi::InterfaceList interfaces; - interfaces.emplace_back( - ipmi::network::ETHERNET_INTERFACE); - - ancestorMap = ipmi::getAllAncestors( - bus, ipObject.first, std::move(interfaces)); - } - catch (InternalFailure& e) - { - // if unable to get the parent interface - // then commit the error and return. - log<level::ERR>( - "Unable to get the parent interface", - entry("PATH=%s", ipObject.first.c_str()), - entry("INTERFACE=%s", - ipmi::network::ETHERNET_INTERFACE)); - break; - } - // for an ip object there would be single parent - // interface. - networkInterfacePath = ancestorMap.begin()->first; - } - catch (InternalFailure& e) - { - // if there is no ip configured on the system,then - // get the network interface object. - auto networkInterfaceObject = ipmi::getDbusObject( - bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); + // 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; + } - networkInterfacePath = networkInterfaceObject.first; - } + params.id = channel; + params.ifname = std::move(ifname); + return std::move(params); +} - auto variant = ipmi::getDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled"); +/** @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::bus& bus, uint8_t channel) +{ + auto params = maybeGetChannelParams(bus, channel); + if (!params) + { + log<level::ERR>("Failed to get channel params", + entry("CHANNEL=%" PRIu8, channel)); + elog<InternalFailure>(); + } + return std::move(*params); +} - auto dhcpEnabled = variant_ns::get<bool>(variant); - // As per IPMI spec 2=>DHCP, 1=STATIC - auto ipsrc = dhcpEnabled ? ipmi::network::IPOrigin::DHCP - : ipmi::network::IPOrigin::STATIC; +/** @brief Wraps the phosphor logging method to insert some additional metadata + * + * @param[in] params - The parameters for the channel + * ... + */ +template <auto level, typename... Args> +auto logWithChannel(const ChannelParams& params, Args&&... args) +{ + return log<level>(std::forward<Args>(args)..., + entry("CHANNEL=%d", params.id), + entry("IFNAME=%s", params.ifname.c_str())); +} +template <auto level, typename... Args> +auto logWithChannel(const std::optional<ChannelParams>& params, Args&&... args) +{ + if (params) + { + return logWithChannel<level>(*params, std::forward<Args>(args)...); + } + return log<level>(std::forward<Args>(args)...); +} - std::memcpy(data, &ipsrc, ipmi::network::IPSRC_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - std::memcpy(data, &(channelConf->ipsrc), - ipmi::network::IPSRC_SIZE_BYTE); - } - } - break; +/** @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::bus bus(ipmid_get_sd_bus_connection()); + auto params = getChannelParams(bus, channel); + return std::invoke(func, bus, params, std::forward<Args>(args)...); +} - case LanParam::SUBNET: - { - unsigned long mask{}; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto ipObjectInfo = - ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIP); - - auto properties = ipmi::getAllDbusProperties( - bus, ipObjectInfo.second, ipObjectInfo.first, - ipmi::network::IP_INTERFACE); - - auto prefix = variant_ns::get<uint8_t>( - properties["PrefixLength"]); - mask = ipmi::network::MASK_32_BIT; - mask = htonl(mask << (ipmi::network::BITS_32 - prefix)); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } - std::memcpy(data, &mask, - ipmi::network::IPV4_ADDRESS_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - inet_pton(AF_INET, channelConf->netmask.c_str(), - reinterpret_cast<void*>(data)); - } - } - break; +/** @brief Determines if the ethernet interface is using DHCP + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @return True if DHCP is enabled, false otherwise + */ +bool getDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params) +{ + return std::get<bool>(getDbusProperty( + bus, params.service, params.logicalPath, INTF_ETHERNET, "DHCPEnabled")); +} - case LanParam::GATEWAY: - { - std::string gateway; +/** @brief Sets the system value for DHCP on the given interface + * + * @param[in] bus - The bus object used for lookups + * @param[in] params - The parameters for the channel + * @param[in] on - Whether or not to enable DHCP + */ +void setDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params, + bool on) +{ + setDbusProperty(bus, params.service, params.logicalPath, INTF_ETHERNET, + "DHCPEnabled", on); +} - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto systemObject = ipmi::getDbusObject( - bus, ipmi::network::SYSTEMCONFIG_INTERFACE, - ipmi::network::ROOT); +/** @brief Converts a human readable MAC string into MAC bytes + * + * @param[in] mac - The MAC string + * @return MAC in bytes + */ +ether_addr stringToMAC(const char* mac) +{ + const ether_addr* ret = ether_aton(mac); + if (ret == nullptr) + { + log<level::ERR>("Invalid MAC Address", entry("MAC=%s", mac)); + elog<InternalFailure>(); + } + return *ret; +} - auto systemProperties = ipmi::getAllDbusProperties( - bus, systemObject.second, systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE); +/** @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 + */ +ether_addr getMACProperty(sdbusplus::bus::bus& bus, const ChannelParams& params) +{ + auto macStr = std::get<std::string>(getDbusProperty( + bus, params.service, params.ifPath, INTF_MAC, "MACAddress")); + return stringToMAC(macStr.c_str()); +} - gateway = variant_ns::get<std::string>( - systemProperties["DefaultGateway"]); - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - gateway = channelConf->gateway; - } +/** @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::bus& bus, const ChannelParams& params, + const ether_addr& mac) +{ + std::string macStr = ether_ntoa(&mac); + setDbusProperty(bus, params.service, params.ifPath, INTF_MAC, "MACAddress", + macStr); +} - inet_pton(AF_INET, gateway.c_str(), - reinterpret_cast<void*>(data)); - } - break; +/** @brief Turns an IP address string into the network byte order form + * NOTE: This version strictly validates family matches + * + * @param[in] address - The string form of the address + * @return A network byte order address or none if conversion failed + */ +template <int family> +std::optional<typename AddrFamily<family>::addr> + maybeStringToAddr(const char* address) +{ + typename AddrFamily<family>::addr ret; + if (inet_pton(family, address, &ret) == 1) + { + return ret; + } + return std::nullopt; +} - case LanParam::MAC: - { - std::string macAddress; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - auto macObjectInfo = - ipmi::getDbusObject(bus, ipmi::network::MAC_INTERFACE, - ipmi::network::ROOT, ethdevice); +/** @brief Turns an IP address string into the network byte order form + * NOTE: This version strictly validates family matches + * + * @param[in] address - The string form of the address + * @return A network byte order address + */ +template <int family> +typename AddrFamily<family>::addr stringToAddr(const char* address) +{ + auto ret = maybeStringToAddr<family>(address); + if (!ret) + { + log<level::ERR>("Failed to convert IP Address", + entry("FAMILY=%d", family), + entry("ADDRESS=%s", address)); + elog<InternalFailure>(); + } + return *ret; +} - auto variant = ipmi::getDbusProperty( - bus, macObjectInfo.second, macObjectInfo.first, - ipmi::network::MAC_INTERFACE, "MACAddress"); +/** @brief Turns an IP address in network byte order into a string + * + * @param[in] address - The string form of the address + * @return A network byte order address + */ +template <int family> +std::string addrToString(const typename AddrFamily<family>::addr& address) +{ + std::string ret(AddrFamily<family>::maxStrLen, '\0'); + inet_ntop(family, &address, ret.data(), ret.size()); + ret.resize(strlen(ret.c_str())); + return ret; +} - macAddress = variant_ns::get<std::string>(variant); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - macAddress = channelConf->macAddress; - } +/** @brief Retrieves the current gateway for the address family on the system + * NOTE: The gateway is currently system wide and not per channel + * + * @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::bus& bus, const ChannelParams& params) +{ + auto gatewayStr = std::get<std::string>(getDbusProperty( + bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG, + AddrFamily<family>::propertyGateway)); + if (gatewayStr.empty()) + { + return std::nullopt; + } + return stringToAddr<family>(gatewayStr.c_str()); +} - sscanf(macAddress.c_str(), ipmi::network::MAC_ADDRESS_FORMAT, - (data), (data + 1), (data + 2), (data + 3), (data + 4), - (data + 5)); - } - break; +/** @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::bus& bus, const ChannelParams& params, + const char* intf) : + bus(bus), + params(params), intf(intf), + objs(getAllDbusObjects(bus, params.logicalPath, intf, "")) + { + } - case LanParam::VLAN: - { - uint16_t vlanID{}; - if (channelConf->lan_set_in_progress == SET_COMPLETE) - { - try - { - auto ipObjectInfo = ipmi::getIPObject( - bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ipmi::network::IP_TYPE); + class iterator : public ObjectTree::const_iterator + { + public: + using value_type = PropertiesCache::value_type; - vlanID = static_cast<uint16_t>( - ipmi::network::getVLAN(ipObjectInfo.first)); + 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*(); + } - vlanID = htole16(vlanID); + private: + ObjectLookupCache& container; + PropertiesCache::iterator ret; + }; - if (vlanID) - { - // Enable the 16th bit - vlanID |= htole16(ipmi::network::VLAN_ENABLE_MASK); - } - } - // ignore the exception, as it is a valid condition that - // the system is not configured with any IP. - catch (InternalFailure& e) - { - // nothing to do - } + iterator begin() noexcept + { + return iterator(objs.begin(), *this); + } - std::memcpy(data, &vlanID, ipmi::network::VLAN_SIZE_BYTE); - } - else if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) - { - std::memcpy(data, &(channelConf->vlanID), - ipmi::network::VLAN_SIZE_BYTE); - } - } - break; + iterator end() noexcept + { + return iterator(objs.end(), *this); + } - default: - rc = IPMI_CC_PARM_OUT_OF_RANGE; + private: + sdbusplus::bus::bus& 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; } - catch (InternalFailure& e) +}; + +/** @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(sdbusplus::bus::bus& bus, const ChannelParams& params, + uint8_t idx, + const std::unordered_set<IP::AddressOrigin>& origins, + ObjectLookupCache& ips) +{ + for (const auto& [path, properties] : ips) { - commit<InternalFailure>(); - rc = IPMI_CC_UNSPECIFIED_ERROR; - return rc; + const auto& addrStr = std::get<std::string>(properties.at("Address")); + auto addr = maybeStringToAddr<family>(addrStr.c_str()); + if (!addr) + { + continue; + } + + IP::AddressOrigin origin = 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 std::move(ifaddr); } - return rc; + + return std::nullopt; } -namespace cipher +/** @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::bus& bus, const ChannelParams& params, + uint8_t idx, + const std::unordered_set<IP::AddressOrigin>& origins) { + ObjectLookupCache ips(bus, params, INTF_IP); + return findIfAddr<family>(bus, params, idx, origins, ips); +} -std::vector<uint8_t> getCipherList() +/** @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::bus& bus, const std::string& service, + const std::string& path) { - std::vector<uint8_t> cipherList; - - std::ifstream jsonFile(configFile); - if (!jsonFile.is_open()) + if (path.empty()) { - log<level::ERR>("Channel Cipher suites file not found"); - elog<InternalFailure>(); + return; } - - auto data = Json::parse(jsonFile, nullptr, false); - if (data.is_discarded()) + try { - log<level::ERR>("Parsing channel cipher suites JSON failed"); - elog<InternalFailure>(); + auto req = bus.new_method_call(service.c_str(), path.c_str(), + ipmi::DELETE_INTERFACE, "Delete"); + bus.call_noreply(req); } - - // Byte 1 is reserved - cipherList.push_back(0x00); - - for (const auto& record : data) + catch (const sdbusplus::exception::SdBusError& e) { - cipherList.push_back(record.value(cipher, 0)); + if (strcmp(e.name(), "org.freedesktop.DBus.Error.UnknownObject") != 0) + { + // We want to rethrow real errors + throw; + } } +} - return cipherList; +/** @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::bus& bus, const ChannelParams& params, + const 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::xyz::openbmc_project::Network::server::convertForMessage( + AddrFamily<family>::protocol); + newreq.append(protocol, addrToString<family>(address), prefix, ""); + bus.call_noreply(newreq); } -} // namespace cipher +/** @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::bus& bus, const ChannelParams& params) +{ + return getIfAddr<AF_INET>(bus, params, 0, originsV4); +} -ipmi_ret_t ipmi_transport_wildcard(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) +/** @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::bus& bus, const ChannelParams& params, + const std::optional<in_addr>& address, + std::optional<uint8_t> prefix) { - // Status code. - ipmi_ret_t rc = IPMI_CC_INVALID; - *data_len = 0; - return rc; + auto ifaddr = getIfAddr4(bus, params); + if (!ifaddr && !address) + { + log<level::ERR>("Missing address for IPv4 assignment"); + elog<InternalFailure>(); + } + uint8_t fallbackPrefix = AddrFamily<AF_INET>::defaultPrefix; + if (ifaddr) + { + fallbackPrefix = ifaddr->prefix; + deleteObjectIfExists(bus, params.service, ifaddr->path); + } + createIfAddr<AF_INET>(bus, params, address.value_or(ifaddr->address), + prefix.value_or(fallbackPrefix)); } -struct set_lan_t +template <int family> +std::optional<IfNeigh<family>> + findStaticNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::addr& ip, + ObjectLookupCache& neighbors) { - uint8_t channel; - uint8_t parameter; - uint8_t data[8]; // Per IPMI spec, not expecting more than this size -} __attribute__((packed)); + const auto state = + sdbusplus::xyz::openbmc_project::Network::server::convertForMessage( + Neighbor::State::Permanent); + for (const auto& [path, neighbor] : neighbors) + { + const auto& ipStr = std::get<std::string>(neighbor.at("IPAddress")); + auto neighIP = maybeStringToAddr<family>(ipStr.c_str()); + if (!neighIP) + { + continue; + } + if (!equal(*neighIP, ip)) + { + continue; + } + if (state != std::get<std::string>(neighbor.at("State"))) + { + continue; + } + + IfNeigh<family> ret; + ret.path = path; + ret.ip = ip; + const auto& macStr = std::get<std::string>(neighbor.at("MACAddress")); + ret.mac = stringToMAC(macStr.c_str()); + return std::move(ret); + } + + return std::nullopt; +} -ipmi_ret_t checkAndUpdateNetwork(int channel) +template <int family> +void createNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params, + const typename AddrFamily<family>::addr& address, + const ether_addr& mac) { - auto channelConf = getChannelConfig(channel); - using namespace std::chrono_literals; - // time to wait before applying the network changes. - constexpr auto networkTimeout = 10000000us; // 10 sec + auto newreq = + bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(), + INTF_NEIGHBOR_CREATE_STATIC, "Neighbor"); + std::string macStr = ether_ntoa(&mac); + newreq.append(addrToString<family>(address), macStr); + bus.call_noreply(newreq); +} - // Skip the timer. Expecting more update as we are in SET_IN_PROGRESS - if (channelConf->lan_set_in_progress == SET_IN_PROGRESS) +/** @brief Sets the system wide value for the default gateway + * + * @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::bus& bus, const ChannelParams& params, + const 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) { - return IPMI_CC_OK; + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors); } - // Start the timer, if it is direct single param update without - // SET_IN_PROGRESS or many params updated through SET_IN_PROGRESS to - // SET_COMPLETE Note: Even for update with SET_IN_PROGRESS, don't apply the - // changes immediately, as ipmitool sends each param individually - // through SET_IN_PROGRESS to SET_COMPLETE. - channelConf->flush = true; - if (!networkTimer) + setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG, + AddrFamily<family>::propertyGateway, + addrToString<family>(address)); + + // Restore the gateway MAC if we had one + if (neighbor) { - log<level::ERR>("Network timer is not instantiated"); - return IPMI_CC_UNSPECIFIED_ERROR; + deleteObjectIfExists(bus, params.service, neighbor->path); + createNeighbor<family>(bus, params, address, neighbor->mac); } - // start the timer. - networkTimer->start(networkTimeout); - return IPMI_CC_OK; } -ipmi_ret_t ipmi_transport_set_lan(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) +template <int family> +std::optional<IfNeigh<family>> findGatewayNeighbor(sdbusplus::bus::bus& bus, + const ChannelParams& params, + ObjectLookupCache& neighbors) { - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; + auto gateway = getGatewayProperty<family>(bus, params); + if (!gateway) + { + return std::nullopt; + } - char ipaddr[INET_ADDRSTRLEN]; - char netmask[INET_ADDRSTRLEN]; - char gateway[INET_ADDRSTRLEN]; + return findStaticNeighbor<family>(bus, params, *gateway, neighbors); +} - auto reqptr = reinterpret_cast<const set_lan_t*>(request); - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); +template <int family> +std::optional<IfNeigh<family>> getGatewayNeighbor(sdbusplus::bus::bus& bus, + const ChannelParams& params) +{ + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + return findGatewayNeighbor<family>(bus, params, neighbors); +} - // channel number is the lower nibble - int channel = reqptr->channel & CHANNEL_MASK; - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) +template <int family> +void reconfigureGatewayMAC(sdbusplus::bus::bus& bus, + const ChannelParams& params, const ether_addr& mac) +{ + auto gateway = getGatewayProperty<family>(bus, params); + if (!gateway) { - return IPMI_CC_INVALID_FIELD_REQUEST; + log<level::ERR>("Tried to set Gateway MAC without Gateway"); + elog<InternalFailure>(); } - auto channelConf = getChannelConfig(channel); - switch (static_cast<LanParam>(reqptr->parameter)) + ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR); + auto neighbor = + findStaticNeighbor<family>(bus, params, *gateway, neighbors); + if (neighbor) { - case LanParam::IP: - { - std::snprintf(ipaddr, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); + deleteObjectIfExists(bus, params.service, neighbor->path); + } - channelConf->ipaddr.assign(ipaddr); - } - break; + createNeighbor<family>(bus, params, *gateway, mac); +} - case LanParam::IPSRC: - { - uint8_t ipsrc{}; - std::memcpy(&ipsrc, reqptr->data, ipmi::network::IPSRC_SIZE_BYTE); - channelConf->ipsrc = static_cast<ipmi::network::IPOrigin>(ipsrc); - } - break; +/** @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::bus& bus, const ChannelParams& params, + uint8_t idx) +{ + auto ifaddr = getIfAddr<AF_INET6>(bus, params, idx, originsV6Static); + if (ifaddr) + { + deleteObjectIfExists(bus, params.service, ifaddr->path); + } +} - case LanParam::MAC: +/** @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::bus& bus, const ChannelParams& params, + uint8_t idx, const in6_addr& 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: { - char mac[SIZE_MAC]; + auto originStr = sdbusplus::xyz::openbmc_project::Network::server:: + convertForMessage(origin); + log<level::ERR>( + "Invalid IP::AddressOrigin conversion to IPv6Source", + entry("ORIGIN=%s", originStr.c_str())); + elog<InternalFailure>(); + } + } +} - std::snprintf(mac, SIZE_MAC, ipmi::network::MAC_ADDRESS_FORMAT, - reqptr->data[0], reqptr->data[1], reqptr->data[2], - reqptr->data[3], reqptr->data[4], reqptr->data[5]); +/** @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; + in6_addr addr{}; + uint8_t prefix = AddrFamily<AF_INET6>::defaultPrefix; + auto status = IPv6AddressStatus::Disabled; + + auto ifaddr = channelCall<getIfAddr<AF_INET6>>(channel, set, origins); + if (ifaddr) + { + source = originToSourceType(ifaddr->origin); + enabled = true; + addr = ifaddr->address; + prefix = ifaddr->prefix; + status = IPv6AddressStatus::Active; + } - auto macObjectInfo = - ipmi::getDbusObject(bus, ipmi::network::MAC_INTERFACE, - ipmi::network::ROOT, ethdevice); + ret.pack(set); + ret.pack(static_cast<uint4_t>(source), uint3_t{}, enabled); + ret.pack(std::string_view(reinterpret_cast<char*>(&addr), sizeof(addr))); + ret.pack(prefix); + ret.pack(static_cast<uint8_t>(status)); +} - ipmi::setDbusProperty( - bus, macObjectInfo.second, macObjectInfo.first, - ipmi::network::MAC_INTERFACE, "MACAddress", std::string(mac)); +/** @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::bus& bus, const ChannelParams& params) +{ + // VLAN devices will always have a separate logical object + if (params.ifPath == params.logicalPath) + { + return 0; + } - channelConf->macAddress = mac; - } - break; + auto vlan = std::get<uint32_t>(getDbusProperty( + bus, params.service, params.logicalPath, INTF_VLAN, "Id")); + if ((vlan & VLAN_VALUE_MASK) != vlan) + { + logWithChannel<level::ERR>(params, "networkd returned an invalid vlan", + entry("VLAN=%" PRIu32, vlan)); + elog<InternalFailure>(); + } + return vlan; +} - case LanParam::SUBNET: +/** @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::bus& bus, ChannelParams& params) +{ + // Delete all objects associated with the interface + auto objreq = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_INTF, + "GetSubTree"); + objreq.append(PATH_ROOT, 0, std::vector<std::string>{DELETE_INTERFACE}); + auto objreply = bus.call(objreq); + ObjectTree objs; + objreply.read(objs); + for (const auto& [path, impls] : objs) + { + if (path.find(params.ifname) == path.npos) { - std::snprintf(netmask, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); - channelConf->netmask.assign(netmask); + continue; } - break; - - case LanParam::GATEWAY: + for (const auto& [service, intfs] : impls) { - std::snprintf(gateway, INET_ADDRSTRLEN, - ipmi::network::IP_ADDRESS_FORMAT, reqptr->data[0], - reqptr->data[1], reqptr->data[2], reqptr->data[3]); - channelConf->gateway.assign(gateway); + deleteObjectIfExists(bus, service, path); } - break; - - case LanParam::VLAN: + // Update params to reflect the deletion of vlan + if (path == params.logicalPath) { - uint16_t vlan{}; - std::memcpy(&vlan, reqptr->data, ipmi::network::VLAN_SIZE_BYTE); - // We are not storing the enable bit - // We assume that ipmitool always send enable - // bit as 1. - vlan = le16toh(vlan); - channelConf->vlanID = vlan; + params.logicalPath = params.ifPath; } - break; + } - case LanParam::INPROGRESS: - { - if (reqptr->data[0] == SET_COMPLETE) - { - channelConf->lan_set_in_progress = SET_COMPLETE; + // Clear out any settings on the lower physical interface + setDHCPProperty(bus, params, false); +} - log<level::INFO>( - "Network data from Cache", - entry("PREFIX=%s", channelConf->netmask.c_str()), - entry("ADDRESS=%s", channelConf->ipaddr.c_str()), - entry("GATEWAY=%s", channelConf->gateway.c_str()), - entry("VLAN=%d", channelConf->vlanID)); - } - else if (reqptr->data[0] == SET_IN_PROGRESS) // Set In Progress - { - channelConf->lan_set_in_progress = SET_IN_PROGRESS; - } - } - break; +/** @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::bus& bus, ChannelParams& params, uint16_t vlan) +{ + if (vlan == 0) + { + return; + } - default: + auto req = bus.new_method_call(params.service.c_str(), PATH_ROOT, + 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::bus& 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 + 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) { - rc = IPMI_CC_PARM_NOT_SUPPORTED; - return rc; + break; } + ifaddrs6.push_back(std::move(*ifaddr6)); } - rc = checkAndUpdateNetwork(channel); + auto dhcp = getDHCPProperty(bus, params); + 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); - return rc; + // Re-establish the saved settings + setDHCPProperty(bus, params, dhcp); + 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); + } } -struct get_lan_t +/** @brief Turns a prefix into a netmask + * + * @param[in] prefix - The prefix length + * @return The netmask + */ +in_addr prefixToNetmask(uint8_t prefix) { - uint8_t rev_channel; - uint8_t parameter; - uint8_t parameter_set; - uint8_t parameter_block; -} __attribute__((packed)); + if (prefix > 32) + { + log<level::ERR>("Invalid prefix", entry("PREFIX=%" PRIu8, prefix)); + elog<InternalFailure>(); + } + if (prefix == 0) + { + // Avoids 32-bit lshift by 32 UB + return {}; + } + return {htobe32(~UINT32_C(0) << (32 - prefix))}; +} -ipmi_ret_t ipmi_transport_get_lan(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) +/** @brief Turns a a netmask into a prefix length + * + * @param[in] netmask - The netmask in byte form + * @return The prefix length + */ +uint8_t netmaskToPrefix(in_addr netmask) { - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; - const uint8_t current_revision = 0x11; // Current rev per IPMI Spec 2.0 - - get_lan_t* reqptr = (get_lan_t*)request; - // channel number is the lower nibble - int channel = reqptr->rev_channel & CHANNEL_MASK; + uint32_t x = be32toh(netmask.s_addr); + if ((~x & (~x + 1)) != 0) + { + char maskStr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &netmask, maskStr, sizeof(maskStr)); + log<level::ERR>("Invalid netmask", entry("NETMASK=%s", maskStr)); + elog<InternalFailure>(); + } + return static_cast<bool>(x) + ? AddrFamily<AF_INET>::defaultPrefix - __builtin_ctz(x) + : 0; +} - if (reqptr->rev_channel & 0x80) // Revision is bit 7 +// 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()) { - // Only current revision was requested - *data_len = sizeof(current_revision); - std::memcpy(response, ¤t_revision, *data_len); - return IPMI_CC_OK; + return it->second; } + return setStatus[channel] = SetStatus::Complete; +} - static std::vector<uint8_t> cipherList; - static auto listInit = false; +/** + * 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_OECONF_append = " --enable-transport-oem=yes" + * Create a do_compile_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_compile_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 channel, uint8_t parameter, message::Payload& req) +{ + req.trailingOk = true; + return response(ccParamNotSupported); +} - if (!listInit) +RspType<message::Payload> getLanOem(uint8_t channel, uint8_t parameter, + uint8_t set, uint8_t block) +{ + return response(ccParamNotSupported); +} +/** + * @brief is MAC address valid. + * + * This function checks whether the MAC address is valid or not. + * + * @param[in] mac - MAC address. + * @return true if MAC address is valid else retun false. + **/ +bool isValidMACAddress(const ether_addr& mac) +{ + // check if mac address is empty + if (equal(mac, ether_addr{})) { - try - { - cipherList = cipher::getCipherList(); - listInit = true; - } - catch (const std::exception& e) - { - return IPMI_CC_UNSPECIFIED_ERROR; - } + return false; + } + // we accept only unicast MAC addresses and same thing has been checked in + // phosphor-network layer. If the least significant bit of the first octet + // is set to 1, it is multicast MAC else it is unicast MAC address. + if (mac.ether_addr_octet[0] & 1) + { + return false; } + return true; +} - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) +RspType<> setLan(uint4_t channelBits, uint4_t, uint8_t parameter, + message::Payload& req) +{ + auto channel = static_cast<uint8_t>(channelBits); + if (!doesDeviceExist(channel)) { - return IPMI_CC_INVALID_FIELD_REQUEST; + req.trailingOk = true; + return responseInvalidFieldRequest(); } - auto channelConf = getChannelConfig(channel); - LanParam param = static_cast<LanParam>(reqptr->parameter); - switch (param) + switch (static_cast<LanParam>(parameter)) { - case LanParam::INPROGRESS: + case LanParam::SetStatus: { - uint8_t buf[] = {current_revision, - channelConf->lan_set_in_progress}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + uint2_t flag; + uint6_t rsvd; + if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + 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: + case LanParam::AuthSupport: { - uint8_t buf[] = {current_revision, 0x04}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } - case LanParam::AUTHENABLES: + case LanParam::AuthEnables: { - uint8_t buf[] = {current_revision, 0x04, 0x04, 0x04, 0x04, 0x04}; - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } case LanParam::IP: - case LanParam::SUBNET: - case LanParam::GATEWAY: - case LanParam::MAC: { - uint8_t buf[ipmi::network::MAC_ADDRESS_SIZE_BYTE + 1] = {}; - - *data_len = sizeof(current_revision); - std::memcpy(buf, ¤t_revision, *data_len); - - if (getNetworkData(reqptr->parameter, &buf[1], channel) == - IPMI_CC_OK) + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr ip; + std::array<uint8_t, sizeof(ip)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) { - if (param == LanParam::MAC) + return responseReqDataLenInvalid(); + } + copyInto(ip, bytes); + channelCall<reconfigureIfAddr4>(channel, ip, std::nullopt); + return responseSuccess(); + } + case LanParam::IPSrc: + { + uint4_t flag; + uint4_t rsvd; + if (req.unpack(flag, rsvd) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (rsvd) + { + return responseInvalidFieldRequest(); + } + switch (static_cast<IPSrc>(static_cast<uint8_t>(flag))) + { + case IPSrc::DHCP: { - *data_len = sizeof(buf); + channelCall<setDHCPProperty>(channel, true); + return responseSuccess(); } - else + case IPSrc::Unspecified: + case IPSrc::Static: + case IPSrc::BIOS: + case IPSrc::BMC: { - *data_len = ipmi::network::IPV4_ADDRESS_SIZE_BYTE + 1; + channelCall<setDHCPProperty>(channel, false); + return responseSuccess(); } - std::memcpy(response, &buf, *data_len); } - else + return response(ccParamNotSupported); + } + case LanParam::MAC: + { + ether_addr mac; + std::array<uint8_t, sizeof(mac)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) { - rc = IPMI_CC_UNSPECIFIED_ERROR; + return responseReqDataLenInvalid(); } - break; + copyInto(mac, bytes); + + if (!isValidMACAddress(mac)) + { + return responseInvalidFieldRequest(); + } + channelCall<setMACProperty>(channel, mac); + return responseSuccess(); + } + case LanParam::SubnetMask: + { + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr netmask; + std::array<uint8_t, sizeof(netmask)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(netmask, bytes); + channelCall<reconfigureIfAddr4>(channel, std::nullopt, + netmaskToPrefix(netmask)); + return responseSuccess(); + } + case LanParam::Gateway1: + { + if (channelCall<getDHCPProperty>(channel)) + { + return responseCommandNotAvailable(); + } + in_addr gateway; + std::array<uint8_t, sizeof(gateway)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gateway, bytes); + channelCall<setGatewayProperty<AF_INET>>(channel, gateway); + return responseSuccess(); + } + case LanParam::Gateway1MAC: + { + ether_addr gatewayMAC; + std::array<uint8_t, sizeof(gatewayMAC)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gatewayMAC, bytes); + channelCall<reconfigureGatewayMAC<AF_INET>>(channel, gatewayMAC); + return responseSuccess(); } - case LanParam::VLAN: + case LanParam::VLANId: { - uint8_t buf[ipmi::network::VLAN_SIZE_BYTE + 1] = {}; + uint12_t vlanData = 0; + uint3_t reserved = 0; + bool vlanEnable = 0; - *data_len = sizeof(current_revision); - std::memcpy(buf, ¤t_revision, *data_len); - if (getNetworkData(reqptr->parameter, &buf[1], channel) == - IPMI_CC_OK) + if (req.unpack(vlanData) || req.unpack(reserved) || + req.unpack(vlanEnable) || !req.fullyUnpacked()) { - *data_len = sizeof(buf); - std::memcpy(response, &buf, *data_len); + return responseReqDataLenInvalid(); } - break; + + if (reserved) + { + return responseInvalidFieldRequest(); + } + + uint16_t vlan = static_cast<uint16_t>(vlanData); + + if (!vlanEnable) + { + lastDisabledVlan[channel] = vlan; + vlan = 0; + } + channelCall<reconfigureVLAN>(channel, vlan); + + return responseSuccess(); } - case LanParam::IPSRC: + case LanParam::CiphersuiteSupport: + case LanParam::CiphersuiteEntries: + case LanParam::IPFamilySupport: { - uint8_t buff[ipmi::network::IPSRC_SIZE_BYTE + 1] = {}; - *data_len = sizeof(current_revision); - std::memcpy(buff, ¤t_revision, *data_len); - if (getNetworkData(reqptr->parameter, &buff[1], channel) == - IPMI_CC_OK) + req.trailingOk = true; + return response(ccParamReadOnly); + } + case LanParam::IPFamilyEnables: + { + uint8_t enables; + if (req.unpack(enables) != 0 || !req.fullyUnpacked()) { - *data_len = sizeof(buff); - std::memcpy(response, &buff, *data_len); + return responseReqDataLenInvalid(); } - break; + switch (static_cast<IPFamilyEnables>(enables)) + { + case IPFamilyEnables::DualStack: + return responseSuccess(); + case IPFamilyEnables::IPv4Only: + case IPFamilyEnables::IPv6Only: + return response(ccParamNotSupported); + } + return response(ccParamNotSupported); } - case LanParam::CIPHER_SUITE_COUNT: + case LanParam::IPv6Status: { - *(static_cast<uint8_t*>(response)) = current_revision; - // Byte 1 is reserved byte and does not indicate a cipher suite ID, - // so no of cipher suite entry count is one less than the size of - // the vector - auto count = static_cast<uint8_t>(cipherList.size() - 1); - *(static_cast<uint8_t*>(response) + 1) = count; - *data_len = sizeof(current_revision) + sizeof(count); - break; + req.trailingOk = true; + return response(ccParamReadOnly); } - case LanParam::CIPHER_SUITE_ENTRIES: + case LanParam::IPv6StaticAddresses: { - *(static_cast<uint8_t*>(response)) = current_revision; - // Byte 1 is reserved - std::copy_n(cipherList.data(), cipherList.size(), - static_cast<uint8_t*>(response) + 1); - *data_len = sizeof(current_revision) + - static_cast<uint8_t>(cipherList.size()); - break; + uint8_t set; + uint7_t rsvd; + bool enabled; + in6_addr ip; + std::array<uint8_t, sizeof(ip)> ipbytes; + uint8_t prefix; + uint8_t status; + if (req.unpack(set, rsvd, enabled, ipbytes, prefix, status) != 0 || + !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (rsvd) + { + return responseInvalidFieldRequest(); + } + copyInto(ip, ipbytes); + if (enabled) + { + 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; + if (req.unpack(control) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + std::bitset<8> expected; + if (channelCall<getDHCPProperty>(channel)) + { + expected[IPv6RouterControlFlag::Dynamic] = 1; + } + else + { + expected[IPv6RouterControlFlag::Static] = 1; + } + if (expected != control) + { + return responseInvalidFieldRequest(); + } + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1IP: + { + in6_addr gateway; + std::array<uint8_t, sizeof(gateway)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(gateway, bytes); + channelCall<setGatewayProperty<AF_INET6>>(channel, gateway); + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1MAC: + { + ether_addr mac; + std::array<uint8_t, sizeof(mac)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + copyInto(mac, bytes); + channelCall<reconfigureGatewayMAC<AF_INET6>>(channel, mac); + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1PrefixLength: + { + uint8_t prefix; + if (req.unpack(prefix) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + if (prefix != 0) + { + return responseInvalidFieldRequest(); + } + return responseSuccess(); + } + case LanParam::IPv6StaticRouter1PrefixValue: + { + std::array<uint8_t, sizeof(in6_addr)> bytes; + if (req.unpack(bytes) != 0 || !req.fullyUnpacked()) + { + return responseReqDataLenInvalid(); + } + // Accept any prefix value since our prefix length has to be 0 + return responseSuccess(); } - default: - log<level::ERR>("Unsupported parameter", - entry("PARAMETER=0x%x", reqptr->parameter)); - rc = IPMI_CC_PARM_NOT_SUPPORTED; } - return rc; + if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd)) + { + return setLanOem(channel, parameter, req); + } + + req.trailingOk = true; + return response(ccParamNotSupported); } -void applyChanges(int channel) +RspType<message::Payload> getLan(uint4_t channelBits, uint3_t, bool revOnly, + uint8_t parameter, uint8_t set, uint8_t block) { - std::string ipaddress; - std::string gateway; - uint8_t prefix{}; - uint32_t vlanID{}; - std::string networkInterfacePath; - ipmi::DbusObjectInfo ipObject; - ipmi::DbusObjectInfo systemObject; + message::Payload ret; + constexpr uint8_t current_revision = 0x11; + ret.pack(current_revision); - auto ethdevice = ipmi::getChannelName(channel); - if (ethdevice.empty()) + if (revOnly) { - log<level::ERR>("Unable to get the interface name", - entry("CHANNEL=%d", channel)); - return; + return responseSuccess(std::move(ret)); } - auto ethIp = ethdevice + "/" + ipmi::network::IP_TYPE; - auto channelConf = getChannelConfig(channel); - try + auto channel = static_cast<uint8_t>(channelBits); + if (!doesDeviceExist(channel)) { - sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + return responseInvalidFieldRequest(); + } - log<level::INFO>("Network data from Cache", - entry("PREFIX=%s", channelConf->netmask.c_str()), - entry("ADDRESS=%s", channelConf->ipaddr.c_str()), - entry("GATEWAY=%s", channelConf->gateway.c_str()), - entry("VLAN=%d", channelConf->vlanID), - entry("IPSRC=%d", channelConf->ipsrc)); - if (channelConf->vlanID != ipmi::network::VLAN_ID_MASK) + static std::vector<uint8_t> cipherList; + static bool listInit = false; + if (!listInit) + { + try { - // get the first twelve bits which is vlan id - // not interested in rest of the bits. - channelConf->vlanID = le32toh(channelConf->vlanID); - vlanID = channelConf->vlanID & ipmi::network::VLAN_ID_MASK; + cipherList = cipher::getCipherList(); + listInit = true; } - - // if the asked ip src is DHCP then not interested in - // any given data except vlan. - if (channelConf->ipsrc != ipmi::network::IPOrigin::DHCP) + catch (const std::exception& e) { - // always get the system object - systemObject = - ipmi::getDbusObject(bus, ipmi::network::SYSTEMCONFIG_INTERFACE, - ipmi::network::ROOT); + } + } - // the below code is to determine the mode of the interface - // as the handling is same, if the system is configured with - // DHCP or user has given all the data. + switch (static_cast<LanParam>(parameter)) + { + case LanParam::SetStatus: + { + SetStatus status; try { - ipmi::ObjectTree ancestorMap; - - ipmi::InterfaceList interfaces{ - ipmi::network::ETHERNET_INTERFACE}; - - // if the system is having ip object,then - // get the IP object. - ipObject = ipmi::getIPObject(bus, ipmi::network::IP_INTERFACE, - ipmi::network::ROOT, ethIp); - - // Get the parent interface of the IP object. - try - { - ancestorMap = ipmi::getAllAncestors(bus, ipObject.first, - std::move(interfaces)); - } - catch (InternalFailure& e) - { - // if unable to get the parent interface - // then commit the error and return. - log<level::ERR>("Unable to get the parent interface", - entry("PATH=%s", ipObject.first.c_str()), - entry("INTERFACE=%s", - ipmi::network::ETHERNET_INTERFACE)); - commit<InternalFailure>(); - channelConf->clear(); - return; - } - - networkInterfacePath = ancestorMap.begin()->first; + status = setStatus.at(channel); } - catch (InternalFailure& e) + catch (const std::out_of_range&) { - // TODO Currently IPMI supports single interface,need to handle - // Multiple interface through - // https://github.com/openbmc/openbmc/issues/2138 - - // if there is no ip configured on the system,then - // get the network interface object. - auto networkInterfaceObject = - ipmi::getDbusObject(bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); - - networkInterfacePath = std::move(networkInterfaceObject.first); + status = SetStatus::Complete; } - - // get the configured mode on the system. - auto enableDHCP = variant_ns::get<bool>(ipmi::getDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled")); - - // if ip address source is not given then get the ip source mode - // from the system so that it can be applied later. - if (channelConf->ipsrc == ipmi::network::IPOrigin::UNSPECIFIED) + ret.pack(static_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); + in_addr addr{}; + if (ifaddr) { - channelConf->ipsrc = (enableDHCP) - ? ipmi::network::IPOrigin::DHCP - : ipmi::network::IPOrigin::STATIC; + addr = ifaddr->address; } - - // check whether user has given all the data - // or the configured system interface is dhcp enabled, - // in both of the cases get the values from the cache. - if ((!channelConf->ipaddr.empty() && - !channelConf->netmask.empty() && - !channelConf->gateway.empty()) || - (enableDHCP)) // configured system interface mode = DHCP + ret.pack(dataRef(addr)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPSrc: + { + auto src = IPSrc::Static; + if (channelCall<getDHCPProperty>(channel)) { - // convert mask into prefix - ipaddress = channelConf->ipaddr; - prefix = ipmi::network::toPrefix(AF_INET, channelConf->netmask); - gateway = channelConf->gateway; + src = IPSrc::DHCP; } - else // asked ip src = static and configured system src = static - // or partially given data. + ret.pack(static_cast<uint4_t>(src), uint4_t{}); + return responseSuccess(std::move(ret)); + } + case LanParam::MAC: + { + ether_addr mac = channelCall<getMACProperty>(channel); + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); + } + case LanParam::SubnetMask: + { + auto ifaddr = channelCall<getIfAddr4>(channel); + uint8_t prefix = AddrFamily<AF_INET>::defaultPrefix; + if (ifaddr) { - // We have partial filled cache so get the remaining - // info from the system. - - // Get the network data from the system as user has - // not given all the data then use the data fetched from the - // system but it is implementation dependent,IPMI spec doesn't - // force it. - - // if system is not having any ip object don't throw error, - try - { - auto properties = ipmi::getAllDbusProperties( - bus, ipObject.second, ipObject.first, - ipmi::network::IP_INTERFACE); - - ipaddress = channelConf->ipaddr.empty() - ? variant_ns::get<std::string>( - properties["Address"]) - : channelConf->ipaddr; - - prefix = channelConf->netmask.empty() - ? variant_ns::get<uint8_t>( - properties["PrefixLength"]) - : ipmi::network::toPrefix( - AF_INET, channelConf->netmask); - } - catch (InternalFailure& e) - { - log<level::INFO>( - "Failed to get IP object which matches", - entry("INTERFACE=%s", ipmi::network::IP_INTERFACE), - entry("MATCH=%s", ethIp.c_str())); - } - - auto systemProperties = ipmi::getAllDbusProperties( - bus, systemObject.second, systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE); - - gateway = channelConf->gateway.empty() - ? variant_ns::get<std::string>( - systemProperties["DefaultGateway"]) - : channelConf->gateway; + prefix = ifaddr->prefix; } + in_addr netmask = prefixToNetmask(prefix); + ret.pack(dataRef(netmask)); + return responseSuccess(std::move(ret)); } - - // Currently network manager doesn't support purging of all the - // ip addresses and the vlan interfaces from the parent interface, - // TODO once the support is there, will make the change here. - // https://github.com/openbmc/openbmc/issues/2141. - - // TODO Currently IPMI supports single interface,need to handle - // Multiple interface through - // https://github.com/openbmc/openbmc/issues/2138 - - // instead of deleting all the vlan interfaces and - // all the ipv4 address,we will call reset method. - // delete all the vlan interfaces - - ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT, - ipmi::network::VLAN_INTERFACE); - - // set the interface mode to static - auto networkInterfaceObject = - ipmi::getDbusObject(bus, ipmi::network::ETHERNET_INTERFACE, - ipmi::network::ROOT, ethdevice); - - // setting the physical interface mode to static. - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfaceObject.first, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", false); - - networkInterfacePath = networkInterfaceObject.first; - - // delete all the ipv4 addresses - ipmi::deleteAllDbusObjects(bus, ipmi::network::ROOT, - ipmi::network::IP_INTERFACE, ethIp); - - if (vlanID) + case LanParam::Gateway1: { - ipmi::network::createVLAN(bus, ipmi::network::SERVICE, - ipmi::network::ROOT, ethdevice, vlanID); - - auto networkInterfaceObject = ipmi::getDbusObject( - bus, ipmi::network::VLAN_INTERFACE, ipmi::network::ROOT); - - networkInterfacePath = networkInterfaceObject.first; + auto gateway = + channelCall<getGatewayProperty<AF_INET>>(channel).value_or( + in_addr{}); + ret.pack(dataRef(gateway)); + return responseSuccess(std::move(ret)); } - - if (channelConf->ipsrc == ipmi::network::IPOrigin::DHCP) + case LanParam::Gateway1MAC: { - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", true); + ether_addr mac{}; + auto neighbor = channelCall<getGatewayNeighbor<AF_INET>>(channel); + if (neighbor) + { + mac = neighbor->mac; + } + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); } - else + case LanParam::VLANId: { - // change the mode to static - ipmi::setDbusProperty( - bus, ipmi::network::SERVICE, networkInterfacePath, - ipmi::network::ETHERNET_INTERFACE, "DHCPEnabled", false); - - if (!ipaddress.empty()) + uint16_t vlan = channelCall<getVLANProperty>(channel); + if (vlan != 0) { - ipmi::network::createIP(bus, ipmi::network::SERVICE, - networkInterfacePath, ipv4Protocol, - ipaddress, prefix); + vlan |= VLAN_ENABLE_FLAG; } - - if (!gateway.empty()) + else { - ipmi::setDbusProperty(bus, systemObject.second, - systemObject.first, - ipmi::network::SYSTEMCONFIG_INTERFACE, - "DefaultGateway", std::string(gateway)); + vlan = lastDisabledVlan[channel]; } + ret.pack(vlan); + return responseSuccess(std::move(ret)); } - } - catch (sdbusplus::exception::exception& e) - { - log<level::ERR>( - "Failed to set network data", entry("PREFIX=%d", prefix), - entry("ADDRESS=%s", ipaddress.c_str()), - entry("GATEWAY=%s", gateway.c_str()), entry("VLANID=%d", vlanID), - entry("IPSRC=%d", channelConf->ipsrc)); - - commit<InternalFailure>(); - } - - channelConf->clear(); -} - -void commitNetworkChanges() -{ - for (const auto& channel : channelConfig) - { - if (channel.second->flush) + case LanParam::CiphersuiteSupport: + { + if (!listInit) + { + return responseUnspecifiedError(); + } + ret.pack(static_cast<uint8_t>(cipherList.size() - 1)); + return responseSuccess(std::move(ret)); + } + case LanParam::CiphersuiteEntries: + { + 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: { - applyChanges(channel.first); + 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; + if (channelCall<getDHCPProperty>(channel)) + { + control[IPv6RouterControlFlag::Dynamic] = 1; + } + else + { + control[IPv6RouterControlFlag::Static] = 1; + } + ret.pack(control); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1IP: + { + in6_addr gateway{}; + if (!channelCall<getDHCPProperty>(channel)) + { + gateway = + channelCall<getGatewayProperty<AF_INET6>>(channel).value_or( + in6_addr{}); + } + ret.pack(dataRef(gateway)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1MAC: + { + ether_addr mac{}; + auto neighbor = channelCall<getGatewayNeighbor<AF_INET6>>(channel); + if (neighbor) + { + mac = neighbor->mac; + } + ret.pack(dataRef(mac)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1PrefixLength: + { + ret.pack(UINT8_C(0)); + return responseSuccess(std::move(ret)); + } + case LanParam::IPv6StaticRouter1PrefixValue: + { + in6_addr prefix{}; + ret.pack(dataRef(prefix)); + return responseSuccess(std::move(ret)); } } -} -void createNetworkTimer() -{ - if (!networkTimer) + if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd)) { - std::function<void()> networkTimerCallback( - std::bind(&commitNetworkChanges)); - - networkTimer = std::make_unique<phosphor::Timer>(networkTimerCallback); + return getLanOem(channel, parameter, set, block); } -} -void register_netfn_transport_functions() -{ - // As this timer is only for transport handler - // so creating it here. - createNetworkTimer(); - // <Wildcard Command> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_WILDCARD, NULL, - ipmi_transport_wildcard, PRIVILEGE_USER); + return response(ccParamNotSupported); +} - // <Set LAN Configuration Parameters> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_SET_LAN, NULL, - ipmi_transport_set_lan, PRIVILEGE_ADMIN); +} // namespace transport +} // namespace ipmi - // <Get LAN Configuration Parameters> - ipmi_register_callback(NETFUN_TRANSPORT, IPMI_CMD_GET_LAN, NULL, - ipmi_transport_get_lan, PRIVILEGE_OPERATOR); +void register_netfn_transport_functions() __attribute__((constructor)); - return; +void register_netfn_transport_functions() +{ + 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); } diff --git a/transporthandler.hpp b/transporthandler.hpp deleted file mode 100644 index 04d4673..0000000 --- a/transporthandler.hpp +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -#include <ipmid/types.hpp> -#include <string> -// IPMI commands for Transport net functions. -enum ipmi_netfn_storage_cmds -{ - // Get capability bits - IPMI_CMD_SET_LAN = 0x01, - IPMI_CMD_GET_LAN = 0x02, -}; - -// Command specific completion codes -enum ipmi_transport_return_codes -{ - IPMI_CC_PARM_NOT_SUPPORTED = 0x80, -}; - -// Parameters -enum class LanParam : uint8_t -{ - INPROGRESS = 0, - AUTHSUPPORT = 1, // Read-only - AUTHENABLES = 2, - IP = 3, - IPSRC = 4, - MAC = 5, - SUBNET = 6, - IPHEADER_PARAMS = 7, - RMCP_PORT = 8, - RMCP_SECONDARY_PORT = 9, - BMC_GENERATED_ARP_CTRL = 10, - GRATUITOUS_ARP_INTERVAL = 11, - GATEWAY = 12, - GATEWAY_MAC = 13, - GATEWAY_BACKUP = 14, - GATEWAY_BACKUP_MAC = 15, - COMMUNITY_STRING = 16, - LAN_ALERT_DESTINATION_COUNT = 17, // Read-only - LAN_ALERT_DESTINATION_TYPE = 18, // Type per destination - LAN_ALERT_DESTINATIONS = 19, - VLAN = 20, - VLAN_PRIORITY = 21, - CIPHER_SUITE_COUNT = 22, // Read-only - CIPHER_SUITE_ENTRIES = 23, // Read-only - CIPHER_SUITE_PRIVILEGE_LEVELS = 24, - DESTINATION_ADDR_VLAN_TAGS = 25, - BAD_PASSWORD_THRESHOLD = 26, - IPV6_AND_IPV4_SUPPORTED = 50, // Read-only - IPV6_AND_IPV4_ENABLES = 51, - IPV6_HEADER_STATIC_TRAFFIC_CLASS = 52, - IPV6_HEADER_STATIC_HOP_LIMIT = 53, - IPV6_HEADER_FLOW_LABEL = 54, - IPV6_STATUS = 55, // Read-only - IPV6_STATIC_ADDRESSES = 56, - IPV6_DHCPV6_STATIC_DUID_STORAGE_LENGTH = 57, // Read-only - IPV6_DHCPV6_STATIC_DUIDS = 58, - IPV6_DYNAMIC_ADDRESSES = 59, // Read-only - IPV6_DHCPV6_DYNAMIC_DUID_STOR_LEN = 60, // Read-only - IPV6_DHCPV6_DYNAMIC_DUIDS = 61, - IPV6_DHCPV6_TIMING_CONF_SUPPORT = 62, // Read-only - IPV6_DHCPV6_TIMING_CONFIGURATION = 63, - IPV6_ROUTER_ADDRESS_CONF_CTRL = 64, - IPV6_STATIC_ROUTER_1_IP_ADDR = 65, - IPV6_STATIC_ROUTER_1_MAC_ADDR = 66, - IPV6_STATIC_ROUTER_1_PREFIX_LEN = 67, - IPV6_STATIC_ROUTER_1_PREFIX_VAL = 68, - IPV6_STATIC_ROUTER_2_IP_ADDR = 69, - IPV6_STATIC_ROUTER_2_MAC_ADDR = 70, - IPV6_STATIC_ROUTER_2_PREFIX_LEN = 71, - IPV6_STATIC_ROUTER_2_PREFIX_VAL = 72, - DYNAMIC_ROUTER_INFO_SET_COUNT = 73, // Read-only - IPV6_DYNAMIC_ROUTER_INFO_IP_ADDR = 74, // Read-only - IPV6_DYNAMIC_ROUTER_INFO_MAC = 75, // Read-only - IPV6_DYNAMIC_ROUTER_INFO_PREFIX_LEN = 76, // Read-only - IPV6_DYNAMIC_ROUTER_INFO_PREFIX_VAL = 77, // Read-only - IPV6_DYNAMIC_ROUTER_RECV_HOP_LIMIT = 78, - IPV6_NEIGHBOR_TIMING_CONF_SUPPORT = 79, // Read-only - IPV6_NEIGHBOR_TIMING_CONFIGURATION = 80, -}; - -constexpr uint8_t SET_COMPLETE = 0; -constexpr uint8_t SET_IN_PROGRESS = 1; -constexpr uint8_t SET_COMMIT_WRITE = 2; // Optional -constexpr uint8_t SET_IN_PROGRESS_RESERVED = 3; // Reserved - -const int CHANNEL_MASK = 0x0f; -const int NUM_CHANNELS = 0x0f; - -struct ChannelConfig_t -{ - std::string ipaddr; - ipmi::network::IPOrigin ipsrc = ipmi::network::IPOrigin::UNSPECIFIED; - std::string netmask; - std::string gateway; - std::string macAddress; - // IPMI stores the vlan info in 16 bits,32 bits is to aligned - // with phosphor-dbus interfaces. - // vlan id is in 12 bits and the 16th bit is for enable mask. - uint32_t vlanID = ipmi::network::VLAN_ID_MASK; - uint8_t lan_set_in_progress = SET_COMPLETE; - bool flush = false; - - void clear() - { - ipaddr.clear(); - netmask.clear(); - gateway.clear(); - macAddress.clear(); - vlanID = ipmi::network::VLAN_ID_MASK; - ipsrc = ipmi::network::IPOrigin::UNSPECIFIED; - lan_set_in_progress = SET_COMPLETE; - flush = false; - } -}; - -// Given a channel, get the corresponding configuration, -// or allocate it first. -// -// @param[in] channel the channel -// @return the ChannelConfig_t pointer. -struct ChannelConfig_t* getChannelConfig(int channel); - -/** @brief Iterate over all the channelconfig and if - * user has given the data for a channel then - * apply the network changes for that channel. - */ -void commitNetworkChanges(); - -/* @brief Apply the network changes which is there in the - * network cache for a given channel which gets filled - * through setLan command. If some of the network - * parameter was not given by the setLan then this function - * gets the value of that parameter which is already - * configured on the system. - * @param[in] channel: channel number. - */ -void applyChanges(int channel); diff --git a/user_channel/Makefile.am b/user_channel/Makefile.am index 3860a39..747c4c8 100644 --- a/user_channel/Makefile.am +++ b/user_channel/Makefile.am @@ -12,7 +12,12 @@ COMMON_CXX = \ -DBOOST_ASIO_DISABLE_THREADS \ -DBOOST_ALL_NO_LIB -lib_LTLIBRARIES = libuserlayer.la libchannellayer.la + +lib_LTLIBRARIES = + +if FEATURE_LIBUSERLAYER + +lib_LTLIBRARIES += libuserlayer.la libuserlayer_la_SOURCES = \ user_layer.cpp \ user_mgmt.cpp \ @@ -29,7 +34,9 @@ libuserlayer_la_LDFLAGS = \ libuserlayer_la_CXXFLAGS = \ -I$(top_srcdir) \ $(COMMON_CXX) +endif +lib_LTLIBRARIES += libchannellayer.la libchannellayer_la_SOURCES = \ channel_mgmt.cpp \ channel_layer.cpp diff --git a/user_channel/channel_layer.cpp b/user_channel/channel_layer.cpp index 34a596d..c6866c2 100644 --- a/user_channel/channel_layer.cpp +++ b/user_channel/channel_layer.cpp @@ -58,11 +58,6 @@ bool isValidChannel(const uint8_t chNum) return getChannelConfigObject().isValidChannel(chNum); } -uint8_t convertCurrentChannelNum(const uint8_t chNum) -{ - return getChannelConfigObject().convertToChannelIndexNumber(chNum); -} - bool isValidAuthType(const uint8_t chNum, const EAuthType& authType) { return getChannelConfigObject().isValidAuthType(chNum, authType); @@ -142,4 +137,19 @@ 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 index 1a8d64c..4eb51b7 100644 --- a/user_channel/channel_layer.hpp +++ b/user_channel/channel_layer.hpp @@ -16,7 +16,6 @@ #pragma once #include <ipmid/api.h> -#include <ipmid/message.hpp> #include <string> namespace ipmi @@ -24,6 +23,7 @@ namespace ipmi static constexpr uint8_t maxIpmiChannels = 16; static constexpr uint8_t currentChNum = 0xE; +static constexpr uint8_t invalidChannel = 0xff; /** * @enum IPMI return codes specific to channel (refer spec se 22.22 response @@ -280,30 +280,21 @@ ipmi_ret_t getChannelAccessData(const uint8_t chNum, /** @brief provides function to convert current channel number (0xE) * * @param[in] chNum - channel number as requested in commands. - * @param[in] ipmi::context - ipmi context ptr, which has more details + * @param[in] devChannel - channel number as provided by device (not 0xE) * * @return same channel number or proper channel number for current channel * number (0xE). */ -inline uint8_t convertCurrentChannelNum(const uint8_t chNum, - ipmi::Context::ptr ctx) +static inline uint8_t convertCurrentChannelNum(const uint8_t chNum, + const uint8_t devChannel) { if (chNum == currentChNum) { - return ctx->channel; + return devChannel; } return chNum; } -/** @brief provides function to convert current channel number (0xE) - * - * @param[in] chNum - channel number as requested in commands. - * - * @return same channel number or proper channel number for current channel - * number (0xE). - */ -uint8_t convertCurrentChannelNum(const uint8_t chNum); - /** @brief to set channel access data * * @param[in] chNum - channel number @@ -367,4 +358,20 @@ ipmi_ret_t getChannelEnabledAuthType(const uint8_t chNum, const uint8_t priv, */ 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 index 3fb19b2..759de43 100644 --- a/user_channel/channel_mgmt.cpp +++ b/user_channel/channel_mgmt.cpp @@ -34,7 +34,6 @@ namespace ipmi { -namespace variant_ns = sdbusplus::message::variant_ns; using namespace phosphor::logging; static constexpr const char* channelAccessDefaultFilename = @@ -143,7 +142,7 @@ std::string ChannelConfig::getChannelName(const uint8_t chNum) if (!isValidChannel(chNum)) { log<level::ERR>("Invalid channel number.", - entry("ChannelID:%d", chNum)); + entry("CHANNEL_ID=%d", chNum)); throw std::invalid_argument("Invalid channel number"); } @@ -161,7 +160,7 @@ int ChannelConfig::convertToChannelNumberFromChannelName( } } log<level::ERR>("Invalid channel name.", - entry("Channel:%s", chName.c_str())); + entry("CHANNEL=%s", chName.c_str())); throw std::invalid_argument("Invalid channel name"); return -1; @@ -169,15 +168,15 @@ int ChannelConfig::convertToChannelNumberFromChannelName( std::string ChannelConfig::getChannelNameFromPath(const std::string& path) { - std::size_t pos = path.find(networkIntfObjectBasePath); - if (pos == std::string::npos) + + constexpr size_t length = strlen(networkIntfObjectBasePath); + if (((length + 1) >= path.size()) || + path.compare(0, length, networkIntfObjectBasePath)) { - log<level::ERR>("Invalid interface path.", - entry("PATH:%s", path.c_str())); - throw std::invalid_argument("Invalid interface path"); + log<level::ERR>("Invalid object path.", entry("PATH=%s", path.c_str())); + throw std::invalid_argument("Invalid object path"); } - std::string chName = - path.substr(pos + strlen(networkIntfObjectBasePath) + 1); + std::string chName(path, length + 1); return chName; } @@ -192,7 +191,7 @@ void ChannelConfig::processChAccessPropChange( } catch (const std::invalid_argument& e) { - log<level::ERR>("Exception: ", entry("MSG: %s", e.what())); + log<level::ERR>("Exception: ", entry("MSG=%s", e.what())); return; } @@ -204,7 +203,7 @@ void ChannelConfig::processChAccessPropChange( if (prop.first == privilegePropertyString) { propName = privilegePropertyString; - intfPrivStr = variant_ns::get<std::string>(prop.second); + intfPrivStr = std::get<std::string>(prop.second); break; } } @@ -218,7 +217,7 @@ void ChannelConfig::processChAccessPropChange( if (intfPrivStr.empty()) { log<level::ERR>("Invalid privilege string.", - entry("INTF:%s", chName.c_str())); + entry("INTF=%s", chName.c_str())); return; } @@ -231,7 +230,7 @@ void ChannelConfig::processChAccessPropChange( } catch (const std::invalid_argument& e) { - log<level::ERR>("Exception: ", entry("MSG: %s", e.what())); + log<level::ERR>("Exception: ", entry("MSG=%s", e.what())); return; } @@ -614,7 +613,7 @@ ipmi_ret_t ChannelConfig::setChannelAccessPersistData( { log<level::DEBUG>( "Network interface does not exist", - entry("INTERFACE:%s", channelData[chNum].chName.c_str())); + entry("INTERFACE=%s", channelData[chNum].chName.c_str())); return IPMI_CC_UNSPECIFIED_ERROR; } } @@ -787,45 +786,13 @@ EChannelProtocolType return static_cast<EChannelProtocolType>(it->second); } -uint8_t ChannelConfig::convertToChannelIndexNumber(const uint8_t chNum) -{ - - // TODO: There is limitation in current design. we cannot detect exact - // LAN interface(eth0 or eth1) so Implementation may be updated - // when there is any design update to figure out all the interfaces - // independently based on the message. - - static uint8_t curChannel = 0xFF; - - if (curChannel == 0xFF) - { - auto it = interfaceMap.find(getInterfaceIndex()); - if (it == interfaceMap.end()) - { - log<level::ERR>("Invalid Interface type ", - entry("InterfaceIndex: %d", getInterfaceIndex())); - throw std::invalid_argument("Invalid interface type."); - } - - for (auto& channel : channelData) - { - std::string& interfaceName = it->second; - if (channel.chName == interfaceName) - { - curChannel = channel.chID; - break; - } - } - } - return ((chNum == currentChNum) ? curChannel : chNum); -} - Json ChannelConfig::readJsonFile(const std::string& configFile) { std::ifstream jsonFile(configFile); if (!jsonFile.good()) { - log<level::ERR>("JSON file not found"); + log<level::INFO>("JSON file not found", + entry("FILE_NAME=%s", configFile.c_str())); return nullptr; } @@ -837,7 +804,7 @@ Json ChannelConfig::readJsonFile(const std::string& configFile) catch (Json::parse_error& e) { log<level::DEBUG>("Corrupted channel config.", - entry("MSG: %s", e.what())); + entry("MSG=%s", e.what())); throw std::runtime_error("Corrupted channel config file"); } @@ -847,17 +814,33 @@ Json ChannelConfig::readJsonFile(const std::string& configFile) int ChannelConfig::writeJsonFile(const std::string& configFile, const Json& jsonData) { - std::ofstream jsonFile(configFile); - if (!jsonFile.good()) + 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) + { + log<level::ERR>("Error in creating json file", + entry("FILE_NAME = %s", tmpFile.c_str())); + return -EIO; + } + const auto& writeData = jsonData.dump(); + if (write(fd, writeData.c_str(), writeData.size()) != + static_cast<ssize_t>(writeData.size())) { - log<level::ERR>("JSON file not found"); + close(fd); + log<level::ERR>("Error in writing configuration file", + entry("FILE_NAME = %s", tmpFile.c_str())); return -EIO; } + close(fd); - // Write JSON to file - jsonFile << jsonData; + if (std::rename(tmpFile.c_str(), configFile.c_str()) != 0) + { + log<level::ERR>("Error in renaming temporary data file", + entry("FILE_NAME = %s", tmpFile.c_str())); + return -EIO; + } - jsonFile.flush(); return 0; } @@ -900,7 +883,7 @@ int ChannelConfig::loadChannelConfig() { log<level::WARNING>( "Channel not configured so loading default.", - entry("CHANNEL_NUM:%d", chNum)); + entry("CHANNEL_NUM=%d", chNum)); // If user didn't want to configure specific channel (say // reserved channel), then load that index with default values. setDefaultChannelConfig(chNum, defaultChannelName); @@ -938,12 +921,12 @@ int ChannelConfig::loadChannelConfig() catch (const Json::exception& e) { log<level::DEBUG>("Json Exception caught.", - entry("MSG:%s", e.what())); + entry("MSG=%s", e.what())); return -EBADMSG; } catch (const std::invalid_argument& e) { - log<level::ERR>("Corrupted config.", entry("MSG:%s", e.what())); + log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what())); return -EBADMSG; } } @@ -997,7 +980,7 @@ int ChannelConfig::readChannelVolatileData() { log<level::ERR>( "Invalid/corrupted volatile channel access file", - entry("FILE: %s", channelVolatileDataFilename)); + entry("FILE=%s", channelVolatileDataFilename)); throw std::runtime_error( "Corrupted volatile channel access file"); } @@ -1005,12 +988,12 @@ int ChannelConfig::readChannelVolatileData() } catch (const Json::exception& e) { - log<level::DEBUG>("Json Exception caught.", entry("MSG:%s", e.what())); + log<level::DEBUG>("Json Exception caught.", entry("MSG=%s", e.what())); throw std::runtime_error("Corrupted volatile channel access file"); } catch (const std::invalid_argument& e) { - log<level::ERR>("Corrupted config.", entry("MSG:%s", e.what())); + log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what())); throw std::runtime_error("Corrupted volatile channel access file"); } @@ -1065,19 +1048,19 @@ int ChannelConfig::readChannelPersistData() else { log<level::ERR>("Invalid/corrupted nv channel access file", - entry("FILE:%s", channelNvDataFilename)); + entry("FILE=%s", channelNvDataFilename)); throw std::runtime_error("Corrupted nv channel access file"); } } } catch (const Json::exception& e) { - log<level::DEBUG>("Json Exception caught.", entry("MSG:%s", e.what())); + log<level::DEBUG>("Json Exception caught.", entry("MSG=%s", e.what())); throw std::runtime_error("Corrupted nv channel access file"); } catch (const std::invalid_argument& e) { - log<level::ERR>("Corrupted config.", entry("MSG: %s", e.what())); + log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what())); throw std::runtime_error("Corrupted nv channel access file"); } @@ -1120,7 +1103,7 @@ int ChannelConfig::writeChannelVolatileData() } catch (const std::invalid_argument& e) { - log<level::ERR>("Corrupted config.", entry("MSG: %s", e.what())); + log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what())); return -EINVAL; } @@ -1171,7 +1154,7 @@ int ChannelConfig::writeChannelPersistData() } catch (const std::invalid_argument& e) { - log<level::ERR>("Corrupted config.", entry("MSG: %s", e.what())); + log<level::ERR>("Corrupted config.", entry("MSG=%s", e.what())); return -EINVAL; } @@ -1245,10 +1228,10 @@ int ChannelConfig::setDbusProperty(const std::string& service, catch (const sdbusplus::exception::SdBusError& e) { log<level::DEBUG>("set-property failed", - entry("SERVICE:%s", service.c_str()), - entry("OBJPATH:%s", objPath.c_str()), - entry("INTERFACE:%s", interface.c_str()), - entry("PROP:%s", property.c_str())); + entry("SERVICE=%s", service.c_str()), + entry("OBJPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", interface.c_str()), + entry("PROP=%s", property.c_str())); return -EIO; } @@ -1275,10 +1258,10 @@ int ChannelConfig::getDbusProperty(const std::string& service, catch (const sdbusplus::exception::SdBusError& e) { log<level::DEBUG>("get-property failed", - entry("SERVICE:%s", service.c_str()), - entry("OBJPATH:%s", objPath.c_str()), - entry("INTERFACE:%s", interface.c_str()), - entry("PROP:%s", property.c_str())); + entry("SERVICE=%s", service.c_str()), + entry("OBJPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", interface.c_str()), + entry("PROP=%s", property.c_str())); return -EIO; } return 0; @@ -1305,13 +1288,13 @@ int ChannelConfig::syncNetworkChannelConfig() privilegePropertyString, variant)) { log<level::DEBUG>("Network interface does not exist", - entry("INTERFACE:%s", + entry("INTERFACE=%s", channelData[chNum].chName.c_str())); continue; } - intfPrivStr = variant_ns::get<std::string>(variant); + intfPrivStr = std::get<std::string>(variant); } - catch (const variant_ns::bad_variant_access& e) + catch (const std::bad_variant_access& e) { log<level::DEBUG>( "exception: Network interface does not exist"); @@ -1358,6 +1341,9 @@ int ChannelConfig::syncNetworkChannelConfig() void ChannelConfig::initChannelPersistData() { + boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> + channelLock{*channelMutex}; + /* Always read the channel config */ if (loadChannelConfig() != 0) { diff --git a/user_channel/channel_mgmt.hpp b/user_channel/channel_mgmt.hpp index 35bb494..80bd6d9 100644 --- a/user_channel/channel_mgmt.hpp +++ b/user_channel/channel_mgmt.hpp @@ -23,14 +23,14 @@ #include <ctime> #include <nlohmann/json.hpp> #include <sdbusplus/bus.hpp> +#include <variant> namespace ipmi { using Json = nlohmann::json; -using DbusVariant = - sdbusplus::message::variant<std::vector<std::string>, std::string, bool>; +using DbusVariant = std::variant<std::vector<std::string>, std::string, bool>; using DbusChObjProperties = std::vector<std::pair<std::string, DbusVariant>>; @@ -105,6 +105,18 @@ class ChannelConfig */ 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 @@ -212,14 +224,6 @@ class ChannelConfig */ CommandPrivilege convertToPrivLimitIndex(const std::string& value); - /** @brief function to convert channel number to channel index - * - * @param[in] chNum - channel number - * - * @return channel index - */ - uint8_t convertToChannelIndexNumber(const uint8_t chNum); - /** @brief function to write persistent channel configuration to config file * * @return 0 for success, -errno for failure. diff --git a/user_channel/channelcommands.cpp b/user_channel/channelcommands.cpp index d1b275e..adf19d5 100644 --- a/user_channel/channelcommands.cpp +++ b/user_channel/channelcommands.cpp @@ -14,11 +14,10 @@ // limitations under the License. */ -#include "channelcommands.hpp" - #include "apphandler.hpp" #include "channel_layer.hpp" +#include <ipmid/api.hpp> #include <phosphor-logging/log.hpp> #include <regex> @@ -27,241 +26,99 @@ using namespace phosphor::logging; namespace ipmi { -/** @struct SetChannelAccessReq - * - * Structure for set channel access request command (refer spec sec 22.22) - */ -struct SetChannelAccessReq -{ -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t chNum : 4; - uint8_t reserved_1 : 4; - uint8_t accessMode : 3; - uint8_t usrAuthDisabled : 1; - uint8_t msgAuthDisabled : 1; - uint8_t alertDisabled : 1; - uint8_t accessSetMode : 2; - uint8_t privLimit : 4; - uint8_t reserved_2 : 2; - uint8_t privSetMode : 2; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved_1 : 4; - uint8_t chNum : 4; - uint8_t accessSetMode : 2; - uint8_t alertDisabled : 1; - uint8_t msgAuthDisabled : 1; - uint8_t usrAuthDisabled : 1; - uint8_t accessMode : 3; - uint8_t privSetMode : 2; - uint8_t reserved_2 : 2; - uint8_t privLimit : 4; -#endif - -} __attribute__((packed)); - -/** @struct GetChannelAccessReq - * - * Structure for get channel access request command (refer spec sec 22.23) - */ -struct GetChannelAccessReq -{ -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t chNum : 4; - uint8_t reserved_1 : 4; - uint8_t reserved_2 : 6; - uint8_t accessSetMode : 2; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved_1 : 4; - uint8_t chNum : 4; - uint8_t accessSetMode : 2; - uint8_t reserved_2 : 6; -#endif -} __attribute__((packed)); - -/** @struct GetChannelAccessResp +static constexpr const uint8_t ccActionNotSupportedForChannel = 0x82; + +/** @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 * - * Structure for get channel access response command (refer spec sec 22.23) - */ -struct GetChannelAccessResp + * @ 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 BYTE_ORDER == LITTLE_ENDIAN - uint8_t accessMode : 3; - uint8_t usrAuthDisabled : 1; - uint8_t msgAuthDisabled : 1; - uint8_t alertDisabled : 1; - uint8_t reserved_1 : 2; - uint8_t privLimit : 4; - uint8_t reserved_2 : 4; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved_1 : 2; - uint8_t alertDisabled : 1; - uint8_t msgAuthDisabled : 1; - uint8_t usrAuthDisabled : 1; - uint8_t accessMode : 3; - uint8_t reserved_2 : 4; - uint8_t privLimit : 4; -#endif -} __attribute__((packed)); - -/** @struct GetChannelInfoReq - * - * Structure for get channel info request command (refer spec sec 22.24) - */ -struct GetChannelInfoReq -{ -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t chNum : 4; - uint8_t reserved_1 : 4; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved_1 : 4; - uint8_t chNum : 4; -#endif -} __attribute__((packed)); - -/** @struct GetChannelInfoResp - * - * Structure for get channel info response command (refer spec sec 22.24) - */ -struct GetChannelInfoResp -{ -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t chNum : 4; - uint8_t reserved_1 : 4; - uint8_t mediumType : 7; - uint8_t reserved_2 : 1; - uint8_t msgProtType : 5; - uint8_t reserved_3 : 3; - uint8_t actSessCount : 6; - uint8_t sessType : 2; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved_1 : 4; - uint8_t chNum : 4; - uint8_t reserved_2 : 1; - uint8_t mediumType : 7; - uint8_t reserved_3 : 3; - uint8_t msgProtType : 5; - uint8_t sessType : 2; - uint8_t actSessCount : 6; -#endif - uint8_t vendorId[3]; - uint8_t auxChInfo[2]; -} __attribute__((packed)); - -/** @struct GetChannelPayloadSupportReq - * - * Structure for get channel payload support command request (refer spec - * sec 24.8) - */ -struct GetChannelPayloadSupportReq -{ -#if BYTE_ORDER == LITTLE_ENDIAN - uint8_t chNum : 4; - uint8_t reserved : 4; -#endif -#if BYTE_ORDER == BIG_ENDIAN - uint8_t reserved : 4; - uint8_t chNum : 4; -#endif -} __attribute__((packed)); - -/** @struct GetChannelPayloadSupportResp - * - * Structure for get channel payload support command response (refer spec - * sec 24.8) - */ -struct GetChannelPayloadSupportResp -{ - uint8_t stdPayloadType[2]; - uint8_t sessSetupPayloadType[2]; - uint8_t OEMPayloadType[2]; - uint8_t reserved[2]; -} __attribute__((packed)); - -ipmi_ret_t ipmiSetChannelAccess(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) -{ - const SetChannelAccessReq* req = static_cast<SetChannelAccessReq*>(request); - size_t reqLength = *data_len; - - *data_len = 0; + const uint8_t chNum = + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); - if (reqLength != sizeof(*req)) - { - log<level::DEBUG>("Set channel access - Invalid Length"); - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - uint8_t chNum = convertCurrentChannelNum(req->chNum); - if (!isValidChannel(chNum) || req->reserved_1 != 0 || req->reserved_2 != 0) + if (!isValidChannel(chNum) || reserved1 != 0 || reserved2 != 0) { log<level::DEBUG>("Set channel access - Invalid field in request"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } - if (EChannelSessSupported::none == getChannelSessionSupport(chNum)) + if (getChannelSessionSupport(chNum) == EChannelSessSupported::none) { log<level::DEBUG>("Set channel access - No support on channel"); - return IPMI_CC_ACTION_NOT_SUPPORTED_FOR_CHANNEL; + return response(ccActionNotSupportedForChannel); } ChannelAccess chActData; ChannelAccess chNVData; uint8_t setActFlag = 0; uint8_t setNVFlag = 0; - ipmi_ret_t compCode = IPMI_CC_OK; + Cc compCode; - switch (req->accessSetMode) + // 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: - // Do nothing break; case nvData: - chNVData.accessMode = req->accessMode; - chNVData.userAuthDisabled = req->usrAuthDisabled; - chNVData.perMsgAuthDisabled = req->msgAuthDisabled; - chNVData.alertingDisabled = req->alertDisabled; + 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 = req->accessMode; - chActData.userAuthDisabled = req->usrAuthDisabled; - chActData.perMsgAuthDisabled = req->msgAuthDisabled; - chActData.alertingDisabled = req->alertDisabled; + 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: log<level::DEBUG>("Set channel access - Invalid access set mode"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } - switch (req->privSetMode) + // 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: - // Do nothing break; case nvData: - chNVData.privLimit = req->privLimit; + chNVData.privLimit = static_cast<uint8_t>(channelPrivLimit); setNVFlag |= setPrivLimit; break; case activeData: - chActData.privLimit = req->privLimit; + chActData.privLimit = static_cast<uint8_t>(channelPrivLimit); + setActFlag |= setPrivLimit; break; case reserved: default: log<level::DEBUG>("Set channel access - Invalid access priv mode"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } if (setNVFlag != 0) @@ -270,7 +127,7 @@ ipmi_ret_t ipmiSetChannelAccess(ipmi_netfn_t netfn, ipmi_cmd_t cmd, if (compCode != IPMI_CC_OK) { log<level::DEBUG>("Set channel access - Failed to set access data"); - return compCode; + return response(compCode); } } @@ -280,219 +137,250 @@ ipmi_ret_t ipmiSetChannelAccess(ipmi_netfn_t netfn, ipmi_cmd_t cmd, if (compCode != IPMI_CC_OK) { log<level::DEBUG>("Set channel access - Failed to set access data"); - return compCode; + return response(compCode); } } - return IPMI_CC_OK; + return responseSuccess(); } -ipmi_ret_t ipmiGetChannelAccess(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) +/** @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) { - const GetChannelAccessReq* req = static_cast<GetChannelAccessReq*>(request); - size_t reqLength = *data_len; - - *data_len = 0; - - if (reqLength != sizeof(*req)) - { - log<level::DEBUG>("Get channel access - Invalid Length"); - return IPMI_CC_REQ_DATA_LEN_INVALID; - } + const uint8_t chNum = + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); - uint8_t chNum = convertCurrentChannelNum(req->chNum); - if (!isValidChannel(chNum) || req->reserved_1 != 0 || req->reserved_2 != 0) + if (!isValidChannel(chNum) || reserved1 != 0 || reserved2 != 0) { log<level::DEBUG>("Get channel access - Invalid field in request"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } - if ((req->accessSetMode == doNotSet) || (req->accessSetMode == reserved)) + if ((accessSetMode == doNotSet) || (accessSetMode == reserved)) { log<level::DEBUG>("Get channel access - Invalid Access mode"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } - if (EChannelSessSupported::none == getChannelSessionSupport(chNum)) + if (getChannelSessionSupport(chNum) == EChannelSessSupported::none) { log<level::DEBUG>("Get channel access - No support on channel"); - return IPMI_CC_ACTION_NOT_SUPPORTED_FOR_CHANNEL; + return response(ccActionNotSupportedForChannel); } - GetChannelAccessResp* resp = static_cast<GetChannelAccessResp*>(response); - - std::fill(reinterpret_cast<uint8_t*>(resp), - reinterpret_cast<uint8_t*>(resp) + sizeof(*resp), 0); - ChannelAccess chAccess; - ipmi_ret_t compCode = IPMI_CC_OK; - if (req->accessSetMode == nvData) + Cc compCode; + + if (accessSetMode == nvData) { compCode = getChannelAccessPersistData(chNum, chAccess); } - else if (req->accessSetMode == activeData) + else if (accessSetMode == activeData) { compCode = getChannelAccessData(chNum, chAccess); } if (compCode != IPMI_CC_OK) { - return compCode; + return response(compCode); } - resp->accessMode = chAccess.accessMode; - resp->usrAuthDisabled = chAccess.userAuthDisabled; - resp->msgAuthDisabled = chAccess.perMsgAuthDisabled; - resp->alertDisabled = chAccess.alertingDisabled; - resp->privLimit = chAccess.privLimit; + constexpr uint2_t reservedOut1 = 0; + constexpr uint4_t reservedOut2 = 0; - *data_len = sizeof(*resp); - return IPMI_CC_OK; + return responseSuccess( + static_cast<uint3_t>(chAccess.accessMode), chAccess.userAuthDisabled, + chAccess.perMsgAuthDisabled, chAccess.alertingDisabled, reservedOut1, + static_cast<uint4_t>(chAccess.privLimit), reservedOut2); } -ipmi_ret_t ipmiGetChannelInfo(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) +/** @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) { - const GetChannelInfoReq* req = static_cast<GetChannelInfoReq*>(request); - size_t reqLength = *data_len; - - *data_len = 0; - - if (reqLength != sizeof(*req)) - { - log<level::DEBUG>("Get channel info - Invalid Length"); - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - uint8_t chNum = convertCurrentChannelNum(req->chNum); - if (!isValidChannel(chNum) || req->reserved_1 != 0) + uint8_t chNum = + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); + if (!isValidChannel(chNum) || reserved) { - log<level::DEBUG>("Get channel info - Invalid field in request"); - return IPMI_CC_INVALID_FIELD_REQUEST; - } - - // Check the existance of device for session-less channels. - if ((EChannelSessSupported::none != getChannelSessionSupport(chNum)) && - (!(doesDeviceExist(chNum)))) - { - log<level::DEBUG>("Get channel info - Device not exist"); - return IPMI_CC_PARM_OUT_OF_RANGE; + log<level::DEBUG>("Get channel access - Invalid field in request"); + return responseInvalidFieldRequest(); } - GetChannelInfoResp* resp = static_cast<GetChannelInfoResp*>(response); - - std::fill(reinterpret_cast<uint8_t*>(resp), - reinterpret_cast<uint8_t*>(resp) + sizeof(*resp), 0); - ChannelInfo chInfo; - ipmi_ret_t compCode = getChannelInfo(chNum, chInfo); - if (compCode != IPMI_CC_OK) + Cc compCode = getChannelInfo(chNum, chInfo); + if (compCode != ccSuccess) { - return compCode; + log<level::ERR>("Failed to get channel info", + entry("CHANNEL=%x", chNum), + entry("ERRNO=%x", compCode)); + return response(compCode); } - resp->chNum = chNum; - resp->mediumType = chInfo.mediumType; - resp->msgProtType = chInfo.protocolType; - resp->actSessCount = getChannelActiveSessions(chNum); - resp->sessType = chInfo.sessionSupported; - + 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) - resp->vendorId[0] = 0xF2; - resp->vendorId[1] = 0x1B; - resp->vendorId[2] = 0x00; - - // Auxiliary Channel info - byte 1:2 - // TODO: For System Interface(0xF) and OEM channel types, this needs - // to be changed acoordingly. - // All other channel types, its reverved - resp->auxChInfo[0] = 0x00; - resp->auxChInfo[1] = 0x00; + constexpr uint24_t vendorId = 7154; + constexpr uint16_t auxChInfo = 0; - *data_len = sizeof(*resp); - - return IPMI_CC_OK; + return responseSuccess(chNum, reserved1, mediumType, reserved2, + protocolType, reserved3, activeSessionCount, + sessionType, vendorId, auxChInfo); } -ipmi_ret_t ipmiGetChannelPayloadSupport(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) +namespace { - const auto req = static_cast<GetChannelPayloadSupportReq*>(request); - size_t reqLength = *data_len; - - *data_len = 0; - - if (reqLength != sizeof(*req)) - { - log<level::DEBUG>("Get channel payload - Invalid Length"); - return IPMI_CC_REQ_DATA_LEN_INVALID; - } +constexpr uint16_t standardPayloadBit(PayloadType p) +{ + return (1 << static_cast<size_t>(p)); +} - uint8_t chNum = convertCurrentChannelNum(req->chNum); - if (!isValidChannel(chNum) || req->reserved != 0) - { - log<level::DEBUG>("Get channel payload - Invalid field in request"); - return IPMI_CC_INVALID_FIELD_REQUEST; - } +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 - // Not supported on sessionless channels. - if (EChannelSessSupported::none == getChannelSessionSupport(chNum)) +/** @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 (!isValidChannel(chNum) || reserved) { - log<level::DEBUG>("Get channel payload - Sessionless Channel"); - return IPMI_CC_INVALID_FIELD_REQUEST; + log<level::DEBUG>("Get channel access - Invalid field in request"); + return responseInvalidFieldRequest(); } // Session support is available in active LAN channels. - if ((EChannelSessSupported::none != getChannelSessionSupport(chNum)) && - (!(doesDeviceExist(chNum)))) + if ((getChannelSessionSupport(chNum) == EChannelSessSupported::none) || + !(doesDeviceExist(chNum))) { log<level::DEBUG>("Get channel payload - Device not exist"); - return IPMI_CC_INVALID_FIELD_REQUEST; + return responseInvalidFieldRequest(); } - auto resp = static_cast<GetChannelPayloadSupportResp*>(response); + 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); +} - std::fill(reinterpret_cast<uint8_t*>(resp), - reinterpret_cast<uint8_t*>(resp) + sizeof(*resp), 0); +/** @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 - // TODO: Hard coding for now. - // Mapping PayloadTypes to 'GetChannelPayloadSupportResp' fields: - // -------------------------------------------------------------- - // Mask all except least 3 significant bits to get a value in the range of - // 0-7. This value maps to the bit position of given payload type in 'resp' - // fields. + * @returns IPMI completion code plus response data + * - formatVersion - BCD encoded format version info + */ - static constexpr uint8_t payloadByteMask = 0x07; - static constexpr uint8_t stdPayloadTypeIPMI = - 1 << (static_cast<uint8_t>(PayloadType::IPMI) & payloadByteMask); - static constexpr uint8_t stdPayloadTypeSOL = - 1 << (static_cast<uint8_t>(PayloadType::SOL) & payloadByteMask); +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); - static constexpr uint8_t sessPayloadTypeOpenReq = - 1 << (static_cast<uint8_t>(PayloadType::OPEN_SESSION_REQUEST) & - payloadByteMask); - static constexpr uint8_t sessPayloadTypeRAKP1 = - 1 << (static_cast<uint8_t>(PayloadType::RAKP1) & payloadByteMask); - static constexpr uint8_t sessPayloadTypeRAKP3 = - 1 << (static_cast<uint8_t>(PayloadType::RAKP3) & payloadByteMask); + if (reserved || !isValidChannel(channel) || + (getChannelSessionSupport(channel)) == EChannelSessSupported::none) + { + return responseInvalidFieldRequest(); + } + + if (!isValidPayloadType(static_cast<PayloadType>(payloadTypeNum))) + { + log<level::ERR>("Channel payload version - Payload type unavailable"); - resp->stdPayloadType[0] = stdPayloadTypeIPMI | stdPayloadTypeSOL; - // RMCP+ Open Session request, RAKP Message1 and RAKP Message3. - resp->sessSetupPayloadType[0] = - sessPayloadTypeOpenReq | sessPayloadTypeRAKP1 | sessPayloadTypeRAKP3; + constexpr uint8_t payloadTypeNotSupported = 0x80; + return response(payloadTypeNotSupported); + } - *data_len = sizeof(*resp); + // BCD encoded version representation - 1.0 + constexpr uint8_t formatVersion = 0x10; - return IPMI_CC_OK; + return responseSuccess(formatVersion); } void registerChannelFunctions() __attribute__((constructor)); @@ -500,19 +388,20 @@ void registerChannelFunctions() { ipmiChannelInit(); - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_CHANNEL_ACCESS, NULL, - ipmiSetChannelAccess, PRIVILEGE_ADMIN); + registerHandler(prioOpenBmcBase, netFnApp, app::cmdSetChannelAccess, + Privilege::Admin, ipmiSetChannelAccess); - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHANNEL_ACCESS, NULL, - ipmiGetChannelAccess, PRIVILEGE_USER); + registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelAccess, + Privilege::User, ipmiGetChannelAccess); - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHANNEL_INFO, NULL, - ipmiGetChannelInfo, PRIVILEGE_USER); + registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelInfoCommand, + Privilege::User, ipmiGetChannelInfo); - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHANNEL_PAYLOAD_SUPPORT, - NULL, ipmiGetChannelPayloadSupport, PRIVILEGE_USER); + registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadSupport, + Privilege::User, ipmiGetChannelPayloadSupport); - return; + registerHandler(prioOpenBmcBase, netFnApp, app::cmdGetChannelPayloadVersion, + Privilege::User, ipmiGetChannelPayloadVersion); } } // namespace ipmi diff --git a/user_channel/channelcommands.hpp b/user_channel/channelcommands.hpp deleted file mode 100644 index abcead6..0000000 --- a/user_channel/channelcommands.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/* -// 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 commands for channel command NETFN:APP - */ -enum ipmi_netfn_channel_cmds -{ - IPMI_CMD_SET_CHANNEL_ACCESS = 0x40, - IPMI_CMD_GET_CHANNEL_ACCESS = 0x41, - IPMI_CMD_GET_CHANNEL_INFO = 0x42, - IPMI_CMD_GET_CHANNEL_PAYLOAD_SUPPORT = 0x4E, -}; - -void registerChannelFunctions(); -} // namespace ipmi diff --git a/user_channel/passwd_mgr.cpp b/user_channel/passwd_mgr.cpp index 525b2b7..66bdef0 100644 --- a/user_channel/passwd_mgr.cpp +++ b/user_channel/passwd_mgr.cpp @@ -217,9 +217,10 @@ void PasswdMgr::initPasswordMap(void) char* outPtr = reinterpret_cast<char*>(dataBuf.data()); char* nToken = NULL; char* linePtr = strtok_r(outPtr, "\n", &nToken); - size_t userEPos = 0, lineSize = 0; + size_t lineSize = 0; while (linePtr != NULL) { + size_t userEPos = 0; std::string lineStr(linePtr); if ((userEPos = lineStr.find(":")) != std::string::npos) { @@ -355,11 +356,12 @@ int PasswdMgr::updatePasswdSpecialFile(const std::string& userName, if (inBytesLen != 0) { char* outPtr = reinterpret_cast<char*>(dataBuf.data()); - size_t userEPos = 0; char* nToken = NULL; char* linePtr = strtok_r(outPtr, "\n", &nToken); while (linePtr != NULL) { + size_t userEPos = 0; + std::string lineStr(linePtr); if ((userEPos = lineStr.find(":")) != std::string::npos) { @@ -441,7 +443,6 @@ int PasswdMgr::updatePasswdSpecialFile(const std::string& userName, log<level::DEBUG>("Error creating temp file"); return -EIO; } - fd = -1; // don't use fd anymore, as the File object owns it // Set the file mode as of actual ipmi-pass file. if (fchmod(fileno((temp)()), st.st_mode) < 0) diff --git a/user_channel/user_layer.cpp b/user_channel/user_layer.cpp index b241564..b309e86 100644 --- a/user_channel/user_layer.cpp +++ b/user_channel/user_layer.cpp @@ -88,6 +88,12 @@ ipmi_ret_t ipmiUserSetUserPassword(const uint8_t userId, return getUserAccessObject().setUserPassword(userId, userPassword); } +ipmi_ret_t ipmiSetSpecialUserPassword(const std::string& userName, + const std::string& userPassword) +{ + return getUserAccessObject().setSpecialUserPassword(userName, userPassword); +} + ipmi_ret_t ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers, uint8_t& fixedUsers) { @@ -164,4 +170,57 @@ ipmi_ret_t ipmiUserSetPrivilegeAccess(const uint8_t userId, const uint8_t chNum, userId, chNum, userPrivAccess, otherPrivUpdates); } +bool ipmiUserPamAuthenticate(std::string_view userName, + std::string_view userPassword) +{ + return pamUserCheckAuthenticate(userName, userPassword); +} + +ipmi_ret_t ipmiUserSetUserPayloadAccess(const uint8_t chNum, + const uint8_t operation, + const uint8_t userId, + const PayloadAccess& payloadAccess) +{ + + if (!UserAccess::isValidChannel(chNum)) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!UserAccess::isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + + return getUserAccessObject().setUserPayloadAccess(chNum, operation, userId, + payloadAccess); +} + +ipmi_ret_t ipmiUserGetUserPayloadAccess(const uint8_t chNum, + const uint8_t userId, + PayloadAccess& payloadAccess) +{ + + if (!UserAccess::isValidChannel(chNum)) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!UserAccess::isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + + 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 IPMI_CC_OK; +} + } // namespace ipmi diff --git a/user_channel/user_layer.hpp b/user_channel/user_layer.hpp index 5f3567a..450d878 100644 --- a/user_channel/user_layer.hpp +++ b/user_channel/user_layer.hpp @@ -16,6 +16,7 @@ #pragma once #include <ipmid/api.h> +#include <bitset> #include <string> namespace ipmi @@ -37,6 +38,7 @@ 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 * @@ -61,6 +63,19 @@ struct PrivAccess #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 IPMI_CC_OK for success, others for failure. @@ -138,6 +153,16 @@ ipmi_ret_t ipmiUserSetUserName(const uint8_t userId, const char* userName); ipmi_ret_t 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 IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiSetSpecialUserPassword(const std::string& userName, + const std::string& userPassword); + /** @brief get user name * * @param[in] userId - user id @@ -200,4 +225,41 @@ ipmi_ret_t 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 IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t 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 IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t 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 index f0cd7ad..102f990 100644 --- a/user_channel/user_mgmt.cpp +++ b/user_channel/user_mgmt.cpp @@ -16,6 +16,7 @@ #include "user_mgmt.hpp" #include "apphandler.hpp" +#include "channel_layer.hpp" #include <security/pam_appl.h> #include <sys/stat.h> @@ -31,6 +32,7 @@ #include <regex> #include <sdbusplus/bus/match.hpp> #include <sdbusplus/server/object.hpp> +#include <variant> #include <xyz/openbmc_project/Common/error.hpp> #include <xyz/openbmc_project/User/Common/error.hpp> @@ -103,13 +105,10 @@ static std::array<std::string, (PRIVILEGE_OEM + 1)> ipmiPrivIndex = { "priv-custom" // PRIVILEGE_OEM - 5 }; -namespace variant_ns = sdbusplus::message::variant_ns; - using namespace phosphor::logging; using Json = nlohmann::json; -using PrivAndGroupType = - sdbusplus::message::variant<std::string, std::vector<std::string>>; +using PrivAndGroupType = std::variant<std::string, std::vector<std::string>>; using NoResource = sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; @@ -198,12 +197,13 @@ UserAccess& getUserAccessObject() int getUserNameFromPath(const std::string& path, std::string& userName) { - static size_t pos = strlen(userObjBasePath) + 1; - if (path.find(userObjBasePath) == std::string::npos) + constexpr size_t length = strlen(userObjBasePath); + if (((length + 1) >= path.size()) || + path.compare(0, length, userObjBasePath)) { return -EINVAL; } - userName.assign(path, pos, path.size()); + userName.assign(path, length + 1, path.size()); return 0; } @@ -308,7 +308,7 @@ void userUpdatedSignalHandler(UserAccess& usrAccess, { static sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); std::string signal = msg.get_member(); - std::string userName, update, priv, newUserName; + std::string userName, priv, newUserName; std::vector<std::string> groups; bool enabled = false; UserUpdateEvent userEvent = UserUpdateEvent::reservedEvent; @@ -376,17 +376,17 @@ void userUpdatedSignalHandler(UserAccess& usrAccess, std::string member = prop.first; if (member == userPrivProperty) { - priv = variant_ns::get<std::string>(prop.second); + priv = std::get<std::string>(prop.second); userEvent = UserUpdateEvent::userPrivUpdated; } else if (member == userGrpProperty) { - groups = variant_ns::get<std::vector<std::string>>(prop.second); + groups = std::get<std::vector<std::string>>(prop.second); userEvent = UserUpdateEvent::userGrpUpdated; } else if (member == userEnabledProperty) { - enabled = variant_ns::get<bool>(prop.second); + enabled = std::get<bool>(prop.second); userEvent = UserUpdateEvent::userStateUpdated; } // Process based on event type. @@ -472,43 +472,8 @@ UserAccess::UserAccess() : bus(ipmid_get_sd_bus_connection()) userMutex = std::make_unique<boost::interprocess::named_recursive_mutex>( boost::interprocess::open_or_create, ipmiUserMutex); - initUserDataFile(); + cacheUserDataFile(); getSystemPrivAndGroups(); - sigHndlrLock = boost::interprocess::file_lock(ipmiUserDataFile); - // Register it for single object and single process either netipimd / - // host-ipmid - if (userUpdatedSignal == nullptr && sigHndlrLock.try_lock()) - { - log<level::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::message& 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::message& 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::message& msg) { - userUpdatedSignalHandler(*this, msg); - }); - signalHndlrObject = true; - } } UserInfo* UserAccess::getUserInfo(const uint8_t userId) @@ -683,33 +648,64 @@ static int pamFunctionConversation(int numMsg, const struct pam_message** msg, * @return status */ -bool pamUpdatePasswd(const char* username, const char* password) +int pamUpdatePasswd(const char* username, const char* password) { const struct pam_conv localConversation = {pamFunctionConversation, const_cast<char*>(password)}; pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start - if (pam_start("passwd", username, &localConversation, &localAuthHandle) != - PAM_SUCCESS) + 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 = NULL; // this gets set by pam_start + + if (pam_start("dropbear", username.data(), &localConversation, + &localAuthHandle) != PAM_SUCCESS) + { + log<level::ERR>("User Authentication Failure"); return false; } - int retval = pam_chauthtok(localAuthHandle, PAM_SILENT); + + int retval = pam_authenticate(localAuthHandle, + PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); if (retval != PAM_SUCCESS) { - if (retval == PAM_AUTHTOK_ERR) - { - log<level::DEBUG>("Authentication Failure"); - } - else - { - log<level::DEBUG>("pam_chauthtok returned failure", - entry("ERROR=%d", retval)); - } + log<level::DEBUG>("pam_authenticate returned failure", + entry("ERROR=%d", 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; @@ -717,33 +713,51 @@ bool pamUpdatePasswd(const char* username, const char* password) return true; } +ipmi_ret_t UserAccess::setSpecialUserPassword(const std::string& userName, + const std::string& userPassword) +{ + if (pamUpdatePasswd(userName.c_str(), userPassword.c_str()) != PAM_SUCCESS) + { + log<level::DEBUG>("Failed to update password"); + return IPMI_CC_UNSPECIFIED_ERROR; + } + return IPMI_CC_OK; +} + ipmi_ret_t UserAccess::setUserPassword(const uint8_t userId, const char* userPassword) { std::string userName; - if (ipmiUserGetUserName(userId, userName) != IPMI_CC_OK) + if (ipmiUserGetUserName(userId, userName) != ipmi::ccSuccess) { log<level::DEBUG>("User Name not found", - entry("USER-ID:%d", (uint8_t)userId)); - return IPMI_CC_PARM_OUT_OF_RANGE; + entry("USER-ID=%d", (uint8_t)userId)); + return ipmi::ccParmOutOfRange; } std::string passwd; passwd.assign(reinterpret_cast<const char*>(userPassword), 0, maxIpmi20PasswordSize); - if (!std::regex_match(passwd.c_str(), - std::regex("[a-zA-z_0-9][a-zA-Z_0-9,?:`!\"]*"))) - { - log<level::DEBUG>("Invalid password fields", - entry("USER-ID:%d", (uint8_t)userId)); - return IPMI_CC_INVALID_FIELD_REQUEST; - } - if (!pamUpdatePasswd(userName.c_str(), passwd.c_str())) + + int retval = pamUpdatePasswd(userName.c_str(), passwd.c_str()); + + switch (retval) { - log<level::DEBUG>("Failed to update password", - entry("USER-ID:%d", (uint8_t)userId)); - return IPMI_CC_UNSPECIFIED_ERROR; + case PAM_SUCCESS: + { + return ipmi::ccSuccess; + } + case PAM_AUTHTOK_ERR: + { + log<level::DEBUG>("Bad authentication token"); + return ipmi::ccInvalidFieldRequest; + } + default: + { + log<level::DEBUG>("Failed to update password", + entry("USER-ID=%d", (uint8_t)userId)); + return ipmi::ccUnspecifiedError; + } } - return IPMI_CC_OK; } ipmi_ret_t UserAccess::setUserEnabledState(const uint8_t userId, @@ -783,6 +797,60 @@ ipmi_ret_t UserAccess::setUserEnabledState(const uint8_t userId, return IPMI_CC_OK; } +ipmi_ret_t 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 IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + if (operation != enable && operation != disable) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + // 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) + { + log<level::ERR>("Write user data failed"); + return IPMI_CC_UNSPECIFIED_ERROR; + } + return IPMI_CC_OK; +} + ipmi_ret_t UserAccess::setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum, const UserPrivAccess& privAccess, @@ -878,6 +946,26 @@ ipmi_ret_t UserAccess::getUserName(const uint8_t userId, std::string& userName) return IPMI_CC_OK; } +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; +} + ipmi_ret_t UserAccess::setUserName(const uint8_t userId, const char* userNameInChar) { @@ -923,6 +1011,10 @@ ipmi_ret_t UserAccess::setUserName(const uint8_t userId, { try { + if (!isIpmiInAvailableGroupList()) + { + return IPMI_CC_UNSPECIFIED_ERROR; + } // Create new user auto method = bus.new_method_call( getUserServiceName().c_str(), userMgrObjBasePath, @@ -990,6 +1082,98 @@ 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>& + oemPayload, + 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() { @@ -1013,6 +1197,7 @@ void UserAccess::readUserData() 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) { @@ -1036,6 +1221,48 @@ void UserAccess::readUserData() 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) + { + log<level::ERR>("Error in reading IPMI user data file - " + "payload properties corrupted"); + throw std::runtime_error( + "Corrupted IPMI user data file - payload properties"); + } + } + } + catch (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 || @@ -1058,6 +1285,8 @@ void UserAccess::readUserData() 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 = @@ -1078,15 +1307,6 @@ void UserAccess::writeUserData() boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> userLock{*userMutex}; - static std::string tmpFile{std::string(ipmiUserDataFile) + "_tmp"}; - std::ofstream oUsrData(tmpFile, std::ios::out | std::ios::binary); - if (!oUsrData.good()) - { - log<level::ERR>("Error in creating temporary IPMI user data file"); - throw std::ios_base::failure( - "Error in creating temporary IPMI user data file"); - } - Json jsonUsersTbl = Json::array(); // user index 0 is reserved, starts with 1 for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex) @@ -1099,6 +1319,12 @@ void UserAccess::writeUserData() 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] = @@ -1118,12 +1344,35 @@ void UserAccess::writeUserData() 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); } - oUsrData << jsonUsersTbl; - oUsrData.flush(); - oUsrData.close(); + 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) + { + log<level::ERR>("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); + log<level::ERR>("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) { @@ -1252,13 +1501,11 @@ void UserAccess::getSystemPrivAndGroups() auto key = t.first; if (key == allPrivProperty) { - availablePrivileges = - variant_ns::get<std::vector<std::string>>(t.second); + availablePrivileges = std::get<std::vector<std::string>>(t.second); } else if (key == allGrpProperty) { - availableGroups = - variant_ns::get<std::vector<std::string>>(t.second); + availableGroups = std::get<std::vector<std::string>>(t.second); } } // TODO: Implement Supported Privilege & Groups verification logic @@ -1285,15 +1532,15 @@ void UserAccess::getUserProperties(const DbusUserObjProperties& properties, std::string key = t.first; if (key == userPrivProperty) { - usrPriv = variant_ns::get<std::string>(t.second); + usrPriv = std::get<std::string>(t.second); } else if (key == userGrpProperty) { - usrGrps = variant_ns::get<std::vector<std::string>>(t.second); + usrGrps = std::get<std::vector<std::string>>(t.second); } else if (key == userEnabledProperty) { - usrEnabled = variant_ns::get<bool>(t.second); + usrEnabled = std::get<bool>(t.second); } } return; @@ -1312,7 +1559,7 @@ int UserAccess::getUserObjProperties(const DbusUserObjValue& userObjs, return -EIO; } -void UserAccess::initUserDataFile() +void UserAccess::cacheUserDataFile() { boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> userLock{*userMutex}; @@ -1331,10 +1578,49 @@ void UserAccess::initUserDataFile() { usersTbl.user[userIndex].userPrivAccess[chIndex].privilege = privNoAccess; + usersTbl.user[userIndex] + .payloadAccess[chIndex] + .stdPayloadEnables1[static_cast<uint8_t>( + ipmi::PayloadType::SOL)] = true; } } writeUserData(); } + sigHndlrLock = boost::interprocess::file_lock(ipmiUserDataFile); + // Register it for single object and single process either netipimd / + // host-ipmid + if (userUpdatedSignal == nullptr && sigHndlrLock.try_lock()) + { + log<level::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::message& 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::message& 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::message& msg) { + userUpdatedSignalHandler(*this, msg); + }); + signalHndlrObject = true; + } std::map<DbusUserObjPath, DbusUserObjValue> managedObjs; try { @@ -1351,7 +1637,7 @@ void UserAccess::initUserDataFile() entry("PATH=%s", userMgrObjBasePath)); return; } - + bool updateRequired = false; UsersTbl* userData = &usersTbl; // user index 0 is reserved, starts with 1 for (size_t usrIdx = 1; usrIdx <= ipmiMaxUsers; ++usrIdx) @@ -1361,7 +1647,6 @@ void UserAccess::initUserDataFile() { std::vector<std::string> usrGrps; std::string usrPriv; - bool usrEnabled; std::string userName( reinterpret_cast<char*>(userData->user[usrIdx].userName), 0, @@ -1372,12 +1657,15 @@ void UserAccess::initUserDataFile() 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); } @@ -1393,6 +1681,7 @@ void UserAccess::initUserDataFile() .userPrivAccess[getUsrMgmtSyncIndex()] .privilege != priv) { + updateRequired = true; for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { @@ -1403,6 +1692,7 @@ void UserAccess::initUserDataFile() } if (userData->user[usrIdx].userEnabled != usrEnabled) { + updateRequired = true; userData->user[usrIdx].userEnabled = usrEnabled; } } @@ -1412,6 +1702,7 @@ void UserAccess::initUserDataFile() } else { + updateRequired = true; deleteUserIndex(usrIdx); } } @@ -1423,7 +1714,7 @@ void UserAccess::initUserDataFile() { std::vector<std::string> usrGrps; std::string usrPriv, userName; - bool usrEnabled; + bool usrEnabled = false; std::string usrObjPath = std::string(usrObj.first); if (getUserNameFromPath(usrObj.first.str, userName) != 0) { @@ -1435,6 +1726,7 @@ void UserAccess::initUserDataFile() if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) != usrGrps.end()) { + updateRequired = true; // CREATE NEW USER if (true != addUserEntry(userName, usrPriv, usrEnabled)) { @@ -1443,8 +1735,11 @@ void UserAccess::initUserDataFile() } } - // All userData slots update done. Lets write the data - writeUserData(); + if (updateRequired) + { + // All userData slots update done. Lets write the data + writeUserData(); + } return; } diff --git a/user_channel/user_mgmt.hpp b/user_channel/user_mgmt.hpp index 9ea9f6b..159b15c 100644 --- a/user_channel/user_mgmt.hpp +++ b/user_channel/user_mgmt.hpp @@ -16,19 +16,19 @@ #pragma once #include "user_layer.hpp" -#include <ipmid/api.h> - #include <boost/interprocess/sync/file_lock.hpp> #include <boost/interprocess/sync/named_recursive_mutex.hpp> #include <cstdint> #include <ctime> +#include <ipmid/api.hpp> #include <sdbusplus/bus.hpp> +#include <variant> namespace ipmi { using DbusUserPropVariant = - sdbusplus::message::variant<std::vector<std::string>, std::string, bool>; + std::variant<std::vector<std::string>, std::string, bool>; using DbusUserObjPath = sdbusplus::message::object_path; @@ -74,6 +74,7 @@ struct UserInfo bool userEnabled; bool userInSystem; bool fixedUserName; + PayloadAccess payloadAccess[ipmiMaxChannels]; }; /** @struct UsersTbl @@ -86,6 +87,16 @@ struct UsersTbl 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(); @@ -155,6 +166,12 @@ class UserAccess */ bool isValidUserName(const char* userNameInChar); + /** @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 @@ -217,6 +234,16 @@ class UserAccess */ ipmi_ret_t 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 IPMI_CC_OK for success, others for failure. + */ + ipmi_ret_t setSpecialUserPassword(const std::string& userName, + const std::string& userPassword); + /** @brief to set user privilege and access details * * @param[in] userId - user id @@ -231,6 +258,56 @@ class UserAccess 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 IPMI_CC_OK for success, others for failure. + */ + ipmi_ret_t 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 * */ @@ -321,8 +398,9 @@ class UserAccess void getSystemPrivAndGroups(); /** @brief function to init user data from configuration & D-Bus objects + * and to register for signals * */ - void initUserDataFile(); + void cacheUserDataFile(); }; } // namespace ipmi diff --git a/user_channel/usercommands.cpp b/user_channel/usercommands.cpp index 489eeaf..3396d2d 100644 --- a/user_channel/usercommands.cpp +++ b/user_channel/usercommands.cpp @@ -37,6 +37,8 @@ static constexpr uint8_t setPassword = 0x02; static constexpr uint8_t testPassword = 0x03; static constexpr uint8_t passwordKeySize20 = 1; static constexpr uint8_t passwordKeySize16 = 0; +static constexpr uint8_t enableOperation = 0x00; +static constexpr uint8_t disableOperation = 0x01; /** @struct SetUserNameReq * @@ -130,7 +132,7 @@ ipmi::RspType<> ipmiSetUserAccess(ipmi::Context::ptr ctx, uint4_t channel, { uint8_t sessLimit = sessionLimit.value_or(0); uint8_t chNum = - convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx); + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); if (reserved1 != 0 || reserved2 != 0 || sessLimit != 0 || (!isValidChannel(chNum)) || (!ipmiUserIsValidPrivilege(static_cast<uint8_t>(privilege))) || @@ -199,7 +201,7 @@ ipmi::RspType<uint6_t, // max channel users uint6_t userId, uint2_t reserved2) { uint8_t chNum = - convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx); + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); if (reserved1 != 0 || reserved2 != 0 || (!isValidChannel(chNum)) || (EChannelSessSupported::none == getChannelSessionSupport(chNum))) { @@ -307,7 +309,7 @@ ipmi_ret_t ipmiGetUserName(ipmi_netfn_t netfn, ipmi_cmd_t cmd, if (ipmiUserGetUserName(req->userId, userName) != IPMI_CC_OK) { // Invalid User ID log<level::DEBUG>("User Name not found", - entry("USER-ID:%d", (uint8_t)req->userId)); + entry("USER-ID=%d", (uint8_t)req->userId)); return IPMI_CC_PARM_OUT_OF_RANGE; } GetUserNameResp* resp = static_cast<GetUserNameResp*>(response); @@ -366,7 +368,7 @@ ipmi_ret_t ipmiSetUserPassword(ipmi_netfn_t netfn, ipmi_cmd_t cmd, if (ipmiUserGetUserName(req->userId, userName) != IPMI_CC_OK) { log<level::DEBUG>("User Name not found", - entry("USER-ID:%d", (uint8_t)req->userId)); + entry("USER-ID=%d", (uint8_t)req->userId)); return IPMI_CC_PARM_OUT_OF_RANGE; } if (req->operation == setPassword) @@ -391,7 +393,7 @@ ipmi_ret_t ipmiSetUserPassword(ipmi_netfn_t netfn, ipmi_cmd_t cmd, if (password != testPassword) { log<level::DEBUG>("Test password failed", - entry("USER-ID:%d", (uint8_t)req->userId)); + entry("USER-ID=%d", (uint8_t)req->userId)); return static_cast<ipmi_ret_t>( IPMISetPasswordReturnCodes::ipmiCCPasswdFailMismatch); } @@ -450,7 +452,7 @@ ipmi::RspType<uint8_t, // channel number { uint8_t channel = - convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx); + convertCurrentChannelNum(static_cast<uint8_t>(chNum), ctx->channel); if (reserved1 || reserved2 || !isValidChannel(channel) || !isValidPrivLimit(static_cast<uint8_t>(privLevel)) || @@ -485,10 +487,192 @@ ipmi::RspType<uint8_t, // channel number 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) +{ + // 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) + { + return ipmi::responseInvalidFieldRequest(); + } + + auto chNum = + convertCurrentChannelNum(static_cast<uint8_t>(channel), ctx->channel); + if ((operation != enableOperation && operation != disableOperation) || + (!isValidChannel(chNum)) || + (getChannelSessionSupport(chNum) == EChannelSessSupported::none)) + { + return ipmi::responseInvalidFieldRequest(); + } + + if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId))) + { + return ipmi::responseParmOutOfRange(); + } + + PayloadAccess payloadAccess = {0}; + 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 != 0 || reserved2 != 0 || (!isValidChannel(chNum)) || + (getChannelSessionSupport(chNum) == EChannelSessSupported::none)) + { + return ipmi::responseInvalidFieldRequest(); + } + 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 != IPMI_CC_OK) + { + 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() { - ipmiUserInit(); + post_work([]() { ipmiUserInit(); }); ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, ipmi::app::cmdSetUserAccessCommand, ipmi::Privilege::Admin, ipmiSetUserAccess); @@ -510,6 +694,15 @@ void registerUserIpmiFunctions() 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 |