control: Add basic MCTP Control Protocol handler

This will respond to the 4 mandatory MCTP Control Protocol commands.
Applications can register supported types using mctp_control_add_type().

Change-Id: Ia904bcbe118626adf9254ffa71dd8e17fbdfc9b7
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6bfb3c1..7b2506f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,7 +22,7 @@
 add_definitions (-DMCTP_HAVE_STDIO)
 add_definitions (-DMCTP_DEFAULT_ALLOC)
 
-add_library (mctp STATIC alloc.c astlpc.c crc32.c core.c log.c libmctp.h serial.c crc-16-ccitt.c)
+add_library (mctp STATIC alloc.c astlpc.c crc32.c core.c log.c libmctp.h serial.c crc-16-ccitt.c control.c)
 
 target_include_directories (mctp PUBLIC
                             $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
diff --git a/Makefile.am b/Makefile.am
index 984a5d4..bd7db2b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,7 +3,7 @@
 lib_LTLIBRARIES = libmctp.la
 libmctp_la_SOURCES = core.c alloc.c log.c \
 		     libmctp-alloc.h libmctp-log.h \
-		     libmctp-cmds.h
+		     libmctp-cmds.h control.c
 include_HEADERS = libmctp.h
 
 if LIBMCTP_BINDING_serial
diff --git a/control.c b/control.c
new file mode 100644
index 0000000..fedfe56
--- /dev/null
+++ b/control.c
@@ -0,0 +1,309 @@
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include "libmctp-cmds.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "core-internal.h"
+
+#include "control.h"
+
+static void fill_resp(const void *req, struct mctp_ctrl_msg_hdr *hdr)
+{
+	const struct mctp_ctrl_msg_hdr *req_hdr = req;
+	hdr->ic_msg_type = MCTP_CTRL_HDR_MSG_TYPE;
+	hdr->rq_dgram_inst = req_hdr->rq_dgram_inst &
+			     MCTP_CTRL_HDR_INSTANCE_ID_MASK;
+	hdr->command_code = req_hdr->command_code;
+}
+
+static uint8_t mctp_ctrl_set_endpoint_id(struct mctp_bus *bus, uint8_t src_eid,
+					 uint8_t msg_tag, const void *data,
+					 size_t len)
+{
+	if (len != sizeof(struct mctp_ctrl_cmd_set_endpoint_id_req)) {
+		return MCTP_CTRL_CC_ERROR_INVALID_LENGTH;
+	}
+	const struct mctp_ctrl_cmd_set_endpoint_id_req *req = data;
+
+	uint8_t op = req->operation & MCTP_CTRL_SET_EID_OP_MASK;
+	if (!(op == MCTP_CTRL_SET_EID_OP_SET ||
+	      op == MCTP_CTRL_SET_EID_OP_FORCE)) {
+		return MCTP_CTRL_CC_ERROR_INVALID_DATA;
+	}
+
+	if (mctp_bus_set_eid(bus->binding, req->eid)) {
+		return MCTP_CTRL_CC_ERROR_INVALID_DATA;
+	}
+
+	struct mctp_ctrl_cmd_set_endpoint_id_resp *resp =
+		__mctp_msg_alloc(sizeof(*resp), bus->mctp);
+	if (!resp) {
+		mctp_prdebug("no response buffer");
+		return MCTP_CTRL_CC_ERROR;
+	}
+	memset(resp, 0x00, sizeof(*resp));
+	fill_resp(data, &resp->hdr);
+	resp->completion_code = MCTP_CTRL_CC_SUCCESS;
+	resp->status = MCTP_CTRL_SET_EID_STATUS_ACCEPTED;
+	resp->eid = req->eid;
+	resp->pool_size = 0;
+
+	int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag,
+					 resp, sizeof(*resp));
+	if (!rc) {
+		mctp_prdebug("set_endpoint_id response send failed: %d", rc);
+	}
+	return MCTP_CTRL_CC_SUCCESS;
+}
+
+static uint8_t mctp_ctrl_get_endpoint_id(struct mctp_bus *bus, uint8_t src_eid,
+					 uint8_t msg_tag, const void *data,
+					 size_t len)
+{
+	if (len != sizeof(struct mctp_ctrl_msg_hdr)) {
+		/* Expect empty request */
+		return MCTP_CTRL_CC_ERROR_INVALID_LENGTH;
+	}
+	(void)data;
+
+	struct mctp_ctrl_cmd_get_endpoint_id_resp *resp =
+		__mctp_msg_alloc(sizeof(*resp), bus->mctp);
+	if (!resp) {
+		mctp_prdebug("no response buffer");
+		return MCTP_CTRL_CC_ERROR;
+	}
+	memset(resp, 0x00, sizeof(*resp));
+	fill_resp(data, &resp->hdr);
+	resp->completion_code = MCTP_CTRL_CC_SUCCESS;
+	resp->endpoint_id = bus->eid;
+	resp->endpoint_type = MCTP_CTRL_ENDPOINT_TYPE_SIMPLE |
+			      MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC;
+	resp->medium_specific = 0x00;
+
+	int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag,
+					 resp, sizeof(*resp));
+	if (!rc) {
+		mctp_prdebug("get_endpoint_id response send failed: %d", rc);
+	}
+	return MCTP_CTRL_CC_SUCCESS;
+}
+
+#define MCTP_PROTOCOL_COUNT 4
+/* Big endian */
+const uint8_t MCTP_PROTOCOL_VERSIONS[MCTP_PROTOCOL_COUNT * 4] = {
+	// 1.0
+	0xf1,
+	0xf0,
+	0xff,
+	0x00,
+	// 1.1
+	0xf1,
+	0xf1,
+	0xff,
+	0x00,
+	// 1.2
+	0xf1,
+	0xf2,
+	0xff,
+	0x00,
+	// 1.3.3
+	0xf1,
+	0xf3,
+	0xf3,
+	0x00,
+};
+
+static uint8_t mctp_ctrl_get_version(struct mctp_bus *bus, uint8_t src_eid,
+				     uint8_t msg_tag, const void *data,
+				     size_t len)
+{
+	if (len != sizeof(struct mctp_ctrl_cmd_get_version_req)) {
+		return MCTP_CTRL_CC_ERROR_INVALID_LENGTH;
+	}
+	const struct mctp_ctrl_cmd_get_version_req *req = data;
+
+	switch (req->msg_type) {
+	case 0x00:
+	case 0xff:
+		/* Only have versions for MCTP base or control */
+		break;
+	default:
+		return MCTP_CTRL_VERSIONS_NOT_SUPPORTED;
+	}
+
+	/* Return only the versions for MCTP */
+	size_t total_sz = sizeof(struct mctp_ctrl_cmd_get_version_resp) +
+			  sizeof(MCTP_PROTOCOL_VERSIONS);
+
+	struct mctp_ctrl_cmd_get_version_resp *resp =
+		__mctp_msg_alloc(total_sz, bus->mctp);
+	if (!resp) {
+		mctp_prdebug("no response buffer");
+		return MCTP_CTRL_CC_ERROR;
+	}
+	memset(resp, 0x00, total_sz);
+	fill_resp(data, &resp->hdr);
+	resp->completion_code = MCTP_CTRL_CC_SUCCESS;
+	resp->version_count = MCTP_PROTOCOL_COUNT;
+	memcpy(resp->versions, MCTP_PROTOCOL_VERSIONS,
+	       sizeof(MCTP_PROTOCOL_VERSIONS));
+
+	int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag,
+					 resp, total_sz);
+	if (!rc) {
+		mctp_prdebug("mctp get_version response send failed: %d", rc);
+	}
+	return MCTP_CTRL_CC_SUCCESS;
+}
+
+static uint8_t mctp_ctrl_get_types(struct mctp_bus *bus, uint8_t src_eid,
+				   uint8_t msg_tag, const void *data,
+				   size_t len)
+{
+	if (len != sizeof(struct mctp_ctrl_msg_hdr)) {
+		return MCTP_CTRL_CC_ERROR_INVALID_LENGTH;
+	}
+	(void)data;
+
+	size_t total_sz = sizeof(struct mctp_ctrl_cmd_get_types_resp) +
+			  bus->mctp->control.num_msg_types;
+
+	struct mctp_ctrl_cmd_get_types_resp *resp =
+		__mctp_msg_alloc(total_sz, bus->mctp);
+	if (!resp) {
+		mctp_prdebug("no response buffer");
+		return MCTP_CTRL_CC_ERROR;
+	}
+	memset(resp, 0x00, total_sz);
+	fill_resp(data, &resp->hdr);
+	resp->completion_code = MCTP_CTRL_CC_SUCCESS;
+	resp->type_count = bus->mctp->control.num_msg_types;
+	memcpy(resp->types, bus->mctp->control.msg_types,
+	       bus->mctp->control.num_msg_types);
+
+	int rc = mctp_message_tx_alloced(bus->mctp, src_eid, false, msg_tag,
+					 resp, total_sz);
+	if (!rc) {
+		mctp_prdebug("mctp get_types response send failed: %d", rc);
+	}
+	return MCTP_CTRL_CC_SUCCESS;
+}
+
+static void reply_error(struct mctp *mctp, uint8_t src_eid, uint8_t msg_tag,
+			const struct mctp_ctrl_msg_hdr *ctrl_hdr, uint8_t ccode)
+{
+	struct mctp_ctrl_cmd_empty_resp *resp =
+		__mctp_msg_alloc(sizeof(*resp), mctp);
+	if (!resp) {
+		mctp_prdebug("no response buffer");
+		return;
+	}
+	memset(resp, 0x00, sizeof(*resp));
+	fill_resp(ctrl_hdr, &resp->hdr);
+	resp->completion_code = ccode;
+
+	int rc = mctp_message_tx_alloced(mctp, src_eid, false, msg_tag, resp,
+					 sizeof(*resp));
+	if (!rc) {
+		mctp_prdebug("error response send failed: %d", rc);
+	}
+}
+
+/* Control message request handler. This will respond to the mandatory MCTP control
+ * commands */
+bool mctp_control_handler(struct mctp_bus *bus, mctp_eid_t src_eid,
+			  bool tag_owner, uint8_t msg_tag, const void *data,
+			  size_t len)
+{
+	if (!tag_owner) {
+		// Not a request
+		return false;
+	}
+
+	if (len < 1) {
+		// No type byte
+		return false;
+	}
+
+	const struct mctp_ctrl_msg_hdr *ctrl_hdr = data;
+	if (ctrl_hdr->ic_msg_type != MCTP_CTRL_HDR_MSG_TYPE) {
+		// Not Control type
+		return false;
+	}
+
+	if (len < sizeof(struct mctp_ctrl_msg_hdr)) {
+		// Drop short messages, but treat as handled
+		return true;
+	}
+
+	if ((ctrl_hdr->rq_dgram_inst &
+	     (MCTP_CTRL_HDR_FLAG_REQUEST | MCTP_CTRL_HDR_FLAG_DGRAM)) !=
+	    MCTP_CTRL_HDR_FLAG_REQUEST) {
+		// Drop message, isn't a request.
+		// Treat as handled since TO bit was set.
+		return true;
+	}
+
+	// A valid MCTP Control request has been received, process it
+
+	uint8_t cc = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD;
+	switch (ctrl_hdr->command_code) {
+	case MCTP_CTRL_CMD_SET_ENDPOINT_ID:
+		cc = mctp_ctrl_set_endpoint_id(bus, src_eid, msg_tag, data,
+					       len);
+		break;
+	case MCTP_CTRL_CMD_GET_ENDPOINT_ID:
+		cc = mctp_ctrl_get_endpoint_id(bus, src_eid, msg_tag, data,
+					       len);
+		break;
+	case MCTP_CTRL_CMD_GET_VERSION_SUPPORT:
+		cc = mctp_ctrl_get_version(bus, src_eid, msg_tag, data, len);
+		break;
+	case MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT:
+		cc = mctp_ctrl_get_types(bus, src_eid, msg_tag, data, len);
+		break;
+	default:
+		cc = MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD;
+		break;
+	}
+
+	if (cc) {
+		reply_error(bus->mctp, src_eid, msg_tag, ctrl_hdr, cc);
+	}
+
+	// No further handling required.
+	return true;
+}
+
+int mctp_control_add_type(struct mctp *mctp, uint8_t msg_type)
+{
+	/* Check for existing */
+	for (size_t i = 0; i < mctp->control.num_msg_types; i++) {
+		if (mctp->control.msg_types[i] == msg_type) {
+			return 0;
+		}
+	}
+
+	if (mctp->control.num_msg_types == MCTP_CONTROL_MAX_TYPES) {
+		return -ENOSPC;
+	}
+
+	mctp->control.msg_types[mctp->control.num_msg_types] = msg_type;
+	mctp->control.num_msg_types++;
+	return 0;
+}
+
+void mctp_control_remove_type(struct mctp *mctp, uint8_t msg_type)
+{
+	for (size_t i = 0; i < mctp->control.num_msg_types; i++) {
+		if (mctp->control.msg_types[i] == msg_type) {
+			memmove(&mctp->control.msg_types[i],
+				&mctp->control.msg_types[i + 1],
+				mctp->control.num_msg_types - (i + 1));
+			mctp->control.num_msg_types--;
+		}
+	}
+}
diff --git a/control.h b/control.h
new file mode 100644
index 0000000..579ea55
--- /dev/null
+++ b/control.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "libmctp.h"
+
+/* Handle a MCTP control message. Returns true for control requests,
+ * false otherwise */
+bool mctp_control_handler(struct mctp_bus *bus, uint8_t src_eid, bool tag_owner,
+			  uint8_t msg_tag, const void *data, size_t len);
diff --git a/core-internal.h b/core-internal.h
index c8e7b25..ef0fc83 100644
--- a/core-internal.h
+++ b/core-internal.h
@@ -28,6 +28,10 @@
 #define MCTP_DEFAULT_CLOCK_GETTIME 1
 #endif
 
