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/CHANGELOG.md b/CHANGELOG.md
index adcbb06..5d21dea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,6 +67,9 @@
 
 10. Add firmware update FD responder
 
+11. Add PLDM control responder. PLDM types and support commands/versions can be
+    registered.
+
 ### Fixed
 
 1. dsp: platform: Fix location of closing paren in overflow detection
diff --git a/include/libpldm/base.h b/include/libpldm/base.h
index 5b31d41..5c44b99 100644
--- a/include/libpldm/base.h
+++ b/include/libpldm/base.h
@@ -108,7 +108,9 @@
 #define PLDM_GET_VERSION_REQ_BYTES  6
 
 /* Response lengths are inclusive of completion code */
+#define PLDM_GET_TYPES_REQ_BYTES     0
 #define PLDM_GET_TYPES_RESP_BYTES    9
+#define PLDM_GET_TID_REQ_BYTES	     0
 #define PLDM_GET_TID_RESP_BYTES	     2
 #define PLDM_SET_TID_RESP_BYTES	     1
 #define PLDM_GET_COMMANDS_RESP_BYTES 33
diff --git a/include/libpldm/control.h b/include/libpldm/control.h
new file mode 100644
index 0000000..fbe7104
--- /dev/null
+++ b/include/libpldm/control.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <libpldm/pldm.h>
+#include <libpldm/base.h>
+#include <libpldm/utils.h>
+
+enum pldm_control_completion_codes {
+	PLDM_CONTROL_INVALID_DATA_TRANSFER_HANDLE = 0x80,
+	PLDM_CONTROL_INVALID_TRANSFER_OPERATION_FLAG = 0x81,
+	PLDM_CONTROL_INVALID_PLDM_TYPE_IN_REQUEST_DATA = 0x83,
+	PLDM_CONTROL_INVALID_PLDM_VERSION_IN_REQUEST_DATA = 0x84,
+};
+
+// Static storage can be allocated with PLDM_SIZEOF_CONTROL macro */
+struct pldm_control;
+
+/** @brief Handle a PLDM Control message
+ *
+ * @param[in] control
+ * @param[in] req_msg - PLDM incoming request message payload
+ * @param[in] req_len - length of req_msg buffer
+ * @param[out] resp_msg - PLDM outgoing response message payload buffer
+ * @param[inout] resp_len - length of available resp_msg buffer, will be updated
+ *                         with the length written to resp_msg.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ *
+ * Will provide a response to send when resp_len > 0 and returning 0.
+ */
+int pldm_control_handle_msg(struct pldm_control *control, const void *req_msg,
+			    size_t req_len, void *resp_msg, size_t *resp_len);
+
+/** @brief Initialise a struct pldm_control
+ *
+ * @param[in] control
+ * @param[in] pldm_control_size - pass PLDM_SIZEOF_CONTROL
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_control_setup(struct pldm_control *control, size_t pldm_control_size);
+
+/** @brief Add a PLDM type to report.
+ *
+ * @param[in] control
+ * @param[in] type - PLDM type, enum pldm_supported_types
+ * @param[in] versions - list of versions for GetPLDMVersion response.
+ *			 This is an array of 32-bit version values, followed by
+ *			 a CRC32 over the version values. The size of this buffer
+ * 			 is 4*versions_count. The versions buffer must remain
+ *			 present for the duration of the pldm_control's lifetime.
+ * @param[in] versions_count - number of entries in versions, including the trailing CRC32.
+ * @param[in] commands - pointer to an array of bitfield8_t[8], for GetPLDMCommands
+ * 			 response for this type. The buffer must remain
+ *			 present for the duration of the pldm_control's lifetime.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_control_add_type(struct pldm_control *control, uint8_t pldm_type,
+			  const void *versions, size_t versions_count,
+			  const bitfield8_t *commands);
diff --git a/include/libpldm/meson.build b/include/libpldm/meson.build
index a95cb56..2887f0b 100644
--- a/include/libpldm/meson.build
+++ b/include/libpldm/meson.build
@@ -4,6 +4,7 @@
     'bios.h',
     'bios_table.h',
     'compiler.h',
+    'control.h',
     'entity.h',
     'firmware_fd.h',
     'firmware_update.h',
@@ -47,8 +48,16 @@
     prefix: '#include "firmware_device/fd-internal.h"',
     include_directories: [include_src, libpldm_include_dir],
 )
+sizeof_pldm_control = compiler.sizeof(
+    'struct pldm_control',
+    prefix: '#include "control-internal.h"',
+    include_directories: [include_src, libpldm_include_dir],
+)
 sizes_h = configure_file(
-    configuration: {'sizeof_pldm_fd': sizeof_pldm_fd},
+    configuration: {
+        'sizeof_pldm_fd': sizeof_pldm_fd,
+        'sizeof_pldm_control': sizeof_pldm_control,
+    },
     input: 'sizes.h.in',
     output: 'sizes.h',
     install: true,
diff --git a/include/libpldm/sizes.h.in b/include/libpldm/sizes.h.in
index 85c0330..b6287cf 100644
--- a/include/libpldm/sizes.h.in
+++ b/include/libpldm/sizes.h.in
@@ -2,3 +2,6 @@
 
 /* sizeof(struct pldm_fd) */
 #define PLDM_SIZEOF_PLDM_FD @sizeof_pldm_fd@
+
+/* sizeof(struct pldm_control) */
+#define PLDM_SIZEOF_PLDM_CONTROL @sizeof_pldm_control@
diff --git a/src/control-internal.h b/src/control-internal.h
new file mode 100644
index 0000000..00b05b7
--- /dev/null
+++ b/src/control-internal.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/utils.h>
+#include <compiler.h>
+#include <msgbuf.h>
+
+#ifndef PLDM_CONTROL_MAX_VERSION_TYPES
+#define PLDM_CONTROL_MAX_VERSION_TYPES 6
+#endif
+
+struct pldm_type_versions {
+	/* A buffer of ver32_t/uint32_t of version values, followed by crc32 */
+	/* NULL for unused entries */
+	const void *versions;
+	/* Includes the trailing crc32 entry */
+	uint8_t versions_count;
+
+	/* A buffer of 32 entries, for commands 0-0xff */
+	const bitfield8_t *commands;
+
+	uint8_t pldm_type;
+};
+
+struct pldm_control {
+	struct pldm_type_versions types[PLDM_CONTROL_MAX_VERSION_TYPES];
+};
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;
+}
diff --git a/src/meson.build b/src/meson.build
index 3c8afc1..7803d91 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,4 +1,4 @@
-libpldm_sources = files('responder.c', 'utils.c')
+libpldm_sources = files('control.c', 'responder.c', 'utils.c')
 
 subdir('dsp')