[PATCH v2 2/5] riscv: Introduce alternative mechanism to apply errata solution
Anup Patel
anup at brainfault.org
Wed Mar 17 11:39:01 GMT 2021
On Wed, Mar 17, 2021 at 3:04 PM Vincent Chen <vincent.chen at sifive.com> wrote:
>
> Introduce the "alternative" mechanism from ARM64 and x86 to apply the CPU
> vendors' errata solution at runtime. The main purpose of this patch is
> to provide a framework. Therefore, the implementation is quite basic for
> now so that some scenarios could not use this schemei, such as patching
> code to a module, relocating the patching code and heterogeneous CPU
> topology.
>
> Users could use the macro ALTERNATIVE to apply an errata to the existing
> code flow. In the macro ALTERNATIVE, users need to specify the manufacturer
> information(vendorid, archid, and impid) for this errata. Therefore, kernel
> will know this errata is suitable for which CPU core. During the booting
> procedure, kernel will select the errata required by the CPU core and then
> patch it. It means that the kernel only applies the errata to the specified
> CPU core. In this case, the vendor's errata does not affect each other at
> runtime. The above patching procedure only occurs during the booting phase,
> so we only take the overhead of the "alternative" mechanism once.
>
> This "alternative" mechanism is enabled by default to ensure that all
> required errata will be applied. However, users can disable this feature by
> the Kconfig "CONFIG_RISCV_ERRATA_ALTERNATIVE".
>
> Signed-off-by: Vincent Chen <vincent.chen at sifive.com>
Looks good to me.
Reviewed-by: Anup Patel <anup at brainfault.org>
Regards,
Anup
> ---
> arch/riscv/Kconfig | 1 +
> arch/riscv/Kconfig.erratas | 12 +++
> arch/riscv/Makefile | 1 +
> arch/riscv/errata/Makefile | 1 +
> arch/riscv/errata/alternative.c | 69 ++++++++++++++
> arch/riscv/include/asm/alternative-macros.h | 142 ++++++++++++++++++++++++++++
> arch/riscv/include/asm/alternative.h | 36 +++++++
> arch/riscv/include/asm/asm.h | 1 +
> arch/riscv/include/asm/csr.h | 3 +
> arch/riscv/include/asm/errata_list.h | 12 +++
> arch/riscv/include/asm/sections.h | 1 +
> arch/riscv/include/asm/vendorid_list.h | 10 ++
> arch/riscv/kernel/smpboot.c | 4 +
> arch/riscv/kernel/vmlinux.lds.S | 7 ++
> 14 files changed, 300 insertions(+)
> create mode 100644 arch/riscv/Kconfig.erratas
> create mode 100644 arch/riscv/errata/Makefile
> create mode 100644 arch/riscv/errata/alternative.c
> create mode 100644 arch/riscv/include/asm/alternative-macros.h
> create mode 100644 arch/riscv/include/asm/alternative.h
> create mode 100644 arch/riscv/include/asm/errata_list.h
> create mode 100644 arch/riscv/include/asm/vendorid_list.h
>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index a998babc1237..2e26251fe1f2 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -204,6 +204,7 @@ config LOCKDEP_SUPPORT
> def_bool y
>
> source "arch/riscv/Kconfig.socs"
> +source "arch/riscv/Kconfig.erratas"
>
> menu "Platform type"
>
> diff --git a/arch/riscv/Kconfig.erratas b/arch/riscv/Kconfig.erratas
> new file mode 100644
> index 000000000000..4d0bafc536df
> --- /dev/null
> +++ b/arch/riscv/Kconfig.erratas
> @@ -0,0 +1,12 @@
> +menu "CPU errata selection"
> +
> +config RISCV_ERRATA_ALTERNATIVE
> + bool "RISC-V alternative scheme"
> + default y
> + help
> + This Kconfig allows the kernel to automatically patch the
> + errata required by the execution platform at run time. The
> + code patching is performed once in the boot stages. It means
> + that the overhead from this mechanism is just taken once.
> +
> +endmenu
> diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile
> index 1368d943f1f3..1f5c03082976 100644
> --- a/arch/riscv/Makefile
> +++ b/arch/riscv/Makefile
> @@ -87,6 +87,7 @@ KBUILD_IMAGE := $(boot)/Image.gz
> head-y := arch/riscv/kernel/head.o
>
> core-y += arch/riscv/
> +core-$(CONFIG_RISCV_ERRATA_ALTERNATIVE) += arch/riscv/errata/
>
> libs-y += arch/riscv/lib/
> libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a
> diff --git a/arch/riscv/errata/Makefile b/arch/riscv/errata/Makefile
> new file mode 100644
> index 000000000000..43e6d5424367
> --- /dev/null
> +++ b/arch/riscv/errata/Makefile
> @@ -0,0 +1 @@
> +obj-y += alternative.o
> diff --git a/arch/riscv/errata/alternative.c b/arch/riscv/errata/alternative.c
> new file mode 100644
> index 000000000000..8efa60ad69b7
> --- /dev/null
> +++ b/arch/riscv/errata/alternative.c
> @@ -0,0 +1,69 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * alternative runtime patching
> + * inspired by the ARM64 and x86 version
> + *
> + * Copyright (C) 2021 Sifive.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/cpu.h>
> +#include <linux/uaccess.h>
> +#include <asm/alternative.h>
> +#include <asm/sections.h>
> +#include <asm/vendorid_list.h>
> +#include <asm/sbi.h>
> +#include <asm/csr.h>
> +
> +static struct cpu_manufacturer_info_t {
> + unsigned long vendor_id;
> + unsigned long arch_id;
> + unsigned long imp_id;
> +} cpu_mfr_info;
> +
> +static void (*vendor_patch_func)(struct alt_entry *begin, struct alt_entry *end,
> + unsigned long archid, unsigned long impid);
> +
> +static inline void __init riscv_fill_cpu_mfr_info(void)
> +{
> +#ifdef CONFIG_RISCV_M_MODE
> + cpu_mfr_info.vendor_id = csr_read(CSR_MVENDORID);
> + cpu_mfr_info.arch_id = csr_read(CSR_MARCHID);
> + cpu_mfr_info.imp_id = csr_read(CSR_MIMPID);
> +#else
> + cpu_mfr_info.vendor_id = sbi_get_mvendorid();
> + cpu_mfr_info.arch_id = sbi_get_marchid();
> + cpu_mfr_info.imp_id = sbi_get_mimpid();
> +#endif
> +}
> +
> +static void __init init_alternative(void)
> +{
> + riscv_fill_cpu_mfr_info();
> +
> + switch (cpu_mfr_info.vendor_id) {
> + default:
> + vendor_patch_func = NULL;
> + }
> +}
> +
> +/*
> + * This is called very early in the boot process (directly after we run
> + * a feature detect on the boot CPU). No need to worry about other CPUs
> + * here.
> + */
> +void __init apply_boot_alternatives(void)
> +{
> + /* If called on non-boot cpu things could go wrong */
> + WARN_ON(smp_processor_id() != 0);
> +
> + init_alternative();
> +
> + if (!vendor_patch_func)
> + return;
> +
> + vendor_patch_func((struct alt_entry *)__alt_start,
> + (struct alt_entry *)__alt_end,
> + cpu_mfr_info.arch_id, cpu_mfr_info.imp_id);
> +}
> +
> diff --git a/arch/riscv/include/asm/alternative-macros.h b/arch/riscv/include/asm/alternative-macros.h
> new file mode 100644
> index 000000000000..88c08705f64a
> --- /dev/null
> +++ b/arch/riscv/include/asm/alternative-macros.h
> @@ -0,0 +1,142 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __ASM_ALTERNATIVE_MACROS_H
> +#define __ASM_ALTERNATIVE_MACROS_H
> +
> +#ifdef CONFIG_RISCV_ERRATA_ALTERNATIVE
> +
> +#ifdef __ASSEMBLY__
> +
> +.macro ALT_ENTRY oldptr newptr vendor_id errata_id new_len
> + RISCV_PTR \oldptr
> + RISCV_PTR \newptr
> + REG_ASM \vendor_id
> + REG_ASM \new_len
> + .word \errata_id
> +.endm
> +
> +.macro ALT_NEW_CONTENT vendor_id, errata_id, enable = 1, new_c : vararg
> + .if \enable
> + .pushsection .alternative, "a"
> + ALT_ENTRY 886b, 888f, \vendor_id, \errata_id, 889f - 888f
> + .popsection
> + .subsection 1
> +888 :
> + \new_c
> +889 :
> + .previous
> + .org . - (889b - 888b) + (887b - 886b)
> + .org . - (887b - 886b) + (889b - 888b)
> + .endif
> +.endm
> +
> +.macro __ALTERNATIVE_CFG old_c, new_c, vendor_id, errata_id, enable
> +886 :
> + \old_c
> +887 :
> + ALT_NEW_CONTENT \vendor_id, \errata_id, \enable, \new_c
> +.endm
> +
> +#define _ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, CONFIG_k) \
> + __ALTERNATIVE_CFG old_c, new_c, vendor_id, errata_id, IS_ENABLED(CONFIG_k)
> +
> +#else /* !__ASSEMBLY__ */
> +
> +#include <asm/asm.h>
> +#include <linux/stringify.h>
> +
> +#define ALT_ENTRY(oldptr, newptr, vendor_id, errata_id, newlen) \
> + RISCV_PTR " " oldptr "\n" \
> + RISCV_PTR " " newptr "\n" \
> + REG_ASM " " vendor_id "\n" \
> + REG_ASM " " newlen "\n" \
> + ".word " errata_id "\n"
> +
> +#define ALT_NEW_CONSTENT(vendor_id, errata_id, enable, new_c) \
> + ".if " __stringify(enable) " == 1\n" \
> + ".pushsection .alternative, \"a\"\n" \
> + ALT_ENTRY("886b", "888f", __stringify(vendor_id), __stringify(errata_id), "889f - 888f") \
> + ".popsection\n" \
> + ".subsection 1\n" \
> + "888 :\n" \
> + new_c "\n" \
> + "889 :\n" \
> + ".previous\n" \
> + ".org . - (887b - 886b) + (889b - 888b)\n" \
> + ".org . - (889b - 888b) + (887b - 886b)\n" \
> + ".endif\n"
> +
> +#define __ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, enable) \
> + "886 :\n" \
> + old_c "\n" \
> + "887 :\n" \
> + ALT_NEW_CONSTENT(vendor_id, errata_id, enable, new_c)
> +
> +#define _ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, CONFIG_k) \
> + __ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, IS_ENABLED(CONFIG_k))
> +
> +#endif /* __ASSEMBLY__ */
> +
> +#else /* !CONFIG_RISCV_ERRATA_ALTERNATIVE*/
> +#ifdef __ASSEMBLY__
> +
> +.macro __ALTERNATIVE_CFG old_c
> + \old_c
> +.endm
> +
> +#define _ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, CONFIG_k) \
> + __ALTERNATIVE_CFG old_c
> +
> +#else /* !__ASSEMBLY__ */
> +
> +#define __ALTERNATIVE_CFG(old_c) \
> + old_c "\n"
> +
> +#define _ALTERNATIVE_CFG(old_c, new_c, vendor_id, errata_id, CONFIG_k) \
> + __ALTERNATIVE_CFG(old_c)
> +
> +#endif /* __ASSEMBLY__ */
> +#endif /* CONFIG_RISCV_ERRATA_ALTERNATIVE */
> +/*
> + * Usage:
> + * ALTERNATIVE(old_content, new_content, vendor_id, errata_id, CONFIG_k)
> + * in the assembly code. Otherwise,
> + * asm(ALTERNATIVE(old_content, new_content, vendor_id, errata_id, CONFIG_k));
> + *
> + * old_content: The old content which is probably replaced with new content.
> + * new_content: The new content.
> + * vendor_id: The CPU vendor ID.
> + * errata_id: The errata ID.
> + * CONFIG_k: The Kconfig of this errata. When Kconfig is disabled, the old
> + * content will alwyas be executed.
> + */
> +#define ALTERNATIVE(old_content, new_content, vendor_id, errata_id, CONFIG_k) \
> + _ALTERNATIVE_CFG(old_content, new_content, vendor_id, errata_id, CONFIG_k)
> +
> +/*
> + * A vendor wants to replace an old_content, but another vendor has used
> + * ALTERNATIVE() to patch its customized content at the same location. In
> + * this case, this vendor can create a new macro ALTERNATIVE_2() based
> + * on the following sample code and then replace ALTERNATIVE() with
> + * ALTERNATIVE_2() to append its customized content.
> + *
> + * .macro __ALTERNATIVE_CFG_2 old_c, new_c_1, vendor_id_1, errata_id_1, enable_1, \
> + * new_c_2, vendor_id_2, errata_id_2, enable_2
> + * 886 :
> + * \old_c
> + * 887 :
> + * ALT_NEW_CONTENT \vendor_id_1, \errata_id_1, \enable_1, \new_c_1
> + * ALT_NEW_CONTENT \vendor_id_2, \errata_id_2, \enable_2, \new_c_2
> + * .endm
> + *
> + * #define _ALTERNATIVE_CFG_2(old_c, new_c_1, vendor_id_1, errata_id_1, CONFIG_k_1, \
> + * new_c_2, vendor_id_2, errata_id_2, CONFIG_k_2) \
> + * __ALTERNATIVE_CFG_2 old_c, new_c_1, vendor_id_1, errata_id_1, IS_ENABLED(CONFIG_k_1), \
> + * new_c_2, vendor_id_2, errata_id_2, IS_ENABLED(CONFIG_k_2) \
> + *
> + * #define ALTERNATIVE_2(old_content, new_content_1, vendor_id_1, errata_id_1, CONFIG_k_1, \
> + * new_content_2, vendor_id_2, errata_id_2, CONFIG_k_2) \
> + * _ALTERNATIVE_CFG_2(old_content, new_content_1, vendor_id_1, errata_id_1, CONFIG_k_1, \
> + * new_content_2, vendor_id_2, errata_id_2, CONFIG_k_2)
> + *
> + */
> +#endif
> diff --git a/arch/riscv/include/asm/alternative.h b/arch/riscv/include/asm/alternative.h
> new file mode 100644
> index 000000000000..430bc4fea133
> --- /dev/null
> +++ b/arch/riscv/include/asm/alternative.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2021 Sifive.
> + */
> +
> +#ifndef __ASM_ALTERNATIVE_H
> +#define __ASM_ALTERNATIVE_H
> +
> +#define ERRATA_STRING_LENGTH_MAX 32
> +
> +#include <asm/alternative-macros.h>
> +
> +#ifndef __ASSEMBLY__
> +
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/stddef.h>
> +#include <asm/hwcap.h>
> +
> +void __init apply_boot_alternatives(void);
> +
> +struct alt_entry {
> + void *old_ptr; /* address of original instruciton or data */
> + void *alt_ptr; /* address of replacement instruction or data */
> + unsigned long vendor_id; /* cpu vendor id */
> + unsigned long alt_len; /* The replacement size */
> + unsigned int errata_id; /* The errata id */
> +} __packed;
> +
> +struct errata_checkfunc_id {
> + unsigned long vendor_id;
> + bool (*func)(struct alt_entry *alt);
> +};
> +
> +#endif
> +#endif
> diff --git a/arch/riscv/include/asm/asm.h b/arch/riscv/include/asm/asm.h
> index 9c992a88d858..618d7c5af1a2 100644
> --- a/arch/riscv/include/asm/asm.h
> +++ b/arch/riscv/include/asm/asm.h
> @@ -23,6 +23,7 @@
> #define REG_L __REG_SEL(ld, lw)
> #define REG_S __REG_SEL(sd, sw)
> #define REG_SC __REG_SEL(sc.d, sc.w)
> +#define REG_ASM __REG_SEL(.dword, .word)
> #define SZREG __REG_SEL(8, 4)
> #define LGREG __REG_SEL(3, 2)
>
> diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h
> index caadfc1d7487..87ac65696871 100644
> --- a/arch/riscv/include/asm/csr.h
> +++ b/arch/riscv/include/asm/csr.h
> @@ -115,6 +115,9 @@
> #define CSR_MIP 0x344
> #define CSR_PMPCFG0 0x3a0
> #define CSR_PMPADDR0 0x3b0
> +#define CSR_MVENDORID 0xf11
> +#define CSR_MARCHID 0xf12
> +#define CSR_MIMPID 0xf13
> #define CSR_MHARTID 0xf14
>
> #ifdef CONFIG_RISCV_M_MODE
> diff --git a/arch/riscv/include/asm/errata_list.h b/arch/riscv/include/asm/errata_list.h
> new file mode 100644
> index 000000000000..1b56131431c9
> --- /dev/null
> +++ b/arch/riscv/include/asm/errata_list.h
> @@ -0,0 +1,12 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2021 Sifive.
> + */
> +#ifndef ASM_ERRATA_LIST_H
> +#define ASM_ERRATA_LIST_H
> +
> +#ifdef CONFIG_ERRATA_SIFIVE
> +#define ERRATA_SIFIVE_NUMBER 0
> +#endif
> +
> +#endif
> diff --git a/arch/riscv/include/asm/sections.h b/arch/riscv/include/asm/sections.h
> index 1595c5b60cfd..8a303fb1ee3b 100644
> --- a/arch/riscv/include/asm/sections.h
> +++ b/arch/riscv/include/asm/sections.h
> @@ -11,5 +11,6 @@ extern char _start[];
> extern char _start_kernel[];
> extern char __init_data_begin[], __init_data_end[];
> extern char __init_text_begin[], __init_text_end[];
> +extern char __alt_start[], __alt_end[];
>
> #endif /* __ASM_SECTIONS_H */
> diff --git a/arch/riscv/include/asm/vendorid_list.h b/arch/riscv/include/asm/vendorid_list.h
> new file mode 100644
> index 000000000000..9d934215b3c8
> --- /dev/null
> +++ b/arch/riscv/include/asm/vendorid_list.h
> @@ -0,0 +1,10 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2021 SiFive
> + */
> +#ifndef ASM_VENDOR_LIST_H
> +#define ASM_VENDOR_LIST_H
> +
> +#define SIFIVE_VENDOR_ID 0x489
> +
> +#endif
> diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c
> index 5e276c25646f..9a408e2942ac 100644
> --- a/arch/riscv/kernel/smpboot.c
> +++ b/arch/riscv/kernel/smpboot.c
> @@ -32,6 +32,7 @@
> #include <asm/sections.h>
> #include <asm/sbi.h>
> #include <asm/smp.h>
> +#include <asm/alternative.h>
>
> #include "head.h"
>
> @@ -40,6 +41,9 @@ static DECLARE_COMPLETION(cpu_running);
> void __init smp_prepare_boot_cpu(void)
> {
> init_cpu_topology();
> +#ifdef CONFIG_RISCV_ERRATA_ALTERNATIVE
> + apply_boot_alternatives();
> +#endif
> }
>
> void __init smp_prepare_cpus(unsigned int max_cpus)
> diff --git a/arch/riscv/kernel/vmlinux.lds.S b/arch/riscv/kernel/vmlinux.lds.S
> index de03cb22d0e9..7e61bc1dc36e 100644
> --- a/arch/riscv/kernel/vmlinux.lds.S
> +++ b/arch/riscv/kernel/vmlinux.lds.S
> @@ -90,6 +90,13 @@ SECTIONS
> }
>
> __init_data_end = .;
> +
> + . = ALIGN(8);
> + .alternative : {
> + __alt_start = .;
> + *(.alternative)
> + __alt_end = .;
> + }
> __init_end = .;
>
> /* Start of data section */
> --
> 2.7.4
>
>
> _______________________________________________
> linux-riscv mailing list
> linux-riscv at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
More information about the linux-riscv
mailing list