| // SPDX-License-Identifier: Apache-2.0 |
| // Copyright (C) 2018 IBM Corp. |
| |
| #include "config.h" |
| |
| #include "hiomap.hpp" |
| |
| #include <endian.h> |
| #include <host-ipmid/ipmid-api.h> |
| #include <string.h> |
| #include <systemd/sd-bus.h> |
| |
| #include <fstream> |
| #include <functional> |
| #include <host-ipmid/ipmid-host-cmd-utils.hpp> |
| #include <host-ipmid/ipmid-host-cmd.hpp> |
| #include <iostream> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/exception.hpp> |
| |
| using namespace sdbusplus; |
| using namespace phosphor::host::command; |
| |
| static void register_openpower_hiomap_commands() __attribute__((constructor)); |
| |
| namespace openpower |
| { |
| namespace flash |
| { |
| constexpr auto BMC_EVENT_DAEMON_READY = 1 << 7; |
| constexpr auto BMC_EVENT_FLASH_CTRL_LOST = 1 << 6; |
| constexpr auto BMC_EVENT_WINDOW_RESET = 1 << 1; |
| constexpr auto BMC_EVENT_PROTOCOL_RESET = 1 << 0; |
| |
| constexpr auto IPMI_CMD_HIOMAP_EVENT = 0x0f; |
| |
| constexpr auto HIOMAPD_SERVICE = "xyz.openbmc_project.Hiomapd"; |
| constexpr auto HIOMAPD_OBJECT = "/xyz/openbmc_project/Hiomapd"; |
| constexpr auto HIOMAPD_IFACE = "xyz.openbmc_project.Hiomapd.Protocol"; |
| constexpr auto HIOMAPD_IFACE_V2 = "xyz.openbmc_project.Hiomapd.Protocol.V2"; |
| |
| constexpr auto DBUS_IFACE_PROPERTIES = "org.freedesktop.DBus.Properties"; |
| |
| struct hiomap |
| { |
| bus::bus *bus; |
| |
| /* Signals */ |
| bus::match::match *properties; |
| bus::match::match *window_reset; |
| bus::match::match *bmc_reboot; |
| |
| /* Protocol state */ |
| std::map<std::string, int> event_lookup; |
| uint8_t bmc_events; |
| }; |
| |
| /* TODO: Replace get/put with packed structs and direct assignment */ |
| template <typename T> static inline T get(void *buf) |
| { |
| T t; |
| memcpy(&t, buf, sizeof(t)); |
| return t; |
| } |
| |
| template <typename T> static inline void put(void *buf, T &&t) |
| { |
| memcpy(buf, &t, sizeof(t)); |
| } |
| |
| typedef ipmi_ret_t (*hiomap_command)(ipmi_request_t req, ipmi_response_t resp, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context); |
| |
| struct errno_cc_entry |
| { |
| int err; |
| int cc; |
| }; |
| |
| static const errno_cc_entry errno_cc_map[] = { |
| {0, IPMI_CC_OK}, |
| {EBUSY, IPMI_CC_BUSY}, |
| {ENOTSUP, IPMI_CC_INVALID}, |
| {ETIMEDOUT, 0xc3}, /* FIXME: Replace when defined in ipmid-api.h */ |
| {ENOSPC, 0xc4}, /* FIXME: Replace when defined in ipmid-api.h */ |
| {EINVAL, IPMI_CC_PARM_OUT_OF_RANGE}, |
| {ENODEV, IPMI_CC_SENSOR_INVALID}, |
| {EPERM, IPMI_CC_INSUFFICIENT_PRIVILEGE}, |
| {EACCES, IPMI_CC_INSUFFICIENT_PRIVILEGE}, |
| {-1, IPMI_CC_UNSPECIFIED_ERROR}, |
| }; |
| |
| static int hiomap_xlate_errno(int err) |
| { |
| const errno_cc_entry *entry = &errno_cc_map[0]; |
| |
| while (!(entry->err == err || entry->err == -1)) |
| { |
| entry++; |
| } |
| |
| return entry->cc; |
| } |
| |
| static void ipmi_hiomap_event_response(IpmiCmdData cmd, bool status) |
| { |
| using namespace phosphor::logging; |
| |
| if (!status) |
| { |
| log<level::ERR>("Failed to deliver host command", |
| entry("SEL_COMMAND=%x:%x", cmd.first, cmd.second)); |
| } |
| } |
| |
| static int hiomap_handle_property_update(struct hiomap *ctx, |
| sdbusplus::message::message &msg) |
| { |
| std::map<std::string, sdbusplus::message::variant<bool>> msgData; |
| |
| std::string iface; |
| msg.read(iface, msgData); |
| |
| for (auto const &x : msgData) |
| { |
| if (!ctx->event_lookup.count(x.first)) |
| { |
| /* Unsupported event? */ |
| continue; |
| } |
| |
| uint8_t mask = ctx->event_lookup[x.first]; |
| auto value = sdbusplus::message::variant_ns::get<bool>(x.second); |
| |
| if (value) |
| { |
| ctx->bmc_events |= mask; |
| } |
| else |
| { |
| ctx->bmc_events &= ~mask; |
| } |
| } |
| |
| auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events); |
| |
| ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response)); |
| |
| return 0; |
| } |
| |
| static bus::match::match hiomap_match_properties(struct hiomap *ctx) |
| { |
| auto properties = |
| bus::match::rules::propertiesChanged(HIOMAPD_OBJECT, HIOMAPD_IFACE_V2); |
| |
| bus::match::match match( |
| *ctx->bus, properties, |
| std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1)); |
| |
| return match; |
| } |
| |
| static int hiomap_handle_signal_v2(struct hiomap *ctx, const char *name) |
| { |
| ctx->bmc_events |= ctx->event_lookup[name]; |
| |
| auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events); |
| |
| ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response)); |
| |
| return 0; |
| } |
| |
| static bus::match::match hiomap_match_signal_v2(struct hiomap *ctx, |
| const char *name) |
| { |
| using namespace bus::match; |
| |
| auto signals = rules::type::signal() + rules::path(HIOMAPD_OBJECT) + |
| rules::interface(HIOMAPD_IFACE_V2) + rules::member(name); |
| |
| bus::match::match match(*ctx->bus, signals, |
| std::bind(hiomap_handle_signal_v2, ctx, name)); |
| |
| return match; |
| } |
| |
| static ipmi_ret_t hiomap_reset(ipmi_request_t request, ipmi_response_t response, |
| ipmi_data_len_t data_len, ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE, "Reset"); |
| try |
| { |
| ctx->bus->call(m); |
| |
| *data_len = 0; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static ipmi_ret_t hiomap_get_info(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| if (*data_len < 1) |
| { |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| |
| uint8_t *reqdata = (uint8_t *)request; |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE, "GetInfo"); |
| m.append(reqdata[0]); |
| |
| try |
| { |
| auto reply = ctx->bus->call(m); |
| |
| uint8_t version; |
| uint8_t blockSizeShift; |
| uint16_t timeout; |
| reply.read(version, blockSizeShift, timeout); |
| |
| uint8_t *respdata = (uint8_t *)response; |
| |
| /* FIXME: Assumes v2! */ |
| put(&respdata[0], version); |
| put(&respdata[1], blockSizeShift); |
| put(&respdata[2], htole16(timeout)); |
| |
| *data_len = 4; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static ipmi_ret_t hiomap_get_flash_info(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE_V2, "GetFlashInfo"); |
| try |
| { |
| auto reply = ctx->bus->call(m); |
| |
| uint16_t flashSize, eraseSize; |
| reply.read(flashSize, eraseSize); |
| |
| uint8_t *respdata = (uint8_t *)response; |
| put(&respdata[0], htole16(flashSize)); |
| put(&respdata[2], htole16(eraseSize)); |
| |
| *data_len = 4; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static ipmi_ret_t hiomap_create_window(struct hiomap *ctx, bool ro, |
| ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len) |
| { |
| if (*data_len < 4) |
| { |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| |
| uint8_t *reqdata = (uint8_t *)request; |
| auto windowType = ro ? "CreateReadWindow" : "CreateWriteWindow"; |
| |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE_V2, windowType); |
| m.append(le16toh(get<uint16_t>(&reqdata[0]))); |
| m.append(le16toh(get<uint16_t>(&reqdata[2]))); |
| |
| try |
| { |
| auto reply = ctx->bus->call(m); |
| |
| uint16_t lpcAddress, size, offset; |
| reply.read(lpcAddress, size, offset); |
| |
| uint8_t *respdata = (uint8_t *)response; |
| |
| /* FIXME: Assumes v2! */ |
| put(&respdata[0], htole16(lpcAddress)); |
| put(&respdata[2], htole16(size)); |
| put(&respdata[4], htole16(offset)); |
| |
| *data_len = 6; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static ipmi_ret_t hiomap_create_read_window(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| return hiomap_create_window(ctx, true, request, response, data_len); |
| } |
| |
| static ipmi_ret_t hiomap_create_write_window(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| return hiomap_create_window(ctx, false, request, response, data_len); |
| } |
| |
| static ipmi_ret_t hiomap_close_window(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| if (*data_len < 1) |
| { |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| |
| uint8_t *reqdata = (uint8_t *)request; |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE_V2, "CloseWindow"); |
| m.append(reqdata[0]); |
| |
| try |
| { |
| auto reply = ctx->bus->call(m); |
| |
| *data_len = 0; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static ipmi_ret_t hiomap_mark_dirty(ipmi_request_t request, |
| ipmi_response_t response, |
| ipmi_data_len_t data_len, |
| ipmi_context_t context) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| if (*data_len < 4) |
| { |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| |
| uint8_t *reqdata = (uint8_t *)request; |
| auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT, |
| HIOMAPD_IFACE_V2, "MarkDirty"); |
| /* FIXME: Assumes v2 */ |
| m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */ |
| m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */ |
| |
| try |
| { |
| auto reply = ctx->bus->call(m); |
| |
| *data_len = 0; |
| } |
| catch (const exception::SdBusError &e) |
| { |
| return hiomap_xlate_errno(e.get_errno()); |
| } |
| |
| return IPMI_CC_OK; |
| } |
| |
| static const hiomap_command hiomap_commands[] = { |
| [0] = NULL, /* 0 is an invalid command ID */ |
| [1] = hiomap_reset, |
| [2] = hiomap_get_info, |
| [3] = hiomap_get_flash_info, |
| [4] = hiomap_create_read_window, |
| [5] = hiomap_close_window, |
| [6] = hiomap_create_write_window, |
| [7] = hiomap_mark_dirty, |
| }; |
| |
| /* FIXME: Define this in the "right" place, wherever that is */ |
| /* FIXME: Double evaluation */ |
| #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) |
| |
| static ipmi_ret_t hiomap_dispatch(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) |
| { |
| struct hiomap *ctx = static_cast<struct hiomap *>(context); |
| |
| if (*data_len < 2) |
| { |
| *data_len = 0; |
| return IPMI_CC_REQ_DATA_LEN_INVALID; |
| } |
| |
| uint8_t *ipmi_req = (uint8_t *)request; |
| uint8_t *ipmi_resp = (uint8_t *)response; |
| uint8_t hiomap_cmd = ipmi_req[0]; |
| |
| if (hiomap_cmd == 0 || hiomap_cmd > ARRAY_SIZE(hiomap_commands) - 1) |
| { |
| *data_len = 0; |
| return IPMI_CC_PARM_OUT_OF_RANGE; |
| } |
| uint8_t *flash_req = ipmi_req + 2; |
| size_t flash_len = *data_len - 2; |
| uint8_t *flash_resp = ipmi_resp + 2; |
| |
| ipmi_ret_t cc = |
| hiomap_commands[hiomap_cmd](flash_req, flash_resp, &flash_len, context); |
| if (cc != IPMI_CC_OK) |
| { |
| *data_len = 0; |
| return cc; |
| } |
| |
| /* Populate the response command and sequence */ |
| ipmi_resp[0] = hiomap_cmd; |
| ipmi_resp[1] = ipmi_req[1]; |
| |
| *data_len = flash_len + 2; |
| |
| return cc; |
| } |
| } // namespace flash |
| } // namespace openpower |
| |
| static void register_openpower_hiomap_commands() |
| { |
| using namespace openpower::flash; |
| |
| /* FIXME: Clean this up? Can we unregister? */ |
| struct hiomap *ctx = new hiomap(); |
| |
| /* Initialise mapping from signal and property names to status bit */ |
| ctx->event_lookup["DaemonReady"] = BMC_EVENT_DAEMON_READY; |
| ctx->event_lookup["FlashControlLost"] = BMC_EVENT_FLASH_CTRL_LOST; |
| ctx->event_lookup["WindowReset"] = BMC_EVENT_WINDOW_RESET; |
| ctx->event_lookup["ProtocolReset"] = BMC_EVENT_PROTOCOL_RESET; |
| |
| ctx->bus = new bus::bus(ipmid_get_sd_bus_connection()); |
| |
| /* Initialise signal handling */ |
| |
| /* |
| * Can't use temporaries here because that causes SEGFAULTs due to slot |
| * destruction (!?), so enjoy the weird wrapping. |
| */ |
| ctx->properties = |
| new bus::match::match(std::move(hiomap_match_properties(ctx))); |
| ctx->bmc_reboot = new bus::match::match( |
| std::move(hiomap_match_signal_v2(ctx, "ProtocolReset"))); |
| ctx->window_reset = new bus::match::match( |
| std::move(hiomap_match_signal_v2(ctx, "WindowReset"))); |
| |
| ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_HIOMAP, ctx, |
| openpower::flash::hiomap_dispatch, SYSTEM_INTERFACE); |
| } |