libpldm: Add PLDM control responder
This will respond to the mandatory PLDM control command types.
Change-Id: I483bfdb6513cc6ec77a04480397993e42160c0ae
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/src/control.c b/src/control.c
new file mode 100644
index 0000000..f9b1c59
--- /dev/null
+++ b/src/control.c
@@ -0,0 +1,340 @@
+#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;
+}