astlpc: Introduce MTU negotiation

MTU negotiation is implemented in a backwards-compatible manner with
version 1 of the astlpc binding. Functionally, MTU negotation involves
proposing arrangements of the Rx and Tx buffer layouts. It is assumed
that the MTU is a packet sized to fill the Tx buffer as described in the
control space.

For v1 of the binding the MTU is defined in terms of the MCTP_BTU
constant provided by libmctp.h. MCTP_BTU is used regardless of the
buffer sizes specified in the control space (which MUST describe buffers
supporting at least MCTP_BTU-sized packets).

For v2 of the binding the MTU is defined in terms of the appropriate
buffer's size field in the control space.

The sequence of events for negotiating the MTU under v2 is as follows:

1. The BMC initialises its binding, filling out the Rx and Tx buffer
properties with the largest configuration it supports.

2. The host initialises its binding, writing its maximum Rx buffer size
before sending `channel-init` to the BMC.

3. The BMC receives `channel-init`, negotiates protocol version 2 and
then validates the host's proposed buffer configuration. If the proposed
configuration is invalid (e.g. out-of-bounds values) the BMC terminates
channel initialisation leaving the channel-active bit clear and writing
the zero to the negotiated version field. If the proposal is valid, the
BMC calculates the buffer sizes according to the available constraints
and writes the chosen buffer configuration to the control region.

4. Assuming the version negotiation and buffer configuration are
successful, the BMC sets `channel-active` and notifies the host

5. The host reads `channel-active`, accepts the negotiation of v2 and
validates the buffer configuration. If the validation passes, then the
buffer configuration is the configuration used for the remainder of the
session. If validation fails then the host MUST NOT send MCTP packets
via the LPC binding until a valid buffer configuration can be
negotiated.

Change-Id: I89107593f220418d746c2d73771348ed8f7f3e87
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/tests/test_astlpc.c b/tests/test_astlpc.c
index 332e949..6de249a 100644
--- a/tests/test_astlpc.c
+++ b/tests/test_astlpc.c
@@ -4,6 +4,7 @@
 #include "config.h"
 #endif
 
+#define ASTLPC_VER_CUR 2
 #include "astlpc.c"
 
 #ifdef pr_fmt
@@ -181,6 +182,7 @@
 	/* Initialise the binding */
 	ep->astlpc = mctp_astlpc_init(mode, MCTP_BTU, lpc_mem,
 				      &astlpc_direct_mmio_ops, &ep->mmio);
+	assert(ep->astlpc);
 
 	return mctp_register_bus(ep->mctp, &ep->astlpc->binding, eid);
 }
@@ -712,6 +714,229 @@
 	free(lpc_mem);
 }
 
