i2c: Add binding for MCTP over I2C transport

Implements DSP0237. This has a fixed neighbor table (currently 4
entries), with neighbors learned on reception, or set with
mctp_i2c_set_neighbour().

Change-Id: I9b1e2c3673149cd0b9fee0d8113f3cac0e336bc7
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/i2c-internal.h b/i2c-internal.h
new file mode 100644
index 0000000..95ab81d
--- /dev/null
+++ b/i2c-internal.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#pragma once
+
+#include <assert.h>
+#include "libmctp.h"
+#include "libmctp-i2c.h"
+
+/* Limited by bytecount field */
+static_assert(I2C_BTU <= 254);
+
+#ifndef MCTP_I2C_NEIGH_COUNT
+#define MCTP_I2C_NEIGH_COUNT 4
+#endif
+
+struct mctp_i2c_hdr {
+	uint8_t dest;
+	uint8_t cmd;
+	uint8_t bytecount;
+	uint8_t source;
+};
+
+struct mctp_i2c_neigh {
+	bool used;
+	/* 7-bit address */
+	uint8_t addr;
+	uint8_t eid;
+	/* from platform_now(), for LRU eviction */
+	uint64_t last_seen_timestamp;
+};
+
+struct mctp_binding_i2c {
+	struct mctp_binding binding;
+
+	struct mctp_i2c_neigh neigh[MCTP_I2C_NEIGH_COUNT];
+
+	uint8_t own_addr;
+
+	uint8_t tx_storage[sizeof(struct mctp_i2c_hdr) +
+			   MCTP_PKTBUF_SIZE(I2C_BTU)] __attribute((aligned(8)));
+	uint8_t rx_storage[sizeof(struct mctp_i2c_hdr) +
+			   MCTP_PKTBUF_SIZE(I2C_BTU)] __attribute((aligned(8)));
+
+	mctp_i2c_tx_fn tx_fn;
+	void *tx_ctx;
+};
diff --git a/i2c.c b/i2c.c
new file mode 100644
index 0000000..d648396
--- /dev/null
+++ b/i2c.c
@@ -0,0 +1,278 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "container_of.h"
+#include "libmctp-i2c.h"
+#include "i2c-internal.h"
+
+static const uint8_t MCTP_I2C_COMMAND = 0x0f;
+
+#define binding_to_i2c(b) container_of(b, struct mctp_binding_i2c, binding)
+
+static bool mctp_i2c_valid_addr(uint8_t addr)
+{
+	return addr <= 0x7f;
+}
+
+static bool mctp_i2c_valid_eid(uint8_t eid)
+{
+	/* Disallow reserved range */
+	return eid >= 8 && eid < 0xff;
+}
+
+static int mctp_i2c_core_start(struct mctp_binding *binding)
+{
+	mctp_binding_set_tx_enabled(binding, true);
+	return 0;
+}
+
+/* Returns 0 if an entry is found, or -ENOENT otherwise.
+ * The last seen timestamp will be updated for found entries */
+static int mctp_i2c_neigh_get(struct mctp_binding_i2c *i2c, uint8_t eid,
+			      uint8_t *ret_neigh_addr)
+{
+	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
+		struct mctp_i2c_neigh *n = &i2c->neigh[i];
+		if (n->used && n->eid == eid) {
+			n->last_seen_timestamp = mctp_now(i2c->binding.mctp);
+			*ret_neigh_addr = n->addr;
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+/* Adds a new neighbour entry. If the table is full, the oldest
+ * entry will be evicted. If eid already exists, that entry will
+ * be replaced. */
+static void mctp_i2c_neigh_add(struct mctp_binding_i2c *i2c, uint8_t eid,
+			       uint8_t addr)
+{
+	assert(addr <= 0x7f);
+	struct mctp_i2c_neigh *entry = NULL;
+	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
+		struct mctp_i2c_neigh *n = &i2c->neigh[i];
+		if (!n->used) {
+			/* Spare entry, use it */
+			entry = n;
+			break;
+		}
+
+		if (n->eid == eid) {
+			/* Replacing existing entry */
+			entry = n;
+			break;
+		}
+
+		if (!entry ||
+		    n->last_seen_timestamp < entry->last_seen_timestamp) {
+			/* Use this as the provisional oldest, keep iterating */
+			entry = n;
+		}
+	}
+	assert(entry);
+
+	entry->addr = addr;
+	entry->eid = eid;
+	entry->used = true;
+	entry->last_seen_timestamp = mctp_now(i2c->binding.mctp);
+}
+
+static int mctp_binding_i2c_tx(struct mctp_binding *b, struct mctp_pktbuf *pkt)
+{
+	struct mctp_binding_i2c *i2c = binding_to_i2c(b);
+	struct mctp_hdr *hdr = mctp_pktbuf_hdr(pkt);
+	int rc;
+	uint8_t neigh_addr;
+
+	rc = mctp_i2c_neigh_get(i2c, hdr->dest, &neigh_addr);
+	if (rc) {
+		return rc;
+	}
+
+	struct mctp_i2c_hdr *i2c_hdr =
+		mctp_pktbuf_alloc_start(pkt, sizeof(struct mctp_i2c_hdr));
+	i2c_hdr->dest = neigh_addr << 1;
+	i2c_hdr->cmd = MCTP_I2C_COMMAND;
+	size_t bytecount = mctp_pktbuf_size(pkt) -
+			   (offsetof(struct mctp_i2c_hdr, bytecount) + 1);
+	if (bytecount > 0xff) {
+		return -EINVAL;
+	}
+	i2c_hdr->bytecount = bytecount;
+	i2c_hdr->source = i2c->own_addr << 1 | 1;
+
+	rc = i2c->tx_fn(pkt->data + pkt->start, mctp_pktbuf_size(pkt),
+			i2c->tx_ctx);
+	switch (rc) {
+	case -EMSGSIZE:
+	case 0:
+		break;
+	case -EBUSY:
+	default:
+		mctp_binding_set_tx_enabled(&i2c->binding, false);
+	}
+	return rc;
+}
+
+int mctp_i2c_set_neighbour(struct mctp_binding_i2c *i2c, uint8_t eid,
+			   uint8_t addr)
+{
+	if (!mctp_i2c_valid_eid(eid)) {
+		return -EINVAL;
+	}
+	if (!mctp_i2c_valid_addr(addr)) {
+		return -EINVAL;
+	}
+
+	mctp_i2c_neigh_add(i2c, eid, addr);
+	return 0;
+}
+
+int mctp_i2c_setup(struct mctp_binding_i2c *i2c, uint8_t own_addr,
+		   mctp_i2c_tx_fn tx_fn, void *tx_ctx)
+{
+	int rc;
+
+	memset(i2c, 0x0, sizeof(*i2c));
+
+	rc = mctp_i2c_set_address(i2c, own_addr);
+	if (rc) {
+		return rc;
+	}
+
+	i2c->binding.name = "i2c";
+	i2c->binding.version = 1;
+	i2c->binding.pkt_size = MCTP_PACKET_SIZE(I2C_BTU);
+	i2c->binding.pkt_header = sizeof(struct mctp_i2c_hdr);
+	i2c->binding.tx_storage = i2c->tx_storage;
+
+	i2c->binding.start = mctp_i2c_core_start;
+	i2c->binding.tx = mctp_binding_i2c_tx;
+
+	i2c->tx_fn = tx_fn;
+	i2c->tx_ctx = tx_ctx;
+
+	return 0;
+}
+
+int mctp_i2c_set_address(struct mctp_binding_i2c *i2c, uint8_t own_addr)
+{
+	if (!mctp_i2c_valid_addr(own_addr)) {
+		return -EINVAL;
+	}
+
+	i2c->own_addr = own_addr;
+	return 0;
+}
+
+struct mctp_binding *mctp_binding_i2c_core(struct mctp_binding_i2c *i2c)
+{
+	return &i2c->binding;
+}
+
+static int mctp_i2c_hdr_validate(const struct mctp_i2c_hdr *hdr)
+{
+	if (hdr->cmd != MCTP_I2C_COMMAND) {
+		return -EINVAL;
+	}
+	if ((hdr->dest & 1) != 0) {
+		return -EINVAL;
+	}
+	if ((hdr->source & 1) != 1) {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+void mctp_i2c_rx(struct mctp_binding_i2c *i2c, const void *data, size_t len)
+{
+	int rc;
+
+	if (len < sizeof(struct mctp_i2c_hdr)) {
+		return;
+	}
+	const struct mctp_i2c_hdr *hdr = data;
+	rc = mctp_i2c_hdr_validate(hdr);
+	if (rc) {
+		return;
+	}
+
+	if (hdr->bytecount != len - 3) {
+		return;
+	}
+
+	if ((hdr->dest >> 1) != i2c->own_addr) {
+		return;
+	}
+
+	uint8_t src = hdr->source >> 1;
+	if (src == i2c->own_addr) {
+		return;
+	}
+
+	struct mctp_pktbuf *pkt =
+		mctp_pktbuf_init(&i2c->binding, i2c->rx_storage);
+	rc = mctp_pktbuf_push(
+		pkt, (const uint8_t *)data + sizeof(struct mctp_i2c_hdr),
+		len - sizeof(struct mctp_i2c_hdr));
+	if (rc) {
+		// Packet too large for I2C_BTU
+		return;
+	}
+
+	if (mctp_pktbuf_size(pkt) < sizeof(struct mctp_hdr)) {
+		return;
+	}
+
+	struct mctp_hdr *mctp_hdr = mctp_pktbuf_hdr(pkt);
+	if (mctp_hdr->flags_seq_tag & MCTP_HDR_FLAG_TO) {
+		/* Update neighbour entry */
+		mctp_i2c_neigh_add(i2c, mctp_hdr->src, src);
+	}
+
+	mctp_bus_rx(&i2c->binding, pkt);
+}
+
+int mctp_i2c_parse_hdr(const void *data, size_t len, uint8_t *src_addr,
+		       uint8_t *dest_addr, uint8_t *bytecount)
+{
+	int rc;
+
+	if (len < sizeof(struct mctp_i2c_hdr)) {
+		return -EINVAL;
+	}
+	const struct mctp_i2c_hdr *hdr = data;
+	rc = mctp_i2c_hdr_validate(hdr);
+	if (rc) {
+		return rc;
+	}
+
+	if (src_addr) {
+		*src_addr = hdr->source >> 1;
+	}
+	if (dest_addr) {
+		*dest_addr = hdr->dest >> 1;
+	}
+	if (bytecount) {
+		*bytecount = hdr->bytecount;
+	}
+	return 0;
+}
+
+void mctp_i2c_tx_poll(struct mctp_binding_i2c *i2c)
+{
+	mctp_binding_set_tx_enabled(&i2c->binding, true);
+}
diff --git a/libmctp-i2c.h b/libmctp-i2c.h
new file mode 100644
index 0000000..d8f23ca
--- /dev/null
+++ b/libmctp-i2c.h
@@ -0,0 +1,31 @@
+#include <stdint.h>
+
+#include "libmctp.h"
+
+struct mctp_binding_i2c;
+
+typedef int (*mctp_i2c_tx_fn)(const void *buf, size_t len, void *ctx);
+
+/* Configures the i2c binding. */
+int mctp_i2c_setup(struct mctp_binding_i2c *i2c, uint8_t own_addr,
+		   mctp_i2c_tx_fn tx_fn, void *tx_ctx);
+void mctp_i2c_cleanup(struct mctp_binding_i2c *i2c);
+
+int mctp_i2c_set_address(struct mctp_binding_i2c *i2c, uint8_t own_addr);
+
+struct mctp_binding *mctp_binding_i2c_core(struct mctp_binding_i2c *i2c);
+
+int mctp_i2c_set_neighbour(struct mctp_binding_i2c *i2c, uint8_t eid,
+			   uint8_t addr);
+
+void mctp_i2c_rx(struct mctp_binding_i2c *i2c, const void *data, size_t len);
+int mctp_i2c_parse_hdr(const void *data, size_t len, uint8_t *src_addr,
+		       uint8_t *dest_addr, uint8_t *bytecount);
+void mctp_i2c_tx_poll(struct mctp_binding_i2c *i2c);
+
+/* Can be customised if needed */
+#ifndef I2C_BTU
+#define I2C_BTU MCTP_BTU
+#endif
+
+#define MCTP_I2C_PACKET_SIZE (MCTP_PACKET_SIZE(I2C_BTU) + 4)
diff --git a/libmctp-sizes.h.in b/libmctp-sizes.h.in
index cbca7a9..f921c51 100644
--- a/libmctp-sizes.h.in
+++ b/libmctp-sizes.h.in
@@ -1,3 +1,5 @@
 #pragma once
 
 #define MCTP_SIZEOF_STRUCT_MCTP @sizeof_struct_mctp@
+/* sizeof(struct mctp_binding_i2c) */
+#define MCTP_SIZEOF_BINDING_I2C @sizeof_binding_i2c@
diff --git a/meson.build b/meson.build
index aa11c32..fa6ff81 100644
--- a/meson.build
+++ b/meson.build
@@ -38,6 +38,14 @@
     'libmctp-astlpc.h',
 ]
 
+i2c_sources = [
+    'i2c.c',
+]
+
+i2c_headers = [
+    'libmctp-i2c.h',
+]
+
 libmctp_sources = sources
 libmctp_headers = headers
 
@@ -49,6 +57,10 @@
     libmctp_sources += astlpc_sources
     libmctp_headers += astlpc_headers
 endif
+if get_option('bindings').contains('i2c')
+    libmctp_sources += i2c_sources
+    libmctp_headers += i2c_headers
+endif
 
 compiler = meson.get_compiler('c')
 
@@ -156,11 +168,17 @@
     link_with: libmctp,
 )
 
+# TODO: these should depend on the -internal.h headers so they rebuild
+# on changes, unclear how to do that.
 sizeof_mctp = compiler.sizeof('struct mctp',
     include_directories: libmctp_include_dir,
     prefix: '#include "core-internal.h"')
+sizeof_binding_i2c = compiler.sizeof('struct mctp_binding_i2c',
+    include_directories: libmctp_include_dir,
+    prefix: '#include "i2c-internal.h"')
 sizes_h = configure_file(configuration: {
         'sizeof_struct_mctp': sizeof_mctp,
+        'sizeof_binding_i2c': sizeof_binding_i2c,
     },
     input: 'libmctp-sizes.h.in',
     output: 'libmctp-sizes.h',
diff --git a/meson.options b/meson.options
index fda6e56..e3d9f9c 100644
--- a/meson.options
+++ b/meson.options
@@ -8,8 +8,8 @@
     'bindings',
     type: 'array',
     description: 'Bindings to include',
-    choices: ['serial', 'astlpc'],
-    value: ['serial', 'astlpc'],
+    choices: ['serial', 'astlpc', 'i2c'],
+    value: ['serial', 'astlpc', 'i2c'],
 )
 option(
     'default_alloc',
diff --git a/tests/meson.build b/tests/meson.build
index 4ca52a0..96d7d6b 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -13,6 +13,9 @@
 if get_option('bindings').contains('astlpc')
     tests += 'test_astlpc'
 endif
+if get_option('bindings').contains('i2c')
+    tests += 'test_i2c'
+endif
 
 test_include_dirs = [include_directories('.'), libmctp_include_dir]
 foreach t : tests
diff --git a/tests/test_i2c.c b/tests/test_i2c.c
new file mode 100644
index 0000000..067d551
--- /dev/null
+++ b/tests/test_i2c.c
@@ -0,0 +1,263 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "compiler.h"
+#include "range.h"
+#include "libmctp-log.h"
+#include "libmctp-i2c.h"
+#include "libmctp-sizes.h"
+#include "libmctp-alloc.h"
+
+/* For access to mctp_bninding_i2c internals */
+#include "i2c-internal.h"
+
+#ifdef NDEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+struct mctp_binding_serial_pipe {
+	int ingress;
+	int egress;
+
+	struct mctp_binding_serial *serial;
+};
+
+// Sized to test fragmentation and >8 bit length
+#define TEST_MSG_LEN 300
+static uint8_t mctp_msg_src[TEST_MSG_LEN];
+
+struct i2c_test {
+	struct mctp_binding_i2c *i2c;
+	struct mctp *mctp;
+
+	uint8_t rx_msg[TEST_MSG_LEN];
+	size_t rx_len;
+
+	/* Physical addresses. These get set regardless of whether the packet
+	 * is dropped by the stack (no match etc) */
+	uint8_t last_rx_i2c_src;
+	uint8_t last_tx_i2c_dst;
+};
+
+static const uint8_t I2C_ADDR_A = 0x20;
+static const uint8_t I2C_ADDR_B = 0x21;
+static const uint8_t EID_A = 50;
+static const uint8_t EID_B = 51;
+
+static int test_i2c_tx(const void *buf, size_t len, void *ctx)
+{
+	struct i2c_test *test_pair = ctx;
+	struct i2c_test *tx_test = &test_pair[0];
+	struct i2c_test *rx_test = &test_pair[1];
+
+	mctp_prdebug("test_i2c_tx len %zu", len);
+
+	const struct mctp_i2c_hdr *hdr = buf;
+	tx_test->last_tx_i2c_dst = hdr->dest >> 1;
+	rx_test->last_rx_i2c_src = hdr->source >> 1;
+
+	mctp_i2c_rx(rx_test->i2c, buf, len);
+	return 0;
+}
+
+static void test_i2c_rxmsg(uint8_t src_eid, bool tag_owner, uint8_t msg_tag,
+			   void *ctx, void *msg, size_t len)
+{
+	struct i2c_test *test_pair = ctx;
+	// struct i2c_test *tx_test = &test_pair[0];
+	struct i2c_test *rx_test = &test_pair[1];
+
+	mctp_prdebug("test_i2c_rx src %d len %zu tag %d owner %d", src_eid, len,
+		     msg_tag, tag_owner);
+
+	// Must be cleared by previous test runs
+	assert(rx_test->rx_len == 0);
+	memcpy(rx_test->rx_msg, msg, len);
+	rx_test->rx_len = len;
+}
+
+/* Transmits a MCTP message and checks the received message matches */
+static void run_tx_test(struct i2c_test *tx_test, uint8_t dest_eid,
+			size_t tx_len, struct i2c_test *rx_test)
+{
+	int rc;
+	const uint8_t msg_tag = 2;
+	const bool tag_owner = false;
+
+	assert(tx_len <= sizeof(mctp_msg_src));
+	rc = mctp_message_tx(tx_test->mctp, dest_eid, tag_owner, msg_tag,
+			     mctp_msg_src, tx_len);
+	assert(rc == 0);
+
+	while (!mctp_is_tx_ready(tx_test->mctp, dest_eid)) {
+		mctp_i2c_tx_poll(tx_test->i2c);
+	}
+
+	assert(rx_test->rx_len == tx_len);
+	assert(memcmp(rx_test->rx_msg, mctp_msg_src, tx_len) == 0);
+
+	rx_test->rx_len = 0;
+}
+
+static void test_neigh_expiry(struct i2c_test *tx_test,
+			      struct i2c_test *rx_test)
+{
+	const uint8_t msg_tag = 2;
+	const bool tag_owner = true;
+	const size_t msg_len = 5;
+	int rc;
+
+	(void)rx_test;
+
+	/* Clear the tx neighbour table */
+	memset(tx_test->i2c->neigh, 0x0, sizeof(tx_test->i2c->neigh));
+
+	/* Check that all EIDs fail */
+	rx_test->rx_len = 0;
+	for (size_t eid = 8; eid < 254; eid++) {
+		mctp_message_tx(tx_test->mctp, eid, tag_owner, msg_tag,
+				mctp_msg_src, msg_len);
+		/* Not received */
+		assert(rx_test->rx_len == 0);
+	}
+
+	/* Add one entry */
+	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
+				    rx_test->i2c->own_addr);
+	assert(rc == 0);
+	rx_test->rx_len = 0;
+	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
+			msg_len);
+	assert(rx_test->rx_len == msg_len);
+	assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr);
+
+	/* Replace the entry */
+	rx_test->i2c->own_addr++;
+	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
+				    rx_test->i2c->own_addr);
+	assert(rc == 0);
+	rx_test->rx_len = 0;
+	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
+			msg_len);
+	assert(rc == 0);
+	assert(rx_test->rx_len == msg_len);
+	assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr);
+
+	/* Check only one entry is set */
+	size_t count = 0;
+	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
+		struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i];
+		if (n->used) {
+			assert(n->eid == EID_B);
+			count++;
+		}
+	}
+	assert(count == 1);
+
+	/* Ensure we can iterate without overflow.
+	 * If MCTP_I2C_NEIGH_COUNT increases too large this test would need rethinking
+	 * (and eviction may become impossible) */
+	assert((int)EID_B + MCTP_I2C_NEIGH_COUNT < 254);
+	assert((int)I2C_ADDR_B + MCTP_I2C_NEIGH_COUNT < 0x7f);
+
+	/* Fill entries. -1 because one was already filled. */
+	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT - 1; i++) {
+		/* Unused addresses */
+		uint8_t addr = rx_test->i2c->own_addr + i + 1;
+		uint8_t eid = EID_B + i + 1;
+		rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr);
+		assert(rc == 0);
+	}
+
+	/* Check all are used */
+	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
+		struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i];
+		assert(n->used);
+	}
+
+	/* Test eviction */
+	{
+		uint8_t addr =
+			rx_test->i2c->own_addr + MCTP_I2C_NEIGH_COUNT + 1;
+		uint8_t eid = EID_B + MCTP_I2C_NEIGH_COUNT + 1;
+		rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr);
+		assert(rc == 0);
+
+		/* EID_B got evicted, send should fail */
+		rx_test->rx_len = 0;
+		mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag,
+				mctp_msg_src, msg_len);
+		/* Not received */
+		assert(rx_test->rx_len == 0);
+	}
+
+	/* Add EID_B again */
+	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
+				    rx_test->i2c->own_addr);
+	assert(rc == 0);
+	rx_test->rx_len = 0;
+	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
+			msg_len);
+	/* Is received */
+	assert(rx_test->rx_len == msg_len);
+}
+
+int main(void)
+{
+	struct i2c_test scenario[2];
+	struct i2c_test *tx_test = &scenario[0];
+	struct i2c_test *rx_test = &scenario[1];
+
+	mctp_set_log_stdio(MCTP_LOG_DEBUG);
+
+	memset(scenario, 0x0, sizeof(scenario));
+
+	/* Setup a source buffer */
+	for (size_t i = 0; i < sizeof(mctp_msg_src); i++) {
+		mctp_msg_src[i] = i & 0xff;
+	}
+
+	tx_test->mctp = mctp_init();
+	assert(tx_test->mctp);
+	tx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C);
+	assert(tx_test->i2c);
+	rx_test->mctp = mctp_init();
+	assert(rx_test->mctp);
+	rx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C);
+	assert(rx_test->i2c);
+
+	/* TX side */
+	mctp_i2c_setup(tx_test->i2c, I2C_ADDR_A, test_i2c_tx, scenario);
+	mctp_register_bus(tx_test->mctp, mctp_binding_i2c_core(tx_test->i2c),
+			  EID_A);
+	mctp_set_rx_all(tx_test->mctp, NULL, NULL);
+	mctp_i2c_set_neighbour(tx_test->i2c, EID_B, I2C_ADDR_B);
+
+	/* RX side */
+	mctp_i2c_setup(rx_test->i2c, I2C_ADDR_B, NULL, NULL);
+	mctp_register_bus(rx_test->mctp, mctp_binding_i2c_core(rx_test->i2c),
+			  EID_B);
+	mctp_set_rx_all(rx_test->mctp, test_i2c_rxmsg, scenario);
+	// mctp_i2c_set_neighbour(rx_test->i2c, EID_A, I2C_ADDR_A);
+
+	/* Try all message sizes */
+	for (size_t i = 1; i < sizeof(mctp_msg_src); i++) {
+		run_tx_test(tx_test, EID_B, i, rx_test);
+	}
+
+	test_neigh_expiry(tx_test, rx_test);
+
+	return 0;
+}