Files
linux/arch/riscv/errata/thead/errata.c

89 lines
2.1 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2021 Heiko Stuebner <heiko@sntech.de>
*/
#include <linux/bug.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <asm/alternative.h>
#include <asm/cacheflush.h>
#include <asm/errata_list.h>
#include <asm/patch.h>
#include <asm/vendorid_list.h>
riscv: remove usage of function-pointers from cpufeatures and t-head errata Having a list of alternatives to check with a per-entry function pointer to a check function is nice style-wise. But in case of early-alternatives it can clash with the non-relocated kernel and the function pointer in the list pointing to a completely wrong location. This isn't an issue with one or two list entries, as in that case the compiler seems to unroll the loop and even usage of the list structure and then only does relative jumps into the check functions based on this. When adding a third entry to either list though, the issue that was hiding there from the beginning is triggered resulting a jump to a memory address that isn't part of the kernel at all. The list of features/erratas only contained an unused name and the pointer to the check function, so an easy solution for the problem is to just unroll the loop in code, dismantle the whole list structure and just call the relevant check functions one by one ourself. For the T-Head errata this includes moving the stage-check inside the check functions. The issue is only relevant for things that might be called for early- alternatives (T-Head and possible future main extensions), so the SiFive erratas were not affected from the beginning, as they got an early return for early-alternatives in the original patchset. Signed-off-by: Heiko Stuebner <heiko@sntech.de> Tested-by: Samuel Holland <samuel@sholland.org> Link: https://lore.kernel.org/r/20220526205646.258337-6-heiko@sntech.de Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2022-05-26 22:56:46 +02:00
static bool errata_probe_pbmt(unsigned int stage,
unsigned long arch_id, unsigned long impid)
{
if (arch_id != 0 || impid != 0)
return false;
riscv: remove usage of function-pointers from cpufeatures and t-head errata Having a list of alternatives to check with a per-entry function pointer to a check function is nice style-wise. But in case of early-alternatives it can clash with the non-relocated kernel and the function pointer in the list pointing to a completely wrong location. This isn't an issue with one or two list entries, as in that case the compiler seems to unroll the loop and even usage of the list structure and then only does relative jumps into the check functions based on this. When adding a third entry to either list though, the issue that was hiding there from the beginning is triggered resulting a jump to a memory address that isn't part of the kernel at all. The list of features/erratas only contained an unused name and the pointer to the check function, so an easy solution for the problem is to just unroll the loop in code, dismantle the whole list structure and just call the relevant check functions one by one ourself. For the T-Head errata this includes moving the stage-check inside the check functions. The issue is only relevant for things that might be called for early- alternatives (T-Head and possible future main extensions), so the SiFive erratas were not affected from the beginning, as they got an early return for early-alternatives in the original patchset. Signed-off-by: Heiko Stuebner <heiko@sntech.de> Tested-by: Samuel Holland <samuel@sholland.org> Link: https://lore.kernel.org/r/20220526205646.258337-6-heiko@sntech.de Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2022-05-26 22:56:46 +02:00
if (stage == RISCV_ALTERNATIVES_EARLY_BOOT ||
stage == RISCV_ALTERNATIVES_MODULE)
return true;
return false;
}
static bool errata_probe_cmo(unsigned int stage,
unsigned long arch_id, unsigned long impid)
{
#ifdef CONFIG_ERRATA_THEAD_CMO
if (arch_id != 0 || impid != 0)
return false;
if (stage == RISCV_ALTERNATIVES_EARLY_BOOT)
return false;
riscv_noncoherent_supported();
return true;
#else
return false;
#endif
}
riscv: remove usage of function-pointers from cpufeatures and t-head errata Having a list of alternatives to check with a per-entry function pointer to a check function is nice style-wise. But in case of early-alternatives it can clash with the non-relocated kernel and the function pointer in the list pointing to a completely wrong location. This isn't an issue with one or two list entries, as in that case the compiler seems to unroll the loop and even usage of the list structure and then only does relative jumps into the check functions based on this. When adding a third entry to either list though, the issue that was hiding there from the beginning is triggered resulting a jump to a memory address that isn't part of the kernel at all. The list of features/erratas only contained an unused name and the pointer to the check function, so an easy solution for the problem is to just unroll the loop in code, dismantle the whole list structure and just call the relevant check functions one by one ourself. For the T-Head errata this includes moving the stage-check inside the check functions. The issue is only relevant for things that might be called for early- alternatives (T-Head and possible future main extensions), so the SiFive erratas were not affected from the beginning, as they got an early return for early-alternatives in the original patchset. Signed-off-by: Heiko Stuebner <heiko@sntech.de> Tested-by: Samuel Holland <samuel@sholland.org> Link: https://lore.kernel.org/r/20220526205646.258337-6-heiko@sntech.de Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2022-05-26 22:56:46 +02:00
static u32 thead_errata_probe(unsigned int stage,
unsigned long archid, unsigned long impid)
{
u32 cpu_req_errata = 0;
riscv: remove usage of function-pointers from cpufeatures and t-head errata Having a list of alternatives to check with a per-entry function pointer to a check function is nice style-wise. But in case of early-alternatives it can clash with the non-relocated kernel and the function pointer in the list pointing to a completely wrong location. This isn't an issue with one or two list entries, as in that case the compiler seems to unroll the loop and even usage of the list structure and then only does relative jumps into the check functions based on this. When adding a third entry to either list though, the issue that was hiding there from the beginning is triggered resulting a jump to a memory address that isn't part of the kernel at all. The list of features/erratas only contained an unused name and the pointer to the check function, so an easy solution for the problem is to just unroll the loop in code, dismantle the whole list structure and just call the relevant check functions one by one ourself. For the T-Head errata this includes moving the stage-check inside the check functions. The issue is only relevant for things that might be called for early- alternatives (T-Head and possible future main extensions), so the SiFive erratas were not affected from the beginning, as they got an early return for early-alternatives in the original patchset. Signed-off-by: Heiko Stuebner <heiko@sntech.de> Tested-by: Samuel Holland <samuel@sholland.org> Link: https://lore.kernel.org/r/20220526205646.258337-6-heiko@sntech.de Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2022-05-26 22:56:46 +02:00
if (errata_probe_pbmt(stage, archid, impid))
cpu_req_errata |= (1U << ERRATA_THEAD_PBMT);
if (errata_probe_cmo(stage, archid, impid))
cpu_req_errata |= (1U << ERRATA_THEAD_CMO);
return cpu_req_errata;
}
void __init_or_module thead_errata_patch_func(struct alt_entry *begin, struct alt_entry *end,
unsigned long archid, unsigned long impid,
unsigned int stage)
{
struct alt_entry *alt;
u32 cpu_req_errata = thead_errata_probe(stage, archid, impid);
u32 tmp;
for (alt = begin; alt < end; alt++) {
if (alt->vendor_id != THEAD_VENDOR_ID)
continue;
if (alt->errata_id >= ERRATA_THEAD_NUMBER)
continue;
tmp = (1U << alt->errata_id);
if (cpu_req_errata & tmp) {
/* On vm-alternatives, the mmu isn't running yet */
if (stage == RISCV_ALTERNATIVES_EARLY_BOOT)
memcpy((void *)__pa_symbol(alt->old_ptr),
(void *)__pa_symbol(alt->alt_ptr), alt->alt_len);
else
patch_text_nosync(alt->old_ptr, alt->alt_ptr, alt->alt_len);
}
}
if (stage == RISCV_ALTERNATIVES_EARLY_BOOT)
local_flush_icache_all();
}