To aid with debugging of MMU code, let's implement mmuinfo for ARMv8, like we already support for ARMv7. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- arch/arm/configs/multi_v8_defconfig | 1 + arch/arm/cpu/Makefile | 2 +- arch/arm/cpu/mmuinfo.c | 2 + arch/arm/cpu/mmuinfo_64.c | 215 ++++++++++++++++++++++++++++ arch/arm/include/asm/mmuinfo.h | 1 + arch/arm/include/asm/sysreg.h | 76 ++++++++++ commands/Kconfig | 6 +- 7 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 arch/arm/cpu/mmuinfo_64.c create mode 100644 arch/arm/include/asm/sysreg.h diff --git a/arch/arm/configs/multi_v8_defconfig b/arch/arm/configs/multi_v8_defconfig index 62afe3829350..d30158d7f880 100644 --- a/arch/arm/configs/multi_v8_defconfig +++ b/arch/arm/configs/multi_v8_defconfig @@ -57,6 +57,7 @@ CONFIG_LONGHELP=y CONFIG_CMD_IOMEM=y CONFIG_CMD_IMD=y CONFIG_CMD_MEMINFO=y +CONFIG_CMD_ARM_MMUINFO=y CONFIG_CMD_REGULATOR=y CONFIG_CMD_MMC_EXTCSD=y CONFIG_CMD_POLLER=y diff --git a/arch/arm/cpu/Makefile b/arch/arm/cpu/Makefile index a271de2c1f38..5baff2fad087 100644 --- a/arch/arm/cpu/Makefile +++ b/arch/arm/cpu/Makefile @@ -27,7 +27,7 @@ obj-$(CONFIG_ARM_PSCI_CLIENT) += psci-client.o # Any variants can be called as start-armxyz.S # obj-$(CONFIG_CMD_ARM_CPUINFO) += cpuinfo.o -obj-$(CONFIG_MMUINFO) += mmuinfo.o mmuinfo_32.o +obj-$(CONFIG_MMUINFO) += mmuinfo.o mmuinfo_$(S64_32).o obj-$(CONFIG_OFDEVICE) += dtb.o ifeq ($(CONFIG_MMU),) diff --git a/arch/arm/cpu/mmuinfo.c b/arch/arm/cpu/mmuinfo.c index 49e393149b69..413f2f337e95 100644 --- a/arch/arm/cpu/mmuinfo.c +++ b/arch/arm/cpu/mmuinfo.c @@ -12,6 +12,8 @@ int mmuinfo(void *addr) { + if (IS_ENABLED(CONFIG_CPU_V8)) + return mmuinfo_v8(addr); if (IS_ENABLED(CONFIG_CPU_V7) && cpu_architecture() == CPU_ARCH_ARMv7) return mmuinfo_v7(addr); diff --git a/arch/arm/cpu/mmuinfo_64.c b/arch/arm/cpu/mmuinfo_64.c new file mode 100644 index 000000000000..de4945f43e8e --- /dev/null +++ b/arch/arm/cpu/mmuinfo_64.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx>, Pengutronix +/* + * mmuinfo_64.c - Show MMU/cache information via AT instruction + */ + +#include <common.h> +#include <asm/mmuinfo.h> +#include <asm/system.h> +#include <asm/sysreg.h> +#include <linux/bitfield.h> + +#define at_par(reg, addr) ({ \ + asm volatile("at " reg ", %0\n" :: "r" (addr)); \ + isb(); \ + read_sysreg_par(); \ +}) + +#define BITS(from, to, val) FIELD_GET(GENMASK(from, to), val) + +static const char *decode_devmem_attr(u8 attr) +{ + switch (attr & ~0x1) { + case 0b00000000: + return "0b0000 Device-nGnRnE memory"; + case 0b00000100: + return "0b0100 Device-nGnRE memory"; + case 0b00001000: + return "0b1000 Device-nGRE memory"; + case 0b00001100: + return "0b1100 Device-GRE memory"; + default: + return "unknown"; + }; +} + +static char *cache_attr[] = { + "0b0000 Wrongly decoded", + "0b0001 Write-Through Transient, Write-Allocate, no Read-Allocate", + "0b0010 Write-Through Transient, no Write-Allocate", + "0b0011 Write-Through Transient, Write-Allocate", + "0b0100 Non-Cacheable", + "0b0101 Write-Back Transient, Write-Allocate, no Read-Allocate", + "0b0110 Write-Back Transient, no Write-Allocate", + "0b0111 Write-Back Transient, Write-Allocate", + "0b1000 Write-Through Non-transient, no Write-Allocate no Read-Allocate", + "0b1001 Write-Through Non-transient, Write-Allocate no Read-Allocate", + "0b1010 Write-Through Non-transient, no Write-Allocate", + "0b1011 Write-Through Non-transient, Write-Allocate", + "0b1100 Write-Back Non-transient, no Write-Allocate no Read-Allocate", + "0b1101 Write-Back Non-transient, Write-Allocate no Read-Allocate", + "0b1110 Write-Back Non-transient, no Write-Allocate", + "0b1111 Write-Back Non-transient, Write-Allocate", +}; + +static char *share_attr[] = { + "0b00 Non-Shareable", + "0b01 Reserved", + "0b10 Outer Shareable", + "0b11 Inner Shareable", +}; + +static char *stage_fault[] = { + "stage 1 translation", + "stage 2 translation", +}; + +static char *fault_status_leveled[] = { + "Address size fault", /* of translation or translation table base register */ + "Translation fault", + "Access flag fault", + "Permission fault", + "Synchronous External abort", /* level -1 */ + "Synchronous External abort", /* on translation table walk or hardware update of translation table */ + "Synchronous parity or ECC error", /* level -1 */ + "Synchronous parity or ECC error", /* on memory access on translation table walk or hardware update of translation table */ +}; + +static const char *decode_fault_status_level(u8 fst) +{ + if (!(fst & BIT(5))) + return ""; + + switch (BITS(5, 0, fst)) { + case 0b010011: + case 0b011011: + return ", level -1"; + } + + switch (BITS(1, 0, fst)) { + case 0b00: + return ", level 0"; + case 0b01: + return ", level 1"; + case 0b10: + return ", level 2"; + case 0b11: + return ", level 3"; + } + + BUG(); +} + +static const char *decode_fault_status(u8 fst) +{ + + switch (BITS(5, 0, fst)) { + case 0b101001: /* When FEAT_LPA2 is implemented */ + return "Address size fault, level -1"; + case 0b101011: /* When FEAT_LPA2 is implemented */ + return "Translation fault, level -1"; + case 0b110000: + return "TLB conflict abort"; + case 0b110001: /* When FEAT_HAFDBS is implemented */ + return "Unsupported atomic hardware update fault"; + case 0b111101: /* When EL1 is capable of using AArch32 */ + return "Section Domain fault, from an AArch32 stage 1 EL1&0 " + "translation regime using Short-descriptor translation " + "table format"; + case 0b111110: /* When EL1 is capable of using AArch32 */ + return "Page Domain fault, from an AArch32 stage 1 EL1&0 " + "translation regime using Short-descriptor translation " + "table format"; + default: + if (fst & BIT(5)) + return fault_status_leveled[BITS(4, 2, fst)]; + + return "Reserved"; + } +}; + +static void decode_par(unsigned long par) +{ + u8 devmem_attr = BITS(63, 56, par); + + if (par & 1) { + printf(" Translation aborted [9:8]: because of a fault in the %s%s\n", + stage_fault[BITS(9, 9, par)], + BITS(8, 8, par) ? " during a stage 1 translation table walk" : ""); + printf(" Fault Status Code [6:1]: 0x%02lx (%s%s)\n", BITS(6, 1, par), + decode_fault_status(BITS(6, 1, par)), + decode_fault_status_level(BITS(6, 1, par))); + printf(" Failure [0]: 0x1\n"); + } else { + if ((devmem_attr & 0xf0) && (devmem_attr & 0x0f)) { + printf(" Outer mem. attr. [63:60]: 0x%02lx (%s)\n", BITS(63, 60, par), + cache_attr[BITS(63, 60, par)]); + printf(" Inner mem. attr. [59:56]: 0x%02lx (%s)\n", BITS(59, 56, par), + cache_attr[BITS(59, 56, par)]); + } else if ((devmem_attr & 0b11110010) == 0) { + printf(" Memory attr. [63:56]: 0x%02x (%s)\n", + devmem_attr, decode_devmem_attr(devmem_attr)); + if (devmem_attr & 1) + printf(" (XS == 0 if FEAT_XS implemented)\n"); + } else if (devmem_attr == 0b01000000) { + printf(" Outer mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Non-Cacheable"); + printf(" Inner mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Non-Cacheable"); + printf(" (XS == 0 if FEAT_XS implemented)\n"); + } else if (devmem_attr == 0b10100000) { + printf(" Outer mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Write-Through, No Write-Allocate"); + printf(" Inner mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Write-Through"); + printf(" (XS == 0 if FEAT_XS implemented)\n"); + } else if (devmem_attr == 0b11110000) { + printf(" Outer mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Write-Back"); + printf(" Inner mem. attr. [63:56]: 0x%02lx (%s)\n", BITS(63, 56, par), + "Write-Back, Write-Allocate, Non-transient"); + printf(" (if FEAT_MTE2 implemented)\n"); + } + printf(" Physical Address [51:12]: 0x%08lx\n", par & GENMASK(51, 12)); + printf(" Non-Secure [9]: 0x%lx\n", BITS(9, 9, par)); + printf(" Shareability attr. [8:7]: 0x%02lx (%s)\n", BITS(8, 7, par), + share_attr[BITS(8, 7, par)]); + printf(" Failure [0]: 0x0\n"); + } +} + +int mmuinfo_v8(void *_addr) +{ + unsigned long addr = (unsigned long)_addr; + unsigned long priv_read, priv_write; + + switch (current_el()) { + case 3: + priv_read = at_par("s1e3r", addr); + priv_write = at_par("s1e3w", addr); + break; + case 2: + priv_read = at_par("s1e2r", addr); + priv_write = at_par("s1e2w", addr); + break; + case 1: + priv_read = at_par("s1e1r", addr); + priv_write = at_par("s1e1w", addr); + break; + case 0: + priv_read = at_par("s1e0r", addr); + priv_write = at_par("s1e0w", addr); + break; + default: + return -EINVAL; + } + + printf("PAR result for 0x%08lx: \n", addr); + printf(" privileged read: 0x%08lx\n", priv_read); + decode_par(priv_read); + printf(" privileged write: 0x%08lx\n", priv_write); + decode_par(priv_write); + + return 0; +} diff --git a/arch/arm/include/asm/mmuinfo.h b/arch/arm/include/asm/mmuinfo.h index bc17bf8982ab..3005c388b967 100644 --- a/arch/arm/include/asm/mmuinfo.h +++ b/arch/arm/include/asm/mmuinfo.h @@ -4,5 +4,6 @@ #define __ARM_ASM_MMUINFO_H__ int mmuinfo_v7(void *addr); +int mmuinfo_v8(void *addr); #endif diff --git a/arch/arm/include/asm/sysreg.h b/arch/arm/include/asm/sysreg.h new file mode 100644 index 000000000000..7d567e08d8b7 --- /dev/null +++ b/arch/arm/include/asm/sysreg.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Macros for accessing system registers with older binutils. + * + * Copyright (C) 2014 ARM Ltd. + * Author: Catalin Marinas <catalin.marinas@xxxxxxx> + */ + +#ifndef __ASM_SYSREG_H +#define __ASM_SYSREG_H + +#include <asm/system.h> +#include <linux/stringify.h> + +/* + * Unlike read_cpuid, calls to read_sysreg are never expected to be + * optimized away or replaced with synthetic values. + */ +#define read_sysreg(r) ({ \ + u64 __val; \ + asm volatile("mrs %0, " __stringify(r) : "=r" (__val)); \ + __val; \ +}) + +/* + * The "Z" constraint normally means a zero immediate, but when combined with + * the "%x0" template means XZR. + */ +#define write_sysreg(v, r) do { \ + u64 __val = (u64)(v); \ + asm volatile("msr " __stringify(r) ", %x0" \ + : : "rZ" (__val)); \ +} while (0) + +/* + * For registers without architectural names, or simply unsupported by + * GAS. + */ +#define read_sysreg_s(r) ({ \ + u64 __val; \ + asm volatile(__mrs_s("%0", r) : "=r" (__val)); \ + __val; \ +}) + +#define write_sysreg_s(v, r) do { \ + u64 __val = (u64)(v); \ + asm volatile(__msr_s(r, "%x0") : : "rZ" (__val)); \ +} while (0) + +/* + * Modify bits in a sysreg. Bits in the clear mask are zeroed, then bits in the + * set mask are set. Other bits are left as-is. + */ +#define sysreg_clear_set(sysreg, clear, set) do { \ + u64 __scs_val = read_sysreg(sysreg); \ + u64 __scs_new = (__scs_val & ~(u64)(clear)) | (set); \ + if (__scs_new != __scs_val) \ + write_sysreg(__scs_new, sysreg); \ +} while (0) + +#define sysreg_clear_set_s(sysreg, clear, set) do { \ + u64 __scs_val = read_sysreg_s(sysreg); \ + u64 __scs_new = (__scs_val & ~(u64)(clear)) | (set); \ + if (__scs_new != __scs_val) \ + write_sysreg_s(__scs_new, sysreg); \ +} while (0) + +#define read_sysreg_par() ({ \ + u64 par; \ + asm("dmb sy"); \ + par = read_sysreg(par_el1); \ + asm("dmb sy"); \ + par; \ +}) + +#endif /* __ASM_SYSREG_H */ diff --git a/commands/Kconfig b/commands/Kconfig index bc697d52b730..3a43682b2b2c 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -201,13 +201,13 @@ config CMD_MEMINFO config CMD_ARM_MMUINFO bool "mmuinfo command" - depends on CPU_V7 + depends on CPU_V7 || CPU_V8 select MMUINFO help Say yes here to get a mmuinfo command to show some - MMU and cache information using the cp15 registers. + MMU and cache information using the cp15/model-specific registers. - Example: + Example for ARMv7: PAR result for 0x00110000: privileged read: 0x00110090 -- 2.39.2