git: 2aeac25bcb72 - main - arm64: Add a function to restrict the ID registers
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 02 Jun 2025 10:42:00 UTC
The branch main has been updated by andrew:
URL: https://cgit.FreeBSD.org/src/commit/?id=2aeac25bcb72723d436721ab97bb2fd6df3dfb9b
commit 2aeac25bcb72723d436721ab97bb2fd6df3dfb9b
Author: Andrew Turner <andrew@FreeBSD.org>
AuthorDate: 2025-06-02 09:56:59 +0000
Commit: Andrew Turner <andrew@FreeBSD.org>
CommitDate: 2025-06-02 09:56:59 +0000
arm64: Add a function to restrict the ID registers
This will be used when we need to restrict the register values, e.g.
when an erratum is present that means we need to disable a feature we
will need to remove some fields from these registers.
Sponsored by: Arm Ltd
Differential Revision: https://reviews.freebsd.org/D50572
---
sys/arm64/arm64/identcpu.c | 131 ++++++++++++++++++++++++++++++++-------------
sys/arm64/include/cpu.h | 1 +
2 files changed, 94 insertions(+), 38 deletions(-)
diff --git a/sys/arm64/arm64/identcpu.c b/sys/arm64/arm64/identcpu.c
index fe10e0eb551a..6dc9c447ee29 100644
--- a/sys/arm64/arm64/identcpu.c
+++ b/sys/arm64/arm64/identcpu.c
@@ -51,6 +51,9 @@
static MALLOC_DEFINE(M_IDENTCPU, "CPU ID", "arm64 CPU identification memory");
struct cpu_desc;
+#ifdef INVARIANTS
+static bool hwcaps_set = false;
+#endif
static void print_cpu_midr(struct sbuf *sb, u_int cpu);
static void print_cpu_features(u_int cpu, struct cpu_desc *desc,
@@ -2594,13 +2597,62 @@ update_special_reg_field(uint64_t user_reg, u_int type, uint64_t value,
return (user_reg);
}
+static void
+clear_set_special_reg_idx(int idx, uint64_t clear, uint64_t set)
+{
+ const struct mrs_field *fields;
+ uint64_t k_old, k_new;
+ uint64_t f_old, f_new;
+ uint64_t l_old, l_new;
+
+ MPASS(idx < nitems(user_regs));
+
+ k_old = CPU_DESC_FIELD(kern_cpu_desc, idx);
+ k_new = (k_old & ~clear) | set;
+
+ f_old = CPU_DESC_FIELD(user_cpu_desc, idx);
+ f_new = (f_old & ~clear) | set;
+
+ l_old = CPU_DESC_FIELD(l_user_cpu_desc, idx);
+ l_new = (l_old & ~clear) | set;
+
+ fields = user_regs[idx].fields;
+ for (int j = 0; fields[j].type != 0; j++) {
+ u_int type;
+
+ /* Update the FreeBSD userspace ID register view */
+ type = ((fields[j].type & MRS_FREEBSD) != 0) ?
+ fields[j].type :
+ (MRS_EXACT | (fields[j].type & MRS_SAFE_MASK));
+ f_new = update_special_reg_field(f_new,
+ type, f_old, fields[j].width, fields[j].shift,
+ fields[j].sign);
+
+ /* Update the Linux userspace ID register view */
+ type = ((fields[j].type & MRS_LINUX) != 0) ?
+ fields[j].type :
+ (MRS_EXACT | (fields[j].type & MRS_SAFE_MASK));
+ l_new = update_special_reg_field(l_new,
+ type, l_old, fields[j].width, fields[j].shift,
+ fields[j].sign);
+
+ /* Update the kernel ID register view */
+ k_new = update_special_reg_field(k_new,
+ fields[j].type, k_old, fields[j].width,
+ fields[j].shift, fields[j].sign);
+ }
+
+ CPU_DESC_FIELD(kern_cpu_desc, idx) = k_new;
+ CPU_DESC_FIELD(user_cpu_desc, idx) = f_new;
+ CPU_DESC_FIELD(l_user_cpu_desc, idx) = l_new;
+}
+
void
update_special_regs(u_int cpu)
{
struct cpu_desc *desc;
- const struct mrs_field *fields;
- uint64_t l_user_reg, user_reg, kern_reg, value;
- int i, j;
+ uint64_t value;
+ int i;
if (cpu == 0) {
/* Create a user visible cpu description with safe values */
@@ -2618,44 +2670,42 @@ update_special_regs(u_int cpu)
for (i = 0; i < nitems(user_regs); i++) {
value = CPU_DESC_FIELD(*desc, i);
if (cpu == 0) {
- kern_reg = value;
- user_reg = value;
- l_user_reg = value;
- } else {
- kern_reg = CPU_DESC_FIELD(kern_cpu_desc, i);
- user_reg = CPU_DESC_FIELD(user_cpu_desc, i);
- l_user_reg = CPU_DESC_FIELD(l_user_cpu_desc, i);
+ CPU_DESC_FIELD(kern_cpu_desc, i) = value;
+ CPU_DESC_FIELD(user_cpu_desc, i) = value;
+ CPU_DESC_FIELD(l_user_cpu_desc, i) = value;
}
- fields = user_regs[i].fields;
- for (j = 0; fields[j].type != 0; j++) {
- u_int type;
-
- /* Update the FreeBSD userspace ID register view */
- type = ((fields[j].type & MRS_FREEBSD) != 0) ?
- fields[j].type :
- (MRS_EXACT | (fields[j].type & MRS_SAFE_MASK));
- user_reg = update_special_reg_field(user_reg,
- type, value, fields[j].width, fields[j].shift,
- fields[j].sign);
-
- /* Update the Linux userspace ID register view */
- type = ((fields[j].type & MRS_LINUX) != 0) ?
- fields[j].type :
- (MRS_EXACT | (fields[j].type & MRS_SAFE_MASK));
- l_user_reg = update_special_reg_field(l_user_reg,
- type, value, fields[j].width, fields[j].shift,
- fields[j].sign);
-
- /* Update the kernel ID register view */
- kern_reg = update_special_reg_field(kern_reg,
- fields[j].type, value, fields[j].width,
- fields[j].shift, fields[j].sign);
- }
+ clear_set_special_reg_idx(i, UINT64_MAX, value);
+ }
+}
+
+/*
+ * Updates a special register in all views. This creates a copy of the
+ * register then clears it and sets new bits. It will then compare this
+ * with the old version as if it was the ID register for a new CPU.
+ *
+ * It is intended to let code that disables features, e.g. due to errata,
+ * to clear the user visible field.
+ *
+ * This needs to be called before the HWCAPs are set. If called from a CPU
+ * feature handler this safe to call from CPU_FEAT_EARLY_BOOT. It also needs
+ * to be before link_elf_late_ireloc is called. As this is called after the
+ * HWCAPs are set the check for these is enough.
+ */
+void
+update_special_reg(u_int reg, uint64_t clear, uint64_t set)
+{
+ MPASS(hwcaps_set == false);
+ /* There is no locking here, so we only support changing this on CPU0 */
+ /* TODO: Add said locking */
+ MPASS(PCPU_GET(cpuid) == 0);
+
+ for (int i = 0; i < nitems(user_regs); i++) {
+ if (user_regs[i].reg != reg)
+ continue;
- CPU_DESC_FIELD(kern_cpu_desc, i) = kern_reg;
- CPU_DESC_FIELD(user_cpu_desc, i) = user_reg;
- CPU_DESC_FIELD(l_user_cpu_desc, i) = l_user_reg;
+ clear_set_special_reg_idx(i, clear, set);
+ return;
}
}
@@ -2757,6 +2807,11 @@ identify_cpu_sysinit(void *dummy __unused)
prev_desc = desc;
}
+#ifdef INVARIANTS
+ /* Check we dont update the special registers after this point */
+ hwcaps_set = true;
+#endif
+
/* Find the values to export to userspace as AT_HWCAP and AT_HWCAP2 */
parse_cpu_features(true, &user_cpu_desc, &elf_hwcap, &elf_hwcap2);
parse_cpu_features(true, &l_user_cpu_desc, &linux_elf_hwcap,
diff --git a/sys/arm64/include/cpu.h b/sys/arm64/include/cpu.h
index 91ed3634c6d8..df38c69fdb30 100644
--- a/sys/arm64/include/cpu.h
+++ b/sys/arm64/include/cpu.h
@@ -232,6 +232,7 @@ void ptrauth_mp_start(uint64_t);
/* Functions to read the sanitised view of the special registers */
void update_special_regs(u_int);
+void update_special_reg(u_int reg, uint64_t, uint64_t);
bool extract_user_id_field(u_int, u_int, uint8_t *);
bool get_kernel_reg(u_int, uint64_t *);
bool get_kernel_reg_masked(u_int, uint64_t *, uint64_t);