/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __LINUX_PAGE_SIZE_COMPAT_H #define __LINUX_PAGE_SIZE_COMPAT_H /* * include/linux/page_size_compat.h * * Page Size Emulation * * Copyright (c) 2024, Google LLC. * Author: Kalesh Singh * Helper macros for page size emulation. * * The macros for use with the emulated page size are all * namespaced by the prefix '__'. * * The valid range of androidboot.page_shift is [13, 16]. * In other words page sizes of 8KB, 16KB, 32KB and 64KB can * be emulated. */ #include #define __MAX_PAGE_SHIFT 14 #define __MAX_PAGE_SIZE (_AC(1,UL) << __MAX_PAGE_SHIFT) #define __MAX_PAGE_MASK (~(__MAX_PAGE_SIZE-1)) #ifndef __ASSEMBLY__ #include #include #include #include #include #define pgcompat_err(fmt, ...) \ pr_err("pgcompat [%i (%s)]: " fmt, task_pid_nr(current), current->comm, ## __VA_ARGS__) DECLARE_STATIC_KEY_FALSE(page_shift_compat_enabled); extern int page_shift_compat __ro_after_init; #ifdef CONFIG_SHMEM extern vm_fault_t shmem_fault(struct vm_fault *vmf); #endif /* CONFIG_SHMEM */ #ifdef CONFIG_F2FS_FS extern vm_fault_t f2fs_filemap_fault(struct vm_fault *vmf); #endif /* CONFIG_F2FS_FS */ #ifdef CONFIG_X86_64 static __always_inline unsigned __page_shift(void) { if (static_branch_unlikely(&page_shift_compat_enabled)) return page_shift_compat; else return PAGE_SHIFT; } #else /* !CONFIG_X86_64 */ #define __page_shift() PAGE_SHIFT #endif /* CONFIG_X86_64 */ #define __PAGE_SHIFT __page_shift() #define __PAGE_SIZE (_AC(1,UL) << __PAGE_SHIFT) #define __PAGE_MASK (~(__PAGE_SIZE-1)) #define __PAGE_ALIGN(addr) ALIGN(addr, __PAGE_SIZE) #define __PAGE_ALIGN_DOWN(addr) ALIGN_DOWN(addr, __PAGE_SIZE) #define __offset_in_page(p) ((unsigned long)(p) & ~__PAGE_MASK) #define __offset_in_page_log(addr) \ ({ \ if (static_branch_unlikely(&page_shift_compat_enabled) && \ __offset_in_page(addr)) \ pgcompat_err("%s: addr (0x%08lx) not page aligned", __func__, addr); \ (__offset_in_page(addr)); \ }) #define __PAGE_ALIGNED(addr) (!__offset_in_page_log(addr)) /* * Increases @size by an adequate amount to allow __PAGE_SIZE alignment * by rounding up; given that @size is already a multiple of the * base page size (PAGE_SIZE). * * Example: * If __PAGE_SHIFT == PAGE_SHIFT == 12 * @size is increased by 0 * ((1 << (0)) - 1) << PAGE_SHIFT * (1 ) - 1) << PAGE_SHIFT * (0 ) << PAGE_SHIFT * * If __PAGE_SHIFT == 13 and PAGE_SHIFT == 12 * @size is increased by PAGE_SIZE (4KB): * ((1 << (1)) - 1) << PAGE_SHIFT * (2 ) - 1) << PAGE_SHIFT * (1 ) << PAGE_SHIFT * If __PAGE_SHIFT == 14 and PAGE_SHIFT == 12 * @size is increased by 3xPAGE_SIZE (12KB): * ((1 << (2)) - 1) << PAGE_SHIFT * (4 ) - 1) << PAGE_SHIFT * (3 ) << PAGE_SHIFT * ... */ #define __PAGE_SIZE_ROUND_UP_ADJ(size) \ ((size) + (((1 << (__PAGE_SHIFT - PAGE_SHIFT)) - 1) << PAGE_SHIFT)) /* * VMA is exempt from emulated page align requirements * * NOTE: __MAP_NO_COMPAT is not new UABI it is only ever set by the kernel * in ___filemap_fixup() */ #define __VM_NO_COMPAT _BITULL(58) #define __MAP_NO_COMPAT _BITUL(31) /* * Conditional page-alignment based on mmap flags * * If the VMA is allowed to not respect the emulated page size, align using the * base PAGE_SIZE, else align using the emulated __PAGE_SIZE. */ #define __COMPAT_PAGE_ALIGN(size, flags) \ (flags & __MAP_NO_COMPAT) ? PAGE_ALIGN(size) : __PAGE_ALIGN(size) /* * Combines the mmap "flags" argument into "vm_flags" * * If page size emulation is enabled, adds translation of the no-compat flag. */ static __always_inline unsigned long calc_vm_flag_bits(struct file *file, unsigned long flags) { unsigned long flag_bits = __calc_vm_flag_bits(file, flags); if (static_branch_unlikely(&page_shift_compat_enabled)) flag_bits |= _calc_vm_trans(flags, __MAP_NO_COMPAT, __VM_NO_COMPAT ); return flag_bits; } extern unsigned long ___filemap_len(struct inode *inode, unsigned long pgoff, unsigned long len, unsigned long flags); extern void ___filemap_fixup(unsigned long addr, unsigned long prot, unsigned long file_backed_len, unsigned long len); static __always_inline unsigned long __filemap_len(struct inode *inode, unsigned long pgoff, unsigned long len, unsigned long flags) { if (static_branch_unlikely(&page_shift_compat_enabled)) return ___filemap_len(inode, pgoff, len, flags); else return len; } static __always_inline void __filemap_fixup(unsigned long addr, unsigned long prot, unsigned long file_backed_len, unsigned long len) { if (static_branch_unlikely(&page_shift_compat_enabled)) ___filemap_fixup(addr, prot, file_backed_len, len); } extern void __fold_filemap_fixup_entry(struct vma_iterator *iter, unsigned long *end); extern int __fixup_swap_header(struct file *swap_file, struct address_space *mapping); #ifdef CONFIG_PROC_PAGE_MONITOR extern bool __is_emulated_pagemap_file(struct file *file); #else static inline bool __is_emulated_pagemap_file(struct file *file) { return false; } #endif static __always_inline void __adjust_cachestat_counters(struct cachestat *cs) { unsigned int nr_sub_pages = __PAGE_SIZE / PAGE_SIZE; if (nr_sub_pages <= 1) return; cs->nr_cache /= nr_sub_pages; cs->nr_dirty /= nr_sub_pages; cs->nr_writeback /= nr_sub_pages; cs->nr_evicted /= nr_sub_pages; cs->nr_recently_evicted /= nr_sub_pages; } #endif /* !__ASSEMBLY__ */ #endif /* __LINUX_PAGE_SIZE_COMPAT_H */