IB/uverbs: Export ib_umem_get()/ib_umem_release() to modules

Export ib_umem_get()/ib_umem_release() and put low-level drivers in
control of when to call ib_umem_get() to pin and DMA map userspace,
rather than always calling it in ib_uverbs_reg_mr() before calling the
low-level driver's reg_user_mr method.

Also move these functions to be in the ib_core module instead of
ib_uverbs, so that driver modules using them do not depend on
ib_uverbs.

This has a number of advantages:
 - It is better design from the standpoint of making generic code a
   library that can be used or overridden by device-specific code as
   the details of specific devices dictate.
 - Drivers that do not need to pin userspace memory regions do not
   need to take the performance hit of calling ib_mem_get().  For
   example, although I have not tried to implement it in this patch,
   the ipath driver should be able to avoid pinning memory and just
   use copy_{to,from}_user() to access userspace memory regions.
 - Buffers that need special mapping treatment can be identified by
   the low-level driver.  For example, it may be possible to solve
   some Altix-specific memory ordering issues with mthca CQs in
   userspace by mapping CQ buffers with extra flags.
 - Drivers that need to pin and DMA map userspace memory for things
   other than memory regions can use ib_umem_get() directly, instead
   of hacks using extra parameters to their reg_phys_mr method.  For
   example, the mlx4 driver that is pending being merged needs to pin
   and DMA map QP and CQ buffers, but it does not need to create a
   memory key for these buffers.  So the cleanest solution is for mlx4
   to call ib_umem_get() in the create_qp and create_cq methods.

Signed-off-by: Roland Dreier <rolandd@cisco.com>
diff --git a/drivers/infiniband/hw/amso1100/c2_provider.c b/drivers/infiniband/hw/amso1100/c2_provider.c
index 1091662..997cf15 100644
--- a/drivers/infiniband/hw/amso1100/c2_provider.c
+++ b/drivers/infiniband/hw/amso1100/c2_provider.c
@@ -56,6 +56,7 @@
 #include <asm/byteorder.h>
 
 #include <rdma/ib_smi.h>
+#include <rdma/ib_umem.h>
 #include <rdma/ib_user_verbs.h>
 #include "c2.h"
 #include "c2_provider.h"
@@ -396,6 +397,7 @@
 	}
 
 	mr->pd = to_c2pd(ib_pd);
+	mr->umem = NULL;
 	pr_debug("%s - page shift %d, pbl_depth %d, total_len %u, "
 		"*iova_start %llx, first pa %llx, last pa %llx\n",
 		__FUNCTION__, page_shift, pbl_depth, total_len,
@@ -428,8 +430,8 @@
 	return c2_reg_phys_mr(pd, &bl, 1, acc, &kva);
 }
 
-static struct ib_mr *c2_reg_user_mr(struct ib_pd *pd, struct ib_umem *region,
-				    int acc, struct ib_udata *udata)
+static struct ib_mr *c2_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				    u64 virt, int acc, struct ib_udata *udata)
 {
 	u64 *pages;
 	u64 kva = 0;
@@ -441,15 +443,23 @@
 	struct c2_mr *c2mr;
 
 	pr_debug("%s:%u\n", __FUNCTION__, __LINE__);
-	shift = ffs(region->page_size) - 1;
 
 	c2mr = kmalloc(sizeof(*c2mr), GFP_KERNEL);
 	if (!c2mr)
 		return ERR_PTR(-ENOMEM);
 	c2mr->pd = c2pd;
 
+	c2mr->umem = ib_umem_get(pd->uobject->context, start, length, acc);
+	if (IS_ERR(c2mr->umem)) {
+		err = PTR_ERR(c2mr->umem);
+		kfree(c2mr);
+		return ERR_PTR(err);
+	}
+
+	shift = ffs(c2mr->umem->page_size) - 1;
+
 	n = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &c2mr->umem->chunk_list, list)
 		n += chunk->nents;
 
 	pages = kmalloc(n * sizeof(u64), GFP_KERNEL);
