linux/tools/testing/scatterlist/main.c
Maor Gottlieb 07da1223ec lib/scatterlist: Add support in dynamic allocation of SG table from pages
Extend __sg_alloc_table_from_pages to support dynamic allocation of
SG table from pages. It should be used by drivers that can't supply
all the pages at one time.

This function returns the last populated SGE in the table. Users should
pass it as an argument to the function from the second call and forward.
As before, nents will be equal to the number of populated SGEs (chunks).

With this new extension, drivers can benefit the optimization of merging
contiguous pages without a need to allocate all pages in advance and
hold them in a large buffer.

E.g. with the Infiniband driver that allocates a single page for hold the
pages. For 1TB memory registration, the temporary buffer would consume only
4KB, instead of 2GB.

Link: https://lore.kernel.org/r/20201004154340.1080481-2-leon@kernel.org
Signed-off-by: Maor Gottlieb <maorg@nvidia.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
2020-10-05 20:45:45 -03:00

103 lines
2.9 KiB
C

#include <stdio.h>
#include <assert.h>
#include <linux/scatterlist.h>
#define MAX_PAGES (64)
struct test {
int alloc_ret;
unsigned num_pages;
unsigned *pfn;
unsigned size;
unsigned int max_seg;
unsigned int expected_segments;
};
static void set_pages(struct page **pages, const unsigned *array, unsigned num)
{
unsigned int i;
assert(num < MAX_PAGES);
for (i = 0; i < num; i++)
pages[i] = (struct page *)(unsigned long)
((1 + array[i]) * PAGE_SIZE);
}
#define pfn(...) (unsigned []){ __VA_ARGS__ }
static void fail(struct test *test, struct sg_table *st, const char *cond)
{
unsigned int i;
fprintf(stderr, "Failed on '%s'!\n\n", cond);
printf("size = %u, max segment = %u, expected nents = %u\nst->nents = %u, st->orig_nents= %u\n",
test->size, test->max_seg, test->expected_segments, st->nents,
st->orig_nents);
printf("%u input PFNs:", test->num_pages);
for (i = 0; i < test->num_pages; i++)
printf(" %x", test->pfn[i]);
printf("\n");
exit(1);
}
#define VALIDATE(cond, st, test) \
if (!(cond)) \
fail((test), (st), #cond);
int main(void)
{
const unsigned int sgmax = SCATTERLIST_MAX_SEGMENT;
struct test *test, tests[] = {
{ -EINVAL, 1, pfn(0), PAGE_SIZE, PAGE_SIZE + 1, 1 },
{ -EINVAL, 1, pfn(0), PAGE_SIZE, 0, 1 },
{ -EINVAL, 1, pfn(0), PAGE_SIZE, sgmax + 1, 1 },
{ 0, 1, pfn(0), PAGE_SIZE, sgmax, 1 },
{ 0, 1, pfn(0), 1, sgmax, 1 },
{ 0, 2, pfn(0, 1), 2 * PAGE_SIZE, sgmax, 1 },
{ 0, 2, pfn(1, 0), 2 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(0, 1, 2), 3 * PAGE_SIZE, sgmax, 1 },
{ 0, 3, pfn(0, 2, 1), 3 * PAGE_SIZE, sgmax, 3 },
{ 0, 3, pfn(0, 1, 3), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(1, 2, 4), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(1, 3, 4), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 4, pfn(0, 1, 3, 4), 4 * PAGE_SIZE, sgmax, 2 },
{ 0, 5, pfn(0, 1, 3, 4, 5), 5 * PAGE_SIZE, sgmax, 2 },
{ 0, 5, pfn(0, 1, 3, 4, 6), 5 * PAGE_SIZE, sgmax, 3 },
{ 0, 5, pfn(0, 1, 2, 3, 4), 5 * PAGE_SIZE, sgmax, 1 },
{ 0, 5, pfn(0, 1, 2, 3, 4), 5 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 6, pfn(0, 1, 2, 3, 4, 5), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 6, pfn(0, 2, 3, 4, 5, 6), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 4 },
{ 0, 6, pfn(0, 1, 3, 4, 5, 6), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 0, NULL, 0, 0, 0 },
};
unsigned int i;
for (i = 0, test = tests; test->expected_segments; test++, i++) {
struct page *pages[MAX_PAGES];
struct sg_table st;
struct scatterlist *sg;
set_pages(pages, test->pfn, test->num_pages);
sg = __sg_alloc_table_from_pages(&st, pages, test->num_pages, 0,
test->size, test->max_seg, NULL, 0, GFP_KERNEL);
assert(PTR_ERR_OR_ZERO(sg) == test->alloc_ret);
if (test->alloc_ret)
continue;
VALIDATE(st.nents == test->expected_segments, &st, test);
VALIDATE(st.orig_nents == test->expected_segments, &st, test);
sg_free_table(&st);
}
assert(i == (sizeof(tests) / sizeof(tests[0])) - 1);
return 0;
}