Before addition of commit 029ae067431a
("drm/i915: Fix potential overflow of shmem scatterlist length")
and after folios were introduced complete folios were always
allocated, possibly overloading the scatterlist capacity which
was never truly limited to PAGE_SIZE when requested via
max_segment. The above commit addressed scatterlist overloading,
but unintentionally disabled PAGE_SIZE as a valid max_segment
value and failed to take care of remaining pages from folios
above max_segment boundary.
This created a state, where multitude of scatterlists were used
for the same folio, but never counting enough of its pages to
jump to the next folio.

Furthermore, current do-while loop goes over the same folio
multiple times, increasing the refcount each time, and breaks
get/put symmetry: many shmem_read_folio_gfp() gets and only one
put in i915_gem_object_put_pages_shmem() per folio.

Track how many pages have already been counted in a folio and
use that number as an offset on consecutive allocations from the
same folio to ensure it is fully covered before reading next
folio.

Fixes: 029ae067431a ("drm/i915: Fix potential overflow of shmem scatterlist 
length")
Cc: [email protected]
Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/15816
Assisted-by: GitHub Copilot:claude-opus-5
Signed-off-by: Krzysztof Karas <[email protected]>
---
v6:
 * moved max_segment check into its own patch;
 * moved comment about gcc warnings above block of
   initializations;
 * removed
   WARN_ON_ONCE(folio_page_index >= folio_nr_pages(folio))
   block (Andi);
 * restored original condition (folio_pfn(folio) != next_pfn)
   (Janusz);
 * dropped folio_page_index and folio initializations;
 * expanded the commit message to contain information about
   previously unexplained folio reference leaks;
 * ran final checks with Claude Opus and added a tag.

 drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 112 +++++++++++++---------
 1 file changed, 67 insertions(+), 45 deletions(-)

diff --git a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c 
b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c
index 4b5ce9a2f74f..8e5f2a7d5f6b 100644
--- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c
+++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c
@@ -70,7 +70,11 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, 
struct sg_table *st,
        unsigned int page_count; /* restricted by sg_alloc_table */
        unsigned long i;
        struct scatterlist *sg;
-       unsigned long next_pfn = 0;     /* suppress gcc warning */
+       /* suppress gcc warnings */
+       unsigned long next_pfn = 0;
+       unsigned long folio_start = 0;
+       unsigned long folio_end = 0;
+       struct folio *folio;
        gfp_t noreclaim;
        int ret;
 
@@ -104,7 +108,7 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, 
struct sg_table *st,
        sg = st->sgl;
        st->nents = 0;
        for (i = 0; i < page_count; i++) {
-               struct folio *folio;
+               unsigned long folio_page_index;
                unsigned long nr_pages;
                const unsigned int shrink[] = {
                        I915_SHRINK_BOUND | I915_SHRINK_UNBOUND,
@@ -112,51 +116,58 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, 
struct sg_table *st,
                }, *s = shrink;
                gfp_t gfp = noreclaim;
 
-               do {
-                       cond_resched();
-                       folio = shmem_read_folio_gfp(mapping, i, gfp);
-                       if (!IS_ERR(folio))
-                               break;
+               /* Grab the next folio if we exhausted the current one. */
+               if (!i || i > folio_end) {
+                       do {
+                               cond_resched();
+                               folio = shmem_read_folio_gfp(mapping, i, gfp);
+                               if (!IS_ERR(folio))
+                                       break;
 
-                       if (!*s) {
-                               ret = PTR_ERR(folio);
-                               goto err_sg;
-                       }
+                               if (!*s) {
+                                       ret = PTR_ERR(folio);
+                                       goto err_sg;
+                               }
 
-                       i915_gem_shrink(NULL, i915, 2 * page_count, NULL, *s++);
-
-                       /*
-                        * We've tried hard to allocate the memory by reaping
-                        * our own buffer, now let the real VM do its job and
-                        * go down in flames if truly OOM.
-                        *
-                        * However, since graphics tend to be disposable,
-                        * defer the oom here by reporting the ENOMEM back
-                        * to userspace.
-                        */
-                       if (!*s) {
-                               /* reclaim and warn, but no oom */
-                               gfp = mapping_gfp_mask(mapping);
+                               i915_gem_shrink(NULL, i915, 2 * page_count, 
NULL, *s++);
 
                                /*
-                                * Our bo are always dirty and so we require
-                                * kswapd to reclaim our pages (direct reclaim
-                                * does not effectively begin pageout of our
-                                * buffers on its own). However, direct reclaim
-                                * only waits for kswapd when under allocation
-                                * congestion. So as a result __GFP_RECLAIM is
-                                * unreliable and fails to actually reclaim our
-                                * dirty pages -- unless you try over and over
-                                * again with !__GFP_NORETRY. However, we still
-                                * want to fail this allocation rather than
-                                * trigger the out-of-memory killer and for
-                                * this we want __GFP_RETRY_MAYFAIL.
-                                */
-                               gfp |= __GFP_RETRY_MAYFAIL | __GFP_NOWARN;
-                       }
-               } while (1);
-
-               nr_pages = min3(folio_nr_pages(folio),
+                               * We've tried hard to allocate the memory by 
reaping
+                               * our own buffer, now let the real VM do its 
job and
+                               * go down in flames if truly OOM.
+                               *
+                               * However, since graphics tend to be disposable,
+                               * defer the oom here by reporting the ENOMEM 
back
+                               * to userspace.
+                               */
+                               if (!*s) {
+                                       /* reclaim and warn, but no oom */
+                                       gfp = mapping_gfp_mask(mapping);
+
+                                       /*
+                                        * Our bo are always dirty and so we 
require
+                                        * kswapd to reclaim our pages (direct 
reclaim
+                                        * does not effectively begin pageout 
of our
+                                        * buffers on its own). However, direct 
reclaim
+                                        * only waits for kswapd when under 
allocation
+                                        * congestion. So as a result 
__GFP_RECLAIM is
+                                        * unreliable and fails to actually 
reclaim our
+                                        * dirty pages -- unless you try over 
and over
+                                        * again with !__GFP_NORETRY. However, 
we still
+                                        * want to fail this allocation rather 
than
+                                        * trigger the out-of-memory killer and 
for
+                                        * this we want __GFP_RETRY_MAYFAIL.
+                                        */
+                                       gfp |= __GFP_RETRY_MAYFAIL | 
__GFP_NOWARN;
+                               }
+                       } while (1);
+
+                       folio_start = folio_pgoff(folio);
+                       folio_end = folio_start + folio_nr_pages(folio) - 1;
+               }
+
+               folio_page_index = i - folio_start;
+               nr_pages = min3(folio_nr_pages(folio) - folio_page_index,
                                page_count - i, max_segment / PAGE_SIZE);
 
                if (!i ||
@@ -166,13 +177,24 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, 
struct sg_table *st,
                                sg = sg_next(sg);
 
                        st->nents++;
-                       sg_set_folio(sg, folio, nr_pages * PAGE_SIZE, 0);
+                       sg_set_page(sg, folio_page(folio, folio_page_index),
+                                   nr_pages * PAGE_SIZE, 0);
                } else {
+                       /*
+                        * If our prediction about folio placement is true and
+                        * scatterlist still has space left for more pages,
+                        * then we land here.
+                        */
                        nr_pages = min(nr_pages, (max_segment - sg->length) / 
PAGE_SIZE);
 
                        sg->length += nr_pages * PAGE_SIZE;
                }
-               next_pfn = folio_pfn(folio) + nr_pages;
+
+               /*
+                * We assume folios are placed one after the other in memory
+                * and predict where the next folio begins.
+                */
+               next_pfn = folio_pfn(folio) + folio_page_index + nr_pages;
                i += nr_pages - 1;
 
                /* Check that the i965g/gm workaround works. */
-- 
2.34.1

Reply via email to