+#define BUFFER_MIN ASTLPC_PACKET_SIZE(MCTP_PACKET_SIZE(MCTP_BTU))
+
+static void astlpc_test_buffers_rx_offset_overflow(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { UINT32_MAX, BUFFER_MIN },
+		.tx = { control_size, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_tx_offset_overflow(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, BUFFER_MIN },
+		.tx = { UINT32_MAX, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_rx_size_overflow(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size + BUFFER_MIN, UINT32_MAX },
+		.tx = { control_size, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_tx_size_overflow(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, BUFFER_MIN },
+		.tx = { control_size + BUFFER_MIN, UINT32_MAX },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_rx_window_violation(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { LPC_WIN_SIZE - BUFFER_MIN + 1, BUFFER_MIN },
+		.tx = { control_size, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_tx_window_violation(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, BUFFER_MIN },
+		.tx = { LPC_WIN_SIZE - BUFFER_MIN + 1, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_rx_size_fails_btu(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, BUFFER_MIN - 1 },
+		.tx = { control_size + BUFFER_MIN, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_tx_size_fails_btu(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, BUFFER_MIN },
+		.tx = { control_size + BUFFER_MIN, BUFFER_MIN - 1 },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_overlap_rx_low(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, 2 * BUFFER_MIN },
+		.tx = { control_size + BUFFER_MIN, 2 * BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_overlap_tx_low(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size + BUFFER_MIN, 2 * BUFFER_MIN },
+		.tx = { control_size, 2 * BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_overlap_exact(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { control_size, 2 * BUFFER_MIN },
+		.tx = { control_size, 2 * BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_overlap_control(void)
+{
+	struct mctp_astlpc_layout l = {
+		.rx = { 0, BUFFER_MIN },
+		.tx = { control_size + BUFFER_MIN, BUFFER_MIN },
+	};
+
+	assert(!mctp_astlpc_layout_validate(&l));
+}
+
+static void astlpc_test_buffers_bad_host_proposal(void)
+{
+	struct astlpc_endpoint bmc, host;
+	struct mctp_lpcmap_hdr *hdr;
+	uint8_t kcs[2] = { 0 };
+	void *lpc_mem;
+
+	/* Test harness initialisation */
+	lpc_mem = calloc(1, 1 * 1024 * 1024);
+	assert(lpc_mem);
+
+	/* BMC initialisation */
+	endpoint_init(&bmc, 8, MCTP_BINDING_ASTLPC_MODE_BMC, &kcs, lpc_mem);
+
+	/* Host initialisation */
+	endpoint_init(&host, 9, MCTP_BINDING_ASTLPC_MODE_HOST, &kcs, lpc_mem);
+
+	/*
+	 * Now that the host has initialised the control area, break
+	 * something before polling the BMC
+	 */
+	hdr = lpc_mem;
+	hdr->layout.rx_size = 0;
+
+	mctp_astlpc_poll(bmc.astlpc);
+
+	/* Make sure the BMC has not set the channel to active */
+	assert(!(kcs[MCTP_ASTLPC_KCS_REG_STATUS] & KCS_STATUS_CHANNEL_ACTIVE));
+
+	endpoint_destroy(&host);
+	endpoint_destroy(&bmc);
+	free(lpc_mem);
+}
+
+static void astlpc_test_buffers_bad_bmc_proposal(void)
+{
+	struct astlpc_endpoint bmc, host;
+	struct mctp_lpcmap_hdr *hdr;
+	uint8_t kcs[2] = { 0 };
+	void *lpc_mem;
+	int rc;
+
+	/* Test harness initialisation */
+	lpc_mem = calloc(1, 1 * 1024 * 1024);
+	assert(lpc_mem);
+
+	/* BMC initialisation */
+	endpoint_init(&bmc, 8, MCTP_BINDING_ASTLPC_MODE_BMC, &kcs, lpc_mem);
+
+	/*
+	 * Now that the BMC has initialised the control area, break something
+	 * before initialising the host
+	 */
+	hdr = lpc_mem;
+	hdr->layout.rx_size = 0;
+
+	/* Host initialisation: Fails due to bad layout */
+	rc = endpoint_init(&host, 9, MCTP_BINDING_ASTLPC_MODE_HOST, &kcs,
+			   lpc_mem);
+	assert(rc < 0);
+
+	endpoint_destroy(&host);
+	endpoint_destroy(&bmc);
+	free(lpc_mem);
+}
+
+static void astlpc_test_buffers_bad_bmc_negotiation(void)
+{
+	struct astlpc_endpoint bmc, host;
+	struct mctp_lpcmap_hdr *hdr;
+	uint8_t kcs[2] = { 0 };
+	void *lpc_mem;
+	int rc;
+
+	/* Test harness initialisation */
+	lpc_mem = calloc(1, 1 * 1024 * 1024);
+	assert(lpc_mem);
+
+	/* BMC initialisation */
+	endpoint_init(&bmc, 8, MCTP_BINDING_ASTLPC_MODE_BMC, &kcs, lpc_mem);
+
+	/* Host initialisation */
+	endpoint_init(&host, 9, MCTP_BINDING_ASTLPC_MODE_HOST, &kcs, lpc_mem);
+
+	mctp_astlpc_poll(bmc.astlpc);
+
+	/*
+	 * Now that the BMC has initialised the control area, break something
+	 * before polling the host
+	 */
+	hdr = lpc_mem;
+	hdr->layout.rx_size = 0;
+
+	rc = mctp_astlpc_poll(host.astlpc);
+	assert(rc < 0);
+
+	endpoint_destroy(&host);
+	endpoint_destroy(&bmc);
+	free(lpc_mem);
+}
+
 /* clang-format off */
 #define TEST_CASE(test) { #test, test }
 static const struct {
@@ -733,6 +958,21 @@
 	TEST_CASE(astlpc_test_host_tx_bmc_gone),
 	TEST_CASE(astlpc_test_poll_not_ready),
 	TEST_CASE(astlpc_test_undefined_command),
+	TEST_CASE(astlpc_test_buffers_rx_offset_overflow),
+	TEST_CASE(astlpc_test_buffers_tx_offset_overflow),
+	TEST_CASE(astlpc_test_buffers_rx_size_overflow),
+	TEST_CASE(astlpc_test_buffers_tx_size_overflow),
+	TEST_CASE(astlpc_test_buffers_rx_window_violation),
+	TEST_CASE(astlpc_test_buffers_tx_window_violation),
+	TEST_CASE(astlpc_test_buffers_rx_size_fails_btu),
+	TEST_CASE(astlpc_test_buffers_tx_size_fails_btu),
+	TEST_CASE(astlpc_test_buffers_overlap_rx_low),
+	TEST_CASE(astlpc_test_buffers_overlap_tx_low),
+	TEST_CASE(astlpc_test_buffers_bad_host_proposal),
+	TEST_CASE(astlpc_test_buffers_bad_bmc_proposal),
+	TEST_CASE(astlpc_test_buffers_bad_bmc_negotiation),
+	TEST_CASE(astlpc_test_buffers_overlap_exact),
+	TEST_CASE(astlpc_test_buffers_overlap_control),
 };
 /* clang-format on */