IB/hfi1: Remove user expected buffer invalidate race

During setup, there is a possible race between a page invalidate
and hardware programming.  Add a covering invalidate over the user
target range during setup.  If anything within that range is
invalidated during setup, fail the setup.  Once set up, each
TID will have its own invalidate callback and invalidate.

Fixes: 3889551db2 ("RDMA/hfi1: Use mmu_interval_notifier_insert for user_exp_rcv")
Signed-off-by: Dean Luick <dean.luick@cornelisnetworks.com>
Signed-off-by: Dennis Dalessandro <dennis.dalessandro@cornelisnetworks.com>
Link: https://lore.kernel.org/r/167328549178.1472310.9867497376936699488.stgit@awfm-02.cornelisnetworks.com
Signed-off-by: Leon Romanovsky <leon@kernel.org>
This commit is contained in:
Dean Luick 2023-01-09 12:31:31 -05:00 committed by Leon Romanovsky
parent 1c7edde1b5
commit b3deec2584
2 changed files with 55 additions and 5 deletions

View File

@ -23,6 +23,9 @@ static void cacheless_tid_rb_remove(struct hfi1_filedata *fdata,
static bool tid_rb_invalidate(struct mmu_interval_notifier *mni,
const struct mmu_notifier_range *range,
unsigned long cur_seq);
static bool tid_cover_invalidate(struct mmu_interval_notifier *mni,
const struct mmu_notifier_range *range,
unsigned long cur_seq);
static int program_rcvarray(struct hfi1_filedata *fd, struct tid_user_buf *,
struct tid_group *grp,
unsigned int start, u16 count,
@ -36,6 +39,9 @@ static void clear_tid_node(struct hfi1_filedata *fd, struct tid_rb_node *node);
static const struct mmu_interval_notifier_ops tid_mn_ops = {
.invalidate = tid_rb_invalidate,
};
static const struct mmu_interval_notifier_ops tid_cover_ops = {
.invalidate = tid_cover_invalidate,
};
/*
* Initialize context and file private data needed for Expected
@ -254,6 +260,7 @@ int hfi1_user_exp_rcv_setup(struct hfi1_filedata *fd,
tididx = 0, mapped, mapped_pages = 0;
u32 *tidlist = NULL;
struct tid_user_buf *tidbuf;
unsigned long mmu_seq = 0;
if (!PAGE_ALIGNED(tinfo->vaddr))
return -EINVAL;
@ -264,6 +271,7 @@ int hfi1_user_exp_rcv_setup(struct hfi1_filedata *fd,
if (!tidbuf)
return -ENOMEM;
mutex_init(&tidbuf->cover_mutex);
tidbuf->vaddr = tinfo->vaddr;
tidbuf->length = tinfo->length;
tidbuf->psets = kcalloc(uctxt->expected_count, sizeof(*tidbuf->psets),
@ -273,6 +281,16 @@ int hfi1_user_exp_rcv_setup(struct hfi1_filedata *fd,
goto fail_release_mem;
}
if (fd->use_mn) {
ret = mmu_interval_notifier_insert(
&tidbuf->notifier, current->mm,
tidbuf->vaddr, tidbuf->npages * PAGE_SIZE,
&tid_cover_ops);
if (ret)
goto fail_release_mem;
mmu_seq = mmu_interval_read_begin(&tidbuf->notifier);
}
pinned = pin_rcv_pages(fd, tidbuf);
if (pinned <= 0) {
ret = (pinned < 0) ? pinned : -ENOSPC;
@ -415,6 +433,20 @@ unlock:
unpin_rcv_pages(fd, tidbuf, NULL, mapped_pages, pinned - mapped_pages,
false);
if (fd->use_mn) {
/* check for an invalidate during setup */
bool fail = false;
mutex_lock(&tidbuf->cover_mutex);
fail = mmu_interval_read_retry(&tidbuf->notifier, mmu_seq);
mutex_unlock(&tidbuf->cover_mutex);
if (fail) {
ret = -EBUSY;
goto fail_unprogram;
}
}
tinfo->tidcnt = tididx;
tinfo->length = mapped_pages * PAGE_SIZE;
@ -424,6 +456,8 @@ unlock:
goto fail_unprogram;
}
if (fd->use_mn)
mmu_interval_notifier_remove(&tidbuf->notifier);
kfree(tidbuf->pages);
kfree(tidbuf->psets);
kfree(tidbuf);
@ -442,6 +476,8 @@ fail_unreserve:
fd->tid_used -= pageset_count;
spin_unlock(&fd->tid_lock);
fail_unpin:
if (fd->use_mn)
mmu_interval_notifier_remove(&tidbuf->notifier);
if (pinned > 0)
unpin_rcv_pages(fd, tidbuf, NULL, 0, pinned, false);
fail_release_mem:
@ -740,11 +776,6 @@ static int set_rcvarray_entry(struct hfi1_filedata *fd,
&tid_mn_ops);
if (ret)
goto out_unmap;
/*
* FIXME: This is in the wrong order, the notifier should be
* established before the pages are pinned by pin_rcv_pages.
*/
mmu_interval_read_begin(&node->notifier);
}
fd->entry_to_rb[node->rcventry - uctxt->expected_base] = node;
@ -919,6 +950,23 @@ static bool tid_rb_invalidate(struct mmu_interval_notifier *mni,
return true;
}
static bool tid_cover_invalidate(struct mmu_interval_notifier *mni,
const struct mmu_notifier_range *range,
unsigned long cur_seq)
{
struct tid_user_buf *tidbuf =
container_of(mni, struct tid_user_buf, notifier);
/* take action only if unmapping */
if (range->event == MMU_NOTIFY_UNMAP) {
mutex_lock(&tidbuf->cover_mutex);
mmu_interval_set_seq(mni, cur_seq);
mutex_unlock(&tidbuf->cover_mutex);
}
return true;
}
static void cacheless_tid_rb_remove(struct hfi1_filedata *fdata,
struct tid_rb_node *tnode)
{

View File

@ -16,6 +16,8 @@ struct tid_pageset {
};
struct tid_user_buf {
struct mmu_interval_notifier notifier;
struct mutex cover_mutex;
unsigned long vaddr;
unsigned long length;
unsigned int npages;