bc6bd20ee3
In CPUID registers exposed to userspace, some registers were missing and some fields were not exposed. This patch aligns exposed ID registers and their fields with what the upstream kernel currently exposes. Specifically, the following new ID registers/fields are exposed to userspace: ID_AA64PFR1_EL1.BT: bits 3-0 ID_AA64PFR1_EL1.MTE: bits 11-8 ID_AA64PFR1_EL1.SME: bits 27-24 ID_AA64ZFR0_EL1.SVEver: bits 3-0 ID_AA64ZFR0_EL1.AES: bits 7-4 ID_AA64ZFR0_EL1.BitPerm: bits 19-16 ID_AA64ZFR0_EL1.BF16: bits 23-20 ID_AA64ZFR0_EL1.SHA3: bits 35-32 ID_AA64ZFR0_EL1.SM4: bits 43-40 ID_AA64ZFR0_EL1.I8MM: bits 47-44 ID_AA64ZFR0_EL1.F32MM: bits 55-52 ID_AA64ZFR0_EL1.F64MM: bits 59-56 ID_AA64SMFR0_EL1.F32F32: bit 32 ID_AA64SMFR0_EL1.B16F32: bit 34 ID_AA64SMFR0_EL1.F16F32: bit 35 ID_AA64SMFR0_EL1.I8I32: bits 39-36 ID_AA64SMFR0_EL1.F64F64: bit 48 ID_AA64SMFR0_EL1.I16I64: bits 55-52 ID_AA64SMFR0_EL1.FA64: bit 63 ID_AA64MMFR0_EL1.ECV: bits 63-60 ID_AA64MMFR1_EL1.AFP: bits 47-44 ID_AA64MMFR2_EL1.AT: bits 35-32 ID_AA64ISAR0_EL1.RNDR: bits 63-60 ID_AA64ISAR1_EL1.FRINTTS: bits 35-32 ID_AA64ISAR1_EL1.BF16: bits 47-44 ID_AA64ISAR1_EL1.DGH: bits 51-48 ID_AA64ISAR1_EL1.I8MM: bits 55-52 ID_AA64ISAR2_EL1.WFxT: bits 3-0 ID_AA64ISAR2_EL1.RPRES: bits 7-4 ID_AA64ISAR2_EL1.GPA3: bits 11-8 ID_AA64ISAR2_EL1.APA3: bits 15-12 The code is also refactored to use symbolic names for ID register fields for better readability and maintainability. The test case in tests/tcg/aarch64/sysregs.c is also updated to match the intended behavior. Signed-off-by: Zhuojia Shen <chaosdefinition@hotmail.com> Message-id: DS7PR12MB6309FB585E10772928F14271ACE79@DS7PR12MB6309.namprd12.prod.outlook.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> [PMM: use Sn_n_Cn_Cn_n syntax to work with older assemblers that don't recognize id_aa64isar2_el1 and id_aa64mmfr2_el1] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
185 lines
6.2 KiB
C
185 lines
6.2 KiB
C
/*
|
|
* Check emulated system register access for linux-user mode.
|
|
*
|
|
* See: https://www.kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
|
|
*
|
|
* Copyright (c) 2019 Linaro
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include <asm/hwcap.h>
|
|
#include <stdio.h>
|
|
#include <sys/auxv.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
|
|
#ifndef HWCAP_CPUID
|
|
#define HWCAP_CPUID (1 << 11)
|
|
#endif
|
|
|
|
/*
|
|
* Older assemblers don't recognize newer system register names,
|
|
* but we can still access them by the Sn_n_Cn_Cn_n syntax.
|
|
*/
|
|
#define SYS_ID_AA64ISAR2_EL1 S3_0_C0_C6_2
|
|
#define SYS_ID_AA64MMFR2_EL1 S3_0_C0_C7_2
|
|
|
|
int failed_bit_count;
|
|
|
|
/* Read and print system register `id' value */
|
|
#define get_cpu_reg(id) ({ \
|
|
unsigned long __val = 0xdeadbeef; \
|
|
asm("mrs %0, "#id : "=r" (__val)); \
|
|
printf("%-20s: 0x%016lx\n", #id, __val); \
|
|
__val; \
|
|
})
|
|
|
|
/* As above but also check no bits outside of `mask' are set*/
|
|
#define get_cpu_reg_check_mask(id, mask) ({ \
|
|
unsigned long __cval = get_cpu_reg(id); \
|
|
unsigned long __extra = __cval & ~mask; \
|
|
if (__extra) { \
|
|
printf("%-20s: 0x%016lx\n", " !!extra bits!!", __extra); \
|
|
failed_bit_count++; \
|
|
} \
|
|
})
|
|
|
|
/* As above but check RAZ */
|
|
#define get_cpu_reg_check_zero(id) ({ \
|
|
unsigned long __val = 0xdeadbeef; \
|
|
asm("mrs %0, "#id : "=r" (__val)); \
|
|
if (__val) { \
|
|
printf("%-20s: 0x%016lx (not RAZ!)\n", #id, __val); \
|
|
failed_bit_count++; \
|
|
} \
|
|
})
|
|
|
|
/* Chunk up mask into 63:48, 47:32, 31:16, 15:0 to ease counting */
|
|
#define _m(a, b, c, d) (0x ## a ## b ## c ## d ##ULL)
|
|
|
|
bool should_fail;
|
|
int should_fail_count;
|
|
int should_not_fail_count;
|
|
uintptr_t failed_pc[10];
|
|
|
|
void sigill_handler(int signo, siginfo_t *si, void *data)
|
|
{
|
|
ucontext_t *uc = (ucontext_t *)data;
|
|
|
|
if (should_fail) {
|
|
should_fail_count++;
|
|
} else {
|
|
uintptr_t pc = (uintptr_t) uc->uc_mcontext.pc;
|
|
failed_pc[should_not_fail_count++] = pc;
|
|
}
|
|
uc->uc_mcontext.pc += 4;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
/* Hook in a SIGILL handler */
|
|
memset(&sa, 0, sizeof(struct sigaction));
|
|
sa.sa_flags = SA_SIGINFO;
|
|
sa.sa_sigaction = &sigill_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
|
|
if (sigaction(SIGILL, &sa, 0) != 0) {
|
|
perror("sigaction");
|
|
return 1;
|
|
}
|
|
|
|
/* Counter values have been exposed since Linux 4.12 */
|
|
printf("Checking Counter registers\n");
|
|
|
|
get_cpu_reg(ctr_el0);
|
|
get_cpu_reg(cntvct_el0);
|
|
get_cpu_reg(cntfrq_el0);
|
|
|
|
/* HWCAP_CPUID indicates we can read feature registers, since Linux 4.11 */
|
|
if (!(getauxval(AT_HWCAP) & HWCAP_CPUID)) {
|
|
printf("CPUID registers unavailable\n");
|
|
return 1;
|
|
} else {
|
|
printf("Checking CPUID registers\n");
|
|
}
|
|
|
|
/*
|
|
* Some registers only expose some bits to user-space. Anything
|
|
* that is IMPDEF is exported as 0 to user-space. The _mask checks
|
|
* assert no extra bits are set.
|
|
*
|
|
* This check is *not* comprehensive as some fields are set to
|
|
* minimum valid fields - for the purposes of this check allowed
|
|
* to have non-zero values.
|
|
*/
|
|
get_cpu_reg_check_mask(id_aa64isar0_el1, _m(f0ff,ffff,f0ff,fff0));
|
|
get_cpu_reg_check_mask(id_aa64isar1_el1, _m(00ff,f0ff,ffff,ffff));
|
|
get_cpu_reg_check_mask(SYS_ID_AA64ISAR2_EL1, _m(0000,0000,0000,ffff));
|
|
/* TGran4 & TGran64 as pegged to -1 */
|
|
get_cpu_reg_check_mask(id_aa64mmfr0_el1, _m(f000,0000,ff00,0000));
|
|
get_cpu_reg_check_mask(id_aa64mmfr1_el1, _m(0000,f000,0000,0000));
|
|
get_cpu_reg_check_mask(SYS_ID_AA64MMFR2_EL1, _m(0000,000f,0000,0000));
|
|
/* EL1/EL0 reported as AA64 only */
|
|
get_cpu_reg_check_mask(id_aa64pfr0_el1, _m(000f,000f,00ff,0011));
|
|
get_cpu_reg_check_mask(id_aa64pfr1_el1, _m(0000,0000,0f00,0fff));
|
|
/* all hidden, DebugVer fixed to 0x6 (ARMv8 debug architecture) */
|
|
get_cpu_reg_check_mask(id_aa64dfr0_el1, _m(0000,0000,0000,0006));
|
|
get_cpu_reg_check_zero(id_aa64dfr1_el1);
|
|
get_cpu_reg_check_mask(id_aa64zfr0_el1, _m(0ff0,ff0f,00ff,00ff));
|
|
#ifdef HAS_ARMV9_SME
|
|
get_cpu_reg_check_mask(id_aa64smfr0_el1, _m(80f1,00fd,0000,0000));
|
|
#endif
|
|
|
|
get_cpu_reg_check_zero(id_aa64afr0_el1);
|
|
get_cpu_reg_check_zero(id_aa64afr1_el1);
|
|
|
|
get_cpu_reg_check_mask(midr_el1, _m(0000,0000,ffff,ffff));
|
|
/* mpidr sets bit 31, everything else hidden */
|
|
get_cpu_reg_check_mask(mpidr_el1, _m(0000,0000,8000,0000));
|
|
/* REVIDR is all IMPDEF so should be all zeros to user-space */
|
|
get_cpu_reg_check_zero(revidr_el1);
|
|
|
|
/*
|
|
* There are a block of more registers that are RAZ in the rest of
|
|
* the Op0=3, Op1=0, CRn=0, CRm=0,4,5,6,7 space. However for
|
|
* brevity we don't check stuff that is currently un-allocated
|
|
* here. Feel free to add them ;-)
|
|
*/
|
|
|
|
printf("Remaining registers should fail\n");
|
|
should_fail = true;
|
|
|
|
/* Unexposed register access causes SIGILL */
|
|
get_cpu_reg(id_mmfr0_el1);
|
|
get_cpu_reg(id_mmfr1_el1);
|
|
get_cpu_reg(id_mmfr2_el1);
|
|
get_cpu_reg(id_mmfr3_el1);
|
|
|
|
get_cpu_reg(mvfr0_el1);
|
|
get_cpu_reg(mvfr1_el1);
|
|
|
|
if (should_not_fail_count > 0) {
|
|
int i;
|
|
for (i = 0; i < should_not_fail_count; i++) {
|
|
uintptr_t pc = failed_pc[i];
|
|
uint32_t insn = *(uint32_t *) pc;
|
|
printf("insn %#x @ %#lx unexpected FAIL\n", insn, pc);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (failed_bit_count > 0) {
|
|
printf("Extra information leaked to user-space!\n");
|
|
return 1;
|
|
}
|
|
|
|
return should_fail_count == 6 ? 0 : 1;
|
|
}
|