@@ -459,35 +469,34 @@
 	}
 
 	i = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list) {
+	list_for_each_entry(chunk, &c2mr->umem->chunk_list, list) {
 		for (j = 0; j < chunk->nmap; ++j) {
 			len = sg_dma_len(&chunk->page_list[j]) >> shift;
 			for (k = 0; k < len; ++k) {
 				pages[i++] =
 					sg_dma_address(&chunk->page_list[j]) +
-					(region->page_size * k);
+					(c2mr->umem->page_size * k);
 			}
 		}
 	}
 
-	kva = (u64)region->virt_base;
+	kva = virt;
   	err = c2_nsmr_register_phys_kern(to_c2dev(pd->device),
 					 pages,
- 					 region->page_size,
+					 c2mr->umem->page_size,
 					 i,
-					 region->length,
-					 region->offset,
+					 length,
+					 c2mr->umem->offset,
 					 &kva,
 					 c2_convert_access(acc),
 					 c2mr);
 	kfree(pages);
-	if (err) {
-		kfree(c2mr);
-		return ERR_PTR(err);
-	}
+	if (err)
+		goto err;
 	return &c2mr->ibmr;
 
 err:
+	ib_umem_release(c2mr->umem);
 	kfree(c2mr);
 	return ERR_PTR(err);
 }
@@ -502,8 +511,11 @@
 	err = c2_stag_dealloc(to_c2dev(ib_mr->device), ib_mr->lkey);
 	if (err)
 		pr_debug("c2_stag_dealloc failed: %d\n", err);
-	else
+	else {
+		if (mr->umem)
+			ib_umem_release(mr->umem);
 		kfree(mr);
+	}
 
 	return err;
 }
diff --git a/drivers/infiniband/hw/amso1100/c2_provider.h b/drivers/infiniband/hw/amso1100/c2_provider.h
index fc90622..1076df2 100644
--- a/drivers/infiniband/hw/amso1100/c2_provider.h
+++ b/drivers/infiniband/hw/amso1100/c2_provider.h
@@ -73,6 +73,7 @@
 struct c2_mr {
 	struct ib_mr ibmr;
 	struct c2_pd *pd;
+	struct ib_umem *umem;
 };
 
 struct c2_av;
diff --git a/drivers/infiniband/hw/cxgb3/iwch_provider.c b/drivers/infiniband/hw/cxgb3/iwch_provider.c
index a891493..e7c2c39 100644
--- a/drivers/infiniband/hw/cxgb3/iwch_provider.c
+++ b/drivers/infiniband/hw/cxgb3/iwch_provider.c
@@ -47,6 +47,7 @@
 #include <rdma/iw_cm.h>
 #include <rdma/ib_verbs.h>
 #include <rdma/ib_smi.h>
+#include <rdma/ib_umem.h>
 #include <rdma/ib_user_verbs.h>
 
 #include "cxio_hal.h"
@@ -443,6 +444,8 @@
 	remove_handle(rhp, &rhp->mmidr, mmid);
 	if (mhp->kva)
 		kfree((void *) (unsigned long) mhp->kva);
+	if (mhp->umem)
+		ib_umem_release(mhp->umem);
 	PDBG("%s mmid 0x%x ptr %p\n", __FUNCTION__, mmid, mhp);
 	kfree(mhp);
 	return 0;
@@ -577,8 +580,8 @@
 }
 
 