+#ifndef MCTP_CONTROL_HANDLER
+#define MCTP_CONTROL_HANDLER 1
+#endif
+
 /* Tag expiry timeout, in milliseconds */
 static const uint64_t MCTP_TAG_TIMEOUT = 6000;
 
@@ -84,6 +88,14 @@
 	uint64_t expiry;
 };
 
+#define MCTP_CONTROL_MAX_TYPES 10
+
+struct mctp_control {
+	/* Types to report from Get MCTP Version Support */
+	uint8_t msg_types[MCTP_CONTROL_MAX_TYPES];
+	size_t num_msg_types;
+};
+
 struct mctp {
 	int n_busses;
 	struct mctp_bus busses[MCTP_MAX_BUSSES];
@@ -110,6 +122,10 @@
 	} route_policy;
 	size_t max_message_size;
 
+#if MCTP_CONTROL_HANDLER
+	struct mctp_control control;
+#endif
+
 	void *alloc_ctx;
 
 	uint64_t (*platform_now)(void *);
diff --git a/core.c b/core.c
index e28f6bc..8a0dc13 100644
--- a/core.c
+++ b/core.c
@@ -19,6 +19,7 @@
 #include "range.h"
 #include "compiler.h"
 #include "core-internal.h"
+#include "control.h"
 
 #if MCTP_DEFAULT_CLOCK_GETTIME
 #include <time.h>
