blob: 37debaac5f833572842291cee5af5fa8205b00b2 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2018 IBM Corp.
#include "hiomap.hpp"
#include <endian.h>
#include <ipmid/api.h>
#include <signal.h>
#include <string.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include <cassert>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <ipmid-host/cmd-utils.hpp>
#include <ipmid-host/cmd.hpp>
#include <ipmid/api.hpp>
#include <map>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/exception.hpp>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
/*
Design and integration notes
============================
The primary motivation of the Host I/O Mapping protocol (HIOMAP) is to mediate
host access to a BMC-controlled flash chip housing the host's boot firmware.
openpower-host-ipmi-flash facilitates the system design of transporting the
HIOMAP protocol[1] over IPMI. This is somewhat abusive of IPMI, basically
treating the BT interface as a mailbox with an interrupt each way between the
BMC and the host.
[1] https://github.com/openbmc/mboxbridge/blob/master/Documentation/protocol.md
Using IPMI in this way has a number of challenges, a lot of them on the host
side where we need to bring up the LPC and BT interfaces to enable IPMI before
accessing the flash, and before any interrupts are enabled. There are also
challenges on the BMC side with the design of the current implementation. We
will cover those here.
BMC-side System Design and Integration Issues
---------------------------------------------
The current design is that we have the HIOMAP daemon, mboxd (to be renamed),
exposing a set of DBus interfaces. Whilst the spec defines the IPMI transport
message packing, mboxd knows nothing of IPMI itself, instead relying on the
DBus interface to receive messages from ipmid. ipmid in-turn knows nothing of
the interfaces communicating with it, also relying on DBus to receive messages
from interface-specific daemons, e.g. btbridged[2].
[2] https://github.com/openbmc/btbridge
For this design to function correctly we must ensure that the daemons are
started and shut down in a reasonable order, however defining that order is
somewhat tricky:
1. systemd uses Wants=/Before=/After= relationships in units to define both
start-up *and* shutdown order, in stack push / pop order respectively.
2. Clearly ipmid depends on btbridged to receive messages sent by signals and
replied to by method calls, so it needs a Wants=/After= relationship on
btbridged
3. mboxd depends on ipmid to receive messages sent by method call, and issues a
PropertiesChanged signal to notify of state changes.
Point 3. suggests mboxd should have a Wants=/Before= relationship with ipmid to
ensure ipmid can call into mboxd as messages arrive. However, this causes some
grief with shutdown of the BMC, as mboxd needs to issue a state-change
notification when it is shut down to inform the host that will not repsond to
future requests and that the protocol state has been reset. If mboxd has a
Wants=/Before= relationship with ipmid this message will never propagate to the
host, as ipmid will be shut by systemd before mboxd.
The above leads to mboxd having a Wants=/After= relationship with ipmid. This
ensures that if mboxd is restarted on its own the correct state changes will be
propagated to the host. The case where ipmid attempts to call into mboxd's DBus
interface before mboxd is ready is mitigated by the ready bit in the protocol's
BMC status, which will not yet be set, preventing a conforming host from
attempting to contact mboxd.
While this ordering prevents mboxd from being terminated before ipmid, there is
no control over the *scheduling* of processes to ensure the PropertiesChanged
signal emitted by mboxd before mboxd is terminated is seen by ipmid before
*ipmid* is also terminated. This leads to our first implementation wart:
On the basis that mboxd has a Wants=/After= relationship with ipmid,
openpower-host-ipmi-flash will emit an HIOMAP BMC status event to the host
with the value BMC_EVENT_PROTOCOL_RESET upon receiving SIGTERM iff the BMC
state is not already set to BMC_EVENT_PROTOCOL_RESET.
If ipmid has received SIGTERM the assumption is that it is systemd that is
sending it, and that the Wants=/After= relationship requires that mboxd has
been terminated before ipmid receives SIGTERM. By ensuring
openpower-host-ipmi-flash emits the BMC event state we close the race where the
host is not informed of the termination of mboxd due to scheduling ipmid (to
deliver SIGTERM) prior to scheduling dbus-daemon, where the PropertiesChanged
event would be delivered from mboxd to ipmid.
Observations on the IPMI Specification and Design Details of ipmid
------------------------------------------------------------------
In addition to the system-level design problems with delivering
PropertiesChanged signals during shutdown, IPMI specification and ipmid design
issues exist that make it tedious to ensure that events will be correctly
delivered to the host.
The first necessary observation is that the mechanism for delivering BMC state
change events from mboxd to the host over IPMI uses the SMS ATN bit to indicate
a message is ready for delivery from the BMC to the host system. Retrieving the
BMC state data involves the host recognising that the SMS ATN bit is set,
performing Get Message Flags transaction with the BMC followed by a subsequent
Get Message transaction. Thus, delivery of the HIOMAP protocol's BMC status is
not an atomic event.
The second necessary observation is that the kernel delivers signals
asynchronously. This couples badly with IPMI's event delivery not being atomic:
ipmid can win the race against SIGTERM to receive the PropertiesChanged event
from mboxd, but lose the race to complete delivery to the host.
On this basis, we need to block the delivery of SIGTERM to ipmid until ipmid
has completed the set of `SMS ATN`/`Get Message Flags`/`Get Message`
transactions with the host
One approach to this would be to configure a custom SIGTERM handler that sets
some global application state to indicate that SIGTERM has been delivered. A
better approach that avoids the need for global application state is to simply
block the signal until we are ready to handle it, which we can do via
sigprocmask(2).
The existing design of ipmid makes it feasible to block and unblock
asynchronous SIGTERM as we require. ipmid_send_cmd_to_host() takes a CallBack
function as an argument, which is invoked by
phosphor::host::command::Manager::getNextCommand(). The documentation for
phosphor::host::command::Manager::getNextCommand() says:
@brief Extracts the next entry in the queue and returns
Command and data part of it.
@detail Also calls into the registered handlers so that they can now
send the CommandComplete signal since the interface contract
is that we emit this signal once the message has been
passed to the host (which is required when calling this)
Also, if the queue has more commands, then it will alert the
host
However, its description is not entirely accurate. The callback function is
invoked when ipmid *dequeues* the data to send to the host: Delivery of the
data to the host occurs at some *after* the callback has been invoked.
Invoking the callback before completion of delivery of the data to the host
nullifies the approach of unblocking asynchronous SIGTERM in the callback
associated with sending the HIOMAP BMC state event to the host, as the BMC
kernel can asynchronously terminate the process between the callback being
invoked and the host receiving the BMC state event data.
Overcoming this issue hinges on a significant implementation detail of ipmid:
ipmid uses an sd_event loop in the main function to pump DBus events.
This leads to a third necessary observation:
sd_event can be used to process UNIX signals as well as other events by way
of Linux's signalfd(2) interface.
The fact that sd_event is used to pump DBus events means that ipmid can remain
a single-threaded process. By remaining single-threaded we know that events
processing is sequencial and no two events can be processed simultaneously. A
corollary of this is that DBus events and UNIX signals are serialised with
respect to eachother.
The fourth necessary observation is that we do not need to pump sd_event in
order to complete DBus method calls; sd_bus will handle the pumping independent
of the main loop in order to complete the method invocation.
Implementing Reliable HIOMAP BMC Status Event Delivery
------------------------------------------------------
We achieve reliable delivery of HIOMAP BMC status events in the following way:
1. During plugin initialisation, mask SIGTERM using sigprocmask(2)
2. Subsequent to masking SIGTERM, register
openpower::flash::hiomap_protocol_reset() as the SIGTERM handler using
sd_event_add_signal() to hook a signalfd(2) into sd_event
3. openpower::flash::hiomap_protocol_reset() implements the logic to send the
BMC_EVENT_PROTOCOL_RESET state to the host if necessary, otherwise terminate
the sd_event loop.
4. If it is necessary to send BMC_EVENT_PROTOCOL_RESET to the host in 3, assign
a callback handler that terminates the sd_event loop, which is only
processed after the current iteration is complete.
This process and its use of signalfd integration in the sd_event loop
eliminates the following three races:
1. The scheduler race between mboxd, dbus-daemon and ipmid, by having
openpower-host-ipmi-flash conditionally deliver the protocol reset event if
no such message has been received from mboxd
2. The race between delivering the BMC status event to the host and ipmid
receiving asynchronous SIGTERM after receiving the PropertiesChanged event
from mboxd
3. The race to deliver the BMC status data to the host after unblocking
asynchronous SIGTERM in the host command callback and before receiving
asynchronous SIGTERM.
Ultimately, ipmid could benefit from a redesign that fires the callback *after*
delivering the associated data to the host, but brief inspection determined
that this involved a non-trivial amount of effort.
*/
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";
/* XXX: ipmid is currently single-threaded, pumping dbus events in sequence
* via the main event loop. Thus the code is not forced to be re-entrant. We
* also know that the callback and DBus event handling will not be running
* concurrently.
*
* ipmid_send_cmd_to_host() takes a callback that doesn't define a context
* pointer, so instead use a global. active_event_updates gates manipulation of
* process state, so its definition as a global at least aligns with its use.
*/
static int active_event_updates;
struct hiomap
{
bus_t* bus;
/* Signals */
bus::match_t* properties;
/* Protocol state */
std::map<std::string, int> event_lookup;
uint8_t bmc_events;
uint8_t seq;
};
SignalResponse sigtermResponse = SignalResponse::continueExecution;
/* TODO: Replace get/put with packed structs and direct assignment */
template <typename T>
static inline T get(void* buf)
{
T t;
std::memcpy(&t, buf, sizeof(t));
return t;
}
template <typename T>
static inline void put(void* buf, T&& t)
{
std::memcpy(buf, &t, sizeof(t));
}
using hiomap_command =
std::function<ipmi_ret_t(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));
}
assert(active_event_updates);
active_event_updates--;
if (!active_event_updates)
{
sigtermResponse = SignalResponse::continueExecution;
log<level::DEBUG>("Unblocked SIGTERM");
}
}
static int hiomap_handle_property_update(struct hiomap* ctx,
sdbusplus::message_t& msg)
{
using namespace phosphor::logging;
std::map<std::string, std::variant<bool>> msgData;
sigtermResponse = SignalResponse::breakExecution;
if (!active_event_updates)
{
sigtermResponse = SignalResponse::breakExecution;
log<level::DEBUG>("Blocked SIGTERM");
}
active_event_updates++;
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 = std::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 int hiomap_protocol_reset_response([[maybe_unused]] IpmiCmdData cmd,
[[maybe_unused]] bool status)
{
// If this is running in signal context, ipmid will shutdown
// the event queue as the last signal handler
sigtermResponse = SignalResponse::continueExecution;
return 0;
}
static int hiomap_protocol_reset(struct hiomap* ctx)
{
if (ctx->bmc_events == BMC_EVENT_PROTOCOL_RESET)
{
// If this is running in signal context, ipmid will shutdown
// the event queue as the last signal handler
sigtermResponse = SignalResponse::continueExecution;
return 0;
}
/*
* Send an attention indicating the hiomapd has died
* (BMC_EVENT_DAEMON_READY cleared) and that the protocol has been reset
* (BMC_EVENT_PROTOCOL_RESET set) to indicate to the host that it needs to
* wait for the BMC to come back and renegotiate the protocol.
*
* We know this to be the case in systems that integrate
* openpower-host-ipmi-flash, as hiomapd's unit depends on
* phosphor-ipmi-host, and thus hiomapd has been terminated before ipmid
* receives SIGTERM.
*/
auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, BMC_EVENT_PROTOCOL_RESET);
auto cmdHandler = std::make_tuple(cmd, hiomap_protocol_reset_response);
ipmid_send_cmd_to_host(cmdHandler);
return 0;
}
static bus::match_t hiomap_match_properties(struct hiomap* ctx)
{
auto properties =
bus::match::rules::propertiesChanged(HIOMAPD_OBJECT, HIOMAPD_IFACE_V2);
bus::match_t match(
*ctx->bus, properties,
std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1));
return match;
}
static ipmi_ret_t hiomap_reset([[maybe_unused]] ipmi_request_t request,
[[maybe_unused]] 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_t& 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_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
static ipmi_ret_t hiomap_get_flash_info([[maybe_unused]] 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_t& 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_t& 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,
[[maybe_unused]] 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_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
static ipmi_ret_t hiomap_mark_dirty(ipmi_request_t request,
[[maybe_unused]] 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_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
static ipmi_ret_t hiomap_flush([[maybe_unused]] ipmi_request_t request,
[[maybe_unused]] 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, "Flush");
try
{
/* FIXME: No argument call assumes v2 */
auto reply = ctx->bus->call(m);
*data_len = 0;
}
catch (const exception_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
static ipmi_ret_t hiomap_ack(ipmi_request_t request,
[[maybe_unused]] 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, "Ack");
auto acked = reqdata[0];
m.append(acked);
try
{
auto reply = ctx->bus->call(m);
*data_len = 0;
}
catch (const exception_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
static ipmi_ret_t hiomap_erase(ipmi_request_t request,
[[maybe_unused]] 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, "Erase");
/* 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_t& e)
{
return hiomap_xlate_errno(e.get_errno());
}
return IPMI_CC_OK;
}
#define HIOMAP_C_RESET 1
#define HIOMAP_C_GET_INFO 2
#define HIOMAP_C_GET_FLASH_INFO 3
#define HIOMAP_C_CREATE_READ_WINDOW 4
#define HIOMAP_C_CLOSE_WINDOW 5
#define HIOMAP_C_CREATE_WRITE_WINDOW 6
#define HIOMAP_C_MARK_DIRTY 7
#define HIOMAP_C_FLUSH 8
#define HIOMAP_C_ACK 9
#define HIOMAP_C_ERASE 10
static const std::unordered_map<uint8_t, hiomap_command> hiomap_commands = {
{0, nullptr}, /* Invalid command ID */
{HIOMAP_C_RESET, hiomap_reset},
{HIOMAP_C_GET_INFO, hiomap_get_info},
{HIOMAP_C_GET_FLASH_INFO, hiomap_get_flash_info},
{HIOMAP_C_CREATE_READ_WINDOW, hiomap_create_read_window},
{HIOMAP_C_CLOSE_WINDOW, hiomap_close_window},
{HIOMAP_C_CREATE_WRITE_WINDOW, hiomap_create_write_window},
{HIOMAP_C_MARK_DIRTY, hiomap_mark_dirty},
{HIOMAP_C_FLUSH, hiomap_flush},
{HIOMAP_C_ACK, hiomap_ack},
{HIOMAP_C_ERASE, hiomap_erase},
};
/* 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([[maybe_unused]] ipmi_netfn_t netfn,
[[maybe_unused]] 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 > hiomap_commands.size() - 1)
{
*data_len = 0;
return IPMI_CC_PARM_OUT_OF_RANGE;
}
bool is_unversioned =
(hiomap_cmd == HIOMAP_C_RESET || hiomap_cmd == HIOMAP_C_GET_INFO ||
hiomap_cmd == HIOMAP_C_ACK);
if (!is_unversioned && ctx->seq == ipmi_req[1])
{
*data_len = 0;
return IPMI_CC_INVALID_FIELD_REQUEST;
}
ctx->seq = ipmi_req[1];
uint8_t* flash_req = ipmi_req + 2;
size_t flash_len = *data_len - 2;
uint8_t* flash_resp = ipmi_resp + 2;
auto command = hiomap_commands.find(hiomap_cmd);
if (command == hiomap_commands.end())
{
*data_len = 0;
return IPMI_CC_INVALID;
}
ipmi_ret_t cc = command->second(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] = ctx->seq;
*data_len = flash_len + 2;
return cc;
}
} // namespace flash
} // namespace openpower
static void register_openpower_hiomap_commands()
{
using namespace phosphor::logging;
using namespace openpower::flash;
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_t(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_t(std::move(hiomap_match_properties(ctx)));
std::function<SignalResponse(int)> shutdownHandler =
[ctx]([[maybe_unused]] int signalNumber) {
hiomap_protocol_reset(ctx);
return sigtermResponse;
};
registerSignalHandler(ipmi::prioMax, SIGTERM, shutdownHandler);
ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_HIOMAP, ctx,
openpower::flash::hiomap_dispatch, SYSTEM_INTERFACE);
}