diff mbox series

[04/22] drm/i915/region: support continuous allocations

Message ID 20190927173409.31175-5-matthew.auld@intel.com (mailing list archive)
State New, archived
Headers show
Series LMEM basics | expand

Commit Message

Matthew Auld Sept. 27, 2019, 5:33 p.m. UTC
Some kernel internal objects may need to be allocated as a continuous
block, also thinking ahead the various kernel io_mapping interfaces seem
to expect it, although this is purely a limitation in the kernel
API...so perhaps something to be improved.

Signed-off-by: Matthew Auld <matthew.auld@intel.com>
Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
Cc: Abdiel Janulgue <abdiel.janulgue@linux.intel.com>
---
 .../gpu/drm/i915/gem/i915_gem_object_types.h  |   4 +
 drivers/gpu/drm/i915/gem/i915_gem_region.c    |  15 +-
 drivers/gpu/drm/i915/gem/i915_gem_region.h    |   3 +-
 .../gpu/drm/i915/gem/selftests/huge_pages.c   |   3 +-
 drivers/gpu/drm/i915/intel_memory_region.c    |  13 +-
 drivers/gpu/drm/i915/intel_memory_region.h    |   3 +-
 .../drm/i915/selftests/intel_memory_region.c  | 163 ++++++++++++++++++
 drivers/gpu/drm/i915/selftests/mock_region.c  |   2 +-
 8 files changed, 197 insertions(+), 9 deletions(-)

Comments

Chris Wilson Sept. 27, 2019, 6:35 p.m. UTC | #1
Quoting Matthew Auld (2019-09-27 18:33:51)
>  struct drm_i915_gem_object *
> diff --git a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
> index 4e1805aaeb99..f9fbf2865782 100644
> --- a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
> +++ b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
> @@ -471,7 +471,8 @@ static int igt_mock_memory_region_huge_pages(void *arg)
>                 unsigned int page_size = BIT(bit);
>                 resource_size_t phys;
>  
> -               obj = i915_gem_object_create_region(mem, page_size, 0);
> +               obj = i915_gem_object_create_region(mem, page_size,
> +                                                   I915_BO_ALLOC_CONTIGUOUS);

Seems a good opportunity to test both?

