blob: 8e6031532126be01869537971a5eb31a0f7243d7 [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 <unistd.h>
#include <fstream>
#include <phosphor-logging/log.hpp>
namespace openpower::pels
{
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()
{
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{};
}
}
CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
{
try
{
closeFD();
open();
readInstanceID();
registerReceiveCallback();
doSend(id, size);
}
catch (const std::exception& e)
{
closeFD();
_inProgress = false;
_source.reset();
return CmdStatus::failure;
}
_inProgress = true;
_receiveTimer.restartOnce(_receiveTimeout);
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::readInstanceID()
{
try
{
_instanceID = _dataIface.getPLDMInstanceID(_eid);
}
catch (const std::exception& e)
{
log<level::ERR>(
"Failed to get instance ID from PLDM Requester D-Bus daemon",
entry("ERROR=%s", e.what()));
throw;
}
}
void PLDMInterface::doSend(uint32_t id, uint32_t size)
{
std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
sizeof(id) + sizeof(size)>
requestMsg;
auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
auto rc = encode_new_file_req(_instanceID, pelFileType, id, size, 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;
}
_inProgress = false;
_receiveTimer.setEnabled(false);
closeFD();
_source.reset();
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;
}
}
}
if (_responseFunc)
{
try
{
(*_responseFunc)(status);
}
catch (const std::exception& e)
{
log<level::ERR>("PLDM response callback threw an exception",
entry("ERROR=%s", e.what()));
}
}
if (responseMsg)
{
free(responseMsg);
}
}
void PLDMInterface::receiveTimerExpired()
{
log<level::ERR>("Timed out waiting for PLDM response");
cancelCmd();
if (_responseFunc)
{
try
{
(*_responseFunc)(ResponseStatus::failure);
}
catch (const std::exception& e)
{
log<level::ERR>("PLDM response callback threw an exception",
entry("ERROR=%s", e.what()));
}
}
}
void PLDMInterface::cancelCmd()
{
_inProgress = false;
_source.reset();
if (_receiveTimer.isEnabled())
{
_receiveTimer.setEnabled(false);
}
closeFD();
}
} // namespace openpower::pels