// SPDX-License-Identifier: MIT
/*
* Copyright © 2024 Intel Corporation
*/
#include <drm/ttm/ttm_backup.h>
#include <linux/export.h>
#include <linux/swap.h>
#include "ttm_pool_internal.h"
/*
* Need to map shmem indices to handle since a handle value
* of 0 means error, following the swp_entry_t convention.
*/
static unsigned long ttm_backup_shmem_idx_to_handle(pgoff_t idx)
{
return (unsigned long)idx + 1;
}
static pgoff_t ttm_backup_handle_to_shmem_idx(pgoff_t handle)
{
return handle - 1;
}
/**
* ttm_backup_drop() - release memory associated with a handle
* @backup: The struct backup pointer used to obtain the handle
* @handle: The handle obtained from the @backup_page function.
*/
void ttm_backup_drop(struct file *backup, pgoff_t handle)
{
loff_t start = ttm_backup_handle_to_shmem_idx(handle);
start <<= PAGE_SHIFT;
shmem_truncate_range(file_inode(backup), start,
start + PAGE_SIZE - 1);
}
/**
* ttm_backup_copy_page() - Copy the contents of a previously backed
* up page
* @backup: The struct backup pointer used to back up the page.
* @dst: The struct page to copy into.
* @handle: The handle returned when the page was backed up.
* @intr: Try to perform waits interruptible or at least killable.
* @additional_gfp: GFP mask to add to the default GFP mask if any.
*
* Return: 0 on success, Negative error code on failure, notably
* -EINTR if @intr was set to true and a signal is pending.
*/
int ttm_backup_copy_page(struct file *backup, struct page *dst,
pgoff_t handle, bool intr, gfp_t additional_gfp)
{
struct address_space *mapping = backup->f_mapping;
struct folio *from_folio;
pgoff_t idx = ttm_backup_handle_to_shmem_idx(handle);
from_folio = shmem_read_folio_gfp(mapping, idx, mapping_gfp_mask(mapping)
| additional_gfp);
if (IS_ERR(from_folio))
return PTR_ERR(from_folio);
copy_highpage(dst, folio_file_page(from_folio, idx));
folio_put(from_folio);
return 0;
}
/**
* ttm_backup_backup_folio() - Backup a folio
* @backup: The struct backup pointer to use.
* @folio: The folio to back up.
* @order: The allocation order of @folio. Since TTM allocates higher-order
* pages without __GFP_COMP, folio_nr_pages(@folio) would always
* return 1; the caller must pass the true order explicitly.
* @writeback: Whether to perform immediate writeback of the folio's pages.
* This may have performance implications.
* @idx: A unique integer for the first page of the folio and each struct backup.
* This allows the backup implementation to avoid managing
* its address space separately.
* @folio_gfp: The gfp value used when the folio was allocated.
* Currently unused.
* @alloc_gfp: The gfp to be used when allocating memory.
* @nr_pages_backed: Output. On a successful return, set to the number of
* pages actually backed up, which may be less than (1 << @order)
* if an -ENOMEM was encountered mid-folio.
*
* Context: If called from reclaim context, the caller needs to
* assert that the shrinker gfp has __GFP_FS set, to avoid
* deadlocking on lock_page(). If @writeback is set to true and
* called from reclaim context, the caller also needs to assert
* that the shrinker gfp has __GFP_IO set, since without it,
* we're not allowed to start backup IO.
*
* Return: A handle for the first backed-up page on success (handles for
* subsequent pages follow sequentially). -ENOMEM if no pages could be backed
* up. Any other negative error code if a non-ENOMEM failure occurred; in that
* case any pages backed up so far are truncated before returning.
*/
s64
ttm_backup_backup_folio(struct file *backup, struct folio *folio,
unsigned int order, bool writeback, pgoff_t idx,
gfp_t folio_gfp, gfp_t alloc_gfp,
pgoff_t *nr_pages_backed)
{
struct address_space *mapping = backup->f_mapping;
int nr_pages = 1 << order;
struct folio *to_folio;
int ret, i;
*nr_pages_backed = 0;
for (i = 0; i < nr_pages; ) {
int to_nr, j;
/*
* Only inject past the first subpage so *nr_pages_backed is
* always > 0 here, matching a genuine mid-compound -ENOMEM
* and driving the caller's reactive split fallback instead
* of an early, no-progress failure.
*/
if (IS_ENABLED(CONFIG_FAULT_INJECTION) && i &&
ttm_backup_fault_inject_folio())
to_folio = ERR_PTR(-ENOMEM);
else
to_folio = shmem_read_folio_gfp(mapping, idx + i, alloc_gfp);
if (IS_ERR(to_folio)) {
int err = PTR_ERR(to_folio);
if (err == -ENOMEM && *nr_pages_backed)
return ttm_backup_shmem_idx_to_handle(idx);
if (*nr_pages_backed) {
shmem_truncate_range(file_inode(backup),
(loff_t)idx << PAGE_SHIFT,
((loff_t)(idx + i) << PAGE_SHIFT) - 1);
/*
* The pages just truncated are no longer
* backed up; don't let the caller mistake
* them for valid handles.
*/
*nr_pages_backed = 0;
}
return err;
}
to_nr = min_t(int, nr_pages - i,
folio_next_index(to_folio) - (idx + i));
folio_mark_accessed(to_folio);
folio_lock(to_folio);
folio_mark_dirty(to_folio);
for (j = 0; j < to_nr; j++)
copy_highpage(folio_file_page(to_folio, idx + i + j),
folio_page(folio, i + j));
if (writeback && !folio_mapped(to_folio) &&
folio_clear_dirty_for_io(to_folio)) {
folio_set_reclaim(to_folio);
ret = shmem_write_folio(to_folio);
if (!folio_test_writeback(to_folio))
folio_clear_reclaim(to_folio);
if (ret == AOP_WRITEPAGE_ACTIVATE)
folio_unlock(to_folio);
} else {
folio_unlock(to_folio);
}
folio_put(to_folio);
i += to_nr;
*nr_pages_backed = i;
}
return ttm_backup_shmem_idx_to_handle(idx);
}
/**
* ttm_backup_fini() - Free the struct backup resources after last use.
* @backup: Pointer to the struct backup whose resources to free.
*
* After a call to this function, it's illegal to use the @backup pointer.
*/
void ttm_backup_fini(struct file *backup)
{
fput(backup);
}
/**
* ttm_backup_bytes_avail() - Report the approximate number of bytes of backup space
* left for backup.
*
* This function is intended also for driver use to indicate whether a
* backup attempt is meaningful.
*
* Return: An approximate size of backup space available.
*/
u64 ttm_backup_bytes_avail(void)
{
/*
* The idea behind backing up to shmem is that shmem objects may
* eventually be swapped out. So no point swapping out if there
* is no or low swap-space available. But the accuracy of this
* number also depends on shmem actually swapping out backed-up
* shmem objects without too much buffering.
*/
return (u64)get_nr_swap_pages() << PAGE_SHIFT;
}
EXPORT_SYMBOL_GPL(ttm_backup_bytes_avail);
/**
* ttm_backup_shmem_create() - Create a shmem-based struct backup.
* @size: The maximum size (in bytes) to back up.
*
* Create a backup utilizing shmem objects.
*
* Return: A pointer to a struct file on success,
* an error pointer on error.
*/
struct file *ttm_backup_shmem_create(loff_t size)
{
return shmem_file_setup("ttm shmem backup", size,
EMPTY_VMA_FLAGS);
}