-static struct ib_mr *iwch_reg_user_mr(struct ib_pd *pd, struct ib_umem *region,
-				      int acc, struct ib_udata *udata)
+static struct ib_mr *iwch_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				      u64 virt, int acc, struct ib_udata *udata)
 {
 	__be64 *pages;
 	int shift, n, len;
@@ -591,7 +594,6 @@
 	struct iwch_reg_user_mr_resp uresp;
 
 	PDBG("%s ib_pd %p\n", __FUNCTION__, pd);
-	shift = ffs(region->page_size) - 1;
 
 	php = to_iwch_pd(pd);
 	rhp = php->rhp;
@@ -599,8 +601,17 @@
 	if (!mhp)
 		return ERR_PTR(-ENOMEM);
 
+	mhp->umem = ib_umem_get(pd->uobject->context, start, length, acc);
+	if (IS_ERR(mhp->umem)) {
+		err = PTR_ERR(mhp->umem);
+		kfree(mhp);
+		return ERR_PTR(err);
+	}
+
+	shift = ffs(mhp->umem->page_size) - 1;
+
 	n = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &mhp->umem->chunk_list, list)
 		n += chunk->nents;
 
 	pages = kmalloc(n * sizeof(u64), GFP_KERNEL);
@@ -611,13 +622,13 @@
 
 	i = n = 0;
 
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &mhp->umem->chunk_list, list)
 		for (j = 0; j < chunk->nmap; ++j) {
 			len = sg_dma_len(&chunk->page_list[j]) >> shift;
 			for (k = 0; k < len; ++k) {
 				pages[i++] = cpu_to_be64(sg_dma_address(
 					&chunk->page_list[j]) +
-					region->page_size * k);
+					mhp->umem->page_size * k);
 			}
 		}
 
@@ -625,9 +636,9 @@
 	mhp->attr.pdid = php->pdid;
 	mhp->attr.zbva = 0;
 	mhp->attr.perms = iwch_ib_to_tpt_access(acc);
-	mhp->attr.va_fbo = region->virt_base;
+	mhp->attr.va_fbo = virt;
 	mhp->attr.page_size = shift - 12;
-	mhp->attr.len = (u32) region->length;
+	mhp->attr.len = (u32) length;
 	mhp->attr.pbl_size = i;
 	err = iwch_register_mem(rhp, php, mhp, shift, pages);
 	kfree(pages);
@@ -650,6 +661,7 @@
 	return &mhp->ibmr;
 
 err:
+	ib_umem_release(mhp->umem);
 	kfree(mhp);
 	return ERR_PTR(err);
 }
diff --git a/drivers/infiniband/hw/cxgb3/iwch_provider.h b/drivers/infiniband/hw/cxgb3/iwch_provider.h
index 93bcc56..48833f3 100644
--- a/drivers/infiniband/hw/cxgb3/iwch_provider.h
+++ b/drivers/infiniband/hw/cxgb3/iwch_provider.h
@@ -73,6 +73,7 @@
 
 struct iwch_mr {
 	struct ib_mr ibmr;
+	struct ib_umem *umem;
 	struct iwch_dev *rhp;
 	u64 kva;
 	struct tpt_attributes attr;
diff --git a/drivers/infiniband/hw/ehca/ehca_classes.h b/drivers/infiniband/hw/ehca/ehca_classes.h
index 10fb8fb..f64d42b 100644
--- a/drivers/infiniband/hw/ehca/ehca_classes.h
+++ b/drivers/infiniband/hw/ehca/ehca_classes.h
@@ -176,6 +176,7 @@
 		struct ib_mr ib_mr;	/* must always be first in ehca_mr */
 		struct ib_fmr ib_fmr;	/* must always be first in ehca_mr */
 	} ib;
+	struct ib_umem *umem;
 	spinlock_t mrlock;
 
 	enum ehca_mr_flag flags;
diff --git a/drivers/infiniband/hw/ehca/ehca_iverbs.h b/drivers/infiniband/hw/ehca/ehca_iverbs.h
index e14b029..37e7fe0 100644
--- a/drivers/infiniband/hw/ehca/ehca_iverbs.h
+++ b/drivers/infiniband/hw/ehca/ehca_iverbs.h
@@ -78,8 +78,7 @@
 			       int num_phys_buf,
 			       int mr_access_flags, u64 *iova_start);
 
