blob: c8f59e2554a435a7ea53b529d65b4e0b637c98cc [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/oem/ibm/file_io.h>
#include <libpldm/transport.h>
#include <libpldm/transport/mctp-demux.h>
#include <poll.h>
#include <unistd.h>
#include <phosphor-logging/lg2.hpp>
#include <fstream>
namespace openpower::pels
{
using namespace sdeventplus;
using namespace sdeventplus::source;
using TerminusID = uint8_t;
constexpr auto eidPath = "/usr/share/pldm/host_eid";
constexpr mctp_eid_t defaultEIDValue = 9;
constexpr TerminusID tid = defaultEIDValue;
constexpr uint16_t pelFileType = 0;
PLDMInterface::~PLDMInterface()
{
freeIID();
pldm_instance_db_destroy(_pldm_idb);
closeFD();
}
void PLDMInterface::closeFD()
{
pldm_transport_mctp_demux_destroy(mctpDemux);
mctpDemux = nullptr;
_fd = -1;
pldmTransport = nullptr;
}
void PLDMInterface::readEID()
{
_eid = defaultEIDValue;
std::ifstream eidFile{eidPath};
if (!eidFile.good())
{
lg2::error("Could not open host EID file");
}
else
{
std::string eid;
eidFile >> eid;
if (!eid.empty())
{
_eid = atoi(eid.c_str());
}
else
{
lg2::error("EID file was empty");
}
}
}
void PLDMInterface::open()
{
if (pldmTransport)
{
lg2::error("open: pldmTransport already setup!");
throw std::runtime_error{"open failed"};
}
_fd = openMctpDemuxTransport();
if (_fd < 0)
{
auto e = errno;
lg2::error("Transport open failed. errno = {ERRNO}, rc = {RC}", "ERRNO",
e, "RC", _fd);
throw std::runtime_error{"Transport open failed"};
}
}
int PLDMInterface::openMctpDemuxTransport()
{
int rc = pldm_transport_mctp_demux_init(&mctpDemux);
if (rc)
{
lg2::error(
"openMctpDemuxTransport: Failed to init MCTP demux transport. rc = {RC}",
"RC", rc);
return rc;
}
rc = pldm_transport_mctp_demux_map_tid(mctpDemux, tid, tid);
if (rc)
{
lg2::error(
"openMctpDemuxTransport: Failed to setup tid to eid mapping. rc = {RC}",
"RC", rc);
cleanupCmd();
return rc;
}
pldmTransport = pldm_transport_mctp_demux_core(mctpDemux);
struct pollfd pollfd;
rc = pldm_transport_mctp_demux_init_pollfd(pldmTransport, &pollfd);
if (rc)
{
lg2::error("openMctpDemuxTransport: Failed to get pollfd. rc = {RC}",
"RC", rc);
cleanupCmd();
return rc;
}
return pollfd.fd;
}
void PLDMInterface::startCommand()
{
try
{
closeFD();
open();
registerReceiveCallback();
doSend();
_receiveTimer.restartOnce(_receiveTimeout);
}
catch (const std::exception& e)
{
lg2::error("startCommand exception: {ERROR}", "ERROR", e);
cleanupCmd();
throw;
}
}
void PLDMInterface::allocIID()
{
if (_instanceID)
{
return;
}
pldm_instance_id_t iid = 0;
auto rc = pldm_instance_id_alloc(_pldm_idb, _eid, &iid);
if (rc == -EAGAIN)
{
throw std::runtime_error("No free instance ids");
}
else if (rc)
{
throw std::system_category().default_error_condition(rc);
}
_instanceID = iid;
}
void PLDMInterface::freeIID()
{
if (!_instanceID)
{
return;
}
auto rc = pldm_instance_id_free(_pldm_idb, _eid, *_instanceID);
if (rc == -EINVAL)
{
throw std::runtime_error(
"Instance ID " + std::to_string(*_instanceID) + " for TID " +
std::to_string(_eid) + " was not previously allocated");
}
else if (rc)
{
throw std::system_category().default_error_condition(rc);
}
_instanceID = std::nullopt;
}
CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
{
_pelID = id;
_pelSize = size;
_inProgress = true;
try
{
// Allocate the instance ID, as needed.
if (!_instanceID)
{
allocIID();
}
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, pldmTransport));
}
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)
{
lg2::error("encode_new_file_req failed, rc = {RC}", "RC", rc);
throw std::runtime_error{"encode_new_file_req failed"};
}
pldm_tid_t pldmTID = static_cast<pldm_tid_t>(_eid);
rc = pldm_transport_send_msg(pldmTransport, pldmTID, requestMsg.data(),
requestMsg.size());
if (rc < 0)
{
auto e = errno;
lg2::error("pldm_transport_send_msg failed, rc = {RC}, errno = {ERRNO}",
"RC", rc, "ERRNO", e);
throw std::runtime_error{"pldm_transport_send_msg failed"};
}
}
void PLDMInterface::receive(IO& /*io*/, int /*fd*/, uint32_t revents,
pldm_transport* transport)
{
if (!(revents & EPOLLIN))
{
return;
}
void* responseMsg = nullptr;
size_t responseSize = 0;
ResponseStatus status = ResponseStatus::success;
pldm_tid_t pldmTID;
auto rc = pldm_transport_recv_msg(transport, &pldmTID, &responseMsg,
&responseSize);
struct pldm_msg_hdr* hdr = (struct pldm_msg_hdr*)responseMsg;
if (pldmTID != _eid)
{
// We got a response to someone else's message. Ignore it.
return;
}
if (rc)
{
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;
lg2::error("pldm_transport_recv_msg failed, rc = {RC}, errno = {ERRNO}",
"RC",
static_cast<std::underlying_type_t<pldm_requester_rc_t>>(rc),
"ERRNO", e);
status = ResponseStatus::failure;
responseMsg = nullptr;
}
if (hdr && (hdr->request || hdr->datagram))
{
free(responseMsg);
return;
}
cleanupCmd();
// Can't use this instance ID anymore.
freeIID();
if (status == ResponseStatus::success)
{
uint8_t completionCode = 0;
auto response = reinterpret_cast<pldm_msg*>(responseMsg);
auto decodeRC =
decode_new_file_resp(response, responseSize, &completionCode);
if (decodeRC < 0)
{
lg2::error("decode_new_file_resp failed, rc = {RC}", "RC",
decodeRC);
status = ResponseStatus::failure;
}
else
{
if (completionCode != PLDM_SUCCESS)
{
lg2::error("Bad PLDM completion code {CODE}", "CODE",
completionCode);
status = ResponseStatus::failure;
}
}
}
callResponseFunc(status);
if (responseMsg)
{
free(responseMsg);
}
}
void PLDMInterface::receiveTimerExpired()
{
lg2::error("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()
{
freeIID();
cleanupCmd();
}
void PLDMInterface::cleanupCmd()
{
_inProgress = false;
_source.reset();
if (_receiveTimer.isEnabled())
{
_receiveTimer.setEnabled(false);
}
closeFD();
}
} // namespace openpower::pels