/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #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 #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; }