-struct ib_mr *ehca_reg_user_mr(struct ib_pd *pd,
-			       struct ib_umem *region,
+struct ib_mr *ehca_reg_user_mr(struct ib_pd *pd, u64 start, u64 length, u64 virt,
 			       int mr_access_flags, struct ib_udata *udata);
 
 int ehca_rereg_phys_mr(struct ib_mr *mr,
diff --git a/drivers/infiniband/hw/ehca/ehca_mrmw.c b/drivers/infiniband/hw/ehca/ehca_mrmw.c
index d22ab56..84c5bb4 100644
--- a/drivers/infiniband/hw/ehca/ehca_mrmw.c
+++ b/drivers/infiniband/hw/ehca/ehca_mrmw.c
@@ -39,6 +39,8 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <rdma/ib_umem.h>
+
 #include <asm/current.h>
 
 #include "ehca_iverbs.h"
@@ -238,10 +240,8 @@
 
 /*----------------------------------------------------------------------*/
 
-struct ib_mr *ehca_reg_user_mr(struct ib_pd *pd,
-			       struct ib_umem *region,
-			       int mr_access_flags,
-			       struct ib_udata *udata)
+struct ib_mr *ehca_reg_user_mr(struct ib_pd *pd, u64 start, u64 length, u64 virt,
+			       int mr_access_flags, struct ib_udata *udata)
 {
 	struct ib_mr *ib_mr;
 	struct ehca_mr *e_mr;
@@ -257,11 +257,7 @@
 		ehca_gen_err("bad pd=%p", pd);
 		return ERR_PTR(-EFAULT);
 	}
-	if (!region) {
-		ehca_err(pd->device, "bad input values: region=%p", region);
-		ib_mr = ERR_PTR(-EINVAL);
-		goto reg_user_mr_exit0;
-	}
+
 	if (((mr_access_flags & IB_ACCESS_REMOTE_WRITE) &&
 	     !(mr_access_flags & IB_ACCESS_LOCAL_WRITE)) ||
 	    ((mr_access_flags & IB_ACCESS_REMOTE_ATOMIC) &&
@@ -275,17 +271,10 @@
 		ib_mr = ERR_PTR(-EINVAL);
 		goto reg_user_mr_exit0;
 	}
-	if (region->page_size != PAGE_SIZE) {
-		ehca_err(pd->device, "page size not supported, "
-			 "region->page_size=%x", region->page_size);
-		ib_mr = ERR_PTR(-EINVAL);
-		goto reg_user_mr_exit0;
-	}
 
-	if ((region->length == 0) ||
-	    ((region->virt_base + region->length) < region->virt_base)) {
+	if (length == 0 || virt + length < virt) {
 		ehca_err(pd->device, "bad input values: length=%lx "
-			 "virt_base=%lx", region->length, region->virt_base);
+			 "virt_base=%lx", length, virt);
 		ib_mr = ERR_PTR(-EINVAL);
 		goto reg_user_mr_exit0;
 	}
@@ -297,40 +286,55 @@
 		goto reg_user_mr_exit0;
 	}
 
+	e_mr->umem = ib_umem_get(pd->uobject->context, start, length,
+				 mr_access_flags);
+	if (IS_ERR(e_mr->umem)) {
+		ib_mr = (void *) e_mr->umem;
+		goto reg_user_mr_exit1;
+	}
+
+	if (e_mr->umem->page_size != PAGE_SIZE) {
+		ehca_err(pd->device, "page size not supported, "
+			 "e_mr->umem->page_size=%x", e_mr->umem->page_size);
+		ib_mr = ERR_PTR(-EINVAL);
+		goto reg_user_mr_exit2;
+	}
+
 	/* determine number of MR pages */
-	num_pages_mr = (((region->virt_base % PAGE_SIZE) + region->length +
-			 PAGE_SIZE - 1) / PAGE_SIZE);
-	num_pages_4k = (((region->virt_base % EHCA_PAGESIZE) + region->length +
-			 EHCA_PAGESIZE - 1) / EHCA_PAGESIZE);
+	num_pages_mr = (((virt % PAGE_SIZE) + length + PAGE_SIZE - 1) /
+			PAGE_SIZE);
+	num_pages_4k = (((virt % EHCA_PAGESIZE) + length + EHCA_PAGESIZE - 1) /
+			EHCA_PAGESIZE);
 
 	/* register MR on HCA */
 	pginfo.type       = EHCA_MR_PGI_USER;
 	pginfo.num_pages  = num_pages_mr;
 	pginfo.num_4k     = num_pages_4k;
-	pginfo.region     = region;
-	pginfo.next_4k	  = region->offset / EHCA_PAGESIZE;
+	pginfo.region     = e_mr->umem;
+	pginfo.next_4k	  = e_mr->umem->offset / EHCA_PAGESIZE;
 	pginfo.next_chunk = list_prepare_entry(pginfo.next_chunk,
-					       (&region->chunk_list),
+					       (&e_mr->umem->chunk_list),
 					       list);
 
-	ret = ehca_reg_mr(shca, e_mr, (u64*)region->virt_base,
-			  region->length, mr_access_flags, e_pd, &pginfo,
-			  &e_mr->ib.ib_mr.lkey, &e_mr->ib.ib_mr.rkey);
+	ret = ehca_reg_mr(shca, e_mr, (u64*) virt, length, mr_access_flags, e_pd,
+			  &pginfo, &e_mr->ib.ib_mr.lkey, &e_mr->ib.ib_mr.rkey);
 	if (ret) {
 		ib_mr = ERR_PTR(ret);
-		goto reg_user_mr_exit1;
+		goto reg_user_mr_exit2;
 	}
 
 	/* successful registration of all pages */
 	return &e_mr->ib.ib_mr;
 
+reg_user_mr_exit2:
+	ib_umem_release(e_mr->umem);
 reg_user_mr_exit1:
 	ehca_mr_delete(e_mr);
 reg_user_mr_exit0:
 	if (IS_ERR(ib_mr))
-		ehca_err(pd->device, "rc=%lx pd=%p region=%p mr_access_flags=%x"
+		ehca_err(pd->device, "rc=%lx pd=%p mr_access_flags=%x"
 			 " udata=%p",
-			 PTR_ERR(ib_mr), pd, region, mr_access_flags, udata);
+			 PTR_ERR(ib_mr), pd, mr_access_flags, udata);
 	return ib_mr;
 } /* end ehca_reg_user_mr() */
 
@@ -596,6 +600,9 @@
 		goto dereg_mr_exit0;
 	}
 