@@ -266,6 +267,9 @@
 #if MCTP_DEFAULT_CLOCK_GETTIME
 	mctp->platform_now = mctp_default_now;
 #endif
+#if MCTP_CONTROL_HANDLER
+	mctp_control_add_type(mctp, MCTP_CTRL_HDR_MSG_TYPE);
+#endif
 	return 0;
 }
 
@@ -360,6 +364,16 @@
 	return rc;
 }
 
+int mctp_bus_set_eid(struct mctp_binding *binding, mctp_eid_t eid)
+{
+	if (eid < 8 || eid == 0xff) {
+		return -EINVAL;
+	}
+
+	binding->bus->eid = eid;
+	return 0;
+}
+
 void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding)
 {
 	/*
@@ -444,6 +458,12 @@
 						 buffer, length);
 			return true;
 		}
+	} else {
+#if MCTP_CONTROL_HANDLER
+		/* libmctp will handle control requests */
+		return mctp_control_handler(bus, src, tag_owner, msg_tag,
+					    buffer, length);
+#endif
 	}
 
 	/*
diff --git a/libmctp-cmds.h b/libmctp-cmds.h
index 80f3513..406a096 100644
--- a/libmctp-cmds.h
+++ b/libmctp-cmds.h
@@ -66,6 +66,75 @@
 #define MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD 0x05
 /* 0x80 - 0xFF are command specific */
 
