core: Add mctp_message_tx_request() function
This allocates a tag for messages sent with TO bit set.
Change-Id: Ia8403a06aa449e0218a30edf5ad69781c70d7c52
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/core.c b/core.c
index 274cd91..58826bc 100644
--- a/core.c
+++ b/core.c
@@ -30,6 +30,16 @@
#define MCTP_MAX_BUSSES 2
#endif
+/* Concurrent reassembly contexts. */
+#ifndef MCTP_REASSEMBLY_CTXS
+#define MCTP_REASSEMBLY_CTXS 16
+#endif
+
+/* Outbound request tags */
+#ifndef MCTP_REQ_TAGS
+#define MCTP_REQ_TAGS MCTP_REASSEMBLY_CTXS
+#endif
+
/* Internal data structures */
enum mctp_bus_state {
@@ -74,6 +84,13 @@
size_t fragment_size;
};
+struct mctp_req_tag {
+ /* 0 is an unused entry */
+ mctp_eid_t local;
+ mctp_eid_t remote;
+ uint8_t tag;
+};
+
struct mctp {
int n_busses;
struct mctp_bus busses[MCTP_MAX_BUSSES];
@@ -86,10 +103,13 @@
mctp_capture_fn capture;
void *capture_data;
- /* Message reassembly.
- * @todo: flexible context count
- */
- struct mctp_msg_ctx msg_ctxs[16];
+ /* Message reassembly. */
+ struct mctp_msg_ctx msg_ctxs[MCTP_REASSEMBLY_CTXS];
+
+ /* Allocated outbound TO tags */
+ struct mctp_req_tag req_tags[MCTP_REQ_TAGS];
+ /* used to avoid always allocating tag 0 */
+ uint8_t tag_round_robin;
enum {
ROUTE_ENDPOINT,
@@ -107,6 +127,8 @@
static int mctp_message_tx_on_bus(struct mctp_bus *bus, mctp_eid_t src,
mctp_eid_t dest, bool tag_owner,
uint8_t msg_tag, void *msg, size_t msg_len);
+static void mctp_dealloc_tag(struct mctp_bus *bus, mctp_eid_t local,
+ mctp_eid_t remote, uint8_t tag);
struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len)
{
@@ -531,6 +553,11 @@
if (mctp->route_policy == ROUTE_ENDPOINT &&
mctp_rx_dest_is_local(bus, dest)) {
+ /* Note responses to allocated tags */
+ if (!tag_owner) {
+ mctp_dealloc_tag(bus, dest, src, msg_tag);
+ }
+
/* Handle MCTP Control Messages: */
if (len >= sizeof(struct mctp_ctrl_msg_hdr)) {
struct mctp_ctrl_msg_hdr *msg_hdr = buf;
@@ -961,6 +988,95 @@
msg_len);
}
+static void mctp_dealloc_tag(struct mctp_bus *bus, mctp_eid_t local,
+ mctp_eid_t remote, uint8_t tag)
+{
+ struct mctp *mctp = bus->binding->mctp;
+ if (local == 0 || remote == 0) {
+ return;
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(mctp->req_tags); i++) {
+ struct mctp_req_tag *r = &mctp->req_tags[i];
+ if (r->local == local && r->remote == remote && r->tag == tag) {
+ r->local = 0;
+ r->remote = 0;
+ r->tag = 0;
+ return;
+ }
+ }
+}
+
+static int mctp_alloc_tag(struct mctp *mctp, mctp_eid_t local,
+ mctp_eid_t remote, uint8_t *ret_tag)
+{
+ assert(local != 0);
+ assert(remote != 0);
+
+ uint8_t used = 0;
+ struct mctp_req_tag *spare = NULL;
+ /* Find which tags and slots are used/spare */
+ for (size_t i = 0; i < ARRAY_SIZE(mctp->req_tags); i++) {
+ struct mctp_req_tag *r = &mctp->req_tags[i];
+ if (r->local == 0) {
+ spare = r;
+ } else {
+ // TODO: check timeouts
+ if (r->local == local && r->remote == remote) {
+ used |= 1 << r->tag;
+ }
+ }
+ }
+
+ if (spare == NULL) {
+ // All req_tag slots are in-use
+ return -EBUSY;
+ }
+
+ for (uint8_t t = 0; t < 8; t++) {
+ uint8_t tag = (t + mctp->tag_round_robin) % 8;
+ if ((used & 1 << tag) == 0) {
+ spare->local = local;
+ spare->remote = remote;
+ spare->tag = tag;
+ *ret_tag = tag;
+ mctp->tag_round_robin = (tag + 1) % 8;
+ return 0;
+ }
+ }
+
+ // All 8 tags are used for this src/dest pair
+ return -EBUSY;
+}
+
+int mctp_message_tx_request(struct mctp *mctp, mctp_eid_t eid, void *msg,
+ size_t msg_len, uint8_t *ret_alloc_msg_tag)
+{
+ int rc;
+ struct mctp_bus *bus;
+
+ bus = find_bus_for_eid(mctp, eid);
+ if (!bus) {
+ __mctp_msg_free(msg, mctp);
+ return 0;
+ }
+
+ uint8_t alloc_tag;
+ rc = mctp_alloc_tag(mctp, bus->eid, eid, &alloc_tag);
+ if (rc) {
+ mctp_prdebug("Failed allocating tag");
+ __mctp_msg_free(msg, mctp);
+ return rc;
+ }
+
+ if (ret_alloc_msg_tag) {
+ *ret_alloc_msg_tag = alloc_tag;
+ }
+
+ return mctp_message_tx_alloced(mctp, eid, true, alloc_tag, msg,
+ msg_len);
+}
+
bool mctp_is_tx_ready(struct mctp *mctp, mctp_eid_t eid)
{
struct mctp_bus *bus;
diff --git a/libmctp.h b/libmctp.h
index 680dc39..a8f952a 100644
--- a/libmctp.h
+++ b/libmctp.h
@@ -150,6 +150,22 @@
int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner,
uint8_t msg_tag, const void *msg, size_t msg_len);
+/* Transmit a request message.
+ * @msg: The message buffer to send. Must be suitable for
+ * free(), or the custom mctp_set_alloc_ops() m_msg_free.
+ *
+ * A tag with Tag Owner bit set will allocated for the sent message,
+ * and returned to the caller (TO bit is unset in the returned @alloc_msg_tag).
+ * alloc_msg_tag may be NULL to ignore the returned tag.
+ * If no tags are spare -EBUSY will be returned.
+ *
+ * If an asynchronous binding is being used, it will return -EBUSY if
+ * a message is already pending for transmission (msg will be freed).
+ * Asynchronous users can test mctp_is_tx_ready() prior to sending.
+ */
+int mctp_message_tx_request(struct mctp *mctp, mctp_eid_t eid, void *msg,
+ size_t msg_len, uint8_t *alloc_msg_tag);
+
bool mctp_is_tx_ready(struct mctp *mctp, mctp_eid_t eid);
/* hardware bindings */
diff --git a/tests/test_core.c b/tests/test_core.c
index 2f5d2c7..9c17100 100644
--- a/tests/test_core.c
+++ b/tests/test_core.c
@@ -1,5 +1,4 @@
/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
-
#define _GNU_SOURCE
#ifdef NDEBUG
@@ -18,6 +17,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <errno.h>
#include "compiler.h"
#include "libmctp-alloc.h"
@@ -602,6 +602,76 @@
mctp_destroy(mctp);
}
+/*
+ * This test case tests tag allocation. 8 tags
+ * are allowed to be pending.
+ */
+static void mctp_core_test_tx_alloc_tag()
+{
+ struct mctp *mctp = NULL;
+ struct mctp_binding_test *binding = NULL;
+ struct test_params test_param;
+ uint8_t msg_tag;
+ void *msg;
+ int rc;
+ mctp_eid_t dest_eid1 = 30;
+ size_t msg_len = 10;
+
+ mctp_test_stack_init(&mctp, &binding, dest_eid1);
+ mctp_set_rx_all(mctp, rx_message, &test_param);
+
+ uint8_t used = 0;
+ for (int i = 0; i < 8; i++) {
+ test_param.seen = false;
+ test_param.msg_tag = 0xff;
+ test_param.tag_owner = false;
+
+ msg = __mctp_alloc(msg_len);
+ memset(msg, 0x99, msg_len);
+ rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len,
+ &msg_tag);
+ assert(rc == 0);
+ assert(test_param.seen == true);
+ assert(test_param.msg_tag == msg_tag);
+ assert(test_param.tag_owner == true);
+ used |= (1 << msg_tag);
+ }
+ assert(used == 0xff);
+
+ /* Ran out of tags */
+ test_param.seen = false;
+ msg = __mctp_alloc(msg_len);
+ memset(msg, 0x99, msg_len);
+ rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len, &msg_tag);
+ assert(rc == -EBUSY);
+ assert(test_param.seen == false);
+
+ /* Send/Receive a response to one of those tags */
+ test_param.seen = false;
+ msg = __mctp_alloc(msg_len);
+ memset(msg, 0x99, msg_len);
+ /* Arbitrary one */
+ uint8_t replied_tag = 3;
+ rc = mctp_message_tx_alloced(mctp, dest_eid1, false, replied_tag, msg,
+ msg_len);
+ assert(rc == 0);
+ assert(test_param.seen == true);
+ assert(test_param.msg_tag == replied_tag);
+ assert(test_param.tag_owner == false);
+
+ /* Now sending allocates that tag again, since it is the only spare one */
+ test_param.seen = false;
+ msg = __mctp_alloc(msg_len);
+ memset(msg, 0x99, msg_len);
+ rc = mctp_message_tx_request(mctp, dest_eid1, msg, msg_len, &msg_tag);
+ assert(rc == 0);
+ assert(test_param.seen == true);
+ assert(msg_tag == replied_tag);
+
+ mctp_binding_test_destroy(binding);
+ mctp_destroy(mctp);
+}
+
/* clang-format off */
#define TEST_CASE(test) { #test, test }
static const struct {
@@ -620,6 +690,7 @@
TEST_CASE(mctp_core_test_rx_with_tag_multifragment),
TEST_CASE(mctp_core_test_rx_with_null_dst_eid),
TEST_CASE(mctp_core_test_rx_with_broadcast_dst_eid),
+ TEST_CASE(mctp_core_test_tx_alloc_tag),
};
/* clang-format on */