On Tue, 2024-12-03 at 15:51 +0100, Christian König wrote: > Am 03.12.24 um 14:42 schrieb Thomas Hellström: > > On Tue, 2024-12-03 at 14:12 +0100, Christian König wrote: > > > Am 15.11.24 um 16:01 schrieb Thomas Hellström: > > > > Provide a helper to shrink ttm_tt page-vectors on a per-page > > > > basis. A ttm_backup backend could then in theory get away with > > > > allocating a single temporary page for each struct ttm_tt. > > > > > > > > This is accomplished by splitting larger pages before trying to > > > > back them up. > > > > > > > > In the future we could allow ttm_backup to handle backing up > > > > large pages as well, but currently there's no benefit in > > > > doing that, since the shmem backup backend would have to > > > > split those anyway to avoid allocating too much temporary > > > > memory, and if the backend instead inserts pages into the > > > > swap-cache, those are split on reclaim by the core. > > > > > > > > Due to potential backup- and recover errors, allow partially > > > > swapped > > > > out struct ttm_tt's, although mark them as swapped out stopping > > > > them > > > > from being swapped out a second time. More details in the > > > > ttm_pool.c > > > > DOC section. > > > > > > > > v2: > > > > - A couple of cleanups and error fixes in ttm_pool_back_up_tt. > > > > - s/back_up/backup/ > > > > - Add a writeback parameter to the exported interface. > > > > v8: > > > > - Use a struct for flags for readability (Matt Brost) > > > > - Address misc other review comments (Matt Brost) > > > > v9: > > > > - Update the kerneldoc for the ttm_tt::backup field. > > > > v10: > > > > - Rebase. > > > > v13: > > > > - Rebase on ttm_backup interface change. Update kerneldoc. > > > > - Rebase and adjust ttm_tt_is_swapped(). > > > > > > > > Cc: Christian König<christian.koenig@xxxxxxx> > > > > Cc: Somalapuram Amaranath<Amaranath.Somalapuram@xxxxxxx> > > > > Cc: Matthew Brost<matthew.brost@xxxxxxxxx> > > > > Cc:<dri-devel@xxxxxxxxxxxxxxxxxxxxx> > > > > Signed-off-by: Thomas > > > > Hellström<thomas.hellstrom@xxxxxxxxxxxxxxx> > > > > Reviewed-by: Matthew Brost<matthew.brost@xxxxxxxxx> > > > > --- > > > > drivers/gpu/drm/ttm/ttm_pool.c | 396 > > > > +++++++++++++++++++++++++++++++-- > > > > drivers/gpu/drm/ttm/ttm_tt.c | 37 +++ > > > > include/drm/ttm/ttm_pool.h | 6 + > > > > include/drm/ttm/ttm_tt.h | 32 ++- > > > > 4 files changed, 457 insertions(+), 14 deletions(-) > > > > > > > > diff --git a/drivers/gpu/drm/ttm/ttm_pool.c > > > > b/drivers/gpu/drm/ttm/ttm_pool.c > > > > index 8504dbe19c1a..f58864439edb 100644 > > > > --- a/drivers/gpu/drm/ttm/ttm_pool.c > > > > +++ b/drivers/gpu/drm/ttm/ttm_pool.c > > > > @@ -41,6 +41,7 @@ > > > > #include <asm/set_memory.h> > > > > #endif > > > > > > > > +#include <drm/ttm/ttm_backup.h> > > > > #include <drm/ttm/ttm_pool.h> > > > > #include <drm/ttm/ttm_tt.h> > > > > #include <drm/ttm/ttm_bo.h> > > > > @@ -58,6 +59,32 @@ struct ttm_pool_dma { > > > > unsigned long vaddr; > > > > }; > > > > > > > > +/** > > > > + * struct ttm_pool_tt_restore - State representing restore > > > > from > > > > backup > > > > + * @alloced_pages: Total number of already allocated pages for > > > > the > > > > ttm_tt. > > > > + * @restored_pages: Number of (sub) pages restored from swap > > > > for > > > > this > > > > + * chunk of 1 << @order pages. > > > > + * @first_page: The ttm page ptr representing for > > > > @old_pages[0]. > > > > + * @caching_divide: Page pointer where subsequent pages are > > > > cached. > > > > + * @old_pages: Backup copy of page pointers that were replaced > > > > by > > > > the new > > > > + * page allocation. > > > > + * @pool: The pool used for page allocation while restoring. > > > > + * @order: The order of the last page allocated while > > > > restoring. > > > > + * > > > > + * Recovery from backup might fail when we've recovered less > > > > than > > > > the > > > > + * full ttm_tt. In order not to loose any data (yet), keep > > > > information > > > > + * around that allows us to restart a failed ttm backup > > > > recovery. > > > > + */ > > > > +struct ttm_pool_tt_restore { > > > > + pgoff_t alloced_pages; > > > > + pgoff_t restored_pages; > > > > + struct page **first_page; > > > > + struct page **caching_divide; > > > > + struct ttm_pool *pool; > > > > + unsigned int order; > > > > + struct page *old_pages[]; > > > > +}; > > > > + > > > > static unsigned long page_pool_size; > > > > > > > > MODULE_PARM_DESC(page_pool_size, "Number of pages in the > > > > WC/UC/DMA pool"); > > > > @@ -354,11 +381,105 @@ static unsigned int > > > > ttm_pool_page_order(struct ttm_pool *pool, struct page *p) > > > > return p->private; > > > > } > > > > > > > > +/* > > > > + * To be able to insert single pages into backup directly, > > > > + * we need to split multi-order page allocations and make them > > > > look > > > > + * like single-page allocations. > > > > + */ > > > > +static void ttm_pool_split_for_swap(struct ttm_pool *pool, > > > > struct > > > > page *p) > > > > +{ > > > > + unsigned int order = ttm_pool_page_order(pool, p); > > > > + pgoff_t nr; > > > > + > > > > + if (!order) > > > > + return; > > > > + > > > > + split_page(p, order); > > > What exactly should split_page() do here and why is that > > > necessary? > > > > > > IIRC that function just updated the reference count and updated > > > things > > > like page owner tracking and memcg accounting. Which should both > > > be > > > completely irrelevant here. > > > > > > Or do you just do that so that you can free each page > > > individually? > > Yes, exactly. Like For a 2MiB page we'd otherwise have to allocate > > 2MiB > > of shmem backing storage, potentially from kernel reserves before > > we > > could actually free anything. Since (currently) the shmem objects > > we > > use are 4K-page only, this should make the process "allocate shmem > > and > > back up" much less likely to deplete the kernel memory reserves. > > Ah, yes that makes totally sense now. > > > > > Taking a step back and looking at potentially other solution, like > > direct insertion into the swap cache, then even if inserting a 2MiB > > page into the swap cache, vmscan would split it before writeback, > > and > > still it didn't appear very stable. So inserting one 4K page at a > > time > > seemed neccessary. If I were to take a guess that's why shmem, when > > configured for 2MiB pages, like with i915, also splits the pages > > before > > moving to swap-cache / writeback. > > > > > > > > + nr = 1UL << order; > > > > + while (nr--) > > > > + (p++)->private = 0; > > > > +} > > > > + > > > > +/** > > > > + * DOC: Partial backup and restoration of a struct ttm_tt. > > > > + * > > > > + * Swapout using ttm_backup_backup_page() and swapin using > > > > + * ttm_backup_copy_page() may fail. > > > > + * The former most likely due to lack of swap-space or memory, > > > > the > > > > latter due > > > > + * to lack of memory or because of signal interruption during > > > > waits. > > > > + * > > > > + * Backup failure is easily handled by using a ttm_tt pages > > > > vector > > > > that holds > > > > + * both swap entries and page pointers. This has to be taken > > > > into > > > > account when > > > > + * restoring such a ttm_tt from backup, and when freeing it > > > > while > > > > backed up. > > > > + * When restoring, for simplicity, new pages are actually > > > > allocated from the > > > > + * pool and the contents of any old pages are copied in and > > > > then > > > > the old pages > > > > + * are released. > > > > + * > > > > + * For restoration failures, the struct ttm_pool_tt_restore > > > > holds > > > > sufficient state > > > > + * to be able to resume an interrupted restore, and that > > > > structure > > > > is freed once > > > > + * the restoration is complete. If the struct ttm_tt is > > > > destroyed > > > > while there > > > > + * is a valid struct ttm_pool_tt_restore attached, that is > > > > also > > > > properly taken > > > > + * care of. > > > > + */ > > > > + > > > > +static bool ttm_pool_restore_valid(const struct > > > > ttm_pool_tt_restore *restore) > > > > +{ > > > > + return restore && restore->restored_pages < (1 << > > > > restore- > > > > > order); > > > > +} > > > > + > > > > +static int ttm_pool_restore_tt(struct ttm_pool_tt_restore > > > > *restore, > > > > + struct ttm_backup *backup, > > > > + struct ttm_operation_ctx *ctx) > > > > +{ > > > > + unsigned int i, nr = 1 << restore->order; > > > > + int ret = 0; > > > > + > > > > + if (!ttm_pool_restore_valid(restore)) > > > > + return 0; > > > > + > > > > + for (i = restore->restored_pages; i < nr; ++i) { > > > > + struct page *p = restore->old_pages[i]; > > > > + > > > > + if (ttm_backup_page_ptr_is_handle(p)) { > > > > + unsigned long handle = > > > > ttm_backup_page_ptr_to_handle(p); > > > > + > > > > + if (handle == 0) > > > > + continue; > > > > + > > > > + ret = ttm_backup_copy_page > > > > + (backup, restore- > > > > >first_page[i], > > > > + handle, ctx->interruptible); > > > That coding style looks really odd, I didn't even notice that it > > > is a > > > function call initially. > > > > > > Maybe put everything under the if into a separate function. > > At a minimum, I'll fix up the formatting here. > > > > > > + if (ret) > > > > + break; > > > > + > > > > + ttm_backup_drop(backup, handle); > > > > + } else if (p) { > > > > + /* > > > > + * We could probably avoid splitting > > > > the > > > > old page > > > > + * using clever logic, but ATM we > > > > don't > > > > care, as > > > > + * we prioritize releasing memory > > > > ASAP. > > > > Note that > > > > + * here, the old retained page is > > > > always > > > > write-back > > > > + * cached. > > > > + */ > > > > + ttm_pool_split_for_swap(restore->pool, > > > > p); > > > > + copy_highpage(restore->first_page[i], > > > > p); > > > > + __free_pages(p, 0); > > > > + } > > > > + > > > > + restore->restored_pages++; > > > > + restore->old_pages[i] = NULL; > > > > + cond_resched(); > > > There is a push to remove cond_resched(), see here: > > > https://patchwork.kernel.org/project/linux-mm/patch/20231107230822.371443-30-ankur.a.arora@xxxxxxxxxx/ > > > > > > Not sure in which discussion that removal went, but IIRC we > > > should > > > not > > > add any new users of it. > > I'll read up on that and remove if needed. I'm curious how / if > > voluntary preemption is going to be handled. > > I didn't fully understood it either, but the push kind of seems to be > that drivers or in this cases subsystems are not supposed to mess > with > cond_resched() any more and just rely on preemptive kernels. > > > > > + } > > > > + > > > > + return ret; > > > > +} > > > > + > > > > /* Called when we got a page, either from a pool or newly > > > > allocated */ > > > > static int ttm_pool_page_allocated(struct ttm_pool *pool, > > > > unsigned int order, > > > > struct page *p, dma_addr_t > > > > **dma_addr, > > > > unsigned long *num_pages, > > > > - struct page ***pages) > > > > + struct page ***pages, > > > > + struct ttm_pool_tt_restore > > > > *restore) > > > > { > > > > unsigned int i; > > > > int r; > > > > @@ -369,6 +490,16 @@ static int ttm_pool_page_allocated(struct > > > > ttm_pool *pool, unsigned int order, > > > > return r; > > > > } > > > > > > > > + if (restore) { > > > > + memcpy(restore->old_pages, *pages, > > > > + (1 << order) * sizeof(*restore- > > > > > old_pages)); > > > > + memset(*pages, 0, (1 << order) * > > > > sizeof(**pages)); > > > > + restore->order = order; > > > > + restore->restored_pages = 0; > > > > + restore->first_page = *pages; > > > > + restore->alloced_pages += 1UL << order; > > > > + } > > > > + > > > > *num_pages -= 1 << order; > > > > for (i = 1 << order; i; --i, ++(*pages), ++p) > > > > **pages = p; > > > > @@ -394,22 +525,39 @@ static void ttm_pool_free_range(struct > > > > ttm_pool *pool, struct ttm_tt *tt, > > > > pgoff_t start_page, pgoff_t > > > > end_page) > > > > { > > > > struct page **pages = &tt->pages[start_page]; > > > > + struct ttm_backup *backup = tt->backup; > > > > unsigned int order; > > > > pgoff_t i, nr; > > > > > > > > for (i = start_page; i < end_page; i += nr, pages += > > > > nr) { > > > > struct ttm_pool_type *pt = NULL; > > > > + struct page *p = *pages; > > > > + > > > > + if (ttm_backup_page_ptr_is_handle(p)) { > > > > + unsigned long handle = > > > > ttm_backup_page_ptr_to_handle(p); > > > > + > > > > + nr = 1; > > > > + if (handle != 0) > > > > + ttm_backup_drop(backup, > > > > handle); > > > > + continue; > > > > + } > > > > + > > > > + if (pool) { > > > > + order = ttm_pool_page_order(pool, p); > > > > + nr = (1UL << order); > > > > + if (tt->dma_address) > > > > + ttm_pool_unmap(pool, tt- > > > > > dma_address[i], nr); > > > > > > > > - order = ttm_pool_page_order(pool, *pages); > > > > - nr = (1UL << order); > > > > - if (tt->dma_address) > > > > - ttm_pool_unmap(pool, tt- > > > > >dma_address[i], > > > > nr); > > > > + pt = ttm_pool_select_type(pool, > > > > caching, > > > > order); > > > > + } else { > > > > + order = p->private; > > > > + nr = (1UL << order); > > > > + } > > > > > > > > - pt = ttm_pool_select_type(pool, caching, > > > > order); > > > > if (pt) > > > > - ttm_pool_type_give(pt, *pages); > > > > + ttm_pool_type_give(pt, p); > > > > else > > > > - ttm_pool_free_page(pool, caching, > > > > order, > > > > *pages); > > > > + ttm_pool_free_page(pool, caching, > > > > order, > > > > p); > > > > } > > > > } > > > > > > > > @@ -453,9 +601,36 @@ int ttm_pool_alloc(struct ttm_pool *pool, > > > > struct ttm_tt *tt, > > > > else > > > > gfp_flags |= GFP_HIGHUSER; > > > > > > > > - for (order = min_t(unsigned int, MAX_PAGE_ORDER, > > > > __fls(num_pages)); > > > > - num_pages; > > > > - order = min_t(unsigned int, order, > > > > __fls(num_pages))) > > > > { > > > > + order = min_t(unsigned int, MAX_PAGE_ORDER, > > > > __fls(num_pages)); > > > > + > > > > + if (tt->page_flags & TTM_TT_FLAG_PRIV_BACKED_UP) { > > > > + if (!tt->restore) { > > > > + gfp_t gfp = GFP_KERNEL | __GFP_NOWARN; > > > > + > > > > + if (ctx->gfp_retry_mayfail) > > > > + gfp |= __GFP_RETRY_MAYFAIL; > > > > + > > > > + tt->restore = > > > > + kvzalloc(struct_size(tt- > > > > >restore, > > > > old_pages, > > > > + (size_t)1 > > > > << > > > > order), gfp); > > > > + if (!tt->restore) > > > > + return -ENOMEM; > > > > + } else if (ttm_pool_restore_valid(tt- > > > > >restore)) { > > > > + struct ttm_pool_tt_restore *restore = > > > > tt- > > > > > restore; > > > > + > > > > + num_pages -= restore->alloced_pages; > > > > + order = min_t(unsigned int, order, > > > > __fls(num_pages)); > > > > + pages += restore->alloced_pages; > > > > + r = ttm_pool_restore_tt(restore, tt- > > > > > backup, ctx); > > > > + if (r) > > > > + return r; > > > > + caching = restore->caching_divide; > > > > + } > > > > + > > > > + tt->restore->pool = pool; > > > > + } > > > Hui? Why is that part of the allocation function now? > > > > > > At bare minimum I would expect that this is a new function. > > It's because we now have partially backed up tts, so the restore is > > interleaved on a per-page basis, replacing the backup handles with > > page-pointers. I'll see if I can separate out at least the > > initialization here. > > Yeah, that kind of makes sense. > > My expectation was just that we now have explicit ttm_pool_swapout() > and > ttm_pool_swapin() functions. I fully understand, although in the allocation step, that would also increase the memory pressure since we might momentarily have twice the bo-size allocated, if the shmem object was never swapped out, and we don't want to unnecessarily risc OOM at recover time, although that should be a recoverable situation now. If the OOM receiver can free up system memory resources they can could potentially restart the recover. /Thomas > > Christian. > > > > > /Thomas > > > > > > > Regards, > > > Christian. > > > > > > > + > > > > + for (; num_pages; order = min_t(unsigned int, order, > > > > __fls(num_pages))) { > > > > struct ttm_pool_type *pt; > > > > > > > > page_caching = tt->caching; > > > > @@ -472,11 +647,19 @@ int ttm_pool_alloc(struct ttm_pool *pool, > > > > struct ttm_tt *tt, > > > > r = > > > > ttm_pool_page_allocated(pool, > > > > order, p, > > > > > > > > &dma_addr, > > > > > > > > &num_pages, > > > > - > > > > &pages); > > > > + > > > > &pages, > > > > + > > > > tt- > > > > > restore); > > > > if (r) > > > > goto error_free_page; > > > > > > > > caching = pages; > > > > + if (ttm_pool_restore_valid(tt- > > > > > restore)) { > > > > + r = > > > > ttm_pool_restore_tt(tt->restore, tt->backup, > > > > + > > > > ct > > > > x); > > > > + if (r) > > > > + goto > > > > error_free_all; > > > > + } > > > > + > > > > if (num_pages < (1 << order)) > > > > break; > > > > > > > > @@ -496,9 +679,17 @@ int ttm_pool_alloc(struct ttm_pool *pool, > > > > struct ttm_tt *tt, > > > > caching = pages; > > > > } > > > > r = ttm_pool_page_allocated(pool, > > > > order, > > > > p, &dma_addr, > > > > - > > > > &num_pages, > > > > &pages); > > > > + > > > > &num_pages, > > > > &pages, > > > > + tt- > > > > >restore); > > > > if (r) > > > > goto error_free_page; > > > > + > > > > + if (ttm_pool_restore_valid(tt- > > > > >restore)) { > > > > + r = ttm_pool_restore_tt(tt- > > > > > restore, tt->backup, ctx); > > > > + if (r) > > > > + goto error_free_all; > > > > + } > > > > + > > > > if (PageHighMem(p)) > > > > caching = pages; > > > > } > > > > @@ -517,12 +708,26 @@ int ttm_pool_alloc(struct ttm_pool *pool, > > > > struct ttm_tt *tt, > > > > if (r) > > > > goto error_free_all; > > > > > > > > + if (tt->restore) { > > > > + kvfree(tt->restore); > > > > + tt->restore = NULL; > > > > + } > > > > + > > > > + if (tt->page_flags & TTM_TT_FLAG_PRIV_BACKED_UP) > > > > + tt->page_flags &= ~(TTM_TT_FLAG_PRIV_BACKED_UP > > > > | > > > > + TTM_TT_FLAG_SWAPPED); > > > > + > > > > return 0; > > > > > > > > error_free_page: > > > > ttm_pool_free_page(pool, page_caching, order, p); > > > > > > > > error_free_all: > > > > + if (tt->page_flags & TTM_TT_FLAG_PRIV_BACKED_UP) { > > > > + tt->restore->caching_divide = caching; > > > > + return r; > > > > + } > > > > + > > > > num_pages = tt->num_pages - num_pages; > > > > caching_divide = caching - tt->pages; > > > > ttm_pool_free_range(pool, tt, tt->caching, 0, > > > > caching_divide); > > > > @@ -549,6 +754,171 @@ void ttm_pool_free(struct ttm_pool *pool, > > > > struct ttm_tt *tt) > > > > } > > > > EXPORT_SYMBOL(ttm_pool_free); > > > > > > > > +/** > > > > + * ttm_pool_release_backed_up() - Release content of a > > > > swapped-out > > > > struct ttm_tt > > > > + * @tt: The struct ttm_tt. > > > > + * > > > > + * Release handles with associated content or any remaining > > > > pages > > > > of > > > > + * a backed-up struct ttm_tt. > > > > + */ > > > > +void ttm_pool_release_backed_up(struct ttm_tt *tt) > > > > +{ > > > > + struct ttm_backup *backup = tt->backup; > > > > + struct ttm_pool_tt_restore *restore; > > > > + pgoff_t i, start_page = 0; > > > > + unsigned long handle; > > > > + > > > > + if (!(tt->page_flags & TTM_TT_FLAG_PRIV_BACKED_UP)) > > > > + return; > > > > + > > > > + restore = tt->restore; > > > > + > > > > + if (ttm_pool_restore_valid(restore)) { > > > > + pgoff_t nr = 1UL << restore->order; > > > > + > > > > + for (i = restore->restored_pages; i < nr; ++i) > > > > { > > > > + struct page *p = restore- > > > > >old_pages[i]; > > > > + > > > > + if (ttm_backup_page_ptr_is_handle(p)) > > > > { > > > > + handle = > > > > ttm_backup_page_ptr_to_handle(p); > > > > + if (handle == 0) > > > > + continue; > > > > + > > > > + ttm_backup_drop(backup, > > > > handle); > > > > + } else if (p) { > > > > + ttm_pool_split_for_swap(restor > > > > e- > > > > > pool, p); > > > > + __free_pages(p, 0); > > > > + } > > > > + } > > > > + } > > > > + > > > > + if (restore) { > > > > + pgoff_t mid = restore->caching_divide - tt- > > > > >pages; > > > > + > > > > + start_page = restore->alloced_pages; > > > > + /* Pages that might be dma-mapped and non- > > > > cached > > > > */ > > > > + ttm_pool_free_range(restore->pool, tt, tt- > > > > > caching, > > > > + 0, mid); > > > > + /* Pages that might be dma-mapped but cached > > > > */ > > > > + ttm_pool_free_range(restore->pool, tt, > > > > ttm_cached, > > > > + mid, restore- > > > > >alloced_pages); > > > > + } > > > > + > > > > + /* Shrunken pages. Cached and not dma-mapped. */ > > > > + ttm_pool_free_range(NULL, tt, ttm_cached, start_page, > > > > tt- > > > > > num_pages); > > > > + > > > > + if (restore) { > > > > + kvfree(restore); > > > > + tt->restore = NULL; > > > > + } > > > > + > > > > + tt->page_flags &= ~(TTM_TT_FLAG_PRIV_BACKED_UP | > > > > TTM_TT_FLAG_SWAPPED); > > > > +} > > > > + > > > > +/** > > > > + * ttm_pool_backup_tt() - Back up or purge a struct ttm_tt > > > > + * @pool: The pool used when allocating the struct ttm_tt. > > > > + * @ttm: The struct ttm_tt. > > > > + * @flags: Flags to govern the backup behaviour. > > > > + * > > > > + * Back up or purge a struct ttm_tt. If @purge is true, then > > > > + * all pages will be freed directly to the system rather than > > > > to > > > > the pool > > > > + * they were allocated from, making the function behave > > > > similarly > > > > to > > > > + * ttm_pool_free(). If @purge is false the pages will be > > > > backed up > > > > instead, > > > > + * exchanged for handles. > > > > + * A subsequent call to ttm_pool_alloc() will then read back > > > > the > > > > content and > > > > + * a subsequent call to ttm_pool_release_shrunken() will drop > > > > it. > > > > + * If backup of a page fails for whatever reason, @ttm will > > > > still > > > > be > > > > + * partially backed up, retaining those pages for which backup > > > > fails. > > > > + * > > > > + * Return: Number of pages actually backed up or freed, or > > > > negative > > > > + * error code on error. > > > > + */ > > > > +long ttm_pool_backup_tt(struct ttm_pool *pool, struct ttm_tt > > > > *ttm, > > > > + const struct ttm_backup_flags *flags) > > > > +{ > > > > + struct ttm_backup *backup = ttm->backup; > > > > + struct page *page; > > > > + unsigned long handle; > > > > + gfp_t alloc_gfp; > > > > + gfp_t gfp; > > > > + int ret = 0; > > > > + pgoff_t shrunken = 0; > > > > + pgoff_t i, num_pages; > > > > + > > > > + if ((!ttm_backup_bytes_avail() && !flags->purge) || > > > > + pool->use_dma_alloc || > > > > + (ttm->page_flags & TTM_TT_FLAG_PRIV_BACKED_UP)) > > > > + return -EBUSY; > > > > + > > > > +#ifdef CONFIG_X86 > > > > + /* Anything returned to the system needs to be cached. > > > > */ > > > > + if (ttm->caching != ttm_cached) > > > > + set_pages_array_wb(ttm->pages, ttm- > > > > >num_pages); > > > > +#endif > > > > + > > > > + if (ttm->dma_address || flags->purge) { > > > > + for (i = 0; i < ttm->num_pages; i += > > > > num_pages) { > > > > + unsigned int order; > > > > + > > > > + page = ttm->pages[i]; > > > > + if (unlikely(!page)) { > > > > + num_pages = 1; > > > > + continue; > > > > + } > > > > + > > > > + order = ttm_pool_page_order(pool, > > > > page); > > > > + num_pages = 1UL << order; > > > > + if (ttm->dma_address) > > > > + ttm_pool_unmap(pool, ttm- > > > > > dma_address[i], > > > > + num_pages); > > > > + if (flags->purge) { > > > > + shrunken += num_pages; > > > > + page->private = 0; > > > > + __free_pages(page, order); > > > > + memset(ttm->pages + i, 0, > > > > + num_pages * > > > > sizeof(*ttm- > > > > > pages)); > > > > + } > > > > + } > > > > + } > > > > + > > > > + if (flags->purge) > > > > + return shrunken; > > > > + > > > > + if (pool->use_dma32) > > > > + gfp = GFP_DMA32; > > > > + else > > > > + gfp = GFP_HIGHUSER; > > > > + > > > > + alloc_gfp = GFP_KERNEL | __GFP_HIGH | __GFP_NOWARN | > > > > __GFP_RETRY_MAYFAIL; > > > > + > > > > + for (i = 0; i < ttm->num_pages; ++i) { > > > > + page = ttm->pages[i]; > > > > + if (unlikely(!page)) > > > > + continue; > > > > + > > > > + ttm_pool_split_for_swap(pool, page); > > > > + > > > > + handle = ttm_backup_backup_page(backup, page, > > > > flags->writeback, i, > > > > + gfp, > > > > alloc_gfp); > > > > + if (handle) { > > > > + ttm->pages[i] = > > > > ttm_backup_handle_to_page_ptr(handle); > > > > + put_page(page); > > > > + shrunken++; > > > > + } else { > > > > + /* We allow partially shrunken tts */ > > > > + ret = -ENOMEM; > > > > + break; > > > > + } > > > > + } > > > > + > > > > + if (shrunken) > > > > + ttm->page_flags |= (TTM_TT_FLAG_PRIV_BACKED_UP > > > > | > > > > + TTM_TT_FLAG_SWAPPED); > > > > + > > > > + return shrunken ? shrunken : ret; > > > > +} > > > > + > > > > /** > > > > * ttm_pool_init - Initialize a pool > > > > * > > > > diff --git a/drivers/gpu/drm/ttm/ttm_tt.c > > > > b/drivers/gpu/drm/ttm/ttm_tt.c > > > > index 3baf215eca23..dd4eabe4ad79 100644 > > > > --- a/drivers/gpu/drm/ttm/ttm_tt.c > > > > +++ b/drivers/gpu/drm/ttm/ttm_tt.c > > > > @@ -40,6 +40,7 @@ > > > > #include <drm/drm_cache.h> > > > > #include <drm/drm_device.h> > > > > #include <drm/drm_util.h> > > > > +#include <drm/ttm/ttm_backup.h> > > > > #include <drm/ttm/ttm_bo.h> > > > > #include <drm/ttm/ttm_tt.h> > > > > > > > > @@ -158,6 +159,8 @@ static void ttm_tt_init_fields(struct > > > > ttm_tt > > > > *ttm, > > > > ttm->swap_storage = NULL; > > > > ttm->sg = bo->sg; > > > > ttm->caching = caching; > > > > + ttm->restore = NULL; > > > > + ttm->backup = NULL; > > > > } > > > > > > > > int ttm_tt_init(struct ttm_tt *ttm, struct ttm_buffer_object > > > > *bo, > > > > @@ -182,6 +185,12 @@ void ttm_tt_fini(struct ttm_tt *ttm) > > > > fput(ttm->swap_storage); > > > > ttm->swap_storage = NULL; > > > > > > > > + ttm_pool_release_backed_up(ttm); > > > > + if (ttm->backup) { > > > > + ttm_backup_fini(ttm->backup); > > > > + ttm->backup = NULL; > > > > + } > > > > + > > > > if (ttm->pages) > > > > kvfree(ttm->pages); > > > > else > > > > @@ -253,6 +262,34 @@ int ttm_tt_swapin(struct ttm_tt *ttm) > > > > } > > > > EXPORT_SYMBOL_FOR_TESTS_ONLY(ttm_tt_swapin); > > > > > > > > +/** > > > > + * ttm_tt_backup() - Helper to back up a struct ttm_tt. > > > > + * @bdev: The TTM device. > > > > + * @tt: The struct ttm_tt. > > > > + * @flags: Flags that govern the backup behaviour. > > > > + * > > > > + * Update the page accounting and call ttm_pool_shrink_tt to > > > > free > > > > pages > > > > + * or back them up. > > > > + * > > > > + * Return: Number of pages freed or swapped out, or negative > > > > error > > > > code on > > > > + * error. > > > > + */ > > > > +long ttm_tt_backup(struct ttm_device *bdev, struct ttm_tt *tt, > > > > + const struct ttm_backup_flags flags) > > > > +{ > > > > + long ret; > > > > + > > > > + if (WARN_ON(IS_ERR_OR_NULL(tt->backup))) > > > > + return 0; > > > > + > > > > + ret = ttm_pool_backup_tt(&bdev->pool, tt, &flags); > > > > + > > > > + if (ret > 0) > > > > + tt->page_flags &= ~TTM_TT_FLAG_PRIV_POPULATED; > > > > + > > > > + return ret; > > > > +} > > > > + > > > > /** > > > > * ttm_tt_swapout - swap out tt object > > > > * > > > > diff --git a/include/drm/ttm/ttm_pool.h > > > > b/include/drm/ttm/ttm_pool.h > > > > index 160d954a261e..3112a4be835c 100644 > > > > --- a/include/drm/ttm/ttm_pool.h > > > > +++ b/include/drm/ttm/ttm_pool.h > > > > @@ -33,6 +33,7 @@ > > > > > > > > struct device; > > > > struct seq_file; > > > > +struct ttm_backup_flags; > > > > struct ttm_operation_ctx; > > > > struct ttm_pool; > > > > struct ttm_tt; > > > > @@ -89,6 +90,11 @@ void ttm_pool_fini(struct ttm_pool *pool); > > > > > > > > int ttm_pool_debugfs(struct ttm_pool *pool, struct seq_file > > > > *m); > > > > > > > > +void ttm_pool_release_backed_up(struct ttm_tt *tt); > > > > + > > > > +long ttm_pool_backup_tt(struct ttm_pool *pool, struct ttm_tt > > > > *ttm, > > > > + const struct ttm_backup_flags *flags); > > > > + > > > > int ttm_pool_mgr_init(unsigned long num_pages); > > > > void ttm_pool_mgr_fini(void); > > > > > > > > diff --git a/include/drm/ttm/ttm_tt.h > > > > b/include/drm/ttm/ttm_tt.h > > > > index 991edafdb2dd..6ca2fc7b2a26 100644 > > > > --- a/include/drm/ttm/ttm_tt.h > > > > +++ b/include/drm/ttm/ttm_tt.h > > > > @@ -32,11 +32,13 @@ > > > > #include <drm/ttm/ttm_caching.h> > > > > #include <drm/ttm/ttm_kmap_iter.h> > > > > > > > > +struct ttm_backup; > > > > struct ttm_device; > > > > struct ttm_tt; > > > > struct ttm_resource; > > > > struct ttm_buffer_object; > > > > struct ttm_operation_ctx; > > > > +struct ttm_pool_tt_restore; > > > > > > > > /** > > > > * struct ttm_tt - This is a structure holding the pages, > > > > caching- and aperture > > > > @@ -88,6 +90,9 @@ struct ttm_tt { > > > > * TTM_TT_FLAG_PRIV_POPULATED: TTM internal only. DO > > > > NOT > > > > USE. This is > > > > * set by TTM after ttm_tt_populate() has successfully > > > > returned, and is > > > > * then unset when TTM calls ttm_tt_unpopulate(). > > > > + * > > > > + * TTM_TT_FLAG_PRIV_BACKED_UP: TTM internal only. This > > > > is > > > > set if the > > > > + * struct ttm_tt has been (possibly partially) backed > > > > up. > > > > */ > > > > #define TTM_TT_FLAG_SWAPPED BIT(0) > > > > #define TTM_TT_FLAG_ZERO_ALLOC BIT(1) > > > > @@ -96,6 +101,7 @@ struct ttm_tt { > > > > #define TTM_TT_FLAG_DECRYPTED BIT(4) > > > > > > > > #define TTM_TT_FLAG_PRIV_POPULATED BIT(5) > > > > +#define TTM_TT_FLAG_PRIV_BACKED_UP BIT(6) > > > > uint32_t page_flags; > > > > /** @num_pages: Number of pages in the page array. */ > > > > uint32_t num_pages; > > > > @@ -105,11 +111,20 @@ struct ttm_tt { > > > > dma_addr_t *dma_address; > > > > /** @swap_storage: Pointer to shmem struct file for > > > > swap > > > > storage. */ > > > > struct file *swap_storage; > > > > + /** > > > > + * @backup: Pointer to backup struct for backed up > > > > tts. > > > > + * Could be unified with @swap_storage. Meanwhile, the > > > > driver's > > > > + * ttm_tt_create() callback is responsible for > > > > assigning > > > > + * this field. > > > > + */ > > > > + struct ttm_backup *backup; > > > > /** > > > > * @caching: The current caching state of the pages, > > > > see > > > > enum > > > > * ttm_caching. > > > > */ > > > > enum ttm_caching caching; > > > > + /** @restore: Partial restoration from backup state. > > > > TTM > > > > private */ > > > > + struct ttm_pool_tt_restore *restore; > > > > }; > > > > > > > > /** > > > > @@ -131,7 +146,7 @@ static inline bool > > > > ttm_tt_is_populated(struct > > > > ttm_tt *tt) > > > > > > > > static inline bool ttm_tt_is_swapped(const struct ttm_tt > > > > *tt) > > > > { > > > > - return tt->page_flags & TTM_TT_FLAG_SWAPPED; > > > > + return tt->page_flags & (TTM_TT_FLAG_SWAPPED | > > > > TTM_TT_FLAG_PRIV_BACKED_UP); > > > > } > > > > > > > > /** > > > > @@ -235,6 +250,21 @@ void ttm_tt_mgr_init(unsigned long > > > > num_pages, > > > > unsigned long num_dma32_pages); > > > > struct ttm_kmap_iter *ttm_kmap_iter_tt_init(struct > > > > ttm_kmap_iter_tt *iter_tt, > > > > struct ttm_tt > > > > *tt); > > > > unsigned long ttm_tt_pages_limit(void); > > > > + > > > > +/** > > > > + * struct ttm_backup_flags - Flags to govern backup behaviour. > > > > + * @purge: Free pages without backing up. Bypass pools. > > > > + * @writeback: Attempt to copy contents directly to swap > > > > space, > > > > even > > > > + * if that means blocking on writes to external memory. > > > > + */ > > > > +struct ttm_backup_flags { > > > > + u32 purge : 1; > > > > + u32 writeback : 1; > > > > +}; > > > > + > > > > +long ttm_tt_backup(struct ttm_device *bdev, struct ttm_tt *tt, > > > > + const struct ttm_backup_flags flags); > > > > + > > > > #if IS_ENABLED(CONFIG_AGP) > > > > #include <linux/agp_backend.h> > > > >