forked from Minki/linux
RISC-V Patches for the 5.10 Merge Window, Part 1
This contains a handful of cleanups and new features, including: * A handful of cleanups for our page fault handling. * Improvements to how we fill out cacheinfo. * Support for EFI-based systems. --- This contains a merge from the EFI tree that was necessary as some of the EFI support landed over there. It's my first time doing something like this, I haven't included the set_fs stuff because the base branch it depends on hasn't been merged yet. I'll probably have another merge window PR, as there's more in flight (most notably the fix for new binutils I just sent out), but I figured there was no reason to delay this any longer. -----BEGIN PGP SIGNATURE----- iQJHBAABCgAxFiEEKzw3R0RoQ7JKlDp6LhMZ81+7GIkFAl+KQ6gTHHBhbG1lckBk YWJiZWx0LmNvbQAKCRAuExnzX7sYibmwD/4qWfOW7R/kUWi08ethcaAhNEWLvqIh 2/KjGLORw+NTZ1F4pEFyQG5LRd3yWDT/UXh/k8gXINqmdclNV01Z3T+O7WuRlISs 07i26W1qRpNeJ7lDVhr9foKpeOU/AXvidgoF330nGlyO4HZkYKhK2yB3t8uGWywr Zt/EpMJeBIRKzWiLhOgLAdYJthhZ9AlnouNnr9myHnO5Ksel+AZ/BKYvn7ZbHMns 6vFUxp6392/LERRRIfDqPsTuxPIYMHjuEsGSESLsjAIyq/shgN1knG/C+zwU5DcK zUDBt1DEP7Tb45w7VBASSjn1M+cUolz9/c2dBhlVcdBlk1GKF+KILSTmWUBpQ8oP ETVAuQK5HTcjy9bVcJMj0Oa3mFshVAAByOH+Wyrdo+qSLkb7y3spPvsL4dyjrKjL +pe6C7WvavaEFoQXVWO2sTUBGYt7qDLRdrDgOGBIHylTXhTxf2wYzAF4ZmDROECT Qfc7Ac3aIWYvWDmxE+x8OniuclfZ0DndKLKQj6FJWUTIxFZzTxsHK75d47D1ID0S ZwAmUd0eYjjwMTO/6AM/Aqu3o8IP4GOXjJf4ijxH9+LjpUhm/ibmHDAUY69sU1WX kdX51gQzoEuW7XMVz1HoTSvaGGKtyFDuRxs8RG/tSFaRtznRz0Sro6BpLCeG968n k/d5WL/vZZ/NDA== =FYs/ -----END PGP SIGNATURE----- Merge tag 'riscv-for-linus-5.10-mw0' of git://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux Pull RISC-V updates from Palmer Dabbelt: "A handful of cleanups and new features: - A handful of cleanups for our page fault handling - Improvements to how we fill out cacheinfo - Support for EFI-based systems" * tag 'riscv-for-linus-5.10-mw0' of git://git.kernel.org/pub/scm/linux/kernel/git/riscv/linux: (22 commits) RISC-V: Add page table dump support for uefi RISC-V: Add EFI runtime services RISC-V: Add EFI stub support. RISC-V: Add PE/COFF header for EFI stub RISC-V: Implement late mapping page table allocation functions RISC-V: Add early ioremap support RISC-V: Move DT mapping outof fixmap RISC-V: Fix duplicate included thread_info.h riscv/mm/fault: Set FAULT_FLAG_INSTRUCTION flag in do_page_fault() riscv/mm/fault: Fix inline placement in vmalloc_fault() declaration riscv: Add cache information in AUX vector riscv: Define AT_VECTOR_SIZE_ARCH for ARCH_DLINFO riscv: Set more data to cacheinfo riscv/mm/fault: Move access error check to function riscv/mm/fault: Move FAULT_FLAG_WRITE handling in do_page_fault() riscv/mm/fault: Simplify mm_fault_error() riscv/mm/fault: Move fault error handling to mm_fault_error() riscv/mm/fault: Simplify fault error handling riscv/mm/fault: Move vmalloc fault handling to vmalloc_fault() riscv/mm/fault: Move bad area handling to bad_area() ...
This commit is contained in:
commit
270315b823
@ -38,6 +38,7 @@ config RISCV
|
||||
select GENERIC_ARCH_TOPOLOGY if SMP
|
||||
select GENERIC_ATOMIC64 if !64BIT
|
||||
select GENERIC_CLOCKEVENTS
|
||||
select GENERIC_EARLY_IOREMAP
|
||||
select GENERIC_GETTIMEOFDAY if HAVE_GENERIC_VDSO
|
||||
select GENERIC_IOREMAP
|
||||
select GENERIC_IRQ_MULTI_HANDLER
|
||||
@ -388,6 +389,28 @@ config CMDLINE_FORCE
|
||||
|
||||
endchoice
|
||||
|
||||
config EFI_STUB
|
||||
bool
|
||||
|
||||
config EFI
|
||||
bool "UEFI runtime support"
|
||||
depends on OF
|
||||
select LIBFDT
|
||||
select UCS2_STRING
|
||||
select EFI_PARAMS_FROM_FDT
|
||||
select EFI_STUB
|
||||
select EFI_GENERIC_STUB
|
||||
select EFI_RUNTIME_WRAPPERS
|
||||
select RISCV_ISA_C
|
||||
depends on MMU
|
||||
default y
|
||||
help
|
||||
This option provides support for runtime services provided
|
||||
by UEFI firmware (such as non-volatile variables, realtime
|
||||
clock, and platform reset). A UEFI stub is also provided to
|
||||
allow the kernel to be booted as an EFI application. This
|
||||
is only useful on systems that have UEFI firmware.
|
||||
|
||||
endmenu
|
||||
|
||||
config BUILTIN_DTB
|
||||
@ -400,3 +423,5 @@ menu "Power management options"
|
||||
source "kernel/power/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
source "drivers/firmware/Kconfig"
|
||||
|
@ -80,6 +80,7 @@ head-y := arch/riscv/kernel/head.o
|
||||
core-y += arch/riscv/
|
||||
|
||||
libs-y += arch/riscv/lib/
|
||||
libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a
|
||||
|
||||
PHONY += vdso_install
|
||||
vdso_install:
|
||||
|
@ -130,3 +130,4 @@ CONFIG_DEBUG_BLOCK_EXT_DEVT=y
|
||||
# CONFIG_RUNTIME_TESTING_MENU is not set
|
||||
CONFIG_MEMTEST=y
|
||||
# CONFIG_SYSFS_SYSCALL is not set
|
||||
CONFIG_EFI=y
|
||||
|
@ -1,4 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
generic-y += early_ioremap.h
|
||||
generic-y += extable.h
|
||||
generic-y += flat.h
|
||||
generic-y += kvm_para.h
|
||||
|
@ -1,4 +1,7 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020 SiFive
|
||||
*/
|
||||
|
||||
#ifndef _ASM_RISCV_CACHEINFO_H
|
||||
#define _ASM_RISCV_CACHEINFO_H
|
||||
@ -11,5 +14,7 @@ struct riscv_cacheinfo_ops {
|
||||
};
|
||||
|
||||
void riscv_set_cacheinfo_ops(struct riscv_cacheinfo_ops *ops);
|
||||
uintptr_t get_cache_size(u32 level, enum cache_type type);
|
||||
uintptr_t get_cache_geometry(u32 level, enum cache_type type);
|
||||
|
||||
#endif /* _ASM_RISCV_CACHEINFO_H */
|
||||
|
55
arch/riscv/include/asm/efi.h
Normal file
55
arch/riscv/include/asm/efi.h
Normal file
@ -0,0 +1,55 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
*/
|
||||
#ifndef _ASM_EFI_H
|
||||
#define _ASM_EFI_H
|
||||
|
||||
#include <asm/csr.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/tlbflush.h>
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
extern void efi_init(void);
|
||||
#else
|
||||
#define efi_init()
|
||||
#endif
|
||||
|
||||
int efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md);
|
||||
int efi_set_mapping_permissions(struct mm_struct *mm, efi_memory_desc_t *md);
|
||||
|
||||
#define arch_efi_call_virt_setup() efi_virtmap_load()
|
||||
#define arch_efi_call_virt_teardown() efi_virtmap_unload()
|
||||
|
||||
#define arch_efi_call_virt(p, f, args...) p->f(args)
|
||||
|
||||
#define ARCH_EFI_IRQ_FLAGS_MASK (SR_IE | SR_SPIE)
|
||||
|
||||
/* on RISC-V, the FDT may be located anywhere in system RAM */
|
||||
static inline unsigned long efi_get_max_fdt_addr(unsigned long image_addr)
|
||||
{
|
||||
return ULONG_MAX;
|
||||
}
|
||||
|
||||
/* Load initrd at enough distance from DRAM start */
|
||||
static inline unsigned long efi_get_max_initrd_addr(unsigned long image_addr)
|
||||
{
|
||||
return image_addr + SZ_256M;
|
||||
}
|
||||
|
||||
#define alloc_screen_info(x...) (&screen_info)
|
||||
|
||||
static inline void free_screen_info(struct screen_info *si)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void efifb_setup_from_dmi(struct screen_info *si, const char *opt)
|
||||
{
|
||||
}
|
||||
|
||||
void efi_virtmap_load(void);
|
||||
void efi_virtmap_unload(void);
|
||||
|
||||
#endif /* _ASM_EFI_H */
|
@ -11,6 +11,7 @@
|
||||
#include <uapi/asm/elf.h>
|
||||
#include <asm/auxvec.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/cacheinfo.h>
|
||||
|
||||
/*
|
||||
* These are used to set parameters in the core dumps.
|
||||
@ -61,6 +62,18 @@ extern unsigned long elf_hwcap;
|
||||
do { \
|
||||
NEW_AUX_ENT(AT_SYSINFO_EHDR, \
|
||||
(elf_addr_t)current->mm->context.vdso); \
|
||||
NEW_AUX_ENT(AT_L1I_CACHESIZE, \
|
||||
get_cache_size(1, CACHE_TYPE_INST)); \
|
||||
NEW_AUX_ENT(AT_L1I_CACHEGEOMETRY, \
|
||||
get_cache_geometry(1, CACHE_TYPE_INST)); \
|
||||
NEW_AUX_ENT(AT_L1D_CACHESIZE, \
|
||||
get_cache_size(1, CACHE_TYPE_DATA)); \
|
||||
NEW_AUX_ENT(AT_L1D_CACHEGEOMETRY, \
|
||||
get_cache_geometry(1, CACHE_TYPE_DATA)); \
|
||||
NEW_AUX_ENT(AT_L2_CACHESIZE, \
|
||||
get_cache_size(2, CACHE_TYPE_UNIFIED)); \
|
||||
NEW_AUX_ENT(AT_L2_CACHEGEOMETRY, \
|
||||
get_cache_geometry(2, CACHE_TYPE_UNIFIED)); \
|
||||
} while (0)
|
||||
#define ARCH_HAS_SETUP_ADDITIONAL_PAGES
|
||||
struct linux_binprm;
|
||||
|
@ -22,14 +22,24 @@
|
||||
*/
|
||||
enum fixed_addresses {
|
||||
FIX_HOLE,
|
||||
#define FIX_FDT_SIZE SZ_1M
|
||||
FIX_FDT_END,
|
||||
FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,
|
||||
FIX_PTE,
|
||||
FIX_PMD,
|
||||
FIX_TEXT_POKE1,
|
||||
FIX_TEXT_POKE0,
|
||||
FIX_EARLYCON_MEM_BASE,
|
||||
|
||||
__end_of_permanent_fixed_addresses,
|
||||
/*
|
||||
* Temporary boot-time mappings, used by early_ioremap(),
|
||||
* before ioremap() is functional.
|
||||
*/
|
||||
#define NR_FIX_BTMAPS (SZ_256K / PAGE_SIZE)
|
||||
#define FIX_BTMAPS_SLOTS 7
|
||||
#define TOTAL_FIX_BTMAPS (NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)
|
||||
|
||||
FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
|
||||
FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,
|
||||
|
||||
__end_of_fixed_addresses
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <linux/types.h>
|
||||
#include <linux/pgtable.h>
|
||||
#include <asm/mmiowb.h>
|
||||
#include <asm/early_ioremap.h>
|
||||
|
||||
/*
|
||||
* MMIO access functions are separated out to break dependency cycles
|
||||
|
@ -20,6 +20,8 @@ typedef struct {
|
||||
#endif
|
||||
} mm_context_t;
|
||||
|
||||
void __init create_pgd_mapping(pgd_t *pgdp, uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot);
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* _ASM_RISCV_MMU_H */
|
||||
|
@ -100,6 +100,10 @@
|
||||
|
||||
#define PAGE_KERNEL __pgprot(_PAGE_KERNEL)
|
||||
#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC)
|
||||
#define PAGE_KERNEL_READ __pgprot(_PAGE_KERNEL & ~_PAGE_WRITE)
|
||||
#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC)
|
||||
#define PAGE_KERNEL_READ_EXEC __pgprot((_PAGE_KERNEL & ~_PAGE_WRITE) \
|
||||
| _PAGE_EXEC)
|
||||
|
||||
#define PAGE_TABLE __pgprot(_PAGE_TABLE)
|
||||
|
||||
@ -464,6 +468,7 @@ static inline void __kernel_map_pages(struct page *page, int numpages, int enabl
|
||||
#define kern_addr_valid(addr) (1) /* FIXME */
|
||||
|
||||
extern void *dtb_early_va;
|
||||
extern uintptr_t dtb_early_pa;
|
||||
void setup_bootmem(void);
|
||||
void paging_init(void);
|
||||
|
||||
|
13
arch/riscv/include/asm/sections.h
Normal file
13
arch/riscv/include/asm/sections.h
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
*/
|
||||
#ifndef __ASM_SECTIONS_H
|
||||
#define __ASM_SECTIONS_H
|
||||
|
||||
#include <asm-generic/sections.h>
|
||||
|
||||
extern char _start[];
|
||||
extern char _start_kernel[];
|
||||
|
||||
#endif /* __ASM_SECTIONS_H */
|
@ -10,4 +10,28 @@
|
||||
/* vDSO location */
|
||||
#define AT_SYSINFO_EHDR 33
|
||||
|
||||
/*
|
||||
* The set of entries below represent more extensive information
|
||||
* about the caches, in the form of two entry per cache type,
|
||||
* one entry containing the cache size in bytes, and the other
|
||||
* containing the cache line size in bytes in the bottom 16 bits
|
||||
* and the cache associativity in the next 16 bits.
|
||||
*
|
||||
* The associativity is such that if N is the 16-bit value, the
|
||||
* cache is N way set associative. A value if 0xffff means fully
|
||||
* associative, a value of 1 means directly mapped.
|
||||
*
|
||||
* For all these fields, a value of 0 means that the information
|
||||
* is not known.
|
||||
*/
|
||||
#define AT_L1I_CACHESIZE 40
|
||||
#define AT_L1I_CACHEGEOMETRY 41
|
||||
#define AT_L1D_CACHESIZE 42
|
||||
#define AT_L1D_CACHEGEOMETRY 43
|
||||
#define AT_L2_CACHESIZE 44
|
||||
#define AT_L2_CACHEGEOMETRY 45
|
||||
|
||||
/* entries in ARCH_DLINFO */
|
||||
#define AT_VECTOR_SIZE_ARCH 7
|
||||
|
||||
#endif /* _UAPI_ASM_RISCV_AUXVEC_H */
|
||||
|
@ -55,4 +55,6 @@ obj-$(CONFIG_KGDB) += kgdb.o
|
||||
|
||||
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
||||
|
||||
obj-$(CONFIG_EFI) += efi.o
|
||||
|
||||
clean:
|
||||
|
@ -3,7 +3,6 @@
|
||||
* Copyright (C) 2017 SiFive
|
||||
*/
|
||||
|
||||
#include <linux/cacheinfo.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
@ -25,12 +24,84 @@ cache_get_priv_group(struct cacheinfo *this_leaf)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ci_leaf_init(struct cacheinfo *this_leaf,
|
||||
struct device_node *node,
|
||||
enum cache_type type, unsigned int level)
|
||||
static struct cacheinfo *get_cacheinfo(u32 level, enum cache_type type)
|
||||
{
|
||||
struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(smp_processor_id());
|
||||
struct cacheinfo *this_leaf;
|
||||
int index;
|
||||
|
||||
for (index = 0; index < this_cpu_ci->num_leaves; index++) {
|
||||
this_leaf = this_cpu_ci->info_list + index;
|
||||
if (this_leaf->level == level && this_leaf->type == type)
|
||||
return this_leaf;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uintptr_t get_cache_size(u32 level, enum cache_type type)
|
||||
{
|
||||
struct cacheinfo *this_leaf = get_cacheinfo(level, type);
|
||||
|
||||
return this_leaf ? this_leaf->size : 0;
|
||||
}
|
||||
|
||||
uintptr_t get_cache_geometry(u32 level, enum cache_type type)
|
||||
{
|
||||
struct cacheinfo *this_leaf = get_cacheinfo(level, type);
|
||||
|
||||
return this_leaf ? (this_leaf->ways_of_associativity << 16 |
|
||||
this_leaf->coherency_line_size) :
|
||||
0;
|
||||
}
|
||||
|
||||
static void ci_leaf_init(struct cacheinfo *this_leaf, enum cache_type type,
|
||||
unsigned int level, unsigned int size,
|
||||
unsigned int sets, unsigned int line_size)
|
||||
{
|
||||
this_leaf->level = level;
|
||||
this_leaf->type = type;
|
||||
this_leaf->size = size;
|
||||
this_leaf->number_of_sets = sets;
|
||||
this_leaf->coherency_line_size = line_size;
|
||||
|
||||
/*
|
||||
* If the cache is fully associative, there is no need to
|
||||
* check the other properties.
|
||||
*/
|
||||
if (sets == 1)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Set the ways number for n-ways associative, make sure
|
||||
* all properties are big than zero.
|
||||
*/
|
||||
if (sets > 0 && size > 0 && line_size > 0)
|
||||
this_leaf->ways_of_associativity = (size / sets) / line_size;
|
||||
}
|
||||
|
||||
static void fill_cacheinfo(struct cacheinfo **this_leaf,
|
||||
struct device_node *node, unsigned int level)
|
||||
{
|
||||
unsigned int size, sets, line_size;
|
||||
|
||||
if (!of_property_read_u32(node, "cache-size", &size) &&
|
||||
!of_property_read_u32(node, "cache-block-size", &line_size) &&
|
||||
!of_property_read_u32(node, "cache-sets", &sets)) {
|
||||
ci_leaf_init((*this_leaf)++, CACHE_TYPE_UNIFIED, level, size, sets, line_size);
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(node, "i-cache-size", &size) &&
|
||||
!of_property_read_u32(node, "i-cache-sets", &sets) &&
|
||||
!of_property_read_u32(node, "i-cache-block-size", &line_size)) {
|
||||
ci_leaf_init((*this_leaf)++, CACHE_TYPE_INST, level, size, sets, line_size);
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(node, "d-cache-size", &size) &&
|
||||
!of_property_read_u32(node, "d-cache-sets", &sets) &&
|
||||
!of_property_read_u32(node, "d-cache-block-size", &line_size)) {
|
||||
ci_leaf_init((*this_leaf)++, CACHE_TYPE_DATA, level, size, sets, line_size);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init_cache_level(unsigned int cpu)
|
||||
@ -83,29 +154,24 @@ static int __populate_cache_leaves(unsigned int cpu)
|
||||
struct device_node *prev = NULL;
|
||||
int levels = 1, level = 1;
|
||||
|
||||
if (of_property_read_bool(np, "cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_UNIFIED, level);
|
||||
if (of_property_read_bool(np, "i-cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_INST, level);
|
||||
if (of_property_read_bool(np, "d-cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_DATA, level);
|
||||
/* Level 1 caches in cpu node */
|
||||
fill_cacheinfo(&this_leaf, np, level);
|
||||
|
||||
/* Next level caches in cache nodes */
|
||||
prev = np;
|
||||
while ((np = of_find_next_cache_node(np))) {
|
||||
of_node_put(prev);
|
||||
prev = np;
|
||||
|
||||
if (!of_device_is_compatible(np, "cache"))
|
||||
break;
|
||||
if (of_property_read_u32(np, "cache-level", &level))
|
||||
break;
|
||||
if (level <= levels)
|
||||
break;
|
||||
if (of_property_read_bool(np, "cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_UNIFIED, level);
|
||||
if (of_property_read_bool(np, "i-cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_INST, level);
|
||||
if (of_property_read_bool(np, "d-cache-size"))
|
||||
ci_leaf_init(this_leaf++, np, CACHE_TYPE_DATA, level);
|
||||
|
||||
fill_cacheinfo(&this_leaf, np, level);
|
||||
|
||||
levels = level;
|
||||
}
|
||||
of_node_put(np);
|
||||
|
111
arch/riscv/kernel/efi-header.S
Normal file
111
arch/riscv/kernel/efi-header.S
Normal file
@ -0,0 +1,111 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
* Adapted from arch/arm64/kernel/efi-header.S
|
||||
*/
|
||||
|
||||
#include <linux/pe.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
.macro __EFI_PE_HEADER
|
||||
.long PE_MAGIC
|
||||
coff_header:
|
||||
#ifdef CONFIG_64BIT
|
||||
.short IMAGE_FILE_MACHINE_RISCV64 // Machine
|
||||
#else
|
||||
.short IMAGE_FILE_MACHINE_RISCV32 // Machine
|
||||
#endif
|
||||
.short section_count // NumberOfSections
|
||||
.long 0 // TimeDateStamp
|
||||
.long 0 // PointerToSymbolTable
|
||||
.long 0 // NumberOfSymbols
|
||||
.short section_table - optional_header // SizeOfOptionalHeader
|
||||
.short IMAGE_FILE_DEBUG_STRIPPED | \
|
||||
IMAGE_FILE_EXECUTABLE_IMAGE | \
|
||||
IMAGE_FILE_LINE_NUMS_STRIPPED // Characteristics
|
||||
|
||||
optional_header:
|
||||
#ifdef CONFIG_64BIT
|
||||
.short PE_OPT_MAGIC_PE32PLUS // PE32+ format
|
||||
#else
|
||||
.short PE_OPT_MAGIC_PE32 // PE32 format
|
||||
#endif
|
||||
.byte 0x02 // MajorLinkerVersion
|
||||
.byte 0x14 // MinorLinkerVersion
|
||||
.long __pecoff_text_end - efi_header_end // SizeOfCode
|
||||
.long __pecoff_data_virt_size // SizeOfInitializedData
|
||||
.long 0 // SizeOfUninitializedData
|
||||
.long __efistub_efi_pe_entry - _start // AddressOfEntryPoint
|
||||
.long efi_header_end - _start // BaseOfCode
|
||||
#ifdef CONFIG_32BIT
|
||||
.long __pecoff_text_end - _start // BaseOfData
|
||||
#endif
|
||||
|
||||
extra_header_fields:
|
||||
.quad 0 // ImageBase
|
||||
.long PECOFF_SECTION_ALIGNMENT // SectionAlignment
|
||||
.long PECOFF_FILE_ALIGNMENT // FileAlignment
|
||||
.short 0 // MajorOperatingSystemVersion
|
||||
.short 0 // MinorOperatingSystemVersion
|
||||
.short LINUX_EFISTUB_MAJOR_VERSION // MajorImageVersion
|
||||
.short LINUX_EFISTUB_MINOR_VERSION // MinorImageVersion
|
||||
.short 0 // MajorSubsystemVersion
|
||||
.short 0 // MinorSubsystemVersion
|
||||
.long 0 // Win32VersionValue
|
||||
|
||||
.long _end - _start // SizeOfImage
|
||||
|
||||
// Everything before the kernel image is considered part of the header
|
||||
.long efi_header_end - _start // SizeOfHeaders
|
||||
.long 0 // CheckSum
|
||||
.short IMAGE_SUBSYSTEM_EFI_APPLICATION // Subsystem
|
||||
.short 0 // DllCharacteristics
|
||||
.quad 0 // SizeOfStackReserve
|
||||
.quad 0 // SizeOfStackCommit
|
||||
.quad 0 // SizeOfHeapReserve
|
||||
.quad 0 // SizeOfHeapCommit
|
||||
.long 0 // LoaderFlags
|
||||
.long (section_table - .) / 8 // NumberOfRvaAndSizes
|
||||
|
||||
.quad 0 // ExportTable
|
||||
.quad 0 // ImportTable
|
||||
.quad 0 // ResourceTable
|
||||
.quad 0 // ExceptionTable
|
||||
.quad 0 // CertificationTable
|
||||
.quad 0 // BaseRelocationTable
|
||||
|
||||
// Section table
|
||||
section_table:
|
||||
.ascii ".text\0\0\0"
|
||||
.long __pecoff_text_end - efi_header_end // VirtualSize
|
||||
.long efi_header_end - _start // VirtualAddress
|
||||
.long __pecoff_text_end - efi_header_end // SizeOfRawData
|
||||
.long efi_header_end - _start // PointerToRawData
|
||||
|
||||
.long 0 // PointerToRelocations
|
||||
.long 0 // PointerToLineNumbers
|
||||
.short 0 // NumberOfRelocations
|
||||
.short 0 // NumberOfLineNumbers
|
||||
.long IMAGE_SCN_CNT_CODE | \
|
||||
IMAGE_SCN_MEM_READ | \
|
||||
IMAGE_SCN_MEM_EXECUTE // Characteristics
|
||||
|
||||
.ascii ".data\0\0\0"
|
||||
.long __pecoff_data_virt_size // VirtualSize
|
||||
.long __pecoff_text_end - _start // VirtualAddress
|
||||
.long __pecoff_data_raw_size // SizeOfRawData
|
||||
.long __pecoff_text_end - _start // PointerToRawData
|
||||
|
||||
.long 0 // PointerToRelocations
|
||||
.long 0 // PointerToLineNumbers
|
||||
.short 0 // NumberOfRelocations
|
||||
.short 0 // NumberOfLineNumbers
|
||||
.long IMAGE_SCN_CNT_INITIALIZED_DATA | \
|
||||
IMAGE_SCN_MEM_READ | \
|
||||
IMAGE_SCN_MEM_WRITE // Characteristics
|
||||
|
||||
.set section_count, (. - section_table) / 40
|
||||
|
||||
.balign 0x1000
|
||||
efi_header_end:
|
||||
.endm
|
96
arch/riscv/kernel/efi.c
Normal file
96
arch/riscv/kernel/efi.c
Normal file
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
* Adapted from arch/arm64/kernel/efi.c
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
#include <asm/efi.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/pgtable-bits.h>
|
||||
|
||||
/*
|
||||
* Only regions of type EFI_RUNTIME_SERVICES_CODE need to be
|
||||
* executable, everything else can be mapped with the XN bits
|
||||
* set. Also take the new (optional) RO/XP bits into account.
|
||||
*/
|
||||
static __init pgprot_t efimem_to_pgprot_map(efi_memory_desc_t *md)
|
||||
{
|
||||
u64 attr = md->attribute;
|
||||
u32 type = md->type;
|
||||
|
||||
if (type == EFI_MEMORY_MAPPED_IO)
|
||||
return PAGE_KERNEL;
|
||||
|
||||
/* R-- */
|
||||
if ((attr & (EFI_MEMORY_XP | EFI_MEMORY_RO)) ==
|
||||
(EFI_MEMORY_XP | EFI_MEMORY_RO))
|
||||
return PAGE_KERNEL_READ;
|
||||
|
||||
/* R-X */
|
||||
if (attr & EFI_MEMORY_RO)
|
||||
return PAGE_KERNEL_READ_EXEC;
|
||||
|
||||
/* RW- */
|
||||
if (((attr & (EFI_MEMORY_RP | EFI_MEMORY_WP | EFI_MEMORY_XP)) ==
|
||||
EFI_MEMORY_XP) ||
|
||||
type != EFI_RUNTIME_SERVICES_CODE)
|
||||
return PAGE_KERNEL;
|
||||
|
||||
/* RWX */
|
||||
return PAGE_KERNEL_EXEC;
|
||||
}
|
||||
|
||||
int __init efi_create_mapping(struct mm_struct *mm, efi_memory_desc_t *md)
|
||||
{
|
||||
pgprot_t prot = __pgprot(pgprot_val(efimem_to_pgprot_map(md)) &
|
||||
~(_PAGE_GLOBAL));
|
||||
int i;
|
||||
|
||||
/* RISC-V maps one page at a time */
|
||||
for (i = 0; i < md->num_pages; i++)
|
||||
create_pgd_mapping(mm->pgd, md->virt_addr + i * PAGE_SIZE,
|
||||
md->phys_addr + i * PAGE_SIZE,
|
||||
PAGE_SIZE, prot);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init set_permissions(pte_t *ptep, unsigned long addr, void *data)
|
||||
{
|
||||
efi_memory_desc_t *md = data;
|
||||
pte_t pte = READ_ONCE(*ptep);
|
||||
unsigned long val;
|
||||
|
||||
if (md->attribute & EFI_MEMORY_RO) {
|
||||
val = pte_val(pte) & ~_PAGE_WRITE;
|
||||
val = pte_val(pte) | _PAGE_READ;
|
||||
pte = __pte(val);
|
||||
}
|
||||
if (md->attribute & EFI_MEMORY_XP) {
|
||||
val = pte_val(pte) & ~_PAGE_EXEC;
|
||||
pte = __pte(val);
|
||||
}
|
||||
set_pte(ptep, pte);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __init efi_set_mapping_permissions(struct mm_struct *mm,
|
||||
efi_memory_desc_t *md)
|
||||
{
|
||||
BUG_ON(md->type != EFI_RUNTIME_SERVICES_CODE &&
|
||||
md->type != EFI_RUNTIME_SERVICES_DATA);
|
||||
|
||||
/*
|
||||
* Calling apply_to_page_range() is only safe on regions that are
|
||||
* guaranteed to be mapped down to pages. Since we are only called
|
||||
* for regions that have been mapped using efi_create_mapping() above
|
||||
* (and this is checked by the generic Memory Attributes table parsing
|
||||
* routines), there is no need to check that again here.
|
||||
*/
|
||||
return apply_to_page_range(mm, md->virt_addr,
|
||||
md->num_pages << EFI_PAGE_SHIFT,
|
||||
set_permissions, md);
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
* Copyright (C) 2012 Regents of the University of California
|
||||
*/
|
||||
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/asm.h>
|
||||
#include <linux/init.h>
|
||||
@ -13,6 +12,7 @@
|
||||
#include <asm/csr.h>
|
||||
#include <asm/hwcap.h>
|
||||
#include <asm/image.h>
|
||||
#include "efi-header.S"
|
||||
|
||||
__HEAD
|
||||
ENTRY(_start)
|
||||
@ -22,10 +22,18 @@ ENTRY(_start)
|
||||
* Do not modify it without modifying the structure and all bootloaders
|
||||
* that expects this header format!!
|
||||
*/
|
||||
#ifdef CONFIG_EFI
|
||||
/*
|
||||
* This instruction decodes to "MZ" ASCII required by UEFI.
|
||||
*/
|
||||
c.li s4,-13
|
||||
j _start_kernel
|
||||
#else
|
||||
/* jump to start kernel */
|
||||
j _start_kernel
|
||||
/* reserved */
|
||||
.word 0
|
||||
#endif
|
||||
.balign 8
|
||||
#if __riscv_xlen == 64
|
||||
/* Image load offset(2MB) from start of RAM */
|
||||
@ -43,7 +51,14 @@ ENTRY(_start)
|
||||
.ascii RISCV_IMAGE_MAGIC
|
||||
.balign 4
|
||||
.ascii RISCV_IMAGE_MAGIC2
|
||||
#ifdef CONFIG_EFI
|
||||
.word pe_head_start - _start
|
||||
pe_head_start:
|
||||
|
||||
__EFI_PE_HEADER
|
||||
#else
|
||||
.word 0
|
||||
#endif
|
||||
|
||||
.align 2
|
||||
#ifdef CONFIG_MMU
|
||||
@ -259,7 +274,6 @@ clear_bss_done:
|
||||
#endif
|
||||
/* Start the kernel */
|
||||
call soc_early_init
|
||||
call parse_dtb
|
||||
tail start_kernel
|
||||
|
||||
.Lsecondary_start:
|
||||
|
@ -16,6 +16,4 @@ asmlinkage void __init setup_vm(uintptr_t dtb_pa);
|
||||
extern void *__cpu_up_stack_pointer[];
|
||||
extern void *__cpu_up_task_pointer[];
|
||||
|
||||
void __init parse_dtb(void);
|
||||
|
||||
#endif /* __ASM_HEAD_H */
|
||||
|
51
arch/riscv/kernel/image-vars.h
Normal file
51
arch/riscv/kernel/image-vars.h
Normal file
@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
* Linker script variables to be set after section resolution, as
|
||||
* ld.lld does not like variables assigned before SECTIONS is processed.
|
||||
* Based on arch/arm64/kerne/image-vars.h
|
||||
*/
|
||||
#ifndef __RISCV_KERNEL_IMAGE_VARS_H
|
||||
#define __RISCV_KERNEL_IMAGE_VARS_H
|
||||
|
||||
#ifndef LINKER_SCRIPT
|
||||
#error This file should only be included in vmlinux.lds.S
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
|
||||
/*
|
||||
* The EFI stub has its own symbol namespace prefixed by __efistub_, to
|
||||
* isolate it from the kernel proper. The following symbols are legally
|
||||
* accessed by the stub, so provide some aliases to make them accessible.
|
||||
* Only include data symbols here, or text symbols of functions that are
|
||||
* guaranteed to be safe when executed at another offset than they were
|
||||
* linked at. The routines below are all implemented in assembler in a
|
||||
* position independent manner
|
||||
*/
|
||||
__efistub_memcmp = memcmp;
|
||||
__efistub_memchr = memchr;
|
||||
__efistub_memcpy = memcpy;
|
||||
__efistub_memmove = memmove;
|
||||
__efistub_memset = memset;
|
||||
__efistub_strlen = strlen;
|
||||
__efistub_strnlen = strnlen;
|
||||
__efistub_strcmp = strcmp;
|
||||
__efistub_strncmp = strncmp;
|
||||
__efistub_strrchr = strrchr;
|
||||
|
||||
#ifdef CONFIG_KASAN
|
||||
__efistub___memcpy = memcpy;
|
||||
__efistub___memmove = memmove;
|
||||
__efistub___memset = memset;
|
||||
#endif
|
||||
|
||||
__efistub__start = _start;
|
||||
__efistub__start_kernel = _start_kernel;
|
||||
__efistub__end = _end;
|
||||
__efistub__edata = _edata;
|
||||
__efistub_screen_info = screen_info;
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* __RISCV_KERNEL_IMAGE_VARS_H */
|
@ -17,19 +17,22 @@
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/swiotlb.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/efi.h>
|
||||
|
||||
#include <asm/cpu_ops.h>
|
||||
#include <asm/early_ioremap.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/sections.h>
|
||||
#include <asm/sbi.h>
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/kasan.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#include "head.h"
|
||||
|
||||
#ifdef CONFIG_DUMMY_CONSOLE
|
||||
struct screen_info screen_info = {
|
||||
#if defined(CONFIG_DUMMY_CONSOLE) || defined(CONFIG_EFI)
|
||||
struct screen_info screen_info __section(.data) = {
|
||||
.orig_video_lines = 30,
|
||||
.orig_video_cols = 80,
|
||||
.orig_video_mode = 0,
|
||||
@ -48,8 +51,9 @@ atomic_t hart_lottery __section(.sdata);
|
||||
unsigned long boot_cpu_hartid;
|
||||
static DEFINE_PER_CPU(struct cpu, cpu_devices);
|
||||
|
||||
void __init parse_dtb(void)
|
||||
static void __init parse_dtb(void)
|
||||
{
|
||||
/* Early scan of device tree from init memory */
|
||||
if (early_init_dt_scan(dtb_early_va))
|
||||
return;
|
||||
|
||||
@ -62,6 +66,7 @@ void __init parse_dtb(void)
|
||||
|
||||
void __init setup_arch(char **cmdline_p)
|
||||
{
|
||||
parse_dtb();
|
||||
init_mm.start_code = (unsigned long) _stext;
|
||||
init_mm.end_code = (unsigned long) _etext;
|
||||
init_mm.end_data = (unsigned long) _edata;
|
||||
@ -69,14 +74,19 @@ void __init setup_arch(char **cmdline_p)
|
||||
|
||||
*cmdline_p = boot_command_line;
|
||||
|
||||
early_ioremap_setup();
|
||||
parse_early_param();
|
||||
|
||||
efi_init();
|
||||
setup_bootmem();
|
||||
paging_init();
|
||||
#if IS_ENABLED(CONFIG_BUILTIN_DTB)
|
||||
unflatten_and_copy_device_tree();
|
||||
#else
|
||||
unflatten_device_tree();
|
||||
if (early_init_dt_verify(__va(dtb_early_pa)))
|
||||
unflatten_device_tree();
|
||||
else
|
||||
pr_err("No DTB found in kernel mappings\n");
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SWIOTLB
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <asm/cache.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/set_memory.h>
|
||||
#include "image-vars.h"
|
||||
|
||||
#include <linux/sizes.h>
|
||||
OUTPUT_ARCH(riscv)
|
||||
@ -17,6 +18,9 @@ ENTRY(_start)
|
||||
|
||||
jiffies = jiffies_64;
|
||||
|
||||
PECOFF_SECTION_ALIGNMENT = 0x1000;
|
||||
PECOFF_FILE_ALIGNMENT = 0x200;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Beginning of code and text segment */
|
||||
@ -66,6 +70,11 @@ SECTIONS
|
||||
_etext = .;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
. = ALIGN(PECOFF_SECTION_ALIGNMENT);
|
||||
__pecoff_text_end = .;
|
||||
#endif
|
||||
|
||||
INIT_DATA_SECTION(16)
|
||||
|
||||
/* Start of data section */
|
||||
@ -84,16 +93,26 @@ SECTIONS
|
||||
.sdata : {
|
||||
__global_pointer$ = . + 0x800;
|
||||
*(.sdata*)
|
||||
/* End of data section */
|
||||
_edata = .;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
.pecoff_edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGNMENT); }
|
||||
__pecoff_data_raw_size = ABSOLUTE(. - __pecoff_text_end);
|
||||
#endif
|
||||
|
||||
/* End of data section */
|
||||
_edata = .;
|
||||
|
||||
BSS_SECTION(PAGE_SIZE, PAGE_SIZE, 0)
|
||||
|
||||
.rel.dyn : {
|
||||
*(.rel.dyn*)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
. = ALIGN(PECOFF_SECTION_ALIGNMENT);
|
||||
__pecoff_data_virt_size = ABSOLUTE(. - __pecoff_text_end);
|
||||
#endif
|
||||
_end = .;
|
||||
|
||||
STABS_DEBUG
|
||||
|
@ -19,6 +19,167 @@
|
||||
|
||||
#include "../kernel/head.h"
|
||||
|
||||
static inline void no_context(struct pt_regs *regs, unsigned long addr)
|
||||
{
|
||||
/* Are we prepared to handle this kernel fault? */
|
||||
if (fixup_exception(regs))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Oops. The kernel tried to access some bad page. We'll have to
|
||||
* terminate things with extreme prejudice.
|
||||
*/
|
||||
bust_spinlocks(1);
|
||||
pr_alert("Unable to handle kernel %s at virtual address " REG_FMT "\n",
|
||||
(addr < PAGE_SIZE) ? "NULL pointer dereference" :
|
||||
"paging request", addr);
|
||||
die(regs, "Oops");
|
||||
do_exit(SIGKILL);
|
||||
}
|
||||
|
||||
static inline void mm_fault_error(struct pt_regs *regs, unsigned long addr, vm_fault_t fault)
|
||||
{
|
||||
if (fault & VM_FAULT_OOM) {
|
||||
/*
|
||||
* We ran out of memory, call the OOM killer, and return the userspace
|
||||
* (which will retry the fault, or kill us if we got oom-killed).
|
||||
*/
|
||||
if (!user_mode(regs)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
pagefault_out_of_memory();
|
||||
return;
|
||||
} else if (fault & VM_FAULT_SIGBUS) {
|
||||
/* Kernel mode? Handle exceptions or die */
|
||||
if (!user_mode(regs)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
do_trap(regs, SIGBUS, BUS_ADRERR, addr);
|
||||
return;
|
||||
}
|
||||
BUG();
|
||||
}
|
||||
|
||||
static inline void bad_area(struct pt_regs *regs, struct mm_struct *mm, int code, unsigned long addr)
|
||||
{
|
||||
/*
|
||||
* Something tried to access memory that isn't in our memory map.
|
||||
* Fix it, but check if it's kernel or user first.
|
||||
*/
|
||||
mmap_read_unlock(mm);
|
||||
/* User mode accesses just cause a SIGSEGV */
|
||||
if (user_mode(regs)) {
|
||||
do_trap(regs, SIGSEGV, code, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
no_context(regs, addr);
|
||||
}
|
||||
|
||||
static inline void vmalloc_fault(struct pt_regs *regs, int code, unsigned long addr)
|
||||
{
|
||||
pgd_t *pgd, *pgd_k;
|
||||
pud_t *pud, *pud_k;
|
||||
p4d_t *p4d, *p4d_k;
|
||||
pmd_t *pmd, *pmd_k;
|
||||
pte_t *pte_k;
|
||||
int index;
|
||||
|
||||
/* User mode accesses just cause a SIGSEGV */
|
||||
if (user_mode(regs))
|
||||
return do_trap(regs, SIGSEGV, code, addr);
|
||||
|
||||
/*
|
||||
* Synchronize this task's top level page-table
|
||||
* with the 'reference' page table.
|
||||
*
|
||||
* Do _not_ use "tsk->active_mm->pgd" here.
|
||||
* We might be inside an interrupt in the middle
|
||||
* of a task switch.
|
||||
*/
|
||||
index = pgd_index(addr);
|
||||
pgd = (pgd_t *)pfn_to_virt(csr_read(CSR_SATP)) + index;
|
||||
pgd_k = init_mm.pgd + index;
|
||||
|
||||
if (!pgd_present(*pgd_k)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
set_pgd(pgd, *pgd_k);
|
||||
|
||||
p4d = p4d_offset(pgd, addr);
|
||||
p4d_k = p4d_offset(pgd_k, addr);
|
||||
if (!p4d_present(*p4d_k)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
pud = pud_offset(p4d, addr);
|
||||
pud_k = pud_offset(p4d_k, addr);
|
||||
if (!pud_present(*pud_k)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since the vmalloc area is global, it is unnecessary
|
||||
* to copy individual PTEs
|
||||
*/
|
||||
pmd = pmd_offset(pud, addr);
|
||||
pmd_k = pmd_offset(pud_k, addr);
|
||||
if (!pmd_present(*pmd_k)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
set_pmd(pmd, *pmd_k);
|
||||
|
||||
/*
|
||||
* Make sure the actual PTE exists as well to
|
||||
* catch kernel vmalloc-area accesses to non-mapped
|
||||
* addresses. If we don't do this, this will just
|
||||
* silently loop forever.
|
||||
*/
|
||||
pte_k = pte_offset_kernel(pmd_k, addr);
|
||||
if (!pte_present(*pte_k)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The kernel assumes that TLBs don't cache invalid
|
||||
* entries, but in RISC-V, SFENCE.VMA specifies an
|
||||
* ordering constraint, not a cache flush; it is
|
||||
* necessary even after writing invalid entries.
|
||||
*/
|
||||
local_flush_tlb_page(addr);
|
||||
}
|
||||
|
||||
static inline bool access_error(unsigned long cause, struct vm_area_struct *vma)
|
||||
{
|
||||
switch (cause) {
|
||||
case EXC_INST_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_EXEC)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EXC_LOAD_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_READ)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EXC_STORE_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_WRITE)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
panic("%s: unhandled cause %lu", __func__, cause);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine handles page faults. It determines the address and the
|
||||
* problem, and then passes it off to one of the appropriate routines.
|
||||
@ -48,8 +209,10 @@ asmlinkage void do_page_fault(struct pt_regs *regs)
|
||||
* only copy the information from the master page table,
|
||||
* nothing more.
|
||||
*/
|
||||
if (unlikely((addr >= VMALLOC_START) && (addr <= VMALLOC_END)))
|
||||
goto vmalloc_fault;
|
||||
if (unlikely((addr >= VMALLOC_START) && (addr <= VMALLOC_END))) {
|
||||
vmalloc_fault(regs, code, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Enable interrupts if they were enabled in the parent context. */
|
||||
if (likely(regs->status & SR_PIE))
|
||||
@ -59,25 +222,37 @@ asmlinkage void do_page_fault(struct pt_regs *regs)
|
||||
* If we're in an interrupt, have no user context, or are running
|
||||
* in an atomic region, then we must not take the fault.
|
||||
*/
|
||||
if (unlikely(faulthandler_disabled() || !mm))
|
||||
goto no_context;
|
||||
if (unlikely(faulthandler_disabled() || !mm)) {
|
||||
no_context(regs, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user_mode(regs))
|
||||
flags |= FAULT_FLAG_USER;
|
||||
|
||||
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);
|
||||
|
||||
if (cause == EXC_STORE_PAGE_FAULT)
|
||||
flags |= FAULT_FLAG_WRITE;
|
||||
else if (cause == EXC_INST_PAGE_FAULT)
|
||||
flags |= FAULT_FLAG_INSTRUCTION;
|
||||
retry:
|
||||
mmap_read_lock(mm);
|
||||
vma = find_vma(mm, addr);
|
||||
if (unlikely(!vma))
|
||||
goto bad_area;
|
||||
if (unlikely(!vma)) {
|
||||
bad_area(regs, mm, code, addr);
|
||||
return;
|
||||
}
|
||||
if (likely(vma->vm_start <= addr))
|
||||
goto good_area;
|
||||
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN)))
|
||||
goto bad_area;
|
||||
if (unlikely(expand_stack(vma, addr)))
|
||||
goto bad_area;
|
||||
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
|
||||
bad_area(regs, mm, code, addr);
|
||||
return;
|
||||
}
|
||||
if (unlikely(expand_stack(vma, addr))) {
|
||||
bad_area(regs, mm, code, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok, we have a good vm_area for this memory access, so
|
||||
@ -86,22 +261,9 @@ retry:
|
||||
good_area:
|
||||
code = SEGV_ACCERR;
|
||||
|
||||
switch (cause) {
|
||||
case EXC_INST_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_EXEC))
|
||||
goto bad_area;
|
||||
break;
|
||||
case EXC_LOAD_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_READ))
|
||||
goto bad_area;
|
||||
break;
|
||||
case EXC_STORE_PAGE_FAULT:
|
||||
if (!(vma->vm_flags & VM_WRITE))
|
||||
goto bad_area;
|
||||
flags |= FAULT_FLAG_WRITE;
|
||||
break;
|
||||
default:
|
||||
panic("%s: unhandled cause %lu", __func__, cause);
|
||||
if (unlikely(access_error(cause, vma))) {
|
||||
bad_area(regs, mm, code, addr);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -119,144 +281,22 @@ good_area:
|
||||
if (fault_signal_pending(fault, regs))
|
||||
return;
|
||||
|
||||
if (unlikely((fault & VM_FAULT_RETRY) && (flags & FAULT_FLAG_ALLOW_RETRY))) {
|
||||
flags |= FAULT_FLAG_TRIED;
|
||||
|
||||
/*
|
||||
* No need to mmap_read_unlock(mm) as we would
|
||||
* have already released it in __lock_page_or_retry
|
||||
* in mm/filemap.c.
|
||||
*/
|
||||
goto retry;
|
||||
}
|
||||
|
||||
mmap_read_unlock(mm);
|
||||
|
||||
if (unlikely(fault & VM_FAULT_ERROR)) {
|
||||
if (fault & VM_FAULT_OOM)
|
||||
goto out_of_memory;
|
||||
else if (fault & VM_FAULT_SIGBUS)
|
||||
goto do_sigbus;
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (flags & FAULT_FLAG_ALLOW_RETRY) {
|
||||
if (fault & VM_FAULT_RETRY) {
|
||||
flags |= FAULT_FLAG_TRIED;
|
||||
|
||||
/*
|
||||
* No need to mmap_read_unlock(mm) as we would
|
||||
* have already released it in __lock_page_or_retry
|
||||
* in mm/filemap.c.
|
||||
*/
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
mmap_read_unlock(mm);
|
||||
return;
|
||||
|
||||
/*
|
||||
* Something tried to access memory that isn't in our memory map.
|
||||
* Fix it, but check if it's kernel or user first.
|
||||
*/
|
||||
bad_area:
|
||||
mmap_read_unlock(mm);
|
||||
/* User mode accesses just cause a SIGSEGV */
|
||||
if (user_mode(regs)) {
|
||||
do_trap(regs, SIGSEGV, code, addr);
|
||||
mm_fault_error(regs, addr, fault);
|
||||
return;
|
||||
}
|
||||
|
||||
no_context:
|
||||
/* Are we prepared to handle this kernel fault? */
|
||||
if (fixup_exception(regs))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Oops. The kernel tried to access some bad page. We'll have to
|
||||
* terminate things with extreme prejudice.
|
||||
*/
|
||||
bust_spinlocks(1);
|
||||
pr_alert("Unable to handle kernel %s at virtual address " REG_FMT "\n",
|
||||
(addr < PAGE_SIZE) ? "NULL pointer dereference" :
|
||||
"paging request", addr);
|
||||
die(regs, "Oops");
|
||||
do_exit(SIGKILL);
|
||||
|
||||
/*
|
||||
* We ran out of memory, call the OOM killer, and return the userspace
|
||||
* (which will retry the fault, or kill us if we got oom-killed).
|
||||
*/
|
||||
out_of_memory:
|
||||
mmap_read_unlock(mm);
|
||||
if (!user_mode(regs))
|
||||
goto no_context;
|
||||
pagefault_out_of_memory();
|
||||
return;
|
||||
|
||||
do_sigbus:
|
||||
mmap_read_unlock(mm);
|
||||
/* Kernel mode? Handle exceptions or die */
|
||||
if (!user_mode(regs))
|
||||
goto no_context;
|
||||
do_trap(regs, SIGBUS, BUS_ADRERR, addr);
|
||||
return;
|
||||
|
||||
vmalloc_fault:
|
||||
{
|
||||
pgd_t *pgd, *pgd_k;
|
||||
pud_t *pud, *pud_k;
|
||||
p4d_t *p4d, *p4d_k;
|
||||
pmd_t *pmd, *pmd_k;
|
||||
pte_t *pte_k;
|
||||
int index;
|
||||
|
||||
/* User mode accesses just cause a SIGSEGV */
|
||||
if (user_mode(regs))
|
||||
return do_trap(regs, SIGSEGV, code, addr);
|
||||
|
||||
/*
|
||||
* Synchronize this task's top level page-table
|
||||
* with the 'reference' page table.
|
||||
*
|
||||
* Do _not_ use "tsk->active_mm->pgd" here.
|
||||
* We might be inside an interrupt in the middle
|
||||
* of a task switch.
|
||||
*/
|
||||
index = pgd_index(addr);
|
||||
pgd = (pgd_t *)pfn_to_virt(csr_read(CSR_SATP)) + index;
|
||||
pgd_k = init_mm.pgd + index;
|
||||
|
||||
if (!pgd_present(*pgd_k))
|
||||
goto no_context;
|
||||
set_pgd(pgd, *pgd_k);
|
||||
|
||||
p4d = p4d_offset(pgd, addr);
|
||||
p4d_k = p4d_offset(pgd_k, addr);
|
||||
if (!p4d_present(*p4d_k))
|
||||
goto no_context;
|
||||
|
||||
pud = pud_offset(p4d, addr);
|
||||
pud_k = pud_offset(p4d_k, addr);
|
||||
if (!pud_present(*pud_k))
|
||||
goto no_context;
|
||||
|
||||
/*
|
||||
* Since the vmalloc area is global, it is unnecessary
|
||||
* to copy individual PTEs
|
||||
*/
|
||||
pmd = pmd_offset(pud, addr);
|
||||
pmd_k = pmd_offset(pud_k, addr);
|
||||
if (!pmd_present(*pmd_k))
|
||||
goto no_context;
|
||||
set_pmd(pmd, *pmd_k);
|
||||
|
||||
/*
|
||||
* Make sure the actual PTE exists as well to
|
||||
* catch kernel vmalloc-area accesses to non-mapped
|
||||
* addresses. If we don't do this, this will just
|
||||
* silently loop forever.
|
||||
*/
|
||||
pte_k = pte_offset_kernel(pmd_k, addr);
|
||||
if (!pte_present(*pte_k))
|
||||
goto no_context;
|
||||
|
||||
/*
|
||||
* The kernel assumes that TLBs don't cache invalid
|
||||
* entries, but in RISC-V, SFENCE.VMA specifies an
|
||||
* ordering constraint, not a cache flush; it is
|
||||
* necessary even after writing invalid entries.
|
||||
*/
|
||||
local_flush_tlb_page(addr);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,18 @@ unsigned long empty_zero_page[PAGE_SIZE / sizeof(unsigned long)]
|
||||
EXPORT_SYMBOL(empty_zero_page);
|
||||
|
||||
extern char _start[];
|
||||
void *dtb_early_va;
|
||||
#define DTB_EARLY_BASE_VA PGDIR_SIZE
|
||||
void *dtb_early_va __initdata;
|
||||
uintptr_t dtb_early_pa __initdata;
|
||||
|
||||
struct pt_alloc_ops {
|
||||
pte_t *(*get_pte_virt)(phys_addr_t pa);
|
||||
phys_addr_t (*alloc_pte)(uintptr_t va);
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
pmd_t *(*get_pmd_virt)(phys_addr_t pa);
|
||||
phys_addr_t (*alloc_pmd)(uintptr_t va);
|
||||
#endif
|
||||
};
|
||||
|
||||
static void __init zone_sizes_init(void)
|
||||
{
|
||||
@ -141,8 +152,6 @@ disable:
|
||||
}
|
||||
#endif /* CONFIG_BLK_DEV_INITRD */
|
||||
|
||||
static phys_addr_t dtb_early_pa __initdata;
|
||||
|
||||
void __init setup_bootmem(void)
|
||||
{
|
||||
phys_addr_t mem_size = 0;
|
||||
@ -194,6 +203,8 @@ void __init setup_bootmem(void)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
static struct pt_alloc_ops pt_ops;
|
||||
|
||||
unsigned long va_pa_offset;
|
||||
EXPORT_SYMBOL(va_pa_offset);
|
||||
unsigned long pfn_base;
|
||||
@ -202,7 +213,6 @@ EXPORT_SYMBOL(pfn_base);
|
||||
pgd_t swapper_pg_dir[PTRS_PER_PGD] __page_aligned_bss;
|
||||
pgd_t trampoline_pg_dir[PTRS_PER_PGD] __page_aligned_bss;
|
||||
pte_t fixmap_pte[PTRS_PER_PTE] __page_aligned_bss;
|
||||
static bool mmu_enabled;
|
||||
|
||||
#define MAX_EARLY_MAPPING_SIZE SZ_128M
|
||||
|
||||
@ -224,27 +234,46 @@ void __set_fixmap(enum fixed_addresses idx, phys_addr_t phys, pgprot_t prot)
|
||||
local_flush_tlb_page(addr);
|
||||
}
|
||||
|
||||
static pte_t *__init get_pte_virt(phys_addr_t pa)
|
||||
static inline pte_t *__init get_pte_virt_early(phys_addr_t pa)
|
||||
{
|
||||
if (mmu_enabled) {
|
||||
clear_fixmap(FIX_PTE);
|
||||
return (pte_t *)set_fixmap_offset(FIX_PTE, pa);
|
||||
} else {
|
||||
return (pte_t *)((uintptr_t)pa);
|
||||
}
|
||||
return (pte_t *)((uintptr_t)pa);
|
||||
}
|
||||
|
||||
static phys_addr_t __init alloc_pte(uintptr_t va)
|
||||
static inline pte_t *__init get_pte_virt_fixmap(phys_addr_t pa)
|
||||
{
|
||||
clear_fixmap(FIX_PTE);
|
||||
return (pte_t *)set_fixmap_offset(FIX_PTE, pa);
|
||||
}
|
||||
|
||||
static inline pte_t *get_pte_virt_late(phys_addr_t pa)
|
||||
{
|
||||
return (pte_t *) __va(pa);
|
||||
}
|
||||
|
||||
static inline phys_addr_t __init alloc_pte_early(uintptr_t va)
|
||||
{
|
||||
/*
|
||||
* We only create PMD or PGD early mappings so we
|
||||
* should never reach here with MMU disabled.
|
||||
*/
|
||||
BUG_ON(!mmu_enabled);
|
||||
BUG();
|
||||
}
|
||||
|
||||
static inline phys_addr_t __init alloc_pte_fixmap(uintptr_t va)
|
||||
{
|
||||
return memblock_phys_alloc(PAGE_SIZE, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static phys_addr_t alloc_pte_late(uintptr_t va)
|
||||
{
|
||||
unsigned long vaddr;
|
||||
|
||||
vaddr = __get_free_page(GFP_KERNEL);
|
||||
if (!vaddr || !pgtable_pte_page_ctor(virt_to_page(vaddr)))
|
||||
BUG();
|
||||
return __pa(vaddr);
|
||||
}
|
||||
|
||||
static void __init create_pte_mapping(pte_t *ptep,
|
||||
uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot)
|
||||
@ -269,28 +298,46 @@ pmd_t fixmap_pmd[PTRS_PER_PMD] __page_aligned_bss;
|
||||
#endif
|
||||
pmd_t early_pmd[PTRS_PER_PMD * NUM_EARLY_PMDS] __initdata __aligned(PAGE_SIZE);
|
||||
|
||||
static pmd_t *__init get_pmd_virt(phys_addr_t pa)
|
||||
static pmd_t *__init get_pmd_virt_early(phys_addr_t pa)
|
||||
{
|
||||
if (mmu_enabled) {
|
||||
clear_fixmap(FIX_PMD);
|
||||
return (pmd_t *)set_fixmap_offset(FIX_PMD, pa);
|
||||
} else {
|
||||
return (pmd_t *)((uintptr_t)pa);
|
||||
}
|
||||
/* Before MMU is enabled */
|
||||
return (pmd_t *)((uintptr_t)pa);
|
||||
}
|
||||
|
||||
static phys_addr_t __init alloc_pmd(uintptr_t va)
|
||||
static pmd_t *__init get_pmd_virt_fixmap(phys_addr_t pa)
|
||||
{
|
||||
clear_fixmap(FIX_PMD);
|
||||
return (pmd_t *)set_fixmap_offset(FIX_PMD, pa);
|
||||
}
|
||||
|
||||
static pmd_t *get_pmd_virt_late(phys_addr_t pa)
|
||||
{
|
||||
return (pmd_t *) __va(pa);
|
||||
}
|
||||
|
||||
static phys_addr_t __init alloc_pmd_early(uintptr_t va)
|
||||
{
|
||||
uintptr_t pmd_num;
|
||||
|
||||
if (mmu_enabled)
|
||||
return memblock_phys_alloc(PAGE_SIZE, PAGE_SIZE);
|
||||
|
||||
pmd_num = (va - PAGE_OFFSET) >> PGDIR_SHIFT;
|
||||
BUG_ON(pmd_num >= NUM_EARLY_PMDS);
|
||||
return (uintptr_t)&early_pmd[pmd_num * PTRS_PER_PMD];
|
||||
}
|
||||
|
||||
static phys_addr_t __init alloc_pmd_fixmap(uintptr_t va)
|
||||
{
|
||||
return memblock_phys_alloc(PAGE_SIZE, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static phys_addr_t alloc_pmd_late(uintptr_t va)
|
||||
{
|
||||
unsigned long vaddr;
|
||||
|
||||
vaddr = __get_free_page(GFP_KERNEL);
|
||||
BUG_ON(!vaddr);
|
||||
return __pa(vaddr);
|
||||
}
|
||||
|
||||
static void __init create_pmd_mapping(pmd_t *pmdp,
|
||||
uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot)
|
||||
@ -306,34 +353,34 @@ static void __init create_pmd_mapping(pmd_t *pmdp,
|
||||
}
|
||||
|
||||
if (pmd_none(pmdp[pmd_idx])) {
|
||||
pte_phys = alloc_pte(va);
|
||||
pte_phys = pt_ops.alloc_pte(va);
|
||||
pmdp[pmd_idx] = pfn_pmd(PFN_DOWN(pte_phys), PAGE_TABLE);
|
||||
ptep = get_pte_virt(pte_phys);
|
||||
ptep = pt_ops.get_pte_virt(pte_phys);
|
||||
memset(ptep, 0, PAGE_SIZE);
|
||||
} else {
|
||||
pte_phys = PFN_PHYS(_pmd_pfn(pmdp[pmd_idx]));
|
||||
ptep = get_pte_virt(pte_phys);
|
||||
ptep = pt_ops.get_pte_virt(pte_phys);
|
||||
}
|
||||
|
||||
create_pte_mapping(ptep, va, pa, sz, prot);
|
||||
}
|
||||
|
||||
#define pgd_next_t pmd_t
|
||||
#define alloc_pgd_next(__va) alloc_pmd(__va)
|
||||
#define get_pgd_next_virt(__pa) get_pmd_virt(__pa)
|
||||
#define alloc_pgd_next(__va) pt_ops.alloc_pmd(__va)
|
||||
#define get_pgd_next_virt(__pa) pt_ops.get_pmd_virt(__pa)
|
||||
#define create_pgd_next_mapping(__nextp, __va, __pa, __sz, __prot) \
|
||||
create_pmd_mapping(__nextp, __va, __pa, __sz, __prot)
|
||||
#define fixmap_pgd_next fixmap_pmd
|
||||
#else
|
||||
#define pgd_next_t pte_t
|
||||
#define alloc_pgd_next(__va) alloc_pte(__va)
|
||||
#define get_pgd_next_virt(__pa) get_pte_virt(__pa)
|
||||
#define alloc_pgd_next(__va) pt_ops.alloc_pte(__va)
|
||||
#define get_pgd_next_virt(__pa) pt_ops.get_pte_virt(__pa)
|
||||
#define create_pgd_next_mapping(__nextp, __va, __pa, __sz, __prot) \
|
||||
create_pte_mapping(__nextp, __va, __pa, __sz, __prot)
|
||||
#define fixmap_pgd_next fixmap_pte
|
||||
#endif
|
||||
|
||||
static void __init create_pgd_mapping(pgd_t *pgdp,
|
||||
void __init create_pgd_mapping(pgd_t *pgdp,
|
||||
uintptr_t va, phys_addr_t pa,
|
||||
phys_addr_t sz, pgprot_t prot)
|
||||
{
|
||||
@ -389,10 +436,13 @@ static uintptr_t __init best_map_size(phys_addr_t base, phys_addr_t size)
|
||||
|
||||
asmlinkage void __init setup_vm(uintptr_t dtb_pa)
|
||||
{
|
||||
uintptr_t va, end_va;
|
||||
uintptr_t va, pa, end_va;
|
||||
uintptr_t load_pa = (uintptr_t)(&_start);
|
||||
uintptr_t load_sz = (uintptr_t)(&_end) - load_pa;
|
||||
uintptr_t map_size = best_map_size(load_pa, MAX_EARLY_MAPPING_SIZE);
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
pmd_t fix_bmap_spmd, fix_bmap_epmd;
|
||||
#endif
|
||||
|
||||
va_pa_offset = PAGE_OFFSET - load_pa;
|
||||
pfn_base = PFN_DOWN(load_pa);
|
||||
@ -408,6 +458,12 @@ asmlinkage void __init setup_vm(uintptr_t dtb_pa)
|
||||
BUG_ON((load_pa % map_size) != 0);
|
||||
BUG_ON(load_sz > MAX_EARLY_MAPPING_SIZE);
|
||||
|
||||
pt_ops.alloc_pte = alloc_pte_early;
|
||||
pt_ops.get_pte_virt = get_pte_virt_early;
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
pt_ops.alloc_pmd = alloc_pmd_early;
|
||||
pt_ops.get_pmd_virt = get_pmd_virt_early;
|
||||
#endif
|
||||
/* Setup early PGD for fixmap */
|
||||
create_pgd_mapping(early_pg_dir, FIXADDR_START,
|
||||
(uintptr_t)fixmap_pgd_next, PGDIR_SIZE, PAGE_TABLE);
|
||||
@ -438,17 +494,44 @@ asmlinkage void __init setup_vm(uintptr_t dtb_pa)
|
||||
load_pa + (va - PAGE_OFFSET),
|
||||
map_size, PAGE_KERNEL_EXEC);
|
||||
|
||||
/* Create fixed mapping for early FDT parsing */
|
||||
end_va = __fix_to_virt(FIX_FDT) + FIX_FDT_SIZE;
|
||||
for (va = __fix_to_virt(FIX_FDT); va < end_va; va += PAGE_SIZE)
|
||||
create_pte_mapping(fixmap_pte, va,
|
||||
dtb_pa + (va - __fix_to_virt(FIX_FDT)),
|
||||
PAGE_SIZE, PAGE_KERNEL);
|
||||
|
||||
/* Save pointer to DTB for early FDT parsing */
|
||||
dtb_early_va = (void *)fix_to_virt(FIX_FDT) + (dtb_pa & ~PAGE_MASK);
|
||||
/* Save physical address for memblock reservation */
|
||||
/* Create two consecutive PGD mappings for FDT early scan */
|
||||
pa = dtb_pa & ~(PGDIR_SIZE - 1);
|
||||
create_pgd_mapping(early_pg_dir, DTB_EARLY_BASE_VA,
|
||||
pa, PGDIR_SIZE, PAGE_KERNEL);
|
||||
create_pgd_mapping(early_pg_dir, DTB_EARLY_BASE_VA + PGDIR_SIZE,
|
||||
pa + PGDIR_SIZE, PGDIR_SIZE, PAGE_KERNEL);
|
||||
dtb_early_va = (void *)DTB_EARLY_BASE_VA + (dtb_pa & (PGDIR_SIZE - 1));
|
||||
dtb_early_pa = dtb_pa;
|
||||
|
||||
/*
|
||||
* Bootime fixmap only can handle PMD_SIZE mapping. Thus, boot-ioremap
|
||||
* range can not span multiple pmds.
|
||||
*/
|
||||
BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)
|
||||
!= (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));
|
||||
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
/*
|
||||
* Early ioremap fixmap is already created as it lies within first 2MB
|
||||
* of fixmap region. We always map PMD_SIZE. Thus, both FIX_BTMAP_END
|
||||
* FIX_BTMAP_BEGIN should lie in the same pmd. Verify that and warn
|
||||
* the user if not.
|
||||
*/
|
||||
fix_bmap_spmd = fixmap_pmd[pmd_index(__fix_to_virt(FIX_BTMAP_BEGIN))];
|
||||
fix_bmap_epmd = fixmap_pmd[pmd_index(__fix_to_virt(FIX_BTMAP_END))];
|
||||
if (pmd_val(fix_bmap_spmd) != pmd_val(fix_bmap_epmd)) {
|
||||
WARN_ON(1);
|
||||
pr_warn("fixmap btmap start [%08lx] != end [%08lx]\n",
|
||||
pmd_val(fix_bmap_spmd), pmd_val(fix_bmap_epmd));
|
||||
pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n",
|
||||
fix_to_virt(FIX_BTMAP_BEGIN));
|
||||
pr_warn("fix_to_virt(FIX_BTMAP_END): %08lx\n",
|
||||
fix_to_virt(FIX_BTMAP_END));
|
||||
|
||||
pr_warn("FIX_BTMAP_END: %d\n", FIX_BTMAP_END);
|
||||
pr_warn("FIX_BTMAP_BEGIN: %d\n", FIX_BTMAP_BEGIN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void __init setup_vm_final(void)
|
||||
@ -457,9 +540,16 @@ static void __init setup_vm_final(void)
|
||||
phys_addr_t pa, start, end;
|
||||
u64 i;
|
||||
|
||||
/* Set mmu_enabled flag */
|
||||
mmu_enabled = true;
|
||||
|
||||
/**
|
||||
* MMU is enabled at this point. But page table setup is not complete yet.
|
||||
* fixmap page table alloc functions should be used at this point
|
||||
*/
|
||||
pt_ops.alloc_pte = alloc_pte_fixmap;
|
||||
pt_ops.get_pte_virt = get_pte_virt_fixmap;
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
pt_ops.alloc_pmd = alloc_pmd_fixmap;
|
||||
pt_ops.get_pmd_virt = get_pmd_virt_fixmap;
|
||||
#endif
|
||||
/* Setup swapper PGD for fixmap */
|
||||
create_pgd_mapping(swapper_pg_dir, FIXADDR_START,
|
||||
__pa_symbol(fixmap_pgd_next),
|
||||
@ -488,6 +578,14 @@ static void __init setup_vm_final(void)
|
||||
/* Move to swapper page table */
|
||||
csr_write(CSR_SATP, PFN_DOWN(__pa_symbol(swapper_pg_dir)) | SATP_MODE);
|
||||
local_flush_tlb_all();
|
||||
|
||||
/* generic page allocation functions must be used to setup page table */
|
||||
pt_ops.alloc_pte = alloc_pte_late;
|
||||
pt_ops.get_pte_virt = get_pte_virt_late;
|
||||
#ifndef __PAGETABLE_PMD_FOLDED
|
||||
pt_ops.alloc_pmd = alloc_pmd_late;
|
||||
pt_ops.get_pmd_virt = get_pmd_virt_late;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
asmlinkage void __init setup_vm(uintptr_t dtb_pa)
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Copyright (C) 2019 SiFive
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/seq_file.h>
|
||||
@ -49,6 +50,14 @@ struct addr_marker {
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/* Private information for debugfs */
|
||||
struct ptd_mm_info {
|
||||
struct mm_struct *mm;
|
||||
const struct addr_marker *markers;
|
||||
unsigned long base_addr;
|
||||
unsigned long end;
|
||||
};
|
||||
|
||||
static struct addr_marker address_markers[] = {
|
||||
#ifdef CONFIG_KASAN
|
||||
{KASAN_SHADOW_START, "Kasan shadow start"},
|
||||
@ -68,6 +77,28 @@ static struct addr_marker address_markers[] = {
|
||||
{-1, NULL},
|
||||
};
|
||||
|
||||
static struct ptd_mm_info kernel_ptd_info = {
|
||||
.mm = &init_mm,
|
||||
.markers = address_markers,
|
||||
.base_addr = KERN_VIRT_START,
|
||||
.end = ULONG_MAX,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
static struct addr_marker efi_addr_markers[] = {
|
||||
{ 0, "UEFI runtime start" },
|
||||
{ SZ_1G, "UEFI runtime end" },
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
static struct ptd_mm_info efi_ptd_info = {
|
||||
.mm = &efi_mm,
|
||||
.markers = efi_addr_markers,
|
||||
.base_addr = 0,
|
||||
.end = SZ_2G,
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Page Table Entry */
|
||||
struct prot_bits {
|
||||
u64 mask;
|
||||
@ -245,22 +276,22 @@ static void note_page(struct ptdump_state *pt_st, unsigned long addr,
|
||||
}
|
||||
}
|
||||
|
||||
static void ptdump_walk(struct seq_file *s)
|
||||
static void ptdump_walk(struct seq_file *s, struct ptd_mm_info *pinfo)
|
||||
{
|
||||
struct pg_state st = {
|
||||
.seq = s,
|
||||
.marker = address_markers,
|
||||
.marker = pinfo->markers,
|
||||
.level = -1,
|
||||
.ptdump = {
|
||||
.note_page = note_page,
|
||||
.range = (struct ptdump_range[]) {
|
||||
{KERN_VIRT_START, ULONG_MAX},
|
||||
{pinfo->base_addr, pinfo->end},
|
||||
{0, 0}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ptdump_walk_pgd(&st.ptdump, &init_mm, NULL);
|
||||
ptdump_walk_pgd(&st.ptdump, pinfo->mm, NULL);
|
||||
}
|
||||
|
||||
void ptdump_check_wx(void)
|
||||
@ -293,7 +324,7 @@ void ptdump_check_wx(void)
|
||||
|
||||
static int ptdump_show(struct seq_file *m, void *v)
|
||||
{
|
||||
ptdump_walk(m);
|
||||
ptdump_walk(m, m->private);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -308,8 +339,13 @@ static int ptdump_init(void)
|
||||
for (j = 0; j < ARRAY_SIZE(pte_bits); j++)
|
||||
pg_level[i].mask |= pte_bits[j].mask;
|
||||
|
||||
debugfs_create_file("kernel_page_tables", 0400, NULL, NULL,
|
||||
debugfs_create_file("kernel_page_tables", 0400, NULL, &kernel_ptd_info,
|
||||
&ptdump_fops);
|
||||
#ifdef CONFIG_EFI
|
||||
if (efi_enabled(EFI_RUNTIME_SERVICES))
|
||||
debugfs_create_file("efi_page_tables", 0400, NULL, &efi_ptd_info,
|
||||
&ptdump_fops);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ config EFI_GENERIC_STUB
|
||||
|
||||
config EFI_ARMSTUB_DTB_LOADER
|
||||
bool "Enable the DTB loader"
|
||||
depends on EFI_GENERIC_STUB
|
||||
depends on EFI_GENERIC_STUB && !RISCV
|
||||
default y
|
||||
help
|
||||
Select this config option to add support for the dtb= command
|
||||
@ -123,6 +123,7 @@ config EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER
|
||||
bool "Enable the command line initrd loader" if !X86
|
||||
depends on EFI_STUB && (EFI_GENERIC_STUB || X86)
|
||||
default y
|
||||
depends on !RISCV
|
||||
help
|
||||
Select this config option to add support for the initrd= command
|
||||
line parameter, allowing an initrd that resides on the same volume
|
||||
|
@ -36,6 +36,8 @@ fake_map-$(CONFIG_X86) += x86_fake_mem.o
|
||||
arm-obj-$(CONFIG_EFI) := efi-init.o arm-runtime.o
|
||||
obj-$(CONFIG_ARM) += $(arm-obj-y)
|
||||
obj-$(CONFIG_ARM64) += $(arm-obj-y)
|
||||
riscv-obj-$(CONFIG_EFI) := efi-init.o riscv-runtime.o
|
||||
obj-$(CONFIG_RISCV) += $(riscv-obj-y)
|
||||
obj-$(CONFIG_EFI_CAPSULE_LOADER) += capsule-loader.o
|
||||
obj-$(CONFIG_EFI_EARLYCON) += earlycon.o
|
||||
obj-$(CONFIG_UEFI_CPER_ARM) += cper-arm.o
|
||||
|
@ -23,6 +23,8 @@ cflags-$(CONFIG_ARM64) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
|
||||
cflags-$(CONFIG_ARM) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
|
||||
-fno-builtin -fpic \
|
||||
$(call cc-option,-mno-single-pic-base)
|
||||
cflags-$(CONFIG_RISCV) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \
|
||||
-fpic
|
||||
|
||||
cflags-$(CONFIG_EFI_GENERIC_STUB) += -I$(srctree)/scripts/dtc/libfdt
|
||||
|
||||
@ -64,6 +66,7 @@ lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o fdt.o string.o \
|
||||
lib-$(CONFIG_ARM) += arm32-stub.o
|
||||
lib-$(CONFIG_ARM64) += arm64-stub.o
|
||||
lib-$(CONFIG_X86) += x86-stub.o
|
||||
lib-$(CONFIG_RISCV) += riscv-stub.o
|
||||
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
|
||||
|
||||
# Even when -mbranch-protection=none is set, Clang will generate a
|
||||
@ -112,6 +115,13 @@ STUBCOPY_FLAGS-$(CONFIG_ARM64) += --prefix-alloc-sections=.init \
|
||||
--prefix-symbols=__efistub_
|
||||
STUBCOPY_RELOC-$(CONFIG_ARM64) := R_AARCH64_ABS
|
||||
|
||||
# For RISC-V, we don't need anything special other than arm64. Keep all the
|
||||
# symbols in .init section and make sure that no absolute symbols references
|
||||
# doesn't exist.
|
||||
STUBCOPY_FLAGS-$(CONFIG_RISCV) += --prefix-alloc-sections=.init \
|
||||
--prefix-symbols=__efistub_
|
||||
STUBCOPY_RELOC-$(CONFIG_RISCV) := R_RISCV_HI20
|
||||
|
||||
$(obj)/%.stub.o: $(obj)/%.o FORCE
|
||||
$(call if_changed,stubcopy)
|
||||
|
||||
|
@ -17,7 +17,10 @@
|
||||
|
||||
/*
|
||||
* This is the base address at which to start allocating virtual memory ranges
|
||||
* for UEFI Runtime Services. This is in the low TTBR0 range so that we can use
|
||||
* for UEFI Runtime Services.
|
||||
*
|
||||
* For ARM/ARM64:
|
||||
* This is in the low TTBR0 range so that we can use
|
||||
* any allocation we choose, and eliminate the risk of a conflict after kexec.
|
||||
* The value chosen is the largest non-zero power of 2 suitable for this purpose
|
||||
* both on 32-bit and 64-bit ARM CPUs, to maximize the likelihood that it can
|
||||
@ -25,6 +28,12 @@
|
||||
* Since 32-bit ARM could potentially execute with a 1G/3G user/kernel split,
|
||||
* map everything below 1 GB. (512 MB is a reasonable upper bound for the
|
||||
* entire footprint of the UEFI runtime services memory regions)
|
||||
*
|
||||
* For RISC-V:
|
||||
* There is no specific reason for which, this address (512MB) can't be used
|
||||
* EFI runtime virtual address for RISC-V. It also helps to use EFI runtime
|
||||
* services on both RV32/RV64. Keep the same runtime virtual address for RISC-V
|
||||
* as well to minimize the code churn.
|
||||
*/
|
||||
#define EFI_RT_VIRTUAL_BASE SZ_512M
|
||||
#define EFI_RT_VIRTUAL_SIZE SZ_512M
|
||||
|
109
drivers/firmware/efi/libstub/riscv-stub.c
Normal file
109
drivers/firmware/efi/libstub/riscv-stub.c
Normal file
@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/libfdt.h>
|
||||
|
||||
#include <asm/efi.h>
|
||||
#include <asm/sections.h>
|
||||
|
||||
#include "efistub.h"
|
||||
|
||||
/*
|
||||
* RISC-V requires the kernel image to placed 2 MB aligned base for 64 bit and
|
||||
* 4MB for 32 bit.
|
||||
*/
|
||||
#ifdef CONFIG_64BIT
|
||||
#define MIN_KIMG_ALIGN SZ_2M
|
||||
#else
|
||||
#define MIN_KIMG_ALIGN SZ_4M
|
||||
#endif
|
||||
|
||||
typedef void __noreturn (*jump_kernel_func)(unsigned int, unsigned long);
|
||||
|
||||
static u32 hartid;
|
||||
|
||||
static u32 get_boot_hartid_from_fdt(void)
|
||||
{
|
||||
const void *fdt;
|
||||
int chosen_node, len;
|
||||
const fdt32_t *prop;
|
||||
|
||||
fdt = get_efi_config_table(DEVICE_TREE_GUID);
|
||||
if (!fdt)
|
||||
return U32_MAX;
|
||||
|
||||
chosen_node = fdt_path_offset(fdt, "/chosen");
|
||||
if (chosen_node < 0)
|
||||
return U32_MAX;
|
||||
|
||||
prop = fdt_getprop((void *)fdt, chosen_node, "boot-hartid", &len);
|
||||
if (!prop || len != sizeof(u32))
|
||||
return U32_MAX;
|
||||
|
||||
return fdt32_to_cpu(*prop);
|
||||
}
|
||||
|
||||
efi_status_t check_platform_features(void)
|
||||
{
|
||||
hartid = get_boot_hartid_from_fdt();
|
||||
if (hartid == U32_MAX) {
|
||||
efi_err("/chosen/boot-hartid missing or invalid!\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
void __noreturn efi_enter_kernel(unsigned long entrypoint, unsigned long fdt,
|
||||
unsigned long fdt_size)
|
||||
{
|
||||
unsigned long stext_offset = _start_kernel - _start;
|
||||
unsigned long kernel_entry = entrypoint + stext_offset;
|
||||
jump_kernel_func jump_kernel = (jump_kernel_func)kernel_entry;
|
||||
|
||||
/*
|
||||
* Jump to real kernel here with following constraints.
|
||||
* 1. MMU should be disabled.
|
||||
* 2. a0 should contain hartid
|
||||
* 3. a1 should DT address
|
||||
*/
|
||||
csr_write(CSR_SATP, 0);
|
||||
jump_kernel(hartid, fdt);
|
||||
}
|
||||
|
||||
efi_status_t handle_kernel_image(unsigned long *image_addr,
|
||||
unsigned long *image_size,
|
||||
unsigned long *reserve_addr,
|
||||
unsigned long *reserve_size,
|
||||
efi_loaded_image_t *image)
|
||||
{
|
||||
unsigned long kernel_size = 0;
|
||||
unsigned long preferred_addr;
|
||||
efi_status_t status;
|
||||
|
||||
kernel_size = _edata - _start;
|
||||
*image_addr = (unsigned long)_start;
|
||||
*image_size = kernel_size + (_end - _edata);
|
||||
|
||||
/*
|
||||
* RISC-V kernel maps PAGE_OFFSET virtual address to the same physical
|
||||
* address where kernel is booted. That's why kernel should boot from
|
||||
* as low as possible to avoid wastage of memory. Currently, dram_base
|
||||
* is occupied by the firmware. So the preferred address for kernel to
|
||||
* boot is next aligned address. If preferred address is not available,
|
||||
* relocate_kernel will fall back to efi_low_alloc_above to allocate
|
||||
* lowest possible memory region as long as the address and size meets
|
||||
* the alignment constraints.
|
||||
*/
|
||||
preferred_addr = MIN_KIMG_ALIGN;
|
||||
status = efi_relocate_kernel(image_addr, kernel_size, *image_size,
|
||||
preferred_addr, MIN_KIMG_ALIGN, 0x0);
|
||||
|
||||
if (status != EFI_SUCCESS) {
|
||||
efi_err("Failed to relocate kernel\n");
|
||||
*image_size = 0;
|
||||
}
|
||||
return status;
|
||||
}
|
143
drivers/firmware/efi/riscv-runtime.c
Normal file
143
drivers/firmware/efi/riscv-runtime.c
Normal file
@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Extensible Firmware Interface
|
||||
*
|
||||
* Copyright (C) 2020 Western Digital Corporation or its affiliates.
|
||||
*
|
||||
* Based on Extensible Firmware Interface Specification version 2.4
|
||||
* Adapted from drivers/firmware/efi/arm-runtime.c
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/mm_types.h>
|
||||
#include <linux/preempt.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/pgtable.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/efi.h>
|
||||
#include <asm/mmu.h>
|
||||
#include <asm/pgalloc.h>
|
||||
|
||||
static bool __init efi_virtmap_init(void)
|
||||
{
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
efi_mm.pgd = pgd_alloc(&efi_mm);
|
||||
mm_init_cpumask(&efi_mm);
|
||||
init_new_context(NULL, &efi_mm);
|
||||
|
||||
for_each_efi_memory_desc(md) {
|
||||
phys_addr_t phys = md->phys_addr;
|
||||
int ret;
|
||||
|
||||
if (!(md->attribute & EFI_MEMORY_RUNTIME))
|
||||
continue;
|
||||
if (md->virt_addr == 0)
|
||||
return false;
|
||||
|
||||
ret = efi_create_mapping(&efi_mm, md);
|
||||
if (ret) {
|
||||
pr_warn(" EFI remap %pa: failed to create mapping (%d)\n",
|
||||
&phys, ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (efi_memattr_apply_permissions(&efi_mm, efi_set_mapping_permissions))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable the UEFI Runtime Services if all prerequisites are in place, i.e.,
|
||||
* non-early mapping of the UEFI system table and virtual mappings for all
|
||||
* EFI_MEMORY_RUNTIME regions.
|
||||
*/
|
||||
static int __init riscv_enable_runtime_services(void)
|
||||
{
|
||||
u64 mapsize;
|
||||
|
||||
if (!efi_enabled(EFI_BOOT)) {
|
||||
pr_info("EFI services will not be available.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
efi_memmap_unmap();
|
||||
|
||||
mapsize = efi.memmap.desc_size * efi.memmap.nr_map;
|
||||
|
||||
if (efi_memmap_init_late(efi.memmap.phys_map, mapsize)) {
|
||||
pr_err("Failed to remap EFI memory map\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (efi_soft_reserve_enabled()) {
|
||||
efi_memory_desc_t *md;
|
||||
|
||||
for_each_efi_memory_desc(md) {
|
||||
int md_size = md->num_pages << EFI_PAGE_SHIFT;
|
||||
struct resource *res;
|
||||
|
||||
if (!(md->attribute & EFI_MEMORY_SP))
|
||||
continue;
|
||||
|
||||
res = kzalloc(sizeof(*res), GFP_KERNEL);
|
||||
if (WARN_ON(!res))
|
||||
break;
|
||||
|
||||
res->start = md->phys_addr;
|
||||
res->end = md->phys_addr + md_size - 1;
|
||||
res->name = "Soft Reserved";
|
||||
res->flags = IORESOURCE_MEM;
|
||||
res->desc = IORES_DESC_SOFT_RESERVED;
|
||||
|
||||
insert_resource(&iomem_resource, res);
|
||||
}
|
||||
}
|
||||
|
||||
if (efi_runtime_disabled()) {
|
||||
pr_info("EFI runtime services will be disabled.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (efi_enabled(EFI_RUNTIME_SERVICES)) {
|
||||
pr_info("EFI runtime services access via paravirt.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_info("Remapping and enabling EFI services.\n");
|
||||
|
||||
if (!efi_virtmap_init()) {
|
||||
pr_err("UEFI virtual mapping missing or invalid -- runtime services will not be available\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Set up runtime services function pointers */
|
||||
efi_native_runtime_setup();
|
||||
set_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_initcall(riscv_enable_runtime_services);
|
||||
|
||||
void efi_virtmap_load(void)
|
||||
{
|
||||
preempt_disable();
|
||||
switch_mm(current->active_mm, &efi_mm, NULL);
|
||||
}
|
||||
|
||||
void efi_virtmap_unload(void)
|
||||
{
|
||||
switch_mm(&efi_mm, current->active_mm, NULL);
|
||||
preempt_enable();
|
||||
}
|
Loading…
Reference in New Issue
Block a user