blob: 4f7a708da4817de4e3e75c5e1968b4c268726508 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Page Size Emulation
*
* Copyright (c) 2024, Google LLC.
* Author: Kalesh Singh <kaleshsingh@goole.com>
*/
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kstrtox.h>
#include <linux/mm.h>
#include <linux/page_size_compat.h>
#define MIN_PAGE_SHIFT_COMPAT (PAGE_SHIFT + 1)
#define MAX_PAGE_SHIFT_COMPAT 16 /* Max of 64KB */
#define __MMAP_RND_BITS(x) (x - (__PAGE_SHIFT - PAGE_SHIFT))
DEFINE_STATIC_KEY_FALSE(page_shift_compat_enabled);
EXPORT_SYMBOL_GPL(page_shift_compat_enabled);
int page_shift_compat = MIN_PAGE_SHIFT_COMPAT;
EXPORT_SYMBOL_GPL(page_shift_compat);
static int __init early_page_shift_compat(char *buf)
{
int ret;
ret = kstrtoint(buf, 10, &page_shift_compat);
if (ret)
return ret;
/* Only supported on 4KB kernel */
if (PAGE_SHIFT != 12)
return -ENOTSUPP;
if (page_shift_compat < MIN_PAGE_SHIFT_COMPAT ||
page_shift_compat > MAX_PAGE_SHIFT_COMPAT)
return -EINVAL;
static_branch_enable(&page_shift_compat_enabled);
return 0;
}
early_param("page_shift", early_page_shift_compat);
static int __init init_mmap_rnd_bits(void)
{
if (!static_branch_unlikely(&page_shift_compat_enabled))
return 0;
#ifdef CONFIG_HAVE_ARCH_MMAP_RND_BITS
mmap_rnd_bits_min = __MMAP_RND_BITS(CONFIG_ARCH_MMAP_RND_BITS_MIN);
mmap_rnd_bits_max = __MMAP_RND_BITS(CONFIG_ARCH_MMAP_RND_BITS_MAX);
mmap_rnd_bits = __MMAP_RND_BITS(CONFIG_ARCH_MMAP_RND_BITS);
#endif
return 0;
}
core_initcall(init_mmap_rnd_bits);
/*
* Updates len to avoid mapping off the end of the file.
*
* The length of the original mapping must be updated before
* it's VMA is created to avoid an unaligned munmap in the
* MAP_FIXED fixup mapping.
*/
unsigned long ___filemap_len(struct inode *inode, unsigned long pgoff, unsigned long len,
unsigned long flags)
{
unsigned long file_size;
unsigned long new_len;
pgoff_t max_pgcount;
pgoff_t last_pgoff;
if (flags & __MAP_NO_COMPAT)
return len;
file_size = (unsigned long) i_size_read(inode);
/*
* Round up, so that this is a count (not an index). This simplifies
* the following calculations.
*/
max_pgcount = DIV_ROUND_UP(file_size, PAGE_SIZE);
last_pgoff = pgoff + (len >> PAGE_SHIFT);
if (unlikely(last_pgoff >= max_pgcount)) {
new_len = (max_pgcount - pgoff) << PAGE_SHIFT;
/* Careful of underflows in special files */
if (new_len > 0 && new_len < len)
return new_len;
}
return len;
}
static inline bool is_shmem_fault(const struct vm_operations_struct *vm_ops)
{
#ifdef CONFIG_SHMEM
return vm_ops->fault == shmem_fault;
#else
return false;
#endif
}
static inline bool is_f2fs_filemap_fault(const struct vm_operations_struct *vm_ops)
{
#ifdef CONFIG_F2FS_FS
return vm_ops->fault == f2fs_filemap_fault;
#else
return false;
#endif
}
static inline bool is_filemap_fault(const struct vm_operations_struct *vm_ops)
{
return vm_ops->fault == filemap_fault;
}
/*
* This is called to fill any holes created by ___filemap_len()
* with an anonymous mapping.
*/
void ___filemap_fixup(unsigned long addr, unsigned long prot, unsigned long old_len,
unsigned long new_len)
{
unsigned long anon_len = old_len - new_len;
unsigned long anon_addr = addr + new_len;
struct mm_struct *mm = current->mm;
unsigned long populate = 0;
struct vm_area_struct *vma;
const struct vm_operations_struct *vm_ops;
if (!anon_len)
return;
BUG_ON(new_len > old_len);
/* The original do_mmap() failed */
if (IS_ERR_VALUE(addr))
return;
vma = find_vma(mm, addr);
/*
* This should never happen, VMA was inserted and we still
* haven't released the mmap write lock.
*/
BUG_ON(!vma);
vm_ops = vma->vm_ops;
if (!vm_ops)
return;
/*
* Insert fixup vmas for file backed and shmem backed VMAs.
*
* Faulting off the end of a file will result in SIGBUS since there is no
* file page for the given file offset.
*
* shmem pages live in page cache or swap cache. Looking up a page cache
* page with an index (pgoff) beyond the file is invalid and will result
* in shmem_get_folio_gfp() returning -EINVAL.
*/
if (!is_filemap_fault(vm_ops) && !is_f2fs_filemap_fault(vm_ops) &&
!is_shmem_fault(vm_ops))
return;
/*
* Override the end of the file mapping that is off the file
* with an anonymous mapping.
*/
anon_addr = do_mmap(NULL, anon_addr, anon_len, prot,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED|__MAP_NO_COMPAT,
0, 0, &populate, NULL);
}
/*
* Folds any anon fixup entries created by ___filemap_fixup()
* into the previous mapping so that /proc/<pid>/[s]maps don't
* show unaliged entries.
*/
void __fold_filemap_fixup_entry(struct vma_iterator *iter, unsigned long *end)
{
struct vm_area_struct *next_vma;
/* Not emulating page size? */
if (!static_branch_unlikely(&page_shift_compat_enabled))
return;
/* Advance iterator */
next_vma = vma_next(iter);
/* If fixup VMA, adjust the end to cover its extent */
if (next_vma && (next_vma->vm_flags & __VM_NO_COMPAT)) {
*end = next_vma->vm_end;
return;
}
/* Rewind iterator */
vma_prev(iter);
}