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 = sdbusplus::message::variant_ns::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