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;
+}