+	if (e_mr->umem)
+		ib_umem_release(e_mr->umem);
+
 	/* successful deregistration */
 	ehca_mr_delete(e_mr);
 
diff --git a/drivers/infiniband/hw/ipath/ipath_mr.c b/drivers/infiniband/hw/ipath/ipath_mr.c
index 31e7073..bdeef8d 100644
--- a/drivers/infiniband/hw/ipath/ipath_mr.c
+++ b/drivers/infiniband/hw/ipath/ipath_mr.c
@@ -31,6 +31,7 @@
  * SOFTWARE.
  */
 
+#include <rdma/ib_umem.h>
 #include <rdma/ib_pack.h>
 #include <rdma/ib_smi.h>
 
@@ -147,6 +148,7 @@
 	mr->mr.offset = 0;
 	mr->mr.access_flags = acc;
 	mr->mr.max_segs = num_phys_buf;
+	mr->umem = NULL;
 
 	m = 0;
 	n = 0;
@@ -170,46 +172,56 @@
 /**
  * ipath_reg_user_mr - register a userspace memory region
  * @pd: protection domain for this memory region
- * @region: the user memory region
+ * @start: starting userspace address
+ * @length: length of region to register
+ * @virt_addr: virtual address to use (from HCA's point of view)
  * @mr_access_flags: access flags for this memory region
  * @udata: unused by the InfiniPath driver
  *
  * Returns the memory region on success, otherwise returns an errno.
  */
