From: Charlie Jenkins Eliminate the need to hand-write riscv instructions by using a shell script to autogenerate a header from an instruction table. This is modeled after the syscall table infrastructure. The table is generated externally by riscv-unified-db [1], but is in a simple format to make it possible to use other tools or modify manually. [1] https://github.com/riscv-software-src/riscv-unified-db Signed-off-by: Charlie Jenkins --- This change immediately modifies all of the riscv_insn_* macros. I don't have a clean way of sharing my test cases but what I did was compare the old and new versions of the functions using the following test case: for (unsigned int i = 0; i < ((1ULL << 32) - 1); i++) {\ bool old = riscv_insn_is_##name(i);\ bool new = new_riscv_insn_is_##name(i);\ if (old != new) {\ printf(#name " %u\n", i);\ }\ } for (unsigned int i = 0; i < ((1ULL << 32) - 1); i++) {\ bool old = riscv_insn_is_##name(i);\ bool new = new_riscv_insn_is_##name(i);\ \ if (old != new) {\ printf(#name " %u\n", i);\ return;\ }\ } void check() { check_64(auipc) check_64(jalr) check_64(jal) check_64(beq) check_64(bne) check_64(blt) check_64(bge) check_64(bltu) check_64(bgeu) check_64(ebreak) check_64(sret) check_64(fence) check_32(c_jr) check_32(c_jalr) check_32(c_j) check_32(c_beqz) check_32(c_bnez) check_32(c_ebreak) } This does a simple brute force to check all possible numbers. The only difference with the new version is that the fence instruction refers to the literal "fence" instruction whereas the previous version could have matched on to fence, fence.i, or fence.tso. This does not make a material difference because riscv_insn_is_fence() isn't currently in use. --- arch/riscv/Makefile | 3 + arch/riscv/include/asm/Kbuild | 1 + arch/riscv/include/asm/insn.h | 72 +-- arch/riscv/tools/Makefile | 22 + arch/riscv/tools/insn.tbl | 1392 +++++++++++++++++++++++++++++++++++++++++ arch/riscv/tools/insn_tbl.sh | 256 ++++++++ 6 files changed, 1704 insertions(+), 42 deletions(-) diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile index ce0cc737f870..d14caa8720f8 100644 --- a/arch/riscv/Makefile +++ b/arch/riscv/Makefile @@ -176,6 +176,9 @@ BOOT_TARGETS := Image Image.gz Image.bz2 Image.lz4 Image.lzma Image.lzo Image.zs all: $(notdir $(KBUILD_IMAGE)) +archprepare: + $(Q)$(MAKE) $(build)=arch/riscv/tools insn + loader.bin: loader Image.gz Image.bz2 Image.lz4 Image.lzma Image.lzo Image.zst Image.xz loader xipImage vmlinuz.efi: Image diff --git a/arch/riscv/include/asm/Kbuild b/arch/riscv/include/asm/Kbuild index 7721b63642f4..16be4de3d0b1 100644 --- a/arch/riscv/include/asm/Kbuild +++ b/arch/riscv/include/asm/Kbuild @@ -2,6 +2,7 @@ syscall-y += syscall_table_32.h syscall-y += syscall_table_64.h +generated-y += insn_gen.h generic-y += early_ioremap.h generic-y += flat.h generic-y += fprobe.h diff --git a/arch/riscv/include/asm/insn.h b/arch/riscv/include/asm/insn.h index c3005573e8c9..d562b2b40ba1 100644 --- a/arch/riscv/include/asm/insn.h +++ b/arch/riscv/include/asm/insn.h @@ -8,6 +8,36 @@ #include +/* + * Generate a function to check if a sequence of bits matches an instruction + */ +#define __RISCV_INSN_FUNCS(name) \ +static __always_inline bool riscv_insn_is_##name(u32 _insn) \ +{ \ + BUILD_BUG_ON(~(riscv_insn_##name##_MASK) & (riscv_insn_##name##_MATCH)); \ + return (_insn & (riscv_insn_##name##_MASK)) == (riscv_insn_##name##_MATCH); \ +} + +/* + * Generate a function to check if a sequence of bits matches an instruction + * with constraints. Some instructions require inputs to be specific values. + */ +#define __RISCV_INSN_FUNCS_CONSTRAINED(name, constraints) \ +static __always_inline bool riscv_insn_is_##name(u32 _insn) \ +{ \ + BUILD_BUG_ON(~(riscv_insn_##name##_MASK) & (riscv_insn_##name##_MATCH)); \ + return ((_insn & (riscv_insn_##name##_MASK)) == (riscv_insn_##name##_MATCH)) && \ + (constraints); \ +} + +#define __RISCV_INSN_FUNCS_UNSUPPORTED(name) \ +static __always_inline bool riscv_insn_is_##name(u32 _insn) \ +{ \ + return 0; \ +} + +#include + #define RV_INSN_FUNCT3_MASK GENMASK(14, 12) #define RV_INSN_FUNCT3_OPOFF 12 #define RV_INSN_OPCODE_MASK GENMASK(6, 0) @@ -233,36 +263,6 @@ #define __INSN_OPCODE_MASK _UL(0x7F) #define __INSN_BRANCH_OPCODE _UL(RVG_OPCODE_BRANCH) -#define __RISCV_INSN_FUNCS(name, mask, val) \ -static __always_inline bool riscv_insn_is_##name(u32 code) \ -{ \ - BUILD_BUG_ON(~(mask) & (val)); \ - return (code & (mask)) == (val); \ -} \ - -#if __riscv_xlen == 32 -/* C.JAL is an RV32C-only instruction */ -__RISCV_INSN_FUNCS(c_jal, RVC_MASK_C_JAL, RVC_MATCH_C_JAL) -#else -#define riscv_insn_is_c_jal(opcode) 0 -#endif -__RISCV_INSN_FUNCS(auipc, RVG_MASK_AUIPC, RVG_MATCH_AUIPC) -__RISCV_INSN_FUNCS(jalr, RVG_MASK_JALR, RVG_MATCH_JALR) -__RISCV_INSN_FUNCS(jal, RVG_MASK_JAL, RVG_MATCH_JAL) -__RISCV_INSN_FUNCS(c_j, RVC_MASK_C_J, RVC_MATCH_C_J) -__RISCV_INSN_FUNCS(beq, RVG_MASK_BEQ, RVG_MATCH_BEQ) -__RISCV_INSN_FUNCS(bne, RVG_MASK_BNE, RVG_MATCH_BNE) -__RISCV_INSN_FUNCS(blt, RVG_MASK_BLT, RVG_MATCH_BLT) -__RISCV_INSN_FUNCS(bge, RVG_MASK_BGE, RVG_MATCH_BGE) -__RISCV_INSN_FUNCS(bltu, RVG_MASK_BLTU, RVG_MATCH_BLTU) -__RISCV_INSN_FUNCS(bgeu, RVG_MASK_BGEU, RVG_MATCH_BGEU) -__RISCV_INSN_FUNCS(c_beqz, RVC_MASK_C_BEQZ, RVC_MATCH_C_BEQZ) -__RISCV_INSN_FUNCS(c_bnez, RVC_MASK_C_BNEZ, RVC_MATCH_C_BNEZ) -__RISCV_INSN_FUNCS(c_ebreak, RVC_MASK_C_EBREAK, RVC_MATCH_C_EBREAK) -__RISCV_INSN_FUNCS(ebreak, RVG_MASK_EBREAK, RVG_MATCH_EBREAK) -__RISCV_INSN_FUNCS(sret, RVG_MASK_SRET, RVG_MATCH_SRET) -__RISCV_INSN_FUNCS(fence, RVG_MASK_FENCE, RVG_MATCH_FENCE); - /* special case to catch _any_ system instruction */ static __always_inline bool riscv_insn_is_system(u32 code) { @@ -275,18 +275,6 @@ static __always_inline bool riscv_insn_is_branch(u32 code) return (code & RV_INSN_OPCODE_MASK) == RVG_OPCODE_BRANCH; } -static __always_inline bool riscv_insn_is_c_jr(u32 code) -{ - return (code & RVC_MASK_C_JR) == RVC_MATCH_C_JR && - (code & RVC_INSN_J_RS1_MASK) != 0; -} - -static __always_inline bool riscv_insn_is_c_jalr(u32 code) -{ - return (code & RVC_MASK_C_JALR) == RVC_MATCH_C_JALR && - (code & RVC_INSN_J_RS1_MASK) != 0; -} - #define INSN_MATCH_LB 0x3 #define INSN_MASK_LB 0x707f #define INSN_MATCH_LH 0x1003 diff --git a/arch/riscv/tools/Makefile b/arch/riscv/tools/Makefile new file mode 100644 index 000000000000..5f40439c12e9 --- /dev/null +++ b/arch/riscv/tools/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 + +gen := arch/$(ARCH)/include/generated/asm +insn_tbl := $(src)/insn_tbl.sh +insn := $(src)/insn.tbl + +gen-y := $(gen)/insn_gen.h + +targets += $(addprefix ../../../,$(gen-y)) + +PHONY += insn + +insn: $(gen-y) + +# Create output directory if not already present +$(shell mkdir -p $(gen)) + +quiet_cmd_insn_tbl = INST_TBL $@ + cmd_insn_tbl = $(CONFIG_SHELL) $(insn_tbl) $< $@ + +$(gen)/insn_gen.h: $(insn) $(insn_tbl) FORCE + $(call if_changed,insn_tbl) diff --git a/arch/riscv/tools/insn.tbl b/arch/riscv/tools/insn.tbl new file mode 100644 index 000000000000..5dc3392a5543 --- /dev/null +++ b/arch/riscv/tools/insn.tbl @@ -0,0 +1,1392 @@ +# SPDX-License-Identifier: BSD-3-Clause-Clear +# +# GENERATED WITH https://github.com/riscv-software-src/riscv-unified-db +# "./bin/generate inst-table -o test_table.txt" +# +# Each line of the instruction table should have the following format: +# NAME BASE FIXED_BITS [VARIABLE_LIST] +# NAME instruction name +# BASE instruction base size (common[,(32|64)]) +# "common" means the instruction is valid on both architecture sizes +# "32" or "64" means the instruction is valid on that size +# if the instruction is valid on both architectures but has unique +# encodings, use a 32-bit entry "common,32" and 64-bit entry +# FIXED_BITS bitfields of the fixed bits of an instruction concatenated with '|' +# continuous grouping of fixed bits are in the form of 'bits_MASK useful to help check if arbitrary binary is +# - riscv_insn__MATCH useful to help check if arbitrary binary is +# - riscv_insn_ useful to construct +# - riscv_insn__ useful to extract from +# +# Each line of the instruction table should have the following format: +# NAME BASE FIXED_BITS [VARIABLE_LIST] +# NAME instruction name +# BASE instruction base size (common[,(32|64)]) +# "common" means the instruction is valid on both architecture sizes +# "32" or "64" means the instruction is valid on that size +# if the instruction is valid on both architectures but has unique +# encodings, use a 32-bit entry "common,32" and 64-bit entry +# FIXED_BITS bitfields of the fixed bits of an instruction concatenated with '|' +# continuous grouping of fixed bits are in the form of 'bits&2 "usage: $0 BASE INFILE OUTFILE" >&2 + echo >&2 + echo >&2 " INFILE input instruction table" + echo >&2 " OUTFILE output header file" + exit 1 +} + +if [ $# -ne 2 ]; then + usage +fi + +infile="$1" +outfile="$2" + +file=$(readlink -f $0) + +echo "/* Auto-generated rv${base} header from script arch/${file#*arch/} */" > $outfile + +echo "#ifndef RISCV_INSN_GEN_H" >> $outfile +echo "#define RISCV_INSN_GEN_H" >> $outfile +echo >> $outfile + +printf "#include " >> $outfile +echo >> $outfile + +grep -E "^[a-z\.0-9]+[[:space:]]+" "$infile" | { + while read name base fixed variables; do + echo "/* $name */" + + non_compressed_insn=${name##c.*} + invalid_inst_functions="" + variable_params="" + constraints="" + match="" + mask="" + make="" + + # All compressed instructions start with "c." + size=${non_compressed_insn:+32}; + size=${size:-16}; + + # Replace all . with _ + formatted_inst_name=$name + while [ ! ${formatted_inst_name##*.*} ]; do + prefix=${formatted_inst_name%.*} + suffix=${formatted_inst_name##*.} + contains_dot=${formatted_inst_name##*.*} + formatted_inst_name=${contains_dot:-${prefix}_${suffix}} + done + + # Collect all fixed bits of an instruction + OLD_IFS=$IFS + IFS='|' + for segment in $fixed; do + bits=${segment%<*} + offset=${segment#*<} + + len=${#bits} + + mask="${mask} | 0b" + + while [ $len -gt 0 ]; do + len=$((len - 1)) + mask=${mask}1 + done + + if [ ${offset} -gt 0 ]; then + s=" << ${offset}" + else + s="" + fi + + mask="${mask}${s}" + + match="${match} | 0b${bits}${s}" + done + IFS=$OLD_IFS + + # Instruction only appears in one base + only_base= + if [ "${base}" != "${base%32}" ]; then + echo "#if __riscv_xlen == 32" + only_base=32 + elif [ "${base}" != "${base%64}" ]; then + echo "#if __riscv_xlen == 64" + only_base=64 + fi + + # Standard name for the instruction parameter in generated functions + insn="_insn" + + for variable in ${variables}; do + variable_name="${variable%%[<~=!]*}" + parts="${variable#*=}" + insert_mask="" + sign_extend="" + left_shift="" + extract="" + insert="" + + # Standard name for the variable parameter in generated functions + var="_${variable_name}" + variable_params="${variable_params}u32 ${var}, " + + if [ "${variable}" != "${variable#*~}" ]; then + sign_extend="1" + fi + + if [ "${variable}" != "${variable#*<}" ]; then + left_shift="${variable#*<}" + left_shift="${left_shift%%[=<~!]*}" + else + left_shift="0" + fi + + if [ "${variable}" != "${variable#*!}" ]; then + raw_constraints="${variable#*!}" + raw_constraints="${raw_constraints%%[=<~!]**}" + + OLD_IFS=$IFS + IFS='!' + for constraint in $raw_constraints; do + constraints="${constraints}(riscv_insn_${formatted_inst_name}_extract_${variable_name}(${insn}) != ${constraint}) && " + done + IFS=$OLD_IFS + fi + + offset=0 + while true; do + part=${parts##*|} + + if [ "${part#*-}" = "${part}" ]; then + high="${part}" + low="${part}" + len=1 + else + high="${part%-*}" + low="${part#*-}" + len=$((high - low + 1)) + fi + + # Don't emit shift if 0 + first_shift=${low} + if [ "${first_shift}" = "0" ]; then + first_shift= + fi + + second_shift=$((offset + left_shift)) + if [ "${second_shift}" = "0" ]; then + second_shift= + fi + + extract="${extract} | ((${insn}${first_shift:+ >> }${first_shift} & GENMASK($((len - 1)), 0))${second_shift:+ << }${second_shift})" + insert_mask="${insert_mask} & ~GENMASK(${high}, ${low})" + insert="${insert} | (((${var}${second_shift:+ >> }${second_shift}) & GENMASK($((len - 1)), 0))${first_shift:+ << }${first_shift})" + offset=$((offset + len)) + + if [ "${parts}" = "${part}" ]; then + # Processed all parts of variable + break + fi + + parts=${parts%|*} + done + + extract="${extract# | }" + + if [ ${sign_extend} ]; then + extract="sign_extend32(${extract}, $((offset + left_shift - 1)))" + type="s" + else + type="u" + fi + + printf "static __always_inline ${type}${size} riscv_insn_${formatted_inst_name}_extract_${variable_name}(u${size} ${insn})\n" + printf "{\n" + printf "\treturn ${extract};\n" + printf "}\n" + printf "static __always_inline void riscv_insn_${formatted_inst_name}_insert_${variable_name}(u${size} *${insn}, ${type}32 ${var})\n" + printf "{\n" + printf "\t*_insn &= ${insert_mask# & };\n" + printf "\t*_insn |= ${insert# | };\n" + printf "}\n" + + if [ "${only_base}" ]; then + invalid_inst_functions="${invalid_inst_functions}static __always_inline ${type}${size} riscv_insn_${formatted_inst_name}_extract_${variable_name}(u${size} ${insn}) {\n\tBUILD_BUG_ON_MSG(1, \"${name} is not supported on non ${only_base}-bit systems.\");\n}\n" + fi + + make="${make} riscv_insn_${formatted_inst_name}_insert_${variable_name}(&${insn}, ${var});\n" + done + + variable_params="${variable_params%, }" + variable_params="${variable_params:-void}" + + printf "#define riscv_insn_${formatted_inst_name}_MASK (${mask# | })\n" + printf "#define riscv_insn_${formatted_inst_name}_MATCH (${match# | })\n" + printf "static __always_inline u${size} riscv_insn_${formatted_inst_name}(${variable_params})\n" + printf "{\n" + printf "\tu${size} ${insn} = riscv_insn_${formatted_inst_name}_MATCH;\n" + printf "${make} return ${insn};\n" + printf "}\n" + + # Check against instructions that have a variable that may contain invalid values + if [ "$constraints" ]; then + printf "__RISCV_INSN_FUNCS_CONSTRAINED(${formatted_inst_name}, ${constraints% && });\n" + else + printf "__RISCV_INSN_FUNCS(${formatted_inst_name});\n" + fi + + # If common does not appear in the base, then this instruction only appears in one base + if [ "$base" = "${base#common}" ]; then + printf "#else\n" + printf "__RISCV_INSN_FUNCS_UNSUPPORTED(${formatted_inst_name});\n" + printf "${invalid_inst_functions%\\n}\n" + fi + + # Instruction has a base variant + if [ "$base" != "${base%[24]}" ]; then + echo "#endif" + fi + + echo + done + + echo "#endif /* RISCV_INST_GEN_H */" +} >> $outfile -- 2.54.0