2019-07-15 18:50:57 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2018 Oracle . All Rights Reserved .
* Author : Darrick J . Wong < darrick . wong @ oracle . com >
*/
# include <linux/module.h>
# include <linux/compiler.h>
# include <linux/fs.h>
# include <linux/iomap.h>
# include <linux/swap.h>
/* Swapfile activation */
struct iomap_swapfile_info {
struct iomap iomap ; /* accumulated iomap */
struct swap_info_struct * sis ;
uint64_t lowest_ppage ; /* lowest physical addr seen (pages) */
uint64_t highest_ppage ; /* highest physical addr seen (pages) */
unsigned long nr_pages ; /* number of pages collected */
int nr_extents ; /* extent count */
2021-03-26 20:55:40 +03:00
struct file * file ;
2019-07-15 18:50:57 +03:00
} ;
/*
* Collect physical extents for this swap file . Physical extents reported to
* the swap code must be trimmed to align to a page boundary . The logical
* offset within the file is irrelevant since the swapfile code maps logical
* page numbers of the swap device to the physical page - aligned extents .
*/
static int iomap_swapfile_add_extent ( struct iomap_swapfile_info * isi )
{
struct iomap * iomap = & isi - > iomap ;
unsigned long nr_pages ;
uint64_t first_ppage ;
uint64_t first_ppage_reported ;
uint64_t next_ppage ;
int error ;
/*
* Round the start up and the end down so that the physical
* extent aligns to a page boundary .
*/
first_ppage = ALIGN ( iomap - > addr , PAGE_SIZE ) > > PAGE_SHIFT ;
next_ppage = ALIGN_DOWN ( iomap - > addr + iomap - > length , PAGE_SIZE ) > >
PAGE_SHIFT ;
/* Skip too-short physical extents. */
if ( first_ppage > = next_ppage )
return 0 ;
nr_pages = next_ppage - first_ppage ;
/*
* Calculate how much swap space we ' re adding ; the first page contains
* the swap header and doesn ' t count . The mm still wants that first
* page fed to add_swap_extent , however .
*/
first_ppage_reported = first_ppage ;
if ( iomap - > offset = = 0 )
first_ppage_reported + + ;
if ( isi - > lowest_ppage > first_ppage_reported )
isi - > lowest_ppage = first_ppage_reported ;
if ( isi - > highest_ppage < ( next_ppage - 1 ) )
isi - > highest_ppage = next_ppage - 1 ;
/* Add extent, set up for the next call. */
error = add_swap_extent ( isi - > sis , isi - > nr_pages , nr_pages , first_ppage ) ;
if ( error < 0 )
return error ;
isi - > nr_extents + = error ;
isi - > nr_pages + = nr_pages ;
return 0 ;
}
2021-03-26 20:55:40 +03:00
static int iomap_swapfile_fail ( struct iomap_swapfile_info * isi , const char * str )
{
char * buf , * p = ERR_PTR ( - ENOMEM ) ;
buf = kmalloc ( PATH_MAX , GFP_KERNEL ) ;
if ( buf )
p = file_path ( isi - > file , buf , PATH_MAX ) ;
pr_err ( " swapon: file %s %s \n " , IS_ERR ( p ) ? " <unknown> " : p , str ) ;
kfree ( buf ) ;
return - EINVAL ;
}
2019-07-15 18:50:57 +03:00
/*
* Accumulate iomaps for this swap file . We have to accumulate iomaps because
* swap only cares about contiguous page - aligned physical extents and makes no
* distinction between written and unwritten extents .
*/
2021-08-11 04:33:12 +03:00
static loff_t iomap_swapfile_iter ( const struct iomap_iter * iter ,
struct iomap * iomap , struct iomap_swapfile_info * isi )
2019-07-15 18:50:57 +03:00
{
switch ( iomap - > type ) {
case IOMAP_MAPPED :
case IOMAP_UNWRITTEN :
/* Only real or unwritten extents. */
break ;
case IOMAP_INLINE :
/* No inline data. */
2021-03-26 20:55:40 +03:00
return iomap_swapfile_fail ( isi , " is inline " ) ;
2019-07-15 18:50:57 +03:00
default :
2021-03-26 20:55:40 +03:00
return iomap_swapfile_fail ( isi , " has unallocated extents " ) ;
2019-07-15 18:50:57 +03:00
}
/* No uncommitted metadata or shared blocks. */
2021-03-26 20:55:40 +03:00
if ( iomap - > flags & IOMAP_F_DIRTY )
return iomap_swapfile_fail ( isi , " is not committed " ) ;
if ( iomap - > flags & IOMAP_F_SHARED )
return iomap_swapfile_fail ( isi , " has shared extents " ) ;
2019-07-15 18:50:57 +03:00
/* Only one bdev per swap file. */
2021-03-26 20:55:40 +03:00
if ( iomap - > bdev ! = isi - > sis - > bdev )
return iomap_swapfile_fail ( isi , " outside the main device " ) ;
2019-07-15 18:50:57 +03:00
if ( isi - > iomap . length = = 0 ) {
/* No accumulated extent, so just store it. */
memcpy ( & isi - > iomap , iomap , sizeof ( isi - > iomap ) ) ;
} else if ( isi - > iomap . addr + isi - > iomap . length = = iomap - > addr ) {
/* Append this to the accumulated extent. */
isi - > iomap . length + = iomap - > length ;
} else {
/* Otherwise, add the retained iomap and store this one. */
2021-08-11 04:33:12 +03:00
int error = iomap_swapfile_add_extent ( isi ) ;
2019-07-15 18:50:57 +03:00
if ( error )
return error ;
memcpy ( & isi - > iomap , iomap , sizeof ( isi - > iomap ) ) ;
}
2021-08-11 04:33:12 +03:00
return iomap_length ( iter ) ;
2019-07-15 18:50:57 +03:00
}
/*
* Iterate a swap file ' s iomaps to construct physical extents that can be
* passed to the swapfile subsystem .
*/
int iomap_swapfile_activate ( struct swap_info_struct * sis ,
struct file * swap_file , sector_t * pagespan ,
const struct iomap_ops * ops )
{
2021-08-11 04:33:12 +03:00
struct inode * inode = swap_file - > f_mapping - > host ;
struct iomap_iter iter = {
. inode = inode ,
. pos = 0 ,
. len = ALIGN_DOWN ( i_size_read ( inode ) , PAGE_SIZE ) ,
. flags = IOMAP_REPORT ,
} ;
2019-07-15 18:50:57 +03:00
struct iomap_swapfile_info isi = {
. sis = sis ,
. lowest_ppage = ( sector_t ) - 1ULL ,
2021-03-26 20:55:40 +03:00
. file = swap_file ,
2019-07-15 18:50:57 +03:00
} ;
2021-08-11 04:33:12 +03:00
int ret ;
2019-07-15 18:50:57 +03:00
/*
* Persist all file mapping metadata so that we won ' t have any
* IOMAP_F_DIRTY iomaps .
*/
ret = vfs_fsync ( swap_file , 1 ) ;
if ( ret )
return ret ;
2021-08-11 04:33:12 +03:00
while ( ( ret = iomap_iter ( & iter , ops ) ) > 0 )
iter . processed = iomap_swapfile_iter ( & iter , & iter . iomap , & isi ) ;
if ( ret < 0 )
return ret ;
2019-07-15 18:50:57 +03:00
if ( isi . iomap . length ) {
ret = iomap_swapfile_add_extent ( & isi ) ;
if ( ret )
return ret ;
}
2021-03-09 20:29:11 +03:00
/*
* If this swapfile doesn ' t contain even a single page - aligned
* contiguous range of blocks , reject this useless swapfile to
* prevent confusion later on .
*/
if ( isi . nr_pages = = 0 ) {
pr_warn ( " swapon: Cannot find a single usable page in file. \n " ) ;
return - EINVAL ;
}
2019-07-15 18:50:57 +03:00
* pagespan = 1 + isi . highest_ppage - isi . lowest_ppage ;
sis - > max = isi . nr_pages ;
sis - > pages = isi . nr_pages - 1 ;
sis - > highest_bit = isi . nr_pages - 1 ;
return isi . nr_extents ;
}
EXPORT_SYMBOL_GPL ( iomap_swapfile_activate ) ;