[PATCH v2 5/6] riscv: errata: picoheart: Add workaround for cbo.clean errata
Yicong Yang
yang.yicong at picoheart.com
Tue Jul 28 02:46:26 PDT 2026
cbo.clean may fail to write back the cacheline in a few very corner
cases on the affected Picoheart SoCs. Since cbo.flush is not affected,
the problem could be solved by promoting cbo.clean to cbo.flush to
keep the data coherent. So Implement and register Picoheart specific
riscv_nonstd_cache_ops for all the kernel space cache maintenance
operations. Implement riscv_nonstd_cache_ops->wback with cbo.clean
while other callbacks with corresponding CBO operations.
This could solve the issue in the kernel space. For userspace, clear
the xenvcfg.CBCFE to trap the cbo.{clean, flush} and emulate
the cbo.clean with cbo.flush in the kernel space to avoid the issue.
Signed-off-by: Yicong Yang <yang.yicong at picoheart.com>
---
arch/riscv/Kconfig.errata | 12 ++
arch/riscv/errata/picoheart/errata.c | 159 +++++++++++++++++++++++++++
arch/riscv/include/asm/errata_list.h | 25 ++++-
arch/riscv/kernel/cpufeature.c | 3 +
arch/riscv/kernel/traps.c | 4 +
5 files changed, 202 insertions(+), 1 deletion(-)
diff --git a/arch/riscv/Kconfig.errata b/arch/riscv/Kconfig.errata
index 374d7fcf7936..8ce5c0314321 100644
--- a/arch/riscv/Kconfig.errata
+++ b/arch/riscv/Kconfig.errata
@@ -164,4 +164,16 @@ config ERRATA_PICOHEART
Otherwise, please say "N" here to avoid unnecessary overhead.
+config ERRATA_PICOHEART_CBO_CLEAN
+ bool "Apply Picoheart cbo.clean errata"
+ depends on ERRATA_PICOHEART
+ select RISCV_NONSTANDARD_CACHE_OPS
+ default y
+ help
+ This will apply the cbo.clean errata workaround on the affected
+ Picoheart SoCs where cbo.clean instructions may fail to write back
+ the cacheline in few corner cases.
+
+ If you don't know what to do here, say "Y".
+
endmenu # "CPU errata selection"
diff --git a/arch/riscv/errata/picoheart/errata.c b/arch/riscv/errata/picoheart/errata.c
index 53b690878f78..21480d19c084 100644
--- a/arch/riscv/errata/picoheart/errata.c
+++ b/arch/riscv/errata/picoheart/errata.c
@@ -5,20 +5,179 @@
* Author: Yicong Yang <yang.yicong at picoheart.com>
*/
+#include <linux/align.h>
#include <linux/cacheflush.h>
#include <linux/cleanup.h>
+#include <linux/jump_label.h>
#include <linux/kernel.h>
#include <linux/memory.h>
+#include <linux/mm_types.h>
+#include <linux/mmap_lock.h>
+#include <linux/signal.h>
#include <linux/string.h>
+#include <asm/asm-extable.h>
#include <asm/alternative.h>
+#include <asm/cpufeature.h>
+#include <asm/dma-noncoherent.h>
#include <asm/errata_list.h>
+#include <asm/insn.h>
+#include <asm/insn-def.h>
#include <asm/text-patching.h>
+#include <asm/uaccess.h>
#include <asm/vendor_extensions.h>
#include <asm/vendorid_list.h>
+#ifdef CONFIG_ERRATA_PICOHEART_CBO_CLEAN
+
+static bool insn_is_cbo_clean_flush(u32 insn_buf)
+{
+ u32 rd, opcode, funct3, funct12;
+
+ /* CBO instructions don't have compressed variants */
+ if (GET_INSN_LENGTH(insn_buf) != 4)
+ return false;
+
+ rd = RV_EXTRACT_RD_REG(insn_buf);
+ opcode = insn_buf & __INSN_OPCODE_MASK;
+ funct3 = insn_buf & RV_INSN_FUNCT3_MASK;
+ funct12 = insn_buf & RV_INSN_FUNCT12_MASK;
+
+ if (rd != 0 || opcode != RVG_OPCODE_MISC_MEM ||
+ funct3 != RV_ENCODE_FUNCT3(CBO) ||
+ (funct12 != RV_ENCODE_FUNCT12(CBO_CLEAN) &&
+ funct12 != RV_ENCODE_FUNCT12(CBO_FLUSH)))
+ return false;
+
+ return true;
+}
+
+static int cbo_clean_fault_sicode(unsigned long addr)
+{
+ struct mm_struct *mm = current->mm;
+ struct vm_area_struct *vma;
+
+ guard(mmap_read_lock)(mm);
+ vma = vma_lookup(mm, addr);
+ if (!vma)
+ return SEGV_MAPERR;
+
+ return SEGV_ACCERR;
+}
+
+bool riscv_picoheart_illegal_insn_handler(struct pt_regs *regs)
+{
+ unsigned long insn = regs->badaddr;
+ unsigned long epc = regs->epc;
+ void __user *line_addr;
+ int ret = 0;
+ u64 addr;
+ u32 rs1;
+
+ if (!insn) {
+ if (get_insn(regs, epc, &insn))
+ return false;
+ }
+
+ if (!insn_is_cbo_clean_flush(insn))
+ return false;
+
+ rs1 = RV_EXTRACT_RS1_REG(insn);
+ addr = rs1 ? ((unsigned long *)regs)[rs1] : 0;
+ line_addr = (void __user *)ALIGN_DOWN(addr, riscv_cbom_block_size);
+
+ /*
+ * Check if the target address is within the valid userspace
+ * address range. Otherwise update the bad_cause to match the
+ * store page fault. See the comment below.
+ */
+ if (!access_ok(line_addr, riscv_cbom_block_size)) {
+ current->thread.bad_cause = EXC_STORE_PAGE_FAULT;
+ goto err_map;
+ }
+
+ __enable_user_access();
+ asm volatile("\n"
+ "1: \n"
+ CBO_FLUSH(%[addr])
+ "2: \n"
+ _ASM_EXTABLE_UACCESS_ERR(1b, 2b, %[err])
+ : [err] "+r" (ret)
+ : [addr] "r" (untagged_addr(addr))
+ : "memory");
+ __disable_user_access();
+
+ if (ret)
+ goto err_map;
+
+ regs->epc += GET_INSN_LENGTH(insn);
+ return true;
+
+err_map:
+ /*
+ * We'll reach here if the target address is invalid.
+ * cbo.{flush ,clean} on invalid address will cause
+ * store page fault, update the cause and badaddr.
+ */
+ regs->badaddr = addr;
+ regs->cause = current->thread.bad_cause;
+
+ /*
+ * In emulation of the cbo.clean, a SIGSEGV rather than
+ * a SIGILL should be issued if the target address is
+ * invalid. Use do_trap() to handle the signal delivery
+ * and related stuffs to make it analogous to the page
+ * fault.
+ */
+ do_trap(regs, SIGSEGV, cbo_clean_fault_sicode(untagged_addr(addr)),
+ untagged_addr(addr));
+
+ return true;
+}
+
+#endif /* CONFIG_ERRATA_PICOHEART_CBO_CLEAN */
+
+static void picoheart_errata_cache_wback(phys_addr_t paddr, size_t size)
+{
+ void *vaddr = phys_to_virt(paddr);
+
+ ALT_CMO_OP(FLUSH, vaddr, size, riscv_cbom_block_size);
+}
+
+/*
+ * The errata only affects the cbo.clean (wback) so use standard operations
+ * for other semantic.
+ */
+struct riscv_nonstd_cache_ops picoheart_errata_cmo_ops = {
+ .wback = &picoheart_errata_cache_wback,
+};
+
+DEFINE_STATIC_KEY_FALSE(has_picoheart_cbo_clean_errata);
+
+static void picoheart_errata_probe_cbo_clean(unsigned int stage,
+ unsigned long archid,
+ unsigned long impid)
+{
+ if (!IS_ENABLED(CONFIG_ERRATA_PICOHEART_CBO_CLEAN))
+ return;
+
+ if (stage != RISCV_ALTERNATIVES_BOOT)
+ return;
+
+ if (!riscv_isa_extension_available(NULL, ZICBOM))
+ return;
+
+ if (archid != 0x804a555049544552 || impid != 0x100)
+ return;
+
+ riscv_noncoherent_supported();
+ riscv_noncoherent_register_cache_ops(&picoheart_errata_cmo_ops);
+ static_branch_enable(&has_picoheart_cbo_clean_errata);
+}
+
static u32 picoheart_errata_probe(unsigned int stage, unsigned long archid,
unsigned long impid)
{
+ picoheart_errata_probe_cbo_clean(stage, archid, impid);
return 0;
}
diff --git a/arch/riscv/include/asm/errata_list.h b/arch/riscv/include/asm/errata_list.h
index 6694b5ccdcf8..7e31acae4e0a 100644
--- a/arch/riscv/include/asm/errata_list.h
+++ b/arch/riscv/include/asm/errata_list.h
@@ -117,6 +117,29 @@ asm volatile(ALTERNATIVE( \
#define THEAD_C9XX_RV_IRQ_PMU 17
#define THEAD_C9XX_CSR_SCOUNTEROF 0x5c5
-#endif /* __ASSEMBLER__ */
+#ifdef CONFIG_ERRATA_PICOHEART_CBO_CLEAN
+
+#include <linux/jump_label.h>
+
+DECLARE_STATIC_KEY_FALSE(has_picoheart_cbo_clean_errata);
+
+static inline bool riscv_has_picoheart_zicbom_errata(void)
+{
+ return static_branch_unlikely(&has_picoheart_cbo_clean_errata);
+}
+
+bool riscv_picoheart_illegal_insn_handler(struct pt_regs *regs);
+
+#else /* !CONFIG_ERRATA_PICOHEART_CBO_CLEAN */
+
+static inline bool riscv_has_picoheart_zicbom_errata(void) { return false; }
+static inline bool riscv_picoheart_illegal_insn_handler(struct pt_regs *regs)
+{
+ return false;
+}
+
+#endif /* CONFIG_ERRATA_PICOHEART_CBO_CLEAN */
+
+#endif /* __ASSEMBLY__ */
#endif
diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
index 1fe647e03515..160809d7268d 100644
--- a/arch/riscv/kernel/cpufeature.c
+++ b/arch/riscv/kernel/cpufeature.c
@@ -1208,6 +1208,9 @@ void __init riscv_user_isa_enable(void)
if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_ZICBOP) &&
any_cpu_has_zicbop)
pr_warn("Zicbop disabled as it is unavailable on some harts\n");
+
+ if (riscv_has_picoheart_zicbom_errata())
+ current->thread.envcfg &= ~ENVCFG_CBCFE;
}
#ifdef CONFIG_RISCV_ALTERNATIVE
diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c
index 8c62c771a656..8d0f8d3446b6 100644
--- a/arch/riscv/kernel/traps.c
+++ b/arch/riscv/kernel/traps.c
@@ -177,6 +177,10 @@ asmlinkage __visible __trap_section void do_trap_insn_illegal(struct pt_regs *re
local_irq_enable();
handled = riscv_v_first_use_handler(regs);
+
+ if (!handled && riscv_has_picoheart_zicbom_errata())
+ handled = riscv_picoheart_illegal_insn_handler(regs);
+
if (!handled)
do_trap_error(regs, SIGILL, ILL_ILLOPC, regs->epc,
"Oops - illegal instruction");
--
2.50.1 (Apple Git-155)
More information about the linux-riscv
mailing list