Re: [PATCH v5 1/6] drm/i915/gem: Count mapped pages in a folio

Janusz Krzysztofik <[email protected]>
Newsgroups org.freedesktop.lists.intel-gfx,dev.linux.lists.iommu,org.freedesktop.lists.dri-devel
Organization Intel Technology Poland sp. z o.o. - ul. Slowackiego 173, 80-298 Gdansk - KRS 101882 - NIP 957-07-52-316
Message-ID <[email protected]>
Hi Krzysztof,

While I'm finally more or less OK with your commit description :-), I
still have some comments to code changes you propose.

On Mon, 2026-08-17 at 09:56 +0000, Krzysztof Karas wrote:
> 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.
> 
> 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")
> Closes: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/15816
> Signed-off-by: Krzysztof Karas <[email protected]>
> ---
> v5:
>  * Moved max_segment value validation before allocating shmem table
>  (Sebastian).
>  * Aligned comments (Sebastian).
> 
>  drivers/gpu/drm/i915/gem/i915_gem_shmem.c | 121 ++++++++++++++--------
>  1 file changed, 76 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 06543ae60706..f338dc39fad1 100644
> --- a/drivers/gpu/drm/i915/gem/i915_gem_shmem.c
> +++ b/drivers/gpu/drm/i915/gem/i915_gem_shmem.c
> @@ -68,10 +68,13 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st,
>  			 unsigned int max_segment)
>  {
>  	unsigned int page_count; /* restricted by sg_alloc_table */
> -	unsigned long i;
> +	unsigned long next_pfn = 0; /* suppress gcc warning */
> +	unsigned long folio_start = 0;
> +	unsigned long folio_end = 0;
> +	struct folio *folio = NULL;
>  	struct scatterlist *sg;
> -	unsigned long next_pfn = 0;	/* suppress gcc warning */
>  	gfp_t noreclaim;
> +	unsigned long i;
>  	int ret;
>  
>  	if (overflows_type(size / PAGE_SIZE, page_count))
> @@ -85,6 +88,9 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st,
>  	if (size > resource_size(&mr->region))
>  		return -ENOMEM;
>  
> +	if (max_segment < PAGE_SIZE)
> +		return -EINVAL;
> +
>  	if (sg_alloc_table(st, page_count, GFP_KERNEL | __GFP_NOWARN))
>  		return -ENOMEM;
>  
> @@ -101,7 +107,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 = 0;
>  		unsigned long nr_pages;
>  		const unsigned int shrink[] = {
>  			I915_SHRINK_BOUND | I915_SHRINK_UNBOUND,
> @@ -109,71 +115,95 @@ 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. */

I think that even with folio_start < i < folio_end, 
shmem_read_folio_gfp(..., i, ...) should always return the right folio, so 
we shouldn't worry about getting the same folio again in a relatively rare 
case of the folio size exceeding a scatterlist capacity, we only need to 
take care of tracking an offset within that folio.  Then, ...

> +		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.
> +				 * 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.
>  				 */
> -				gfp |= __GFP_RETRY_MAYFAIL | __GFP_NOWARN;
> -			}
> -		} while (1);
> +				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;
> +		if (WARN_ON_ONCE(folio_page_index >= folio_nr_pages(folio))) {
> +			ret = -EINVAL;
> +			folio_put(folio);
> +			goto err_sg;
> +		}

... the existing code above, including the existing 'do' loop, may be left 
untouched, I believe, an your folio_page_index can easily be calculated 
from an already maintained next_pfn as:

+		folio_page_index = next_pfn - folio_pfn(folio);
+		if (folio_page_index < 0 || folio_page_index >= folio_nr_pages(folio))
+			folio_page_index = 0;

IOW, we need to calculate and apply an offset within the folio only if 
next_pfn is still within the folio's PFN range, otherwise that must be a 
new folio and the offset we apply must be 0.

Then, unless I'm missing something, I believe the patch could be much more 
compact while still correct with my approach.  However, if other reviewers 
are more OK with your proposed changes rather than what I suggest then I 
won't oppose.

>  
>  		nr_pages = min_array(((unsigned long[]) {
> -					folio_nr_pages(folio),
> +					folio_nr_pages(folio) - folio_page_index,
>  					page_count - i,
>  					max_segment / PAGE_SIZE,
>  				      }), 3);
>  
>  		if (!i ||
>  		    sg->length >= max_segment ||
> -		    folio_pfn(folio) != next_pfn) {
> +		    folio_pfn(folio) + folio_page_index != next_pfn) {

I think the existing condition was correct, and still applicable even when 
tracking potential offsets within folios: when there is still some room in 
the current scatterlist, enter the else part that fills up the scatterlist 
with more data only if we've got a new folio that starts exactly where 
the previous one ended to have contiguity of pages preserved, otherwise 
switch to a new scatterlist.

>  			if (i)
>  				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_t(unsigned long, 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. */
> @@ -186,6 +216,7 @@ int shmem_sg_alloc_table(struct drm_i915_private *i915, struct sg_table *st,
>  	i915_sg_trim(st);
>  
>  	return 0;
> +

Not related, I wouldn't mix it in, unless you have a good justification.

Thanks,
Janusz

>  err_sg:
>  	sg_mark_end(sg);
>  	if (sg != st->sgl) {
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.