diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata index 53e28e5b4f92..2a34dceca607 100644 --- a/arch/riscv/Kconfig.errata +++ b/arch/riscv/Kconfig.errata @@ -45,6 +45,17 @@ config ERRATA_MIPS_P8700_PAUSE_OPCODE If you are not using the P8700 processor, say n. +config ERRATA_MIPS_P8700_WFI + bool "Replace WFI with mips.pause for MIPS P8700" + depends on ERRATA_MIPS && 64BIT + default n + help + The RISCV MIPS P8700 has bugs with the WFI instruction. + This errata replaces all WFI instructions with the MIPS + P8700 pause opcode to avoid these issues. + + If you are not using the P8700 processor, say n. + config ERRATA_SIFIVE bool "SiFive errata" depends on RISCV_ALTERNATIVE && 64BIT diff --git a/arch/riscv/errata/mips/errata.c b/arch/riscv/errata/mips/errata.c index ac9a12d0a30c..6e7155527450 100644 --- a/arch/riscv/errata/mips/errata.c +++ b/arch/riscv/errata/mips/errata.c @@ -27,6 +27,20 @@ static inline bool errata_probe_pause(unsigned int stage) return true; } +static inline bool errata_probe_wfi(unsigned int stage) +{ + if (!IS_ENABLED(CONFIG_ERRATA_MIPS_P8700_WFI)) + return false; + + if (!riscv_isa_vendor_extension_available(MIPS_VENDOR_ID, XMIPSEXECTL)) + return false; + + if (stage == RISCV_ALTERNATIVES_EARLY_BOOT) + return false; + + return true; +} + static u32 mips_errata_probe(unsigned int stage) { u32 cpu_req_errata = 0; @@ -34,6 +48,9 @@ static u32 mips_errata_probe(unsigned int stage) if (errata_probe_pause(stage)) cpu_req_errata |= BIT(ERRATA_MIPS_P8700_PAUSE_OPCODE); + if (errata_probe_wfi(stage)) + cpu_req_errata |= BIT(ERRATA_MIPS_P8700_WFI); + return cpu_req_errata; } diff --git a/arch/riscv/include/asm/errata_list.h b/arch/riscv/include/asm/errata_list.h index 6694b5ccdcf8..6fdb445c0f45 100644 --- a/arch/riscv/include/asm/errata_list.h +++ b/arch/riscv/include/asm/errata_list.h @@ -25,6 +25,16 @@ ALTERNATIVE(__stringify(RISCV_PTR do_page_fault), \ __stringify(RISCV_PTR sifive_cip_453_page_fault_trp), \ SIFIVE_VENDOR_ID, ERRATA_SIFIVE_CIP_453, \ CONFIG_ERRATA_SIFIVE_CIP_453) + +#ifdef CONFIG_ERRATA_MIPS_P8700_WFI +#define ALT_WFI \ +ALTERNATIVE("wfi; .rept 7; nop; .endr;", \ + ".rept 8; .4byte 0x00501013; .endr;", MIPS_VENDOR_ID, \ + ERRATA_MIPS_P8700_WFI, CONFIG_ERRATA_MIPS_P8700_WFI) +#else +#define ALT_WFI wfi +#endif + #else /* !__ASSEMBLER__ */ #define ALT_SFENCE_VMA_ASID(asid) \ @@ -53,6 +63,33 @@ asm(ALTERNATIVE( \ : /* no inputs */ \ : "memory") +#ifdef CONFIG_ERRATA_MIPS_P8700_WFI +/* + * A WFI instruction after a mispredicted branch can result in erroneous address translation + * on the MIPS P8700. + * The number of MIPS_PAUSE instructions required was determined by measuring + * Coremark performance on a one-core, two-hart configuration. Eight PAUSE instructions matched + * the latency of a single WFI instruction. + * Usually, the maximum number of cycles per PAUSE instruction is 63. Exceptionally, if the hart's + * LR reservation bit is set, the maximum number of cycles is extended to 255 + * (and the hart is woken up once it is cleared). This case should not affect the patched sites. + */ +#define ALT_RISCV_WFI() \ +asm volatile(ALTERNATIVE( \ + "wfi\n" /* Original RISC-V wfi insn */ \ + __nops(7), \ + ".rept 8;" MIPS_PAUSE ".endr;\n", /* Replacement: mips.pause for P8700 */ \ + MIPS_VENDOR_ID, /* Vendor ID to match */ \ + ERRATA_MIPS_P8700_WFI, /* patch_id */ \ + CONFIG_ERRATA_MIPS_P8700_WFI) \ + : /* no outputs */ \ + : /* no inputs */ \ + : "memory") +#else +#define ALT_RISCV_WFI() \ + __asm__ __volatile__ ("wfi") +#endif + /* * _val is marked as "will be overwritten", so need to set it to 0 * in the default case. diff --git a/arch/riscv/include/asm/errata_list_vendors.h b/arch/riscv/include/asm/errata_list_vendors.h index c62a82f60468..02b5ab0d23a8 100644 --- a/arch/riscv/include/asm/errata_list_vendors.h +++ b/arch/riscv/include/asm/errata_list_vendors.h @@ -23,8 +23,9 @@ #endif #ifdef CONFIG_ERRATA_MIPS -#define ERRATA_MIPS_P8700_PAUSE_OPCODE 0 -#define ERRATA_MIPS_NUMBER 1 +#define ERRATA_MIPS_P8700_PAUSE_OPCODE 0 +#define ERRATA_MIPS_P8700_WFI 1 +#define ERRATA_MIPS_NUMBER 2 #endif #endif /* ASM_ERRATA_LIST_VENDORS_H */ diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h index 815715c67f94..3686f871c8ed 100644 --- a/arch/riscv/include/asm/processor.h +++ b/arch/riscv/include/asm/processor.h @@ -17,6 +17,7 @@ #include #include #include +#include #define arch_get_mmap_end(addr, len, flags) \ ({ \ @@ -187,7 +188,7 @@ extern unsigned long __get_wchan(struct task_struct *p); static inline void wait_for_interrupt(void) { - __asm__ __volatile__ ("wfi"); + ALT_RISCV_WFI(); } extern phys_addr_t dma32_phys_limit; diff --git a/arch/riscv/kernel/head.S b/arch/riscv/kernel/head.S index f6a8ca49e627..1631fd6bc786 100644 --- a/arch/riscv/kernel/head.S +++ b/arch/riscv/kernel/head.S @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include "efi-header.S" __HEAD @@ -189,7 +191,7 @@ secondary_start_sbi: * - receive an early trap, before setup_trap_vector finished * - fail in smp_callin(), as a successful one wouldn't return */ - wfi + ALT_WFI j .Lsecondary_park .align 2