diff --git a/drivers/gpu/drm/i915/i915_gem_userptr.c b/drivers/gpu/drm/i915/i915_gem_userptr.c
index 5da37cc..795ea3e 100644
--- a/drivers/gpu/drm/i915/i915_gem_userptr.c
+++ b/drivers/gpu/drm/i915/i915_gem_userptr.c
@@ -224,10 +224,6 @@ i915_mmu_notifier_add(struct i915_mmu_notifier *mmu,
 	 * remove the objects from the interval tree) before we do
 	 * the check for overlapping objects.
 	 */
-	ret = i915_mutex_lock_interruptible(mmu->dev);
-	if (ret)
-		return ret;
-
 	i915_gem_retire_requests(mmu->dev);
 
 	/* Disallow overlapping userptr objects */
@@ -253,7 +249,6 @@ i915_mmu_notifier_add(struct i915_mmu_notifier *mmu,
 		ret = 0;
 	}
 	spin_unlock(&mmu->lock);
-	mutex_unlock(&mmu->dev->struct_mutex);
 
 	return ret;
 }
@@ -283,19 +278,12 @@ i915_gem_userptr_init__mmu_notifier(struct drm_i915_gem_object *obj,
 		return capable(CAP_SYS_ADMIN) ? 0 : -EPERM;
 
 	down_write(&obj->userptr.mm->mmap_sem);
-	ret = i915_mutex_lock_interruptible(obj->base.dev);
-	if (ret == 0) {
-		mmu = i915_mmu_notifier_get(obj->base.dev, obj->userptr.mm);
-		if (!IS_ERR(mmu))
-			mmu->count++; /* preemptive add to act as a refcount */
-		else
-			ret = PTR_ERR(mmu);
-		mutex_unlock(&obj->base.dev->struct_mutex);
-	}
+	mmu = i915_mmu_notifier_get(obj->base.dev, obj->userptr.mm);
+	if (!IS_ERR(mmu))
+		mmu->count++; /* preemptive add to act as a refcount */
+	else
+		ret = PTR_ERR(mmu);
 	up_write(&obj->userptr.mm->mmap_sem);
-	if (ret)
-		return ret;
-
 	mn = kzalloc(sizeof(*mn), GFP_KERNEL);
 	if (mn == NULL) {
 		ret = -ENOMEM;
@@ -588,6 +576,52 @@ i915_gem_userptr_release(struct drm_i915_gem_object *obj)
 	}
 }
 
