[Intel-gfx] [PATCH 18/18] drm/i915: support transparent-huge-pages through shmemfs
Matthew Auld
matthew.auld at intel.com
Tue Apr 4 22:11:28 UTC 2017
Signed-off-by: Matthew Auld <matthew.auld at intel.com>
---
drivers/gpu/drm/i915/i915_drv.h | 3 +
drivers/gpu/drm/i915/i915_gem.c | 187 +++++++++++++++++++++++++++++++++-------
drivers/gpu/drm/i915/i915_vma.c | 8 ++
3 files changed, 166 insertions(+), 32 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 838ce22a0a40..07dd4d24b93e 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -2672,6 +2672,9 @@ static inline struct scatterlist *__sg_next(struct scatterlist *sg)
* @__pp: page pointer (output)
* @__iter: 'struct sgt_iter' (iterator state, internal)
* @__sgt: sg_table to iterate over (input)
+ *
+ * Be warned, if we using huge-pages @_pp could be a part of a compound page,
+ * so care must be taken. Too thorny?
*/
#define for_each_sgt_page(__pp, __iter, __sgt) \
for ((__iter) = __sgt_iter((__sgt)->sgl, false); \
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 5362f4d18689..1dde01676d37 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -171,7 +171,7 @@ i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj)
struct sg_table *st;
struct scatterlist *sg;
char *vaddr;
- int i;
+ int i, j;
if (WARN_ON(i915_gem_object_needs_bit17_swizzle(obj)))
return ERR_PTR(-EINVAL);
@@ -187,7 +187,7 @@ i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj)
return ERR_PTR(-ENOMEM);
vaddr = phys->vaddr;
- for (i = 0; i < obj->base.size / PAGE_SIZE; i++) {
+ for (i = 0; i < obj->base.size / PAGE_SIZE; ) {
struct page *page;
char *src;
@@ -197,13 +197,15 @@ i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj)
goto err_phys;
}
- src = kmap_atomic(page);
- memcpy(vaddr, src, PAGE_SIZE);
- drm_clflush_virt_range(vaddr, PAGE_SIZE);
- kunmap_atomic(src);
+ for (j = 0; j < hpage_nr_pages(page); ++j, ++i) {
+ src = kmap_atomic(page + j);
+ memcpy(vaddr, src, PAGE_SIZE);
+ drm_clflush_virt_range(vaddr, PAGE_SIZE);
+ kunmap_atomic(src);
+ vaddr += PAGE_SIZE;
+ }
put_page(page);
- vaddr += PAGE_SIZE;
}
i915_gem_chipset_flush(to_i915(obj->base.dev));
@@ -263,9 +265,9 @@ i915_gem_object_put_pages_phys(struct drm_i915_gem_object *obj,
if (obj->mm.dirty) {
struct address_space *mapping = obj->base.filp->f_mapping;
char *vaddr = obj->phys_handle->vaddr;
- int i;
+ int i, j;
- for (i = 0; i < obj->base.size / PAGE_SIZE; i++) {
+ for (i = 0; i < obj->base.size / PAGE_SIZE; ) {
struct page *page;
char *dst;
@@ -273,16 +275,18 @@ i915_gem_object_put_pages_phys(struct drm_i915_gem_object *obj,
if (IS_ERR(page))
continue;
- dst = kmap_atomic(page);
- drm_clflush_virt_range(vaddr, PAGE_SIZE);
- memcpy(dst, vaddr, PAGE_SIZE);
- kunmap_atomic(dst);
+ for (j = 0; j < hpage_nr_pages(page); ++j, ++i) {
+ dst = kmap_atomic(page + j);
+ drm_clflush_virt_range(vaddr, PAGE_SIZE);
+ memcpy(dst, vaddr, PAGE_SIZE);
+ kunmap_atomic(dst);
+ vaddr += PAGE_SIZE;
+ }
set_page_dirty(page);
if (obj->mm.madv == I915_MADV_WILLNEED)
mark_page_accessed(page);
put_page(page);
- vaddr += PAGE_SIZE;
}
obj->mm.dirty = false;
}
@@ -2179,6 +2183,8 @@ i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj,
i915_gem_object_save_bit_17_swizzle(obj, pages);
for_each_sgt_page(page, sgt_iter, pages) {
+ if (PageTail(page))
+ continue;
if (obj->mm.dirty)
set_page_dirty(page);
@@ -2272,6 +2278,15 @@ static bool i915_sg_trim(struct sg_table *orig_st)
return true;
}
+static inline unsigned int i915_shmem_page_size(struct page *page)
+{
+#ifdef CONFIG_TRANSPARENT_HUGE_PAGECACHE
+ return PageTransHuge(page) ? HPAGE_PMD_SIZE : PAGE_SIZE;
+#else
+ return PAGE_SIZE;
+#endif
+}
+
static struct sg_table *
i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
{
@@ -2287,6 +2302,14 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
unsigned int max_segment;
int ret;
gfp_t gfp;
+ const unsigned int gtt_page_sizes[] = {
+ I915_GTT_PAGE_SIZE_1G,
+ I915_GTT_PAGE_SIZE_2M,
+ I915_GTT_PAGE_SIZE_64K,
+ I915_GTT_PAGE_SIZE_4K,
+ };
+ unsigned int page_size;
+ int j;
/* Assert that the object is not currently in any GPU domain. As it
* wasn't in the GTT, there shouldn't be any way it could have been in
@@ -2299,6 +2322,25 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
if (!max_segment)
max_segment = rounddown(UINT_MAX, PAGE_SIZE);
+ /* max_segment is the maximum number of continuous PAGE_SIZE pages we
+ * can have in the bounce buffer, assuming swiotlb. So optimistically
+ * select the largest supported gtt page size which can fit into the
+ * max_segment. Also take care to properly align the max_segment to
+ * said page size to avoid any huge pages spilling across sg entries.
+ */
+ for (j = 0; j < ARRAY_SIZE(gtt_page_sizes); ++j) {
+ unsigned int page_size = gtt_page_sizes[j];
+ unsigned int nr_pages = page_size >> PAGE_SHIFT;
+
+ if (SUPPORTS_PAGE_SIZE(dev_priv, page_size) &&
+ page_size <= obj->page_size &&
+ nr_pages <= max_segment) {
+ max_segment = rounddown(max_segment, nr_pages);
+ obj->gtt_page_size = page_size;
+ break;
+ }
+ }
+
st = kmalloc(sizeof(*st), GFP_KERNEL);
if (st == NULL)
return ERR_PTR(-ENOMEM);
@@ -2309,6 +2351,9 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
return ERR_PTR(-ENOMEM);
}
+ GEM_BUG_ON(!SUPPORTS_PAGE_SIZE(dev_priv, obj->gtt_page_size));
+ GEM_BUG_ON(!IS_ALIGNED(max_segment << PAGE_SHIFT, obj->gtt_page_size));
+
/* Get the list of pages out of our struct file. They'll be pinned
* at this point until we release them.
*
@@ -2319,7 +2364,7 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
gfp |= __GFP_NORETRY | __GFP_NOWARN;
sg = st->sgl;
st->nents = 0;
- for (i = 0; i < page_count; i++) {
+ for (i = 0; i < page_count; i += hpage_nr_pages(page)) {
page = shmem_read_mapping_page_gfp(mapping, i, gfp);
if (unlikely(IS_ERR(page))) {
i915_gem_shrink(dev_priv,
@@ -2349,17 +2394,36 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
goto err_sg;
}
}
+
+ /* If we don't enough huge pages in the pool, fall back to the
+ * minimum page size. We can still allocate huge-pages but now
+ * obj->page_size and obj->gtt_page_size will reflect the
+ * minimum page size in the mapping.
+ */
+ page_size = i915_shmem_page_size(page);
+ if (page_size < obj->page_size) {
+ obj->page_size = PAGE_SIZE;
+ obj->gtt_page_size = I915_GTT_PAGE_SIZE;
+ }
+
+ /* TODO: if we don't use huge-pages or the object is small
+ * we can probably do something clever with continious pages
+ * here, if we have enough of them and they fit nicely into a
+ * gtt page size and max_segment. Imagine a 64K object, and we
+ * get 16 continuous 4K pages, we could get away with a single
+ * 64K pte.
+ */
if (!i ||
sg->length >= max_segment ||
page_to_pfn(page) != last_pfn + 1) {
if (i)
sg = sg_next(sg);
st->nents++;
- sg_set_page(sg, page, PAGE_SIZE, 0);
+ sg_set_page(sg, page, page_size, 0);
} else {
- sg->length += PAGE_SIZE;
+ sg->length += page_size;
}
- last_pfn = page_to_pfn(page);
+ last_pfn = page_to_pfn(page) + hpage_nr_pages(page) - 1;
/* Check that the i965g/gm workaround works. */
WARN_ON((gfp & __GFP_DMA32) && (last_pfn >= 0x00100000UL));
@@ -2372,25 +2436,43 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
ret = i915_gem_gtt_prepare_pages(obj, st);
if (ret) {
- /* DMA remapping failed? One possible cause is that
- * it could not reserve enough large entries, asking
- * for PAGE_SIZE chunks instead may be helpful.
- */
- if (max_segment > PAGE_SIZE) {
- for_each_sgt_page(page, sgt_iter, st)
- put_page(page);
- sg_free_table(st);
-
- max_segment = PAGE_SIZE;
- goto rebuild_st;
- } else {
+ if (max_segment == PAGE_SIZE) {
dev_warn(&dev_priv->drm.pdev->dev,
"Failed to DMA remap %lu pages\n",
page_count);
goto err_pages;
}
+
+ for_each_sgt_page(page, sgt_iter, st) {
+ if (!PageTail(page))
+ put_page(page);
+ }
+ sg_free_table(st);
+
+ /* DMA remapping failed? One possible cause is that
+ * it could not reserve enough large entries, trying
+ * smaller page size chunks instead may be helpful.
+ *
+ * We really don't know what the max_segment should be,
+ * just go with the simple premise that the next
+ * smallest segment will be at least half the size of
+ * the previous.
+ */
+ for (; j < ARRAY_SIZE(gtt_page_sizes); ++j) {
+ unsigned int page_size = gtt_page_sizes[j];
+
+ if (SUPPORTS_PAGE_SIZE(dev_priv, page_size) &&
+ page_size < max_segment) {
+ obj->gtt_page_size = max_segment = page_size;
+ break;
+ }
+ }
+
+ goto rebuild_st;
}
+ GEM_BUG_ON(obj->gtt_page_size > obj->page_size);
+
if (i915_gem_object_needs_bit17_swizzle(obj))
i915_gem_object_do_bit_17_swizzle(obj, st);
@@ -2399,8 +2481,10 @@ i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj)
err_sg:
sg_mark_end(sg);
err_pages:
- for_each_sgt_page(page, sgt_iter, st)
- put_page(page);
+ for_each_sgt_page(page, sgt_iter, st) {
+ if (!PageTail(page))
+ put_page(page);
+ }
sg_free_table(st);
kfree(st);
@@ -4192,10 +4276,36 @@ struct drm_i915_gem_object *
i915_gem_object_create(struct drm_i915_private *dev_priv, u64 size)
{
struct drm_i915_gem_object *obj;
+ unsigned int page_size = PAGE_SIZE;
struct address_space *mapping;
gfp_t mask;
int ret;
+ /* If configured *attempt* to use THP through shmemfs. HPAGE_PMD_SIZE
+ * will either be 2M or 1G depending on the default hugepage_sz. This
+ * is best effort and will of course depend on how many huge-pages we
+ * have available in the pool. We determine the gtt page size when we
+ * actually try pinning the backing storage, where gtt_page_size <=
+ * page_size.
+ *
+ * XXX Some musings:
+ *
+ * - We don't know if the object will be inserted into the ppgtt where
+ * it will be most benificial to have huge-pages, or the ggtt where
+ * the object will always be treated like a 4K object.
+ *
+ * - Similarly should we care if the gtt doesn't support pages sizes >
+ * 4K? If it does then great, if it doesn't then we do at least see
+ * the benefit of reduced fragmentation, so it's not a complete
+ * waste...thoughts?
+ */
+#ifdef CONFIG_TRANSPARENT_HUGE_PAGECACHE
+ if (has_transparent_hugepage() && size >= HPAGE_PMD_SIZE) {
+ page_size = HPAGE_PMD_SIZE;
+ size = round_up(size, page_size);
+ }
+#endif
+
/* There is a prevalence of the assumption that we fit the object's
* page count inside a 32bit _signed_ variable. Let's document this and
* catch if we ever need to fix it. In the meantime, if you do spot
@@ -4227,6 +4337,19 @@ i915_gem_object_create(struct drm_i915_private *dev_priv, u64 size)
i915_gem_object_init(obj, &i915_gem_object_ops);
+ /* In a few places we interact with shmemfs implicitly by writing
+ * through the page_cache prior to pinning the backing storage, this
+ * is for optimisation reasons and prevents shmemfs from needlessly
+ * clearing pages. So in order to control the use of huge-pages, from
+ * both the pinning of the backing store and any implicit interaction
+ * which may end up allocating pages we require more than the provided
+ * read_mapping or getpage interfaces provided by shmem. This should
+ * effectively default to huge-page allocations in shmem for this
+ * mapping.
+ */
+ SHMEM_I(mapping->host)->huge = page_size > PAGE_SIZE;
+ obj->page_size = page_size;
+
obj->base.write_domain = I915_GEM_DOMAIN_CPU;
obj->base.read_domains = I915_GEM_DOMAIN_CPU;
diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c
index 4043145b4310..af295aa3b49c 100644
--- a/drivers/gpu/drm/i915/i915_vma.c
+++ b/drivers/gpu/drm/i915/i915_vma.c
@@ -469,6 +469,14 @@ i915_vma_insert(struct i915_vma *vma, u64 size, u64 alignment, u64 flags)
if (ret)
return ret;
+ /* We don't know the final gtt page size until *after* we pin the
+ * backing store, or that's at least the case for the shmem backend.
+ * Therefore re-adjust the alignment if needed. This is only relevant
+ * for huge-pages being inserted into the ppgtt.
+ */
+ if (!i915_is_ggtt(vma->vm) && alignment < obj->gtt_page_size)
+ alignment = obj->gtt_page_size;
+
if (i915_vm_has_cache_coloring(vma->vm))
color = obj->cache_level;
else if (i915_vm_has_page_coloring(vma->vm))
--
2.9.3
More information about the Intel-gfx
mailing list