+struct mctp_ctrl_cmd_empty_resp {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t completion_code;
+} __attribute__((packed));
+
+/* Set Endpoint ID request, Operation. Bits [1:0] */
+#define MCTP_CTRL_SET_EID_OP_MASK	    0x03
+#define MCTP_CTRL_SET_EID_OP_SET	    0x00
+#define MCTP_CTRL_SET_EID_OP_FORCE	    0x01
+#define MCTP_CTRL_SET_EID_OP_RESET	    0x02
+#define MCTP_CTRL_SET_EID_OP_SET_DISCOVERED 0x03
+
+struct mctp_ctrl_cmd_set_endpoint_id_req {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t operation;
+	uint8_t eid;
+} __attribute__((packed));
+
+/* Set Endpoint ID response, assignment status. Bits [1:0] */
+#define MCTP_CTRL_SET_EID_STATUS_ACCEPTED 0x00
+#define MCTP_CTRL_SET_EID_STATUS_REJECTED 0x01
+
+struct mctp_ctrl_cmd_set_endpoint_id_resp {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t completion_code;
+	uint8_t status;
+	uint8_t eid;
+	uint8_t pool_size;
+} __attribute__((packed));
+
+/* Get Endpoint ID, Endpoint Type. Bits [5:4] */
+#define MCTP_CTRL_ENDPOINT_TYPE_SIMPLE		0x00
+#define MCTP_CTRL_ENDPOINT_TYPE_BUSOWNER_BRIDGE 0x10
+
+/* Get Endpoint ID, Endpoint ID Type. Bits [1:0] */
+#define MCTP_CTRL_ENDPOINT_ID_TYPE_DYNAMIC_ONLY	    0x00
+#define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC	    0x01
+#define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC_SAME	    0x02
+#define MCTP_CTRL_ENDPOINT_ID_TYPE_STATIC_DIFFERENT 0x03
+
+struct mctp_ctrl_cmd_get_endpoint_id_resp {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t completion_code;
+	uint8_t endpoint_id;
+	uint8_t endpoint_type;
+	uint8_t medium_specific;
+} __attribute__((packed));
+
+#define MCTP_CTRL_VERSIONS_NOT_SUPPORTED 0x80
+
+struct mctp_ctrl_cmd_get_version_req {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t msg_type;
+} __attribute__((packed));
+
+struct mctp_ctrl_cmd_get_version_resp {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t completion_code;
+	uint8_t version_count;
+	uint32_t versions[];
+} __attribute__((packed));
+
+struct mctp_ctrl_cmd_get_types_resp {
+	struct mctp_ctrl_msg_hdr hdr;
+	uint8_t completion_code;
+	uint8_t type_count;
+	uint8_t types[];
+} __attribute__((packed));
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libmctp.h b/libmctp.h
index f78132b..2fdd812 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -110,6 +110,8 @@
 
 void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding);
 
