| /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) "core: " fmt |
| |
| #include "libmctp.h" |
| #include "libmctp-alloc.h" |
| #include "libmctp-log.h" |
| #include "libmctp-cmds.h" |
| #include "range.h" |
| #include "compiler.h" |
| #include "core-internal.h" |
| #include "control.h" |
| |
| #if MCTP_DEFAULT_CLOCK_GETTIME |
| #include <time.h> |
| #endif |
| |
| #ifndef ARRAY_SIZE |
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) |
| #endif |
| |
| 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) |
| { |
| size_t size = |
| binding->pkt_size + binding->pkt_header + binding->pkt_trailer; |
| if (len > size) { |
| return NULL; |
| } |
| |
| void *storage = __mctp_alloc(size + sizeof(struct mctp_pktbuf)); |
| if (!storage) { |
| return NULL; |
| } |
| struct mctp_pktbuf *pkt = mctp_pktbuf_init(binding, storage); |
| pkt->alloc = true; |
| pkt->end = pkt->start + len; |
| return pkt; |
| } |
| |
| void mctp_pktbuf_free(struct mctp_pktbuf *pkt) |
| { |
| if (pkt->alloc) { |
| __mctp_free(pkt); |
| } else { |
| mctp_prdebug("pktbuf_free called for non-alloced"); |
| } |
| } |
| |
| struct mctp_pktbuf *mctp_pktbuf_init(struct mctp_binding *binding, |
| void *storage) |
| { |
| size_t size = |
| binding->pkt_size + binding->pkt_header + binding->pkt_trailer; |
| struct mctp_pktbuf *buf = (struct mctp_pktbuf *)storage; |
| buf->size = size; |
| buf->start = binding->pkt_header; |
| buf->end = buf->start; |
| buf->mctp_hdr_off = buf->start; |
| buf->alloc = false; |
| |
| return buf; |
| } |
| |
| struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt) |
| { |
| return (struct mctp_hdr *)(pkt->data + pkt->mctp_hdr_off); |
| } |
| |
| void *mctp_pktbuf_data(struct mctp_pktbuf *pkt) |
| { |
| return pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr); |
| } |
| |
| size_t mctp_pktbuf_size(const struct mctp_pktbuf *pkt) |
| { |
| return pkt->end - pkt->start; |
| } |
| |
| void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, size_t size) |
| { |
| assert(size <= pkt->start); |
| pkt->start -= size; |
| return pkt->data + pkt->start; |
| } |
| |
| void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, size_t size) |
| { |
| void *buf; |
| |
| assert(size <= (pkt->size - pkt->end)); |
| buf = pkt->data + pkt->end; |
| pkt->end += size; |
| return buf; |
| } |
| |
| int mctp_pktbuf_push(struct mctp_pktbuf *pkt, const void *data, size_t len) |
| { |
| void *p; |
| |
| if (pkt->end + len > pkt->size) |
| return -1; |
| |
| p = pkt->data + pkt->end; |
| |
| pkt->end += len; |
| memcpy(p, data, len); |
| |
| return 0; |
| } |
| |
| void *mctp_pktbuf_pop(struct mctp_pktbuf *pkt, size_t len) |
| { |
| if (len > mctp_pktbuf_size(pkt)) |
| return NULL; |
| |
| pkt->end -= len; |
| return pkt->data + pkt->end; |
| } |
| |
| /* Allocate a duplicate of the message and copy it */ |
| static void *mctp_msg_dup(const void *msg, size_t msg_len, struct mctp *mctp) |
| { |
| void *copy = __mctp_msg_alloc(msg_len, mctp); |
| if (!copy) { |
| mctp_prdebug("msg dup len %zu failed", msg_len); |
| return NULL; |
| } |
| |
| memcpy(copy, msg, msg_len); |
| return copy; |
| } |
| |
| /* Message reassembly */ |
| static struct mctp_msg_ctx *mctp_msg_ctx_lookup(struct mctp *mctp, uint8_t src, |
| uint8_t dest, uint8_t tag) |
| { |
| unsigned int i; |
| |
| /* @todo: better lookup, if we add support for more outstanding |
| * message contexts */ |
| for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) { |
| struct mctp_msg_ctx *ctx = &mctp->msg_ctxs[i]; |
| if (ctx->buf && ctx->src == src && ctx->dest == dest && |
| ctx->tag == tag) |
| return ctx; |
| } |
| |
| return NULL; |
| } |
| |
| static struct mctp_msg_ctx *mctp_msg_ctx_create(struct mctp *mctp, uint8_t src, |
| uint8_t dest, uint8_t tag) |
| { |
| struct mctp_msg_ctx *ctx = NULL; |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) { |
| struct mctp_msg_ctx *tmp = &mctp->msg_ctxs[i]; |
| if (!tmp->buf) { |
| ctx = tmp; |
| break; |
| } |
| } |
| |
| if (!ctx) |
| return NULL; |
| |
| ctx->src = src; |
| ctx->dest = dest; |
| ctx->tag = tag; |
| |
| ctx->buf_size = 0; |
| ctx->buf_alloc_size = mctp->max_message_size; |
| ctx->buf = __mctp_msg_alloc(ctx->buf_alloc_size, mctp); |
| if (!ctx->buf) { |
| return NULL; |
| } |
| |
| return ctx; |
| } |
| |
| static void mctp_msg_ctx_drop(struct mctp_bus *bus, struct mctp_msg_ctx *ctx) |
| { |
| /* Free and mark as unused */ |
| __mctp_msg_free(ctx->buf, bus->mctp); |
| ctx->buf = NULL; |
| } |
| |
| static void mctp_msg_ctx_reset(struct mctp_msg_ctx *ctx) |
| { |
| ctx->buf_size = 0; |
| ctx->fragment_size = 0; |
| } |
| |
| static int mctp_msg_ctx_add_pkt(struct mctp_msg_ctx *ctx, |
| struct mctp_pktbuf *pkt) |
| { |
| size_t len; |
| |
| len = mctp_pktbuf_size(pkt) - sizeof(struct mctp_hdr); |
| |
| if (len + ctx->buf_size < ctx->buf_size) { |
| return -1; |
| } |
| |
| if (ctx->buf_size + len > ctx->buf_alloc_size) { |
| return -1; |
| } |
| |
| memcpy((uint8_t *)ctx->buf + ctx->buf_size, mctp_pktbuf_data(pkt), len); |
| ctx->buf_size += len; |
| |
| return 0; |
| } |
| |
| /* Core API functions */ |
| struct mctp *mctp_init(void) |
| { |
| struct mctp *mctp; |
| |
| mctp = __mctp_alloc(sizeof(*mctp)); |
| |
| if (!mctp) |
| return NULL; |
| |
| mctp_setup(mctp, sizeof(*mctp)); |
| return mctp; |
| } |
| |
| #if MCTP_DEFAULT_CLOCK_GETTIME |
| static uint64_t mctp_default_now(void *ctx __attribute__((unused))) |
| { |
| struct timespec tp; |
| int rc = clock_gettime(CLOCK_MONOTONIC, &tp); |
| if (rc) { |
| /* Should not be possible */ |
| return 0; |
| } |
| return (uint64_t)tp.tv_sec * 1000 + tp.tv_nsec / 1000000; |
| } |
| #endif |
| |
| int mctp_setup(struct mctp *mctp, size_t struct_mctp_size) |
| { |
| if (struct_mctp_size < sizeof(struct mctp)) { |
| mctp_prdebug("Mismatching struct mctp"); |
| return -EINVAL; |
| } |
| memset(mctp, 0, sizeof(*mctp)); |
| mctp->max_message_size = MCTP_MAX_MESSAGE_SIZE; |
| #if MCTP_DEFAULT_CLOCK_GETTIME |
| mctp->platform_now = mctp_default_now; |
| #endif |
| #if MCTP_CONTROL_HANDLER |
| mctp_control_add_type(mctp, MCTP_CTRL_HDR_MSG_TYPE); |
| #endif |
| return 0; |
| } |
| |
| void mctp_set_max_message_size(struct mctp *mctp, size_t message_size) |
| { |
| mctp->max_message_size = message_size; |
| } |
| |
| void mctp_set_capture_handler(struct mctp *mctp, mctp_capture_fn fn, void *user) |
| { |
| mctp->capture = fn; |
| mctp->capture_data = user; |
| } |
| |
| static void mctp_bus_destroy(struct mctp_bus *bus, struct mctp *mctp) |
| { |
| if (bus->tx_msg) { |
| __mctp_msg_free(bus->tx_msg, mctp); |
| bus->tx_msg = NULL; |
| } |
| } |
| |
| void mctp_cleanup(struct mctp *mctp) |
| { |
| size_t i; |
| |
| /* Cleanup message assembly contexts */ |
| static_assert(ARRAY_SIZE(mctp->msg_ctxs) < SIZE_MAX, "size"); |
| for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) { |
| struct mctp_msg_ctx *tmp = &mctp->msg_ctxs[i]; |
| if (tmp->buf) |
| __mctp_msg_free(tmp->buf, mctp); |
| } |
| |
| while (mctp->n_busses--) |
| mctp_bus_destroy(&mctp->busses[mctp->n_busses], mctp); |
| } |
| |
| void mctp_destroy(struct mctp *mctp) |
| { |
| mctp_cleanup(mctp); |
| __mctp_free(mctp); |
| } |
| |
| int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data) |
| { |
| mctp->message_rx = fn; |
| mctp->message_rx_data = data; |
| return 0; |
| } |
| |
| static struct mctp_bus *find_bus_for_eid(struct mctp *mctp, mctp_eid_t dest |
| __attribute__((unused))) |
| { |
| if (mctp->n_busses == 0) |
| return NULL; |
| |
| /* for now, just use the first bus. For full routing support, |
| * we will need a table of neighbours */ |
| return &mctp->busses[0]; |
| } |
| |
| int mctp_register_bus(struct mctp *mctp, struct mctp_binding *binding, |
| mctp_eid_t eid) |
| { |
| int rc = 0; |
| |
| /* todo: multiple busses */ |
| static_assert(MCTP_MAX_BUSSES >= 1, "need a bus"); |
| assert(mctp->n_busses == 0); |
| mctp->n_busses = 1; |
| |
| assert(binding->tx_storage); |
| |
| memset(mctp->busses, 0, sizeof(struct mctp_bus)); |
| mctp->busses[0].mctp = mctp; |
| mctp->busses[0].binding = binding; |
| mctp->busses[0].eid = eid; |
| binding->bus = &mctp->busses[0]; |
| binding->mctp = mctp; |
| mctp->route_policy = ROUTE_ENDPOINT; |
| |
| if (binding->start) { |
| rc = binding->start(binding); |
| if (rc < 0) { |
| mctp_prerr("Failed to start binding: %d", rc); |
| binding->bus = NULL; |
| mctp->n_busses = 0; |
| } |
| } |
| |
| return rc; |
| } |
| |
| int mctp_bus_set_eid(struct mctp_binding *binding, mctp_eid_t eid) |
| { |
| if (eid < 8 || eid == 0xff) { |
| return -EINVAL; |
| } |
| |
| binding->bus->eid = eid; |
| return 0; |
| } |
| |
| void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding) |
| { |
| /* |
| * We only support one bus right now; once the call completes we will |
| * have no more busses |
| */ |
| mctp->n_busses = 0; |
| binding->mctp = NULL; |
| binding->bus = NULL; |
| } |
| |
| int mctp_bridge_busses(struct mctp *mctp, struct mctp_binding *b1, |
| struct mctp_binding *b2) |
| { |
| int rc = 0; |
| |
| assert(b1->tx_storage); |
| assert(b2->tx_storage); |
| |
| assert(mctp->n_busses == 0); |
| assert(MCTP_MAX_BUSSES >= 2); |
| memset(mctp->busses, 0, 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; |
| |
| if (b1->start) { |
| rc = b1->start(b1); |
| if (rc < 0) { |
| mctp_prerr("Failed to start bridged bus %s: %d", |
| b1->name, rc); |
| goto done; |
| } |
| } |
| |
| if (b2->start) { |
| rc = b2->start(b2); |
| if (rc < 0) { |
| mctp_prerr("Failed to start bridged bus %s: %d", |
| b2->name, rc); |
| goto done; |
| } |
| } |
| |
| done: |
| return rc; |
| } |
| |
| static inline bool mctp_ctrl_cmd_is_transport(struct mctp_ctrl_msg_hdr *hdr) |
| { |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wtype-limits" |
| return ((hdr->command_code >= MCTP_CTRL_CMD_FIRST_TRANSPORT) && |
| (hdr->command_code <= MCTP_CTRL_CMD_LAST_TRANSPORT)); |
| #pragma GCC diagnostic pop |
| } |
| |
| static bool mctp_ctrl_handle_msg(struct mctp_bus *bus, mctp_eid_t src, |
| uint8_t msg_tag, bool tag_owner, void *buffer, |
| size_t length) |
| { |
| struct mctp_ctrl_msg_hdr *msg_hdr = buffer; |
| |
| /* |
| * Control message is received. If a transport control message handler |
| * is provided, it will called. If there is no dedicated handler, this |
| * function returns false and data can be handled by the generic |
| * message handler. The transport control message handler will be |
| * provided with messages in the command range 0xF0 - 0xFF. |
| */ |
| if (mctp_ctrl_cmd_is_transport(msg_hdr)) { |
| if (bus->binding->control_rx != NULL) { |
| /* MCTP bus binding handler */ |
| bus->binding->control_rx(src, msg_tag, tag_owner, |
| bus->binding->control_rx_data, |
| buffer, length); |
| return true; |
| } |
| } else { |
| #if MCTP_CONTROL_HANDLER |
| /* libmctp will handle control requests */ |
| return mctp_control_handler(bus, src, tag_owner, msg_tag, |
| buffer, length); |
| #endif |
| } |
| |
| /* |
| * Command was not handled, due to lack of specific callback. |
| * It will be passed to regular message_rx handler. |
| */ |
| return false; |
| } |
| |
| static inline bool mctp_rx_dest_is_local(struct mctp_bus *bus, mctp_eid_t dest) |
| { |
| return dest == bus->eid || dest == MCTP_EID_NULL || |
| dest == MCTP_EID_BROADCAST; |
| } |
| |
| static inline bool mctp_ctrl_cmd_is_request(struct mctp_ctrl_msg_hdr *hdr) |
| { |
| return hdr->ic_msg_type == MCTP_CTRL_HDR_MSG_TYPE && |
| hdr->rq_dgram_inst & MCTP_CTRL_HDR_FLAG_REQUEST; |
| } |
| |
| /* |
| * Receive the complete MCTP message and route it. |
| * Asserts: |
| * 'buf' is not NULL. |
| */ |
| static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus, mctp_eid_t src, |
| mctp_eid_t dest, bool tag_owner, uint8_t msg_tag, void *buf, |
| size_t len) |
| { |
| assert(buf != NULL); |
| |
| 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; |
| |
| /* |
| * Identify if this is a control request message. |
| * See DSP0236 v1.3.0 sec. 11.5. |
| */ |
| if (mctp_ctrl_cmd_is_request(msg_hdr)) { |
| bool handled; |
| handled = mctp_ctrl_handle_msg( |
| bus, src, msg_tag, tag_owner, buf, len); |
| if (handled) |
| return; |
| } |
| } |
| |
| if (mctp->message_rx) |
| mctp->message_rx(src, tag_owner, msg_tag, |
| 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; |
| |
| void *copy = mctp_msg_dup(buf, len, mctp); |
| if (!copy) { |
| return; |
| } |
| |
| mctp_message_tx_on_bus(dest_bus, src, dest, tag_owner, |
| msg_tag, copy, len); |
| } |
| } |
| } |
| |
| void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt) |
| { |
| struct mctp_bus *bus = binding->bus; |
| struct mctp *mctp = binding->mctp; |
| uint8_t flags, exp_seq, seq, tag; |
| struct mctp_msg_ctx *ctx; |
| struct mctp_hdr *hdr; |
| bool tag_owner; |
| size_t len; |
| void *p; |
| int rc; |
| |
| assert(bus); |
| |
| /* Drop packet if it was smaller than mctp hdr size */ |
| if (mctp_pktbuf_size(pkt) < sizeof(struct mctp_hdr)) |
| goto out; |
| |
| if (mctp->capture) |
| mctp->capture(pkt, MCTP_MESSAGE_CAPTURE_INCOMING, |
| mctp->capture_data); |
| |
| hdr = mctp_pktbuf_hdr(pkt); |
| |
| if (hdr->src == MCTP_EID_BROADCAST) { |
| /* drop packets with broadcast EID src */ |
| goto out; |
| } |
| |
| /* small optimisation: don't bother reassembly if we're going to |
| * drop the packet in mctp_rx anyway */ |
| if (mctp->route_policy == ROUTE_ENDPOINT && |
| !mctp_rx_dest_is_local(bus, hdr->dest)) |
| goto out; |
| |
| flags = hdr->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM); |
| tag = (hdr->flags_seq_tag >> MCTP_HDR_TAG_SHIFT) & MCTP_HDR_TAG_MASK; |
| seq = (hdr->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK; |
| tag_owner = (hdr->flags_seq_tag >> MCTP_HDR_TO_SHIFT) & |
| MCTP_HDR_TO_MASK; |
| |
| switch (flags) { |
| case MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM: |
| /* single-packet message - send straight up to rx function, |
| * no need to create a message context */ |
| len = pkt->end - pkt->mctp_hdr_off - sizeof(struct mctp_hdr); |
| p = mctp_msg_dup(pkt->data + pkt->mctp_hdr_off + |
| sizeof(struct mctp_hdr), |
| len, mctp); |
| if (p) { |
| mctp_rx(mctp, bus, hdr->src, hdr->dest, tag_owner, tag, |
| p, len); |
| __mctp_msg_free(p, mctp); |
| } |
| 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, hdr->dest, tag); |
| if (ctx) { |
| mctp_msg_ctx_reset(ctx); |
| } else { |
| ctx = mctp_msg_ctx_create(mctp, hdr->src, hdr->dest, |
| tag); |
| /* If context creation fails due to exhaution of contexts we |
| * can support, drop the packet */ |
| if (!ctx) { |
| mctp_prdebug("Context buffers exhausted."); |
| goto out; |
| } |
| } |
| |
| /* Save the fragment size, subsequent middle fragments |
| * should of the same size */ |
| ctx->fragment_size = mctp_pktbuf_size(pkt); |
| |
| rc = mctp_msg_ctx_add_pkt(ctx, pkt); |
| if (rc) { |
| mctp_msg_ctx_drop(bus, ctx); |
| } else { |
| ctx->last_seq = seq; |
| } |
| |
| break; |
| |
| case MCTP_HDR_FLAG_EOM: |
| ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag); |
| if (!ctx) |
| goto out; |
| |
| exp_seq = (ctx->last_seq + 1) % 4; |
| |
| if (exp_seq != seq) { |
| mctp_prdebug( |
| "Sequence number %d does not match expected %d", |
| seq, exp_seq); |
| mctp_msg_ctx_drop(bus, ctx); |
| goto out; |
| } |
| |
| len = mctp_pktbuf_size(pkt); |
| |
| if (len > ctx->fragment_size) { |
| mctp_prdebug("Unexpected fragment size. Expected" |
| " less than %zu, received = %zu", |
| ctx->fragment_size, len); |
| mctp_msg_ctx_drop(bus, ctx); |
| goto out; |
| } |
| |
| rc = mctp_msg_ctx_add_pkt(ctx, pkt); |
| if (!rc) |
| mctp_rx(mctp, bus, ctx->src, ctx->dest, tag_owner, tag, |
| ctx->buf, ctx->buf_size); |
| |
| mctp_msg_ctx_drop(bus, ctx); |
| break; |
| |
| case 0: |
| /* Neither SOM nor EOM */ |
| ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag); |
| if (!ctx) |
| goto out; |
| |
| exp_seq = (ctx->last_seq + 1) % 4; |
| if (exp_seq != seq) { |
| mctp_prdebug( |
| "Sequence number %d does not match expected %d", |
| seq, exp_seq); |
| mctp_msg_ctx_drop(bus, ctx); |
| goto out; |
| } |
| |
| len = mctp_pktbuf_size(pkt); |
| |
| if (len != ctx->fragment_size) { |
| mctp_prdebug("Unexpected fragment size. Expected = %zu " |
| "received = %zu", |
| ctx->fragment_size, len); |
| mctp_msg_ctx_drop(bus, ctx); |
| goto out; |
| } |
| |
| rc = mctp_msg_ctx_add_pkt(ctx, pkt); |
| if (rc) { |
| mctp_msg_ctx_drop(bus, ctx); |
| goto out; |
| } |
| ctx->last_seq = seq; |
| |
| break; |
| } |
| out: |
| return; |
| } |
| |
| static int mctp_packet_tx(struct mctp_bus *bus, struct mctp_pktbuf *pkt) |
| { |
| struct mctp *mctp = bus->binding->mctp; |
| |
| if (bus->state != mctp_bus_state_tx_enabled) { |
| mctp_prdebug("tx with bus disabled"); |
| return -1; |
| } |
| |
| if (mctp->capture) |
| mctp->capture(pkt, MCTP_MESSAGE_CAPTURE_OUTGOING, |
| mctp->capture_data); |
| |
| return bus->binding->tx(bus->binding, pkt); |
| } |
| |
| /* Returns a pointer to the binding's tx_storage */ |
| static struct mctp_pktbuf *mctp_next_tx_pkt(struct mctp_bus *bus) |
| { |
| if (!bus->tx_msg) { |
| return NULL; |
| } |
| |
| size_t p = bus->tx_msgpos; |
| size_t msg_len = bus->tx_msglen; |
| size_t payload_len = msg_len - p; |
| size_t max_payload_len = MCTP_BODY_SIZE(bus->binding->pkt_size); |
| if (payload_len > max_payload_len) |
| payload_len = max_payload_len; |
| |
| struct mctp_pktbuf *pkt = |
| mctp_pktbuf_init(bus->binding, bus->binding->tx_storage); |
| struct mctp_hdr *hdr = mctp_pktbuf_hdr(pkt); |
| |
| hdr->ver = bus->binding->version & 0xf; |
| hdr->dest = bus->tx_dest; |
| hdr->src = bus->tx_src; |
| hdr->flags_seq_tag = (bus->tx_to << MCTP_HDR_TO_SHIFT) | |
| (bus->tx_tag << MCTP_HDR_TAG_SHIFT); |
| |
| if (p == 0) |
| hdr->flags_seq_tag |= MCTP_HDR_FLAG_SOM; |
| if (p + payload_len >= msg_len) |
| hdr->flags_seq_tag |= MCTP_HDR_FLAG_EOM; |
| hdr->flags_seq_tag |= bus->tx_seq << MCTP_HDR_SEQ_SHIFT; |
| |
| memcpy(mctp_pktbuf_data(pkt), (uint8_t *)bus->tx_msg + p, payload_len); |
| pkt->end = pkt->start + sizeof(*hdr) + payload_len; |
| bus->tx_pktlen = payload_len; |
| |
| mctp_prdebug( |
| "tx dst %d tag %d payload len %zu seq %d. msg pos %zu len %zu", |
| hdr->dest, bus->tx_tag, payload_len, bus->tx_seq, p, msg_len); |
| |
| return pkt; |
| } |
| |
| /* Called when a packet has successfully been sent */ |
| static void mctp_tx_complete(struct mctp_bus *bus) |
| { |
| if (!bus->tx_msg) { |
| mctp_prdebug("tx complete no message"); |
| return; |
| } |
| |
| bus->tx_seq = (bus->tx_seq + 1) & MCTP_HDR_SEQ_MASK; |
| bus->tx_msgpos += bus->tx_pktlen; |
| |
| if (bus->tx_msgpos >= bus->tx_msglen) { |
| __mctp_msg_free(bus->tx_msg, bus->binding->mctp); |
| bus->tx_msg = NULL; |
| } |
| } |
| |
| static void mctp_send_tx_queue(struct mctp_bus *bus) |
| { |
| struct mctp_pktbuf *pkt; |
| |
| while (bus->tx_msg && bus->state == mctp_bus_state_tx_enabled) { |
| int rc; |
| |
| pkt = mctp_next_tx_pkt(bus); |
| |
| rc = mctp_packet_tx(bus, pkt); |
| switch (rc) { |
| /* If transmission succeded */ |
| case 0: |
| /* Drop the packet */ |
| mctp_tx_complete(bus); |
| break; |
| |
| /* If the binding was busy */ |
| case -EBUSY: |
| /* Keep the packet for next try */ |
| mctp_prdebug("tx EBUSY"); |
| return; |
| |
| /* Some other unknown error occurred */ |
| default: |
| /* Drop the packet */ |
| mctp_prdebug("tx drop %d", rc); |
| mctp_tx_complete(bus); |
| return; |
| }; |
| } |
| } |
| |
| void mctp_binding_set_tx_enabled(struct mctp_binding *binding, bool enable) |
| { |
| struct mctp_bus *bus = binding->bus; |
| |
| switch (bus->state) { |
| case mctp_bus_state_constructed: |
| if (!enable) |
| return; |
| |
| if (binding->pkt_size < MCTP_PACKET_SIZE(MCTP_BTU)) { |
| mctp_prerr( |
| "Cannot start %s binding with invalid MTU: %zu", |
| binding->name, |
| MCTP_BODY_SIZE(binding->pkt_size)); |
| return; |
| } |
| |
| bus->state = mctp_bus_state_tx_enabled; |
| mctp_prinfo("%s binding started", binding->name); |
| return; |
| case mctp_bus_state_tx_enabled: |
| if (enable) |
| return; |
| |
| bus->state = mctp_bus_state_tx_disabled; |
| mctp_prdebug("%s binding Tx disabled", binding->name); |
| return; |
| case mctp_bus_state_tx_disabled: |
| if (!enable) |
| return; |
| |
| bus->state = mctp_bus_state_tx_enabled; |
| mctp_prdebug("%s binding Tx enabled", binding->name); |
| mctp_send_tx_queue(bus); |
| return; |
| } |
| } |
| |
| 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) |
| { |
| size_t max_payload_len; |
| int rc; |
| |
| if (bus->state == mctp_bus_state_constructed) { |
| rc = -ENXIO; |
| goto err; |
| } |
| |
| if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag) { |
| rc = -EINVAL; |
| goto err; |
| } |
| |
| max_payload_len = MCTP_BODY_SIZE(bus->binding->pkt_size); |
| |
| { |
| const bool valid_mtu = max_payload_len >= MCTP_BTU; |
| assert(valid_mtu); |
| if (!valid_mtu) { |
| rc = -EINVAL; |
| goto err; |
| } |
| } |
| |
| mctp_prdebug( |
| "%s: Generating packets for transmission of %zu byte message from %hhu to %hhu", |
| __func__, msg_len, src, dest); |
| |
| if (bus->tx_msg) { |
| mctp_prdebug("Bus busy"); |
| rc = -EBUSY; |
| goto err; |
| } |
| |
| /* Take the message to send */ |
| bus->tx_msg = msg; |
| bus->tx_msglen = msg_len; |
| bus->tx_msgpos = 0; |
| /* bus->tx_seq is allowed to continue from previous message */ |
| bus->tx_src = src; |
| bus->tx_dest = dest; |
| bus->tx_to = tag_owner; |
| bus->tx_tag = msg_tag; |
| |
| mctp_send_tx_queue(bus); |
| return 0; |
| |
| err: |
| __mctp_msg_free(msg, bus->binding->mctp); |
| return rc; |
| } |
| |
| int mctp_message_tx_alloced(struct mctp *mctp, mctp_eid_t eid, bool tag_owner, |
| uint8_t msg_tag, void *msg, size_t msg_len) |
| { |
| struct mctp_bus *bus; |
| |
| /* TODO: Protect against same tag being used across |
| * different callers */ |
| if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag) { |
| mctp_prerr("Incorrect message tag %u passed.", msg_tag); |
| __mctp_msg_free(msg, mctp); |
| return -EINVAL; |
| } |
| |
| bus = find_bus_for_eid(mctp, eid); |
| if (!bus) { |
| __mctp_msg_free(msg, mctp); |
| return 0; |
| } |
| |
| return mctp_message_tx_on_bus(bus, bus->eid, eid, tag_owner, msg_tag, |
| msg, msg_len); |
| } |
| |
| 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) |
| { |
| void *copy = mctp_msg_dup(msg, msg_len, mctp); |
| if (!copy) { |
| return -ENOMEM; |
| } |
| |
| return mctp_message_tx_alloced(mctp, eid, tag_owner, msg_tag, copy, |
| msg_len); |
| } |
| |
| void mctp_set_now_op(struct mctp *mctp, uint64_t (*now)(void *), void *ctx) |
| { |
| assert(now); |
| mctp->platform_now = now; |
| mctp->platform_now_ctx = ctx; |
| } |
| |
| uint64_t mctp_now(struct mctp *mctp) |
| { |
| assert(mctp->platform_now); |
| return mctp->platform_now(mctp->platform_now_ctx); |
| } |
| |
| 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) { |
| 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; |
| r->expiry = 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); |
| uint64_t now = mctp_now(mctp); |
| |
| 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 || r->expiry < now) { |
| spare = r; |
| } else { |
| 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; |
| spare->expiry = now + MCTP_TAG_TIMEOUT; |
| *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; |
| |
| bus = find_bus_for_eid(mctp, eid); |
| if (!bus) { |
| return true; |
| } |
| return bus->tx_msg == NULL; |
| } |
| |
| void *mctp_get_alloc_ctx(struct mctp *mctp) |
| { |
| return mctp->alloc_ctx; |
| } |
| |
| void mctp_set_alloc_ctx(struct mctp *mctp, void *ctx) |
| { |
| mctp->alloc_ctx = ctx; |
| } |