astlpc: Implement async support via buffer state tracking

To remove the unbounded loop in mctp_astlpc_kcs_send() we need the
capability to retry sending the pending command once the data value has
been consumed. However, the link is duplex and we may enter the state
where we have multiple pending commands.

Rather than explicitly track the set of pending commands, track the
buffer state in order to derive both the pollfd state needed by the
caller _and_ the pending buffer synchronisation commands.

Unfortunately due to the structure of the code the integration of the
state tracking is a little messy, but is capable of correctly booting a
P10 system.

I've put up a less constrained and perhaps aspirational implementation
of how the state tracking could be implemented if we feel the need to
rework things going forward:

https://github.com/amboar/libmctp-shmb/

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I693946dae5336aa5d0f07bcd66c36f1ccd391054
diff --git a/tests/test_astlpc.c b/tests/test_astlpc.c
index f781fa5..a485016 100644
--- a/tests/test_astlpc.c
+++ b/tests/test_astlpc.c
@@ -1295,6 +1295,68 @@
 	network_destroy(&ctx);
 }
 
+static void astlpc_test_async_exchange(void)
+{
+	struct astlpc_test ctx = { 0 };
+	uint8_t msg[MCTP_BTU];
+	struct pollfd pollfd;
+	uint8_t tag = 0;
+
+	network_init(&ctx);
+
+	memset(&msg[0], 0x5a, MCTP_BTU);
+
+	/* (1)
+	 * Fill the KCS transmit buffer by sending a message from the BMC to the host without
+	 * dequeuing it on the host side
+	 */
+	mctp_message_tx(ctx.bmc.mctp, 9, MCTP_MESSAGE_TO_SRC, tag, msg,
+			sizeof(msg));
+
+	/* (2)
+	 * Assert that we're still listening for in-bound messages on the BMC
+	 */
+	mctp_astlpc_init_pollfd(ctx.bmc.astlpc, &pollfd);
+	assert(pollfd.events & POLLIN);
+	assert(!(pollfd.events & POLLOUT));
+
+	/* (3)
+	 * Send a message from the host to the BMC and dequeue the message on the BMC, triggering a
+	 * buffer ownership transfer command back to the host
+	 */
+	mctp_message_tx(ctx.host.mctp, 8, MCTP_MESSAGE_TO_SRC, tag, msg,
+			sizeof(msg));
+	mctp_astlpc_poll(ctx.bmc.astlpc);
+
+	/* (4)
+	 * Assert that the BMC has to wait for the host to dequeue the ownership transfer command
+	 * from (1) before further transfers take place.
+	 */
+	mctp_astlpc_init_pollfd(ctx.bmc.astlpc, &pollfd);
+	assert(!(pollfd.events & POLLIN));
+	assert(pollfd.events & POLLOUT);
+
+	/* (5)
+	 * Dequeue the message from (1) on the host side, allowing transmisson of the outstanding
+	 * ownership transfer command from (3)
+	 */
+	mctp_astlpc_poll(ctx.host.astlpc);
+
+	/* (6)
+	 * Emulate a POLLOUT event on the BMC side
+	 */
+	mctp_astlpc_poll(ctx.bmc.astlpc);
+
+	/* (7)
+	 * Assert that we're again listening for in-bound messages on the BMC.
+	 */
+	mctp_astlpc_init_pollfd(ctx.bmc.astlpc, &pollfd);
+	assert(pollfd.events & POLLIN);
+	assert(!(pollfd.events & POLLOUT));
+
+	network_destroy(&ctx);
+}
+
 /* clang-format off */
 #define TEST_CASE(test) { #test, test }
 static const struct {
@@ -1338,6 +1400,7 @@
 	TEST_CASE(astlpc_test_tx_before_channel_init),
 	TEST_CASE(astlpc_test_corrupt_host_tx),
 	TEST_CASE(astlpc_test_corrupt_bmc_tx),
+	TEST_CASE(astlpc_test_async_exchange),
 };
 /* clang-format on */