-struct ib_mr *ipath_reg_user_mr(struct ib_pd *pd, struct ib_umem *region,
-				int mr_access_flags, struct ib_udata *udata)
+struct ib_mr *ipath_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				u64 virt_addr, int mr_access_flags,
+				struct ib_udata *udata)
 {
 	struct ipath_mr *mr;
+	struct ib_umem *umem;
 	struct ib_umem_chunk *chunk;
 	int n, m, i;
 	struct ib_mr *ret;
 
-	if (region->length == 0) {
+	if (length == 0) {
 		ret = ERR_PTR(-EINVAL);
 		goto bail;
 	}
 
+	umem = ib_umem_get(pd->uobject->context, start, length, mr_access_flags);
+	if (IS_ERR(umem))
+		return (void *) umem;
+
 	n = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &umem->chunk_list, list)
 		n += chunk->nents;
 
 	mr = alloc_mr(n, &to_idev(pd->device)->lk_table);
 	if (!mr) {
 		ret = ERR_PTR(-ENOMEM);
+		ib_umem_release(umem);
 		goto bail;
 	}
 
 	mr->mr.pd = pd;
-	mr->mr.user_base = region->user_base;
-	mr->mr.iova = region->virt_base;
-	mr->mr.length = region->length;
-	mr->mr.offset = region->offset;
+	mr->mr.user_base = start;
+	mr->mr.iova = virt_addr;
+	mr->mr.length = length;
+	mr->mr.offset = umem->offset;
 	mr->mr.access_flags = mr_access_flags;
 	mr->mr.max_segs = n;
+	mr->umem = umem;
 
 	m = 0;
 	n = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list) {
+	list_for_each_entry(chunk, &umem->chunk_list, list) {
 		for (i = 0; i < chunk->nents; i++) {
 			void *vaddr;
 
@@ -219,7 +231,7 @@
 				goto bail;
 			}
 			mr->mr.map[m]->segs[n].vaddr = vaddr;
-			mr->mr.map[m]->segs[n].length = region->page_size;
+			mr->mr.map[m]->segs[n].length = umem->page_size;
 			n++;
 			if (n == IPATH_SEGSZ) {
 				m++;
@@ -253,6 +265,10 @@
 		i--;
 		kfree(mr->mr.map[i]);
 	}
+
+	if (mr->umem)
+		ib_umem_release(mr->umem);
+
 	kfree(mr);
 	return 0;
 }
diff --git a/drivers/infiniband/hw/ipath/ipath_verbs.h b/drivers/infiniband/hw/ipath/ipath_verbs.h
index 7064fc2..088b837 100644
--- a/drivers/infiniband/hw/ipath/ipath_verbs.h
+++ b/drivers/infiniband/hw/ipath/ipath_verbs.h
@@ -251,6 +251,7 @@
 /* Memory region */
 struct ipath_mr {
 	struct ib_mr ibmr;
+	struct ib_umem *umem;
 	struct ipath_mregion mr;	/* must be last */
 };
 
@@ -751,8 +752,8 @@
 				struct ib_phys_buf *buffer_list,
 				int num_phys_buf, int acc, u64 *iova_start);
 
-struct ib_mr *ipath_reg_user_mr(struct ib_pd *pd, struct ib_umem *region,
-				int mr_access_flags,
+struct ib_mr *ipath_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				u64 virt_addr, int mr_access_flags,
 				struct ib_udata *udata);
 
 int ipath_dereg_mr(struct ib_mr *ibmr);
diff --git a/drivers/infiniband/hw/mthca/mthca_provider.c b/drivers/infiniband/hw/mthca/mthca_provider.c
index 1c05486..6bcde1c 100644
--- a/drivers/infiniband/hw/mthca/mthca_provider.c
+++ b/drivers/infiniband/hw/mthca/mthca_provider.c
@@ -37,6 +37,7 @@
  */
 
 #include <rdma/ib_smi.h>
+#include <rdma/ib_umem.h>
 #include <rdma/ib_user_verbs.h>
 #include <linux/mm.h>
 
@@ -908,6 +909,8 @@
 		return ERR_PTR(err);
 	}
 
