diff options
Diffstat (limited to 'drivers/accel/amdxdna/amdxdna_mailbox.c')
| -rw-r--r-- | drivers/accel/amdxdna/amdxdna_mailbox.c | 221 |
1 files changed, 150 insertions, 71 deletions
diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index cc8865f4e79c..c4701b2703d7 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -9,7 +9,6 @@ #include <linux/interrupt.h> #include <linux/iopoll.h> #include <linux/slab.h> -#include <linux/xarray.h> #define CREATE_TRACE_POINTS #include <trace/events/amdxdna.h> @@ -35,9 +34,12 @@ (_chann)->msix_irq, ##args); \ }) +#define MSG_BUF_SZ(chann) \ + (mailbox_get_ringbuf_size(chann, CHAN_RES_X2I) + \ + sizeof(struct mailbox_msg)) + #define MAGIC_VAL 0x1D000000U #define MAGIC_VAL_MASK 0xFF000000 -#define MAX_MSG_ID_ENTRIES 256 #define MSG_RX_TIMER 200 /* milliseconds */ #define MAILBOX_NAME "xdna_mailbox" @@ -57,8 +59,6 @@ struct mailbox_channel { struct xdna_mailbox_chann_res res[CHAN_RES_NUM]; int msix_irq; u32 iohub_int_addr; - struct xarray chan_xa; - u32 next_msgid; u32 x2i_tail; /* Received msg related fields */ @@ -66,6 +66,12 @@ struct mailbox_channel { struct work_struct rx_work; u32 i2x_head; bool bad_state; + + /* Pre-allocated message buffer pool */ + void *msg_buf; + u32 msg_buf_num; + u32 next_slot; /* next slot index to try for allocation */ + struct mutex lock; }; #define MSG_BODY_SZ GENMASK(10, 0) @@ -90,12 +96,36 @@ struct mailbox_pkg { #define TOMBSTONE 0xDEADFACE struct mailbox_msg { + bool busy; void *handle; int (*notify_cb)(void *handle, void __iomem *data, size_t size); size_t pkg_size; /* package size in bytes */ struct mailbox_pkg pkg; }; +/* + * Clear busy before invoking the callback so that a new submission can + * reuse the slot immediately - by the time notify_cb returns, the caller + * may already have submitted a new job that needs this slot. + * + * notify_cb and handle are cached in locals first. smp_store_release() + * ensures those loads complete before busy is cleared - without it the + * compiler or CPU could sink the loads past the store, letting a + * concurrent sender overwrite notify_cb/handle before we read them. + * Paired with smp_load_acquire() in mailbox_acquire_msgid(). + */ +static int mailbox_msg_done(struct mailbox_msg *mb_msg, void __iomem *data, size_t size) +{ + int (*notify_cb)(void *, void __iomem *, size_t) = mb_msg->notify_cb; + void *handle = mb_msg->handle; + + /* Pairs with smp_load_acquire() in mailbox_acquire_msgid(). */ + smp_store_release(&mb_msg->busy, false); + if (notify_cb) + return notify_cb(handle, data, size); + return 0; +} + static void mailbox_reg_write(struct mailbox_channel *mb_chann, u32 mbox_reg, u32 data) { struct xdna_mailbox_res *mb_res = &mb_chann->mb->res; @@ -161,38 +191,57 @@ static inline int mailbox_validate_msgid(int msg_id) return (msg_id & MAGIC_VAL_MASK) == MAGIC_VAL; } -static int mailbox_acquire_msgid(struct mailbox_channel *mb_chann, struct mailbox_msg *mb_msg) +static struct mailbox_msg *mailbox_msg_ptr(struct mailbox_channel *mb_chann, u32 slot) { - u32 msg_id; - int ret; + return mb_chann->msg_buf + slot * MSG_BUF_SZ(mb_chann); +} - ret = xa_alloc_cyclic_irq(&mb_chann->chan_xa, &msg_id, mb_msg, - XA_LIMIT(0, MAX_MSG_ID_ENTRIES - 1), - &mb_chann->next_msgid, GFP_NOWAIT); - if (ret < 0) - return ret; +static int mailbox_acquire_msgid(struct mailbox_channel *mb_chann) +{ + struct mailbox_msg *mb_msg; + u32 slot; + u32 i; /* - * Add MAGIC_VAL to the higher bits. + * msg_id = slot | MAGIC_VAL. Scan slots starting from next_slot. + * smp_load_acquire() pairs with smp_store_release() in mailbox_msg_done() + * to ensure that once we observe busy == false, notify_cb and handle are + * also visible as their post-completion values before we overwrite them. */ - msg_id |= MAGIC_VAL; - return msg_id; -} + for (i = 0; i < mb_chann->msg_buf_num; i++) { + slot = (mb_chann->next_slot + i) % mb_chann->msg_buf_num; + mb_msg = mailbox_msg_ptr(mb_chann, slot); + /* Pairs with smp_store_release() in mailbox_msg_done(). */ + if (!smp_load_acquire(&mb_msg->busy)) { + mb_chann->next_slot = (slot + 1) % mb_chann->msg_buf_num; + return slot | MAGIC_VAL; + } + } -static void mailbox_release_msgid(struct mailbox_channel *mb_chann, int msg_id) -{ - msg_id &= ~MAGIC_VAL_MASK; - xa_erase_irq(&mb_chann->chan_xa, msg_id); + return -ENOBUFS; } -static void mailbox_release_msg(struct mailbox_channel *mb_chann, - struct mailbox_msg *mb_msg) +static struct mailbox_msg *mailbox_get_msg_buf(struct mailbox_channel *mb_chann, + size_t msg_size) { - MB_DBG(mb_chann, "msg_id 0x%x msg opcode 0x%x", - mb_msg->pkg.header.id, mb_msg->pkg.header.opcode); - if (mb_msg->notify_cb) - mb_msg->notify_cb(mb_msg->handle, NULL, 0); - kfree(mb_msg); + struct mailbox_msg *mb_msg; + int msg_id; + u32 slot; + + /* msg_id = slot | MAGIC_VAL */ + msg_id = mailbox_acquire_msgid(mb_chann); + if (msg_id < 0) { + MB_ERR(mb_chann, "No free message slot"); + return NULL; + } + + slot = msg_id & ~MAGIC_VAL_MASK; + mb_msg = mailbox_msg_ptr(mb_chann, slot); + memset(mb_msg, 0, msg_size + sizeof(*mb_msg)); + mb_msg->busy = true; + mb_msg->pkg.header.id = msg_id; + + return mb_msg; } static int @@ -224,7 +273,7 @@ check_again: if (tail < head && tmp_tail >= head) { ret = read_poll_timeout(mailbox_get_headptr, head, tmp_tail < head || tail >= head, - 1, 100, false, mb_chann, CHAN_RES_X2I); + 1, 2000000, false, mb_chann, CHAN_RES_X2I); if (ret) return ret; @@ -248,8 +297,9 @@ mailbox_get_resp(struct mailbox_channel *mb_chann, struct xdna_msg_header *heade void __iomem *data) { struct mailbox_msg *mb_msg; - int msg_id; int ret = 0; + int msg_id; + u32 slot; msg_id = header->id; if (!mailbox_validate_msgid(msg_id)) { @@ -257,22 +307,32 @@ mailbox_get_resp(struct mailbox_channel *mb_chann, struct xdna_msg_header *heade return -EINVAL; } - msg_id &= ~MAGIC_VAL_MASK; - mb_msg = xa_erase_irq(&mb_chann->chan_xa, msg_id); - if (!mb_msg) { - MB_ERR(mb_chann, "Cannot find msg 0x%x", msg_id); + slot = msg_id & ~MAGIC_VAL_MASK; + if (unlikely(slot >= mb_chann->msg_buf_num)) { + MB_ERR(mb_chann, "Invalid msg_id 0x%x", msg_id); return -EINVAL; } + mb_msg = mailbox_msg_ptr(mb_chann, slot); MB_DBG(mb_chann, "opcode 0x%x size %d id 0x%x", header->opcode, header->total_size, header->id); - if (mb_msg->notify_cb) { - ret = mb_msg->notify_cb(mb_msg->handle, data, header->total_size); - if (unlikely(ret)) - MB_ERR(mb_chann, "Message callback ret %d", ret); + + /* + * msg_id is slot | MAGIC_VAL and is reused as soon as the slot is + * freed. The management firmware protocol guarantees exactly one + * response for each request, so a response cannot refer to a previous + * user of a reused slot. Keep the busy check as a defensive guard + * against an unexpected response for an idle slot. + */ + if (unlikely(!mb_msg->busy)) { + MB_WARN_ONCE(mb_chann, "Unexpected response for idle slot 0x%x", msg_id); + return 0; } - kfree(mb_msg); + ret = mailbox_msg_done(mb_msg, data, header->total_size); + if (unlikely(ret)) + MB_ERR(mb_chann, "Message callback ret %d", ret); + return ret; } @@ -399,7 +459,7 @@ again: } int xdna_mailbox_send_msg(struct mailbox_channel *mb_chann, - const struct xdna_mailbox_msg *msg, u64 tx_timeout) + const struct xdna_mailbox_msg *msg) { struct xdna_msg_header *header; struct mailbox_msg *mb_msg; @@ -428,9 +488,12 @@ int xdna_mailbox_send_msg(struct mailbox_channel *mb_chann, return -EPIPE; } - mb_msg = kzalloc(sizeof(*mb_msg) + pkg_size, GFP_KERNEL); - if (!mb_msg) - return -ENOMEM; + mutex_lock(&mb_chann->lock); + mb_msg = mailbox_get_msg_buf(mb_chann, pkg_size); + if (!mb_msg) { + mutex_unlock(&mb_chann->lock); + return -ENOBUFS; + } mb_msg->handle = msg->handle; mb_msg->notify_cb = msg->notify_cb; @@ -447,28 +510,18 @@ int xdna_mailbox_send_msg(struct mailbox_channel *mb_chann, header->opcode = msg->opcode; memcpy(mb_msg->pkg.payload, msg->send_data, msg->send_size); - ret = mailbox_acquire_msgid(mb_chann, mb_msg); - if (unlikely(ret < 0)) { - MB_ERR(mb_chann, "mailbox_acquire_msgid failed"); - goto msg_id_failed; - } - header->id = ret; - MB_DBG(mb_chann, "opcode 0x%x size %d id 0x%x", header->opcode, header->total_size, header->id); ret = mailbox_send_msg(mb_chann, mb_msg); if (ret) { MB_DBG(mb_chann, "Error in mailbox send msg, ret %d", ret); - goto release_id; + mb_msg->busy = false; + mutex_unlock(&mb_chann->lock); + return ret; } + mutex_unlock(&mb_chann->lock); - return 0; - -release_id: - mailbox_release_msgid(mb_chann, header->id); -msg_id_failed: - kfree(mb_msg); return ret; } @@ -481,12 +534,13 @@ struct mailbox_channel *xdna_mailbox_alloc_channel(struct mailbox *mb) return NULL; INIT_WORK(&mb_chann->rx_work, mailbox_rx_worker); - mb_chann->work_q = create_singlethread_workqueue(MAILBOX_NAME); + mb_chann->work_q = alloc_ordered_workqueue(MAILBOX_NAME, WQ_MEM_RECLAIM); if (!mb_chann->work_q) { MB_ERR(mb_chann, "Create workqueue failed"); goto free_chann; } mb_chann->mb = mb; + mutex_init(&mb_chann->lock); return mb_chann; @@ -500,7 +554,9 @@ void xdna_mailbox_free_channel(struct mailbox_channel *mb_chann) if (!mb_chann) return; + kfree(mb_chann->msg_buf); destroy_workqueue(mb_chann->work_q); + mutex_destroy(&mb_chann->lock); kfree(mb_chann); } @@ -509,21 +565,32 @@ xdna_mailbox_start_channel(struct mailbox_channel *mb_chann, const struct xdna_mailbox_chann_res *x2i, const struct xdna_mailbox_chann_res *i2x, u32 iohub_int_addr, - int mb_irq) + int mb_irq, u32 n_msg) { int ret; - if (!is_power_of_2(x2i->rb_size) || !is_power_of_2(i2x->rb_size)) { - pr_err("Ring buf size must be power of 2\n"); - return -EINVAL; - } - + /* + * The ring buffer geometry, rb_start_addr and rb_size for both the + * x2i and the i2x channel, comes from AMD signed firmware, through + * the management or mailbox information block, or through the + * CREATE_CONTEXT response. The driver trusts those values by design + * and does not bound them against the mapped region. + */ mb_chann->msix_irq = mb_irq; mb_chann->iohub_int_addr = iohub_int_addr; memcpy(&mb_chann->res[CHAN_RES_X2I], x2i, sizeof(*x2i)); memcpy(&mb_chann->res[CHAN_RES_I2X], i2x, sizeof(*i2x)); - xa_init_flags(&mb_chann->chan_xa, XA_FLAGS_ALLOC | XA_FLAGS_LOCK_IRQ); + /* + * msg_buf is a flat array of n_msg slots, separate from the hardware + * ring buffer. Each slot is rb_size bytes - the combined size of the + * mailbox_msg metadata and the package payload must not exceed rb_size. + */ + mb_chann->msg_buf = kcalloc(n_msg, MSG_BUF_SZ(mb_chann), GFP_KERNEL); + if (!mb_chann->msg_buf) + return -ENOMEM; + + mb_chann->msg_buf_num = n_msg; mb_chann->x2i_tail = mailbox_get_tailptr(mb_chann, CHAN_RES_X2I); mb_chann->i2x_head = mailbox_get_headptr(mb_chann, CHAN_RES_I2X); @@ -531,6 +598,8 @@ xdna_mailbox_start_channel(struct mailbox_channel *mb_chann, ret = request_irq(mb_irq, mailbox_irq_handler, 0, MAILBOX_NAME, mb_chann); if (ret) { MB_ERR(mb_chann, "Failed to request irq %d ret %d", mb_irq, ret); + kfree(mb_chann->msg_buf); + mb_chann->msg_buf = NULL; return ret; } @@ -544,7 +613,7 @@ xdna_mailbox_start_channel(struct mailbox_channel *mb_chann, void xdna_mailbox_stop_channel(struct mailbox_channel *mb_chann) { struct mailbox_msg *mb_msg; - unsigned long msg_id; + u32 i; if (!mb_chann) return; @@ -555,12 +624,22 @@ void xdna_mailbox_stop_channel(struct mailbox_channel *mb_chann) /* Cancel RX work and wait for it to finish */ drain_workqueue(mb_chann->work_q); - /* We can clean up and release resources */ - xa_for_each_start(&mb_chann->chan_xa, msg_id, mb_msg, mb_chann->next_msgid) - mailbox_release_msg(mb_chann, mb_msg); - xa_for_each_range(&mb_chann->chan_xa, msg_id, mb_msg, 0, mb_chann->next_msgid - 1) - mailbox_release_msg(mb_chann, mb_msg); - xa_destroy(&mb_chann->chan_xa); + /* Release any in-flight messages in the pool + * + * Drain in submission order: next_slot points to the slot after + * the most recently assigned one, so start from next_slot and + * wrap around so callbacks fire oldest-first. + */ + for (i = 0; i < mb_chann->msg_buf_num; i++) { + u32 slot = (mb_chann->next_slot + i) % mb_chann->msg_buf_num; + + mb_msg = mailbox_msg_ptr(mb_chann, slot); + if (!mb_msg->busy) + continue; + MB_DBG(mb_chann, "msg_id 0x%x msg opcode 0x%x", + mb_msg->pkg.header.id, mb_msg->pkg.header.opcode); + mailbox_msg_done(mb_msg, NULL, 0); + } MB_DBG(mb_chann, "Mailbox channel stopped, irq: %d", mb_chann->msix_irq); } |
