diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /arch/arm/kernel/setup.c | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/arm/kernel/setup.c')
-rw-r--r-- | arch/arm/kernel/setup.c | 1340 |
1 files changed, 1340 insertions, 0 deletions
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c new file mode 100644 index 000000000..b06602cea --- /dev/null +++ b/arch/arm/kernel/setup.c @@ -0,0 +1,1340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/arch/arm/kernel/setup.c + * + * Copyright (C) 1995-2001 Russell King + */ +#include <linux/efi.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/utsname.h> +#include <linux/initrd.h> +#include <linux/console.h> +#include <linux/seq_file.h> +#include <linux/screen_info.h> +#include <linux/of_platform.h> +#include <linux/init.h> +#include <linux/kexec.h> +#include <linux/libfdt.h> +#include <linux/of_fdt.h> +#include <linux/cpu.h> +#include <linux/interrupt.h> +#include <linux/smp.h> +#include <linux/proc_fs.h> +#include <linux/memblock.h> +#include <linux/bug.h> +#include <linux/compiler.h> +#include <linux/sort.h> +#include <linux/psci.h> + +#include <asm/unified.h> +#include <asm/cp15.h> +#include <asm/cpu.h> +#include <asm/cputype.h> +#include <asm/efi.h> +#include <asm/elf.h> +#include <asm/early_ioremap.h> +#include <asm/fixmap.h> +#include <asm/procinfo.h> +#include <asm/psci.h> +#include <asm/sections.h> +#include <asm/setup.h> +#include <asm/smp_plat.h> +#include <asm/mach-types.h> +#include <asm/cacheflush.h> +#include <asm/cachetype.h> +#include <asm/tlbflush.h> +#include <asm/xen/hypervisor.h> + +#include <asm/prom.h> +#include <asm/mach/arch.h> +#include <asm/mach/irq.h> +#include <asm/mach/time.h> +#include <asm/system_info.h> +#include <asm/system_misc.h> +#include <asm/traps.h> +#include <asm/unwind.h> +#include <asm/memblock.h> +#include <asm/virt.h> + +#include "atags.h" + + +#if defined(CONFIG_FPE_NWFPE) || defined(CONFIG_FPE_FASTFPE) +char fpe_type[8]; + +static int __init fpe_setup(char *line) +{ + memcpy(fpe_type, line, 8); + return 1; +} + +__setup("fpe=", fpe_setup); +#endif + +extern void init_default_cache_policy(unsigned long); +extern void paging_init(const struct machine_desc *desc); +extern void early_mm_init(const struct machine_desc *); +extern void adjust_lowmem_bounds(void); +extern enum reboot_mode reboot_mode; +extern void setup_dma_zone(const struct machine_desc *desc); + +unsigned int processor_id; +EXPORT_SYMBOL(processor_id); +unsigned int __machine_arch_type __read_mostly; +EXPORT_SYMBOL(__machine_arch_type); +unsigned int cacheid __read_mostly; +EXPORT_SYMBOL(cacheid); + +unsigned int __atags_pointer __initdata; + +unsigned int system_rev; +EXPORT_SYMBOL(system_rev); + +const char *system_serial; +EXPORT_SYMBOL(system_serial); + +unsigned int system_serial_low; +EXPORT_SYMBOL(system_serial_low); + +unsigned int system_serial_high; +EXPORT_SYMBOL(system_serial_high); + +unsigned int elf_hwcap __read_mostly; +EXPORT_SYMBOL(elf_hwcap); + +unsigned int elf_hwcap2 __read_mostly; +EXPORT_SYMBOL(elf_hwcap2); + + +#ifdef MULTI_CPU +struct processor processor __ro_after_init; +#if defined(CONFIG_BIG_LITTLE) && defined(CONFIG_HARDEN_BRANCH_PREDICTOR) +struct processor *cpu_vtable[NR_CPUS] = { + [0] = &processor, +}; +#endif +#endif +#ifdef MULTI_TLB +struct cpu_tlb_fns cpu_tlb __ro_after_init; +#endif +#ifdef MULTI_USER +struct cpu_user_fns cpu_user __ro_after_init; +#endif +#ifdef MULTI_CACHE +struct cpu_cache_fns cpu_cache __ro_after_init; +#endif +#ifdef CONFIG_OUTER_CACHE +struct outer_cache_fns outer_cache __ro_after_init; +EXPORT_SYMBOL(outer_cache); +#endif + +/* + * Cached cpu_architecture() result for use by assembler code. + * C code should use the cpu_architecture() function instead of accessing this + * variable directly. + */ +int __cpu_architecture __read_mostly = CPU_ARCH_UNKNOWN; + +struct stack { + u32 irq[3]; + u32 abt[3]; + u32 und[3]; + u32 fiq[3]; +} ____cacheline_aligned; + +#ifndef CONFIG_CPU_V7M +static struct stack stacks[NR_CPUS]; +#endif + +char elf_platform[ELF_PLATFORM_SIZE]; +EXPORT_SYMBOL(elf_platform); + +static const char *cpu_name; +static const char *machine_name; +static char __initdata cmd_line[COMMAND_LINE_SIZE]; +const struct machine_desc *machine_desc __initdata; + +static union { char c[4]; unsigned long l; } endian_test __initdata = { { 'l', '?', '?', 'b' } }; +#define ENDIANNESS ((char)endian_test.l) + +DEFINE_PER_CPU(struct cpuinfo_arm, cpu_data); + +/* + * Standard memory resources + */ +static struct resource mem_res[] = { + { + .name = "Video RAM", + .start = 0, + .end = 0, + .flags = IORESOURCE_MEM + }, + { + .name = "Kernel code", + .start = 0, + .end = 0, + .flags = IORESOURCE_SYSTEM_RAM + }, + { + .name = "Kernel data", + .start = 0, + .end = 0, + .flags = IORESOURCE_SYSTEM_RAM + } +}; + +#define video_ram mem_res[0] +#define kernel_code mem_res[1] +#define kernel_data mem_res[2] + +static struct resource io_res[] = { + { + .name = "reserved", + .start = 0x3bc, + .end = 0x3be, + .flags = IORESOURCE_IO | IORESOURCE_BUSY + }, + { + .name = "reserved", + .start = 0x378, + .end = 0x37f, + .flags = IORESOURCE_IO | IORESOURCE_BUSY + }, + { + .name = "reserved", + .start = 0x278, + .end = 0x27f, + .flags = IORESOURCE_IO | IORESOURCE_BUSY + } +}; + +#define lp0 io_res[0] +#define lp1 io_res[1] +#define lp2 io_res[2] + +static const char *proc_arch[] = { + "undefined/unknown", + "3", + "4", + "4T", + "5", + "5T", + "5TE", + "5TEJ", + "6TEJ", + "7", + "7M", + "?(12)", + "?(13)", + "?(14)", + "?(15)", + "?(16)", + "?(17)", +}; + +#ifdef CONFIG_CPU_V7M +static int __get_cpu_architecture(void) +{ + return CPU_ARCH_ARMv7M; +} +#else +static int __get_cpu_architecture(void) +{ + int cpu_arch; + + if ((read_cpuid_id() & 0x0008f000) == 0) { + cpu_arch = CPU_ARCH_UNKNOWN; + } else if ((read_cpuid_id() & 0x0008f000) == 0x00007000) { + cpu_arch = (read_cpuid_id() & (1 << 23)) ? CPU_ARCH_ARMv4T : CPU_ARCH_ARMv3; + } else if ((read_cpuid_id() & 0x00080000) == 0x00000000) { + cpu_arch = (read_cpuid_id() >> 16) & 7; + if (cpu_arch) + cpu_arch += CPU_ARCH_ARMv3; + } else if ((read_cpuid_id() & 0x000f0000) == 0x000f0000) { + /* Revised CPUID format. Read the Memory Model Feature + * Register 0 and check for VMSAv7 or PMSAv7 */ + unsigned int mmfr0 = read_cpuid_ext(CPUID_EXT_MMFR0); + if ((mmfr0 & 0x0000000f) >= 0x00000003 || + (mmfr0 & 0x000000f0) >= 0x00000030) + cpu_arch = CPU_ARCH_ARMv7; + else if ((mmfr0 & 0x0000000f) == 0x00000002 || + (mmfr0 & 0x000000f0) == 0x00000020) + cpu_arch = CPU_ARCH_ARMv6; + else + cpu_arch = CPU_ARCH_UNKNOWN; + } else + cpu_arch = CPU_ARCH_UNKNOWN; + + return cpu_arch; +} +#endif + +int __pure cpu_architecture(void) +{ + BUG_ON(__cpu_architecture == CPU_ARCH_UNKNOWN); + + return __cpu_architecture; +} + +static int cpu_has_aliasing_icache(unsigned int arch) +{ + int aliasing_icache; + unsigned int id_reg, num_sets, line_size; + + /* PIPT caches never alias. */ + if (icache_is_pipt()) + return 0; + + /* arch specifies the register format */ + switch (arch) { + case CPU_ARCH_ARMv7: + set_csselr(CSSELR_ICACHE | CSSELR_L1); + isb(); + id_reg = read_ccsidr(); + line_size = 4 << ((id_reg & 0x7) + 2); + num_sets = ((id_reg >> 13) & 0x7fff) + 1; + aliasing_icache = (line_size * num_sets) > PAGE_SIZE; + break; + case CPU_ARCH_ARMv6: + aliasing_icache = read_cpuid_cachetype() & (1 << 11); + break; + default: + /* I-cache aliases will be handled by D-cache aliasing code */ + aliasing_icache = 0; + } + + return aliasing_icache; +} + +static void __init cacheid_init(void) +{ + unsigned int arch = cpu_architecture(); + + if (arch >= CPU_ARCH_ARMv6) { + unsigned int cachetype = read_cpuid_cachetype(); + + if ((arch == CPU_ARCH_ARMv7M) && !(cachetype & 0xf000f)) { + cacheid = 0; + } else if ((cachetype & (7 << 29)) == 4 << 29) { + /* ARMv7 register format */ + arch = CPU_ARCH_ARMv7; + cacheid = CACHEID_VIPT_NONALIASING; + switch (cachetype & (3 << 14)) { + case (1 << 14): + cacheid |= CACHEID_ASID_TAGGED; + break; + case (3 << 14): + cacheid |= CACHEID_PIPT; + break; + } + } else { + arch = CPU_ARCH_ARMv6; + if (cachetype & (1 << 23)) + cacheid = CACHEID_VIPT_ALIASING; + else + cacheid = CACHEID_VIPT_NONALIASING; + } + if (cpu_has_aliasing_icache(arch)) + cacheid |= CACHEID_VIPT_I_ALIASING; + } else { + cacheid = CACHEID_VIVT; + } + + pr_info("CPU: %s data cache, %s instruction cache\n", + cache_is_vivt() ? "VIVT" : + cache_is_vipt_aliasing() ? "VIPT aliasing" : + cache_is_vipt_nonaliasing() ? "PIPT / VIPT nonaliasing" : "unknown", + cache_is_vivt() ? "VIVT" : + icache_is_vivt_asid_tagged() ? "VIVT ASID tagged" : + icache_is_vipt_aliasing() ? "VIPT aliasing" : + icache_is_pipt() ? "PIPT" : + cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown"); +} + +/* + * These functions re-use the assembly code in head.S, which + * already provide the required functionality. + */ +extern struct proc_info_list *lookup_processor_type(unsigned int); + +void __init early_print(const char *str, ...) +{ + extern void printascii(const char *); + char buf[256]; + va_list ap; + + va_start(ap, str); + vsnprintf(buf, sizeof(buf), str, ap); + va_end(ap); + +#ifdef CONFIG_DEBUG_LL + printascii(buf); +#endif + printk("%s", buf); +} + +#ifdef CONFIG_ARM_PATCH_IDIV + +static inline u32 __attribute_const__ sdiv_instruction(void) +{ + if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) { + /* "sdiv r0, r0, r1" */ + u32 insn = __opcode_thumb32_compose(0xfb90, 0xf0f1); + return __opcode_to_mem_thumb32(insn); + } + + /* "sdiv r0, r0, r1" */ + return __opcode_to_mem_arm(0xe710f110); +} + +static inline u32 __attribute_const__ udiv_instruction(void) +{ + if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) { + /* "udiv r0, r0, r1" */ + u32 insn = __opcode_thumb32_compose(0xfbb0, 0xf0f1); + return __opcode_to_mem_thumb32(insn); + } + + /* "udiv r0, r0, r1" */ + return __opcode_to_mem_arm(0xe730f110); +} + +static inline u32 __attribute_const__ bx_lr_instruction(void) +{ + if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) { + /* "bx lr; nop" */ + u32 insn = __opcode_thumb32_compose(0x4770, 0x46c0); + return __opcode_to_mem_thumb32(insn); + } + + /* "bx lr" */ + return __opcode_to_mem_arm(0xe12fff1e); +} + +static void __init patch_aeabi_idiv(void) +{ + extern void __aeabi_uidiv(void); + extern void __aeabi_idiv(void); + uintptr_t fn_addr; + unsigned int mask; + + mask = IS_ENABLED(CONFIG_THUMB2_KERNEL) ? HWCAP_IDIVT : HWCAP_IDIVA; + if (!(elf_hwcap & mask)) + return; + + pr_info("CPU: div instructions available: patching division code\n"); + + fn_addr = ((uintptr_t)&__aeabi_uidiv) & ~1; + asm ("" : "+g" (fn_addr)); + ((u32 *)fn_addr)[0] = udiv_instruction(); + ((u32 *)fn_addr)[1] = bx_lr_instruction(); + flush_icache_range(fn_addr, fn_addr + 8); + + fn_addr = ((uintptr_t)&__aeabi_idiv) & ~1; + asm ("" : "+g" (fn_addr)); + ((u32 *)fn_addr)[0] = sdiv_instruction(); + ((u32 *)fn_addr)[1] = bx_lr_instruction(); + flush_icache_range(fn_addr, fn_addr + 8); +} + +#else +static inline void patch_aeabi_idiv(void) { } +#endif + +static void __init cpuid_init_hwcaps(void) +{ + int block; + u32 isar5; + + if (cpu_architecture() < CPU_ARCH_ARMv7) + return; + + block = cpuid_feature_extract(CPUID_EXT_ISAR0, 24); + if (block >= 2) + elf_hwcap |= HWCAP_IDIVA; + if (block >= 1) + elf_hwcap |= HWCAP_IDIVT; + + /* LPAE implies atomic ldrd/strd instructions */ + block = cpuid_feature_extract(CPUID_EXT_MMFR0, 0); + if (block >= 5) + elf_hwcap |= HWCAP_LPAE; + + /* check for supported v8 Crypto instructions */ + isar5 = read_cpuid_ext(CPUID_EXT_ISAR5); + + block = cpuid_feature_extract_field(isar5, 4); + if (block >= 2) + elf_hwcap2 |= HWCAP2_PMULL; + if (block >= 1) + elf_hwcap2 |= HWCAP2_AES; + + block = cpuid_feature_extract_field(isar5, 8); + if (block >= 1) + elf_hwcap2 |= HWCAP2_SHA1; + + block = cpuid_feature_extract_field(isar5, 12); + if (block >= 1) + elf_hwcap2 |= HWCAP2_SHA2; + + block = cpuid_feature_extract_field(isar5, 16); + if (block >= 1) + elf_hwcap2 |= HWCAP2_CRC32; +} + +static void __init elf_hwcap_fixup(void) +{ + unsigned id = read_cpuid_id(); + + /* + * HWCAP_TLS is available only on 1136 r1p0 and later, + * see also kuser_get_tls_init. + */ + if (read_cpuid_part() == ARM_CPU_PART_ARM1136 && + ((id >> 20) & 3) == 0) { + elf_hwcap &= ~HWCAP_TLS; + return; + } + + /* Verify if CPUID scheme is implemented */ + if ((id & 0x000f0000) != 0x000f0000) + return; + + /* + * If the CPU supports LDREX/STREX and LDREXB/STREXB, + * avoid advertising SWP; it may not be atomic with + * multiprocessing cores. + */ + if (cpuid_feature_extract(CPUID_EXT_ISAR3, 12) > 1 || + (cpuid_feature_extract(CPUID_EXT_ISAR3, 12) == 1 && + cpuid_feature_extract(CPUID_EXT_ISAR4, 20) >= 3)) + elf_hwcap &= ~HWCAP_SWP; +} + +/* + * cpu_init - initialise one CPU. + * + * cpu_init sets up the per-CPU stacks. + */ +void notrace cpu_init(void) +{ +#ifndef CONFIG_CPU_V7M + unsigned int cpu = smp_processor_id(); + struct stack *stk = &stacks[cpu]; + + if (cpu >= NR_CPUS) { + pr_crit("CPU%u: bad primary CPU number\n", cpu); + BUG(); + } + + /* + * This only works on resume and secondary cores. For booting on the + * boot cpu, smp_prepare_boot_cpu is called after percpu area setup. + */ + set_my_cpu_offset(per_cpu_offset(cpu)); + + cpu_proc_init(); + + /* + * Define the placement constraint for the inline asm directive below. + * In Thumb-2, msr with an immediate value is not allowed. + */ +#ifdef CONFIG_THUMB2_KERNEL +#define PLC_l "l" +#define PLC_r "r" +#else +#define PLC_l "I" +#define PLC_r "I" +#endif + + /* + * setup stacks for re-entrant exception handlers + */ + __asm__ ( + "msr cpsr_c, %1\n\t" + "add r14, %0, %2\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %3\n\t" + "add r14, %0, %4\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %5\n\t" + "add r14, %0, %6\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" + : + : "r" (stk), + PLC_r (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), + "I" (offsetof(struct stack, irq[0])), + PLC_r (PSR_F_BIT | PSR_I_BIT | ABT_MODE), + "I" (offsetof(struct stack, abt[0])), + PLC_r (PSR_F_BIT | PSR_I_BIT | UND_MODE), + "I" (offsetof(struct stack, und[0])), + PLC_r (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), + PLC_l (PSR_F_BIT | PSR_I_BIT | SVC_MODE) + : "r14"); +#endif +} + +u32 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = MPIDR_INVALID }; + +void __init smp_setup_processor_id(void) +{ + int i; + u32 mpidr = is_smp() ? read_cpuid_mpidr() & MPIDR_HWID_BITMASK : 0; + u32 cpu = MPIDR_AFFINITY_LEVEL(mpidr, 0); + + cpu_logical_map(0) = cpu; + for (i = 1; i < nr_cpu_ids; ++i) + cpu_logical_map(i) = i == cpu ? 0 : i; + + /* + * clear __my_cpu_offset on boot CPU to avoid hang caused by + * using percpu variable early, for example, lockdep will + * access percpu variable inside lock_release + */ + set_my_cpu_offset(0); + + pr_info("Booting Linux on physical CPU 0x%x\n", mpidr); +} + +struct mpidr_hash mpidr_hash; +#ifdef CONFIG_SMP +/** + * smp_build_mpidr_hash - Pre-compute shifts required at each affinity + * level in order to build a linear index from an + * MPIDR value. Resulting algorithm is a collision + * free hash carried out through shifting and ORing + */ +static void __init smp_build_mpidr_hash(void) +{ + u32 i, affinity; + u32 fs[3], bits[3], ls, mask = 0; + /* + * Pre-scan the list of MPIDRS and filter out bits that do + * not contribute to affinity levels, ie they never toggle. + */ + for_each_possible_cpu(i) + mask |= (cpu_logical_map(i) ^ cpu_logical_map(0)); + pr_debug("mask of set bits 0x%x\n", mask); + /* + * Find and stash the last and first bit set at all affinity levels to + * check how many bits are required to represent them. + */ + for (i = 0; i < 3; i++) { + affinity = MPIDR_AFFINITY_LEVEL(mask, i); + /* + * Find the MSB bit and LSB bits position + * to determine how many bits are required + * to express the affinity level. + */ + ls = fls(affinity); + fs[i] = affinity ? ffs(affinity) - 1 : 0; + bits[i] = ls - fs[i]; + } + /* + * An index can be created from the MPIDR by isolating the + * significant bits at each affinity level and by shifting + * them in order to compress the 24 bits values space to a + * compressed set of values. This is equivalent to hashing + * the MPIDR through shifting and ORing. It is a collision free + * hash though not minimal since some levels might contain a number + * of CPUs that is not an exact power of 2 and their bit + * representation might contain holes, eg MPIDR[7:0] = {0x2, 0x80}. + */ + mpidr_hash.shift_aff[0] = fs[0]; + mpidr_hash.shift_aff[1] = MPIDR_LEVEL_BITS + fs[1] - bits[0]; + mpidr_hash.shift_aff[2] = 2*MPIDR_LEVEL_BITS + fs[2] - + (bits[1] + bits[0]); + mpidr_hash.mask = mask; + mpidr_hash.bits = bits[2] + bits[1] + bits[0]; + pr_debug("MPIDR hash: aff0[%u] aff1[%u] aff2[%u] mask[0x%x] bits[%u]\n", + mpidr_hash.shift_aff[0], + mpidr_hash.shift_aff[1], + mpidr_hash.shift_aff[2], + mpidr_hash.mask, + mpidr_hash.bits); + /* + * 4x is an arbitrary value used to warn on a hash table much bigger + * than expected on most systems. + */ + if (mpidr_hash_size() > 4 * num_possible_cpus()) + pr_warn("Large number of MPIDR hash buckets detected\n"); + sync_cache_w(&mpidr_hash); +} +#endif + +/* + * locate processor in the list of supported processor types. The linker + * builds this table for us from the entries in arch/arm/mm/proc-*.S + */ +struct proc_info_list *lookup_processor(u32 midr) +{ + struct proc_info_list *list = lookup_processor_type(midr); + + if (!list) { + pr_err("CPU%u: configuration botched (ID %08x), CPU halted\n", + smp_processor_id(), midr); + while (1) + /* can't use cpu_relax() here as it may require MMU setup */; + } + + return list; +} + +static void __init setup_processor(void) +{ + unsigned int midr = read_cpuid_id(); + struct proc_info_list *list = lookup_processor(midr); + + cpu_name = list->cpu_name; + __cpu_architecture = __get_cpu_architecture(); + + init_proc_vtable(list->proc); +#ifdef MULTI_TLB + cpu_tlb = *list->tlb; +#endif +#ifdef MULTI_USER + cpu_user = *list->user; +#endif +#ifdef MULTI_CACHE + cpu_cache = *list->cache; +#endif + + pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n", + list->cpu_name, midr, midr & 15, + proc_arch[cpu_architecture()], get_cr()); + + snprintf(init_utsname()->machine, __NEW_UTS_LEN + 1, "%s%c", + list->arch_name, ENDIANNESS); + snprintf(elf_platform, ELF_PLATFORM_SIZE, "%s%c", + list->elf_name, ENDIANNESS); + elf_hwcap = list->elf_hwcap; + + cpuid_init_hwcaps(); + patch_aeabi_idiv(); + +#ifndef CONFIG_ARM_THUMB + elf_hwcap &= ~(HWCAP_THUMB | HWCAP_IDIVT); +#endif +#ifdef CONFIG_MMU + init_default_cache_policy(list->__cpu_mm_mmu_flags); +#endif + erratum_a15_798181_init(); + + elf_hwcap_fixup(); + + cacheid_init(); + cpu_init(); +} + +void __init dump_machine_table(void) +{ + const struct machine_desc *p; + + early_print("Available machine support:\n\nID (hex)\tNAME\n"); + for_each_machine_desc(p) + early_print("%08x\t%s\n", p->nr, p->name); + + early_print("\nPlease check your kernel config and/or bootloader.\n"); + + while (true) + /* can't use cpu_relax() here as it may require MMU setup */; +} + +int __init arm_add_memory(u64 start, u64 size) +{ + u64 aligned_start; + + /* + * Ensure that start/size are aligned to a page boundary. + * Size is rounded down, start is rounded up. + */ + aligned_start = PAGE_ALIGN(start); + if (aligned_start > start + size) + size = 0; + else + size -= aligned_start - start; + +#ifndef CONFIG_PHYS_ADDR_T_64BIT + if (aligned_start > ULONG_MAX) { + pr_crit("Ignoring memory at 0x%08llx outside 32-bit physical address space\n", + (long long)start); + return -EINVAL; + } + + if (aligned_start + size > ULONG_MAX) { + pr_crit("Truncating memory at 0x%08llx to fit in 32-bit physical address space\n", + (long long)start); + /* + * To ensure bank->start + bank->size is representable in + * 32 bits, we use ULONG_MAX as the upper limit rather than 4GB. + * This means we lose a page after masking. + */ + size = ULONG_MAX - aligned_start; + } +#endif + + if (aligned_start < PHYS_OFFSET) { + if (aligned_start + size <= PHYS_OFFSET) { + pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n", + aligned_start, aligned_start + size); + return -EINVAL; + } + + pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n", + aligned_start, (u64)PHYS_OFFSET); + + size -= PHYS_OFFSET - aligned_start; + aligned_start = PHYS_OFFSET; + } + + start = aligned_start; + size = size & ~(phys_addr_t)(PAGE_SIZE - 1); + + /* + * Check whether this memory region has non-zero size or + * invalid node number. + */ + if (size == 0) + return -EINVAL; + + memblock_add(start, size); + return 0; +} + +/* + * Pick out the memory size. We look for mem=size@start, + * where start and size are "size[KkMm]" + */ + +static int __init early_mem(char *p) +{ + static int usermem __initdata = 0; + u64 size; + u64 start; + char *endp; + + /* + * If the user specifies memory size, we + * blow away any automatically generated + * size. + */ + if (usermem == 0) { + usermem = 1; + memblock_remove(memblock_start_of_DRAM(), + memblock_end_of_DRAM() - memblock_start_of_DRAM()); + } + + start = PHYS_OFFSET; + size = memparse(p, &endp); + if (*endp == '@') + start = memparse(endp + 1, NULL); + + arm_add_memory(start, size); + + return 0; +} +early_param("mem", early_mem); + +static void __init request_standard_resources(const struct machine_desc *mdesc) +{ + phys_addr_t start, end, res_end; + struct resource *res; + u64 i; + + kernel_code.start = virt_to_phys(_text); + kernel_code.end = virt_to_phys(__init_begin - 1); + kernel_data.start = virt_to_phys(_sdata); + kernel_data.end = virt_to_phys(_end - 1); + + for_each_mem_range(i, &start, &end) { + unsigned long boot_alias_start; + + /* + * In memblock, end points to the first byte after the + * range while in resourses, end points to the last byte in + * the range. + */ + res_end = end - 1; + + /* + * Some systems have a special memory alias which is only + * used for booting. We need to advertise this region to + * kexec-tools so they know where bootable RAM is located. + */ + boot_alias_start = phys_to_idmap(start); + if (arm_has_idmap_alias() && boot_alias_start != IDMAP_INVALID_ADDR) { + res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES); + if (!res) + panic("%s: Failed to allocate %zu bytes\n", + __func__, sizeof(*res)); + res->name = "System RAM (boot alias)"; + res->start = boot_alias_start; + res->end = phys_to_idmap(res_end); + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + request_resource(&iomem_resource, res); + } + + res = memblock_alloc(sizeof(*res), SMP_CACHE_BYTES); + if (!res) + panic("%s: Failed to allocate %zu bytes\n", __func__, + sizeof(*res)); + res->name = "System RAM"; + res->start = start; + res->end = res_end; + res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY; + + request_resource(&iomem_resource, res); + + if (kernel_code.start >= res->start && + kernel_code.end <= res->end) + request_resource(res, &kernel_code); + if (kernel_data.start >= res->start && + kernel_data.end <= res->end) + request_resource(res, &kernel_data); + } + + if (mdesc->video_start) { + video_ram.start = mdesc->video_start; + video_ram.end = mdesc->video_end; + request_resource(&iomem_resource, &video_ram); + } + + /* + * Some machines don't have the possibility of ever + * possessing lp0, lp1 or lp2 + */ + if (mdesc->reserve_lp0) + request_resource(&ioport_resource, &lp0); + if (mdesc->reserve_lp1) + request_resource(&ioport_resource, &lp1); + if (mdesc->reserve_lp2) + request_resource(&ioport_resource, &lp2); +} + +#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE) || \ + defined(CONFIG_EFI) +struct screen_info screen_info = { + .orig_video_lines = 30, + .orig_video_cols = 80, + .orig_video_mode = 0, + .orig_video_ega_bx = 0, + .orig_video_isVGA = 1, + .orig_video_points = 8 +}; +#endif + +static int __init customize_machine(void) +{ + /* + * customizes platform devices, or adds new ones + * On DT based machines, we fall back to populating the + * machine from the device tree, if no callback is provided, + * otherwise we would always need an init_machine callback. + */ + if (machine_desc->init_machine) + machine_desc->init_machine(); + + return 0; +} +arch_initcall(customize_machine); + +static int __init init_machine_late(void) +{ + struct device_node *root; + int ret; + + if (machine_desc->init_late) + machine_desc->init_late(); + + root = of_find_node_by_path("/"); + if (root) { + ret = of_property_read_string(root, "serial-number", + &system_serial); + if (ret) + system_serial = NULL; + } + + if (!system_serial) + system_serial = kasprintf(GFP_KERNEL, "%08x%08x", + system_serial_high, + system_serial_low); + + return 0; +} +late_initcall(init_machine_late); + +#ifdef CONFIG_KEXEC +/* + * The crash region must be aligned to 128MB to avoid + * zImage relocating below the reserved region. + */ +#define CRASH_ALIGN (128 << 20) + +static inline unsigned long long get_total_mem(void) +{ + unsigned long total; + + total = max_low_pfn - min_low_pfn; + return total << PAGE_SHIFT; +} + +/** + * reserve_crashkernel() - reserves memory are for crash kernel + * + * This function reserves memory area given in "crashkernel=" kernel command + * line parameter. The memory reserved is used by a dump capture kernel when + * primary kernel is crashing. + */ +static void __init reserve_crashkernel(void) +{ + unsigned long long crash_size, crash_base; + unsigned long long total_mem; + int ret; + + total_mem = get_total_mem(); + ret = parse_crashkernel(boot_command_line, total_mem, + &crash_size, &crash_base); + if (ret) + return; + + if (crash_base <= 0) { + unsigned long long crash_max = idmap_to_phys((u32)~0); + unsigned long long lowmem_max = __pa(high_memory - 1) + 1; + if (crash_max > lowmem_max) + crash_max = lowmem_max; + crash_base = memblock_find_in_range(CRASH_ALIGN, crash_max, + crash_size, CRASH_ALIGN); + if (!crash_base) { + pr_err("crashkernel reservation failed - No suitable area found.\n"); + return; + } + } else { + unsigned long long start; + + start = memblock_find_in_range(crash_base, + crash_base + crash_size, + crash_size, SECTION_SIZE); + if (start != crash_base) { + pr_err("crashkernel reservation failed - memory is in use.\n"); + return; + } + } + + ret = memblock_reserve(crash_base, crash_size); + if (ret < 0) { + pr_warn("crashkernel reservation failed - memory is in use (0x%lx)\n", + (unsigned long)crash_base); + return; + } + + pr_info("Reserving %ldMB of memory at %ldMB for crashkernel (System RAM: %ldMB)\n", + (unsigned long)(crash_size >> 20), + (unsigned long)(crash_base >> 20), + (unsigned long)(total_mem >> 20)); + + /* The crashk resource must always be located in normal mem */ + crashk_res.start = crash_base; + crashk_res.end = crash_base + crash_size - 1; + insert_resource(&iomem_resource, &crashk_res); + + if (arm_has_idmap_alias()) { + /* + * If we have a special RAM alias for use at boot, we + * need to advertise to kexec tools where the alias is. + */ + static struct resource crashk_boot_res = { + .name = "Crash kernel (boot alias)", + .flags = IORESOURCE_BUSY | IORESOURCE_MEM, + }; + + crashk_boot_res.start = phys_to_idmap(crash_base); + crashk_boot_res.end = crashk_boot_res.start + crash_size - 1; + insert_resource(&iomem_resource, &crashk_boot_res); + } +} +#else +static inline void reserve_crashkernel(void) {} +#endif /* CONFIG_KEXEC */ + +void __init hyp_mode_check(void) +{ +#ifdef CONFIG_ARM_VIRT_EXT + sync_boot_mode(); + + if (is_hyp_mode_available()) { + pr_info("CPU: All CPU(s) started in HYP mode.\n"); + pr_info("CPU: Virtualization extensions available.\n"); + } else if (is_hyp_mode_mismatched()) { + pr_warn("CPU: WARNING: CPU(s) started in wrong/inconsistent modes (primary CPU mode 0x%x)\n", + __boot_cpu_mode & MODE_MASK); + pr_warn("CPU: This may indicate a broken bootloader or firmware.\n"); + } else + pr_info("CPU: All CPU(s) started in SVC mode.\n"); +#endif +} + +void __init setup_arch(char **cmdline_p) +{ + const struct machine_desc *mdesc = NULL; + void *atags_vaddr = NULL; + + if (__atags_pointer) + atags_vaddr = FDT_VIRT_BASE(__atags_pointer); + + setup_processor(); + if (atags_vaddr) { + mdesc = setup_machine_fdt(atags_vaddr); + if (mdesc) + memblock_reserve(__atags_pointer, + fdt_totalsize(atags_vaddr)); + } + if (!mdesc) + mdesc = setup_machine_tags(atags_vaddr, __machine_arch_type); + if (!mdesc) { + early_print("\nError: invalid dtb and unrecognized/unsupported machine ID\n"); + early_print(" r1=0x%08x, r2=0x%08x\n", __machine_arch_type, + __atags_pointer); + if (__atags_pointer) + early_print(" r2[]=%*ph\n", 16, atags_vaddr); + dump_machine_table(); + } + + machine_desc = mdesc; + machine_name = mdesc->name; + dump_stack_set_arch_desc("%s", mdesc->name); + + if (mdesc->reboot_mode != REBOOT_HARD) + reboot_mode = mdesc->reboot_mode; + + init_mm.start_code = (unsigned long) _text; + init_mm.end_code = (unsigned long) _etext; + init_mm.end_data = (unsigned long) _edata; + init_mm.brk = (unsigned long) _end; + + /* populate cmd_line too for later use, preserving boot_command_line */ + strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE); + *cmdline_p = cmd_line; + + early_fixmap_init(); + early_ioremap_init(); + + parse_early_param(); + +#ifdef CONFIG_MMU + early_mm_init(mdesc); +#endif + setup_dma_zone(mdesc); + xen_early_init(); + efi_init(); + /* + * Make sure the calculation for lowmem/highmem is set appropriately + * before reserving/allocating any mmeory + */ + adjust_lowmem_bounds(); + arm_memblock_init(mdesc); + /* Memory may have been removed so recalculate the bounds. */ + adjust_lowmem_bounds(); + + early_ioremap_reset(); + + paging_init(mdesc); + request_standard_resources(mdesc); + + if (mdesc->restart) + arm_pm_restart = mdesc->restart; + + unflatten_device_tree(); + + arm_dt_init_cpu_maps(); + psci_dt_init(); +#ifdef CONFIG_SMP + if (is_smp()) { + if (!mdesc->smp_init || !mdesc->smp_init()) { + if (psci_smp_available()) + smp_set_ops(&psci_smp_ops); + else if (mdesc->smp) + smp_set_ops(mdesc->smp); + } + smp_init_cpus(); + smp_build_mpidr_hash(); + } +#endif + + if (!is_smp()) + hyp_mode_check(); + + reserve_crashkernel(); + +#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER + handle_arch_irq = mdesc->handle_irq; +#endif + +#ifdef CONFIG_VT +#if defined(CONFIG_VGA_CONSOLE) + conswitchp = &vga_con; +#endif +#endif + + if (mdesc->init_early) + mdesc->init_early(); +} + + +static int __init topology_init(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct cpuinfo_arm *cpuinfo = &per_cpu(cpu_data, cpu); + cpuinfo->cpu.hotpluggable = platform_can_hotplug_cpu(cpu); + register_cpu(&cpuinfo->cpu, cpu); + } + + return 0; +} +subsys_initcall(topology_init); + +#ifdef CONFIG_HAVE_PROC_CPU +static int __init proc_cpu_init(void) +{ + struct proc_dir_entry *res; + + res = proc_mkdir("cpu", NULL); + if (!res) + return -ENOMEM; + return 0; +} +fs_initcall(proc_cpu_init); +#endif + +static const char *hwcap_str[] = { + "swp", + "half", + "thumb", + "26bit", + "fastmult", + "fpa", + "vfp", + "edsp", + "java", + "iwmmxt", + "crunch", + "thumbee", + "neon", + "vfpv3", + "vfpv3d16", + "tls", + "vfpv4", + "idiva", + "idivt", + "vfpd32", + "lpae", + "evtstrm", + NULL +}; + +static const char *hwcap2_str[] = { + "aes", + "pmull", + "sha1", + "sha2", + "crc32", + NULL +}; + +static int c_show(struct seq_file *m, void *v) +{ + int i, j; + u32 cpuid; + + for_each_online_cpu(i) { + /* + * glibc reads /proc/cpuinfo to determine the number of + * online processors, looking for lines beginning with + * "processor". Give glibc what it expects. + */ + seq_printf(m, "processor\t: %d\n", i); + cpuid = is_smp() ? per_cpu(cpu_data, i).cpuid : read_cpuid_id(); + seq_printf(m, "model name\t: %s rev %d (%s)\n", + cpu_name, cpuid & 15, elf_platform); + +#if defined(CONFIG_SMP) + seq_printf(m, "BogoMIPS\t: %lu.%02lu\n", + per_cpu(cpu_data, i).loops_per_jiffy / (500000UL/HZ), + (per_cpu(cpu_data, i).loops_per_jiffy / (5000UL/HZ)) % 100); +#else + seq_printf(m, "BogoMIPS\t: %lu.%02lu\n", + loops_per_jiffy / (500000/HZ), + (loops_per_jiffy / (5000/HZ)) % 100); +#endif + /* dump out the processor features */ + seq_puts(m, "Features\t: "); + + for (j = 0; hwcap_str[j]; j++) + if (elf_hwcap & (1 << j)) + seq_printf(m, "%s ", hwcap_str[j]); + + for (j = 0; hwcap2_str[j]; j++) + if (elf_hwcap2 & (1 << j)) + seq_printf(m, "%s ", hwcap2_str[j]); + + seq_printf(m, "\nCPU implementer\t: 0x%02x\n", cpuid >> 24); + seq_printf(m, "CPU architecture: %s\n", + proc_arch[cpu_architecture()]); + + if ((cpuid & 0x0008f000) == 0x00000000) { + /* pre-ARM7 */ + seq_printf(m, "CPU part\t: %07x\n", cpuid >> 4); + } else { + if ((cpuid & 0x0008f000) == 0x00007000) { + /* ARM7 */ + seq_printf(m, "CPU variant\t: 0x%02x\n", + (cpuid >> 16) & 127); + } else { + /* post-ARM7 */ + seq_printf(m, "CPU variant\t: 0x%x\n", + (cpuid >> 20) & 15); + } + seq_printf(m, "CPU part\t: 0x%03x\n", + (cpuid >> 4) & 0xfff); + } + seq_printf(m, "CPU revision\t: %d\n\n", cpuid & 15); + } + + seq_printf(m, "Hardware\t: %s\n", machine_name); + seq_printf(m, "Revision\t: %04x\n", system_rev); + seq_printf(m, "Serial\t\t: %s\n", system_serial); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < 1 ? (void *)1 : NULL; +} + +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return NULL; +} + +static void c_stop(struct seq_file *m, void *v) +{ +} + +const struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = c_show +}; |