xref: /openbmc/openpower-host-ipmi-flash/hiomap.cpp (revision 89707266466e9adf374a5dea90a6791d436194a5)
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3 
4 #include "hiomap.hpp"
5 
6 #include <endian.h>
7 #include <ipmid/api.h>
8 #include <signal.h>
9 #include <string.h>
10 #include <systemd/sd-bus.h>
11 #include <systemd/sd-event.h>
12 
13 #include <cassert>
14 #include <cstring>
15 #include <fstream>
16 #include <functional>
17 #include <iostream>
18 #include <ipmid-host/cmd-utils.hpp>
19 #include <ipmid-host/cmd.hpp>
20 #include <ipmid/api.hpp>
21 #include <map>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/bus.hpp>
24 #include <sdbusplus/bus/match.hpp>
25 #include <sdbusplus/exception.hpp>
26 #include <string>
27 #include <tuple>
28 #include <unordered_map>
29 #include <utility>
30 
31 /*
32 
33 Design and integration notes
34 ============================
35 
36 The primary motivation of the Host I/O Mapping protocol (HIOMAP) is to mediate
37 host access to a BMC-controlled flash chip housing the host's boot firmware.
38 
39 openpower-host-ipmi-flash facilitates the system design of transporting the
40 HIOMAP protocol[1] over IPMI. This is somewhat abusive of IPMI, basically
41 treating the BT interface as a mailbox with an interrupt each way between the
42 BMC and the host.
43 
44 [1] https://github.com/openbmc/mboxbridge/blob/master/Documentation/protocol.md
45 
46 Using IPMI in this way has a number of challenges, a lot of them on the host
47 side where we need to bring up the LPC and BT interfaces to enable IPMI before
48 accessing the flash, and before any interrupts are enabled. There are also
49 challenges on the BMC side with the design of the current implementation. We
50 will cover those here.
51 
52 BMC-side System Design and Integration Issues
53 ---------------------------------------------
54 
55 The current design is that we have the HIOMAP daemon, mboxd (to be renamed),
56 exposing a set of DBus interfaces. Whilst the spec defines the IPMI transport
57 message packing, mboxd knows nothing of IPMI itself, instead relying on the
58 DBus interface to receive messages from ipmid. ipmid in-turn knows nothing of
59 the interfaces communicating with it, also relying on DBus to receive messages
60 from interface-specific daemons, e.g. btbridged[2].
61 
62 [2] https://github.com/openbmc/btbridge
63 
64 For this design to function correctly we must ensure that the daemons are
65 started and shut down in a reasonable order, however defining that order is
66 somewhat tricky:
67 
68 1. systemd uses Wants=/Before=/After= relationships in units to define both
69    start-up *and* shutdown order, in stack push / pop order respectively.
70 2. Clearly ipmid depends on btbridged to receive messages sent by signals and
71    replied to by method calls, so it needs a Wants=/After= relationship on
72    btbridged
73 3. mboxd depends on ipmid to receive messages sent by method call, and issues a
74    PropertiesChanged signal to notify of state changes.
75 
76 Point 3. suggests mboxd should have a Wants=/Before= relationship with ipmid to
77 ensure ipmid can call into mboxd as messages arrive. However, this causes some
78 grief with shutdown of the BMC, as mboxd needs to issue a state-change
79 notification when it is shut down to inform the host that will not repsond to
80 future requests and that the protocol state has been reset. If mboxd has a
81 Wants=/Before= relationship with ipmid this message will never propagate to the
82 host, as ipmid will be shut by systemd before mboxd.
83 
84 The above leads to mboxd having a Wants=/After= relationship with ipmid. This
85 ensures that if mboxd is restarted on its own the correct state changes will be
86 propagated to the host. The case where ipmid attempts to call into mboxd's DBus
87 interface before mboxd is ready is mitigated by the ready bit in the protocol's
88 BMC status, which will not yet be set, preventing a conforming host from
89 attempting to contact mboxd.
90 
91 While this ordering prevents mboxd from being terminated before ipmid, there is
92 no control over the *scheduling* of processes to ensure the PropertiesChanged
93 signal emitted by mboxd before mboxd is terminated is seen by ipmid before
94 *ipmid* is also terminated. This leads to our first implementation wart:
95 
96   On the basis that mboxd has a Wants=/After= relationship with ipmid,
97   openpower-host-ipmi-flash will emit an HIOMAP BMC status event to the host
98   with the value BMC_EVENT_PROTOCOL_RESET upon receiving SIGTERM iff the BMC
99   state is not already set to BMC_EVENT_PROTOCOL_RESET.
100 
101 If ipmid has received SIGTERM the assumption is that it is systemd that is
102 sending it, and that the Wants=/After= relationship requires that mboxd has
103 been terminated before ipmid receives SIGTERM. By ensuring
104 openpower-host-ipmi-flash emits the BMC event state we close the race where the
105 host is not informed of the termination of mboxd due to scheduling ipmid (to
106 deliver SIGTERM) prior to scheduling dbus-daemon, where the PropertiesChanged
107 event would be delivered from mboxd to ipmid.
108 
109 Observations on the IPMI Specification and Design Details of ipmid
110 ------------------------------------------------------------------
111 
112 In addition to the system-level design problems with delivering
113 PropertiesChanged signals during shutdown, IPMI specification and ipmid design
114 issues exist that make it tedious to ensure that events will be correctly
115 delivered to the host.
116 
117 The first necessary observation is that the mechanism for delivering BMC state
118 change events from mboxd to the host over IPMI uses the SMS ATN bit to indicate
119 a message is ready for delivery from the BMC to the host system. Retrieving the
120 BMC state data involves the host recognising that the SMS ATN bit is set,
121 performing Get Message Flags transaction with the BMC followed by a subsequent
122 Get Message transaction. Thus, delivery of the HIOMAP protocol's BMC status is
123 not an atomic event.
124 
125 The second necessary observation is that the kernel delivers signals
126 asynchronously. This couples badly with IPMI's event delivery not being atomic:
127 ipmid can win the race against SIGTERM to receive the PropertiesChanged event
128 from mboxd, but lose the race to complete delivery to the host.
129 
130   On this basis, we need to block the delivery of SIGTERM to ipmid until ipmid
131   has completed the set of `SMS ATN`/`Get Message Flags`/`Get Message`
132   transactions with the host
133 
134 One approach to this would be to configure a custom SIGTERM handler that sets
135 some global application state to indicate that SIGTERM has been delivered. A
136 better approach that avoids the need for global application state is to simply
137 block the signal until we are ready to handle it, which we can do via
138 sigprocmask(2).
139 
140 The existing design of ipmid makes it feasible to block and unblock
141 asynchronous SIGTERM as we require. ipmid_send_cmd_to_host() takes a CallBack
142 function as an argument, which is invoked by
143 phosphor::host::command::Manager::getNextCommand(). The documentation for
144 phosphor::host::command::Manager::getNextCommand() says:
145 
146   @brief  Extracts the next entry in the queue and returns
147           Command and data part of it.
148 
149   @detail Also calls into the registered handlers so that they can now
150           send the CommandComplete signal since the interface contract
151           is that we emit this signal once the message has been
152           passed to the host (which is required when calling this)
153 
154           Also, if the queue has more commands, then it will alert the
155           host
156 
157 However, its description is not entirely accurate. The callback function is
158 invoked when ipmid *dequeues* the data to send to the host: Delivery of the
159 data to the host occurs at some *after* the callback has been invoked.
160 
161 Invoking the callback before completion of delivery of the data to the host
162 nullifies the approach of unblocking asynchronous SIGTERM in the callback
163 associated with sending the HIOMAP BMC state event to the host, as the BMC
164 kernel can asynchronously terminate the process between the callback being
165 invoked and the host receiving the BMC state event data.
166 
167 Overcoming this issue hinges on a significant implementation detail of ipmid:
168 
169   ipmid uses an sd_event loop in the main function to pump DBus events.
170 
171 This leads to a third necessary observation:
172 
173   sd_event can be used to process UNIX signals as well as other events by way
174   of Linux's signalfd(2) interface.
175 
176 The fact that sd_event is used to pump DBus events means that ipmid can remain
177 a single-threaded process. By remaining single-threaded we know that events
178 processing is sequencial and no two events can be processed simultaneously. A
179 corollary of this is that DBus events and UNIX signals are serialised with
180 respect to eachother.
181 
182 The fourth necessary observation is that we do not need to pump sd_event in
183 order to complete DBus method calls; sd_bus will handle the pumping independent
184 of the main loop in order to complete the method invocation.
185 
186 Implementing Reliable HIOMAP BMC Status Event Delivery
187 ------------------------------------------------------
188 
189 We achieve reliable delivery of HIOMAP BMC status events in the following way:
190 
191 1. During plugin initialisation, mask SIGTERM using sigprocmask(2)
192 2. Subsequent to masking SIGTERM, register
193    openpower::flash::hiomap_protocol_reset() as the SIGTERM handler using
194    sd_event_add_signal() to hook a signalfd(2) into sd_event
195 3. openpower::flash::hiomap_protocol_reset() implements the logic to send the
196    BMC_EVENT_PROTOCOL_RESET state to the host if necessary, otherwise terminate
197    the sd_event loop.
198 4. If it is necessary to send BMC_EVENT_PROTOCOL_RESET to the host in 3, assign
199    a callback handler that terminates the sd_event loop, which is only
200    processed after the current iteration is complete.
201 
202 This process and its use of signalfd integration in the sd_event loop
203 eliminates the following three races:
204 
205 1. The scheduler race between mboxd, dbus-daemon and ipmid, by having
206    openpower-host-ipmi-flash conditionally deliver the protocol reset event if
207    no such message has been received from mboxd
208 2. The race between delivering the BMC status event to the host and ipmid
209    receiving asynchronous SIGTERM after receiving the PropertiesChanged event
210    from mboxd
211 3. The race to deliver the BMC status data to the host after unblocking
212    asynchronous SIGTERM in the host command callback and before receiving
213    asynchronous SIGTERM.
214 
215 Ultimately, ipmid could benefit from a redesign that fires the callback *after*
216 delivering the associated data to the host, but brief inspection determined
217 that this involved a non-trivial amount of effort.
218 
219 */
220 
221 using namespace sdbusplus;
222 using namespace phosphor::host::command;
223 
224 static void register_openpower_hiomap_commands() __attribute__((constructor));
225 
226 namespace openpower
227 {
228 namespace flash
229 {
230 constexpr auto BMC_EVENT_DAEMON_READY = 1 << 7;
231 constexpr auto BMC_EVENT_FLASH_CTRL_LOST = 1 << 6;
232 constexpr auto BMC_EVENT_WINDOW_RESET = 1 << 1;
233 constexpr auto BMC_EVENT_PROTOCOL_RESET = 1 << 0;
234 
235 constexpr auto IPMI_CMD_HIOMAP_EVENT = 0x0f;
236 
237 constexpr auto HIOMAPD_SERVICE = "xyz.openbmc_project.Hiomapd";
238 constexpr auto HIOMAPD_OBJECT = "/xyz/openbmc_project/Hiomapd";
239 constexpr auto HIOMAPD_IFACE = "xyz.openbmc_project.Hiomapd.Protocol";
240 constexpr auto HIOMAPD_IFACE_V2 = "xyz.openbmc_project.Hiomapd.Protocol.V2";
241 
242 constexpr auto DBUS_IFACE_PROPERTIES = "org.freedesktop.DBus.Properties";
243 
244 /* XXX: ipmid is currently single-threaded, pumping dbus events in sequence
245  * via the main event loop. Thus the code is not forced to be re-entrant. We
246  * also know that the callback and DBus event handling will not be running
247  * concurrently.
248  *
249  * ipmid_send_cmd_to_host() takes a callback that doesn't define a context
250  * pointer, so instead use a global. active_event_updates gates manipulation of
251  * process state, so its definition as a global at least aligns with its use.
252  */
253 static int active_event_updates;
254 
255 struct hiomap
256 {
257     bus::bus* bus;
258 
259     /* Signals */
260     bus::match::match* properties;
261 
262     /* Protocol state */
263     std::map<std::string, int> event_lookup;
264     uint8_t bmc_events;
265     uint8_t seq;
266 };
267 
268 SignalResponse sigtermResponse = SignalResponse::continueExecution;
269 
270 /* TODO: Replace get/put with packed structs and direct assignment */
271 template <typename T>
272 static inline T get(void* buf)
273 {
274     T t;
275     std::memcpy(&t, buf, sizeof(t));
276     return t;
277 }
278 
279 template <typename T>
280 static inline void put(void* buf, T&& t)
281 {
282     std::memcpy(buf, &t, sizeof(t));
283 }
284 
285 using hiomap_command =
286     std::function<ipmi_ret_t(ipmi_request_t req, ipmi_response_t resp,
287                              ipmi_data_len_t data_len, ipmi_context_t context)>;
288 struct errno_cc_entry
289 {
290     int err;
291     int cc;
292 };
293 
294 static const errno_cc_entry errno_cc_map[] = {
295     {0, IPMI_CC_OK},
296     {EBUSY, IPMI_CC_BUSY},
297     {ENOTSUP, IPMI_CC_INVALID},
298     {ETIMEDOUT, 0xc3}, /* FIXME: Replace when defined in ipmid-api.h */
299     {ENOSPC, 0xc4},    /* FIXME: Replace when defined in ipmid-api.h */
300     {EINVAL, IPMI_CC_PARM_OUT_OF_RANGE},
301     {ENODEV, IPMI_CC_SENSOR_INVALID},
302     {EPERM, IPMI_CC_INSUFFICIENT_PRIVILEGE},
303     {EACCES, IPMI_CC_INSUFFICIENT_PRIVILEGE},
304     {-1, IPMI_CC_UNSPECIFIED_ERROR},
305 };
306 
307 static int hiomap_xlate_errno(int err)
308 {
309     const errno_cc_entry* entry = &errno_cc_map[0];
310 
311     while (!(entry->err == err || entry->err == -1))
312     {
313         entry++;
314     }
315 
316     return entry->cc;
317 }
318 
319 static void ipmi_hiomap_event_response(IpmiCmdData cmd, bool status)
320 {
321     using namespace phosphor::logging;
322 
323     if (!status)
324     {
325         log<level::ERR>("Failed to deliver host command",
326                         entry("SEL_COMMAND=%x:%x", cmd.first, cmd.second));
327     }
328 
329     assert(active_event_updates);
330     active_event_updates--;
331     if (!active_event_updates)
332     {
333         sigtermResponse = SignalResponse::continueExecution;
334         log<level::DEBUG>("Unblocked SIGTERM");
335     }
336 }
337 
338 static int hiomap_handle_property_update(struct hiomap* ctx,
339                                          sdbusplus::message::message& msg)
340 {
341     using namespace phosphor::logging;
342 
343     std::map<std::string, std::variant<bool>> msgData;
344 
345     sigtermResponse = SignalResponse::breakExecution;
346     if (!active_event_updates)
347     {
348         sigtermResponse = SignalResponse::breakExecution;
349         log<level::DEBUG>("Blocked SIGTERM");
350     }
351     active_event_updates++;
352 
353     std::string iface;
354     msg.read(iface, msgData);
355 
356     for (auto const& x : msgData)
357     {
358         if (!ctx->event_lookup.count(x.first))
359         {
360             /* Unsupported event? */
361             continue;
362         }
363 
364         uint8_t mask = ctx->event_lookup[x.first];
365         auto value = std::get<bool>(x.second);
366 
367         if (value)
368         {
369             ctx->bmc_events |= mask;
370         }
371         else
372         {
373             ctx->bmc_events &= ~mask;
374         }
375     }
376 
377     auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
378 
379     ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
380 
381     return 0;
382 }
383 
384 static int hiomap_protocol_reset_response([[maybe_unused]] IpmiCmdData cmd,
385                                           [[maybe_unused]] bool status)
386 {
387     // If this is running in signal context, ipmid will shutdown
388     // the event queue as the last signal handler
389     sigtermResponse = SignalResponse::continueExecution;
390     return 0;
391 }
392 
393 static int hiomap_protocol_reset(struct hiomap* ctx)
394 {
395     if (ctx->bmc_events == BMC_EVENT_PROTOCOL_RESET)
396     {
397         // If this is running in signal context, ipmid will shutdown
398         // the event queue as the last signal handler
399         sigtermResponse = SignalResponse::continueExecution;
400         return 0;
401     }
402 
403     /*
404      * Send an attention indicating the hiomapd has died
405      * (BMC_EVENT_DAEMON_READY cleared) and that the protocol has been reset
406      * (BMC_EVENT_PROTOCOL_RESET set) to indicate to the host that it needs to
407      * wait for the BMC to come back and renegotiate the protocol.
408      *
409      * We know this to be the case in systems that integrate
410      * openpower-host-ipmi-flash, as hiomapd's unit depends on
411      * phosphor-ipmi-host, and thus hiomapd has been terminated before ipmid
412      * receives SIGTERM.
413      */
414     auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, BMC_EVENT_PROTOCOL_RESET);
415 
416     auto cmdHandler = std::make_tuple(cmd, hiomap_protocol_reset_response);
417     ipmid_send_cmd_to_host(cmdHandler);
418 
419     return 0;
420 }
421 
422 static bus::match::match hiomap_match_properties(struct hiomap* ctx)
423 {
424     auto properties =
425         bus::match::rules::propertiesChanged(HIOMAPD_OBJECT, HIOMAPD_IFACE_V2);
426 
427     bus::match::match match(
428         *ctx->bus, properties,
429         std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1));
430 
431     return match;
432 }
433 
434 static ipmi_ret_t hiomap_reset([[maybe_unused]] ipmi_request_t request,
435                                [[maybe_unused]] ipmi_response_t response,
436                                ipmi_data_len_t data_len, ipmi_context_t context)
437 {
438     struct hiomap* ctx = static_cast<struct hiomap*>(context);
439 
440     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
441                                        HIOMAPD_IFACE, "Reset");
442     try
443     {
444         ctx->bus->call(m);
445 
446         *data_len = 0;
447     }
448     catch (const exception::exception& e)
449     {
450         return hiomap_xlate_errno(e.get_errno());
451     }
452 
453     return IPMI_CC_OK;
454 }
455 
456 static ipmi_ret_t hiomap_get_info(ipmi_request_t request,
457                                   ipmi_response_t response,
458                                   ipmi_data_len_t data_len,
459                                   ipmi_context_t context)
460 {
461     struct hiomap* ctx = static_cast<struct hiomap*>(context);
462 
463     if (*data_len < 1)
464     {
465         return IPMI_CC_REQ_DATA_LEN_INVALID;
466     }
467 
468     uint8_t* reqdata = (uint8_t*)request;
469     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
470                                        HIOMAPD_IFACE, "GetInfo");
471     m.append(reqdata[0]);
472 
473     try
474     {
475         auto reply = ctx->bus->call(m);
476 
477         uint8_t version;
478         uint8_t blockSizeShift;
479         uint16_t timeout;
480         reply.read(version, blockSizeShift, timeout);
481 
482         uint8_t* respdata = (uint8_t*)response;
483 
484         /* FIXME: Assumes v2! */
485         put(&respdata[0], version);
486         put(&respdata[1], blockSizeShift);
487         put(&respdata[2], htole16(timeout));
488 
489         *data_len = 4;
490     }
491     catch (const exception::exception& e)
492     {
493         return hiomap_xlate_errno(e.get_errno());
494     }
495 
496     return IPMI_CC_OK;
497 }
498 
499 static ipmi_ret_t hiomap_get_flash_info([[maybe_unused]] ipmi_request_t request,
500                                         ipmi_response_t response,
501                                         ipmi_data_len_t data_len,
502                                         ipmi_context_t context)
503 {
504     struct hiomap* ctx = static_cast<struct hiomap*>(context);
505 
506     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
507                                        HIOMAPD_IFACE_V2, "GetFlashInfo");
508     try
509     {
510         auto reply = ctx->bus->call(m);
511 
512         uint16_t flashSize, eraseSize;
513         reply.read(flashSize, eraseSize);
514 
515         uint8_t* respdata = (uint8_t*)response;
516         put(&respdata[0], htole16(flashSize));
517         put(&respdata[2], htole16(eraseSize));
518 
519         *data_len = 4;
520     }
521     catch (const exception::exception& e)
522     {
523         return hiomap_xlate_errno(e.get_errno());
524     }
525 
526     return IPMI_CC_OK;
527 }
528 
529 static ipmi_ret_t hiomap_create_window(struct hiomap* ctx, bool ro,
530                                        ipmi_request_t request,
531                                        ipmi_response_t response,
532                                        ipmi_data_len_t data_len)
533 {
534     if (*data_len < 4)
535     {
536         return IPMI_CC_REQ_DATA_LEN_INVALID;
537     }
538 
539     uint8_t* reqdata = (uint8_t*)request;
540     auto windowType = ro ? "CreateReadWindow" : "CreateWriteWindow";
541 
542     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
543                                        HIOMAPD_IFACE_V2, windowType);
544     m.append(le16toh(get<uint16_t>(&reqdata[0])));
545     m.append(le16toh(get<uint16_t>(&reqdata[2])));
546 
547     try
548     {
549         auto reply = ctx->bus->call(m);
550 
551         uint16_t lpcAddress, size, offset;
552         reply.read(lpcAddress, size, offset);
553 
554         uint8_t* respdata = (uint8_t*)response;
555 
556         /* FIXME: Assumes v2! */
557         put(&respdata[0], htole16(lpcAddress));
558         put(&respdata[2], htole16(size));
559         put(&respdata[4], htole16(offset));
560 
561         *data_len = 6;
562     }
563     catch (const exception::exception& e)
564     {
565         return hiomap_xlate_errno(e.get_errno());
566     }
567 
568     return IPMI_CC_OK;
569 }
570 
571 static ipmi_ret_t hiomap_create_read_window(ipmi_request_t request,
572                                             ipmi_response_t response,
573                                             ipmi_data_len_t data_len,
574                                             ipmi_context_t context)
575 {
576     struct hiomap* ctx = static_cast<struct hiomap*>(context);
577 
578     return hiomap_create_window(ctx, true, request, response, data_len);
579 }
580 
581 static ipmi_ret_t hiomap_create_write_window(ipmi_request_t request,
582                                              ipmi_response_t response,
583                                              ipmi_data_len_t data_len,
584                                              ipmi_context_t context)
585 {
586     struct hiomap* ctx = static_cast<struct hiomap*>(context);
587 
588     return hiomap_create_window(ctx, false, request, response, data_len);
589 }
590 
591 static ipmi_ret_t hiomap_close_window(ipmi_request_t request,
592                                       [[maybe_unused]] ipmi_response_t response,
593                                       ipmi_data_len_t data_len,
594                                       ipmi_context_t context)
595 {
596     struct hiomap* ctx = static_cast<struct hiomap*>(context);
597 
598     if (*data_len < 1)
599     {
600         return IPMI_CC_REQ_DATA_LEN_INVALID;
601     }
602 
603     uint8_t* reqdata = (uint8_t*)request;
604     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
605                                        HIOMAPD_IFACE_V2, "CloseWindow");
606     m.append(reqdata[0]);
607 
608     try
609     {
610         auto reply = ctx->bus->call(m);
611 
612         *data_len = 0;
613     }
614     catch (const exception::exception& e)
615     {
616         return hiomap_xlate_errno(e.get_errno());
617     }
618 
619     return IPMI_CC_OK;
620 }
621 
622 static ipmi_ret_t hiomap_mark_dirty(ipmi_request_t request,
623                                     [[maybe_unused]] ipmi_response_t response,
624                                     ipmi_data_len_t data_len,
625                                     ipmi_context_t context)
626 {
627     struct hiomap* ctx = static_cast<struct hiomap*>(context);
628 
629     if (*data_len < 4)
630     {
631         return IPMI_CC_REQ_DATA_LEN_INVALID;
632     }
633 
634     uint8_t* reqdata = (uint8_t*)request;
635     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
636                                        HIOMAPD_IFACE_V2, "MarkDirty");
637     /* FIXME: Assumes v2 */
638     m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
639     m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
640 
641     try
642     {
643         auto reply = ctx->bus->call(m);
644 
645         *data_len = 0;
646     }
647     catch (const exception::exception& e)
648     {
649         return hiomap_xlate_errno(e.get_errno());
650     }
651 
652     return IPMI_CC_OK;
653 }
654 
655 static ipmi_ret_t hiomap_flush([[maybe_unused]] ipmi_request_t request,
656                                [[maybe_unused]] ipmi_response_t response,
657                                ipmi_data_len_t data_len, ipmi_context_t context)
658 {
659     struct hiomap* ctx = static_cast<struct hiomap*>(context);
660 
661     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
662                                        HIOMAPD_IFACE_V2, "Flush");
663 
664     try
665     {
666         /* FIXME: No argument call assumes v2 */
667         auto reply = ctx->bus->call(m);
668 
669         *data_len = 0;
670     }
671     catch (const exception::exception& e)
672     {
673         return hiomap_xlate_errno(e.get_errno());
674     }
675 
676     return IPMI_CC_OK;
677 }
678 
679 static ipmi_ret_t hiomap_ack(ipmi_request_t request,
680                              [[maybe_unused]] ipmi_response_t response,
681                              ipmi_data_len_t data_len, ipmi_context_t context)
682 {
683     struct hiomap* ctx = static_cast<struct hiomap*>(context);
684 
685     if (*data_len < 1)
686     {
687         return IPMI_CC_REQ_DATA_LEN_INVALID;
688     }
689 
690     uint8_t* reqdata = (uint8_t*)request;
691     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
692                                        HIOMAPD_IFACE_V2, "Ack");
693     auto acked = reqdata[0];
694     m.append(acked);
695 
696     try
697     {
698         auto reply = ctx->bus->call(m);
699 
700         *data_len = 0;
701     }
702     catch (const exception::exception& e)
703     {
704         return hiomap_xlate_errno(e.get_errno());
705     }
706 
707     return IPMI_CC_OK;
708 }
709 
710 static ipmi_ret_t hiomap_erase(ipmi_request_t request,
711                                [[maybe_unused]] ipmi_response_t response,
712                                ipmi_data_len_t data_len, ipmi_context_t context)
713 {
714     struct hiomap* ctx = static_cast<struct hiomap*>(context);
715 
716     if (*data_len < 4)
717     {
718         return IPMI_CC_REQ_DATA_LEN_INVALID;
719     }
720 
721     uint8_t* reqdata = (uint8_t*)request;
722     auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
723                                        HIOMAPD_IFACE_V2, "Erase");
724     /* FIXME: Assumes v2 */
725     m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
726     m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
727 
728     try
729     {
730         auto reply = ctx->bus->call(m);
731 
732         *data_len = 0;
733     }
734     catch (const exception::exception& e)
735     {
736         return hiomap_xlate_errno(e.get_errno());
737     }
738 
739     return IPMI_CC_OK;
740 }
741 
742 #define HIOMAP_C_RESET 1
743 #define HIOMAP_C_GET_INFO 2
744 #define HIOMAP_C_GET_FLASH_INFO 3
745 #define HIOMAP_C_CREATE_READ_WINDOW 4
746 #define HIOMAP_C_CLOSE_WINDOW 5
747 #define HIOMAP_C_CREATE_WRITE_WINDOW 6
748 #define HIOMAP_C_MARK_DIRTY 7
749 #define HIOMAP_C_FLUSH 8
750 #define HIOMAP_C_ACK 9
751 #define HIOMAP_C_ERASE 10
752 
753 static const std::unordered_map<uint8_t, hiomap_command> hiomap_commands = {
754     {0, nullptr}, /* Invalid command ID */
755     {HIOMAP_C_RESET, hiomap_reset},
756     {HIOMAP_C_GET_INFO, hiomap_get_info},
757     {HIOMAP_C_GET_FLASH_INFO, hiomap_get_flash_info},
758     {HIOMAP_C_CREATE_READ_WINDOW, hiomap_create_read_window},
759     {HIOMAP_C_CLOSE_WINDOW, hiomap_close_window},
760     {HIOMAP_C_CREATE_WRITE_WINDOW, hiomap_create_write_window},
761     {HIOMAP_C_MARK_DIRTY, hiomap_mark_dirty},
762     {HIOMAP_C_FLUSH, hiomap_flush},
763     {HIOMAP_C_ACK, hiomap_ack},
764     {HIOMAP_C_ERASE, hiomap_erase},
765 };
766 
767 /* FIXME: Define this in the "right" place, wherever that is */
768 /* FIXME: Double evaluation */
769 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
770 
771 static ipmi_ret_t hiomap_dispatch([[maybe_unused]] ipmi_netfn_t netfn,
772                                   [[maybe_unused]] ipmi_cmd_t cmd,
773                                   ipmi_request_t request,
774                                   ipmi_response_t response,
775                                   ipmi_data_len_t data_len,
776                                   ipmi_context_t context)
777 {
778     struct hiomap* ctx = static_cast<struct hiomap*>(context);
779 
780     if (*data_len < 2)
781     {
782         *data_len = 0;
783         return IPMI_CC_REQ_DATA_LEN_INVALID;
784     }
785 
786     uint8_t* ipmi_req = (uint8_t*)request;
787     uint8_t* ipmi_resp = (uint8_t*)response;
788     uint8_t hiomap_cmd = ipmi_req[0];
789 
790     if (hiomap_cmd == 0 || hiomap_cmd > hiomap_commands.size() - 1)
791     {
792         *data_len = 0;
793         return IPMI_CC_PARM_OUT_OF_RANGE;
794     }
795 
796     bool is_unversioned =
797         (hiomap_cmd == HIOMAP_C_RESET || hiomap_cmd == HIOMAP_C_GET_INFO ||
798          hiomap_cmd == HIOMAP_C_ACK);
799     if (!is_unversioned && ctx->seq == ipmi_req[1])
800     {
801         *data_len = 0;
802         return IPMI_CC_INVALID_FIELD_REQUEST;
803     }
804 
805     ctx->seq = ipmi_req[1];
806 
807     uint8_t* flash_req = ipmi_req + 2;
808     size_t flash_len = *data_len - 2;
809     uint8_t* flash_resp = ipmi_resp + 2;
810 
811     auto command = hiomap_commands.find(hiomap_cmd);
812     if (command == hiomap_commands.end())
813     {
814         *data_len = 0;
815         return IPMI_CC_INVALID;
816     }
817     ipmi_ret_t cc = command->second(flash_req, flash_resp, &flash_len, context);
818     if (cc != IPMI_CC_OK)
819     {
820         *data_len = 0;
821         return cc;
822     }
823 
824     /* Populate the response command and sequence */
825     ipmi_resp[0] = hiomap_cmd;
826     ipmi_resp[1] = ctx->seq;
827 
828     *data_len = flash_len + 2;
829 
830     return cc;
831 }
832 } // namespace flash
833 } // namespace openpower
834 
835 static void register_openpower_hiomap_commands()
836 {
837     using namespace phosphor::logging;
838     using namespace openpower::flash;
839 
840     struct hiomap* ctx = new hiomap();
841 
842     /* Initialise mapping from signal and property names to status bit */
843     ctx->event_lookup["DaemonReady"] = BMC_EVENT_DAEMON_READY;
844     ctx->event_lookup["FlashControlLost"] = BMC_EVENT_FLASH_CTRL_LOST;
845     ctx->event_lookup["WindowReset"] = BMC_EVENT_WINDOW_RESET;
846     ctx->event_lookup["ProtocolReset"] = BMC_EVENT_PROTOCOL_RESET;
847 
848     ctx->bus = new bus::bus(ipmid_get_sd_bus_connection());
849 
850     /* Initialise signal handling */
851 
852     /*
853      * Can't use temporaries here because that causes SEGFAULTs due to slot
854      * destruction (!?), so enjoy the weird wrapping.
855      */
856     ctx->properties =
857         new bus::match::match(std::move(hiomap_match_properties(ctx)));
858 
859     std::function<SignalResponse(int)> shutdownHandler =
860         [ctx]([[maybe_unused]] int signalNumber) {
861             hiomap_protocol_reset(ctx);
862             return sigtermResponse;
863         };
864     registerSignalHandler(ipmi::prioMax, SIGTERM, shutdownHandler);
865 
866     ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_HIOMAP, ctx,
867                            openpower::flash::hiomap_dispatch, SYSTEM_INTERFACE);
868 }
869