/** * Copyright © 2019 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pldm_interface.hpp" #include #include #include #include #include #include namespace openpower::pels { namespace service { constexpr auto pldm = "xyz.openbmc_project.PLDM"; } namespace object_path { constexpr auto pldm = "/xyz/openbmc_project/pldm"; } namespace interface { constexpr auto pldm_requester = "xyz.openbmc_project.PLDM.Requester"; } using namespace phosphor::logging; using namespace sdeventplus; using namespace sdeventplus::source; constexpr auto eidPath = "/usr/share/pldm/host_eid"; constexpr mctp_eid_t defaultEIDValue = 9; constexpr uint16_t pelFileType = 0; PLDMInterface::~PLDMInterface() { sd_bus_unref(_bus); closeFD(); } void PLDMInterface::closeFD() { if (_fd >= 0) { close(_fd); _fd = -1; } } void PLDMInterface::readEID() { _eid = defaultEIDValue; std::ifstream eidFile{eidPath}; if (!eidFile.good()) { log("Could not open host EID file"); } else { std::string eid; eidFile >> eid; if (!eid.empty()) { _eid = atoi(eid.c_str()); } else { log("EID file was empty"); } } } void PLDMInterface::open() { _fd = pldm_open(); if (_fd < 0) { auto e = errno; log("pldm_open failed", entry("ERRNO=%d", e), entry("RC=%d\n", _fd)); throw std::exception{}; } } void PLDMInterface::instanceIDCallback(sd_bus_message* msg) { if (!_inProgress) { // A cancelCmd was run, just return log( "A command was canceled while waiting for the instance ID"); return; } bool failed = false; auto rc = sd_bus_message_get_errno(msg); if (rc) { log("GetInstanceId D-Bus method failed", entry("ERRNO=%d", rc)); failed = true; } else { uint8_t id; rc = sd_bus_message_read_basic(msg, 'y', &id); if (rc < 0) { log("Could not read instance ID out of message", entry("ERROR=%d", rc)); failed = true; } else { _instanceID = id; } } if (failed) { _inProgress = false; callResponseFunc(ResponseStatus::failure); } else { startCommand(); } } int iidCallback(sd_bus_message* msg, void* data, sd_bus_error* /*err*/) { auto* interface = static_cast(data); interface->instanceIDCallback(msg); return 0; } void PLDMInterface::startCommand() { try { closeFD(); open(); registerReceiveCallback(); doSend(); _receiveTimer.restartOnce(_receiveTimeout); } catch (const std::exception& e) { cleanupCmd(); callResponseFunc(ResponseStatus::failure); } } void PLDMInterface::startReadInstanceID() { auto rc = sd_bus_call_method_async( _bus, NULL, service::pldm, object_path::pldm, interface::pldm_requester, "GetInstanceId", iidCallback, this, "y", _eid); if (rc < 0) { log("Error calling sd_bus_call_method_async", entry("RC=%d", rc), entry("MSG=%s", strerror(-rc))); throw std::exception{}; } } CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size) { _pelID = id; _pelSize = size; _inProgress = true; try { // Kick off the async call to get the instance ID if // necessary, otherwise start the command itself. if (!_instanceID) { startReadInstanceID(); } else { startCommand(); } } catch (const std::exception& e) { _inProgress = false; return CmdStatus::failure; } return CmdStatus::success; } void PLDMInterface::registerReceiveCallback() { _source = std::make_unique( _event, _fd, EPOLLIN, std::bind(std::mem_fn(&PLDMInterface::receive), this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } void PLDMInterface::doSend() { std::array requestMsg; auto request = reinterpret_cast(requestMsg.data()); auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize, request); if (rc != PLDM_SUCCESS) { log("encode_new_file_req failed", entry("RC=%d", rc)); throw std::exception{}; } rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size()); if (rc < 0) { auto e = errno; log("pldm_send failed", entry("RC=%d", rc), entry("ERRNO=%d", e)); throw std::exception{}; } } void PLDMInterface::receive(IO& /*io*/, int fd, uint32_t revents) { if (!(revents & EPOLLIN)) { return; } uint8_t* responseMsg = nullptr; size_t responseSize = 0; ResponseStatus status = ResponseStatus::success; auto rc = pldm_recv(_eid, fd, *_instanceID, &responseMsg, &responseSize); if (rc < 0) { if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH) { // We got a response to someone else's message. Ignore it. return; } else if (rc == PLDM_REQUESTER_NOT_RESP_MSG) { // Due to the MCTP loopback, we may get notified of the message // we just sent. return; } auto e = errno; log("pldm_recv failed", entry("RC=%d", rc), entry("ERRNO=%d", e)); status = ResponseStatus::failure; responseMsg = nullptr; } cleanupCmd(); // Can't use this instance ID anymore. _instanceID = std::nullopt; if (status == ResponseStatus::success) { uint8_t completionCode = 0; auto response = reinterpret_cast(responseMsg); auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES, &completionCode); if (decodeRC < 0) { log("decode_new_file_resp failed", entry("RC=%d", decodeRC)); status = ResponseStatus::failure; } else { if (completionCode != PLDM_SUCCESS) { log("Bad PLDM completion code", entry("COMPLETION_CODE=%d", completionCode)); status = ResponseStatus::failure; } } } callResponseFunc(status); if (responseMsg) { free(responseMsg); } } void PLDMInterface::receiveTimerExpired() { log("Timed out waiting for PLDM response"); // Cleanup, but keep the instance ID because the host didn't // respond so we can still use it. cleanupCmd(); callResponseFunc(ResponseStatus::failure); } void PLDMInterface::cancelCmd() { _instanceID = std::nullopt; cleanupCmd(); } void PLDMInterface::cleanupCmd() { _inProgress = false; _source.reset(); if (_receiveTimer.isEnabled()) { _receiveTimer.setEnabled(false); } closeFD(); } } // namespace openpower::pels