RDMA/bnxt_re: Defer toggle page free to rdma_user_mmap_entry teardown

Fix the page lifetime by making the rdma_user_mmap_entry the sole owner
of the toggle page allocation. Creating the rdma_user_mmap_entry and page
during the CQ/SRQ creation time. Freeing the page is handled when the
mmap free is called. Introduce struct bnxt_re_toggle_mem to carry
the mmap_offset for the lifetime of the GET_TOGGLE_MEM uobject handle.

bnxt_re_destroy_cq/srq can erase the entry from the XArray and call
rdma_user_mmap_entry_remove() on the toggle_entry concurrently with
the caller's xa_load() and its subsequent use of that toggle_entry.
Guard against this by taking an extra kref directly on the
toggle_entry's rdma_user_mmap_entry while the GET_TOGGLE_MEM handle
exists, released when the handle is destroyed. This pins exactly the
resource that GET_TOGGLE_MEM hands out (the mmap offset/page),
independent of the CQ/SRQ's own lifetime.

Signed-off-by: Selvin Xavier <selvin.xavier@broadcom.com>
Signed-off-by: Leon Romanovsky <leon@kernel.org>
This commit is contained in:
Selvin Xavier 2026-07-21 04:54:38 -07:00 committed by Leon Romanovsky
parent 97eafb59d4
commit e202e3a55c
3 changed files with 75 additions and 18 deletions

View File

@ -2169,7 +2169,7 @@ int bnxt_re_destroy_srq(struct ib_srq *ib_srq, struct ib_udata *udata)
rdma_udata_to_drv_context(udata, struct bnxt_re_ucontext, ib_uctx);
if (uctx)
free_page((unsigned long)srq->uctx_srq_page);
rdma_user_mmap_entry_remove(&srq->toggle_entry->rdma_entry);
}
ib_umem_release(srq->umem);
atomic_dec(&rdev->stats.res.srq_count);
@ -2282,10 +2282,17 @@ int bnxt_re_create_srq(struct ib_srq *ib_srq,
rc = -ENOMEM;
goto fail_destroy_srq;
}
srq->toggle_entry = bnxt_re_mmap_entry_insert(uctx, (u64)srq->uctx_srq_page,
BNXT_RE_MMAP_TOGGLE_PAGE,
NULL);
if (!srq->toggle_entry) {
rc = -ENOMEM;
goto fail_free_srq_page;
}
if (xa_is_err(xa_store(&uctx->srq_xa, srq->qplib_srq.id,
ib_srq->uobject, GFP_KERNEL))) {
rc = -ENOMEM;
goto fail_free_srq_page;
goto fail_remove_toggle_entry;
}
resp.comp_mask |= BNXT_RE_SRQ_TOGGLE_PAGE_SUPPORT;
}
@ -2303,10 +2310,13 @@ int bnxt_re_create_srq(struct ib_srq *ib_srq,
fail_respond:
if (rdev->chip_ctx->modes.toggle_bits & BNXT_QPLIB_SRQ_TOGGLE_BIT) {
xa_erase(&uctx->srq_xa, srq->qplib_srq.id);
free_page((unsigned long)srq->uctx_srq_page);
goto fail_remove_toggle_entry;
}
bnxt_qplib_destroy_srq(&rdev->qplib_res, &srq->qplib_srq);
goto fail;
fail_remove_toggle_entry:
rdma_user_mmap_entry_remove(&srq->toggle_entry->rdma_entry);
goto fail_destroy_srq;
fail_free_srq_page:
free_page((unsigned long)srq->uctx_srq_page);
fail_destroy_srq:
@ -3510,7 +3520,7 @@ int bnxt_re_destroy_cq(struct ib_cq *ib_cq, struct ib_udata *udata)
rdma_udata_to_drv_context(udata, struct bnxt_re_ucontext, ib_uctx);
if (uctx)
free_page((unsigned long)cq->uctx_cq_page);
rdma_user_mmap_entry_remove(&cq->toggle_entry->rdma_entry);
}
bnxt_re_put_nq(rdev, nq);
@ -3591,10 +3601,16 @@ int bnxt_re_create_user_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *att
rc = -ENOMEM;
goto destroy_cq;
}
cq->toggle_entry = bnxt_re_mmap_entry_insert(uctx, (u64)cq->uctx_cq_page,
BNXT_RE_MMAP_TOGGLE_PAGE, NULL);
if (!cq->toggle_entry) {
rc = -ENOMEM;
goto free_cq_page;
}
if (xa_is_err(xa_store(&uctx->cq_xa, cq->qplib_cq.id,
ibcq->uobject, GFP_KERNEL))) {
rc = -ENOMEM;
goto free_cq_page;
goto remove_toggle_entry;
}
resp.comp_mask |= BNXT_RE_CQ_TOGGLE_PAGE_SUPPORT;
}
@ -3610,6 +3626,10 @@ int bnxt_re_create_user_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *att
free_mem:
if (cctx->modes.toggle_bits & BNXT_QPLIB_CQ_TOGGLE_BIT)
xa_erase(&uctx->cq_xa, cq->qplib_cq.id);
remove_toggle_entry:
if (cctx->modes.toggle_bits & BNXT_QPLIB_CQ_TOGGLE_BIT)
rdma_user_mmap_entry_remove(&cq->toggle_entry->rdma_entry);
goto destroy_cq;
free_cq_page:
free_page((unsigned long)cq->uctx_cq_page);
destroy_cq:
@ -5060,6 +5080,16 @@ void bnxt_re_mmap_free(struct rdma_user_mmap_entry *rdma_entry)
bnxt_entry = container_of(rdma_entry, struct bnxt_re_user_mmap_entry,
rdma_entry);
/*
* For toggle pages the kernel VA was stored directly in mem_offset
* at creation time (bnxt_re_create_user_cq / bnxt_re_create_srq).
* Free it here this is the only place it is freed, ensuring the
* page outlives every concurrent bnxt_re_mmap() call that may have
* incremented the entry's reference count.
*/
if (bnxt_entry->mmap_flag == BNXT_RE_MMAP_TOGGLE_PAGE)
free_page((unsigned long)bnxt_entry->mem_offset);
if (bnxt_entry->dpi_valid)
bnxt_qplib_free_uc_dpi(&bnxt_entry->uctx->rdev->qplib_res,
&bnxt_entry->dpi);

