]> git.baikalelectronics.ru Git - kernel.git/commitdiff
LoongArch: Add ELF and module support
authorHuacai Chen <chenhuacai@loongson.cn>
Tue, 31 May 2022 10:04:11 +0000 (18:04 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Fri, 3 Jun 2022 12:09:28 +0000 (20:09 +0800)
Add ELF-related definition and module relocation code for basic
LoongArch support.

Cc: Jessica Yu <jeyu@kernel.org>
Reviewed-by: WANG Xuerui <git@xen0n.name>
Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Reviewed-by: Jiaxun Yang <jiaxun.yang@flygoat.com>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
12 files changed:
arch/loongarch/include/asm/cpufeature.h [new file with mode: 0644]
arch/loongarch/include/asm/elf.h [new file with mode: 0644]
arch/loongarch/include/asm/exec.h [new file with mode: 0644]
arch/loongarch/include/asm/module.h [new file with mode: 0644]
arch/loongarch/include/asm/module.lds.h [new file with mode: 0644]
arch/loongarch/include/asm/vermagic.h [new file with mode: 0644]
arch/loongarch/include/uapi/asm/auxvec.h [new file with mode: 0644]
arch/loongarch/include/uapi/asm/hwcap.h [new file with mode: 0644]
arch/loongarch/kernel/elf.c [new file with mode: 0644]
arch/loongarch/kernel/inst.c [new file with mode: 0644]
arch/loongarch/kernel/module-sections.c [new file with mode: 0644]
arch/loongarch/kernel/module.c [new file with mode: 0644]

diff --git a/arch/loongarch/include/asm/cpufeature.h b/arch/loongarch/include/asm/cpufeature.h
new file mode 100644 (file)
index 0000000..4da22a8
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * CPU feature definitions for module loading, used by
+ * module_cpu_feature_match(), see uapi/asm/hwcap.h for LoongArch CPU features.
+ *
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#ifndef __ASM_CPUFEATURE_H
+#define __ASM_CPUFEATURE_H
+
+#include <uapi/asm/hwcap.h>
+#include <asm/elf.h>
+
+#define MAX_CPU_FEATURES (8 * sizeof(elf_hwcap))
+
+#define cpu_feature(x)         ilog2(HWCAP_ ## x)
+
+static inline bool cpu_have_feature(unsigned int num)
+{
+       return elf_hwcap & (1UL << num);
+}
+
+#endif /* __ASM_CPUFEATURE_H */
diff --git a/arch/loongarch/include/asm/elf.h b/arch/loongarch/include/asm/elf.h
new file mode 100644 (file)
index 0000000..f3960b1
--- /dev/null
@@ -0,0 +1,301 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#ifndef _ASM_ELF_H
+#define _ASM_ELF_H
+
+#include <linux/auxvec.h>
+#include <linux/fs.h>
+#include <uapi/linux/elf.h>
+
+#include <asm/current.h>
+#include <asm/vdso.h>
+
+/* The ABI of a file. */
+#define EF_LOONGARCH_ABI_LP64_SOFT_FLOAT       0x1
+#define EF_LOONGARCH_ABI_LP64_SINGLE_FLOAT     0x2
+#define EF_LOONGARCH_ABI_LP64_DOUBLE_FLOAT     0x3
+
+#define EF_LOONGARCH_ABI_ILP32_SOFT_FLOAT      0x5
+#define EF_LOONGARCH_ABI_ILP32_SINGLE_FLOAT    0x6
+#define EF_LOONGARCH_ABI_ILP32_DOUBLE_FLOAT    0x7
+
+/* LoongArch relocation types used by the dynamic linker */
+#define R_LARCH_NONE                           0
+#define R_LARCH_32                             1
+#define R_LARCH_64                             2
+#define R_LARCH_RELATIVE                       3
+#define R_LARCH_COPY                           4
+#define R_LARCH_JUMP_SLOT                      5
+#define R_LARCH_TLS_DTPMOD32                   6
+#define R_LARCH_TLS_DTPMOD64                   7
+#define R_LARCH_TLS_DTPREL32                   8
+#define R_LARCH_TLS_DTPREL64                   9
+#define R_LARCH_TLS_TPREL32                    10
+#define R_LARCH_TLS_TPREL64                    11
+#define R_LARCH_IRELATIVE                      12
+#define R_LARCH_MARK_LA                                20
+#define R_LARCH_MARK_PCREL                     21
+#define R_LARCH_SOP_PUSH_PCREL                 22
+#define R_LARCH_SOP_PUSH_ABSOLUTE              23
+#define R_LARCH_SOP_PUSH_DUP                   24
+#define R_LARCH_SOP_PUSH_GPREL                 25
+#define R_LARCH_SOP_PUSH_TLS_TPREL             26
+#define R_LARCH_SOP_PUSH_TLS_GOT               27
+#define R_LARCH_SOP_PUSH_TLS_GD                        28
+#define R_LARCH_SOP_PUSH_PLT_PCREL             29
+#define R_LARCH_SOP_ASSERT                     30
+#define R_LARCH_SOP_NOT                                31
+#define R_LARCH_SOP_SUB                                32
+#define R_LARCH_SOP_SL                         33
+#define R_LARCH_SOP_SR                         34
+#define R_LARCH_SOP_ADD                                35
+#define R_LARCH_SOP_AND                                36
+#define R_LARCH_SOP_IF_ELSE                    37
+#define R_LARCH_SOP_POP_32_S_10_5              38
+#define R_LARCH_SOP_POP_32_U_10_12             39
+#define R_LARCH_SOP_POP_32_S_10_12             40
+#define R_LARCH_SOP_POP_32_S_10_16             41
+#define R_LARCH_SOP_POP_32_S_10_16_S2          42
+#define R_LARCH_SOP_POP_32_S_5_20              43
+#define R_LARCH_SOP_POP_32_S_0_5_10_16_S2      44
+#define R_LARCH_SOP_POP_32_S_0_10_10_16_S2     45
+#define R_LARCH_SOP_POP_32_U                   46
+#define R_LARCH_ADD8                           47
+#define R_LARCH_ADD16                          48
+#define R_LARCH_ADD24                          49
+#define R_LARCH_ADD32                          50
+#define R_LARCH_ADD64                          51
+#define R_LARCH_SUB8                           52
+#define R_LARCH_SUB16                          53
+#define R_LARCH_SUB24                          54
+#define R_LARCH_SUB32                          55
+#define R_LARCH_SUB64                          56
+#define R_LARCH_GNU_VTINHERIT                  57
+#define R_LARCH_GNU_VTENTRY                    58
+
+#ifndef ELF_ARCH
+
+/* ELF register definitions */
+
+/*
+ * General purpose have the following registers:
+ *     Register        Number
+ *     GPRs            32
+ *     ORIG_A0         1
+ *     ERA             1
+ *     BADVADDR        1
+ *     CRMD            1
+ *     PRMD            1
+ *     EUEN            1
+ *     ECFG            1
+ *     ESTAT           1
+ *     Reserved        5
+ */
+#define ELF_NGREG      45
+
+/*
+ * Floating point have the following registers:
+ *     Register        Number
+ *     FPR             32
+ *     FCC             1
+ *     FCSR            1
+ */
+#define ELF_NFPREG     34
+
+typedef unsigned long elf_greg_t;
+typedef elf_greg_t elf_gregset_t[ELF_NGREG];
+
+typedef double elf_fpreg_t;
+typedef elf_fpreg_t elf_fpregset_t[ELF_NFPREG];
+
+void loongarch_dump_regs64(u64 *uregs, const struct pt_regs *regs);
+
+#ifdef CONFIG_32BIT
+/*
+ * This is used to ensure we don't load something for the wrong architecture.
+ */
+#define elf_check_arch elf32_check_arch
+
+/*
+ * These are used to set parameters in the core dumps.
+ */
+#define ELF_CLASS      ELFCLASS32
+
+#define ELF_CORE_COPY_REGS(dest, regs) \
+       loongarch_dump_regs32((u32 *)&(dest), (regs));
+
+#endif /* CONFIG_32BIT */
+
+#ifdef CONFIG_64BIT
+/*
+ * This is used to ensure we don't load something for the wrong architecture.
+ */
+#define elf_check_arch elf64_check_arch
+
+/*
+ * These are used to set parameters in the core dumps.
+ */
+#define ELF_CLASS      ELFCLASS64
+
+#define ELF_CORE_COPY_REGS(dest, regs) \
+       loongarch_dump_regs64((u64 *)&(dest), (regs));
+
+#endif /* CONFIG_64BIT */
+
+/*
+ * These are used to set parameters in the core dumps.
+ */
+#define ELF_DATA       ELFDATA2LSB
+#define ELF_ARCH       EM_LOONGARCH
+
+#endif /* !defined(ELF_ARCH) */
+
+#define loongarch_elf_check_machine(x) ((x)->e_machine == EM_LOONGARCH)
+
+#define vmcore_elf32_check_arch loongarch_elf_check_machine
+#define vmcore_elf64_check_arch loongarch_elf_check_machine
+
+/*
+ * Return non-zero if HDR identifies an 32bit ELF binary.
+ */
+#define elf32_check_arch(hdr)                                          \
+({                                                                     \
+       int __res = 1;                                                  \
+       struct elfhdr *__h = (hdr);                                     \
+                                                                       \
+       if (!loongarch_elf_check_machine(__h))                          \
+               __res = 0;                                              \
+       if (__h->e_ident[EI_CLASS] != ELFCLASS32)                       \
+               __res = 0;                                              \
+                                                                       \
+       __res;                                                          \
+})
+
+/*
+ * Return non-zero if HDR identifies an 64bit ELF binary.
+ */
+#define elf64_check_arch(hdr)                                          \
+({                                                                     \
+       int __res = 1;                                                  \
+       struct elfhdr *__h = (hdr);                                     \
+                                                                       \
+       if (!loongarch_elf_check_machine(__h))                          \
+               __res = 0;                                              \
+       if (__h->e_ident[EI_CLASS] != ELFCLASS64)                       \
+               __res = 0;                                              \
+                                                                       \
+       __res;                                                          \
+})
+
+#ifdef CONFIG_32BIT
+
+#define SET_PERSONALITY2(ex, state)                                    \
+do {                                                                   \
+       current->thread.vdso = &vdso_info;                              \
+                                                                       \
+       loongarch_set_personality_fcsr(state);                          \
+                                                                       \
+       if (personality(current->personality) != PER_LINUX)             \
+               set_personality(PER_LINUX);                             \
+} while (0)
+
+#endif /* CONFIG_32BIT */
+
+#ifdef CONFIG_64BIT
+
+#define SET_PERSONALITY2(ex, state)                                    \
+do {                                                                   \
+       unsigned int p;                                                 \
+                                                                       \
+       clear_thread_flag(TIF_32BIT_REGS);                              \
+       clear_thread_flag(TIF_32BIT_ADDR);                              \
+                                                                       \
+       current->thread.vdso = &vdso_info;                              \
+       loongarch_set_personality_fcsr(state);                          \
+                                                                       \
+       p = personality(current->personality);                          \
+       if (p != PER_LINUX32 && p != PER_LINUX)                         \
+               set_personality(PER_LINUX);                             \
+} while (0)
+
+#endif /* CONFIG_64BIT */
+
+#define CORE_DUMP_USE_REGSET
+#define ELF_EXEC_PAGESIZE      PAGE_SIZE
+
+/*
+ * This yields a mask that user programs can use to figure out what
+ * instruction set this cpu supports. This could be done in userspace,
+ * but it's not easy, and we've already done it here.
+ */
+
+#define ELF_HWCAP      (elf_hwcap)
+extern unsigned int elf_hwcap;
+#include <asm/hwcap.h>
+
+/*
+ * This yields a string that ld.so will use to load implementation
+ * specific libraries for optimization.         This is more specific in
+ * intent than poking at uname or /proc/cpuinfo.
+ */
+
+#define ELF_PLATFORM  __elf_platform
+extern const char *__elf_platform;
+
+#define ELF_PLAT_INIT(_r, load_addr)   do { \
+       _r->regs[1] = _r->regs[2] = _r->regs[3] = _r->regs[4] = 0;      \
+       _r->regs[5] = _r->regs[6] = _r->regs[7] = _r->regs[8] = 0;      \
+       _r->regs[9] = _r->regs[10] = _r->regs[11] = _r->regs[12] = 0;   \
+       _r->regs[13] = _r->regs[14] = _r->regs[15] = _r->regs[16] = 0;  \
+       _r->regs[17] = _r->regs[18] = _r->regs[19] = _r->regs[20] = 0;  \
+       _r->regs[21] = _r->regs[22] = _r->regs[23] = _r->regs[24] = 0;  \
+       _r->regs[25] = _r->regs[26] = _r->regs[27] = _r->regs[28] = 0;  \
+       _r->regs[29] = _r->regs[30] = _r->regs[31] = 0;                 \
+} while (0)
+
+/*
+ * This is the location that an ET_DYN program is loaded if exec'ed. Typical
+ * use of this is to invoke "./ld.so someprog" to test out a new version of
+ * the loader. We need to make sure that it is out of the way of the program
+ * that it will "exec", and that there is sufficient room for the brk.
+ */
+
+#define ELF_ET_DYN_BASE                (TASK_SIZE / 3 * 2)
+
+/* update AT_VECTOR_SIZE_ARCH if the number of NEW_AUX_ENT entries changes */
+#define ARCH_DLINFO                                                    \
+do {                                                                   \
+       NEW_AUX_ENT(AT_SYSINFO_EHDR,                                    \
+                   (unsigned long)current->mm->context.vdso);          \
+} while (0)
+
+#define ARCH_HAS_SETUP_ADDITIONAL_PAGES 1
+struct linux_binprm;
+extern int arch_setup_additional_pages(struct linux_binprm *bprm,
+                                      int uses_interp);
+
+struct arch_elf_state {
+       int fp_abi;
+       int interp_fp_abi;
+};
+
+#define LOONGARCH_ABI_FP_ANY   (0)
+
+#define INIT_ARCH_ELF_STATE {                  \
+       .fp_abi = LOONGARCH_ABI_FP_ANY,         \
+       .interp_fp_abi = LOONGARCH_ABI_FP_ANY,  \
+}
+
+#define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
+
+extern int arch_elf_pt_proc(void *ehdr, void *phdr, struct file *elf,
+                           bool is_interp, struct arch_elf_state *state);
+
+extern int arch_check_elf(void *ehdr, bool has_interpreter, void *interp_ehdr,
+                         struct arch_elf_state *state);
+
+extern void loongarch_set_personality_fcsr(struct arch_elf_state *state);
+
+#endif /* _ASM_ELF_H */
diff --git a/arch/loongarch/include/asm/exec.h b/arch/loongarch/include/asm/exec.h
new file mode 100644 (file)
index 0000000..ba02208
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#ifndef _ASM_EXEC_H
+#define _ASM_EXEC_H
+
+extern unsigned long arch_align_stack(unsigned long sp);
+
+#endif /* _ASM_EXEC_H */
diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
new file mode 100644 (file)
index 0000000..9f6718d
--- /dev/null
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#ifndef _ASM_MODULE_H
+#define _ASM_MODULE_H
+
+#include <asm/inst.h>
+#include <asm-generic/module.h>
+
+#define RELA_STACK_DEPTH 16
+
+struct mod_section {
+       Elf_Shdr *shdr;
+       int num_entries;
+       int max_entries;
+};
+
+struct mod_arch_specific {
+       struct mod_section plt;
+       struct mod_section plt_idx;
+};
+
+struct plt_entry {
+       u32 inst_lu12iw;
+       u32 inst_lu32id;
+       u32 inst_lu52id;
+       u32 inst_jirl;
+};
+
+struct plt_idx_entry {
+       unsigned long symbol_addr;
+};
+
+Elf_Addr module_emit_plt_entry(struct module *mod, unsigned long val);
+
+static inline struct plt_entry emit_plt_entry(unsigned long val)
+{
+       u32 lu12iw, lu32id, lu52id, jirl;
+
+       lu12iw = (lu12iw_op << 25 | (((val >> 12) & 0xfffff) << 5) | LOONGARCH_GPR_T1);
+       lu32id = larch_insn_gen_lu32id(LOONGARCH_GPR_T1, ADDR_IMM(val, LU32ID));
+       lu52id = larch_insn_gen_lu52id(LOONGARCH_GPR_T1, LOONGARCH_GPR_T1, ADDR_IMM(val, LU52ID));
+       jirl = larch_insn_gen_jirl(0, LOONGARCH_GPR_T1, 0, (val & 0xfff));
+
+       return (struct plt_entry) { lu12iw, lu32id, lu52id, jirl };
+}
+
+static inline struct plt_idx_entry emit_plt_idx_entry(unsigned long val)
+{
+       return (struct plt_idx_entry) { val };
+}
+
+static inline int get_plt_idx(unsigned long val, const struct mod_section *sec)
+{
+       int i;
+       struct plt_idx_entry *plt_idx = (struct plt_idx_entry *)sec->shdr->sh_addr;
+
+       for (i = 0; i < sec->num_entries; i++) {
+               if (plt_idx[i].symbol_addr == val)
+                       return i;
+       }
+
+       return -1;
+}
+
+static inline struct plt_entry *get_plt_entry(unsigned long val,
+                                     const struct mod_section *sec_plt,
+                                     const struct mod_section *sec_plt_idx)
+{
+       int plt_idx = get_plt_idx(val, sec_plt_idx);
+       struct plt_entry *plt = (struct plt_entry *)sec_plt->shdr->sh_addr;
+
+       if (plt_idx < 0)
+               return NULL;
+
+       return plt + plt_idx;
+}
+
+#endif /* _ASM_MODULE_H */
diff --git a/arch/loongarch/include/asm/module.lds.h b/arch/loongarch/include/asm/module.lds.h
new file mode 100644 (file)
index 0000000..31c1c0d
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2020-2022 Loongson Technology Corporation Limited */
+SECTIONS {
+       . = ALIGN(4);
+       .plt : { BYTE(0) }
+       .plt.idx : { BYTE(0) }
+}
diff --git a/arch/loongarch/include/asm/vermagic.h b/arch/loongarch/include/asm/vermagic.h
new file mode 100644 (file)
index 0000000..8b47ccf
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#ifndef _ASM_VERMAGIC_H
+#define _ASM_VERMAGIC_H
+
+#define MODULE_PROC_FAMILY "LOONGARCH "
+
+#ifdef CONFIG_32BIT
+#define MODULE_KERNEL_TYPE "32BIT "
+#elif defined CONFIG_64BIT
+#define MODULE_KERNEL_TYPE "64BIT "
+#endif
+
+#define MODULE_ARCH_VERMAGIC \
+       MODULE_PROC_FAMILY MODULE_KERNEL_TYPE
+
+#endif /* _ASM_VERMAGIC_H */
diff --git a/arch/loongarch/include/uapi/asm/auxvec.h b/arch/loongarch/include/uapi/asm/auxvec.h
new file mode 100644 (file)
index 0000000..922d9e6
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Author: Hanlu Li <lihanlu@loongson.cn>
+ *         Huacai Chen <chenhuacai@loongson.cn>
+ *
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#ifndef __ASM_AUXVEC_H
+#define __ASM_AUXVEC_H
+
+/* Location of VDSO image. */
+#define AT_SYSINFO_EHDR                33
+
+#define AT_VECTOR_SIZE_ARCH 1 /* entries in ARCH_DLINFO */
+
+#endif /* __ASM_AUXVEC_H */
diff --git a/arch/loongarch/include/uapi/asm/hwcap.h b/arch/loongarch/include/uapi/asm/hwcap.h
new file mode 100644 (file)
index 0000000..8840b72
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_ASM_HWCAP_H
+#define _UAPI_ASM_HWCAP_H
+
+/* HWCAP flags */
+#define HWCAP_LOONGARCH_CPUCFG         (1 << 0)
+#define HWCAP_LOONGARCH_LAM            (1 << 1)
+#define HWCAP_LOONGARCH_UAL            (1 << 2)
+#define HWCAP_LOONGARCH_FPU            (1 << 3)
+#define HWCAP_LOONGARCH_LSX            (1 << 4)
+#define HWCAP_LOONGARCH_LASX           (1 << 5)
+#define HWCAP_LOONGARCH_CRC32          (1 << 6)
+#define HWCAP_LOONGARCH_COMPLEX                (1 << 7)
+#define HWCAP_LOONGARCH_CRYPTO         (1 << 8)
+#define HWCAP_LOONGARCH_LVZ            (1 << 9)
+#define HWCAP_LOONGARCH_LBT_X86                (1 << 10)
+#define HWCAP_LOONGARCH_LBT_ARM                (1 << 11)
+#define HWCAP_LOONGARCH_LBT_MIPS       (1 << 12)
+
+#endif /* _UAPI_ASM_HWCAP_H */
diff --git a/arch/loongarch/kernel/elf.c b/arch/loongarch/kernel/elf.c
new file mode 100644 (file)
index 0000000..183e94f
--- /dev/null
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Huacai Chen <chenhuacai@loongson.cn>
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#include <linux/binfmts.h>
+#include <linux/elf.h>
+#include <linux/export.h>
+#include <linux/sched.h>
+
+#include <asm/cpu-features.h>
+#include <asm/cpu-info.h>
+
+int arch_elf_pt_proc(void *_ehdr, void *_phdr, struct file *elf,
+                    bool is_interp, struct arch_elf_state *state)
+{
+       return 0;
+}
+
+int arch_check_elf(void *_ehdr, bool has_interpreter, void *_interp_ehdr,
+                  struct arch_elf_state *state)
+{
+       return 0;
+}
+
+void loongarch_set_personality_fcsr(struct arch_elf_state *state)
+{
+       current->thread.fpu.fcsr = boot_cpu_data.fpu_csr0;
+}
diff --git a/arch/loongarch/kernel/inst.c b/arch/loongarch/kernel/inst.c
new file mode 100644 (file)
index 0000000..b1df0ec
--- /dev/null
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#include <asm/inst.h>
+
+u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm)
+{
+       union loongarch_instruction insn;
+
+       insn.reg1i20_format.opcode = lu32id_op;
+       insn.reg1i20_format.rd = rd;
+       insn.reg1i20_format.immediate = imm;
+
+       return insn.word;
+}
+
+u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm)
+{
+       union loongarch_instruction insn;
+
+       insn.reg2i12_format.opcode = lu52id_op;
+       insn.reg2i12_format.rd = rd;
+       insn.reg2i12_format.rj = rj;
+       insn.reg2i12_format.immediate = imm;
+
+       return insn.word;
+}
+
+u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest)
+{
+       union loongarch_instruction insn;
+
+       insn.reg2i16_format.opcode = jirl_op;
+       insn.reg2i16_format.rd = rd;
+       insn.reg2i16_format.rj = rj;
+       insn.reg2i16_format.immediate = (dest - pc) >> 2;
+
+       return insn.word;
+}
diff --git a/arch/loongarch/kernel/module-sections.c b/arch/loongarch/kernel/module-sections.c
new file mode 100644 (file)
index 0000000..6d49828
--- /dev/null
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#include <linux/elf.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+Elf_Addr module_emit_plt_entry(struct module *mod, unsigned long val)
+{
+       int nr;
+       struct mod_section *plt_sec = &mod->arch.plt;
+       struct mod_section *plt_idx_sec = &mod->arch.plt_idx;
+       struct plt_entry *plt = get_plt_entry(val, plt_sec, plt_idx_sec);
+       struct plt_idx_entry *plt_idx;
+
+       if (plt)
+               return (Elf_Addr)plt;
+
+       nr = plt_sec->num_entries;
+
+       /* There is no duplicate entry, create a new one */
+       plt = (struct plt_entry *)plt_sec->shdr->sh_addr;
+       plt[nr] = emit_plt_entry(val);
+       plt_idx = (struct plt_idx_entry *)plt_idx_sec->shdr->sh_addr;
+       plt_idx[nr] = emit_plt_idx_entry(val);
+
+       plt_sec->num_entries++;
+       plt_idx_sec->num_entries++;
+       BUG_ON(plt_sec->num_entries > plt_sec->max_entries);
+
+       return (Elf_Addr)&plt[nr];
+}
+
+static int is_rela_equal(const Elf_Rela *x, const Elf_Rela *y)
+{
+       return x->r_info == y->r_info && x->r_addend == y->r_addend;
+}
+
+static bool duplicate_rela(const Elf_Rela *rela, int idx)
+{
+       int i;
+
+       for (i = 0; i < idx; i++) {
+               if (is_rela_equal(&rela[i], &rela[idx]))
+                       return true;
+       }
+
+       return false;
+}
+
+static void count_max_entries(Elf_Rela *relas, int num, unsigned int *plts)
+{
+       unsigned int i, type;
+
+       for (i = 0; i < num; i++) {
+               type = ELF_R_TYPE(relas[i].r_info);
+               if (type == R_LARCH_SOP_PUSH_PLT_PCREL) {
+                       if (!duplicate_rela(relas, i))
+                               (*plts)++;
+               }
+       }
+}
+
+int module_frob_arch_sections(Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
+                             char *secstrings, struct module *mod)
+{
+       unsigned int i, num_plts = 0;
+
+       /*
+        * Find the empty .plt sections.
+        */
+       for (i = 0; i < ehdr->e_shnum; i++) {
+               if (!strcmp(secstrings + sechdrs[i].sh_name, ".plt"))
+                       mod->arch.plt.shdr = sechdrs + i;
+               else if (!strcmp(secstrings + sechdrs[i].sh_name, ".plt.idx"))
+                       mod->arch.plt_idx.shdr = sechdrs + i;
+       }
+
+       if (!mod->arch.plt.shdr) {
+               pr_err("%s: module PLT section(s) missing\n", mod->name);
+               return -ENOEXEC;
+       }
+       if (!mod->arch.plt_idx.shdr) {
+               pr_err("%s: module PLT.IDX section(s) missing\n", mod->name);
+               return -ENOEXEC;
+       }
+
+       /* Calculate the maxinum number of entries */
+       for (i = 0; i < ehdr->e_shnum; i++) {
+               int num_rela = sechdrs[i].sh_size / sizeof(Elf_Rela);
+               Elf_Rela *relas = (void *)ehdr + sechdrs[i].sh_offset;
+               Elf_Shdr *dst_sec = sechdrs + sechdrs[i].sh_info;
+
+               if (sechdrs[i].sh_type != SHT_RELA)
+                       continue;
+
+               /* ignore relocations that operate on non-exec sections */
+               if (!(dst_sec->sh_flags & SHF_EXECINSTR))
+                       continue;
+
+               count_max_entries(relas, num_rela, &num_plts);
+       }
+
+       mod->arch.plt.shdr->sh_type = SHT_NOBITS;
+       mod->arch.plt.shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
+       mod->arch.plt.shdr->sh_addralign = L1_CACHE_BYTES;
+       mod->arch.plt.shdr->sh_size = (num_plts + 1) * sizeof(struct plt_entry);
+       mod->arch.plt.num_entries = 0;
+       mod->arch.plt.max_entries = num_plts;
+
+       mod->arch.plt_idx.shdr->sh_type = SHT_NOBITS;
+       mod->arch.plt_idx.shdr->sh_flags = SHF_ALLOC;
+       mod->arch.plt_idx.shdr->sh_addralign = L1_CACHE_BYTES;
+       mod->arch.plt_idx.shdr->sh_size = (num_plts + 1) * sizeof(struct plt_idx_entry);
+       mod->arch.plt_idx.num_entries = 0;
+       mod->arch.plt_idx.max_entries = num_plts;
+
+       return 0;
+}
diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
new file mode 100644 (file)
index 0000000..87b3768
--- /dev/null
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: Hanlu Li <lihanlu@loongson.cn>
+ *         Huacai Chen <chenhuacai@loongson.cn>
+ *
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#define pr_fmt(fmt) "kmod: " fmt
+
+#include <linux/moduleloader.h>
+#include <linux/elf.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+
+static inline bool signed_imm_check(long val, unsigned int bit)
+{
+       return -(1L << (bit - 1)) <= val && val < (1L << (bit - 1));
+}
+
+static inline bool unsigned_imm_check(unsigned long val, unsigned int bit)
+{
+       return val < (1UL << bit);
+}
+
+static int rela_stack_push(s64 stack_value, s64 *rela_stack, size_t *rela_stack_top)
+{
+       if (*rela_stack_top >= RELA_STACK_DEPTH)
+               return -ENOEXEC;
+
+       rela_stack[(*rela_stack_top)++] = stack_value;
+       pr_debug("%s stack_value = 0x%llx\n", __func__, stack_value);
+
+       return 0;
+}
+
+static int rela_stack_pop(s64 *stack_value, s64 *rela_stack, size_t *rela_stack_top)
+{
+       if (*rela_stack_top == 0)
+               return -ENOEXEC;
+
+       *stack_value = rela_stack[--(*rela_stack_top)];
+       pr_debug("%s stack_value = 0x%llx\n", __func__, *stack_value);
+
+       return 0;
+}
+
+static int apply_r_larch_none(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       return 0;
+}
+
+static int apply_r_larch_error(struct module *me, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       pr_err("%s: Unsupport relocation type %u, please add its support.\n", me->name, type);
+       return -EINVAL;
+}
+
+static int apply_r_larch_32(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       *location = v;
+       return 0;
+}
+
+static int apply_r_larch_64(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       *(Elf_Addr *)location = v;
+       return 0;
+}
+
+static int apply_r_larch_sop_push_pcrel(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       return rela_stack_push(v - (u64)location, rela_stack, rela_stack_top);
+}
+
+static int apply_r_larch_sop_push_absolute(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       return rela_stack_push(v, rela_stack, rela_stack_top);
+}
+
+static int apply_r_larch_sop_push_dup(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       int err = 0;
+       s64 opr1;
+
+       err = rela_stack_pop(&opr1, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+       err = rela_stack_push(opr1, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+       err = rela_stack_push(opr1, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int apply_r_larch_sop_push_plt_pcrel(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       ptrdiff_t offset = (void *)v - (void *)location;
+
+       if (offset >= SZ_128M)
+               v = module_emit_plt_entry(mod, v);
+
+       if (offset < -SZ_128M)
+               v = module_emit_plt_entry(mod, v);
+
+       return apply_r_larch_sop_push_pcrel(mod, location, v, rela_stack, rela_stack_top, type);
+}
+
+static int apply_r_larch_sop(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       int err = 0;
+       s64 opr1, opr2, opr3;
+
+       if (type == R_LARCH_SOP_IF_ELSE) {
+               err = rela_stack_pop(&opr3, rela_stack, rela_stack_top);
+               if (err)
+                       return err;
+       }
+
+       err = rela_stack_pop(&opr2, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+       err = rela_stack_pop(&opr1, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+
+       switch (type) {
+       case R_LARCH_SOP_AND:
+               err = rela_stack_push(opr1 & opr2, rela_stack, rela_stack_top);
+               break;
+       case R_LARCH_SOP_ADD:
+               err = rela_stack_push(opr1 + opr2, rela_stack, rela_stack_top);
+               break;
+       case R_LARCH_SOP_SUB:
+               err = rela_stack_push(opr1 - opr2, rela_stack, rela_stack_top);
+               break;
+       case R_LARCH_SOP_SL:
+               err = rela_stack_push(opr1 << opr2, rela_stack, rela_stack_top);
+               break;
+       case R_LARCH_SOP_SR:
+               err = rela_stack_push(opr1 >> opr2, rela_stack, rela_stack_top);
+               break;
+       case R_LARCH_SOP_IF_ELSE:
+               err = rela_stack_push(opr1 ? opr2 : opr3, rela_stack, rela_stack_top);
+               break;
+       default:
+               pr_err("%s: Unsupport relocation type %u\n", mod->name, type);
+               return -EINVAL;
+       }
+
+       return err;
+}
+
+static int apply_r_larch_sop_imm_field(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       int err = 0;
+       s64 opr1;
+       union loongarch_instruction *insn = (union loongarch_instruction *)location;
+
+       err = rela_stack_pop(&opr1, rela_stack, rela_stack_top);
+       if (err)
+               return err;
+
+       switch (type) {
+       case R_LARCH_SOP_POP_32_U_10_12:
+               if (!unsigned_imm_check(opr1, 12))
+                       goto overflow;
+
+               /* (*(uint32_t *) PC) [21 ... 10] = opr [11 ... 0] */
+               insn->reg2i12_format.immediate = opr1 & 0xfff;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_10_12:
+               if (!signed_imm_check(opr1, 12))
+                       goto overflow;
+
+               insn->reg2i12_format.immediate = opr1 & 0xfff;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_10_16:
+               if (!signed_imm_check(opr1, 16))
+                       goto overflow;
+
+               insn->reg2i16_format.immediate = opr1 & 0xffff;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_10_16_S2:
+               if (opr1 % 4)
+                       goto unaligned;
+
+               if (!signed_imm_check(opr1, 18))
+                       goto overflow;
+
+               insn->reg2i16_format.immediate = (opr1 >> 2) & 0xffff;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_5_20:
+               if (!signed_imm_check(opr1, 20))
+                       goto overflow;
+
+               insn->reg1i20_format.immediate = (opr1) & 0xfffff;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_0_5_10_16_S2:
+               if (opr1 % 4)
+                       goto unaligned;
+
+               if (!signed_imm_check(opr1, 23))
+                       goto overflow;
+
+               opr1 >>= 2;
+               insn->reg1i21_format.immediate_l = opr1 & 0xffff;
+               insn->reg1i21_format.immediate_h = (opr1 >> 16) & 0x1f;
+               return 0;
+       case R_LARCH_SOP_POP_32_S_0_10_10_16_S2:
+               if (opr1 % 4)
+                       goto unaligned;
+
+               if (!signed_imm_check(opr1, 28))
+                       goto overflow;
+
+               opr1 >>= 2;
+               insn->reg0i26_format.immediate_l = opr1 & 0xffff;
+               insn->reg0i26_format.immediate_h = (opr1 >> 16) & 0x3ff;
+               return 0;
+       case R_LARCH_SOP_POP_32_U:
+               if (!unsigned_imm_check(opr1, 32))
+                       goto overflow;
+
+               /* (*(uint32_t *) PC) = opr */
+               *location = (u32)opr1;
+               return 0;
+       default:
+               pr_err("%s: Unsupport relocation type %u\n", mod->name, type);
+               return -EINVAL;
+       }
+
+overflow:
+       pr_err("module %s: opr1 = 0x%llx overflow! dangerous %s (%u) relocation\n",
+               mod->name, opr1, __func__, type);
+       return -ENOEXEC;
+
+unaligned:
+       pr_err("module %s: opr1 = 0x%llx unaligned! dangerous %s (%u) relocation\n",
+               mod->name, opr1, __func__, type);
+       return -ENOEXEC;
+}
+
+static int apply_r_larch_add_sub(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type)
+{
+       switch (type) {
+       case R_LARCH_ADD32:
+               *(s32 *)location += v;
+               return 0;
+       case R_LARCH_ADD64:
+               *(s64 *)location += v;
+               return 0;
+       case R_LARCH_SUB32:
+               *(s32 *)location -= v;
+               return 0;
+       case R_LARCH_SUB64:
+               *(s64 *)location -= v;
+               return 0;
+       default:
+               pr_err("%s: Unsupport relocation type %u\n", mod->name, type);
+               return -EINVAL;
+       }
+}
+
+/*
+ * reloc_handlers_rela() - Apply a particular relocation to a module
+ * @mod: the module to apply the reloc to
+ * @location: the address at which the reloc is to be applied
+ * @v: the value of the reloc, with addend for RELA-style
+ * @rela_stack: the stack used for store relocation info, LOCAL to THIS module
+ * @rela_stac_top: where the stack operation(pop/push) applies to
+ *
+ * Return: 0 upon success, else -ERRNO
+ */
+typedef int (*reloc_rela_handler)(struct module *mod, u32 *location, Elf_Addr v,
+                       s64 *rela_stack, size_t *rela_stack_top, unsigned int type);
+
+/* The handlers for known reloc types */
+static reloc_rela_handler reloc_rela_handlers[] = {
+       [R_LARCH_NONE ... R_LARCH_SUB64]                     = apply_r_larch_error,
+
+       [R_LARCH_NONE]                                       = apply_r_larch_none,
+       [R_LARCH_32]                                         = apply_r_larch_32,
+       [R_LARCH_64]                                         = apply_r_larch_64,
+       [R_LARCH_MARK_LA]                                    = apply_r_larch_none,
+       [R_LARCH_MARK_PCREL]                                 = apply_r_larch_none,
+       [R_LARCH_SOP_PUSH_PCREL]                             = apply_r_larch_sop_push_pcrel,
+       [R_LARCH_SOP_PUSH_ABSOLUTE]                          = apply_r_larch_sop_push_absolute,
+       [R_LARCH_SOP_PUSH_DUP]                               = apply_r_larch_sop_push_dup,
+       [R_LARCH_SOP_PUSH_PLT_PCREL]                         = apply_r_larch_sop_push_plt_pcrel,
+       [R_LARCH_SOP_SUB ... R_LARCH_SOP_IF_ELSE]            = apply_r_larch_sop,
+       [R_LARCH_SOP_POP_32_S_10_5 ... R_LARCH_SOP_POP_32_U] = apply_r_larch_sop_imm_field,
+       [R_LARCH_ADD32 ... R_LARCH_SUB64]                    = apply_r_larch_add_sub,
+};
+
+int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
+                      unsigned int symindex, unsigned int relsec,
+                      struct module *mod)
+{
+       int i, err;
+       unsigned int type;
+       s64 rela_stack[RELA_STACK_DEPTH];
+       size_t rela_stack_top = 0;
+       reloc_rela_handler handler;
+       void *location;
+       Elf_Addr v;
+       Elf_Sym *sym;
+       Elf_Rela *rel = (void *) sechdrs[relsec].sh_addr;
+
+       pr_debug("%s: Applying relocate section %u to %u\n", __func__, relsec,
+              sechdrs[relsec].sh_info);
+
+       rela_stack_top = 0;
+       for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
+               /* This is where to make the change */
+               location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset;
+               /* This is the symbol it is referring to */
+               sym = (Elf_Sym *)sechdrs[symindex].sh_addr + ELF_R_SYM(rel[i].r_info);
+               if (IS_ERR_VALUE(sym->st_value)) {
+                       /* Ignore unresolved weak symbol */
+                       if (ELF_ST_BIND(sym->st_info) == STB_WEAK)
+                               continue;
+                       pr_warn("%s: Unknown symbol %s\n", mod->name, strtab + sym->st_name);
+                       return -ENOENT;
+               }
+
+               type = ELF_R_TYPE(rel[i].r_info);
+
+               if (type < ARRAY_SIZE(reloc_rela_handlers))
+                       handler = reloc_rela_handlers[type];
+               else
+                       handler = NULL;
+
+               if (!handler) {
+                       pr_err("%s: Unknown relocation type %u\n", mod->name, type);
+                       return -EINVAL;
+               }
+
+               pr_debug("type %d st_value %llx r_addend %llx loc %llx\n",
+                      (int)ELF_R_TYPE(rel[i].r_info),
+                      sym->st_value, rel[i].r_addend, (u64)location);
+
+               v = sym->st_value + rel[i].r_addend;
+               err = handler(mod, location, v, rela_stack, &rela_stack_top, type);
+               if (err)
+                       return err;
+       }
+
+       return 0;
+}
+
+void *module_alloc(unsigned long size)
+{
+       return __vmalloc_node_range(size, 1, MODULES_VADDR, MODULES_END,
+                       GFP_KERNEL, PAGE_KERNEL, 0, NUMA_NO_NODE, __builtin_return_address(0));
+}