blob: dca8a2d9ae29d4d8a12dba00e0f22465e6265fe9 [file] [log] [blame]
/**
* 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 <libpldm/base.h>
#include <libpldm/file_io.h>
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <phosphor-logging/log.hpp>
#include <fstream>
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<level::ERR>("Could not open host EID file");
}
else
{
std::string eid;
eidFile >> eid;
if (!eid.empty())
{
_eid = atoi(eid.c_str());
}
else
{
log<level::ERR>("EID file was empty");
}
}
}
void PLDMInterface::open()
{
_fd = pldm_open();
if (_fd < 0)
{
auto e = errno;
log<level::ERR>("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<level::INFO>(
"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<level::ERR>("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<level::ERR>("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<PLDMInterface*>(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<level::ERR>("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<IO>(
_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<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
sizeof(_pelID) + sizeof(uint64_t)>
requestMsg;
auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
auto rc = encode_new_file_req(*_instanceID, pelFileType, _pelID, _pelSize,
request);
if (rc != PLDM_SUCCESS)
{
log<level::ERR>("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<level::ERR>("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<level::ERR>("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<pldm_msg*>(responseMsg);
auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES,
&completionCode);
if (decodeRC < 0)
{
log<level::ERR>("decode_new_file_resp failed",
entry("RC=%d", decodeRC));
status = ResponseStatus::failure;
}
else
{
if (completionCode != PLDM_SUCCESS)
{
log<level::ERR>("Bad PLDM completion code",
entry("COMPLETION_CODE=%d", completionCode));
status = ResponseStatus::failure;
}
}
}
callResponseFunc(status);
if (responseMsg)
{
free(responseMsg);
}
}
void PLDMInterface::receiveTimerExpired()
{
log<level::ERR>("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