View File

@ -80,6 +80,7 @@ struct bnxt_re_srq {
struct ib_umem *umem;
spinlock_t lock; /* protect srq */
void *uctx_srq_page;
struct bnxt_re_user_mmap_entry *toggle_entry;
};
struct bnxt_re_qp {
@ -114,6 +115,7 @@ struct bnxt_re_cq {
struct ib_umem *resize_umem;
int resize_cqe;
void *uctx_cq_page;
struct bnxt_re_user_mmap_entry *toggle_entry;
};
struct bnxt_re_mr {

View File

@ -213,19 +213,23 @@ DECLARE_UVERBS_GLOBAL_METHODS(BNXT_RE_OBJECT_NOTIFY_DRV,
&UVERBS_METHOD(BNXT_RE_METHOD_NOTIFY_DRV));
/* Toggle MEM */
struct bnxt_re_toggle_mem {
struct bnxt_re_user_mmap_entry *toggle_entry;
u64 mmap_offset;
};
static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_TOGGLE_MEM)(struct uverbs_attr_bundle *attrs)
{
struct ib_uobject *uobj = uverbs_attr_get_uobject(attrs, BNXT_RE_TOGGLE_MEM_HANDLE);
enum bnxt_re_mmap_flag mmap_flag = BNXT_RE_MMAP_TOGGLE_PAGE;
struct bnxt_re_user_mmap_entry *toggle_entry = NULL;
enum bnxt_re_get_toggle_mem_type res_type;
struct bnxt_re_user_mmap_entry *entry;
struct bnxt_re_toggle_mem *tmem;
struct ib_uobject *res_uobj;
struct bnxt_re_ucontext *uctx;
struct ib_ucontext *ib_uctx;
u32 length = PAGE_SIZE;
u64 mem_offset;
u64 mmap_offset = 0;
u32 offset = 0;
u64 addr = 0;
u32 res_id;
int err;
@ -243,6 +247,10 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_TOGGLE_MEM)(struct uverbs_attr_bund
return err;
/*
* Hold xa_lock across xa_load + kref_get so that a concurrent
* bnxt_re_destroy_cq/srq cannot call __xa_erase and remove the
* toggle_entry between our load and our reference on it.
*
* bnxt_re_create_cq/srq() publishes the uobject into cq_xa/srq_xa
* before returning to the uverbs core, but the core only sets
* uobject->object once the create callback has returned success.
@ -257,7 +265,13 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_TOGGLE_MEM)(struct uverbs_attr_bund
res_uobj = xa_load(&uctx->cq_xa, res_id);
if (res_uobj && res_uobj->object) {
cq = container_of(res_uobj->object, struct bnxt_re_cq, ib_cq);
addr = (u64)cq->uctx_cq_page;
if (cq->toggle_entry)
mmap_offset =
rdma_user_mmap_get_offset(&cq->toggle_entry->rdma_entry);
if (mmap_offset) {
kref_get(&cq->toggle_entry->rdma_entry.ref);
toggle_entry = cq->toggle_entry;
}
}
xa_unlock(&uctx->cq_xa);
} else if (res_type == BNXT_RE_SRQ_TOGGLE_MEM) {
@ -267,24 +281,34 @@ static int UVERBS_HANDLER(BNXT_RE_METHOD_GET_TOGGLE_MEM)(struct uverbs_attr_bund
res_uobj = xa_load(&uctx->srq_xa, res_id);
if (res_uobj && res_uobj->object) {
srq = container_of(res_uobj->object, struct bnxt_re_srq, ib_srq);
addr = (u64)srq->uctx_srq_page;
if (srq->toggle_entry)
mmap_offset =
rdma_user_mmap_get_offset(&srq->toggle_entry->rdma_entry);
if (mmap_offset) {
kref_get(&srq->toggle_entry->rdma_entry.ref);
toggle_entry = srq->toggle_entry;
}
}
xa_unlock(&uctx->srq_xa);
} else {
return -EOPNOTSUPP;
}
if (!addr)
if (!mmap_offset)
return -EOPNOTSUPP;
entry = bnxt_re_mmap_entry_insert(uctx, addr, mmap_flag, &mem_offset);
if (!entry)
tmem = kzalloc_obj(*tmem);
if (!tmem) {
rdma_user_mmap_entry_put(&toggle_entry->rdma_entry);
return -ENOMEM;
}
uobj->object = entry;
tmem->toggle_entry = toggle_entry;
tmem->mmap_offset = mmap_offset;
uobj->object = tmem;
uverbs_finalize_uobj_create(attrs, BNXT_RE_TOGGLE_MEM_HANDLE);
err = uverbs_copy_to(attrs, BNXT_RE_TOGGLE_MEM_MMAP_PAGE,
&mem_offset, sizeof(mem_offset));
&mmap_offset, sizeof(mmap_offset));
if (err)
return err;
@ -305,9 +329,10 @@ static int get_toggle_mem_obj_cleanup(struct ib_uobject *uobject,
enum rdma_remove_reason why,
struct uverbs_attr_bundle *attrs)
{
struct bnxt_re_user_mmap_entry *entry = uobject->object;
struct bnxt_re_toggle_mem *tmem = uobject->object;
rdma_user_mmap_entry_remove(&entry->rdma_entry);
rdma_user_mmap_entry_put(&tmem->toggle_entry->rdma_entry);
kfree(tmem);
return 0;
}