+/* Carve out the address space for later use */
+static int i915_gem_userptr_reserve_vma(struct drm_i915_gem_object *obj,
+					struct i915_address_space *vm,
+					uint64_t offset,
+					uint64_t size)
+{
+	struct i915_vma *vma;
+	int ret;
+
+	vma = i915_gem_obj_to_vma(obj, vm);
+	if (vma)
+		return -ENXIO;
+
+	vma = i915_gem_obj_lookup_or_create_vma(obj, vm);
+	if (!vma)
+		return PTR_ERR(vma);
+
+	BUG_ON(!drm_mm_initialized(&vm->mm));
+
+	if (vma->uptr) {
+		DRM_INFO("Already had a userptr\n");
+		return 0;
+	}
+	if (vma->node.allocated) {
+		DRM_INFO("Node was previously allocated\n");
+		return -EBUSY;
+	}
+
+	vma->node.start = offset;
+	vma->node.size = size;
+	vma->node.color = 0;
+	ret = drm_mm_reserve_node(&vm->mm, &vma->node);
+	if (ret) {
+		/* There are two reasons this can fail.
+		 * 1. The user is using a mix of relocs and userptr, and a reloc
+		 * won.
+		 * TODO: handle better.
+		 */
+		return ret;
+	}
+
+	vma->uptr = 1;
+
+	return 0;
+}
+
 static const struct drm_i915_gem_object_ops i915_gem_userptr_ops = {
 	.get_pages = i915_gem_userptr_get_pages,
 	.put_pages = i915_gem_userptr_put_pages,
@@ -630,37 +664,62 @@ static const struct drm_i915_gem_object_ops i915_gem_userptr_ops = {
 int
 i915_gem_userptr_ioctl(struct drm_device *dev, void *data, struct drm_file *file)
 {
-	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct drm_i915_file_private *file_priv = file->driver_priv;
 	struct drm_i915_gem_userptr *args = data;
 	struct drm_i915_gem_object *obj;
+	struct i915_hw_context *ctx;
+	struct i915_address_space *vm;
 	int ret;
 	u32 handle;
 
+	ret = i915_mutex_lock_interruptible(dev);
+	if (ret)
+		return ret;
+
+#define goto_err(__err) do { \
+	ret = (__err); \
+	goto out; \
+} while (0)
+
+	ctx = i915_gem_context_get(file_priv, args->ctx_id);
+	if (IS_ERR(ctx))
+		goto_err(PTR_ERR(ctx));
+
+	/* i915_gem_context_reference(ctx); */
+
 	if (args->flags & ~(I915_USERPTR_READ_ONLY |
+			    I915_USERPTR_GPU_MIRROR |
 			    I915_USERPTR_UNSYNCHRONIZED))
-		return -EINVAL;
+		goto_err(-EINVAL);
 
 	if (offset_in_page(args->user_ptr | args->user_size))
-		return -EINVAL;
-
-	if (args->user_size > dev_priv->gtt.base.total)
-		return -E2BIG;
+		goto_err(-EINVAL);
 
 	if (!access_ok(args->flags & I915_USERPTR_READ_ONLY ? VERIFY_READ : VERIFY_WRITE,
 		       (char __user *)(unsigned long)args->user_ptr, args->user_size))
-		return -EFAULT;
+		goto_err(-EFAULT);
 
 	if (args->flags & I915_USERPTR_READ_ONLY) {
 		/* On almost all of the current hw, we cannot tell the GPU that a
 		 * page is readonly, so this is just a placeholder in the uAPI.
 		 */
-		return -ENODEV;
+		goto_err(-ENODEV);
+	}
+
+	vm = ctx->vm;
+	if (args->user_size > vm->total)
+		goto_err(-E2BIG);
+
+	if (args->flags & I915_USERPTR_GPU_MIRROR) {
+		if (!HAS_48B_PPGTT(dev))
+			goto_err(-ENODEV);
 	}
 
 	/* Allocate the new object */
 	obj = i915_gem_object_alloc(dev);
 	if (obj == NULL)
-		return -ENOMEM;
+		goto_err(-ENOMEM);
+#undef goto_err
 
 	drm_gem_private_object_init(dev, &obj->base, args->user_size);
 	i915_gem_object_init(obj, &i915_gem_userptr_ops);
@@ -680,9 +739,16 @@ i915_gem_userptr_ioctl(struct drm_device *dev, void *data, struct drm_file *file
 		ret = i915_gem_userptr_init__mmu_notifier(obj, args->flags);
 	if (ret == 0)
 		ret = drm_gem_handle_create(file, &obj->base, &handle);
+	if (ret == 0 && args->flags & I915_USERPTR_GPU_MIRROR) {
+		ret = i915_gem_userptr_reserve_vma(obj, vm, args->user_ptr, args->user_size);
+		if (ret)
+			DRM_DEBUG_DRIVER("Failed to reserve GPU mirror %d\n", ret);
+	}
 
 	/* drop reference from allocate - handle holds it now */
-	drm_gem_object_unreference_unlocked(&obj->base);
+	drm_gem_object_unreference(&obj->base);
+out:
+	mutex_unlock(&dev->struct_mutex);
 	if (ret)
 		return ret;
 
diff --git a/include/uapi/drm/i915_drm.h b/include/uapi/drm/i915_drm.h
index dd98463..addab22 100644
--- a/include/uapi/drm/i915_drm.h
+++ b/include/uapi/drm/i915_drm.h
@@ -1056,15 +1056,18 @@ struct drm_i915_reset_stats {
 struct drm_i915_gem_userptr {
 	__u64 user_ptr;
 	__u64 user_size;
+	__u32 ctx_id;
 	__u32 flags;
-#define I915_USERPTR_READ_ONLY 0x1
-#define I915_USERPTR_UNSYNCHRONIZED 0x80000000
+#define I915_USERPTR_READ_ONLY		(1<<0)
+#define I915_USERPTR_GPU_MIRROR		(1<<1)
+#define I915_USERPTR_UNSYNCHRONIZED	(1<<31)
 	/**
 	 * Returned handle for the object.
 	 *
 	 * Object handles are nonzero.
 	 */
 	__u32 handle;
+	__u32 pad;
 };
 
 #endif /* _UAPI_I915_DRM_H_ */
