98eb30fe4c
The cpu_spec is a struct holding various information about the CPU the kernel is executing on. It's populated early in boot and must not change after that. In particular the cpu_features and mmu_features hold the set of discovered CPU/MMU features and are used to set static keys for each feature, and do binary patching of assembly. So any change to the cpu_features/mmu_features later in boot will not be reflected in the state of the static keys or patched code. There is already logic to check that cpu_features/mmu_features don't change, see check_features() in feature-fixups.c. But as another layer of protection the entire cpu_spec should be read only after init, annotate it as such. Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Link: https://msgid.link/20231025012452.1985680-1-mpe@ellerman.id.au
183 lines
4.3 KiB
C
183 lines
4.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2001 Ben. Herrenschmidt (benh@kernel.crashing.org)
|
|
*
|
|
* Modifications for ppc64:
|
|
* Copyright (C) 2003 Dave Engebretsen <engebret@us.ibm.com>
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/threads.h>
|
|
#include <linux/init.h>
|
|
#include <linux/export.h>
|
|
#include <linux/jump_label.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <asm/cputable.h>
|
|
#include <asm/mce.h>
|
|
#include <asm/mmu.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/cpu_setup.h>
|
|
|
|
static struct cpu_spec the_cpu_spec __ro_after_init;
|
|
|
|
struct cpu_spec *cur_cpu_spec __ro_after_init = NULL;
|
|
EXPORT_SYMBOL(cur_cpu_spec);
|
|
|
|
/* The platform string corresponding to the real PVR */
|
|
const char *powerpc_base_platform;
|
|
|
|
#include "cpu_specs.h"
|
|
|
|
void __init set_cur_cpu_spec(struct cpu_spec *s)
|
|
{
|
|
struct cpu_spec *t = &the_cpu_spec;
|
|
|
|
t = PTRRELOC(t);
|
|
/*
|
|
* use memcpy() instead of *t = *s so that GCC replaces it
|
|
* by __memcpy() when KASAN is active
|
|
*/
|
|
memcpy(t, s, sizeof(*t));
|
|
|
|
*PTRRELOC(&cur_cpu_spec) = &the_cpu_spec;
|
|
}
|
|
|
|
static struct cpu_spec * __init setup_cpu_spec(unsigned long offset,
|
|
struct cpu_spec *s)
|
|
{
|
|
struct cpu_spec *t = &the_cpu_spec;
|
|
struct cpu_spec old;
|
|
|
|
t = PTRRELOC(t);
|
|
old = *t;
|
|
|
|
/*
|
|
* Copy everything, then do fixups. Use memcpy() instead of *t = *s
|
|
* so that GCC replaces it by __memcpy() when KASAN is active
|
|
*/
|
|
memcpy(t, s, sizeof(*t));
|
|
|
|
/*
|
|
* If we are overriding a previous value derived from the real
|
|
* PVR with a new value obtained using a logical PVR value,
|
|
* don't modify the performance monitor fields.
|
|
*/
|
|
if (old.num_pmcs && !s->num_pmcs) {
|
|
t->num_pmcs = old.num_pmcs;
|
|
t->pmc_type = old.pmc_type;
|
|
|
|
/*
|
|
* Let's ensure that the
|
|
* fix for the PMAO bug is enabled on compatibility mode.
|
|
*/
|
|
t->cpu_features |= old.cpu_features & CPU_FTR_PMAO_BUG;
|
|
}
|
|
|
|
/* Set kuap ON at startup, will be disabled later if cmdline has 'nosmap' */
|
|
if (IS_ENABLED(CONFIG_PPC_KUAP) && IS_ENABLED(CONFIG_PPC32))
|
|
t->mmu_features |= MMU_FTR_KUAP;
|
|
|
|
*PTRRELOC(&cur_cpu_spec) = &the_cpu_spec;
|
|
|
|
/*
|
|
* Set the base platform string once; assumes
|
|
* we're called with real pvr first.
|
|
*/
|
|
if (*PTRRELOC(&powerpc_base_platform) == NULL)
|
|
*PTRRELOC(&powerpc_base_platform) = t->platform;
|
|
|
|
#if defined(CONFIG_PPC64) || defined(CONFIG_BOOKE)
|
|
/* ppc64 and booke expect identify_cpu to also call setup_cpu for
|
|
* that processor. I will consolidate that at a later time, for now,
|
|
* just use #ifdef. We also don't need to PTRRELOC the function
|
|
* pointer on ppc64 and booke as we are running at 0 in real mode
|
|
* on ppc64 and reloc_offset is always 0 on booke.
|
|
*/
|
|
if (t->cpu_setup) {
|
|
t->cpu_setup(offset, t);
|
|
}
|
|
#endif /* CONFIG_PPC64 || CONFIG_BOOKE */
|
|
|
|
return t;
|
|
}
|
|
|
|
struct cpu_spec * __init identify_cpu(unsigned long offset, unsigned int pvr)
|
|
{
|
|
struct cpu_spec *s = cpu_specs;
|
|
int i;
|
|
|
|
BUILD_BUG_ON(!ARRAY_SIZE(cpu_specs));
|
|
|
|
s = PTRRELOC(s);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cpu_specs); i++,s++) {
|
|
if ((pvr & s->pvr_mask) == s->pvr_value)
|
|
return setup_cpu_spec(offset, s);
|
|
}
|
|
|
|
BUG();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Used by cpufeatures to get the name for CPUs with a PVR table.
|
|
* If they don't hae a PVR table, cpufeatures gets the name from
|
|
* cpu device-tree node.
|
|
*/
|
|
void __init identify_cpu_name(unsigned int pvr)
|
|
{
|
|
struct cpu_spec *s = cpu_specs;
|
|
struct cpu_spec *t = &the_cpu_spec;
|
|
int i;
|
|
|
|
s = PTRRELOC(s);
|
|
t = PTRRELOC(t);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cpu_specs); i++,s++) {
|
|
if ((pvr & s->pvr_mask) == s->pvr_value) {
|
|
t->cpu_name = s->cpu_name;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_JUMP_LABEL_FEATURE_CHECKS
|
|
struct static_key_true cpu_feature_keys[NUM_CPU_FTR_KEYS] = {
|
|
[0 ... NUM_CPU_FTR_KEYS - 1] = STATIC_KEY_TRUE_INIT
|
|
};
|
|
EXPORT_SYMBOL_GPL(cpu_feature_keys);
|
|
|
|
void __init cpu_feature_keys_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_CPU_FTR_KEYS; i++) {
|
|
unsigned long f = 1ul << i;
|
|
|
|
if (!(cur_cpu_spec->cpu_features & f))
|
|
static_branch_disable(&cpu_feature_keys[i]);
|
|
}
|
|
}
|
|
|
|
struct static_key_true mmu_feature_keys[NUM_MMU_FTR_KEYS] = {
|
|
[0 ... NUM_MMU_FTR_KEYS - 1] = STATIC_KEY_TRUE_INIT
|
|
};
|
|
EXPORT_SYMBOL(mmu_feature_keys);
|
|
|
|
void __init mmu_feature_keys_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_MMU_FTR_KEYS; i++) {
|
|
unsigned long f = 1ul << i;
|
|
|
|
if (!(cur_cpu_spec->mmu_features & f))
|
|
static_branch_disable(&mmu_feature_keys[i]);
|
|
}
|
|
}
|
|
#endif
|