blob: f9b1c59809e2f02c907661b7471bf7bda0fc66d9 [file] [log] [blame] [edit]
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <libpldm/pldm.h>
#include <libpldm/utils.h>
#include <libpldm/platform.h>
#include <libpldm/control.h>
#include <compiler.h>
#include <msgbuf.h>
#include "control-internal.h"
#define PLDM_BASE_VERSIONS_COUNT 2
static const uint32_t PLDM_BASE_VERSIONS[PLDM_BASE_VERSIONS_COUNT] = {
/* PLDM 1.1.0 is current implemented. */
0xf1f1f000,
/* CRC. Calculated with python:
hex(crccheck.crc.Crc32.calc(struct.pack('<I', 0xf1f1f000)))
*/
0x539dbeba,
};
const bitfield8_t PLDM_CONTROL_COMMANDS[32] = {
// 0x00..0x07
{ .byte = (1 << PLDM_GET_TID | 1 << PLDM_GET_PLDM_VERSION |
1 << PLDM_GET_PLDM_TYPES | 1 << PLDM_GET_PLDM_COMMANDS) }
};
static int pldm_control_reply_error(uint8_t ccode,
const struct pldm_header_info *req_hdr,
struct pldm_msg *resp,
size_t *resp_payload_len)
{
int rc;
/* 1 byte completion code */
if (*resp_payload_len < 1) {
return -EOVERFLOW;
}
*resp_payload_len = 1;
rc = encode_cc_only_resp(req_hdr->instance, PLDM_FWUP, req_hdr->command,
ccode, resp);
if (rc != PLDM_SUCCESS) {
return -EINVAL;
}
return 0;
}
static int pldm_control_get_tid(const struct pldm_header_info *hdr,
const struct pldm_msg *req LIBPLDM_CC_UNUSED,
size_t req_payload_len, struct pldm_msg *resp,
size_t *resp_payload_len)
{
if (req_payload_len != PLDM_GET_TID_REQ_BYTES) {
return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr,
resp, resp_payload_len);
}
if (*resp_payload_len <= PLDM_GET_TID_RESP_BYTES) {
return -EOVERFLOW;
}
*resp_payload_len = PLDM_GET_TID_RESP_BYTES;
uint8_t cc = encode_get_tid_resp(hdr->instance, PLDM_SUCCESS,
PLDM_TID_UNASSIGNED, resp);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
return 0;
}
static int pldm_control_get_version(struct pldm_control *control,
const struct pldm_header_info *hdr,
const struct pldm_msg *req,
size_t req_payload_len,
struct pldm_msg *resp,
size_t *resp_payload_len)
{
uint8_t cc;
uint32_t handle;
uint8_t opflag;
uint8_t type;
cc = decode_get_version_req(req, req_payload_len, &handle, &opflag,
&type);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
/* Response is always sent as a single transfer */
if (opflag != PLDM_GET_FIRSTPART) {
return pldm_control_reply_error(
PLDM_CONTROL_INVALID_TRANSFER_OPERATION_FLAG, hdr, resp,
resp_payload_len);
}
const struct pldm_type_versions *v = NULL;
for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
if (control->types[i].pldm_type == type &&
control->types[i].versions) {
v = &control->types[i];
break;
}
}
if (!v) {
return pldm_control_reply_error(
PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr,
resp, resp_payload_len);
}
/* encode_get_version_resp doesn't have length checking */
uint32_t required_resp_payload =
1 + 4 + 1 + v->versions_count * sizeof(ver32_t);
if (*resp_payload_len < required_resp_payload) {
return -EOVERFLOW;
}
*resp_payload_len = required_resp_payload;
/* crc32 is included in the versions buffer */
cc = encode_get_version_resp(hdr->instance, PLDM_SUCCESS, 0,
PLDM_START_AND_END, v->versions,
v->versions_count * sizeof(ver32_t), resp);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
return 0;
}
static int pldm_control_get_types(struct pldm_control *control,
const struct pldm_header_info *hdr,
const struct pldm_msg *req LIBPLDM_CC_UNUSED,
size_t req_payload_len, struct pldm_msg *resp,
size_t *resp_payload_len)
{
uint8_t cc;
if (req_payload_len != PLDM_GET_TYPES_REQ_BYTES) {
return pldm_control_reply_error(PLDM_ERROR_INVALID_LENGTH, hdr,
resp, resp_payload_len);
}
bitfield8_t types[8];
memset(types, 0, sizeof(types));
for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
uint8_t ty = control->types[i].pldm_type;
if (ty < 64 && control->types[i].versions) {
uint8_t bit = 1 << (ty % 8);
types[ty / 8].byte |= bit;
}
}
/* encode_get_types_resp doesn't have length checking */
uint32_t required_resp_payload = 1 + 8;
if (*resp_payload_len < required_resp_payload) {
return -EOVERFLOW;
}
*resp_payload_len = required_resp_payload;
cc = encode_get_types_resp(hdr->instance, PLDM_SUCCESS, types, resp);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
return 0;
}
static int pldm_control_get_commands(struct pldm_control *control,
const struct pldm_header_info *hdr,
const struct pldm_msg *req,
size_t req_payload_len,
struct pldm_msg *resp,
size_t *resp_payload_len)
{
uint8_t cc;
uint8_t ty;
// version in request is ignored, since SelectPLDMVersion isn't supported currently
ver32_t version;
cc = decode_get_commands_req(req, req_payload_len, &ty, &version);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
const struct pldm_type_versions *v = NULL;
for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
if (control->types[i].pldm_type == ty &&
control->types[i].versions && control->types[i].commands) {
v = &control->types[i];
break;
}
}
if (!v) {
return pldm_control_reply_error(
PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA, hdr,
resp, resp_payload_len);
}
/* encode_get_commands_resp doesn't have length checking */
uint32_t required_resp_payload = 1 + 32;
if (*resp_payload_len < required_resp_payload) {
return -EOVERFLOW;
}
*resp_payload_len = required_resp_payload;
cc = encode_get_commands_resp(hdr->instance, PLDM_SUCCESS, v->commands,
resp);
if (cc) {
return pldm_control_reply_error(cc, hdr, resp,
resp_payload_len);
}
return 0;
}
/* A response should only be used when this returns 0, and *resp_len > 0 */
LIBPLDM_ABI_TESTING
int pldm_control_handle_msg(struct pldm_control *control, const void *req_msg,
size_t req_len, void *resp_msg, size_t *resp_len)
{
int rc;
/* Space for header plus completion code */
if (*resp_len < sizeof(struct pldm_msg_hdr) + 1) {
return -EOVERFLOW;
}
size_t resp_payload_len = *resp_len - sizeof(struct pldm_msg_hdr);
struct pldm_msg *resp = resp_msg;
if (req_len < sizeof(struct pldm_msg_hdr)) {
return -EOVERFLOW;
}
size_t req_payload_len = req_len - sizeof(struct pldm_msg_hdr);
const struct pldm_msg *req = req_msg;
struct pldm_header_info hdr;
rc = unpack_pldm_header(&req->hdr, &hdr);
if (rc != PLDM_SUCCESS) {
return -EINVAL;
}
if (hdr.pldm_type != PLDM_BASE) {
/* Caller should not have passed non-control */
return -ENOMSG;
}
if (hdr.msg_type != PLDM_REQUEST) {
return -EINVAL;
}
/* Dispatch command */
switch (hdr.command) {
case PLDM_GET_TID:
rc = pldm_control_get_tid(&hdr, req, req_payload_len, resp,
&resp_payload_len);
break;
case PLDM_GET_PLDM_VERSION:
rc = pldm_control_get_version(control, &hdr, req,
req_payload_len, resp,
&resp_payload_len);
break;
case PLDM_GET_PLDM_TYPES:
rc = pldm_control_get_types(control, &hdr, req, req_payload_len,
resp, &resp_payload_len);
break;
case PLDM_GET_PLDM_COMMANDS:
rc = pldm_control_get_commands(control, &hdr, req,
req_payload_len, resp,
&resp_payload_len);
break;
default:
rc = pldm_control_reply_error(PLDM_ERROR_UNSUPPORTED_PLDM_CMD,
&hdr, resp, &resp_payload_len);
}
if (rc == 0) {
*resp_len = resp_payload_len + sizeof(struct pldm_msg_hdr);
}
return rc;
}
LIBPLDM_ABI_TESTING
int pldm_control_setup(struct pldm_control *control, size_t pldm_control_size)
{
int rc;
if (pldm_control_size < sizeof(struct pldm_control)) {
return -EINVAL;
}
memset(control, 0, sizeof(struct pldm_control));
rc = pldm_control_add_type(control, PLDM_BASE, &PLDM_BASE_VERSIONS,
PLDM_BASE_VERSIONS_COUNT,
PLDM_CONTROL_COMMANDS);
if (rc) {
return rc;
}
return 0;
}
LIBPLDM_ABI_TESTING
int pldm_control_add_type(struct pldm_control *control, uint8_t pldm_type,
const void *versions, size_t versions_count,
const bitfield8_t *commands)
{
if (versions_count < 2) {
/* At least one version must be provided, along with a CRC32 */
return -EINVAL;
}
struct pldm_type_versions *v = NULL;
for (int i = 0; i < PLDM_CONTROL_MAX_VERSION_TYPES; i++) {
if (control->types[i].versions == NULL ||
(control->types[i].versions != NULL &&
control->types[i].pldm_type == pldm_type)) {
v = &control->types[i];
break;
}
}
if (!v) {
return -ENOMEM;
// No spare slots
}
v->pldm_type = pldm_type;
v->versions = versions;
v->versions_count = versions_count;
v->commands = commands;
return 0;
}