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