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