core,API: Add bridge support
This change introduces a facility to bridge messages between two
bindings.
This is implemented through a new mctp_bridge_busses() API, which
applies a new routing policy, sending packets from one binding to the
other. This is in contrast to the current policy of dropping all
non-local packets.
To do this, the message context code needs to know both source and
destination EIDs, so add them to the mctp_msg_ctx_lookup() criteria.
Also, add a small test for bridge mode.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Change-Id: If532613525ddbf81df249e26d0f23825996f7bda
diff --git a/Makefile.am b/Makefile.am
index c224064..c712cfe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,7 @@
TESTS = $(check_PROGRAMS)
-check_PROGRAMS = tests/test_eid tests/test_seq
+check_PROGRAMS = tests/test_eid tests/test_seq tests/test_bridge
# 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/README.md b/README.md
index ab89bbf..50519e3 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,22 @@
for the serial binding, the `mctp_serial_read()` function should be invoked
when the file-descriptor for the serial device has data available.
+### Bridging
+
+libmctp implements basic support for bridging between two hardware bindings.
+In this mode, bindings may have different MTUs, so packets are reassembled into
+their messages, then the messages are re-packetised for the outgoing binding.
+
+For bridging between two endpoints, use the `mctp_bridge_busses()` function:
+
+ * `mctp = mctp_init()`: Initialise the MCTP core
+ * `b1 = mctp_<binding>_init(); b2 = mctp_<binding>_init()`: Initialise two hardware bindings
+ * `mctp_bridge_busses(mctp, b1, b2)`: Setup bridge
+
+Note that no EIDs are defined here; the bridge does not deliver any messages
+to a local rx callback, and messages are bridged as-is.
+
+
Integration
-----------
diff --git a/core.c b/core.c
index ff4bd63..e0cb95d 100644
--- a/core.c
+++ b/core.c
@@ -30,6 +30,7 @@
struct mctp_msg_ctx {
uint8_t src;
+ uint8_t dest;
uint8_t tag;
uint8_t last_seq;
void *buf;
@@ -38,8 +39,8 @@
};
struct mctp {
- /* todo: multiple busses */
- struct mctp_bus busses[1];
+ int n_busses;
+ struct mctp_bus *busses;
/* Message RX callback */
mctp_rx_fn message_rx;
@@ -49,6 +50,11 @@
* @todo: flexible context count
*/
struct mctp_msg_ctx msg_ctxs[16];
+
+ enum {
+ ROUTE_ENDPOINT,
+ ROUTE_BRIDGE,
+ } route_policy;
};
#ifndef BUILD_ASSERT
@@ -60,6 +66,9 @@
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif
+static int mctp_message_tx_on_bus(struct mctp *mctp, struct mctp_bus *bus,
+ mctp_eid_t src, mctp_eid_t dest, void *msg, size_t msg_len);
+
struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len)
{
struct mctp_pktbuf *buf;
@@ -133,7 +142,7 @@
/* Message reassembly */
static struct mctp_msg_ctx *mctp_msg_ctx_lookup(struct mctp *mctp,
- uint8_t src, uint8_t tag)
+ uint8_t src, uint8_t dest, uint8_t tag)
{
unsigned int i;
@@ -141,7 +150,7 @@
* message contexts */
for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) {
struct mctp_msg_ctx *ctx = &mctp->msg_ctxs[i];
- if (ctx->src == src && ctx->tag == tag)
+ if (ctx->src == src && ctx->dest == dest && ctx->tag == tag)
return ctx;
}
@@ -149,7 +158,7 @@
}
static struct mctp_msg_ctx *mctp_msg_ctx_create(struct mctp *mctp,
- uint8_t src, uint8_t tag)
+ uint8_t src, uint8_t dest, uint8_t tag)
{
struct mctp_msg_ctx *ctx = NULL;
unsigned int i;
@@ -166,6 +175,7 @@
return NULL;
ctx->src = src;
+ ctx->dest = dest;
ctx->tag = tag;
ctx->buf_size = 0;
@@ -229,6 +239,8 @@
static struct mctp_bus *find_bus_for_eid(struct mctp *mctp,
mctp_eid_t dest __attribute__((unused)))
{
+ /* for now, just use the first bus. For full routing support,
+ * we will need a table of neighbours */
return &mctp->busses[0];
}
@@ -237,14 +249,56 @@
mctp_eid_t eid)
{
/* todo: multiple busses */
- assert(!mctp->busses[0].binding);
+ assert(mctp->n_busses == 0);
+ mctp->n_busses = 1;
+ mctp->busses = __mctp_alloc(sizeof(struct mctp_bus));
mctp->busses[0].binding = binding;
mctp->busses[0].eid = eid;
binding->bus = &mctp->busses[0];
binding->mctp = mctp;
+ mctp->route_policy = ROUTE_ENDPOINT;
return 0;
}
+int mctp_bridge_busses(struct mctp *mctp,
+ struct mctp_binding *b1, struct mctp_binding *b2)
+{
+ assert(mctp->n_busses == 0);
+ mctp->busses = __mctp_alloc(2 * sizeof(struct mctp_bus));
+ mctp->n_busses = 2;
+ mctp->busses[0].binding = b1;
+ b1->bus = &mctp->busses[0];
+ b1->mctp = mctp;
+ mctp->busses[1].binding = b2;
+ b2->bus = &mctp->busses[1];
+ b2->mctp = mctp;
+
+ mctp->route_policy = ROUTE_BRIDGE;
+ 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)
+{
+ if (mctp->route_policy == ROUTE_ENDPOINT &&
+ dest == bus->eid && mctp->message_rx)
+ mctp->message_rx(src, mctp->message_rx_data, buf, len);
+
+ if (mctp->route_policy == ROUTE_BRIDGE) {
+ int i;
+
+ for (i = 0; i < mctp->n_busses; i++) {
+ struct mctp_bus *dest_bus = &mctp->busses[i];
+ if (dest_bus == bus)
+ continue;
+
+ mctp_message_tx_on_bus(mctp, dest_bus,
+ src, dest, buf, len);
+ }
+
+ }
+}
+
void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt)
{
struct mctp_bus *bus = binding->bus;
@@ -260,8 +314,9 @@
hdr = mctp_pktbuf_hdr(pkt);
- if (hdr->dest != bus->eid)
- /* @todo: non-local packet routing */
+ /* small optimisation: don't bother reassembly if we're going to
+ * drop the packet in mctp_rx anyway */
+ if (mctp->route_policy == ROUTE_ENDPOINT && hdr->dest != bus->eid)
goto out;
flags = hdr->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM);
@@ -274,18 +329,19 @@
* no need to create a message context */
len = pkt->end - pkt->mctp_hdr_off - sizeof(struct mctp_hdr);
p = pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr),
- mctp->message_rx(hdr->src, mctp->message_rx_data, p, len);
+ mctp_rx(mctp, bus, hdr->src, hdr->dest, p, len);
break;
case MCTP_HDR_FLAG_SOM:
/* start of a new message - start the new context for
* future message reception. If an existing context is
* already present, drop it. */
- ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+ ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
if (ctx) {
mctp_msg_ctx_reset(ctx);
} else {
- ctx = mctp_msg_ctx_create(mctp, hdr->src, tag);
+ ctx = mctp_msg_ctx_create(mctp,
+ hdr->src, hdr->dest, tag);
}
rc = mctp_msg_ctx_add_pkt(ctx, pkt);
@@ -298,7 +354,7 @@
break;
case MCTP_HDR_FLAG_EOM:
- ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+ ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
if (!ctx)
goto out;
@@ -313,17 +369,16 @@
}
rc = mctp_msg_ctx_add_pkt(ctx, pkt);
- if (!rc) {
- mctp->message_rx(ctx->src, mctp->message_rx_data,
+ if (!rc)
+ mctp_rx(mctp, bus, ctx->src, ctx->dest,
ctx->buf, ctx->buf_size);
- }
mctp_msg_ctx_drop(ctx);
break;
case 0:
/* Neither SOM nor EOM */
- ctx = mctp_msg_ctx_lookup(mctp, hdr->src, tag);
+ ctx = mctp_msg_ctx_lookup(mctp, hdr->src,hdr->dest, tag);
if (!ctx)
goto out;
@@ -386,16 +441,14 @@
mctp_send_tx_queue(bus);
}
-int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
- void *msg, size_t msg_len)
+static int mctp_message_tx_on_bus(struct mctp *mctp, struct mctp_bus *bus,
+ mctp_eid_t src, mctp_eid_t dest, void *msg, size_t msg_len)
{
size_t max_payload_len, payload_len, p;
struct mctp_pktbuf *pkt;
struct mctp_hdr *hdr;
- struct mctp_bus *bus;
int i;
- bus = find_bus_for_eid(mctp, eid);
max_payload_len = bus->binding->pkt_size - sizeof(*hdr);
/* queue up packets, each of max MCTP_MTU size */
@@ -404,13 +457,14 @@
if (payload_len > max_payload_len)
payload_len = max_payload_len;
- pkt = mctp_pktbuf_alloc(bus->binding, payload_len + sizeof(*hdr));
+ pkt = mctp_pktbuf_alloc(bus->binding,
+ payload_len + sizeof(*hdr));
hdr = mctp_pktbuf_hdr(pkt);
/* todo: tags */
hdr->ver = bus->binding->version & 0xf;
- hdr->dest = eid;
- hdr->src = bus->eid;
+ hdr->dest = dest;
+ hdr->src = src;
hdr->flags_seq_tag = MCTP_HDR_FLAG_TO |
(0 << MCTP_HDR_TAG_SHIFT);
@@ -437,3 +491,12 @@
return 0;
}
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid,
+ void *msg, size_t msg_len)
+{
+ struct mctp_bus *bus;
+
+ bus = find_bus_for_eid(mctp, eid);
+ return mctp_message_tx_on_bus(mctp, bus, bus->eid, eid, msg, msg_len);
+}
diff --git a/libmctp.h b/libmctp.h
index 5219a35..523891f 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -63,11 +63,24 @@
/* Register a binding to the MCTP core, and creates a bus (populating
* binding->bus).
+ *
+ * If this function is called, the MCTP stack is initialised as an 'endpoint',
+ * and will deliver local packets to a RX callback - see `mctp_set_rx_all()`
+ * below.
*/
int mctp_register_bus(struct mctp *mctp,
struct mctp_binding *binding,
mctp_eid_t eid);
+/* Create a simple bidirectional bridge between busses.
+ *
+ * In this mode, the MCTP stack is initialised as a bridge. There is no EID
+ * defined, so no packets are considered local. Instead, all messages from one
+ * binding are forwarded to the other.
+ */
+int mctp_bridge_busses(struct mctp *mctp,
+ struct mctp_binding *b1, struct mctp_binding *b2);
+
typedef void (*mctp_rx_fn)(uint8_t src_eid, void *data,
void *msg, size_t len);
diff --git a/tests/test-utils.h b/tests/test-utils.h
index 94f43b8..966a2df 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -5,6 +5,11 @@
#include <libmctp.h>
+#ifndef container_of
+#define container_of(ptr, type, member) \
+ (type *)((char *)(ptr) - (char *)&((type *)0)->member)
+#endif
+
/* test binding implementation */
/* standard binding interface */
diff --git a/tests/test_bridge.c b/tests/test_bridge.c
new file mode 100644
index 0000000..496ae19
--- /dev/null
+++ b/tests/test_bridge.c
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <libmctp.h>
+#include <libmctp-alloc.h>
+
+#include "test-utils.h"
+
+struct mctp_binding_bridge {
+ struct mctp_binding binding;
+ int rx_count;
+ int tx_count;
+ uint8_t last_pkt_data;
+};
+
+struct test_ctx {
+ struct mctp *mctp;
+ struct mctp_binding_bridge *bindings[2];
+};
+
+static int mctp_binding_bridge_tx(struct mctp_binding *b,
+ struct mctp_pktbuf *pkt)
+{
+ struct mctp_binding_bridge *binding = container_of(b,
+ struct mctp_binding_bridge, binding);
+
+ binding->tx_count++;
+ assert(mctp_pktbuf_size(pkt) == sizeof(struct mctp_hdr) + 1);
+ binding->last_pkt_data = *(uint8_t *)mctp_pktbuf_data(pkt);
+
+ return 0;
+}
+
+static int mctp_binding_bridge_rx(struct mctp_binding_bridge *binding,
+ uint8_t key)
+{
+ struct mctp_pktbuf *pkt;
+ struct mctp_hdr *hdr;
+ uint8_t *buf;
+
+ pkt = mctp_pktbuf_alloc(&binding->binding,
+ sizeof(struct mctp_hdr) + 1);
+
+ hdr = mctp_pktbuf_hdr(pkt);
+ hdr->flags_seq_tag = MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM;
+
+ /* arbitrary src/dest, as we're bridging */
+ hdr->src = 1;
+ hdr->dest = 2;
+
+ buf = mctp_pktbuf_data(pkt);
+ *buf = key;
+
+ binding->rx_count++;
+ mctp_bus_rx(&binding->binding, pkt);
+}
+
+static struct mctp_binding_bridge *mctp_binding_bridge_init(void)
+{
+ struct mctp_binding_bridge *binding;
+
+ binding = __mctp_alloc(sizeof(*binding));
+ memset(binding, 0, sizeof(*binding));
+ binding->binding.name = "test";
+ binding->binding.version = 1;
+ binding->binding.tx = mctp_binding_bridge_tx;
+ binding->binding.pkt_size = MCTP_BMTU;
+ binding->binding.pkt_pad = 0;
+ return binding;
+}
+
+int main(void)
+{
+ struct test_ctx _ctx, *ctx = &_ctx;
+
+ ctx->mctp = mctp_init();
+ ctx->bindings[0] = mctp_binding_bridge_init();
+ ctx->bindings[1] = mctp_binding_bridge_init();
+
+ mctp_bridge_busses(ctx->mctp,
+ &ctx->bindings[0]->binding,
+ &ctx->bindings[1]->binding);
+
+ mctp_binding_set_tx_enabled(&ctx->bindings[0]->binding, true);
+ mctp_binding_set_tx_enabled(&ctx->bindings[1]->binding, true);
+
+ mctp_binding_bridge_rx(ctx->bindings[0], 0xaa);
+ assert(ctx->bindings[0]->tx_count == 0);
+ assert(ctx->bindings[1]->tx_count == 1);
+ assert(ctx->bindings[1]->last_pkt_data == 0xaa);
+
+ mctp_binding_bridge_rx(ctx->bindings[1], 0x55);
+ assert(ctx->bindings[1]->tx_count == 1);
+ assert(ctx->bindings[0]->tx_count == 1);
+ assert(ctx->bindings[0]->last_pkt_data == 0x55);
+
+ return EXIT_SUCCESS;
+}