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/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;
+}