Message ID | 20170119114158.17941-4-chris@chris-wilson.co.uk (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 19/01/2017 11:41, Chris Wilson wrote: > Start exercising the scattergather lists, especially looking at > iteration after coalescing. > > v2: Comment on the peculiarity of table construction (i.e. why this > sg_table might be interesting). Just a comment added as request, cool, I can r-b then! Erm no, it is not just a comment but a lot of other changes as well... > Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> > Cc: Tvrtko Ursulin <tvrtko.ursulin@intel.com> > --- > drivers/gpu/drm/i915/i915_gem.c | 11 +- > .../gpu/drm/i915/selftests/i915_mock_selftests.h | 1 + > drivers/gpu/drm/i915/selftests/scatterlist.c | 331 +++++++++++++++++++++ > 3 files changed, 340 insertions(+), 3 deletions(-) > create mode 100644 drivers/gpu/drm/i915/selftests/scatterlist.c > > diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c > index 7c3895230a8a..04edbcaffa25 100644 > --- a/drivers/gpu/drm/i915/i915_gem.c > +++ b/drivers/gpu/drm/i915/i915_gem.c > @@ -2215,17 +2215,17 @@ void __i915_gem_object_put_pages(struct drm_i915_gem_object *obj, > mutex_unlock(&obj->mm.lock); > } > > -static void i915_sg_trim(struct sg_table *orig_st) > +static bool i915_sg_trim(struct sg_table *orig_st) > { > struct sg_table new_st; > struct scatterlist *sg, *new_sg; > unsigned int i; > > if (orig_st->nents == orig_st->orig_nents) > - return; > + return false; > > if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN)) > - return; > + return false; > > new_sg = new_st.sgl; > for_each_sg(orig_st->sgl, sg, orig_st->nents, i) { > @@ -2238,6 +2238,7 @@ static void i915_sg_trim(struct sg_table *orig_st) > sg_free_table(orig_st); > > *orig_st = new_st; > + return true; > } > > static struct sg_table * > @@ -4937,3 +4938,7 @@ i915_gem_object_get_dma_address(struct drm_i915_gem_object *obj, > sg = i915_gem_object_get_sg(obj, n, &offset); > return sg_dma_address(sg) + (offset << PAGE_SHIFT); > } > + > +#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) > +#include "selftests/scatterlist.c" > +#endif > diff --git a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h > index 69e97a2ba4a6..5f0bdda42ed8 100644 > --- a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h > +++ b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h > @@ -9,3 +9,4 @@ > * Tests are executed in order by igt/drv_selftest > */ > selftest(sanitycheck, i915_mock_sanitycheck) /* keep first (igt selfcheck) */ > +selftest(scatterlist, scatterlist_mock_selftests) > diff --git a/drivers/gpu/drm/i915/selftests/scatterlist.c b/drivers/gpu/drm/i915/selftests/scatterlist.c > new file mode 100644 > index 000000000000..4000fdd1b7db > --- /dev/null > +++ b/drivers/gpu/drm/i915/selftests/scatterlist.c > @@ -0,0 +1,331 @@ > +/* > + * Copyright © 2016 Intel Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the "Software"), > + * to deal in the Software without restriction, including without limitation > + * the rights to use, copy, modify, merge, publish, distribute, sublicense, > + * and/or sell copies of the Software, and to permit persons to whom the > + * Software is furnished to do so, subject to the following conditions: > + * > + * The above copyright notice and this permission notice (including the next > + * paragraph) shall be included in all copies or substantial portions of the > + * Software. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL > + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER > + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING > + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS > + * IN THE SOFTWARE. > + */ > + > +#include <linux/prime_numbers.h> > +#include <linux/random.h> > + > +#include "i915_selftest.h" > + > +#define PFN_BIAS (1 << 10) > + > +struct pfn_table { > + struct sg_table st; > + unsigned long start, end; > +}; > + > +typedef unsigned int (*npages_fn_t)(unsigned long n, > + unsigned long count, sg table cannot store unsigned long of pages but call it future proofing. :) > + struct rnd_state *rnd); > + > +static noinline int expect_pfn_sg(struct pfn_table *pt, Why noinline? > + npages_fn_t npages_fn, > + struct rnd_state *rnd, > + const char *who, > + unsigned long timeout) > +{ > + struct scatterlist *sg; > + unsigned long pfn, n; > + > + pfn = pt->start; > + for_each_sg(pt->st.sgl, sg, pt->st.nents, n) { > + struct page *page = sg_page(sg); > + unsigned int npages = npages_fn(n, pt->st.nents, rnd); > + > + if (page_to_pfn(page) != pfn) { > + pr_err("%s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sg)\n", > + who, pfn, page_to_pfn(page)); No __func__ here compared to other messages. > + return -EINVAL; > + } > + > + if (sg->length != npages * PAGE_SIZE) { > + pr_err("%s: %s copied wrong sg length, expected size %lu, found %u (using for_each_sg)\n", > + __func__, who, npages * PAGE_SIZE, sg->length); > + return -EINVAL; > + } > + > + if (igt_timeout(timeout, "%s timed out\n", who)) > + return -EINTR; > + > + pfn += npages; > + } > + if (pfn != pt->end) { > + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", > + __func__, who, pt->end, pfn); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static noinline int expect_pfn_sg_page_iter(struct pfn_table *pt, > + const char *who, > + unsigned long timeout) > +{ > + struct sg_page_iter sgiter; > + unsigned long pfn; > + > + pfn = pt->start; > + for_each_sg_page(pt->st.sgl, &sgiter, pt->st.nents, 0) { > + struct page *page = sg_page_iter_page(&sgiter); > + > + if (page != pfn_to_page(pfn)) { > + pr_err("%s: %s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sg_page)\n", > + __func__, who, pfn, page_to_pfn(page)); > + return -EINVAL; > + } > + > + if (igt_timeout(timeout, "%s timed out\n", who)) > + return -EINTR; > + > + pfn++; > + } > + if (pfn != pt->end) { > + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", > + __func__, who, pt->end, pfn); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static noinline int expect_pfn_sgtiter(struct pfn_table *pt, > + const char *who, > + unsigned long timeout) > +{ > + struct sgt_iter sgt; > + struct page *page; > + unsigned long pfn; > + > + pfn = pt->start; > + for_each_sgt_page(page, sgt, &pt->st) { > + if (page != pfn_to_page(pfn)) { > + pr_err("%s: %s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sgt_page)\n", > + __func__, who, pfn, page_to_pfn(page)); > + return -EINVAL; > + } > + > + if (igt_timeout(timeout, "%s timed out\n", who)) > + return -EINTR; > + > + pfn++; > + } > + if (pfn != pt->end) { > + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", > + __func__, who, pt->end, pfn); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int expect_pfn_sgtable(struct pfn_table *pt, > + npages_fn_t npages_fn, > + struct rnd_state *rnd, > + const char *who, > + unsigned long timeout) > +{ > + int err; > + > + err = expect_pfn_sg(pt, npages_fn, rnd, who, timeout); > + if (err) > + return err; > + > + err = expect_pfn_sg_page_iter(pt, who, timeout); > + if (err) > + return err; > + > + err = expect_pfn_sgtiter(pt, who, timeout); > + if (err) > + return err; > + > + return 0; > +} > + > +static unsigned int one(unsigned long n, > + unsigned long count, > + struct rnd_state *rnd) > +{ > + return 1; > +} > + > +static unsigned int grow(unsigned long n, > + unsigned long count, > + struct rnd_state *rnd) > +{ > + return n + 1; > +} > + > +static unsigned int shrink(unsigned long n, > + unsigned long count, > + struct rnd_state *rnd) > +{ > + return count - n; > +} > + > +static unsigned int random(unsigned long n, > + unsigned long count, > + struct rnd_state *rnd) > +{ > + return 1 + (prandom_u32_state(rnd) % 1024); > +} > + > +static bool alloc_table(struct pfn_table *pt, > + unsigned long count, unsigned long max, > + npages_fn_t npages_fn, > + struct rnd_state *rnd) > +{ > + struct scatterlist *sg; > + unsigned long n, pfn; > + > + if (sg_alloc_table(&pt->st, max, > + GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN)) > + return false; > + > + /* count should be less than 20 to prevent overflowing sg->length */ > + GEM_BUG_ON(overflows_type(count * PAGE_SIZE, sg->length)); > + > + /* Construct a table where each scatterlist contains different number > + * of entries. The idea is to check that we can iterate the individual > + * pages from inside the coalesced lists. > + */ > + pt->start = PFN_BIAS; > + pfn = pt->start; > + sg = pt->st.sgl; > + for (n = 0; n < count; n++) { > + unsigned long npages = npages_fn(n, count, rnd); > + > + if (n) > + sg = sg_next(sg); > + sg_set_page(sg, pfn_to_page(pfn), npages * PAGE_SIZE, 0); > + > + GEM_BUG_ON(page_to_pfn(sg_page(sg)) != pfn); > + GEM_BUG_ON(sg->length != npages * PAGE_SIZE); > + GEM_BUG_ON(sg->offset != 0); > + > + pfn += npages; > + } > + sg_mark_end(sg); > + pt->st.nents = n; > + pt->end = pfn; > + > + return true; > +} > + > +static const npages_fn_t npages_funcs[] = { > + one, > + grow, > + shrink, > + random, > + NULL, > +}; > + > +static int igt_sg_alloc(void *ignored) > +{ > + IGT_TIMEOUT(end_time); > + const unsigned long max_order = 20; /* approximating a 4GiB object */ > + struct rnd_state prng; > + unsigned long prime; > + > + for_each_prime_number(prime, max_order) { > + unsigned long size = BIT(prime); > + int offset; > + > + for (offset = -1; offset <= 1; offset++) { > + unsigned long sz = size + offset; > + const npages_fn_t *npages; > + struct pfn_table pt; > + int err; > + > + for (npages = npages_funcs; *npages; npages++) { > + prandom_seed_state(&prng, > + i915_selftest.random_seed); > + if (!alloc_table(&pt, sz, sz, *npages, &prng)) > + return 0; /* out of memory, give up */ You don't have skip status? Sounds not ideal to silently abort. > + > + prandom_seed_state(&prng, > + i915_selftest.random_seed); > + err = expect_pfn_sgtable(&pt, *npages, &prng, > + "sg_alloc_table", > + end_time); Random numbers you use are guaranteed to be the same sequence after you re-set the seed? Probably yes since otherwise this wouldn't have ever worked.. I just remember some discussion on what source we use and it looked like we might be using proper random numbers on some CPUs, or even urandom which I didn't think has that property. > + sg_free_table(&pt.st); > + if (err) > + return err; > + } > + } > + } > + > + return 0; > +} > + > +static int igt_sg_trim(void *ignored) > +{ > + IGT_TIMEOUT(end_time); > + const unsigned long max = PAGE_SIZE; /* not prime! */ > + struct pfn_table pt; > + unsigned long prime; > + > + for_each_prime_number(prime, max) { > + const npages_fn_t *npages; > + int err; > + > + for (npages = npages_funcs; *npages; npages++) { > + struct rnd_state prng; > + > + prandom_seed_state(&prng, i915_selftest.random_seed); > + if (!alloc_table(&pt, prime, max, *npages, &prng)) > + return 0; /* out of memory, give up */ > + > + err = 0; > + if (i915_sg_trim(&pt.st)) { > + if (pt.st.orig_nents != prime || > + pt.st.nents != prime) { > + pr_err("i915_sg_trim failed (nents %u, orig_nents %u), expected %lu\n", > + pt.st.nents, pt.st.orig_nents, prime); > + err = -EINVAL; > + } else { > + prandom_seed_state(&prng, > + i915_selftest.random_seed); > + err = expect_pfn_sgtable(&pt, > + *npages, &prng, > + "i915_sg_trim", > + end_time); > + } > + } Similar to alloc_table failures above - no log or action when i915_sg_trim fails due out of memory? > + sg_free_table(&pt.st); > + if (err) > + return err; > + } > + } > + > + return 0; > +} > + > +int scatterlist_mock_selftests(void) > +{ > + static const struct i915_subtest tests[] = { > + SUBTEST(igt_sg_alloc), > + SUBTEST(igt_sg_trim), > + }; > + > + return i915_subtests(tests, NULL); > +} > Regards, Tvrtko
On Wed, Feb 01, 2017 at 11:17:39AM +0000, Tvrtko Ursulin wrote: > >+static noinline int expect_pfn_sg(struct pfn_table *pt, > > Why noinline? So they show up in perf individually. > >+ > >+ for (npages = npages_funcs; *npages; npages++) { > >+ prandom_seed_state(&prng, > >+ i915_selftest.random_seed); > >+ if (!alloc_table(&pt, sz, sz, *npages, &prng)) > >+ return 0; /* out of memory, give up */ > > You don't have skip status? Sounds not ideal to silently abort. It runs until we use all physical memory, if left to its own devices. It's not a skip if we have already completed some tests. ENOMEM of the test setup itself is not what I'm testing for here, the test is for the iterators. > >+ > >+ prandom_seed_state(&prng, > >+ i915_selftest.random_seed); > >+ err = expect_pfn_sgtable(&pt, *npages, &prng, > >+ "sg_alloc_table", > >+ end_time); > > Random numbers you use are guaranteed to be the same sequence after > you re-set the seed? Probably yes since otherwise this wouldn't have > ever worked.. I just remember some discussion on what source we use > and it looked like we might be using proper random numbers on some > CPUs, or even urandom which I didn't think has that property. It's a completely deterministic prng. get_random_int() is the urandom equivalent. > >+static int igt_sg_trim(void *ignored) > >+{ > >+ IGT_TIMEOUT(end_time); > >+ const unsigned long max = PAGE_SIZE; /* not prime! */ > >+ struct pfn_table pt; > >+ unsigned long prime; > >+ > >+ for_each_prime_number(prime, max) { > >+ const npages_fn_t *npages; > >+ int err; > >+ > >+ for (npages = npages_funcs; *npages; npages++) { > >+ struct rnd_state prng; > >+ > >+ prandom_seed_state(&prng, i915_selftest.random_seed); > >+ if (!alloc_table(&pt, prime, max, *npages, &prng)) > >+ return 0; /* out of memory, give up */ > >+ > >+ err = 0; > >+ if (i915_sg_trim(&pt.st)) { > >+ if (pt.st.orig_nents != prime || > >+ pt.st.nents != prime) { > >+ pr_err("i915_sg_trim failed (nents %u, orig_nents %u), expected %lu\n", > >+ pt.st.nents, pt.st.orig_nents, prime); > >+ err = -EINVAL; > >+ } else { > >+ prandom_seed_state(&prng, > >+ i915_selftest.random_seed); > >+ err = expect_pfn_sgtable(&pt, > >+ *npages, &prng, > >+ "i915_sg_trim", > >+ end_time); > >+ } > >+ } > > Similar to alloc_table failures above - no log or action when > i915_sg_trim fails due out of memory? No, simply because that's an expected and acceptable result. The question should be whether we always want to check after sg_trim. -Chris
On 01/02/2017 11:34, Chris Wilson wrote: > On Wed, Feb 01, 2017 at 11:17:39AM +0000, Tvrtko Ursulin wrote: >>> + >>> + for (npages = npages_funcs; *npages; npages++) { >>> + prandom_seed_state(&prng, >>> + i915_selftest.random_seed); >>> + if (!alloc_table(&pt, sz, sz, *npages, &prng)) >>> + return 0; /* out of memory, give up */ >> >> You don't have skip status? Sounds not ideal to silently abort. > > It runs until we use all physical memory, if left to its own devices. It's > not a skip if we have already completed some tests. ENOMEM of the test > setup itself is not what I'm testing for here, the test is for the > iterators. But suppose you mess up the test so the starting condition asks for impossible amount of memory but the test claims it passed. I don't think that is a good behaviour. >>> +static int igt_sg_trim(void *ignored) >>> +{ >>> + IGT_TIMEOUT(end_time); >>> + const unsigned long max = PAGE_SIZE; /* not prime! */ >>> + struct pfn_table pt; >>> + unsigned long prime; >>> + >>> + for_each_prime_number(prime, max) { >>> + const npages_fn_t *npages; >>> + int err; >>> + >>> + for (npages = npages_funcs; *npages; npages++) { >>> + struct rnd_state prng; >>> + >>> + prandom_seed_state(&prng, i915_selftest.random_seed); >>> + if (!alloc_table(&pt, prime, max, *npages, &prng)) >>> + return 0; /* out of memory, give up */ >>> + >>> + err = 0; >>> + if (i915_sg_trim(&pt.st)) { >>> + if (pt.st.orig_nents != prime || >>> + pt.st.nents != prime) { >>> + pr_err("i915_sg_trim failed (nents %u, orig_nents %u), expected %lu\n", >>> + pt.st.nents, pt.st.orig_nents, prime); >>> + err = -EINVAL; >>> + } else { >>> + prandom_seed_state(&prng, >>> + i915_selftest.random_seed); >>> + err = expect_pfn_sgtable(&pt, >>> + *npages, &prng, >>> + "i915_sg_trim", >>> + end_time); >>> + } >>> + } >> >> Similar to alloc_table failures above - no log or action when >> i915_sg_trim fails due out of memory? > > No, simply because that's an expected and acceptable result. The > question should be whether we always want to check after sg_trim. Same as above really, I think that creates a big doubt in the test output. Regards, Tvrtko
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index 7c3895230a8a..04edbcaffa25 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -2215,17 +2215,17 @@ void __i915_gem_object_put_pages(struct drm_i915_gem_object *obj, mutex_unlock(&obj->mm.lock); } -static void i915_sg_trim(struct sg_table *orig_st) +static bool i915_sg_trim(struct sg_table *orig_st) { struct sg_table new_st; struct scatterlist *sg, *new_sg; unsigned int i; if (orig_st->nents == orig_st->orig_nents) - return; + return false; if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN)) - return; + return false; new_sg = new_st.sgl; for_each_sg(orig_st->sgl, sg, orig_st->nents, i) { @@ -2238,6 +2238,7 @@ static void i915_sg_trim(struct sg_table *orig_st) sg_free_table(orig_st); *orig_st = new_st; + return true; } static struct sg_table * @@ -4937,3 +4938,7 @@ i915_gem_object_get_dma_address(struct drm_i915_gem_object *obj, sg = i915_gem_object_get_sg(obj, n, &offset); return sg_dma_address(sg) + (offset << PAGE_SHIFT); } + +#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) +#include "selftests/scatterlist.c" +#endif diff --git a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h index 69e97a2ba4a6..5f0bdda42ed8 100644 --- a/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h +++ b/drivers/gpu/drm/i915/selftests/i915_mock_selftests.h @@ -9,3 +9,4 @@ * Tests are executed in order by igt/drv_selftest */ selftest(sanitycheck, i915_mock_sanitycheck) /* keep first (igt selfcheck) */ +selftest(scatterlist, scatterlist_mock_selftests) diff --git a/drivers/gpu/drm/i915/selftests/scatterlist.c b/drivers/gpu/drm/i915/selftests/scatterlist.c new file mode 100644 index 000000000000..4000fdd1b7db --- /dev/null +++ b/drivers/gpu/drm/i915/selftests/scatterlist.c @@ -0,0 +1,331 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <linux/prime_numbers.h> +#include <linux/random.h> + +#include "i915_selftest.h" + +#define PFN_BIAS (1 << 10) + +struct pfn_table { + struct sg_table st; + unsigned long start, end; +}; + +typedef unsigned int (*npages_fn_t)(unsigned long n, + unsigned long count, + struct rnd_state *rnd); + +static noinline int expect_pfn_sg(struct pfn_table *pt, + npages_fn_t npages_fn, + struct rnd_state *rnd, + const char *who, + unsigned long timeout) +{ + struct scatterlist *sg; + unsigned long pfn, n; + + pfn = pt->start; + for_each_sg(pt->st.sgl, sg, pt->st.nents, n) { + struct page *page = sg_page(sg); + unsigned int npages = npages_fn(n, pt->st.nents, rnd); + + if (page_to_pfn(page) != pfn) { + pr_err("%s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sg)\n", + who, pfn, page_to_pfn(page)); + return -EINVAL; + } + + if (sg->length != npages * PAGE_SIZE) { + pr_err("%s: %s copied wrong sg length, expected size %lu, found %u (using for_each_sg)\n", + __func__, who, npages * PAGE_SIZE, sg->length); + return -EINVAL; + } + + if (igt_timeout(timeout, "%s timed out\n", who)) + return -EINTR; + + pfn += npages; + } + if (pfn != pt->end) { + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", + __func__, who, pt->end, pfn); + return -EINVAL; + } + + return 0; +} + +static noinline int expect_pfn_sg_page_iter(struct pfn_table *pt, + const char *who, + unsigned long timeout) +{ + struct sg_page_iter sgiter; + unsigned long pfn; + + pfn = pt->start; + for_each_sg_page(pt->st.sgl, &sgiter, pt->st.nents, 0) { + struct page *page = sg_page_iter_page(&sgiter); + + if (page != pfn_to_page(pfn)) { + pr_err("%s: %s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sg_page)\n", + __func__, who, pfn, page_to_pfn(page)); + return -EINVAL; + } + + if (igt_timeout(timeout, "%s timed out\n", who)) + return -EINTR; + + pfn++; + } + if (pfn != pt->end) { + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", + __func__, who, pt->end, pfn); + return -EINVAL; + } + + return 0; +} + +static noinline int expect_pfn_sgtiter(struct pfn_table *pt, + const char *who, + unsigned long timeout) +{ + struct sgt_iter sgt; + struct page *page; + unsigned long pfn; + + pfn = pt->start; + for_each_sgt_page(page, sgt, &pt->st) { + if (page != pfn_to_page(pfn)) { + pr_err("%s: %s left pages out of order, expected pfn %lu, found pfn %lu (using for_each_sgt_page)\n", + __func__, who, pfn, page_to_pfn(page)); + return -EINVAL; + } + + if (igt_timeout(timeout, "%s timed out\n", who)) + return -EINTR; + + pfn++; + } + if (pfn != pt->end) { + pr_err("%s: %s finished on wrong pfn, expected %lu, found %lu\n", + __func__, who, pt->end, pfn); + return -EINVAL; + } + + return 0; +} + +static int expect_pfn_sgtable(struct pfn_table *pt, + npages_fn_t npages_fn, + struct rnd_state *rnd, + const char *who, + unsigned long timeout) +{ + int err; + + err = expect_pfn_sg(pt, npages_fn, rnd, who, timeout); + if (err) + return err; + + err = expect_pfn_sg_page_iter(pt, who, timeout); + if (err) + return err; + + err = expect_pfn_sgtiter(pt, who, timeout); + if (err) + return err; + + return 0; +} + +static unsigned int one(unsigned long n, + unsigned long count, + struct rnd_state *rnd) +{ + return 1; +} + +static unsigned int grow(unsigned long n, + unsigned long count, + struct rnd_state *rnd) +{ + return n + 1; +} + +static unsigned int shrink(unsigned long n, + unsigned long count, + struct rnd_state *rnd) +{ + return count - n; +} + +static unsigned int random(unsigned long n, + unsigned long count, + struct rnd_state *rnd) +{ + return 1 + (prandom_u32_state(rnd) % 1024); +} + +static bool alloc_table(struct pfn_table *pt, + unsigned long count, unsigned long max, + npages_fn_t npages_fn, + struct rnd_state *rnd) +{ + struct scatterlist *sg; + unsigned long n, pfn; + + if (sg_alloc_table(&pt->st, max, + GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN)) + return false; + + /* count should be less than 20 to prevent overflowing sg->length */ + GEM_BUG_ON(overflows_type(count * PAGE_SIZE, sg->length)); + + /* Construct a table where each scatterlist contains different number + * of entries. The idea is to check that we can iterate the individual + * pages from inside the coalesced lists. + */ + pt->start = PFN_BIAS; + pfn = pt->start; + sg = pt->st.sgl; + for (n = 0; n < count; n++) { + unsigned long npages = npages_fn(n, count, rnd); + + if (n) + sg = sg_next(sg); + sg_set_page(sg, pfn_to_page(pfn), npages * PAGE_SIZE, 0); + + GEM_BUG_ON(page_to_pfn(sg_page(sg)) != pfn); + GEM_BUG_ON(sg->length != npages * PAGE_SIZE); + GEM_BUG_ON(sg->offset != 0); + + pfn += npages; + } + sg_mark_end(sg); + pt->st.nents = n; + pt->end = pfn; + + return true; +} + +static const npages_fn_t npages_funcs[] = { + one, + grow, + shrink, + random, + NULL, +}; + +static int igt_sg_alloc(void *ignored) +{ + IGT_TIMEOUT(end_time); + const unsigned long max_order = 20; /* approximating a 4GiB object */ + struct rnd_state prng; + unsigned long prime; + + for_each_prime_number(prime, max_order) { + unsigned long size = BIT(prime); + int offset; + + for (offset = -1; offset <= 1; offset++) { + unsigned long sz = size + offset; + const npages_fn_t *npages; + struct pfn_table pt; + int err; + + for (npages = npages_funcs; *npages; npages++) { + prandom_seed_state(&prng, + i915_selftest.random_seed); + if (!alloc_table(&pt, sz, sz, *npages, &prng)) + return 0; /* out of memory, give up */ + + prandom_seed_state(&prng, + i915_selftest.random_seed); + err = expect_pfn_sgtable(&pt, *npages, &prng, + "sg_alloc_table", + end_time); + sg_free_table(&pt.st); + if (err) + return err; + } + } + } + + return 0; +} + +static int igt_sg_trim(void *ignored) +{ + IGT_TIMEOUT(end_time); + const unsigned long max = PAGE_SIZE; /* not prime! */ + struct pfn_table pt; + unsigned long prime; + + for_each_prime_number(prime, max) { + const npages_fn_t *npages; + int err; + + for (npages = npages_funcs; *npages; npages++) { + struct rnd_state prng; + + prandom_seed_state(&prng, i915_selftest.random_seed); + if (!alloc_table(&pt, prime, max, *npages, &prng)) + return 0; /* out of memory, give up */ + + err = 0; + if (i915_sg_trim(&pt.st)) { + if (pt.st.orig_nents != prime || + pt.st.nents != prime) { + pr_err("i915_sg_trim failed (nents %u, orig_nents %u), expected %lu\n", + pt.st.nents, pt.st.orig_nents, prime); + err = -EINVAL; + } else { + prandom_seed_state(&prng, + i915_selftest.random_seed); + err = expect_pfn_sgtable(&pt, + *npages, &prng, + "i915_sg_trim", + end_time); + } + } + sg_free_table(&pt.st); + if (err) + return err; + } + } + + return 0; +} + +int scatterlist_mock_selftests(void) +{ + static const struct i915_subtest tests[] = { + SUBTEST(igt_sg_alloc), + SUBTEST(igt_sg_trim), + }; + + return i915_subtests(tests, NULL); +}
Start exercising the scattergather lists, especially looking at iteration after coalescing. v2: Comment on the peculiarity of table construction (i.e. why this sg_table might be interesting). Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Cc: Tvrtko Ursulin <tvrtko.ursulin@intel.com> --- drivers/gpu/drm/i915/i915_gem.c | 11 +- .../gpu/drm/i915/selftests/i915_mock_selftests.h | 1 + drivers/gpu/drm/i915/selftests/scatterlist.c | 331 +++++++++++++++++++++ 3 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/i915/selftests/scatterlist.c