core: Support transport control commands

This change introduces a control message request handler for MCTP
bindings. If a handler is provided, transport control messages will be
forwarded to the handler, otherwise they will be forwarded to the
default handler.

Change-Id: I62266d6bf2d512ec97759c0b8a3477c5e433d609
Signed-off-by: Wiktor Gołgowski <wiktor.golgowski@linux.intel.com>
[AJ: Split out general control message handler, formatting]
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a0cace7..3b0097d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,10 @@
 target_link_libraries (test_serial mctp)
 add_test (NAME serial COMMAND test_serial)
 
+add_executable (test_cmds tests/test_cmds.c tests/test-utils.c)
+target_link_libraries (test_cmds mctp)
+add_test (NAME control_commands COMMAND test_cmds)
+
 install (TARGETS mctp DESTINATION lib)
 install (FILES libmctp.h DESTINATION include)
 
diff --git a/Makefile.am b/Makefile.am
index c93d452..75535d4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,8 @@
 
 lib_LTLIBRARIES = libmctp.la
 libmctp_la_SOURCES = core.c alloc.c log.c \
-		     libmctp-alloc.h libmctp-log.h
+		     libmctp-alloc.h libmctp-log.h \
+		     libmctp-cmds.h
 include_HEADERS = libmctp.h
 
 if LIBMCTP_BINDING_serial
@@ -40,7 +41,7 @@
 TESTS = $(check_PROGRAMS)
 
 check_PROGRAMS = tests/test_eid tests/test_seq tests/test_bridge \
-		 tests/test_astlpc tests/test_serial
+		 tests/test_astlpc tests/test_serial tests/test_cmds
 # We set a global LDADD here, as there's no way to specify it for all
 # tests. This means other targets' LDADDs need to be overridden.
 LDADD = tests/libtest-utils.a libmctp.la
diff --git a/core.c b/core.c
index 34bd75a..d3f8184 100644
--- a/core.c
+++ b/core.c
@@ -14,6 +14,7 @@
 #include "libmctp.h"
 #include "libmctp-alloc.h"
 #include "libmctp-log.h"
+#include "libmctp-cmds.h"
 
 /* Internal data structures */
 
@@ -313,12 +314,85 @@
 	return 0;
 }
 
