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 <ipmid-host/cmd-utils.hpp>
14 #include <ipmid-host/cmd.hpp>
15 #include <ipmid/api.hpp>
16 #include <phosphor-logging/log.hpp>
17 #include <sdbusplus/bus.hpp>
18 #include <sdbusplus/bus/match.hpp>
19 #include <sdbusplus/exception.hpp>
20
21 #include <cassert>
22 #include <cstring>
23 #include <fstream>
24 #include <functional>
25 #include <iostream>
26 #include <map>
27 #include <string>
28 #include <tuple>
29 #include <unordered_map>
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_t* bus;
259
260 /* Signals */
261 bus::match_t* 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>
get(void * buf)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>
put(void * buf,T && t)281 static inline void put(void* buf, T&& t)
282 {
283 std::memcpy(buf, &t, sizeof(t));
284 }
285
286 using hiomap_command =
287 std::function<ipmi_ret_t(ipmi_request_t req, ipmi_response_t resp,
288 ipmi_data_len_t data_len, ipmi_context_t context)>;
289 struct errno_cc_entry
290 {
291 int err;
292 int cc;
293 };
294
295 static const errno_cc_entry errno_cc_map[] = {
296 {0, IPMI_CC_OK},
297 {EBUSY, IPMI_CC_BUSY},
298 {ENOTSUP, IPMI_CC_INVALID},
299 {ETIMEDOUT, 0xc3}, /* FIXME: Replace when defined in ipmid-api.h */
300 {ENOSPC, 0xc4}, /* FIXME: Replace when defined in ipmid-api.h */
301 {EINVAL, IPMI_CC_PARM_OUT_OF_RANGE},
302 {ENODEV, IPMI_CC_SENSOR_INVALID},
303 {EPERM, IPMI_CC_INSUFFICIENT_PRIVILEGE},
304 {EACCES, IPMI_CC_INSUFFICIENT_PRIVILEGE},
305 {-1, IPMI_CC_UNSPECIFIED_ERROR},
306 };
307
hiomap_xlate_errno(int err)308 static int hiomap_xlate_errno(int err)
309 {
310 const errno_cc_entry* entry = &errno_cc_map[0];
311
312 while (!(entry->err == err || entry->err == -1))
313 {
314 entry++;
315 }
316
317 return entry->cc;
318 }
319
ipmi_hiomap_event_response(IpmiCmdData cmd,bool status)320 static void ipmi_hiomap_event_response(IpmiCmdData cmd, bool status)
321 {
322 using namespace phosphor::logging;
323
324 if (!status)
325 {
326 log<level::ERR>("Failed to deliver host command",
327 entry("SEL_COMMAND=%x:%x", cmd.first, cmd.second));
328 }
329
330 assert(active_event_updates);
331 active_event_updates--;
332 if (!active_event_updates)
333 {
334 sigtermResponse = SignalResponse::continueExecution;
335 log<level::DEBUG>("Unblocked SIGTERM");
336 }
337 }
338
hiomap_handle_property_update(struct hiomap * ctx,sdbusplus::message_t & msg)339 static int hiomap_handle_property_update(struct hiomap* ctx,
340 sdbusplus::message_t& msg)
341 {
342 using namespace phosphor::logging;
343
344 std::map<std::string, std::variant<bool>> msgData;
345
346 sigtermResponse = SignalResponse::breakExecution;
347 if (!active_event_updates)
348 {
349 sigtermResponse = SignalResponse::breakExecution;
350 log<level::DEBUG>("Blocked SIGTERM");
351 }
352 active_event_updates++;
353
354 std::string iface;
355 msg.read(iface, msgData);
356
357 for (const auto& x : msgData)
358 {
359 if (!ctx->event_lookup.count(x.first))
360 {
361 /* Unsupported event? */
362 continue;
363 }
364
365 uint8_t mask = ctx->event_lookup[x.first];
366 auto value = std::get<bool>(x.second);
367
368 if (value)
369 {
370 ctx->bmc_events |= mask;
371 }
372 else
373 {
374 ctx->bmc_events &= ~mask;
375 }
376 }
377
378 auto cmd = std::make_pair(IPMI_CMD_HIOMAP_EVENT, ctx->bmc_events);
379
380 ipmid_send_cmd_to_host(std::make_tuple(cmd, ipmi_hiomap_event_response));
381
382 return 0;
383 }
384
hiomap_protocol_reset_response(IpmiCmdData cmd,bool status)385 static int hiomap_protocol_reset_response([[maybe_unused]] IpmiCmdData cmd,
386 [[maybe_unused]] 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
hiomap_protocol_reset(struct hiomap * ctx)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
hiomap_match_properties(struct hiomap * ctx)423 static bus::match_t hiomap_match_properties(struct hiomap* ctx)
424 {
425 auto properties = bus::match::rules::propertiesChanged(HIOMAPD_OBJECT,
426 HIOMAPD_IFACE_V2);
427
428 bus::match_t match(
429 *ctx->bus, properties,
430 std::bind(hiomap_handle_property_update, ctx, std::placeholders::_1));
431
432 return match;
433 }
434
hiomap_reset(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)435 static ipmi_ret_t hiomap_reset([[maybe_unused]] ipmi_request_t request,
436 [[maybe_unused]] ipmi_response_t response,
437 ipmi_data_len_t data_len, ipmi_context_t context)
438 {
439 struct hiomap* ctx = static_cast<struct hiomap*>(context);
440
441 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
442 HIOMAPD_IFACE, "Reset");
443 try
444 {
445 ctx->bus->call(m);
446
447 *data_len = 0;
448 }
449 catch (const exception_t& e)
450 {
451 return hiomap_xlate_errno(e.get_errno());
452 }
453
454 return IPMI_CC_OK;
455 }
456
hiomap_get_info(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)457 static ipmi_ret_t hiomap_get_info(ipmi_request_t request,
458 ipmi_response_t response,
459 ipmi_data_len_t data_len,
460 ipmi_context_t context)
461 {
462 struct hiomap* ctx = static_cast<struct hiomap*>(context);
463
464 if (*data_len < 1)
465 {
466 return IPMI_CC_REQ_DATA_LEN_INVALID;
467 }
468
469 uint8_t* reqdata = (uint8_t*)request;
470 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
471 HIOMAPD_IFACE, "GetInfo");
472 m.append(reqdata[0]);
473
474 try
475 {
476 auto reply = ctx->bus->call(m);
477
478 uint8_t version;
479 uint8_t blockSizeShift;
480 uint16_t timeout;
481 reply.read(version, blockSizeShift, timeout);
482
483 uint8_t* respdata = (uint8_t*)response;
484
485 /* FIXME: Assumes v2! */
486 put(&respdata[0], version);
487 put(&respdata[1], blockSizeShift);
488 put(&respdata[2], htole16(timeout));
489
490 *data_len = 4;
491 }
492 catch (const exception_t& e)
493 {
494 return hiomap_xlate_errno(e.get_errno());
495 }
496
497 return IPMI_CC_OK;
498 }
499
hiomap_get_flash_info(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)500 static ipmi_ret_t hiomap_get_flash_info([[maybe_unused]] ipmi_request_t request,
501 ipmi_response_t response,
502 ipmi_data_len_t data_len,
503 ipmi_context_t context)
504 {
505 struct hiomap* ctx = static_cast<struct hiomap*>(context);
506
507 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
508 HIOMAPD_IFACE_V2, "GetFlashInfo");
509 try
510 {
511 auto reply = ctx->bus->call(m);
512
513 uint16_t flashSize, eraseSize;
514 reply.read(flashSize, eraseSize);
515
516 uint8_t* respdata = (uint8_t*)response;
517 put(&respdata[0], htole16(flashSize));
518 put(&respdata[2], htole16(eraseSize));
519
520 *data_len = 4;
521 }
522 catch (const exception_t& e)
523 {
524 return hiomap_xlate_errno(e.get_errno());
525 }
526
527 return IPMI_CC_OK;
528 }
529
hiomap_create_window(struct hiomap * ctx,bool ro,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len)530 static ipmi_ret_t hiomap_create_window(struct hiomap* ctx, bool ro,
531 ipmi_request_t request,
532 ipmi_response_t response,
533 ipmi_data_len_t data_len)
534 {
535 if (*data_len < 4)
536 {
537 return IPMI_CC_REQ_DATA_LEN_INVALID;
538 }
539
540 uint8_t* reqdata = (uint8_t*)request;
541 auto windowType = ro ? "CreateReadWindow" : "CreateWriteWindow";
542
543 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
544 HIOMAPD_IFACE_V2, windowType);
545 m.append(le16toh(get<uint16_t>(&reqdata[0])));
546 m.append(le16toh(get<uint16_t>(&reqdata[2])));
547
548 try
549 {
550 auto reply = ctx->bus->call(m);
551
552 uint16_t lpcAddress, size, offset;
553 reply.read(lpcAddress, size, offset);
554
555 uint8_t* respdata = (uint8_t*)response;
556
557 /* FIXME: Assumes v2! */
558 put(&respdata[0], htole16(lpcAddress));
559 put(&respdata[2], htole16(size));
560 put(&respdata[4], htole16(offset));
561
562 *data_len = 6;
563 }
564 catch (const exception_t& e)
565 {
566 return hiomap_xlate_errno(e.get_errno());
567 }
568
569 return IPMI_CC_OK;
570 }
571
hiomap_create_read_window(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)572 static ipmi_ret_t hiomap_create_read_window(ipmi_request_t request,
573 ipmi_response_t response,
574 ipmi_data_len_t data_len,
575 ipmi_context_t context)
576 {
577 struct hiomap* ctx = static_cast<struct hiomap*>(context);
578
579 return hiomap_create_window(ctx, true, request, response, data_len);
580 }
581
hiomap_create_write_window(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)582 static ipmi_ret_t hiomap_create_write_window(ipmi_request_t request,
583 ipmi_response_t response,
584 ipmi_data_len_t data_len,
585 ipmi_context_t context)
586 {
587 struct hiomap* ctx = static_cast<struct hiomap*>(context);
588
589 return hiomap_create_window(ctx, false, request, response, data_len);
590 }
591
hiomap_close_window(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)592 static ipmi_ret_t hiomap_close_window(ipmi_request_t request,
593 [[maybe_unused]] ipmi_response_t response,
594 ipmi_data_len_t data_len,
595 ipmi_context_t context)
596 {
597 struct hiomap* ctx = static_cast<struct hiomap*>(context);
598
599 if (*data_len < 1)
600 {
601 return IPMI_CC_REQ_DATA_LEN_INVALID;
602 }
603
604 uint8_t* reqdata = (uint8_t*)request;
605 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
606 HIOMAPD_IFACE_V2, "CloseWindow");
607 m.append(reqdata[0]);
608
609 try
610 {
611 auto reply = ctx->bus->call(m);
612
613 *data_len = 0;
614 }
615 catch (const exception_t& e)
616 {
617 return hiomap_xlate_errno(e.get_errno());
618 }
619
620 return IPMI_CC_OK;
621 }
622
hiomap_mark_dirty(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)623 static ipmi_ret_t hiomap_mark_dirty(ipmi_request_t request,
624 [[maybe_unused]] ipmi_response_t response,
625 ipmi_data_len_t data_len,
626 ipmi_context_t context)
627 {
628 struct hiomap* ctx = static_cast<struct hiomap*>(context);
629
630 if (*data_len < 4)
631 {
632 return IPMI_CC_REQ_DATA_LEN_INVALID;
633 }
634
635 uint8_t* reqdata = (uint8_t*)request;
636 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
637 HIOMAPD_IFACE_V2, "MarkDirty");
638 /* FIXME: Assumes v2 */
639 m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
640 m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
641
642 try
643 {
644 auto reply = ctx->bus->call(m);
645
646 *data_len = 0;
647 }
648 catch (const exception_t& e)
649 {
650 return hiomap_xlate_errno(e.get_errno());
651 }
652
653 return IPMI_CC_OK;
654 }
655
hiomap_flush(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)656 static ipmi_ret_t hiomap_flush([[maybe_unused]] ipmi_request_t request,
657 [[maybe_unused]] ipmi_response_t response,
658 ipmi_data_len_t data_len, ipmi_context_t context)
659 {
660 struct hiomap* ctx = static_cast<struct hiomap*>(context);
661
662 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
663 HIOMAPD_IFACE_V2, "Flush");
664
665 try
666 {
667 /* FIXME: No argument call assumes v2 */
668 auto reply = ctx->bus->call(m);
669
670 *data_len = 0;
671 }
672 catch (const exception_t& e)
673 {
674 return hiomap_xlate_errno(e.get_errno());
675 }
676
677 return IPMI_CC_OK;
678 }
679
hiomap_ack(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)680 static ipmi_ret_t hiomap_ack(ipmi_request_t request,
681 [[maybe_unused]] ipmi_response_t response,
682 ipmi_data_len_t data_len, ipmi_context_t context)
683 {
684 struct hiomap* ctx = static_cast<struct hiomap*>(context);
685
686 if (*data_len < 1)
687 {
688 return IPMI_CC_REQ_DATA_LEN_INVALID;
689 }
690
691 uint8_t* reqdata = (uint8_t*)request;
692 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
693 HIOMAPD_IFACE_V2, "Ack");
694 auto acked = reqdata[0];
695 m.append(acked);
696
697 try
698 {
699 auto reply = ctx->bus->call(m);
700
701 *data_len = 0;
702 }
703 catch (const exception_t& e)
704 {
705 return hiomap_xlate_errno(e.get_errno());
706 }
707
708 return IPMI_CC_OK;
709 }
710
hiomap_erase(ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)711 static ipmi_ret_t hiomap_erase(ipmi_request_t request,
712 [[maybe_unused]] ipmi_response_t response,
713 ipmi_data_len_t data_len, ipmi_context_t context)
714 {
715 struct hiomap* ctx = static_cast<struct hiomap*>(context);
716
717 if (*data_len < 4)
718 {
719 return IPMI_CC_REQ_DATA_LEN_INVALID;
720 }
721
722 uint8_t* reqdata = (uint8_t*)request;
723 auto m = ctx->bus->new_method_call(HIOMAPD_SERVICE, HIOMAPD_OBJECT,
724 HIOMAPD_IFACE_V2, "Erase");
725 /* FIXME: Assumes v2 */
726 m.append(le16toh(get<uint16_t>(&reqdata[0]))); /* offset */
727 m.append(le16toh(get<uint16_t>(&reqdata[2]))); /* size */
728
729 try
730 {
731 auto reply = ctx->bus->call(m);
732
733 *data_len = 0;
734 }
735 catch (const exception_t& e)
736 {
737 return hiomap_xlate_errno(e.get_errno());
738 }
739
740 return IPMI_CC_OK;
741 }
742
743 #define HIOMAP_C_RESET 1
744 #define HIOMAP_C_GET_INFO 2
745 #define HIOMAP_C_GET_FLASH_INFO 3
746 #define HIOMAP_C_CREATE_READ_WINDOW 4
747 #define HIOMAP_C_CLOSE_WINDOW 5
748 #define HIOMAP_C_CREATE_WRITE_WINDOW 6
749 #define HIOMAP_C_MARK_DIRTY 7
750 #define HIOMAP_C_FLUSH 8
751 #define HIOMAP_C_ACK 9
752 #define HIOMAP_C_ERASE 10
753
754 static const std::unordered_map<uint8_t, hiomap_command> hiomap_commands = {
755 {0, nullptr}, /* Invalid command ID */
756 {HIOMAP_C_RESET, hiomap_reset},
757 {HIOMAP_C_GET_INFO, hiomap_get_info},
758 {HIOMAP_C_GET_FLASH_INFO, hiomap_get_flash_info},
759 {HIOMAP_C_CREATE_READ_WINDOW, hiomap_create_read_window},
760 {HIOMAP_C_CLOSE_WINDOW, hiomap_close_window},
761 {HIOMAP_C_CREATE_WRITE_WINDOW, hiomap_create_write_window},
762 {HIOMAP_C_MARK_DIRTY, hiomap_mark_dirty},
763 {HIOMAP_C_FLUSH, hiomap_flush},
764 {HIOMAP_C_ACK, hiomap_ack},
765 {HIOMAP_C_ERASE, hiomap_erase},
766 };
767
768 /* FIXME: Define this in the "right" place, wherever that is */
769 /* FIXME: Double evaluation */
770 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
771
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)772 static ipmi_ret_t hiomap_dispatch([[maybe_unused]] ipmi_netfn_t netfn,
773 [[maybe_unused]] ipmi_cmd_t cmd,
774 ipmi_request_t request,
775 ipmi_response_t response,
776 ipmi_data_len_t data_len,
777 ipmi_context_t context)
778 {
779 struct hiomap* ctx = static_cast<struct hiomap*>(context);
780
781 if (*data_len < 2)
782 {
783 *data_len = 0;
784 return IPMI_CC_REQ_DATA_LEN_INVALID;
785 }
786
787 uint8_t* ipmi_req = (uint8_t*)request;
788 uint8_t* ipmi_resp = (uint8_t*)response;
789 uint8_t hiomap_cmd = ipmi_req[0];
790
791 if (hiomap_cmd == 0 || hiomap_cmd > hiomap_commands.size() - 1)
792 {
793 *data_len = 0;
794 return IPMI_CC_PARM_OUT_OF_RANGE;
795 }
796
797 bool is_unversioned = (hiomap_cmd == HIOMAP_C_RESET ||
798 hiomap_cmd == HIOMAP_C_GET_INFO ||
799 hiomap_cmd == HIOMAP_C_ACK);
800 if (!is_unversioned && ctx->seq == ipmi_req[1])
801 {
802 *data_len = 0;
803 return IPMI_CC_INVALID_FIELD_REQUEST;
804 }
805
806 ctx->seq = ipmi_req[1];
807
808 uint8_t* flash_req = ipmi_req + 2;
809 size_t flash_len = *data_len - 2;
810 uint8_t* flash_resp = ipmi_resp + 2;
811
812 auto command = hiomap_commands.find(hiomap_cmd);
813 if (command == hiomap_commands.end())
814 {
815 *data_len = 0;
816 return IPMI_CC_INVALID;
817 }
818 ipmi_ret_t cc = command->second(flash_req, flash_resp, &flash_len, context);
819 if (cc != IPMI_CC_OK)
820 {
821 *data_len = 0;
822 return cc;
823 }
824
825 /* Populate the response command and sequence */
826 ipmi_resp[0] = hiomap_cmd;
827 ipmi_resp[1] = ctx->seq;
828
829 *data_len = flash_len + 2;
830
831 return cc;
832 }
833 } // namespace flash
834 } // namespace openpower
835
register_openpower_hiomap_commands()836 static void register_openpower_hiomap_commands()
837 {
838 using namespace phosphor::logging;
839 using namespace openpower::flash;
840
841 struct hiomap* ctx = new hiomap();
842
843 /* Initialise mapping from signal and property names to status bit */
844 ctx->event_lookup["DaemonReady"] = BMC_EVENT_DAEMON_READY;
845 ctx->event_lookup["FlashControlLost"] = BMC_EVENT_FLASH_CTRL_LOST;
846 ctx->event_lookup["WindowReset"] = BMC_EVENT_WINDOW_RESET;
847 ctx->event_lookup["ProtocolReset"] = BMC_EVENT_PROTOCOL_RESET;
848
849 ctx->bus = new bus_t(ipmid_get_sd_bus_connection());
850
851 /* Initialise signal handling */
852
853 /*
854 * Can't use temporaries here because that causes SEGFAULTs due to slot
855 * destruction (!?), so enjoy the weird wrapping.
856 */
857 ctx->properties = new bus::match_t(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