Skip to content

Commit

Permalink
RISC-V: fix auipc-jalr addresses in patched alternatives
Browse files Browse the repository at this point in the history
Alternatives live in a different section, so addresses used by call
functions will point to wrong locations after the patch got applied.

Similar to arm64, adjust the location to consider that offset.

Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Reviewed-by: Andrew Jones <ajones@ventanamicro.com>
Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Tested-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Signed-off-by: Heiko Stuebner <heiko.stuebner@vrull.eu>
Link: https://lore.kernel.org/r/20221223221332.4127602-13-heiko@sntech.de
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
Heiko Stuebner authored and Palmer Dabbelt committed Dec 29, 2022

Unverified

No user is associated with the committer email.
1 parent 47f0575 commit 27c653c
Showing 3 changed files with 63 additions and 1 deletion.
3 changes: 3 additions & 0 deletions arch/riscv/include/asm/alternative.h
Original file line number Diff line number Diff line change
@@ -27,6 +27,9 @@ void __init apply_boot_alternatives(void);
void __init apply_early_boot_alternatives(void);
void apply_module_alternatives(void *start, size_t length);

void riscv_alternative_fix_offsets(void *alt_ptr, unsigned int len,
int patch_offset);

struct alt_entry {
void *old_ptr; /* address of original instruciton or data */
void *alt_ptr; /* address of replacement instruction or data */
56 changes: 56 additions & 0 deletions arch/riscv/kernel/alternative.c
Original file line number Diff line number Diff line change
@@ -15,6 +15,8 @@
#include <asm/vendorid_list.h>
#include <asm/sbi.h>
#include <asm/csr.h>
#include <asm/insn.h>
#include <asm/patch.h>

struct cpu_manufacturer_info_t {
unsigned long vendor_id;
@@ -53,6 +55,60 @@ static void __init_or_module riscv_fill_cpu_mfr_info(struct cpu_manufacturer_inf
}
}

static u32 riscv_instruction_at(void *p)
{
u16 *parcel = p;

return (u32)parcel[0] | (u32)parcel[1] << 16;
}

static void riscv_alternative_fix_auipc_jalr(void *ptr, u32 auipc_insn,
u32 jalr_insn, int patch_offset)
{
u32 call[2] = { auipc_insn, jalr_insn };
s32 imm;

/* get and adjust new target address */
imm = riscv_insn_extract_utype_itype_imm(auipc_insn, jalr_insn);
imm -= patch_offset;

/* update instructions */
riscv_insn_insert_utype_itype_imm(&call[0], &call[1], imm);

/* patch the call place again */
patch_text_nosync(ptr, call, sizeof(u32) * 2);
}

void riscv_alternative_fix_offsets(void *alt_ptr, unsigned int len,
int patch_offset)
{
int num_insn = len / sizeof(u32);
int i;

for (i = 0; i < num_insn; i++) {
u32 insn = riscv_instruction_at(alt_ptr + i * sizeof(u32));

/*
* May be the start of an auipc + jalr pair
* Needs to check that at least one more instruction
* is in the list.
*/
if (riscv_insn_is_auipc(insn) && i < num_insn - 1) {
u32 insn2 = riscv_instruction_at(alt_ptr + (i + 1) * sizeof(u32));

if (!riscv_insn_is_jalr(insn2))
continue;

/* if instruction pair is a call, it will use the ra register */
if (RV_EXTRACT_RD_REG(insn) != 1)
continue;

riscv_alternative_fix_auipc_jalr(alt_ptr + i * sizeof(u32),
insn, insn2, patch_offset);
}
}
}

/*
* 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
5 changes: 4 additions & 1 deletion arch/riscv/kernel/cpufeature.c
Original file line number Diff line number Diff line change
@@ -339,8 +339,11 @@ void __init_or_module riscv_cpufeature_patch_func(struct alt_entry *begin,
}

tmp = (1U << alt->errata_id);
if (cpu_req_feature & tmp)
if (cpu_req_feature & tmp) {
patch_text_nosync(alt->old_ptr, alt->alt_ptr, alt->alt_len);
riscv_alternative_fix_offsets(alt->old_ptr, alt->alt_len,
alt->old_ptr - alt->alt_ptr);
}
}
}
#endif

0 comments on commit 27c653c

Please sign in to comment.