+	mr->umem = NULL;
+
 	return &mr->ibmr;
 }
 
@@ -1003,11 +1006,13 @@
 	}
 
 	kfree(page_list);
+	mr->umem = NULL;
+
 	return &mr->ibmr;
 }
 
-static struct ib_mr *mthca_reg_user_mr(struct ib_pd *pd, struct ib_umem *region,
-				       int acc, struct ib_udata *udata)
+static struct ib_mr *mthca_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
+				       u64 virt, int acc, struct ib_udata *udata)
 {
 	struct mthca_dev *dev = to_mdev(pd->device);
 	struct ib_umem_chunk *chunk;
@@ -1018,20 +1023,26 @@
 	int err = 0;
 	int write_mtt_size;
 
-	shift = ffs(region->page_size) - 1;
-
 	mr = kmalloc(sizeof *mr, GFP_KERNEL);
 	if (!mr)
 		return ERR_PTR(-ENOMEM);
 
+	mr->umem = ib_umem_get(pd->uobject->context, start, length, acc);
+	if (IS_ERR(mr->umem)) {
+		err = PTR_ERR(mr->umem);
+		goto err;
+	}
+
+	shift = ffs(mr->umem->page_size) - 1;
+
 	n = 0;
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &mr->umem->chunk_list, list)
 		n += chunk->nents;
 
 	mr->mtt = mthca_alloc_mtt(dev, n);
 	if (IS_ERR(mr->mtt)) {
 		err = PTR_ERR(mr->mtt);
-		goto err;
+		goto err_umem;
 	}
 
 	pages = (u64 *) __get_free_page(GFP_KERNEL);
@@ -1044,12 +1055,12 @@
 
 	write_mtt_size = min(mthca_write_mtt_size(dev), (int) (PAGE_SIZE / sizeof *pages));
 
-	list_for_each_entry(chunk, &region->chunk_list, list)
+	list_for_each_entry(chunk, &mr->umem->chunk_list, list)
 		for (j = 0; j < chunk->nmap; ++j) {
 			len = sg_dma_len(&chunk->page_list[j]) >> shift;
 			for (k = 0; k < len; ++k) {
 				pages[i++] = sg_dma_address(&chunk->page_list[j]) +
-					region->page_size * k;
+					mr->umem->page_size * k;
 				/*
 				 * Be friendly to write_mtt and pass it chunks
 				 * of appropriate size.
@@ -1071,8 +1082,8 @@
 	if (err)
 		goto err_mtt;
 
-	err = mthca_mr_alloc(dev, to_mpd(pd)->pd_num, shift, region->virt_base,
-			     region->length, convert_access(acc), mr);
+	err = mthca_mr_alloc(dev, to_mpd(pd)->pd_num, shift, virt, length,
+			     convert_access(acc), mr);
 
 	if (err)
 		goto err_mtt;
@@ -1082,6 +1093,9 @@
 err_mtt:
 	mthca_free_mtt(dev, mr->mtt);
 
+err_umem:
+	ib_umem_release(mr->umem);
+
 err:
 	kfree(mr);
 	return ERR_PTR(err);
@@ -1090,8 +1104,12 @@
 static int mthca_dereg_mr(struct ib_mr *mr)
 {
 	struct mthca_mr *mmr = to_mmr(mr);
+
 	mthca_free_mr(to_mdev(mr->device), mmr);
+	if (mmr->umem)
+		ib_umem_release(mmr->umem);
 	kfree(mmr);
+
 	return 0;
 }
 
diff --git a/drivers/infiniband/hw/mthca/mthca_provider.h b/drivers/infiniband/hw/mthca/mthca_provider.h
index 1d266ac..262616c 100644
--- a/drivers/infiniband/hw/mthca/mthca_provider.h
+++ b/drivers/infiniband/hw/mthca/mthca_provider.h
@@ -73,6 +73,7 @@
 
 struct mthca_mr {
 	struct ib_mr      ibmr;
+	struct ib_umem   *umem;
 	struct mthca_mtt *mtt;
 };