Merge branch 'devel-stable' into for-next
This commit is contained in:
commit
02196144a0
@ -240,15 +240,6 @@ config NEED_RET_TO_USER
|
||||
config ARCH_MTD_XIP
|
||||
bool
|
||||
|
||||
config VECTORS_BASE
|
||||
hex
|
||||
default 0xffff0000 if MMU || CPU_HIGH_VECTOR
|
||||
default DRAM_BASE if REMAP_VECTORS_TO_RAM
|
||||
default 0x00000000
|
||||
help
|
||||
The base address of exception vectors. This must be two pages
|
||||
in size.
|
||||
|
||||
config ARM_PATCH_PHYS_VIRT
|
||||
bool "Patch physical to virtual translations at runtime" if EMBEDDED
|
||||
default y
|
||||
@ -2006,6 +1997,17 @@ config XIP_PHYS_ADDR
|
||||
be linked for and stored to. This address is dependent on your
|
||||
own flash usage.
|
||||
|
||||
config XIP_DEFLATED_DATA
|
||||
bool "Store kernel .data section compressed in ROM"
|
||||
depends on XIP_KERNEL
|
||||
select ZLIB_INFLATE
|
||||
help
|
||||
Before the kernel is actually executed, its .data section has to be
|
||||
copied to RAM from ROM. This option allows for storing that data
|
||||
in compressed form and decompressed to RAM rather than merely being
|
||||
copied, saving some precious ROM space. A possible drawback is a
|
||||
slightly longer boot delay.
|
||||
|
||||
config KEXEC
|
||||
bool "Kexec system call (EXPERIMENTAL)"
|
||||
depends on (!SMP || PM_SLEEP_SMP)
|
||||
|
@ -52,8 +52,8 @@ config REMAP_VECTORS_TO_RAM
|
||||
|
||||
config ARM_MPU
|
||||
bool 'Use the ARM v7 PMSA Compliant MPU'
|
||||
depends on CPU_V7
|
||||
default y
|
||||
depends on CPU_V7 || CPU_V7M
|
||||
default y if CPU_V7
|
||||
help
|
||||
Some ARM systems without an MMU have instead a Memory Protection
|
||||
Unit (MPU) that defines the type and permissions for regions of
|
||||
|
@ -31,8 +31,19 @@ targets := Image zImage xipImage bootpImage uImage
|
||||
|
||||
ifeq ($(CONFIG_XIP_KERNEL),y)
|
||||
|
||||
cmd_deflate_xip_data = $(CONFIG_SHELL) -c \
|
||||
'$(srctree)/$(src)/deflate_xip_data.sh $< $@ || { rm -f $@; false; }'
|
||||
|
||||
ifeq ($(CONFIG_XIP_DEFLATED_DATA),y)
|
||||
quiet_cmd_mkxip = XIPZ $@
|
||||
cmd_mkxip = $(cmd_objcopy) && $(cmd_deflate_xip_data)
|
||||
else
|
||||
quiet_cmd_mkxip = $(quiet_cmd_objcopy)
|
||||
cmd_mkxip = $(cmd_objcopy)
|
||||
endif
|
||||
|
||||
$(obj)/xipImage: vmlinux FORCE
|
||||
$(call if_changed,objcopy)
|
||||
$(call if_changed,mkxip)
|
||||
@$(kecho) ' Physical Address of xipImage: $(CONFIG_XIP_PHYS_ADDR)'
|
||||
|
||||
$(obj)/Image $(obj)/zImage: FORCE
|
||||
|
64
arch/arm/boot/deflate_xip_data.sh
Executable file
64
arch/arm/boot/deflate_xip_data.sh
Executable file
@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
|
||||
# XIP kernel .data segment compressor
|
||||
#
|
||||
# Created by: Nicolas Pitre, August 2017
|
||||
# Copyright: (C) 2017 Linaro Limited
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License version 2 as
|
||||
# published by the Free Software Foundation.
|
||||
|
||||
# This script locates the start of the .data section in xipImage and
|
||||
# substitutes it with a compressed version. The needed offsets are obtained
|
||||
# from symbol addresses in vmlinux. It is expected that .data extends to
|
||||
# the end of xipImage.
|
||||
|
||||
set -e
|
||||
|
||||
VMLINUX="$1"
|
||||
XIPIMAGE="$2"
|
||||
|
||||
DD="dd status=none"
|
||||
|
||||
# Use "make V=1" to debug this script.
|
||||
case "$KBUILD_VERBOSE" in
|
||||
*1*)
|
||||
set -x
|
||||
;;
|
||||
esac
|
||||
|
||||
sym_val() {
|
||||
# extract hex value for symbol in $1
|
||||
local val=$($NM "$VMLINUX" | sed -n "/ $1$/{s/ .*$//p;q}")
|
||||
[ "$val" ] || { echo "can't find $1 in $VMLINUX" 1>&2; exit 1; }
|
||||
# convert from hex to decimal
|
||||
echo $((0x$val))
|
||||
}
|
||||
|
||||
__data_loc=$(sym_val __data_loc)
|
||||
_edata_loc=$(sym_val _edata_loc)
|
||||
base_offset=$(sym_val _xiprom)
|
||||
|
||||
# convert to file based offsets
|
||||
data_start=$(($__data_loc - $base_offset))
|
||||
data_end=$(($_edata_loc - $base_offset))
|
||||
|
||||
# Make sure data occupies the last part of the file.
|
||||
file_end=$(stat -c "%s" "$XIPIMAGE")
|
||||
if [ "$file_end" != "$data_end" ]; then
|
||||
printf "end of xipImage doesn't match with _edata_loc (%#x vs %#x)\n" \
|
||||
$(($file_end + $base_offset)) $_edata_loc 2>&1
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# be ready to clean up
|
||||
trap 'rm -f "$XIPIMAGE.tmp"' 0 1 2 3
|
||||
|
||||
# substitute the data section by a compressed version
|
||||
$DD if="$XIPIMAGE" count=$data_start iflag=count_bytes of="$XIPIMAGE.tmp"
|
||||
$DD if="$XIPIMAGE" skip=$data_start iflag=skip_bytes |
|
||||
gzip -9 >> "$XIPIMAGE.tmp"
|
||||
|
||||
# replace kernel binary
|
||||
mv -f "$XIPIMAGE.tmp" "$XIPIMAGE"
|
@ -173,6 +173,11 @@ static inline unsigned int __attribute_const__ read_cpuid_cachetype(void)
|
||||
return read_cpuid(CPUID_CACHETYPE);
|
||||
}
|
||||
|
||||
static inline unsigned int __attribute_const__ read_cpuid_mputype(void)
|
||||
{
|
||||
return read_cpuid(CPUID_MPUIR);
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_CPU_V7M)
|
||||
|
||||
static inline unsigned int __attribute_const__ read_cpuid_id(void)
|
||||
@ -185,6 +190,11 @@ static inline unsigned int __attribute_const__ read_cpuid_cachetype(void)
|
||||
return readl(BASEADDR_V7M_SCB + V7M_SCB_CTR);
|
||||
}
|
||||
|
||||
static inline unsigned int __attribute_const__ read_cpuid_mputype(void)
|
||||
{
|
||||
return readl(BASEADDR_V7M_SCB + MPU_TYPE);
|
||||
}
|
||||
|
||||
#else /* ifdef CONFIG_CPU_CP15 / elif defined(CONFIG_CPU_V7M) */
|
||||
|
||||
static inline unsigned int __attribute_const__ read_cpuid_id(void)
|
||||
|
@ -100,10 +100,15 @@ struct elf32_hdr;
|
||||
extern int elf_check_arch(const struct elf32_hdr *);
|
||||
#define elf_check_arch elf_check_arch
|
||||
|
||||
#define ELFOSABI_ARM_FDPIC 65 /* ARM FDPIC platform */
|
||||
#define elf_check_fdpic(x) ((x)->e_ident[EI_OSABI] == ELFOSABI_ARM_FDPIC)
|
||||
#define elf_check_const_displacement(x) ((x)->e_flags & EF_ARM_PIC)
|
||||
#define ELF_FDPIC_CORE_EFLAGS 0
|
||||
|
||||
#define vmcore_elf64_check_arch(x) (0)
|
||||
|
||||
extern int arm_elf_read_implies_exec(const struct elf32_hdr *, int);
|
||||
#define elf_read_implies_exec(ex,stk) arm_elf_read_implies_exec(&(ex), stk)
|
||||
extern int arm_elf_read_implies_exec(int);
|
||||
#define elf_read_implies_exec(ex,stk) arm_elf_read_implies_exec(stk)
|
||||
|
||||
struct task_struct;
|
||||
int dump_task_regs(struct task_struct *t, elf_gregset_t *elfregs);
|
||||
@ -120,6 +125,13 @@ int dump_task_regs(struct task_struct *t, elf_gregset_t *elfregs);
|
||||
have no such handler. */
|
||||
#define ELF_PLAT_INIT(_r, load_addr) (_r)->ARM_r0 = 0
|
||||
|
||||
#define ELF_FDPIC_PLAT_INIT(_r, _exec_map_addr, _interp_map_addr, dynamic_addr) \
|
||||
do { \
|
||||
(_r)->ARM_r7 = _exec_map_addr; \
|
||||
(_r)->ARM_r8 = _interp_map_addr; \
|
||||
(_r)->ARM_r9 = dynamic_addr; \
|
||||
} while(0)
|
||||
|
||||
extern void elf_set_personality(const struct elf32_hdr *);
|
||||
#define SET_PERSONALITY(ex) elf_set_personality(&(ex))
|
||||
|
||||
|
@ -14,6 +14,10 @@ typedef struct {
|
||||
#ifdef CONFIG_VDSO
|
||||
unsigned long vdso;
|
||||
#endif
|
||||
#ifdef CONFIG_BINFMT_ELF_FDPIC
|
||||
unsigned long exec_fdpic_loadmap;
|
||||
unsigned long interp_fdpic_loadmap;
|
||||
#endif
|
||||
} mm_context_t;
|
||||
|
||||
#ifdef CONFIG_CPU_HAS_ASID
|
||||
@ -33,6 +37,10 @@ typedef struct {
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned long end_brk;
|
||||
#ifdef CONFIG_BINFMT_ELF_FDPIC
|
||||
unsigned long exec_fdpic_loadmap;
|
||||
unsigned long interp_fdpic_loadmap;
|
||||
#endif
|
||||
} mm_context_t;
|
||||
|
||||
#endif
|
||||
|
@ -1,8 +1,6 @@
|
||||
#ifndef __ARM_MPU_H
|
||||
#define __ARM_MPU_H
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
|
||||
/* MPUIR layout */
|
||||
#define MPUIR_nU 1
|
||||
#define MPUIR_DREGION 8
|
||||
@ -17,6 +15,11 @@
|
||||
/* MPU D/I Size Register fields */
|
||||
#define MPU_RSR_SZ 1
|
||||
#define MPU_RSR_EN 0
|
||||
#define MPU_RSR_SD 8
|
||||
|
||||
/* Number of subregions (SD) */
|
||||
#define MPU_NR_SUBREGS 8
|
||||
#define MPU_MIN_SUBREG_SIZE 256
|
||||
|
||||
/* The D/I RSR value for an enabled region spanning the whole of memory */
|
||||
#define MPU_RSR_ALL_MEM 63
|
||||
@ -38,6 +41,7 @@
|
||||
#endif
|
||||
|
||||
/* Access permission bits of ACR (only define those that we use)*/
|
||||
#define MPU_AP_PL1RO_PL0NA (0x5 << 8)
|
||||
#define MPU_AP_PL1RW_PL0RW (0x3 << 8)
|
||||
#define MPU_AP_PL1RW_PL0R0 (0x2 << 8)
|
||||
#define MPU_AP_PL1RW_PL0NA (0x1 << 8)
|
||||
@ -46,7 +50,7 @@
|
||||
#define MPU_PROBE_REGION 0
|
||||
#define MPU_BG_REGION 1
|
||||
#define MPU_RAM_REGION 2
|
||||
#define MPU_VECTORS_REGION 3
|
||||
#define MPU_ROM_REGION 3
|
||||
|
||||
/* Maximum number of regions Linux is interested in */
|
||||
#define MPU_MAX_REGIONS 16
|
||||
@ -64,13 +68,23 @@ struct mpu_rgn {
|
||||
};
|
||||
|
||||
struct mpu_rgn_info {
|
||||
u32 mpuir;
|
||||
unsigned int used;
|
||||
struct mpu_rgn rgns[MPU_MAX_REGIONS];
|
||||
};
|
||||
extern struct mpu_rgn_info mpu_rgn_info;
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
|
||||
extern void __init adjust_lowmem_bounds_mpu(void);
|
||||
extern void __init mpu_setup(void);
|
||||
|
||||
#else
|
||||
|
||||
static inline void adjust_lowmem_bounds_mpu(void) {}
|
||||
static inline void mpu_setup(void) {}
|
||||
|
||||
#endif /* !CONFIG_ARM_MPU */
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#endif /* CONFIG_ARM_MPU */
|
||||
|
||||
#endif
|
||||
|
@ -47,15 +47,24 @@ struct thread_struct {
|
||||
|
||||
#define INIT_THREAD { }
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
#define nommu_start_thread(regs) do { } while (0)
|
||||
#else
|
||||
#define nommu_start_thread(regs) regs->ARM_r10 = current->mm->start_data
|
||||
#endif
|
||||
|
||||
#define start_thread(regs,pc,sp) \
|
||||
({ \
|
||||
unsigned long r7, r8, r9; \
|
||||
\
|
||||
if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC)) { \
|
||||
r7 = regs->ARM_r7; \
|
||||
r8 = regs->ARM_r8; \
|
||||
r9 = regs->ARM_r9; \
|
||||
} \
|
||||
memset(regs->uregs, 0, sizeof(regs->uregs)); \
|
||||
if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) && \
|
||||
current->personality & FDPIC_FUNCPTRS) { \
|
||||
regs->ARM_r7 = r7; \
|
||||
regs->ARM_r8 = r8; \
|
||||
regs->ARM_r9 = r9; \
|
||||
regs->ARM_r10 = current->mm->start_data; \
|
||||
} else if (!IS_ENABLED(CONFIG_MMU)) \
|
||||
regs->ARM_r10 = current->mm->start_data; \
|
||||
if (current->personality & ADDR_LIMIT_32BIT) \
|
||||
regs->ARM_cpsr = USR_MODE; \
|
||||
else \
|
||||
@ -65,7 +74,6 @@ struct thread_struct {
|
||||
regs->ARM_cpsr |= PSR_ENDSTATE; \
|
||||
regs->ARM_pc = pc & ~1; /* pc */ \
|
||||
regs->ARM_sp = sp; /* sp */ \
|
||||
nommu_start_thread(regs); \
|
||||
})
|
||||
|
||||
/* Forward declaration, a strange C thing */
|
||||
|
@ -60,7 +60,7 @@ asmlinkage void secondary_start_kernel(void);
|
||||
*/
|
||||
struct secondary_data {
|
||||
union {
|
||||
unsigned long mpu_rgn_szr;
|
||||
struct mpu_rgn_info *mpu_rgn_info;
|
||||
u64 pgdir;
|
||||
};
|
||||
unsigned long swapper_pg_dir;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define _ASMARM_UCONTEXT_H
|
||||
|
||||
#include <asm/fpstate.h>
|
||||
#include <asm/user.h>
|
||||
|
||||
/*
|
||||
* struct sigcontext only has room for the basic registers, but struct
|
||||
|
@ -57,6 +57,16 @@
|
||||
#define V7M_SCB_CCSIDR 0x80 /* Cache size ID register */
|
||||
#define V7M_SCB_CSSELR 0x84 /* Cache size selection register */
|
||||
|
||||
/* Memory-mapped MPU registers for M-class */
|
||||
#define MPU_TYPE 0x90
|
||||
#define MPU_CTRL 0x94
|
||||
#define MPU_CTRL_ENABLE 1
|
||||
#define MPU_CTRL_PRIVDEFENA (1 << 2)
|
||||
|
||||
#define MPU_RNR 0x98
|
||||
#define MPU_RBAR 0x9c
|
||||
#define MPU_RASR 0xa0
|
||||
|
||||
/* Cache opeartions */
|
||||
#define V7M_SCB_ICIALLU 0x250 /* I-cache invalidate all to PoU */
|
||||
#define V7M_SCB_ICIMVAU 0x258 /* I-cache invalidate by MVA to PoU */
|
||||
|
@ -31,6 +31,10 @@
|
||||
#define PTRACE_SETVFPREGS 28
|
||||
#define PTRACE_GETHBPREGS 29
|
||||
#define PTRACE_SETHBPREGS 30
|
||||
#define PTRACE_GETFDPIC 31
|
||||
|
||||
#define PTRACE_GETFDPIC_EXEC 0
|
||||
#define PTRACE_GETFDPIC_INTERP 1
|
||||
|
||||
/*
|
||||
* PSR bits
|
||||
|
@ -35,5 +35,6 @@
|
||||
#define __ARM_NR_usr26 (__ARM_NR_BASE+3)
|
||||
#define __ARM_NR_usr32 (__ARM_NR_BASE+4)
|
||||
#define __ARM_NR_set_tls (__ARM_NR_BASE+5)
|
||||
#define __ARM_NR_get_tls (__ARM_NR_BASE+6)
|
||||
|
||||
#endif /* _UAPI__ASM_ARM_UNISTD_H */
|
||||
|
@ -87,6 +87,11 @@ head-y := head$(MMUEXT).o
|
||||
obj-$(CONFIG_DEBUG_LL) += debug.o
|
||||
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
|
||||
|
||||
# This is executed very early using a temporary stack when no memory allocator
|
||||
# nor global data is available. Everything has to be allocated on the stack.
|
||||
CFLAGS_head-inflate-data.o := $(call cc-option,-Wframe-larger-than=10240)
|
||||
obj-$(CONFIG_XIP_DEFLATED_DATA) += head-inflate-data.o
|
||||
|
||||
obj-$(CONFIG_ARM_VIRT_EXT) += hyp-stub.o
|
||||
AFLAGS_hyp-stub.o :=-Wa,-march=armv7-a
|
||||
ifeq ($(CONFIG_ARM_PSCI),y)
|
||||
|
@ -23,11 +23,13 @@
|
||||
#include <asm/mach/arch.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/memory.h>
|
||||
#include <asm/mpu.h>
|
||||
#include <asm/procinfo.h>
|
||||
#include <asm/suspend.h>
|
||||
#include <asm/vdso_datapage.h>
|
||||
#include <asm/hardware/cache-l2x0.h>
|
||||
#include <linux/kbuild.h>
|
||||
#include "signal.h"
|
||||
|
||||
/*
|
||||
* Make sure that the compiler and target are compatible.
|
||||
@ -112,6 +114,9 @@ int main(void)
|
||||
DEFINE(SVC_ADDR_LIMIT, offsetof(struct svc_pt_regs, addr_limit));
|
||||
DEFINE(SVC_REGS_SIZE, sizeof(struct svc_pt_regs));
|
||||
BLANK();
|
||||
DEFINE(SIGFRAME_RC3_OFFSET, offsetof(struct sigframe, retcode[3]));
|
||||
DEFINE(RT_SIGFRAME_RC3_OFFSET, offsetof(struct rt_sigframe, sig.retcode[3]));
|
||||
BLANK();
|
||||
#ifdef CONFIG_CACHE_L2X0
|
||||
DEFINE(L2X0_R_PHY_BASE, offsetof(struct l2x0_regs, phy_base));
|
||||
DEFINE(L2X0_R_AUX_CTRL, offsetof(struct l2x0_regs, aux_ctrl));
|
||||
@ -182,6 +187,16 @@ int main(void)
|
||||
BLANK();
|
||||
#ifdef CONFIG_VDSO
|
||||
DEFINE(VDSO_DATA_SIZE, sizeof(union vdso_data_store));
|
||||
#endif
|
||||
BLANK();
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
DEFINE(MPU_RNG_INFO_RNGS, offsetof(struct mpu_rgn_info, rgns));
|
||||
DEFINE(MPU_RNG_INFO_USED, offsetof(struct mpu_rgn_info, used));
|
||||
|
||||
DEFINE(MPU_RNG_SIZE, sizeof(struct mpu_rgn));
|
||||
DEFINE(MPU_RGN_DRBAR, offsetof(struct mpu_rgn, drbar));
|
||||
DEFINE(MPU_RGN_DRSR, offsetof(struct mpu_rgn, drsr));
|
||||
DEFINE(MPU_RGN_DRACR, offsetof(struct mpu_rgn, dracr));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <linux/personality.h>
|
||||
#include <linux/binfmts.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/elf-fdpic.h>
|
||||
#include <asm/system_info.h>
|
||||
|
||||
int elf_check_arch(const struct elf32_hdr *x)
|
||||
@ -80,7 +81,7 @@ EXPORT_SYMBOL(elf_set_personality);
|
||||
* - the binary requires an executable stack
|
||||
* - we're running on a CPU which doesn't support NX.
|
||||
*/
|
||||
int arm_elf_read_implies_exec(const struct elf32_hdr *x, int executable_stack)
|
||||
int arm_elf_read_implies_exec(int executable_stack)
|
||||
{
|
||||
if (executable_stack != EXSTACK_DISABLE_X)
|
||||
return 1;
|
||||
@ -89,3 +90,24 @@ int arm_elf_read_implies_exec(const struct elf32_hdr *x, int executable_stack)
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(arm_elf_read_implies_exec);
|
||||
|
||||
#if defined(CONFIG_MMU) && defined(CONFIG_BINFMT_ELF_FDPIC)
|
||||
|
||||
void elf_fdpic_arch_lay_out_mm(struct elf_fdpic_params *exec_params,
|
||||
struct elf_fdpic_params *interp_params,
|
||||
unsigned long *start_stack,
|
||||
unsigned long *start_brk)
|
||||
{
|
||||
elf_set_personality(&exec_params->hdr);
|
||||
|
||||
exec_params->load_addr = 0x8000;
|
||||
interp_params->load_addr = ELF_ET_DYN_BASE;
|
||||
*start_stack = TASK_SIZE - SZ_16M;
|
||||
|
||||
if ((exec_params->flags & ELF_FDPIC_FLAG_ARRANGEMENT) == ELF_FDPIC_FLAG_INDEPENDENT) {
|
||||
exec_params->flags &= ~ELF_FDPIC_FLAG_ARRANGEMENT;
|
||||
exec_params->flags |= ELF_FDPIC_FLAG_CONSTDISP;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -79,47 +79,69 @@ ENDPROC(__vet_atags)
|
||||
*/
|
||||
__INIT
|
||||
__mmap_switched:
|
||||
adr r3, __mmap_switched_data
|
||||
|
||||
ldmia r3!, {r4, r5, r6, r7}
|
||||
cmp r4, r5 @ Copy data segment if needed
|
||||
1: cmpne r5, r6
|
||||
ldrne fp, [r4], #4
|
||||
strne fp, [r5], #4
|
||||
bne 1b
|
||||
mov r7, r1
|
||||
mov r8, r2
|
||||
mov r10, r0
|
||||
|
||||
mov fp, #0 @ Clear BSS (and zero fp)
|
||||
1: cmp r6, r7
|
||||
strcc fp, [r6],#4
|
||||
bcc 1b
|
||||
adr r4, __mmap_switched_data
|
||||
mov fp, #0
|
||||
|
||||
ARM( ldmia r3, {r4, r5, r6, r7, sp})
|
||||
THUMB( ldmia r3, {r4, r5, r6, r7} )
|
||||
THUMB( ldr sp, [r3, #16] )
|
||||
str r9, [r4] @ Save processor ID
|
||||
str r1, [r5] @ Save machine type
|
||||
str r2, [r6] @ Save atags pointer
|
||||
cmp r7, #0
|
||||
strne r0, [r7] @ Save control register values
|
||||
#if defined(CONFIG_XIP_DEFLATED_DATA)
|
||||
ARM( ldr sp, [r4], #4 )
|
||||
THUMB( ldr sp, [r4] )
|
||||
THUMB( add r4, #4 )
|
||||
bl __inflate_kernel_data @ decompress .data to RAM
|
||||
teq r0, #0
|
||||
bne __error
|
||||
#elif defined(CONFIG_XIP_KERNEL)
|
||||
ARM( ldmia r4!, {r0, r1, r2, sp} )
|
||||
THUMB( ldmia r4!, {r0, r1, r2, r3} )
|
||||
THUMB( mov sp, r3 )
|
||||
sub r2, r2, r1
|
||||
bl memcpy @ copy .data to RAM
|
||||
#endif
|
||||
|
||||
ARM( ldmia r4!, {r0, r1, sp} )
|
||||
THUMB( ldmia r4!, {r0, r1, r3} )
|
||||
THUMB( mov sp, r3 )
|
||||
sub r1, r1, r0
|
||||
bl __memzero @ clear .bss
|
||||
|
||||
ldmia r4, {r0, r1, r2, r3}
|
||||
str r9, [r0] @ Save processor ID
|
||||
str r7, [r1] @ Save machine type
|
||||
str r8, [r2] @ Save atags pointer
|
||||
cmp r3, #0
|
||||
strne r10, [r3] @ Save control register values
|
||||
mov lr, #0
|
||||
b start_kernel
|
||||
ENDPROC(__mmap_switched)
|
||||
|
||||
.align 2
|
||||
.type __mmap_switched_data, %object
|
||||
__mmap_switched_data:
|
||||
.long __data_loc @ r4
|
||||
.long _sdata @ r5
|
||||
.long __bss_start @ r6
|
||||
.long _end @ r7
|
||||
.long processor_id @ r4
|
||||
.long __machine_arch_type @ r5
|
||||
.long __atags_pointer @ r6
|
||||
#ifdef CONFIG_CPU_CP15
|
||||
.long cr_alignment @ r7
|
||||
#else
|
||||
.long 0 @ r7
|
||||
#ifdef CONFIG_XIP_KERNEL
|
||||
#ifndef CONFIG_XIP_DEFLATED_DATA
|
||||
.long _sdata @ r0
|
||||
.long __data_loc @ r1
|
||||
.long _edata_loc @ r2
|
||||
#endif
|
||||
.long __bss_stop @ sp (temporary stack in .bss)
|
||||
#endif
|
||||
|
||||
.long __bss_start @ r0
|
||||
.long __bss_stop @ r1
|
||||
.long init_thread_union + THREAD_START_SP @ sp
|
||||
|
||||
.long processor_id @ r0
|
||||
.long __machine_arch_type @ r1
|
||||
.long __atags_pointer @ r2
|
||||
#ifdef CONFIG_CPU_CP15
|
||||
.long cr_alignment @ r3
|
||||
#else
|
||||
.long 0 @ r3
|
||||
#endif
|
||||
.size __mmap_switched_data, . - __mmap_switched_data
|
||||
|
||||
/*
|
||||
|
62
arch/arm/kernel/head-inflate-data.c
Normal file
62
arch/arm/kernel/head-inflate-data.c
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* XIP kernel .data segment decompressor
|
||||
*
|
||||
* Created by: Nicolas Pitre, August 2017
|
||||
* Copyright: (C) 2017 Linaro Limited
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/zutil.h>
|
||||
|
||||
/* for struct inflate_state */
|
||||
#include "../../../lib/zlib_inflate/inftrees.h"
|
||||
#include "../../../lib/zlib_inflate/inflate.h"
|
||||
#include "../../../lib/zlib_inflate/infutil.h"
|
||||
|
||||
extern char __data_loc[];
|
||||
extern char _edata_loc[];
|
||||
extern char _sdata[];
|
||||
|
||||
/*
|
||||
* This code is called very early during the boot process to decompress
|
||||
* the .data segment stored compressed in ROM. Therefore none of the global
|
||||
* variables are valid yet, hence no kernel services such as memory
|
||||
* allocation is available. Everything must be allocated on the stack and
|
||||
* we must avoid any global data access. We use a temporary stack located
|
||||
* in the .bss area. The linker script makes sure the .bss is big enough
|
||||
* to hold our stack frame plus some room for called functions.
|
||||
*
|
||||
* We mimic the code in lib/decompress_inflate.c to use the smallest work
|
||||
* area possible. And because everything is statically allocated on the
|
||||
* stack then there is no need to clean up before returning.
|
||||
*/
|
||||
|
||||
int __init __inflate_kernel_data(void)
|
||||
{
|
||||
struct z_stream_s stream, *strm = &stream;
|
||||
struct inflate_state state;
|
||||
char *in = __data_loc;
|
||||
int rc;
|
||||
|
||||
/* Check and skip gzip header (assume no filename) */
|
||||
if (in[0] != 0x1f || in[1] != 0x8b || in[2] != 0x08 || in[3] & ~3)
|
||||
return -1;
|
||||
in += 10;
|
||||
|
||||
strm->workspace = &state;
|
||||
strm->next_in = in;
|
||||
strm->avail_in = _edata_loc - __data_loc; /* upper bound */
|
||||
strm->next_out = _sdata;
|
||||
strm->avail_out = _edata_loc - __data_loc;
|
||||
zlib_inflateInit2(strm, -MAX_WBITS);
|
||||
WS(strm)->inflate_state.wsize = 0;
|
||||
WS(strm)->inflate_state.window = NULL;
|
||||
rc = zlib_inflate(strm, Z_FINISH);
|
||||
if (rc == Z_OK || rc == Z_STREAM_END)
|
||||
rc = strm->avail_out; /* should be 0 */
|
||||
return rc;
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
*/
|
||||
#include <linux/linkage.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/ptrace.h>
|
||||
@ -110,8 +111,8 @@ ENTRY(secondary_startup)
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
/* Use MPU region info supplied by __cpu_up */
|
||||
ldr r6, [r7] @ get secondary_data.mpu_szr
|
||||
bl __setup_mpu @ Initialize the MPU
|
||||
ldr r6, [r7] @ get secondary_data.mpu_rgn_info
|
||||
bl __secondary_setup_mpu @ Initialize the MPU
|
||||
#endif
|
||||
|
||||
badr lr, 1f @ return (PIC) address
|
||||
@ -175,19 +176,33 @@ ENDPROC(__after_proc_init)
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
|
||||
|
||||
#ifndef CONFIG_CPU_V7M
|
||||
/* Set which MPU region should be programmed */
|
||||
.macro set_region_nr tmp, rgnr
|
||||
.macro set_region_nr tmp, rgnr, unused
|
||||
mov \tmp, \rgnr @ Use static region numbers
|
||||
mcr p15, 0, \tmp, c6, c2, 0 @ Write RGNR
|
||||
.endm
|
||||
|
||||
/* Setup a single MPU region, either D or I side (D-side for unified) */
|
||||
.macro setup_region bar, acr, sr, side = MPU_DATA_SIDE
|
||||
.macro setup_region bar, acr, sr, side = MPU_DATA_SIDE, unused
|
||||
mcr p15, 0, \bar, c6, c1, (0 + \side) @ I/DRBAR
|
||||
mcr p15, 0, \acr, c6, c1, (4 + \side) @ I/DRACR
|
||||
mcr p15, 0, \sr, c6, c1, (2 + \side) @ I/DRSR
|
||||
.endm
|
||||
#else
|
||||
.macro set_region_nr tmp, rgnr, base
|
||||
mov \tmp, \rgnr
|
||||
str \tmp, [\base, #MPU_RNR]
|
||||
.endm
|
||||
|
||||
.macro setup_region bar, acr, sr, unused, base
|
||||
lsl \acr, \acr, #16
|
||||
orr \acr, \acr, \sr
|
||||
str \bar, [\base, #MPU_RBAR]
|
||||
str \acr, [\base, #MPU_RASR]
|
||||
.endm
|
||||
|
||||
#endif
|
||||
/*
|
||||
* Setup the MPU and initial MPU Regions. We create the following regions:
|
||||
* Region 0: Use this for probing the MPU details, so leave disabled.
|
||||
@ -201,64 +216,137 @@ ENDPROC(__after_proc_init)
|
||||
ENTRY(__setup_mpu)
|
||||
|
||||
/* Probe for v7 PMSA compliance */
|
||||
mrc p15, 0, r0, c0, c1, 4 @ Read ID_MMFR0
|
||||
M_CLASS(movw r12, #:lower16:BASEADDR_V7M_SCB)
|
||||
M_CLASS(movt r12, #:upper16:BASEADDR_V7M_SCB)
|
||||
|
||||
AR_CLASS(mrc p15, 0, r0, c0, c1, 4) @ Read ID_MMFR0
|
||||
M_CLASS(ldr r0, [r12, 0x50])
|
||||
and r0, r0, #(MMFR0_PMSA) @ PMSA field
|
||||
teq r0, #(MMFR0_PMSAv7) @ PMSA v7
|
||||
bne __error_p @ Fail: ARM_MPU on NOT v7 PMSA
|
||||
bxne lr
|
||||
|
||||
/* Determine whether the D/I-side memory map is unified. We set the
|
||||
* flags here and continue to use them for the rest of this function */
|
||||
mrc p15, 0, r0, c0, c0, 4 @ MPUIR
|
||||
AR_CLASS(mrc p15, 0, r0, c0, c0, 4) @ MPUIR
|
||||
M_CLASS(ldr r0, [r12, #MPU_TYPE])
|
||||
ands r5, r0, #MPUIR_DREGION_SZMASK @ 0 size d region => No MPU
|
||||
beq __error_p @ Fail: ARM_MPU and no MPU
|
||||
bxeq lr
|
||||
tst r0, #MPUIR_nU @ MPUIR_nU = 0 for unified
|
||||
|
||||
/* Setup second region first to free up r6 */
|
||||
set_region_nr r0, #MPU_RAM_REGION
|
||||
set_region_nr r0, #MPU_RAM_REGION, r12
|
||||
isb
|
||||
/* Full access from PL0, PL1, shared for CONFIG_SMP, cacheable */
|
||||
ldr r0, =PLAT_PHYS_OFFSET @ RAM starts at PHYS_OFFSET
|
||||
ldr r5,=(MPU_AP_PL1RW_PL0RW | MPU_RGN_NORMAL)
|
||||
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE @ PHYS_OFFSET, shared, enabled
|
||||
beq 1f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE @ PHYS_OFFSET, shared, enabled
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE, r12 @ PHYS_OFFSET, shared, enabled
|
||||
beq 1f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE, r12 @ PHYS_OFFSET, shared, enabled
|
||||
1: isb
|
||||
|
||||
/* First/background region */
|
||||
set_region_nr r0, #MPU_BG_REGION
|
||||
set_region_nr r0, #MPU_BG_REGION, r12
|
||||
isb
|
||||
/* Execute Never, strongly ordered, inaccessible to PL0, rw PL1 */
|
||||
mov r0, #0 @ BG region starts at 0x0
|
||||
ldr r5,=(MPU_ACR_XN | MPU_RGN_STRONGLY_ORDERED | MPU_AP_PL1RW_PL0NA)
|
||||
mov r6, #MPU_RSR_ALL_MEM @ 4GB region, enabled
|
||||
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE @ 0x0, BG region, enabled
|
||||
beq 2f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE @ 0x0, BG region, enabled
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE, r12 @ 0x0, BG region, enabled
|
||||
beq 2f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE r12 @ 0x0, BG region, enabled
|
||||
2: isb
|
||||
|
||||
/* Vectors region */
|
||||
set_region_nr r0, #MPU_VECTORS_REGION
|
||||
#ifdef CONFIG_XIP_KERNEL
|
||||
set_region_nr r0, #MPU_ROM_REGION, r12
|
||||
isb
|
||||
/* Shared, inaccessible to PL0, rw PL1 */
|
||||
mov r0, #CONFIG_VECTORS_BASE @ Cover from VECTORS_BASE
|
||||
ldr r5,=(MPU_AP_PL1RW_PL0NA | MPU_RGN_NORMAL)
|
||||
/* Writing N to bits 5:1 (RSR_SZ) --> region size 2^N+1 */
|
||||
mov r6, #(((2 * PAGE_SHIFT - 1) << MPU_RSR_SZ) | 1 << MPU_RSR_EN)
|
||||
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE @ VECTORS_BASE, PL0 NA, enabled
|
||||
beq 3f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE @ VECTORS_BASE, PL0 NA, enabled
|
||||
ldr r5,=(MPU_AP_PL1RO_PL0NA | MPU_RGN_NORMAL)
|
||||
|
||||
ldr r0, =CONFIG_XIP_PHYS_ADDR @ ROM start
|
||||
ldr r6, =(_exiprom) @ ROM end
|
||||
sub r6, r6, r0 @ Minimum size of region to map
|
||||
clz r6, r6 @ Region size must be 2^N...
|
||||
rsb r6, r6, #31 @ ...so round up region size
|
||||
lsl r6, r6, #MPU_RSR_SZ @ Put size in right field
|
||||
orr r6, r6, #(1 << MPU_RSR_EN) @ Set region enabled bit
|
||||
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE, r12 @ XIP_PHYS_ADDR, shared, enabled
|
||||
beq 3f @ Memory-map not unified
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE, r12 @ XIP_PHYS_ADDR, shared, enabled
|
||||
3: isb
|
||||
#endif
|
||||
|
||||
/* Enable the MPU */
|
||||
AR_CLASS(mrc p15, 0, r0, c1, c0, 0) @ Read SCTLR
|
||||
AR_CLASS(bic r0, r0, #CR_BR) @ Disable the 'default mem-map'
|
||||
AR_CLASS(orr r0, r0, #CR_M) @ Set SCTRL.M (MPU on)
|
||||
AR_CLASS(mcr p15, 0, r0, c1, c0, 0) @ Enable MPU
|
||||
|
||||
M_CLASS(ldr r0, [r12, #MPU_CTRL])
|
||||
M_CLASS(bic r0, #MPU_CTRL_PRIVDEFENA)
|
||||
M_CLASS(orr r0, #MPU_CTRL_ENABLE)
|
||||
M_CLASS(str r0, [r12, #MPU_CTRL])
|
||||
isb
|
||||
|
||||
ret lr
|
||||
ENDPROC(__setup_mpu)
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* r6: pointer at mpu_rgn_info
|
||||
*/
|
||||
|
||||
ENTRY(__secondary_setup_mpu)
|
||||
/* Probe for v7 PMSA compliance */
|
||||
mrc p15, 0, r0, c0, c1, 4 @ Read ID_MMFR0
|
||||
and r0, r0, #(MMFR0_PMSA) @ PMSA field
|
||||
teq r0, #(MMFR0_PMSAv7) @ PMSA v7
|
||||
bne __error_p
|
||||
|
||||
/* Determine whether the D/I-side memory map is unified. We set the
|
||||
* flags here and continue to use them for the rest of this function */
|
||||
mrc p15, 0, r0, c0, c0, 4 @ MPUIR
|
||||
ands r5, r0, #MPUIR_DREGION_SZMASK @ 0 size d region => No MPU
|
||||
beq __error_p
|
||||
|
||||
ldr r4, [r6, #MPU_RNG_INFO_USED]
|
||||
mov r5, #MPU_RNG_SIZE
|
||||
add r3, r6, #MPU_RNG_INFO_RNGS
|
||||
mla r3, r4, r5, r3
|
||||
|
||||
1:
|
||||
tst r0, #MPUIR_nU @ MPUIR_nU = 0 for unified
|
||||
sub r3, r3, #MPU_RNG_SIZE
|
||||
sub r4, r4, #1
|
||||
|
||||
set_region_nr r0, r4
|
||||
isb
|
||||
|
||||
ldr r0, [r3, #MPU_RGN_DRBAR]
|
||||
ldr r6, [r3, #MPU_RGN_DRSR]
|
||||
ldr r5, [r3, #MPU_RGN_DRACR]
|
||||
|
||||
setup_region r0, r5, r6, MPU_DATA_SIDE
|
||||
beq 2f
|
||||
setup_region r0, r5, r6, MPU_INSTR_SIDE
|
||||
2: isb
|
||||
|
||||
mrc p15, 0, r0, c0, c0, 4 @ Reevaluate the MPUIR
|
||||
cmp r4, #0
|
||||
bgt 1b
|
||||
|
||||
/* Enable the MPU */
|
||||
mrc p15, 0, r0, c1, c0, 0 @ Read SCTLR
|
||||
bic r0, r0, #CR_BR @ Disable the 'default mem-map'
|
||||
bic r0, r0, #CR_BR @ Disable the 'default mem-map'
|
||||
orr r0, r0, #CR_M @ Set SCTRL.M (MPU on)
|
||||
mcr p15, 0, r0, c1, c0, 0 @ Enable MPU
|
||||
isb
|
||||
|
||||
ret lr
|
||||
ENDPROC(__setup_mpu)
|
||||
#endif
|
||||
ENDPROC(__secondary_setup_mpu)
|
||||
|
||||
#endif /* CONFIG_SMP */
|
||||
#endif /* CONFIG_ARM_MPU */
|
||||
#include "head-common.S"
|
||||
|
@ -19,11 +19,12 @@
|
||||
#include <asm/elf.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/traps.h>
|
||||
#include <asm/ucontext.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <asm/vfp.h>
|
||||
|
||||
extern const unsigned long sigreturn_codes[7];
|
||||
#include "signal.h"
|
||||
|
||||
extern const unsigned long sigreturn_codes[17];
|
||||
|
||||
static unsigned long signal_return_offset;
|
||||
|
||||
@ -172,15 +173,6 @@ static int restore_vfp_context(char __user **auxp)
|
||||
/*
|
||||
* Do a signal return; undo the signal stack. These are aligned to 64-bit.
|
||||
*/
|
||||
struct sigframe {
|
||||
struct ucontext uc;
|
||||
unsigned long retcode[2];
|
||||
};
|
||||
|
||||
struct rt_sigframe {
|
||||
struct siginfo info;
|
||||
struct sigframe sig;
|
||||
};
|
||||
|
||||
static int restore_sigframe(struct pt_regs *regs, struct sigframe __user *sf)
|
||||
{
|
||||
@ -366,9 +358,20 @@ setup_return(struct pt_regs *regs, struct ksignal *ksig,
|
||||
unsigned long __user *rc, void __user *frame)
|
||||
{
|
||||
unsigned long handler = (unsigned long)ksig->ka.sa.sa_handler;
|
||||
unsigned long handler_fdpic_GOT = 0;
|
||||
unsigned long retcode;
|
||||
int thumb = 0;
|
||||
unsigned int idx, thumb = 0;
|
||||
unsigned long cpsr = regs->ARM_cpsr & ~(PSR_f | PSR_E_BIT);
|
||||
bool fdpic = IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) &&
|
||||
(current->personality & FDPIC_FUNCPTRS);
|
||||
|
||||
if (fdpic) {
|
||||
unsigned long __user *fdpic_func_desc =
|
||||
(unsigned long __user *)handler;
|
||||
if (__get_user(handler, &fdpic_func_desc[0]) ||
|
||||
__get_user(handler_fdpic_GOT, &fdpic_func_desc[1]))
|
||||
return 1;
|
||||
}
|
||||
|
||||
cpsr |= PSR_ENDSTATE;
|
||||
|
||||
@ -408,9 +411,26 @@ setup_return(struct pt_regs *regs, struct ksignal *ksig,
|
||||
|
||||
if (ksig->ka.sa.sa_flags & SA_RESTORER) {
|
||||
retcode = (unsigned long)ksig->ka.sa.sa_restorer;
|
||||
if (fdpic) {
|
||||
/*
|
||||
* We need code to load the function descriptor.
|
||||
* That code follows the standard sigreturn code
|
||||
* (6 words), and is made of 3 + 2 words for each
|
||||
* variant. The 4th copied word is the actual FD
|
||||
* address that the assembly code expects.
|
||||
*/
|
||||
idx = 6 + thumb * 3;
|
||||
if (ksig->ka.sa.sa_flags & SA_SIGINFO)
|
||||
idx += 5;
|
||||
if (__put_user(sigreturn_codes[idx], rc ) ||
|
||||
__put_user(sigreturn_codes[idx+1], rc+1) ||
|
||||
__put_user(sigreturn_codes[idx+2], rc+2) ||
|
||||
__put_user(retcode, rc+3))
|
||||
return 1;
|
||||
goto rc_finish;
|
||||
}
|
||||
} else {
|
||||
unsigned int idx = thumb << 1;
|
||||
|
||||
idx = thumb << 1;
|
||||
if (ksig->ka.sa.sa_flags & SA_SIGINFO)
|
||||
idx += 3;
|
||||
|
||||
@ -422,6 +442,7 @@ setup_return(struct pt_regs *regs, struct ksignal *ksig,
|
||||
__put_user(sigreturn_codes[idx+1], rc+1))
|
||||
return 1;
|
||||
|
||||
rc_finish:
|
||||
#ifdef CONFIG_MMU
|
||||
if (cpsr & MODE32_BIT) {
|
||||
struct mm_struct *mm = current->mm;
|
||||
@ -441,7 +462,7 @@ setup_return(struct pt_regs *regs, struct ksignal *ksig,
|
||||
* the return code written onto the stack.
|
||||
*/
|
||||
flush_icache_range((unsigned long)rc,
|
||||
(unsigned long)(rc + 2));
|
||||
(unsigned long)(rc + 3));
|
||||
|
||||
retcode = ((unsigned long)rc) + thumb;
|
||||
}
|
||||
@ -451,6 +472,8 @@ setup_return(struct pt_regs *regs, struct ksignal *ksig,
|
||||
regs->ARM_sp = (unsigned long)frame;
|
||||
regs->ARM_lr = retcode;
|
||||
regs->ARM_pc = handler;
|
||||
if (fdpic)
|
||||
regs->ARM_r9 = handler_fdpic_GOT;
|
||||
regs->ARM_cpsr = cpsr;
|
||||
|
||||
return 0;
|
||||
|
11
arch/arm/kernel/signal.h
Normal file
11
arch/arm/kernel/signal.h
Normal file
@ -0,0 +1,11 @@
|
||||
#include <asm/ucontext.h>
|
||||
|
||||
struct sigframe {
|
||||
struct ucontext uc;
|
||||
unsigned long retcode[4];
|
||||
};
|
||||
|
||||
struct rt_sigframe {
|
||||
struct siginfo info;
|
||||
struct sigframe sig;
|
||||
};
|
@ -14,6 +14,8 @@
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <asm/assembler.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
/*
|
||||
@ -51,6 +53,17 @@ ARM_OK( .arm )
|
||||
.thumb
|
||||
.endm
|
||||
|
||||
.macro arm_fdpic_slot n
|
||||
.org sigreturn_codes + 24 + 20 * (\n)
|
||||
ARM_OK( .arm )
|
||||
.endm
|
||||
|
||||
.macro thumb_fdpic_slot n
|
||||
.org sigreturn_codes + 24 + 20 * (\n) + 12
|
||||
.thumb
|
||||
.endm
|
||||
|
||||
|
||||
#if __LINUX_ARM_ARCH__ <= 4
|
||||
/*
|
||||
* Note we manually set minimally required arch that supports
|
||||
@ -90,13 +103,46 @@ ARM_OK( swi #(__NR_rt_sigreturn)|(__NR_OABI_SYSCALL_BASE) )
|
||||
movs r7, #(__NR_rt_sigreturn - __NR_SYSCALL_BASE)
|
||||
swi #0
|
||||
|
||||
/* ARM sigreturn restorer FDPIC bounce code snippet */
|
||||
arm_fdpic_slot 0
|
||||
ARM_OK( ldr r3, [sp, #SIGFRAME_RC3_OFFSET] )
|
||||
ARM_OK( ldmia r3, {r3, r9} )
|
||||
#ifdef CONFIG_ARM_THUMB
|
||||
ARM_OK( bx r3 )
|
||||
#else
|
||||
ARM_OK( ret r3 )
|
||||
#endif
|
||||
|
||||
/* Thumb sigreturn restorer FDPIC bounce code snippet */
|
||||
thumb_fdpic_slot 0
|
||||
ldr r3, [sp, #SIGFRAME_RC3_OFFSET]
|
||||
ldmia r3, {r2, r3}
|
||||
mov r9, r3
|
||||
bx r2
|
||||
|
||||
/* ARM sigreturn_rt restorer FDPIC bounce code snippet */
|
||||
arm_fdpic_slot 1
|
||||
ARM_OK( ldr r3, [sp, #RT_SIGFRAME_RC3_OFFSET] )
|
||||
ARM_OK( ldmia r3, {r3, r9} )
|
||||
#ifdef CONFIG_ARM_THUMB
|
||||
ARM_OK( bx r3 )
|
||||
#else
|
||||
ARM_OK( ret r3 )
|
||||
#endif
|
||||
|
||||
/* Thumb sigreturn_rt restorer FDPIC bounce code snippet */
|
||||
thumb_fdpic_slot 1
|
||||
ldr r3, [sp, #RT_SIGFRAME_RC3_OFFSET]
|
||||
ldmia r3, {r2, r3}
|
||||
mov r9, r3
|
||||
bx r2
|
||||
|
||||
/*
|
||||
* Note on addtional space: setup_return in signal.c
|
||||
* algorithm uses two words copy regardless whether
|
||||
* it is thumb case or not, so we need additional
|
||||
* word after real last entry.
|
||||
* Note on additional space: setup_return in signal.c
|
||||
* always copies the same number of words regardless whether
|
||||
* it is thumb case or not, so we need one additional padding
|
||||
* word after the last entry.
|
||||
*/
|
||||
arm_slot 2
|
||||
.space 4
|
||||
|
||||
.size sigreturn_codes, . - sigreturn_codes
|
||||
|
@ -114,7 +114,7 @@ int __cpu_up(unsigned int cpu, struct task_struct *idle)
|
||||
*/
|
||||
secondary_data.stack = task_stack_page(idle) + THREAD_START_SP;
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
secondary_data.mpu_rgn_szr = mpu_rgn_info.rgns[MPU_RAM_REGION].drsr;
|
||||
secondary_data.mpu_rgn_info = &mpu_rgn_info;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
|
@ -655,6 +655,9 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
|
||||
set_tls(regs->ARM_r0);
|
||||
return 0;
|
||||
|
||||
case NR(get_tls):
|
||||
return current_thread_info()->tp_value[0];
|
||||
|
||||
default:
|
||||
/* Calls 9f00xx..9f07ff are defined to return -ENOSYS
|
||||
if not implemented, rather than raising SIGILL. This
|
||||
|
@ -6,6 +6,8 @@
|
||||
/* No __ro_after_init data in the .rodata section - which will always be ro */
|
||||
#define RO_AFTER_INIT_DATA
|
||||
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include <asm-generic/vmlinux.lds.h>
|
||||
#include <asm/cache.h>
|
||||
#include <asm/thread_info.h>
|
||||
@ -77,9 +79,7 @@ SECTIONS
|
||||
*(.text.fixup)
|
||||
*(__ex_table)
|
||||
#endif
|
||||
#ifndef CONFIG_SMP_ON_UP
|
||||
*(.alt.smp.init)
|
||||
#endif
|
||||
*(.discard)
|
||||
*(.discard.*)
|
||||
}
|
||||
@ -181,19 +181,7 @@ SECTIONS
|
||||
*(.taglist.init)
|
||||
__tagtable_end = .;
|
||||
}
|
||||
#ifdef CONFIG_SMP_ON_UP
|
||||
.init.smpalt : {
|
||||
__smpalt_begin = .;
|
||||
*(.alt.smp.init)
|
||||
__smpalt_end = .;
|
||||
}
|
||||
#endif
|
||||
.init.pv_table : {
|
||||
__pv_table_begin = .;
|
||||
*(.pv_table)
|
||||
__pv_table_end = .;
|
||||
}
|
||||
.init.data : {
|
||||
.init.rodata : {
|
||||
INIT_SETUP(16)
|
||||
INIT_CALLS
|
||||
CON_INITCALL
|
||||
@ -201,48 +189,49 @@ SECTIONS
|
||||
INIT_RAM_FS
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
. = ALIGN(SZ_128K);
|
||||
#endif
|
||||
_exiprom = .; /* End of XIP ROM area */
|
||||
|
||||
/*
|
||||
* From this point, stuff is considered writable and will be copied to RAM
|
||||
*/
|
||||
__data_loc = ALIGN(4); /* location in file */
|
||||
. = PAGE_OFFSET + TEXT_OFFSET; /* location in memory */
|
||||
#undef LOAD_OFFSET
|
||||
#define LOAD_OFFSET (PAGE_OFFSET + TEXT_OFFSET - __data_loc)
|
||||
|
||||
. = ALIGN(THREAD_SIZE);
|
||||
_sdata = .;
|
||||
RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE)
|
||||
.data.ro_after_init : AT(ADDR(.data.ro_after_init) - LOAD_OFFSET) {
|
||||
*(.data..ro_after_init)
|
||||
}
|
||||
_edata = .;
|
||||
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
__init_begin = .;
|
||||
.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {
|
||||
INIT_DATA
|
||||
}
|
||||
.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
|
||||
ARM_EXIT_KEEP(EXIT_DATA)
|
||||
}
|
||||
#ifdef CONFIG_SMP
|
||||
PERCPU_SECTION(L1_CACHE_BYTES)
|
||||
#endif
|
||||
|
||||
_exiprom = .; /* End of XIP ROM area */
|
||||
__data_loc = ALIGN(4); /* location in binary */
|
||||
. = PAGE_OFFSET + TEXT_OFFSET;
|
||||
/*
|
||||
* End of copied data. We need a dummy section to get its LMA.
|
||||
* Also located before final ALIGN() as trailing padding is not stored
|
||||
* in the resulting binary file and useless to copy.
|
||||
*/
|
||||
.data.endmark : AT(ADDR(.data.endmark) - LOAD_OFFSET) { }
|
||||
_edata_loc = LOADADDR(.data.endmark);
|
||||
|
||||
.data : AT(__data_loc) {
|
||||
_data = .; /* address in memory */
|
||||
_sdata = .;
|
||||
|
||||
/*
|
||||
* first, the init task union, aligned
|
||||
* to an 8192 byte boundary.
|
||||
*/
|
||||
INIT_TASK_DATA(THREAD_SIZE)
|
||||
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
__init_begin = .;
|
||||
INIT_DATA
|
||||
ARM_EXIT_KEEP(EXIT_DATA)
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
__init_end = .;
|
||||
|
||||
*(.data..ro_after_init)
|
||||
|
||||
NOSAVE_DATA
|
||||
CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
|
||||
READ_MOSTLY_DATA(L1_CACHE_BYTES)
|
||||
|
||||
/*
|
||||
* and the usual data section
|
||||
*/
|
||||
DATA_DATA
|
||||
CONSTRUCTORS
|
||||
|
||||
_edata = .;
|
||||
}
|
||||
_edata_loc = __data_loc + SIZEOF(.data);
|
||||
|
||||
BUG_TABLE
|
||||
. = ALIGN(PAGE_SIZE);
|
||||
__init_end = .;
|
||||
|
||||
#ifdef CONFIG_HAVE_TCM
|
||||
/*
|
||||
@ -301,7 +290,7 @@ SECTIONS
|
||||
}
|
||||
#endif
|
||||
|
||||
BSS_SECTION(0, 0, 0)
|
||||
BSS_SECTION(0, 0, 8)
|
||||
_end = .;
|
||||
|
||||
STABS_DEBUG
|
||||
@ -322,3 +311,29 @@ ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
|
||||
*/
|
||||
ASSERT(__hyp_idmap_text_end - (__hyp_idmap_text_start & PAGE_MASK) <= PAGE_SIZE,
|
||||
"HYP init code too big or misaligned")
|
||||
|
||||
#ifdef CONFIG_XIP_DEFLATED_DATA
|
||||
/*
|
||||
* The .bss is used as a stack area for __inflate_kernel_data() whose stack
|
||||
* frame is 9568 bytes. Make sure it has extra room left.
|
||||
*/
|
||||
ASSERT((_end - __bss_start) >= 12288, ".bss too small for CONFIG_XIP_DEFLATED_DATA")
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
/*
|
||||
* Due to PMSAv7 restriction on base address and size we have to
|
||||
* enforce minimal alignment restrictions. It was seen that weaker
|
||||
* alignment restriction on _xiprom will likely force XIP address
|
||||
* space spawns multiple MPU regions thus it is likely we run in
|
||||
* situation when we are reprogramming MPU region we run on with
|
||||
* something which doesn't cover reprogramming code itself, so as soon
|
||||
* as we update MPU settings we'd immediately try to execute straight
|
||||
* from background region which is XN.
|
||||
* It seem that alignment in 1M should suit most users.
|
||||
* _exiprom is aligned as 1/8 of 1M so can be covered by subregion
|
||||
* disable
|
||||
*/
|
||||
ASSERT(!(_xiprom & (SZ_1M - 1)), "XIP start address may cause MPU programming issues")
|
||||
ASSERT(!(_exiprom & (SZ_128K - 1)), "XIP end address may cause MPU programming issues")
|
||||
#endif
|
||||
|
@ -214,14 +214,9 @@ SECTIONS
|
||||
*(.pv_table)
|
||||
__pv_table_end = .;
|
||||
}
|
||||
.init.data : {
|
||||
INIT_DATA
|
||||
INIT_SETUP(16)
|
||||
INIT_CALLS
|
||||
CON_INITCALL
|
||||
SECURITY_INITCALL
|
||||
INIT_RAM_FS
|
||||
}
|
||||
|
||||
INIT_DATA_SECTION(16)
|
||||
|
||||
.exit.data : {
|
||||
ARM_EXIT_KEEP(EXIT_DATA)
|
||||
}
|
||||
@ -236,33 +231,10 @@ SECTIONS
|
||||
. = ALIGN(THREAD_SIZE);
|
||||
#endif
|
||||
__init_end = .;
|
||||
__data_loc = .;
|
||||
|
||||
.data : AT(__data_loc) {
|
||||
_data = .; /* address in memory */
|
||||
_sdata = .;
|
||||
|
||||
/*
|
||||
* first, the init task union, aligned
|
||||
* to an 8192 byte boundary.
|
||||
*/
|
||||
INIT_TASK_DATA(THREAD_SIZE)
|
||||
|
||||
NOSAVE_DATA
|
||||
CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
|
||||
READ_MOSTLY_DATA(L1_CACHE_BYTES)
|
||||
|
||||
/*
|
||||
* and the usual data section
|
||||
*/
|
||||
DATA_DATA
|
||||
CONSTRUCTORS
|
||||
|
||||
_edata = .;
|
||||
}
|
||||
_edata_loc = __data_loc + SIZEOF(.data);
|
||||
|
||||
BUG_TABLE
|
||||
_sdata = .;
|
||||
RW_DATA_SECTION(L1_CACHE_BYTES, PAGE_SIZE, THREAD_SIZE)
|
||||
_edata = .;
|
||||
|
||||
#ifdef CONFIG_HAVE_TCM
|
||||
/*
|
||||
|
@ -9,6 +9,7 @@ obj-$(CONFIG_MMU) += fault-armv.o flush.o idmap.o ioremap.o \
|
||||
|
||||
ifneq ($(CONFIG_MMU),y)
|
||||
obj-y += nommu.o
|
||||
obj-$(CONFIG_ARM_MPU) += pmsa-v7.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_ARM_PTDUMP) += dump.o
|
||||
|
@ -27,259 +27,7 @@ unsigned long vectors_base;
|
||||
|
||||
#ifdef CONFIG_ARM_MPU
|
||||
struct mpu_rgn_info mpu_rgn_info;
|
||||
|
||||
/* Region number */
|
||||
static void rgnr_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c2, 0" : : "r" (v));
|
||||
}
|
||||
|
||||
/* Data-side / unified region attributes */
|
||||
|
||||
/* Region access control register */
|
||||
static void dracr_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 4" : : "r" (v));
|
||||
}
|
||||
|
||||
/* Region size register */
|
||||
static void drsr_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 2" : : "r" (v));
|
||||
}
|
||||
|
||||
/* Region base address register */
|
||||
static void drbar_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 0" : : "r" (v));
|
||||
}
|
||||
|
||||
static u32 drbar_read(void)
|
||||
{
|
||||
u32 v;
|
||||
asm("mrc p15, 0, %0, c6, c1, 0" : "=r" (v));
|
||||
return v;
|
||||
}
|
||||
/* Optional instruction-side region attributes */
|
||||
|
||||
/* I-side Region access control register */
|
||||
static void iracr_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 5" : : "r" (v));
|
||||
}
|
||||
|
||||
/* I-side Region size register */
|
||||
static void irsr_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 3" : : "r" (v));
|
||||
}
|
||||
|
||||
/* I-side Region base address register */
|
||||
static void irbar_write(u32 v)
|
||||
{
|
||||
asm("mcr p15, 0, %0, c6, c1, 1" : : "r" (v));
|
||||
}
|
||||
|
||||
static unsigned long irbar_read(void)
|
||||
{
|
||||
unsigned long v;
|
||||
asm("mrc p15, 0, %0, c6, c1, 1" : "=r" (v));
|
||||
return v;
|
||||
}
|
||||
|
||||
/* MPU initialisation functions */
|
||||
void __init adjust_lowmem_bounds_mpu(void)
|
||||
{
|
||||
phys_addr_t phys_offset = PHYS_OFFSET;
|
||||
phys_addr_t aligned_region_size, specified_mem_size, rounded_mem_size;
|
||||
struct memblock_region *reg;
|
||||
bool first = true;
|
||||
phys_addr_t mem_start;
|
||||
phys_addr_t mem_end;
|
||||
|
||||
for_each_memblock(memory, reg) {
|
||||
if (first) {
|
||||
/*
|
||||
* Initially only use memory continuous from
|
||||
* PHYS_OFFSET */
|
||||
if (reg->base != phys_offset)
|
||||
panic("First memory bank must be contiguous from PHYS_OFFSET");
|
||||
|
||||
mem_start = reg->base;
|
||||
mem_end = reg->base + reg->size;
|
||||
specified_mem_size = reg->size;
|
||||
first = false;
|
||||
} else {
|
||||
/*
|
||||
* memblock auto merges contiguous blocks, remove
|
||||
* all blocks afterwards in one go (we can't remove
|
||||
* blocks separately while iterating)
|
||||
*/
|
||||
pr_notice("Ignoring RAM after %pa, memory at %pa ignored\n",
|
||||
&mem_end, ®->base);
|
||||
memblock_remove(reg->base, 0 - reg->base);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* MPU has curious alignment requirements: Size must be power of 2, and
|
||||
* region start must be aligned to the region size
|
||||
*/
|
||||
if (phys_offset != 0)
|
||||
pr_info("PHYS_OFFSET != 0 => MPU Region size constrained by alignment requirements\n");
|
||||
|
||||
/*
|
||||
* Maximum aligned region might overflow phys_addr_t if phys_offset is
|
||||
* 0. Hence we keep everything below 4G until we take the smaller of
|
||||
* the aligned_region_size and rounded_mem_size, one of which is
|
||||
* guaranteed to be smaller than the maximum physical address.
|
||||
*/
|
||||
aligned_region_size = (phys_offset - 1) ^ (phys_offset);
|
||||
/* Find the max power-of-two sized region that fits inside our bank */
|
||||
rounded_mem_size = (1 << __fls(specified_mem_size)) - 1;
|
||||
|
||||
/* The actual region size is the smaller of the two */
|
||||
aligned_region_size = aligned_region_size < rounded_mem_size
|
||||
? aligned_region_size + 1
|
||||
: rounded_mem_size + 1;
|
||||
|
||||
if (aligned_region_size != specified_mem_size) {
|
||||
pr_warn("Truncating memory from %pa to %pa (MPU region constraints)",
|
||||
&specified_mem_size, &aligned_region_size);
|
||||
memblock_remove(mem_start + aligned_region_size,
|
||||
specified_mem_size - aligned_region_size);
|
||||
|
||||
mem_end = mem_start + aligned_region_size;
|
||||
}
|
||||
|
||||
pr_debug("MPU Region from %pa size %pa (end %pa))\n",
|
||||
&phys_offset, &aligned_region_size, &mem_end);
|
||||
|
||||
}
|
||||
|
||||
static int mpu_present(void)
|
||||
{
|
||||
return ((read_cpuid_ext(CPUID_EXT_MMFR0) & MMFR0_PMSA) == MMFR0_PMSAv7);
|
||||
}
|
||||
|
||||
static int mpu_max_regions(void)
|
||||
{
|
||||
/*
|
||||
* We don't support a different number of I/D side regions so if we
|
||||
* have separate instruction and data memory maps then return
|
||||
* whichever side has a smaller number of supported regions.
|
||||
*/
|
||||
u32 dregions, iregions, mpuir;
|
||||
mpuir = read_cpuid(CPUID_MPUIR);
|
||||
|
||||
dregions = iregions = (mpuir & MPUIR_DREGION_SZMASK) >> MPUIR_DREGION;
|
||||
|
||||
/* Check for separate d-side and i-side memory maps */
|
||||
if (mpuir & MPUIR_nU)
|
||||
iregions = (mpuir & MPUIR_IREGION_SZMASK) >> MPUIR_IREGION;
|
||||
|
||||
/* Use the smallest of the two maxima */
|
||||
return min(dregions, iregions);
|
||||
}
|
||||
|
||||
static int mpu_iside_independent(void)
|
||||
{
|
||||
/* MPUIR.nU specifies whether there is *not* a unified memory map */
|
||||
return read_cpuid(CPUID_MPUIR) & MPUIR_nU;
|
||||
}
|
||||
|
||||
static int mpu_min_region_order(void)
|
||||
{
|
||||
u32 drbar_result, irbar_result;
|
||||
/* We've kept a region free for this probing */
|
||||
rgnr_write(MPU_PROBE_REGION);
|
||||
isb();
|
||||
/*
|
||||
* As per ARM ARM, write 0xFFFFFFFC to DRBAR to find the minimum
|
||||
* region order
|
||||
*/
|
||||
drbar_write(0xFFFFFFFC);
|
||||
drbar_result = irbar_result = drbar_read();
|
||||
drbar_write(0x0);
|
||||
/* If the MPU is non-unified, we use the larger of the two minima*/
|
||||
if (mpu_iside_independent()) {
|
||||
irbar_write(0xFFFFFFFC);
|
||||
irbar_result = irbar_read();
|
||||
irbar_write(0x0);
|
||||
}
|
||||
isb(); /* Ensure that MPU region operations have completed */
|
||||
/* Return whichever result is larger */
|
||||
return __ffs(max(drbar_result, irbar_result));
|
||||
}
|
||||
|
||||
static int mpu_setup_region(unsigned int number, phys_addr_t start,
|
||||
unsigned int size_order, unsigned int properties)
|
||||
{
|
||||
u32 size_data;
|
||||
|
||||
/* We kept a region free for probing resolution of MPU regions*/
|
||||
if (number > mpu_max_regions() || number == MPU_PROBE_REGION)
|
||||
return -ENOENT;
|
||||
|
||||
if (size_order > 32)
|
||||
return -ENOMEM;
|
||||
|
||||
if (size_order < mpu_min_region_order())
|
||||
return -ENOMEM;
|
||||
|
||||
/* Writing N to bits 5:1 (RSR_SZ) specifies region size 2^N+1 */
|
||||
size_data = ((size_order - 1) << MPU_RSR_SZ) | 1 << MPU_RSR_EN;
|
||||
|
||||
dsb(); /* Ensure all previous data accesses occur with old mappings */
|
||||
rgnr_write(number);
|
||||
isb();
|
||||
drbar_write(start);
|
||||
dracr_write(properties);
|
||||
isb(); /* Propagate properties before enabling region */
|
||||
drsr_write(size_data);
|
||||
|
||||
/* Check for independent I-side registers */
|
||||
if (mpu_iside_independent()) {
|
||||
irbar_write(start);
|
||||
iracr_write(properties);
|
||||
isb();
|
||||
irsr_write(size_data);
|
||||
}
|
||||
isb();
|
||||
|
||||
/* Store region info (we treat i/d side the same, so only store d) */
|
||||
mpu_rgn_info.rgns[number].dracr = properties;
|
||||
mpu_rgn_info.rgns[number].drbar = start;
|
||||
mpu_rgn_info.rgns[number].drsr = size_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up default MPU regions, doing nothing if there is no MPU
|
||||
*/
|
||||
void __init mpu_setup(void)
|
||||
{
|
||||
int region_err;
|
||||
if (!mpu_present())
|
||||
return;
|
||||
|
||||
region_err = mpu_setup_region(MPU_RAM_REGION, PHYS_OFFSET,
|
||||
ilog2(memblock.memory.regions[0].size),
|
||||
MPU_AP_PL1RW_PL0RW | MPU_RGN_NORMAL);
|
||||
if (region_err) {
|
||||
panic("MPU region initialization failure! %d", region_err);
|
||||
} else {
|
||||
pr_info("Using ARMv7 PMSA Compliant MPU. "
|
||||
"Region independence: %s, Max regions: %d\n",
|
||||
mpu_iside_independent() ? "Yes" : "No",
|
||||
mpu_max_regions());
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void adjust_lowmem_bounds_mpu(void) {}
|
||||
static void __init mpu_setup(void) {}
|
||||
#endif /* CONFIG_ARM_MPU */
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_CPU_CP15
|
||||
#ifdef CONFIG_CPU_HIGH_VECTOR
|
||||
|
484
arch/arm/mm/pmsa-v7.c
Normal file
484
arch/arm/mm/pmsa-v7.c
Normal file
@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Based on linux/arch/arm/mm/nommu.c
|
||||
*
|
||||
* ARM PMSAv7 supporting functions.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/memblock.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/cp15.h>
|
||||
#include <asm/cputype.h>
|
||||
#include <asm/mpu.h>
|
||||
#include <asm/sections.h>
|
||||
|
||||
#include "mm.h"
|
||||
|
||||
struct region {
|
||||
phys_addr_t base;
|
||||
phys_addr_t size;
|
||||
unsigned long subreg;
|
||||
};
|
||||
|
||||
static struct region __initdata mem[MPU_MAX_REGIONS];
|
||||
#ifdef CONFIG_XIP_KERNEL
|
||||
static struct region __initdata xip[MPU_MAX_REGIONS];
|
||||
#endif
|
||||
|
||||
static unsigned int __initdata mpu_min_region_order;
|
||||
static unsigned int __initdata mpu_max_regions;
|
||||
|
||||
static int __init __mpu_min_region_order(void);
|
||||
static int __init __mpu_max_regions(void);
|
||||
|
||||
#ifndef CONFIG_CPU_V7M
|
||||
|
||||
#define DRBAR __ACCESS_CP15(c6, 0, c1, 0)
|
||||
#define IRBAR __ACCESS_CP15(c6, 0, c1, 1)
|
||||
#define DRSR __ACCESS_CP15(c6, 0, c1, 2)
|
||||
#define IRSR __ACCESS_CP15(c6, 0, c1, 3)
|
||||
#define DRACR __ACCESS_CP15(c6, 0, c1, 4)
|
||||
#define IRACR __ACCESS_CP15(c6, 0, c1, 5)
|
||||
#define RNGNR __ACCESS_CP15(c6, 0, c2, 0)
|
||||
|
||||
/* Region number */
|
||||
static inline void rgnr_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, RNGNR);
|
||||
}
|
||||
|
||||
/* Data-side / unified region attributes */
|
||||
|
||||
/* Region access control register */
|
||||
static inline void dracr_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, DRACR);
|
||||
}
|
||||
|
||||
/* Region size register */
|
||||
static inline void drsr_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, DRSR);
|
||||
}
|
||||
|
||||
/* Region base address register */
|
||||
static inline void drbar_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, DRBAR);
|
||||
}
|
||||
|
||||
static inline u32 drbar_read(void)
|
||||
{
|
||||
return read_sysreg(DRBAR);
|
||||
}
|
||||
/* Optional instruction-side region attributes */
|
||||
|
||||
/* I-side Region access control register */
|
||||
static inline void iracr_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, IRACR);
|
||||
}
|
||||
|
||||
/* I-side Region size register */
|
||||
static inline void irsr_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, IRSR);
|
||||
}
|
||||
|
||||
/* I-side Region base address register */
|
||||
static inline void irbar_write(u32 v)
|
||||
{
|
||||
write_sysreg(v, IRBAR);
|
||||
}
|
||||
|
||||
static inline u32 irbar_read(void)
|
||||
{
|
||||
return read_sysreg(IRBAR);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void rgnr_write(u32 v)
|
||||
{
|
||||
writel_relaxed(v, BASEADDR_V7M_SCB + MPU_RNR);
|
||||
}
|
||||
|
||||
/* Data-side / unified region attributes */
|
||||
|
||||
/* Region access control register */
|
||||
static inline void dracr_write(u32 v)
|
||||
{
|
||||
u32 rsr = readl_relaxed(BASEADDR_V7M_SCB + MPU_RASR) & GENMASK(15, 0);
|
||||
|
||||
writel_relaxed((v << 16) | rsr, BASEADDR_V7M_SCB + MPU_RASR);
|
||||
}
|
||||
|
||||
/* Region size register */
|
||||
static inline void drsr_write(u32 v)
|
||||
{
|
||||
u32 racr = readl_relaxed(BASEADDR_V7M_SCB + MPU_RASR) & GENMASK(31, 16);
|
||||
|
||||
writel_relaxed(v | racr, BASEADDR_V7M_SCB + MPU_RASR);
|
||||
}
|
||||
|
||||
/* Region base address register */
|
||||
static inline void drbar_write(u32 v)
|
||||
{
|
||||
writel_relaxed(v, BASEADDR_V7M_SCB + MPU_RBAR);
|
||||
}
|
||||
|
||||
static inline u32 drbar_read(void)
|
||||
{
|
||||
return readl_relaxed(BASEADDR_V7M_SCB + MPU_RBAR);
|
||||
}
|
||||
|
||||
/* ARMv7-M only supports a unified MPU, so I-side operations are nop */
|
||||
|
||||
static inline void iracr_write(u32 v) {}
|
||||
static inline void irsr_write(u32 v) {}
|
||||
static inline void irbar_write(u32 v) {}
|
||||
static inline unsigned long irbar_read(void) {return 0;}
|
||||
|
||||
#endif
|
||||
|
||||
static int __init mpu_present(void)
|
||||
{
|
||||
return ((read_cpuid_ext(CPUID_EXT_MMFR0) & MMFR0_PMSA) == MMFR0_PMSAv7);
|
||||
}
|
||||
|
||||
static bool __init try_split_region(phys_addr_t base, phys_addr_t size, struct region *region)
|
||||
{
|
||||
unsigned long subreg, bslots, sslots;
|
||||
phys_addr_t abase = base & ~(size - 1);
|
||||
phys_addr_t asize = base + size - abase;
|
||||
phys_addr_t p2size = 1 << __fls(asize);
|
||||
phys_addr_t bdiff, sdiff;
|
||||
|
||||
if (p2size != asize)
|
||||
p2size *= 2;
|
||||
|
||||
bdiff = base - abase;
|
||||
sdiff = p2size - asize;
|
||||
subreg = p2size / MPU_NR_SUBREGS;
|
||||
|
||||
if ((bdiff % subreg) || (sdiff % subreg))
|
||||
return false;
|
||||
|
||||
bslots = bdiff / subreg;
|
||||
sslots = sdiff / subreg;
|
||||
|
||||
if (bslots || sslots) {
|
||||
int i;
|
||||
|
||||
if (subreg < MPU_MIN_SUBREG_SIZE)
|
||||
return false;
|
||||
|
||||
if (bslots + sslots > MPU_NR_SUBREGS)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < bslots; i++)
|
||||
_set_bit(i, ®ion->subreg);
|
||||
|
||||
for (i = 1; i <= sslots; i++)
|
||||
_set_bit(MPU_NR_SUBREGS - i, ®ion->subreg);
|
||||
}
|
||||
|
||||
region->base = abase;
|
||||
region->size = p2size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int __init allocate_region(phys_addr_t base, phys_addr_t size,
|
||||
unsigned int limit, struct region *regions)
|
||||
{
|
||||
int count = 0;
|
||||
phys_addr_t diff = size;
|
||||
int attempts = MPU_MAX_REGIONS;
|
||||
|
||||
while (diff) {
|
||||
/* Try cover region as is (maybe with help of subregions) */
|
||||
if (try_split_region(base, size, ®ions[count])) {
|
||||
count++;
|
||||
base += size;
|
||||
diff -= size;
|
||||
size = diff;
|
||||
} else {
|
||||
/*
|
||||
* Maximum aligned region might overflow phys_addr_t
|
||||
* if "base" is 0. Hence we keep everything below 4G
|
||||
* until we take the smaller of the aligned region
|
||||
* size ("asize") and rounded region size ("p2size"),
|
||||
* one of which is guaranteed to be smaller than the
|
||||
* maximum physical address.
|
||||
*/
|
||||
phys_addr_t asize = (base - 1) ^ base;
|
||||
phys_addr_t p2size = (1 << __fls(diff)) - 1;
|
||||
|
||||
size = asize < p2size ? asize + 1 : p2size + 1;
|
||||
}
|
||||
|
||||
if (count > limit)
|
||||
break;
|
||||
|
||||
if (!attempts)
|
||||
break;
|
||||
|
||||
attempts--;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* MPU initialisation functions */
|
||||
void __init adjust_lowmem_bounds_mpu(void)
|
||||
{
|
||||
phys_addr_t specified_mem_size = 0, total_mem_size = 0;
|
||||
struct memblock_region *reg;
|
||||
bool first = true;
|
||||
phys_addr_t mem_start;
|
||||
phys_addr_t mem_end;
|
||||
unsigned int mem_max_regions;
|
||||
int num, i;
|
||||
|
||||
if (!mpu_present())
|
||||
return;
|
||||
|
||||
/* Free-up MPU_PROBE_REGION */
|
||||
mpu_min_region_order = __mpu_min_region_order();
|
||||
|
||||
/* How many regions are supported */
|
||||
mpu_max_regions = __mpu_max_regions();
|
||||
|
||||
mem_max_regions = min((unsigned int)MPU_MAX_REGIONS, mpu_max_regions);
|
||||
|
||||
/* We need to keep one slot for background region */
|
||||
mem_max_regions--;
|
||||
|
||||
#ifndef CONFIG_CPU_V7M
|
||||
/* ... and one for vectors */
|
||||
mem_max_regions--;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_XIP_KERNEL
|
||||
/* plus some regions to cover XIP ROM */
|
||||
num = allocate_region(CONFIG_XIP_PHYS_ADDR, __pa(_exiprom) - CONFIG_XIP_PHYS_ADDR,
|
||||
mem_max_regions, xip);
|
||||
|
||||
mem_max_regions -= num;
|
||||
#endif
|
||||
|
||||
for_each_memblock(memory, reg) {
|
||||
if (first) {
|
||||
phys_addr_t phys_offset = PHYS_OFFSET;
|
||||
|
||||
/*
|
||||
* Initially only use memory continuous from
|
||||
* PHYS_OFFSET */
|
||||
if (reg->base != phys_offset)
|
||||
panic("First memory bank must be contiguous from PHYS_OFFSET");
|
||||
|
||||
mem_start = reg->base;
|
||||
mem_end = reg->base + reg->size;
|
||||
specified_mem_size = reg->size;
|
||||
first = false;
|
||||
} else {
|
||||
/*
|
||||
* memblock auto merges contiguous blocks, remove
|
||||
* all blocks afterwards in one go (we can't remove
|
||||
* blocks separately while iterating)
|
||||
*/
|
||||
pr_notice("Ignoring RAM after %pa, memory at %pa ignored\n",
|
||||
&mem_end, ®->base);
|
||||
memblock_remove(reg->base, 0 - reg->base);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num = allocate_region(mem_start, specified_mem_size, mem_max_regions, mem);
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
unsigned long subreg = mem[i].size / MPU_NR_SUBREGS;
|
||||
|
||||
total_mem_size += mem[i].size - subreg * hweight_long(mem[i].subreg);
|
||||
|
||||
pr_debug("MPU: base %pa size %pa disable subregions: %*pbl\n",
|
||||
&mem[i].base, &mem[i].size, MPU_NR_SUBREGS, &mem[i].subreg);
|
||||
}
|
||||
|
||||
if (total_mem_size != specified_mem_size) {
|
||||
pr_warn("Truncating memory from %pa to %pa (MPU region constraints)",
|
||||
&specified_mem_size, &total_mem_size);
|
||||
memblock_remove(mem_start + total_mem_size,
|
||||
specified_mem_size - total_mem_size);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init __mpu_max_regions(void)
|
||||
{
|
||||
/*
|
||||
* We don't support a different number of I/D side regions so if we
|
||||
* have separate instruction and data memory maps then return
|
||||
* whichever side has a smaller number of supported regions.
|
||||
*/
|
||||
u32 dregions, iregions, mpuir;
|
||||
|
||||
mpuir = read_cpuid_mputype();
|
||||
|
||||
dregions = iregions = (mpuir & MPUIR_DREGION_SZMASK) >> MPUIR_DREGION;
|
||||
|
||||
/* Check for separate d-side and i-side memory maps */
|
||||
if (mpuir & MPUIR_nU)
|
||||
iregions = (mpuir & MPUIR_IREGION_SZMASK) >> MPUIR_IREGION;
|
||||
|
||||
/* Use the smallest of the two maxima */
|
||||
return min(dregions, iregions);
|
||||
}
|
||||
|
||||
static int __init mpu_iside_independent(void)
|
||||
{
|
||||
/* MPUIR.nU specifies whether there is *not* a unified memory map */
|
||||
return read_cpuid_mputype() & MPUIR_nU;
|
||||
}
|
||||
|
||||
static int __init __mpu_min_region_order(void)
|
||||
{
|
||||
u32 drbar_result, irbar_result;
|
||||
|
||||
/* We've kept a region free for this probing */
|
||||
rgnr_write(MPU_PROBE_REGION);
|
||||
isb();
|
||||
/*
|
||||
* As per ARM ARM, write 0xFFFFFFFC to DRBAR to find the minimum
|
||||
* region order
|
||||
*/
|
||||
drbar_write(0xFFFFFFFC);
|
||||
drbar_result = irbar_result = drbar_read();
|
||||
drbar_write(0x0);
|
||||
/* If the MPU is non-unified, we use the larger of the two minima*/
|
||||
if (mpu_iside_independent()) {
|
||||
irbar_write(0xFFFFFFFC);
|
||||
irbar_result = irbar_read();
|
||||
irbar_write(0x0);
|
||||
}
|
||||
isb(); /* Ensure that MPU region operations have completed */
|
||||
/* Return whichever result is larger */
|
||||
|
||||
return __ffs(max(drbar_result, irbar_result));
|
||||
}
|
||||
|
||||
static int __init mpu_setup_region(unsigned int number, phys_addr_t start,
|
||||
unsigned int size_order, unsigned int properties,
|
||||
unsigned int subregions, bool need_flush)
|
||||
{
|
||||
u32 size_data;
|
||||
|
||||
/* We kept a region free for probing resolution of MPU regions*/
|
||||
if (number > mpu_max_regions
|
||||
|| number >= MPU_MAX_REGIONS)
|
||||
return -ENOENT;
|
||||
|
||||
if (size_order > 32)
|
||||
return -ENOMEM;
|
||||
|
||||
if (size_order < mpu_min_region_order)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Writing N to bits 5:1 (RSR_SZ) specifies region size 2^N+1 */
|
||||
size_data = ((size_order - 1) << MPU_RSR_SZ) | 1 << MPU_RSR_EN;
|
||||
size_data |= subregions << MPU_RSR_SD;
|
||||
|
||||
if (need_flush)
|
||||
flush_cache_all();
|
||||
|
||||
dsb(); /* Ensure all previous data accesses occur with old mappings */
|
||||
rgnr_write(number);
|
||||
isb();
|
||||
drbar_write(start);
|
||||
dracr_write(properties);
|
||||
isb(); /* Propagate properties before enabling region */
|
||||
drsr_write(size_data);
|
||||
|
||||
/* Check for independent I-side registers */
|
||||
if (mpu_iside_independent()) {
|
||||
irbar_write(start);
|
||||
iracr_write(properties);
|
||||
isb();
|
||||
irsr_write(size_data);
|
||||
}
|
||||
isb();
|
||||
|
||||
/* Store region info (we treat i/d side the same, so only store d) */
|
||||
mpu_rgn_info.rgns[number].dracr = properties;
|
||||
mpu_rgn_info.rgns[number].drbar = start;
|
||||
mpu_rgn_info.rgns[number].drsr = size_data;
|
||||
|
||||
mpu_rgn_info.used++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up default MPU regions, doing nothing if there is no MPU
|
||||
*/
|
||||
void __init mpu_setup(void)
|
||||
{
|
||||
int i, region = 0, err = 0;
|
||||
|
||||
if (!mpu_present())
|
||||
return;
|
||||
|
||||
/* Setup MPU (order is important) */
|
||||
|
||||
/* Background */
|
||||
err |= mpu_setup_region(region++, 0, 32,
|
||||
MPU_ACR_XN | MPU_RGN_STRONGLY_ORDERED | MPU_AP_PL1RW_PL0NA,
|
||||
0, false);
|
||||
|
||||
#ifdef CONFIG_XIP_KERNEL
|
||||
/* ROM */
|
||||
for (i = 0; i < ARRAY_SIZE(xip); i++) {
|
||||
/*
|
||||
* In case we overwrite RAM region we set earlier in
|
||||
* head-nommu.S (which is cachable) all subsequent
|
||||
* data access till we setup RAM bellow would be done
|
||||
* with BG region (which is uncachable), thus we need
|
||||
* to clean and invalidate cache.
|
||||
*/
|
||||
bool need_flush = region == MPU_RAM_REGION;
|
||||
|
||||
if (!xip[i].size)
|
||||
continue;
|
||||
|
||||
err |= mpu_setup_region(region++, xip[i].base, ilog2(xip[i].size),
|
||||
MPU_AP_PL1RO_PL0NA | MPU_RGN_NORMAL,
|
||||
xip[i].subreg, need_flush);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* RAM */
|
||||
for (i = 0; i < ARRAY_SIZE(mem); i++) {
|
||||
if (!mem[i].size)
|
||||
continue;
|
||||
|
||||
err |= mpu_setup_region(region++, mem[i].base, ilog2(mem[i].size),
|
||||
MPU_AP_PL1RW_PL0RW | MPU_RGN_NORMAL,
|
||||
mem[i].subreg, false);
|
||||
}
|
||||
|
||||
/* Vectors */
|
||||
#ifndef CONFIG_CPU_V7M
|
||||
err |= mpu_setup_region(region++, vectors_base, ilog2(2 * PAGE_SIZE),
|
||||
MPU_AP_PL1RW_PL0NA | MPU_RGN_NORMAL,
|
||||
0, false);
|
||||
#endif
|
||||
if (err) {
|
||||
panic("MPU region initialization failure! %d", err);
|
||||
} else {
|
||||
pr_info("Using ARMv7 PMSA Compliant MPU. "
|
||||
"Region independence: %s, Used %d of %d regions\n",
|
||||
mpu_iside_independent() ? "Yes" : "No",
|
||||
mpu_rgn_info.used, mpu_max_regions);
|
||||
}
|
||||
}
|
@ -34,8 +34,8 @@ config ARCH_BINFMT_ELF_STATE
|
||||
|
||||
config BINFMT_ELF_FDPIC
|
||||
bool "Kernel support for FDPIC ELF binaries"
|
||||
default y
|
||||
depends on (FRV || BLACKFIN || (SUPERH32 && !MMU) || C6X)
|
||||
default y if !BINFMT_ELF
|
||||
depends on (ARM || FRV || BLACKFIN || (SUPERH32 && !MMU) || C6X)
|
||||
select ELFCORE
|
||||
help
|
||||
ELF FDPIC binaries are based on ELF, but allow the individual load
|
||||
|
@ -51,6 +51,11 @@
|
||||
#define user_siginfo_t siginfo_t
|
||||
#endif
|
||||
|
||||
/* That's for binfmt_elf_fdpic to deal with */
|
||||
#ifndef elf_check_fdpic
|
||||
#define elf_check_fdpic(ex) false
|
||||
#endif
|
||||
|
||||
static int load_elf_binary(struct linux_binprm *bprm);
|
||||
static unsigned long elf_map(struct file *, unsigned long, struct elf_phdr *,
|
||||
int, int, unsigned long);
|
||||
@ -541,7 +546,8 @@ static unsigned long load_elf_interp(struct elfhdr *interp_elf_ex,
|
||||
if (interp_elf_ex->e_type != ET_EXEC &&
|
||||
interp_elf_ex->e_type != ET_DYN)
|
||||
goto out;
|
||||
if (!elf_check_arch(interp_elf_ex))
|
||||
if (!elf_check_arch(interp_elf_ex) ||
|
||||
elf_check_fdpic(interp_elf_ex))
|
||||
goto out;
|
||||
if (!interpreter->f_op->mmap)
|
||||
goto out;
|
||||
@ -718,6 +724,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
|
||||
goto out;
|
||||
if (!elf_check_arch(&loc->elf_ex))
|
||||
goto out;
|
||||
if (elf_check_fdpic(&loc->elf_ex))
|
||||
goto out;
|
||||
if (!bprm->file->f_op->mmap)
|
||||
goto out;
|
||||
|
||||
@ -817,7 +825,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
|
||||
if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
|
||||
goto out_free_dentry;
|
||||
/* Verify the interpreter has a valid arch */
|
||||
if (!elf_check_arch(&loc->interp_elf_ex))
|
||||
if (!elf_check_arch(&loc->interp_elf_ex) ||
|
||||
elf_check_fdpic(&loc->interp_elf_ex))
|
||||
goto out_free_dentry;
|
||||
|
||||
/* Load the interpreter program headers */
|
||||
@ -1190,6 +1199,8 @@ static int load_elf_library(struct file *file)
|
||||
if (elf_ex.e_type != ET_EXEC || elf_ex.e_phnum > 2 ||
|
||||
!elf_check_arch(&elf_ex) || !file->f_op->mmap)
|
||||
goto out;
|
||||
if (elf_check_fdpic(&elf_ex))
|
||||
goto out;
|
||||
|
||||
/* Now read in all of the header information */
|
||||
|
||||
|
@ -378,6 +378,11 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
|
||||
executable_stack);
|
||||
if (retval < 0)
|
||||
goto error;
|
||||
#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES
|
||||
retval = arch_setup_additional_pages(bprm, !!interpreter_name);
|
||||
if (retval < 0)
|
||||
goto error;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* load the executable and interpreter into memory */
|
||||
@ -831,6 +836,9 @@ static int elf_fdpic_map_file(struct elf_fdpic_params *params,
|
||||
if (phdr->p_vaddr >= seg->p_vaddr &&
|
||||
phdr->p_vaddr + phdr->p_memsz <=
|
||||
seg->p_vaddr + seg->p_memsz) {
|
||||
Elf32_Dyn __user *dyn;
|
||||
Elf32_Sword d_tag;
|
||||
|
||||
params->dynamic_addr =
|
||||
(phdr->p_vaddr - seg->p_vaddr) +
|
||||
seg->addr;
|
||||
@ -843,8 +851,9 @@ static int elf_fdpic_map_file(struct elf_fdpic_params *params,
|
||||
goto dynamic_error;
|
||||
|
||||
tmp = phdr->p_memsz / sizeof(Elf32_Dyn);
|
||||
if (((Elf32_Dyn *)
|
||||
params->dynamic_addr)[tmp - 1].d_tag != 0)
|
||||
dyn = (Elf32_Dyn __user *)params->dynamic_addr;
|
||||
__get_user(d_tag, &dyn[tmp - 1].d_tag);
|
||||
if (d_tag != 0)
|
||||
goto dynamic_error;
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user