[PATCH] riscv: Work to remove kernel dependence on the M-extension

Michael T. Kloos
2022-03-09T05:29:04Z

Added a new config symbol RISCV_ISA_M to enable the usage of the
multiplication, division, and remainder (modulus) instructions
from the M-extension. This configures the march build flag to
either include or omit it.

I didn't find any assembly using any of the instructions from
the M-extension. However, the BPF JIT is a complicating factor.
Currently, it emits M-extension instructions to implement various
BPF operations. For now, I have made HAVE_EBPF_JIT depend on
CONFIG_RISCV_ISA_M.

I have added the supplementary integer arithmetic functions in
the file "arch/riscv/lib/ext_m_supplement.c". All the code
contained in this file is wrapped in an ifndef contingent on the
presence of CONFIG_RISCV_ISA_M.

Signed-off-by: Michael T. Kloos <michael@michaelkloos.com>
---
arch/riscv/Kconfig | 20 +-
arch/riscv/Makefile | 6 +-
arch/riscv/lib/Makefile | 1 +
arch/riscv/lib/ext_m_supplement.c | 588 ++++++++++++++++++++++++++++++
4 files changed, 608 insertions(+), 7 deletions(-)
create mode 100644 arch/riscv/lib/ext_m_supplement.c

diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 5adcbd9b5e88..40e1110a405c 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -83,7 +83,7 @@ config RISCV
select HAVE_CONTEXT_TRACKING
select HAVE_DEBUG_KMEMLEAK
select HAVE_DMA_CONTIGUOUS if MMU
- select HAVE_EBPF_JIT if MMU
+ select HAVE_EBPF_JIT if (MMU && RISCV_ISA_M)
select HAVE_FUNCTION_ERROR_INJECTION
select HAVE_GCC_PLUGINS
select HAVE_GENERIC_VDSO if MMU && 64BIT
@@ -323,15 +323,25 @@ config NODES_SHIFT
Specify the maximum number of NUMA Nodes available on the target
system. Increases memory reserved to accommodate various tables.

+config RISCV_ISA_M
+ bool "Emit multiplication instructions when building Linux"
+ default y
+ help
+ Adds "M" to the ISA subsets that the toolchain is allowed to emit
+ when building Linux, which results in multiplication, division, and
+ remainder instructions in the Linux binary.
+
+ If you don't know what to do here, say Y.
+
config RISCV_ISA_C
bool "Emit compressed instructions when building Linux"
default y
help
- Adds "C" to the ISA subsets that the toolchain is allowed to emit
- when building Linux, which results in compressed instructions in the
- Linux binary.
+ Adds "C" to the ISA subsets that the toolchain is allowed to emit
+ when building Linux, which results in compressed instructions in the
+ Linux binary.

- If you don't know what to do here, say Y.
+ If you don't know what to do here, say Y.

menu "supported PMU type"
depends on PERF_EVENTS
diff --git a/arch/riscv/Makefile b/arch/riscv/Makefile
index 7d81102cffd4..7e24cfe51ef7 100644
--- a/arch/riscv/Makefile
+++ b/arch/riscv/Makefile
@@ -46,8 +46,10 @@ endif
endif

# ISA string setting
-riscv-march-$(CONFIG_ARCH_RV32I) := rv32ima
-riscv-march-$(CONFIG_ARCH_RV64I) := rv64ima
+riscv-march-$(CONFIG_ARCH_RV32I) := rv32i
+riscv-march-$(CONFIG_ARCH_RV64I) := rv64i
+riscv-march-$(CONFIG_RISCV_ISA_M) := $(riscv-march-y)m
+riscv-march-y := $(riscv-march-y)a
riscv-march-$(CONFIG_FPU) := $(riscv-march-y)fd
riscv-march-$(CONFIG_RISCV_ISA_C) := $(riscv-march-y)c

diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile
index 25d5c9664e57..965eebaaa1ce 100644
--- a/arch/riscv/lib/Makefile
+++ b/arch/riscv/lib/Makefile
@@ -5,5 +5,6 @@ lib-y += memset.o
lib-y += memmove.o
lib-$(CONFIG_MMU) += uaccess.o
lib-$(CONFIG_64BIT) += tishift.o
+lib-y += ext_m_supplement.o

obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o
diff --git a/arch/riscv/lib/ext_m_supplement.c b/arch/riscv/lib/ext_m_supplement.c
new file mode 100644
index 000000000000..42ced0ea9fe2
--- /dev/null
+++ b/arch/riscv/lib/ext_m_supplement.c
@@ -0,0 +1,588 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 Michael T. Kloos <michael@michaelkloos.com>
+ */
+
+/*
+ * The GNU manual page here:
+ * https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
+ * describes these functions in terms of signed and unsigned, int and long.
+ * However, these prototypes are wrong when the size of int and long are
+ * considered per the RISC-V ABI specification. The RISC-V port of the GCC
+ * complier does not follow the standard of those prototypes. This is
+ * discussed in this thread:
+ * https://github.com/riscv-collab/riscv-gcc/issues/324
+ *
+ * On the RISC-V Architecture:
+ * - __xyyysi3 always refers to 32-bit integers.
+ * - __xyyydi3 always refers to 64-bit integers.
+ * - __xyyyti3 always refers to 128-bit integers. (Only implemented for rv64)
+ *
+ * Per the RISC-V ABI specification, the C base types are:
+ * - int: 32-bits wide.
+ * - long: XLEN-bits wide. It matches the register width.
+ * - long long: 64-bits wide.
+ *
+ * Therefore, the correct RISC-V function prototypes are:
+ * - signed int __mulsi3(signed int a, signed int b);
+ * - signed long long __muldi3(signed long long a, signed long long b);
+ * - signed __int128 __multi3(signed __int128 a, signed __int128 b);
+ *
+ * - signed int __divsi3(signed int a, signed int b);
+ * - signed long long __divdi3(signed long long a, signed long long b);
+ * - signed __int128 __divti3(signed __int128 a, signed __int128 b);
+ *
+ * - unsigned int __udivsi3(unsigned int a, unsigned int b);
+ * - unsigned long long __udivdi3(unsigned long long a, unsigned long long b);
+ * - unsigned __int128 __udivti3(unsigned __int128 a, unsigned __int128 b);
+ *
+ * - signed int __modsi3(signed int a, signed int b);
+ * - signed long long __moddi3(signed long long a, signed long long b);
+ * - signed __int128 __modti3(signed __int128 a, signed __int128 b);
+ *
+ * - unsigned int __umodsi3(unsigned int a, unsigned int b);
+ * - unsigned long long __umoddi3(unsigned long long a, unsigned long long b);
+ * - unsigned __int128 __umodti3(unsigned __int128 a, unsigned __int128 b);
+ */
+
+/*
+ * This C code is not portable across architectures. It is RISC-V specific.
+ */
+
+#ifndef CONFIG_RISCV_ISA_M
+
+#include <linux/export.h>
+
+signed int __mulsi3(signed int a, signed int b)
+{
+ unsigned int ua;
+ unsigned int ub;
+ unsigned int j;
+ unsigned int i;
+ signed int r;
+
+ ua = a;
+ ub = b;
+
+ j = 0;
+ for (i = 0; i < sizeof(signed int) * 8; i++) {
+ if (!ua || !ub)
+ break;
+ if (ua & 0x1)
+ j += ub;
+ ua >>= 1;
+ ub <<= 1;
+ }
+
+ r = j;
+
+ return r;
+}
+EXPORT_SYMBOL(__mulsi3);
+
+signed long long __muldi3(signed long long a, signed long long b)
+{
+ unsigned long long ua;
+ unsigned long long ub;
+ unsigned long long j;
+ unsigned int i;
+ signed long long r;
+
+ ua = a;
+ ub = b;
+
+ j = 0;
+ for (i = 0; i < sizeof(signed long long) * 8; i++) {
+ if (!ua || !ub)
+ break;
+ if (ua & 0x1)
+ j += ub;
+ ua >>= 1;
+ ub <<= 1;
+ }
+
+ r = j;
+
+ return r;
+}
+EXPORT_SYMBOL(__muldi3);
+
+#ifdef CONFIG_64BIT
+signed __int128 __multi3(signed __int128 a, signed __int128 b)
+{
+ unsigned __int128 ua;
+ unsigned __int128 ub;
+ unsigned __int128 j;
+ unsigned int i;
+ signed __int128 r;
+
+ ua = a;
+ ub = b;
+
+ j = 0;
+ for (i = 0; i < sizeof(signed __int128) * 8; i++) {
+ if (!ua || !ub)
+ break;
+ if (ua & 0x1)
+ j += ub;
+ ua >>= 1;
+ ub <<= 1;
+ }
+
+ r = j;
+
+ return r;
+}
+EXPORT_SYMBOL(__multi3);
+#endif
+
+signed int __divsi3(signed int a, signed int b)
+{
+ unsigned int ua;
+ unsigned int ub;
+ unsigned int j;
+ unsigned int i;
+ signed int r;
+
+ if (b == 0)
+ return (signed int)(-1);
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ j = 0;
+ i = 0;
+ while (ua >= ub) {
+ if ((signed int)ub < 0) {
+ ua -= ub;
+ j |= 1u << i;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub) {
+ ua -= ub;
+ j |= 1u << i;
+ }
+ }
+
+ r = j;
+ a ^= b;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__divsi3);
+
+signed long long __divdi3(signed long long a, signed long long b)
+{
+ unsigned long long ua;
+ unsigned long long ub;
+ unsigned long long j;
+ unsigned int i;
+ signed long long r;
+
+ if (b == 0)
+ return (signed long long)(-1);
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ j = 0;
+ i = 0;
+ while (ua >= ub) {
+ if ((signed long long)ub < 0) {
+ ua -= ub;
+ j |= 1ull << i;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub) {
+ ua -= ub;
+ j |= 1ull << i;
+ }
+ }
+
+ r = j;
+ a ^= b;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__divdi3);
+
+#ifdef CONFIG_64BIT
+signed __int128 __divti3(signed __int128 a, signed __int128 b)
+{
+ unsigned __int128 ua;
+ unsigned __int128 ub;
+ unsigned __int128 j;
+ unsigned int i;
+ signed __int128 r;
+
+ if (b == 0)
+ return (signed __int128)(-1);
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ j = 0;
+ i = 0;
+ while (ua >= ub) {
+ if ((signed __int128)ub < 0) {
+ ua -= ub;
+ j |= ((unsigned __int128)1) << i;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub) {
+ ua -= ub;
+ j |= ((unsigned __int128)1) << i;
+ }
+ }
+
+ r = j;
+ a ^= b;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__divti3);
+#endif
+
+unsigned int __udivsi3(unsigned int a, unsigned int b)
+{
+ unsigned int j;
+ unsigned int i;
+
+ if (b == 0)
+ return (signed int)(-1);
+
+ j = 0;
+ i = 0;
+ while (a >= b) {
+ if ((signed int)b < 0) {
+ a -= b;
+ j |= 1u << i;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b) {
+ a -= b;
+ j |= 1u << i;
+ }
+ }
+
+ return j;
+}
+EXPORT_SYMBOL(__udivsi3);
+
+unsigned long long __udivdi3(unsigned long long a, unsigned long long b)
+{
+ unsigned long long j;
+ unsigned long long i;
+
+ if (b == 0)
+ return (signed long long)(-1);
+
+ j = 0;
+ i = 0;
+ while (a >= b) {
+ if ((signed long long)b < 0) {
+ a -= b;
+ j |= 1ull << i;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b) {
+ a -= b;
+ j |= 1ull << i;
+ }
+ }
+
+ return j;
+}
+EXPORT_SYMBOL(__udivdi3);
+
+#ifdef CONFIG_64BIT
+unsigned __int128 __udivti3(unsigned __int128 a, unsigned __int128 b)
+{
+ unsigned __int128 j;
+ unsigned __int128 i;
+
+ if (b == 0)
+ return (signed __int128)(-1);
+
+ j = 0;
+ i = 0;
+ while (a >= b) {
+ if ((signed __int128)b < 0) {
+ a -= b;
+ j |= ((unsigned __int128)1) << i;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b) {
+ a -= b;
+ j |= ((unsigned __int128)1) << i;
+ }
+ }
+
+ return j;
+}
+EXPORT_SYMBOL(__udivti3);
+#endif
+
+signed int __modsi3(signed int a, signed int b)
+{
+ unsigned int ua;
+ unsigned int ub;
+ unsigned int i;
+ signed int r;
+
+ if (b == 0)
+ return a;
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ i = 0;
+ while (ua >= ub) {
+ if ((signed int)ub < 0) {
+ ua -= ub;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub)
+ ua -= ub;
+ }
+
+ r = ua;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__modsi3);
+
+signed long long __moddi3(signed long long a, signed long long b)
+{
+ unsigned long long ua;
+ unsigned long long ub;
+ unsigned int i;
+ signed long long r;
+
+ if (b == 0)
+ return a;
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ i = 0;
+ while (ua >= ub) {
+ if ((signed long long)ub < 0) {
+ ua -= ub;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub)
+ ua -= ub;
+ }
+
+ r = ua;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__moddi3);
+
+#ifdef CONFIG_64BIT
+signed __int128 __modti3(signed __int128 a, signed __int128 b)
+{
+ unsigned __int128 ua;
+ unsigned __int128 ub;
+ unsigned int i;
+ signed __int128 r;
+
+ if (b == 0)
+ return a;
+
+ ua = a;
+ ub = b;
+ if (a < 0)
+ ua = -a;
+ if (b < 0)
+ ub = -b;
+
+ i = 0;
+ while (ua >= ub) {
+ if ((signed __int128)ub < 0) {
+ ua -= ub;
+ break;
+ }
+ ub <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ ub >>= 1;
+ if (ua >= ub)
+ ua -= ub;
+ }
+
+ r = ua;
+ if (a < 0)
+ r = -r;
+
+ return r;
+}
+EXPORT_SYMBOL(__modti3);
+#endif
+
+unsigned int __umodsi3(unsigned int a, unsigned int b)
+{
+ unsigned int i;
+
+ if (b == 0)
+ return a;
+
+ i = 0;
+ while (a >= b) {
+ if ((signed int)b < 0) {
+ a -= b;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b)
+ a -= b;
+ }
+
+ return a;
+}
+EXPORT_SYMBOL(__umodsi3);
+
+unsigned long long __umoddi3(unsigned long long a, unsigned long long b)
+{
+ unsigned long long i;
+
+ if (b == 0)
+ return a;
+
+ i = 0;
+ while (a >= b) {
+ if ((signed long long)b < 0) {
+ a -= b;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b)
+ a -= b;
+ }
+
+ return a;
+}
+EXPORT_SYMBOL(__umoddi3);
+
+#ifdef CONFIG_64BIT
+unsigned __int128 __umodti3(unsigned __int128 a, unsigned __int128 b)
+{
+ unsigned __int128 i;
+
+ if (b == 0)
+ return a;
+
+ i = 0;
+ while (a >= b) {
+ if ((signed __int128)b < 0) {
+ a -= b;
+ break;
+ }
+ b <<= 1;
+ i++;
+ }
+ while (i > 0) {
+ i--;
+ b >>= 1;
+ if (a >= b)
+ a -= b;
+ }
+
+ return a;
+}
+EXPORT_SYMBOL(__umodti3);
+#endif
+
+#endif
--
2.34.1