-static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus,
-		mctp_eid_t src, mctp_eid_t dest, void *buf, size_t len)
+static inline bool mctp_ctrl_cmd_is_transport(struct mctp_ctrl_msg_hdr *hdr)
 {
+	return ((hdr->command_code >= MCTP_CTRL_CMD_FIRST_TRANSPORT) &&
+		(hdr->command_code <= MCTP_CTRL_CMD_LAST_TRANSPORT));
+}
+
+static bool mctp_ctrl_handle_msg(struct mctp *mctp, struct mctp_bus *bus,
+				 mctp_eid_t src, mctp_eid_t dest, void *buffer,
+				 size_t length)
+{
+	struct mctp_ctrl_msg_hdr *msg_hdr = buffer;
+
+	/*
+	 * Control message is received. If a transport control message handler
+	 * is provided, it will called. If there is no dedicated handler, this
+	 * function returns false and data can be handled by the generic
+	 * message handler. The transport control message handler will be
+	 * provided with messages in the command range 0xF0 - 0xFF.
+	 */
+	if (mctp_ctrl_cmd_is_transport(msg_hdr)) {
+		if (bus->binding->control_rx != NULL) {
+			/* MCTP bus binding handler */
+			bus->binding->control_rx(src,
+						 bus->binding->control_rx_data,
+						 buffer, length);
+			return true;
+		}
+	}
+
+	/*
+	 * Command was not handled, due to lack of specific callback.
+	 * It will be passed to regular message_rx handler.
+	 */
+	return false;
+}
+
+static inline bool mctp_rx_dest_is_local(struct mctp_bus *bus, mctp_eid_t dest)
+{
+	return dest == bus->eid || dest == MCTP_EID_NULL ||
+	       dest == MCTP_EID_BROADCAST;
+}
+
+static inline bool mctp_ctrl_cmd_is_request(struct mctp_ctrl_msg_hdr *hdr)
+{
+	return hdr->ic_msg_type == MCTP_CTRL_HDR_MSG_TYPE &&
+	       hdr->rq_dgram_inst & MCTP_CTRL_HDR_FLAG_REQUEST;
+}
+
+/*
+ * Receive the complete MCTP message and route it.
+ * Asserts:
+ *     'buf' is not NULL.
+ */
+static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus, mctp_eid_t src,
+		    mctp_eid_t dest, void *buf, size_t len)
+{
+	assert(buf != NULL);
+
 	if (mctp->route_policy == ROUTE_ENDPOINT &&
-			dest == bus->eid && mctp->message_rx)
-		mctp->message_rx(src, mctp->message_rx_data, buf, len);
+	    mctp_rx_dest_is_local(bus, dest)) {
+		/* Handle MCTP Control Messages: */
+		if (len >= sizeof(struct mctp_ctrl_msg_hdr)) {
+			struct mctp_ctrl_msg_hdr *msg_hdr = buf;
+
+			/*
+			 * Identify if this is a control request message.
+			 * See DSP0236 v1.3.0 sec. 11.5.
+			 */
+			if (mctp_ctrl_cmd_is_request(msg_hdr)) {
+				bool handled;
+				handled = mctp_ctrl_handle_msg(mctp, bus, src,
+							       dest, buf, len);
+				if (handled)
+					return;
+			}
+		}
+		if (mctp->message_rx)
+			mctp->message_rx(src, mctp->message_rx_data, buf, len);
+	}
 
 	if (mctp->route_policy == ROUTE_BRIDGE) {
 		int i;
diff --git a/libmctp-cmds.h b/libmctp-cmds.h
new file mode 100644
index 0000000..b35fc08
--- /dev/null
+++ b/libmctp-cmds.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#ifndef _LIBMCTP_CMDS_H
+#define _LIBMCTP_CMDS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "libmctp.h"
+
+/*
+ * Helper structs and functions for MCTP control messages.
+ * See DSP0236 v1.3.0 sec. 11 for reference.
+ */
+
+struct mctp_ctrl_msg_hdr {
+	uint8_t ic_msg_type;
+	uint8_t rq_dgram_inst;
+	uint8_t command_code;
+	uint8_t completion_code;
+};
+
+#define MCTP_CTRL_HDR_MSG_TYPE 0
+#define MCTP_CTRL_HDR_FLAG_REQUEST (1 << 7)
+#define MCTP_CTRL_HDR_FLAG_DGRAM (1 << 6)
+#define MCTP_CTRL_HDR_INSTANCE_ID_MASK 0x1F
+
+/*
+ * MCTP Control Command IDs
+ * See DSP0236 v1.3.0 Table 12.
+ */
+#define MCTP_CTRL_CMD_RESERVED 0x00
+#define MCTP_CTRL_CMD_SET_ENDPOINT_ID 0x01
+#define MCTP_CTRL_CMD_GET_ENDPOINT_ID 0x02
+#define MCTP_CTRL_CMD_GET_ENDPOINT_UUID 0x03
+#define MCTP_CTRL_CMD_GET_VERSION_SUPPORT 0x04
+#define MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT 0x05
+#define MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT 0x06
+#define MCTP_CTRL_CMD_RESOLVE_ENDPOINT_ID 0x07
+#define MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS 0x08
+#define MCTP_CTRL_CMD_ROUTING_INFO_UPDATE 0x09
+#define MCTP_CTRL_CMD_GET_ROUTING_TABLE_ENTRIES 0x0A
+#define MCTP_CTRL_CMD_PREPARE_ENDPOINT_DISCOVERY 0x0B
+#define MCTP_CTRL_CMD_ENDPOINT_DISCOVERY 0x0C
+#define MCTP_CTRL_CMD_DISCOVERY_NOTIFY 0x0D
+#define MCTP_CTRL_CMD_GET_NETWORK_ID 0x0E
+#define MCTP_CTRL_CMD_QUERY_HOP 0x0F
+#define MCTP_CTRL_CMD_RESOLVE_UUID 0x10
+#define MCTP_CTRL_CMD_QUERY_RATE_LIMIT 0x11
+#define MCTP_CTRL_CMD_REQUEST_TX_RATE_LIMIT 0x12
+#define MCTP_CTRL_CMD_UPDATE_RATE_LIMIT 0x13
+#define MCTP_CTRL_CMD_QUERY_SUPPORTED_INTERFACES 0x14
+#define MCTP_CTRL_CMD_MAX 0x15
+/* 0xF0 - 0xFF are transport specific */
+#define MCTP_CTRL_CMD_FIRST_TRANSPORT 0xF0
+#define MCTP_CTRL_CMD_LAST_TRANSPORT 0xFF
+
+/*
+ * MCTP Control Completion Codes
+ * See DSP0236 v1.3.0 Table 13.
+ */
+#define MCTP_CTRL_CC_SUCCESS 0x00
+#define MCTP_CTRL_CC_ERROR 0x01
+#define MCTP_CTRL_CC_ERROR_INVALID_DATA 0x02
+#define MCTP_CTRL_CC_ERROR_INVALID_LENGTH 0x03
+#define MCTP_CTRL_CC_ERROR_NOT_READY 0x04
+#define MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD 0x05
+/* 0x80 - 0xFF are command specific */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMCTP_CMDS_H */
diff --git a/libmctp.h b/libmctp.h
index 0a40c0e..621e952 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -14,6 +14,10 @@
 
 typedef uint8_t mctp_eid_t;
 
+/* Special Endpoint ID values */
+#define MCTP_EID_NULL 0
+#define MCTP_EID_BROADCAST 0xff
+
 /* MCTP packet definitions */
 struct mctp_hdr {
 	uint8_t	ver;
@@ -92,15 +96,16 @@
 
 /* hardware bindings */
 struct mctp_binding {
-	const char	*name;
-	uint8_t		version;
-	struct mctp_bus	*bus;
-	struct mctp	*mctp;
-	int		pkt_size;
-	int		pkt_pad;
-	int		(*start)(struct mctp_binding *binding);
-	int		(*tx)(struct mctp_binding *binding,
-				struct mctp_pktbuf *pkt);
+	const char *name;
+	uint8_t version;
+	struct mctp_bus *bus;
+	struct mctp *mctp;
+	int pkt_size;
+	int pkt_pad;
+	int (*start)(struct mctp_binding *binding);
+	int (*tx)(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
+	mctp_rx_fn control_rx;
+	void *control_rx_data;
 };
 
 void mctp_binding_set_tx_enabled(struct mctp_binding *binding, bool enable);
diff --git a/tests/test_cmds.c b/tests/test_cmds.c
new file mode 100644
index 0000000..7a05c35
--- /dev/null
+++ b/tests/test_cmds.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include "test-utils.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <libmctp.h>
+#include <libmctp-alloc.h>
+#include <libmctp-cmds.h>
+
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+static const mctp_eid_t eid_1 = 9;
+static const mctp_eid_t eid_2 = 10;
+
+struct msg_payload {
+	struct mctp_hdr hdr;
+	struct mctp_ctrl_msg_hdr ctrl_hdr;
+};
+
+struct callback_data {
+	uint8_t invoked;
+	union {
+		uint8_t command_code;
+		uint8_t completion_code;
+	};
+};
+
+static void control_message_transport_callback(mctp_eid_t src, void *data,
+					       void *buf, size_t len)
+{
+	struct callback_data *ctx = data;
+	struct mctp_ctrl_msg_hdr *msg_hdr = buf;
+	printf("Transport control message received - command code: 0x%X\n",
+	       msg_hdr->command_code);
+	ctx->invoked++;
+	assert(msg_hdr->command_code == ctx->command_code);
+}
+
+static void rcv_ctrl_msg(struct mctp_binding *b, const void *buf, size_t len)
+{
+	struct mctp_pktbuf *pkt = mctp_pktbuf_alloc(b, len);
+	memcpy(mctp_pktbuf_hdr(pkt), buf, len);
+	mctp_bus_rx(b, pkt);
+}
+
+static void setup_test_binding(struct mctp_binding *test_binding,
+			       struct mctp *test_endpoint, void *callback_ctx)
+{
+	assert(test_binding != NULL);
+	assert(test_endpoint != NULL);
+	assert(callback_ctx != NULL);
+
+	memset(test_binding, 0, sizeof(*test_binding));
+	test_binding->name = "test";
+	test_binding->version = 1;
+	test_binding->tx = NULL;
+	test_binding->pkt_size = MCTP_PACKET_SIZE(MCTP_BTU);
+	test_binding->pkt_pad = 0;
+	test_binding->control_rx = control_message_transport_callback;
+	test_binding->control_rx_data = callback_ctx;
+
+	mctp_register_bus(test_endpoint, test_binding, eid_1);
+	mctp_binding_set_tx_enabled(test_binding, true);
+}
+
+static void send_transport_control_message(void)
+{
+	struct mctp *endpoint = mctp_init();
+	struct mctp_binding binding;
+	struct callback_data ctx;
+	static const struct msg_payload send_control_message_payload = {
+		.hdr = {
+			.dest = eid_1,
+			.src = eid_2,
+			.flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM,
+		},
+		.ctrl_hdr = {
+			.ic_msg_type = MCTP_CTRL_HDR_MSG_TYPE,
+			.rq_dgram_inst = MCTP_CTRL_HDR_FLAG_REQUEST,
+			.command_code = 0xF2,
+		},
+	};
+
+	memset(&ctx, 0, sizeof(ctx));
+	setup_test_binding(&binding, endpoint, &ctx);
+	ctx.command_code = send_control_message_payload.ctrl_hdr.command_code;
+	printf("Sending transport control message: 0x%X\n",
+	       send_control_message_payload.ctrl_hdr.command_code);
+	rcv_ctrl_msg(&binding, (void *)&send_control_message_payload,
+		     sizeof(send_control_message_payload));
+	assert(ctx.invoked == 1);
+
+	mctp_destroy(endpoint);
+}
+
+int main(int argc, char *argv[])
+{
+	send_transport_control_message();
+
+	return EXIT_SUCCESS;
+}