>                 if (IS_ERR(obj)) {
>                         err = PTR_ERR(obj);
>                         goto out_destroy_device;
> diff --git a/drivers/gpu/drm/i915/intel_memory_region.c b/drivers/gpu/drm/i915/intel_memory_region.c
> index e48d5c37c4df..7a66872d9eac 100644
> --- a/drivers/gpu/drm/i915/intel_memory_region.c
> +++ b/drivers/gpu/drm/i915/intel_memory_region.c
> @@ -47,8 +47,8 @@ __intel_memory_region_get_pages_buddy(struct intel_memory_region *mem,
>                                       unsigned int flags,
>                                       struct list_head *blocks)
>  {
> -       unsigned long n_pages = size >> ilog2(mem->mm.chunk_size);
>         unsigned int min_order = 0;
> +       unsigned long n_pages;
>  
>         GEM_BUG_ON(!IS_ALIGNED(size, mem->mm.chunk_size));
>         GEM_BUG_ON(!list_empty(blocks));
> @@ -58,6 +58,13 @@ __intel_memory_region_get_pages_buddy(struct intel_memory_region *mem,
>                             ilog2(mem->mm.chunk_size);
>         }
>  
> +       if (flags & I915_ALLOC_CONTIGUOUS) {
> +               size = roundup_pow_of_two(size);
> +               min_order = ilog2(size) - ilog2(mem->mm.chunk_size);
> +       }
> +
> +       n_pages = size >> ilog2(mem->mm.chunk_size);
> +
>         mutex_lock(&mem->mm_lock);
>  
>         do {
> @@ -104,7 +111,9 @@ __intel_memory_region_get_block_buddy(struct intel_memory_region *mem,
>         int ret;
>  
>         INIT_LIST_HEAD(&blocks);
> -       ret = __intel_memory_region_get_pages_buddy(mem, size, 0, &blocks);
> +       ret = __intel_memory_region_get_pages_buddy(mem, size,
> +                                                   I915_ALLOC_CONTIGUOUS,
> +                                                   &blocks);

This chunk looks odd. Quick explanation why we don't pass flags here?

>         if (ret)
>                 return ERR_PTR(ret);
Ruhl, Michael J Sept. 27, 2019, 6:46 p.m. UTC | #2
>-----Original Message-----
>From: Intel-gfx [mailto:intel-gfx-bounces@lists.freedesktop.org] On Behalf Of
>Matthew Auld
>Sent: Friday, September 27, 2019 1:34 PM
>To: intel-gfx@lists.freedesktop.org
>Cc: daniel.vetter@ffwll.ch
>Subject: [Intel-gfx] [PATCH 04/22] drm/i915/region: support continuous
>allocations
>
>Some kernel internal objects may need to be allocated as a continuous

Nit:

You refer to the "continuous block", but the then you create the "CONTIGUOUS"
allocations.

s/continuous/contiguous?

Mike

>block, also thinking ahead the various kernel io_mapping interfaces seem
>to expect it, although this is purely a limitation in the kernel
>API...so perhaps something to be improved.
>
>Signed-off-by: Matthew Auld <matthew.auld@intel.com>
>Cc: Joonas Lahtinen <joonas.lahtinen@linux.intel.com>
>Cc: Abdiel Janulgue <abdiel.janulgue@linux.intel.com>
>---
> .../gpu/drm/i915/gem/i915_gem_object_types.h  |   4 +
> drivers/gpu/drm/i915/gem/i915_gem_region.c    |  15 +-
> drivers/gpu/drm/i915/gem/i915_gem_region.h    |   3 +-
> .../gpu/drm/i915/gem/selftests/huge_pages.c   |   3 +-
> drivers/gpu/drm/i915/intel_memory_region.c    |  13 +-
> drivers/gpu/drm/i915/intel_memory_region.h    |   3 +-
> .../drm/i915/selftests/intel_memory_region.c  | 163 ++++++++++++++++++
> drivers/gpu/drm/i915/selftests/mock_region.c  |   2 +-
> 8 files changed, 197 insertions(+), 9 deletions(-)
>
>diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
>b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
>index d36c860c9c6f..7acd383f174f 100644
>--- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
>+++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
>@@ -117,6 +117,10 @@ struct drm_i915_gem_object {
>
> 	I915_SELFTEST_DECLARE(struct list_head st_link);
>
>+	unsigned long flags;
>+#define I915_BO_ALLOC_CONTIGUOUS BIT(0)
>+#define I915_BO_ALLOC_FLAGS (I915_BO_ALLOC_CONTIGUOUS)
>+
> 	/*
> 	 * Is the object to be mapped as read-only to the GPU
> 	 * Only honoured if hardware has relevant pte bit
>diff --git a/drivers/gpu/drm/i915/gem/i915_gem_region.c
>b/drivers/gpu/drm/i915/gem/i915_gem_region.c
>index 5c3bfc121921..b317a5c84144 100644
>--- a/drivers/gpu/drm/i915/gem/i915_gem_region.c
>+++ b/drivers/gpu/drm/i915/gem/i915_gem_region.c
>@@ -23,10 +23,10 @@ i915_gem_object_get_pages_buddy(struct
>drm_i915_gem_object *obj)
> {
> 	struct intel_memory_region *mem = obj->mm.region;
> 	struct list_head *blocks = &obj->mm.blocks;
>-	unsigned int flags = I915_ALLOC_MIN_PAGE_SIZE;
> 	resource_size_t size = obj->base.size;
> 	resource_size_t prev_end;
> 	struct i915_buddy_block *block;
>+	unsigned int flags;
> 	struct sg_table *st;
> 	struct scatterlist *sg;
> 	unsigned int sg_page_sizes;
>@@ -42,6 +42,10 @@ i915_gem_object_get_pages_buddy(struct
>drm_i915_gem_object *obj)
> 		return -ENOMEM;
> 	}
>
>+	flags = I915_ALLOC_MIN_PAGE_SIZE;
>+	if (obj->flags & I915_BO_ALLOC_CONTIGUOUS)
>+		flags |= I915_ALLOC_CONTIGUOUS;
>+
> 	ret = __intel_memory_region_get_pages_buddy(mem, size, flags,
>blocks);
> 	if (ret)
> 		goto err_free_sg;
>@@ -56,7 +60,8 @@ i915_gem_object_get_pages_buddy(struct
>drm_i915_gem_object *obj)
> 	list_for_each_entry(block, blocks, link) {
> 		u64 block_size, offset;
>
>-		block_size = i915_buddy_block_size(&mem->mm, block);
>+		block_size = min_t(u64, size,
>+				   i915_buddy_block_size(&mem->mm,
>block));
> 		offset = i915_buddy_block_offset(block);
>
> 		GEM_BUG_ON(overflows_type(block_size, sg->length));
>@@ -98,10 +103,12 @@ i915_gem_object_get_pages_buddy(struct
>drm_i915_gem_object *obj)
> }
>
> void i915_gem_object_init_memory_region(struct drm_i915_gem_object
>*obj,
>-					struct intel_memory_region *mem)
>+					struct intel_memory_region *mem,
>+					unsigned long flags)
> {
> 	INIT_LIST_HEAD(&obj->mm.blocks);
> 	obj->mm.region = mem;
>+	obj->flags = flags;
> }
>
> void i915_gem_object_release_memory_region(struct
>drm_i915_gem_object *obj)
>@@ -115,6 +122,8 @@ i915_gem_object_create_region(struct
>intel_memory_region *mem,
> {
> 	struct drm_i915_gem_object *obj;
>
>+	GEM_BUG_ON(flags & ~I915_BO_ALLOC_FLAGS);
>+
> 	if (!mem)
> 		return ERR_PTR(-ENODEV);
>
>diff --git a/drivers/gpu/drm/i915/gem/i915_gem_region.h
>b/drivers/gpu/drm/i915/gem/i915_gem_region.h
>index ebddc86d78f7..f2ff6f8bff74 100644
>--- a/drivers/gpu/drm/i915/gem/i915_gem_region.h
>+++ b/drivers/gpu/drm/i915/gem/i915_gem_region.h
>@@ -17,7 +17,8 @@ void i915_gem_object_put_pages_buddy(struct
>drm_i915_gem_object *obj,
> 				     struct sg_table *pages);
>
> void i915_gem_object_init_memory_region(struct drm_i915_gem_object
>*obj,
>-					struct intel_memory_region *mem);
>+					struct intel_memory_region *mem,
>+					unsigned long flags);
> void i915_gem_object_release_memory_region(struct
>drm_i915_gem_object *obj);
>
> struct drm_i915_gem_object *
>diff --git a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
>b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
>index 4e1805aaeb99..f9fbf2865782 100644
>--- a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
>+++ b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
>@@ -471,7 +471,8 @@ static int igt_mock_memory_region_huge_pages(void
>*arg)
> 		unsigned int page_size = BIT(bit);
> 		resource_size_t phys;
>
>-		obj = i915_gem_object_create_region(mem, page_size, 0);
>+		obj = i915_gem_object_create_region(mem, page_size,
>+
>I915_BO_ALLOC_CONTIGUOUS);
> 		if (IS_ERR(obj)) {
> 			err = PTR_ERR(obj);
> 			goto out_destroy_device;
>diff --git a/drivers/gpu/drm/i915/intel_memory_region.c
>b/drivers/gpu/drm/i915/intel_memory_region.c
>index e48d5c37c4df..7a66872d9eac 100644
>--- a/drivers/gpu/drm/i915/intel_memory_region.c
>+++ b/drivers/gpu/drm/i915/intel_memory_region.c
>@@ -47,8 +47,8 @@ __intel_memory_region_get_pages_buddy(struct
>intel_memory_region *mem,
> 				      unsigned int flags,
> 				      struct list_head *blocks)
> {
>-	unsigned long n_pages = size >> ilog2(mem->mm.chunk_size);
> 	unsigned int min_order = 0;
>+	unsigned long n_pages;
>
> 	GEM_BUG_ON(!IS_ALIGNED(size, mem->mm.chunk_size));
> 	GEM_BUG_ON(!list_empty(blocks));
>@@ -58,6 +58,13 @@ __intel_memory_region_get_pages_buddy(struct
>intel_memory_region *mem,
> 			    ilog2(mem->mm.chunk_size);
> 	}
>
>+	if (flags & I915_ALLOC_CONTIGUOUS) {
>+		size = roundup_pow_of_two(size);
>+		min_order = ilog2(size) - ilog2(mem->mm.chunk_size);
>+	}
>+
>+	n_pages = size >> ilog2(mem->mm.chunk_size);
>+
> 	mutex_lock(&mem->mm_lock);
>
> 	do {
>@@ -104,7 +111,9 @@ __intel_memory_region_get_block_buddy(struct
>intel_memory_region *mem,
> 	int ret;
>
> 	INIT_LIST_HEAD(&blocks);
>-	ret = __intel_memory_region_get_pages_buddy(mem, size, 0,
>&blocks);
>+	ret = __intel_memory_region_get_pages_buddy(mem, size,
>+						    I915_ALLOC_CONTIGUOUS,
>+						    &blocks);
> 	if (ret)
> 		return ERR_PTR(ret);
>
>diff --git a/drivers/gpu/drm/i915/intel_memory_region.h
>b/drivers/gpu/drm/i915/intel_memory_region.h
>index ae1ce298bcd1..1dad51b2fc96 100644
>--- a/drivers/gpu/drm/i915/intel_memory_region.h
>+++ b/drivers/gpu/drm/i915/intel_memory_region.h
>@@ -17,7 +17,8 @@ struct drm_i915_gem_object;
> struct intel_memory_region;
> struct sg_table;
>
>-#define I915_ALLOC_MIN_PAGE_SIZE   BIT(0)
>+#define I915_ALLOC_MIN_PAGE_SIZE  BIT(0)
>+#define I915_ALLOC_CONTIGUOUS     BIT(1)
>
> struct intel_memory_region_ops {
> 	unsigned int flags;
>diff --git a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
>b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
>index 54f9a624b4e1..c43d00ec38ea 100644
>--- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
>+++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
>@@ -13,6 +13,7 @@
>
> #include "gem/i915_gem_region.h"
> #include "gem/selftests/mock_context.h"
>+#include "selftests/i915_random.h"
>
> static void close_objects(struct list_head *objects)
> {
>@@ -89,10 +90,172 @@ static int igt_mock_fill(void *arg)
> 	return err;
> }
>
>+static struct drm_i915_gem_object *
>+igt_object_create(struct intel_memory_region *mem,
>+		  struct list_head *objects,
>+		  u64 size,
>+		  unsigned int flags)
>+{
>+	struct drm_i915_gem_object *obj;
>+	int err;
>+
>+	obj = i915_gem_object_create_region(mem, size, flags);
>+	if (IS_ERR(obj))
>+		return obj;
>+
>+	err = i915_gem_object_pin_pages(obj);
>+	if (err)
>+		goto put;
>+
>+	list_add(&obj->st_link, objects);
>+	return obj;
>+
>+put:
>+	i915_gem_object_put(obj);
>+	return ERR_PTR(err);
>+}
>+
>+void igt_object_release(struct drm_i915_gem_object *obj)
>+{
>+	i915_gem_object_unpin_pages(obj);
>+	__i915_gem_object_put_pages(obj, I915_MM_NORMAL);
>+	i915_gem_object_put(obj);
>+	list_del(&obj->st_link);
>+}
>+
>+static int igt_mock_continuous(void *arg)
>+{
>+	struct intel_memory_region *mem = arg;
>+	struct drm_i915_gem_object *obj;
>+	unsigned long n_objects;
>+	LIST_HEAD(objects);
>+	LIST_HEAD(holes);
>+	I915_RND_STATE(prng);
>+	resource_size_t target;
>+	resource_size_t total;
>+	resource_size_t min;
>+	int err = 0;
>+
>+	total = resource_size(&mem->region);
>+
>+	/* Min size */
>+	obj = igt_object_create(mem, &objects, mem->mm.chunk_size,
>+				I915_BO_ALLOC_CONTIGUOUS);
>+	if (IS_ERR(obj))
>+		return PTR_ERR(obj);
>+
>+	if (obj->mm.pages->nents != 1) {
>+		pr_err("%s min object spans multiple sg entries\n",
>__func__);
>+		err = -EINVAL;
>+		goto err_close_objects;
>+	}
>+
>+	igt_object_release(obj);
>+
>+	/* Max size */
>+	obj = igt_object_create(mem, &objects, total,
>I915_BO_ALLOC_CONTIGUOUS);
>+	if (IS_ERR(obj))
>+		return PTR_ERR(obj);
>+
>+	if (obj->mm.pages->nents != 1) {
>+		pr_err("%s max object spans multiple sg entries\n",
>__func__);
>+		err = -EINVAL;
>+		goto err_close_objects;
>+	}
>+
>+	igt_object_release(obj);
>+
>+	/* Internal fragmentation should not bleed into the object size */
>+	target = round_up(prandom_u32_state(&prng) % total, PAGE_SIZE);
>+	target = max_t(u64, PAGE_SIZE, target);
>+
>+	obj = igt_object_create(mem, &objects, target,
>+				I915_BO_ALLOC_CONTIGUOUS);
>+	if (IS_ERR(obj))
>+		return PTR_ERR(obj);
>+
>+	if (obj->base.size != target) {
>+		pr_err("%s obj->base.size(%llx) != target(%llx)\n", __func__,
>+		       (u64)obj->base.size, (u64)target);
>+		err = -EINVAL;
>+		goto err_close_objects;
>+	}
>+
>+	if (obj->mm.pages->nents != 1) {
>+		pr_err("%s object spans multiple sg entries\n", __func__);
>+		err = -EINVAL;
>+		goto err_close_objects;
>+	}
>+
>+	igt_object_release(obj);
>+
>+	/*
>+	 * Try to fragment the address space, such that half of it is free, but
>+	 * the max contiguous block size is SZ_64K.
>+	 */
>+
>+	target = SZ_64K;
>+	n_objects = div64_u64(total, target);
>+
>+	while (n_objects--) {
>+		struct list_head *list;
>+
>+		if (n_objects % 2)
>+			list = &holes;
>+		else
>+			list = &objects;
>+
>+		obj = igt_object_create(mem, list, target,
>+					I915_BO_ALLOC_CONTIGUOUS);
>+		if (IS_ERR(obj)) {
>+			err = PTR_ERR(obj);
>+			goto err_close_objects;
>+		}
>+	}
>+
>+	close_objects(&holes);
>+
>+	min = target;
>+	target = total >> 1;
>+
>+	/* Make sure we can still allocate all the fragmented space */
>+	obj = igt_object_create(mem, &objects, target, 0);
>+	if (IS_ERR(obj))
>+		return PTR_ERR(obj);
>+
>+	igt_object_release(obj);
>+
>+	/*
>+	 * Even though we have enough free space, we don't have a big
>enough
>+	 * contiguous block. Make sure that holds true.
>+	 */
>+
>+	do {
>+		bool should_fail = target > min;
>+
>+		obj = igt_object_create(mem, &objects, target,
>+					I915_BO_ALLOC_CONTIGUOUS);
>+		if (should_fail != IS_ERR(obj)) {
>+			pr_err("%s target allocation(%llx) mismatch\n",
>+			       __func__, (u64)target);
>+			err = -EINVAL;
>+			goto err_close_objects;
>+		}
>+
>+		target >>= 1;
>+	} while (target >= mem->mm.chunk_size);
>+
>+err_close_objects:
>+	list_splice_tail(&holes, &objects);
>+	close_objects(&objects);
>+	return err;
>+}
>+
> int intel_memory_region_mock_selftests(void)
> {
> 	static const struct i915_subtest tests[] = {
> 		SUBTEST(igt_mock_fill),
>+		SUBTEST(igt_mock_continuous),
> 	};
> 	struct intel_memory_region *mem;
> 	struct drm_i915_private *i915;
>diff --git a/drivers/gpu/drm/i915/selftests/mock_region.c
>b/drivers/gpu/drm/i915/selftests/mock_region.c
>index 0e9a575ede3b..7b0c99ddc2d5 100644
>--- a/drivers/gpu/drm/i915/selftests/mock_region.c
>+++ b/drivers/gpu/drm/i915/selftests/mock_region.c
>@@ -36,7 +36,7 @@ mock_object_create(struct intel_memory_region
>*mem,
>
> 	i915_gem_object_set_cache_coherency(obj, I915_CACHE_NONE);
>
>-	i915_gem_object_init_memory_region(obj, mem);
>+	i915_gem_object_init_memory_region(obj, mem, flags);
>
> 	return obj;
> }
>--
>2.20.1
>
>_______________________________________________
>Intel-gfx mailing list
>Intel-gfx@lists.freedesktop.org
>https://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox series

Patch

diff --git a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
index d36c860c9c6f..7acd383f174f 100644
--- a/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
+++ b/drivers/gpu/drm/i915/gem/i915_gem_object_types.h
@@ -117,6 +117,10 @@  struct drm_i915_gem_object {
 
 	I915_SELFTEST_DECLARE(struct list_head st_link);
 
+	unsigned long flags;
+#define I915_BO_ALLOC_CONTIGUOUS BIT(0)
+#define I915_BO_ALLOC_FLAGS (I915_BO_ALLOC_CONTIGUOUS)
+
 	/*
 	 * Is the object to be mapped as read-only to the GPU
 	 * Only honoured if hardware has relevant pte bit
diff --git a/drivers/gpu/drm/i915/gem/i915_gem_region.c b/drivers/gpu/drm/i915/gem/i915_gem_region.c
index 5c3bfc121921..b317a5c84144 100644
--- a/drivers/gpu/drm/i915/gem/i915_gem_region.c
+++ b/drivers/gpu/drm/i915/gem/i915_gem_region.c
@@ -23,10 +23,10 @@  i915_gem_object_get_pages_buddy(struct drm_i915_gem_object *obj)
 {
 	struct intel_memory_region *mem = obj->mm.region;
 	struct list_head *blocks = &obj->mm.blocks;
-	unsigned int flags = I915_ALLOC_MIN_PAGE_SIZE;
 	resource_size_t size = obj->base.size;
 	resource_size_t prev_end;
 	struct i915_buddy_block *block;
+	unsigned int flags;
 	struct sg_table *st;
 	struct scatterlist *sg;
 	unsigned int sg_page_sizes;
@@ -42,6 +42,10 @@  i915_gem_object_get_pages_buddy(struct drm_i915_gem_object *obj)
 		return -ENOMEM;
 	}
 
+	flags = I915_ALLOC_MIN_PAGE_SIZE;
+	if (obj->flags & I915_BO_ALLOC_CONTIGUOUS)
+		flags |= I915_ALLOC_CONTIGUOUS;
+
 	ret = __intel_memory_region_get_pages_buddy(mem, size, flags, blocks);
 	if (ret)
 		goto err_free_sg;
@@ -56,7 +60,8 @@  i915_gem_object_get_pages_buddy(struct drm_i915_gem_object *obj)
 	list_for_each_entry(block, blocks, link) {
 		u64 block_size, offset;
 
-		block_size = i915_buddy_block_size(&mem->mm, block);
+		block_size = min_t(u64, size,
+				   i915_buddy_block_size(&mem->mm, block));
 		offset = i915_buddy_block_offset(block);
 
 		GEM_BUG_ON(overflows_type(block_size, sg->length));
@@ -98,10 +103,12 @@  i915_gem_object_get_pages_buddy(struct drm_i915_gem_object *obj)
 }
 
 void i915_gem_object_init_memory_region(struct drm_i915_gem_object *obj,
-					struct intel_memory_region *mem)
+					struct intel_memory_region *mem,
+					unsigned long flags)
 {
 	INIT_LIST_HEAD(&obj->mm.blocks);
 	obj->mm.region = mem;
+	obj->flags = flags;
 }
 
 void i915_gem_object_release_memory_region(struct drm_i915_gem_object *obj)
@@ -115,6 +122,8 @@  i915_gem_object_create_region(struct intel_memory_region *mem,
 {
 	struct drm_i915_gem_object *obj;
 
+	GEM_BUG_ON(flags & ~I915_BO_ALLOC_FLAGS);
+
 	if (!mem)
 		return ERR_PTR(-ENODEV);
 
diff --git a/drivers/gpu/drm/i915/gem/i915_gem_region.h b/drivers/gpu/drm/i915/gem/i915_gem_region.h
index ebddc86d78f7..f2ff6f8bff74 100644
--- a/drivers/gpu/drm/i915/gem/i915_gem_region.h
+++ b/drivers/gpu/drm/i915/gem/i915_gem_region.h
@@ -17,7 +17,8 @@  void i915_gem_object_put_pages_buddy(struct drm_i915_gem_object *obj,
 				     struct sg_table *pages);
 
 void i915_gem_object_init_memory_region(struct drm_i915_gem_object *obj,
-					struct intel_memory_region *mem);
+					struct intel_memory_region *mem,
+					unsigned long flags);
 void i915_gem_object_release_memory_region(struct drm_i915_gem_object *obj);
 
 struct drm_i915_gem_object *
diff --git a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
index 4e1805aaeb99..f9fbf2865782 100644
--- a/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
+++ b/drivers/gpu/drm/i915/gem/selftests/huge_pages.c
@@ -471,7 +471,8 @@  static int igt_mock_memory_region_huge_pages(void *arg)
 		unsigned int page_size = BIT(bit);
 		resource_size_t phys;
 
-		obj = i915_gem_object_create_region(mem, page_size, 0);
+		obj = i915_gem_object_create_region(mem, page_size,
+						    I915_BO_ALLOC_CONTIGUOUS);
 		if (IS_ERR(obj)) {
 			err = PTR_ERR(obj);
 			goto out_destroy_device;
diff --git a/drivers/gpu/drm/i915/intel_memory_region.c b/drivers/gpu/drm/i915/intel_memory_region.c
index e48d5c37c4df..7a66872d9eac 100644
--- a/drivers/gpu/drm/i915/intel_memory_region.c
+++ b/drivers/gpu/drm/i915/intel_memory_region.c
@@ -47,8 +47,8 @@  __intel_memory_region_get_pages_buddy(struct intel_memory_region *mem,
 				      unsigned int flags,
 				      struct list_head *blocks)
 {
-	unsigned long n_pages = size >> ilog2(mem->mm.chunk_size);
 	unsigned int min_order = 0;
+	unsigned long n_pages;
 
 	GEM_BUG_ON(!IS_ALIGNED(size, mem->mm.chunk_size));
 	GEM_BUG_ON(!list_empty(blocks));
@@ -58,6 +58,13 @@  __intel_memory_region_get_pages_buddy(struct intel_memory_region *mem,
 			    ilog2(mem->mm.chunk_size);
 	}
 
+	if (flags & I915_ALLOC_CONTIGUOUS) {
+		size = roundup_pow_of_two(size);
+		min_order = ilog2(size) - ilog2(mem->mm.chunk_size);
+	}
+
+	n_pages = size >> ilog2(mem->mm.chunk_size);
+
 	mutex_lock(&mem->mm_lock);
 
 	do {
@@ -104,7 +111,9 @@  __intel_memory_region_get_block_buddy(struct intel_memory_region *mem,
 	int ret;
 
 	INIT_LIST_HEAD(&blocks);
-	ret = __intel_memory_region_get_pages_buddy(mem, size, 0, &blocks);
+	ret = __intel_memory_region_get_pages_buddy(mem, size,
+						    I915_ALLOC_CONTIGUOUS,
+						    &blocks);
 	if (ret)
 		return ERR_PTR(ret);
 
diff --git a/drivers/gpu/drm/i915/intel_memory_region.h b/drivers/gpu/drm/i915/intel_memory_region.h
index ae1ce298bcd1..1dad51b2fc96 100644
--- a/drivers/gpu/drm/i915/intel_memory_region.h
+++ b/drivers/gpu/drm/i915/intel_memory_region.h
@@ -17,7 +17,8 @@  struct drm_i915_gem_object;
 struct intel_memory_region;
 struct sg_table;
 
-#define I915_ALLOC_MIN_PAGE_SIZE   BIT(0)
+#define I915_ALLOC_MIN_PAGE_SIZE  BIT(0)
+#define I915_ALLOC_CONTIGUOUS     BIT(1)
 
 struct intel_memory_region_ops {
 	unsigned int flags;
diff --git a/drivers/gpu/drm/i915/selftests/intel_memory_region.c b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
index 54f9a624b4e1..c43d00ec38ea 100644
--- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
+++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
@@ -13,6 +13,7 @@ 
 
 #include "gem/i915_gem_region.h"
 #include "gem/selftests/mock_context.h"
+#include "selftests/i915_random.h"
 
 static void close_objects(struct list_head *objects)
 {
@@ -89,10 +90,172 @@  static int igt_mock_fill(void *arg)
 	return err;
 }
 
+static struct drm_i915_gem_object *
+igt_object_create(struct intel_memory_region *mem,
+		  struct list_head *objects,
+		  u64 size,
+		  unsigned int flags)
+{
+	struct drm_i915_gem_object *obj;
+	int err;
+
+	obj = i915_gem_object_create_region(mem, size, flags);
+	if (IS_ERR(obj))
+		return obj;
+
+	err = i915_gem_object_pin_pages(obj);
+	if (err)
+		goto put;
+
+	list_add(&obj->st_link, objects);
+	return obj;
+
+put:
+	i915_gem_object_put(obj);
+	return ERR_PTR(err);
+}
+
+void igt_object_release(struct drm_i915_gem_object *obj)
+{
+	i915_gem_object_unpin_pages(obj);
+	__i915_gem_object_put_pages(obj, I915_MM_NORMAL);
+	i915_gem_object_put(obj);
+	list_del(&obj->st_link);
+}
+
+static int igt_mock_continuous(void *arg)
+{
+	struct intel_memory_region *mem = arg;
+	struct drm_i915_gem_object *obj;
+	unsigned long n_objects;
+	LIST_HEAD(objects);
+	LIST_HEAD(holes);
+	I915_RND_STATE(prng);
+	resource_size_t target;
+	resource_size_t total;
+	resource_size_t min;
+	int err = 0;
+
+	total = resource_size(&mem->region);
+
+	/* Min size */
+	obj = igt_object_create(mem, &objects, mem->mm.chunk_size,
+				I915_BO_ALLOC_CONTIGUOUS);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+
+	if (obj->mm.pages->nents != 1) {
+		pr_err("%s min object spans multiple sg entries\n", __func__);
+		err = -EINVAL;
+		goto err_close_objects;
+	}
+
+	igt_object_release(obj);
+
+	/* Max size */
+	obj = igt_object_create(mem, &objects, total, I915_BO_ALLOC_CONTIGUOUS);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+
+	if (obj->mm.pages->nents != 1) {
+		pr_err("%s max object spans multiple sg entries\n", __func__);
+		err = -EINVAL;
+		goto err_close_objects;
+	}
+
+	igt_object_release(obj);
+
+	/* Internal fragmentation should not bleed into the object size */
+	target = round_up(prandom_u32_state(&prng) % total, PAGE_SIZE);
+	target = max_t(u64, PAGE_SIZE, target);
+
+	obj = igt_object_create(mem, &objects, target,
+				I915_BO_ALLOC_CONTIGUOUS);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+
+	if (obj->base.size != target) {
+		pr_err("%s obj->base.size(%llx) != target(%llx)\n", __func__,
+		       (u64)obj->base.size, (u64)target);
+		err = -EINVAL;
+		goto err_close_objects;
+	}
+
+	if (obj->mm.pages->nents != 1) {
+		pr_err("%s object spans multiple sg entries\n", __func__);
+		err = -EINVAL;
+		goto err_close_objects;
+	}
+
+	igt_object_release(obj);
+
+	/*
+	 * Try to fragment the address space, such that half of it is free, but
+	 * the max contiguous block size is SZ_64K.
+	 */
+
+	target = SZ_64K;
+	n_objects = div64_u64(total, target);
+
+	while (n_objects--) {
+		struct list_head *list;
+
+		if (n_objects % 2)
+			list = &holes;
+		else
+			list = &objects;
+
+		obj = igt_object_create(mem, list, target,
+					I915_BO_ALLOC_CONTIGUOUS);
+		if (IS_ERR(obj)) {
+			err = PTR_ERR(obj);
+			goto err_close_objects;
+		}
+	}
+
+	close_objects(&holes);
+
+	min = target;
+	target = total >> 1;
+
+	/* Make sure we can still allocate all the fragmented space */
+	obj = igt_object_create(mem, &objects, target, 0);
+	if (IS_ERR(obj))
+		return PTR_ERR(obj);
+
+	igt_object_release(obj);
+
+	/*
+	 * Even though we have enough free space, we don't have a big enough
+	 * contiguous block. Make sure that holds true.
+	 */
+
+	do {
+		bool should_fail = target > min;
+
+		obj = igt_object_create(mem, &objects, target,
+					I915_BO_ALLOC_CONTIGUOUS);
+		if (should_fail != IS_ERR(obj)) {
+			pr_err("%s target allocation(%llx) mismatch\n",
+			       __func__, (u64)target);
+			err = -EINVAL;
+			goto err_close_objects;
+		}
+
+		target >>= 1;
+	} while (target >= mem->mm.chunk_size);
+
+err_close_objects:
+	list_splice_tail(&holes, &objects);
+	close_objects(&objects);
+	return err;
+}
+
 int intel_memory_region_mock_selftests(void)
 {
 	static const struct i915_subtest tests[] = {
 		SUBTEST(igt_mock_fill),
+		SUBTEST(igt_mock_continuous),
 	};
 	struct intel_memory_region *mem;
 	struct drm_i915_private *i915;
diff --git a/drivers/gpu/drm/i915/selftests/mock_region.c b/drivers/gpu/drm/i915/selftests/mock_region.c
index 0e9a575ede3b..7b0c99ddc2d5 100644
--- a/drivers/gpu/drm/i915/selftests/mock_region.c
+++ b/drivers/gpu/drm/i915/selftests/mock_region.c
@@ -36,7 +36,7 @@  mock_object_create(struct intel_memory_region *mem,
 
 	i915_gem_object_set_cache_coherency(obj, I915_CACHE_NONE);
 
-	i915_gem_object_init_memory_region(obj, mem);
+	i915_gem_object_init_memory_region(obj, mem, flags);
 
 	return obj;
 }