+int mctp_bus_set_eid(struct mctp_binding *binding, mctp_eid_t eid);
+
 /* Create a simple bidirectional bridge between busses.
  *
  * In this mode, the MCTP stack is initialised as a bridge. There is no EID
@@ -233,6 +235,14 @@
 /* Returns a timestamp in milliseconds */
 uint64_t mctp_now(struct mctp *mctp);
 
+int mctp_control_handler_enable(struct mctp *mctp);
+void mctp_control_handler_disable(struct mctp *mctp);
+
+/* Add/remove message types to be reported by Get MCTP Version Support.
+ * Control type is added automatically for the control handler */
+int mctp_control_add_type(struct mctp *mctp, uint8_t msg_type);
+void mctp_control_remove_type(struct mctp *mctp, uint8_t msg_type);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/meson.build b/meson.build
index fa6ff81..28c0126 100644
--- a/meson.build
+++ b/meson.build
@@ -14,6 +14,7 @@
 sources = [
      'core.c',
      'alloc.c',
+     'control.c',
 ]
 
 headers = [
@@ -45,6 +46,9 @@
 i2c_headers = [
     'libmctp-i2c.h',
 ]
+control_sources = [
+    'control.c',
+]
 
 libmctp_sources = sources
 libmctp_headers = headers
@@ -61,6 +65,9 @@
     libmctp_sources += i2c_sources
     libmctp_headers += i2c_headers
 endif
+if get_option('control')
+    libmctp_sources += control_sources
+endif
 
 compiler = meson.get_compiler('c')
 
diff --git a/meson.options b/meson.options
index e3d9f9c..18b594e 100644
--- a/meson.options
+++ b/meson.options
@@ -43,3 +43,9 @@
     value: false,
     description: 'Don\'t include any logging functionality',
 )
+option(
+    'control',
+    type: 'boolean',
+    value: true,
+    description: 'Include MCTP control protocol handler',
+)