Merge patch series "RISC-V Hardware Probing User Interface"
Evan Green <evan@rivosinc.com> says: There's been a bunch of off-list discussions about this, including at Plumbers. The original plan was to do something involving providing an ISA string to userspace, but ISA strings just aren't sufficient for a stable ABI any more: in order to parse an ISA string users need the version of the specifications that the string is written to, the version of each extension (sometimes at a finer granularity than the RISC-V releases/versions encode), and the expected use case for the ISA string (ie, is it a U-mode or M-mode string). That's a lot of complexity to try and keep ABI compatible and it's probably going to continue to grow, as even if there's no more complexity in the specifications we'll have to deal with the various ISA string parsing oddities that end up all over userspace. Instead this patch set takes a very different approach and provides a set of key/value pairs that encode various bits about the system. The big advantage here is that we can clearly define what these mean so we can ensure ABI stability, but it also allows us to encode information that's unlikely to ever appear in an ISA string (see the misaligned access performance, for example). The resulting interface looks a lot like what arm64 and x86 do, and will hopefully fit well into something like ACPI in the future. The actual user interface is a syscall, with a vDSO function in front of it. The vDSO function can answer some queries without a syscall at all, and falls back to the syscall for cases it doesn't have answers to. Currently we prepopulate it with an array of answers for all keys and a CPU set of "all CPUs". This can be adjusted as necessary to provide fast answers to the most common queries. An example series in glibc exposing this syscall and using it in an ifunc selector for memcpy can be found at [1]. I was asked about the performance delta between this and something like sysfs. I created a small test program and ran it on a Nezha D1 Allwinner board. Doing each operation 100000 times and dividing, these operations take the following amount of time: - open()+read()+close() of /sys/kernel/cpu_byteorder: 3.8us - access("/sys/kernel/cpu_byteorder", R_OK): 1.3us - riscv_hwprobe() vDSO and syscall: .0094us - riscv_hwprobe() vDSO with no syscall: 0.0091us These numbers get farther apart if we query multiple keys, as sysfs will scale linearly with the number of keys, where the dedicated syscall stays the same. To frame these numbers, I also did a tight fork/exec/wait loop, which I measured as 4.8ms. So doing 4 open/read/close operations is a delta of about 0.3%, versus a single vDSO call is a delta of essentially zero. [1] https://patchwork.ozlabs.org/project/glibc/list/?series=343050 * b4-shazam-merge: RISC-V: Add hwprobe vDSO function and data selftests: Test the new RISC-V hwprobe interface RISC-V: hwprobe: Support probing of misaligned access performance RISC-V: hwprobe: Add support for RISCV_HWPROBE_BASE_BEHAVIOR_IMA RISC-V: Add a syscall for HW probing RISC-V: Move struct riscv_cpuinfo to new header Link: https://lore.kernel.org/r/20230407231103.2622178-1-evan@rivosinc.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
This commit is contained in:
commit
eb04e72b34
86
Documentation/riscv/hwprobe.rst
Normal file
86
Documentation/riscv/hwprobe.rst
Normal file
@ -0,0 +1,86 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
RISC-V Hardware Probing Interface
|
||||
---------------------------------
|
||||
|
||||
The RISC-V hardware probing interface is based around a single syscall, which
|
||||
is defined in <asm/hwprobe.h>::
|
||||
|
||||
struct riscv_hwprobe {
|
||||
__s64 key;
|
||||
__u64 value;
|
||||
};
|
||||
|
||||
long sys_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, cpu_set_t *cpus,
|
||||
unsigned int flags);
|
||||
|
||||
The arguments are split into three groups: an array of key-value pairs, a CPU
|
||||
set, and some flags. The key-value pairs are supplied with a count. Userspace
|
||||
must prepopulate the key field for each element, and the kernel will fill in the
|
||||
value if the key is recognized. If a key is unknown to the kernel, its key field
|
||||
will be cleared to -1, and its value set to 0. The CPU set is defined by
|
||||
CPU_SET(3). For value-like keys (eg. vendor/arch/impl), the returned value will
|
||||
be only be valid if all CPUs in the given set have the same value. Otherwise -1
|
||||
will be returned. For boolean-like keys, the value returned will be a logical
|
||||
AND of the values for the specified CPUs. Usermode can supply NULL for cpus and
|
||||
0 for cpu_count as a shortcut for all online CPUs. There are currently no flags,
|
||||
this value must be zero for future compatibility.
|
||||
|
||||
On success 0 is returned, on failure a negative error code is returned.
|
||||
|
||||
The following keys are defined:
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_MVENDORID`: Contains the value of ``mvendorid``,
|
||||
as defined by the RISC-V privileged architecture specification.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_MARCHID`: Contains the value of ``marchid``, as
|
||||
defined by the RISC-V privileged architecture specification.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_MIMPLID`: Contains the value of ``mimplid``, as
|
||||
defined by the RISC-V privileged architecture specification.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_BASE_BEHAVIOR`: A bitmask containing the base
|
||||
user-visible behavior that this kernel supports. The following base user ABIs
|
||||
are defined:
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_BASE_BEHAVIOR_IMA`: Support for rv32ima or
|
||||
rv64ima, as defined by version 2.2 of the user ISA and version 1.10 of the
|
||||
privileged ISA, with the following known exceptions (more exceptions may be
|
||||
added, but only if it can be demonstrated that the user ABI is not broken):
|
||||
|
||||
* The :fence.i: instruction cannot be directly executed by userspace
|
||||
programs (it may still be executed in userspace via a
|
||||
kernel-controlled mechanism such as the vDSO).
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_IMA_EXT_0`: A bitmask containing the extensions
|
||||
that are compatible with the :c:macro:`RISCV_HWPROBE_BASE_BEHAVIOR_IMA`:
|
||||
base system behavior.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_IMA_FD`: The F and D extensions are supported, as
|
||||
defined by commit cd20cee ("FMIN/FMAX now implement
|
||||
minimumNumber/maximumNumber, not minNum/maxNum") of the RISC-V ISA manual.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_IMA_C`: The C extension is supported, as defined
|
||||
by version 2.2 of the RISC-V ISA manual.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_KEY_CPUPERF_0`: A bitmask that contains performance
|
||||
information about the selected set of processors.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_MISALIGNED_UNKNOWN`: The performance of misaligned
|
||||
accesses is unknown.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_MISALIGNED_EMULATED`: Misaligned accesses are
|
||||
emulated via software, either in or below the kernel. These accesses are
|
||||
always extremely slow.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_MISALIGNED_SLOW`: Misaligned accesses are supported
|
||||
in hardware, but are slower than the cooresponding aligned accesses
|
||||
sequences.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_MISALIGNED_FAST`: Misaligned accesses are supported
|
||||
in hardware and are faster than the cooresponding aligned accesses
|
||||
sequences.
|
||||
|
||||
* :c:macro:`RISCV_HWPROBE_MISALIGNED_UNSUPPORTED`: Misaligned accesses are
|
||||
not supported at all and will generate a misaligned address fault.
|
@ -7,6 +7,7 @@ RISC-V architecture
|
||||
|
||||
boot-image-header
|
||||
vm-layout
|
||||
hwprobe
|
||||
patch-acceptance
|
||||
uabi
|
||||
|
||||
|
@ -33,6 +33,7 @@ config RISCV
|
||||
select ARCH_HAS_STRICT_MODULE_RWX if MMU && !XIP_KERNEL
|
||||
select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
|
||||
select ARCH_HAS_UBSAN_SANITIZE_ALL
|
||||
select ARCH_HAS_VDSO_DATA
|
||||
select ARCH_OPTIONAL_KERNEL_RWX if ARCH_HAS_STRICT_KERNEL_RWX
|
||||
select ARCH_OPTIONAL_KERNEL_RWX_DEFAULT
|
||||
select ARCH_STACKWALK
|
||||
|
@ -11,7 +11,9 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/alternative.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/errata_list.h>
|
||||
#include <asm/hwprobe.h>
|
||||
#include <asm/patch.h>
|
||||
#include <asm/vendorid_list.h>
|
||||
|
||||
@ -115,3 +117,11 @@ void __init_or_module thead_errata_patch_func(struct alt_entry *begin, struct al
|
||||
if (stage == RISCV_ALTERNATIVES_EARLY_BOOT)
|
||||
local_flush_icache_all();
|
||||
}
|
||||
|
||||
void __init_or_module thead_feature_probe_func(unsigned int cpu,
|
||||
unsigned long archid,
|
||||
unsigned long impid)
|
||||
{
|
||||
if ((archid == 0) && (impid == 0))
|
||||
per_cpu(misaligned_access_speed, cpu) = RISCV_HWPROBE_MISALIGNED_FAST;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define ALT_OLD_PTR(a) __ALT_PTR(a, old_offset)
|
||||
#define ALT_ALT_PTR(a) __ALT_PTR(a, alt_offset)
|
||||
|
||||
void __init probe_vendor_features(unsigned int cpu);
|
||||
void __init apply_boot_alternatives(void);
|
||||
void __init apply_early_boot_alternatives(void);
|
||||
void apply_module_alternatives(void *start, size_t length);
|
||||
@ -52,11 +53,15 @@ void thead_errata_patch_func(struct alt_entry *begin, struct alt_entry *end,
|
||||
unsigned long archid, unsigned long impid,
|
||||
unsigned int stage);
|
||||
|
||||
void thead_feature_probe_func(unsigned int cpu, unsigned long archid,
|
||||
unsigned long impid);
|
||||
|
||||
void riscv_cpufeature_patch_func(struct alt_entry *begin, struct alt_entry *end,
|
||||
unsigned int stage);
|
||||
|
||||
#else /* CONFIG_RISCV_ALTERNATIVE */
|
||||
|
||||
static inline void probe_vendor_features(unsigned int cpu) { }
|
||||
static inline void apply_boot_alternatives(void) { }
|
||||
static inline void apply_early_boot_alternatives(void) { }
|
||||
static inline void apply_module_alternatives(void *start, size_t length) { }
|
||||
|
23
arch/riscv/include/asm/cpufeature.h
Normal file
23
arch/riscv/include/asm/cpufeature.h
Normal file
@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2022-2023 Rivos, Inc
|
||||
*/
|
||||
|
||||
#ifndef _ASM_CPUFEATURE_H
|
||||
#define _ASM_CPUFEATURE_H
|
||||
|
||||
/*
|
||||
* These are probed via a device_initcall(), via either the SBI or directly
|
||||
* from the corresponding CSRs.
|
||||
*/
|
||||
struct riscv_cpuinfo {
|
||||
unsigned long mvendorid;
|
||||
unsigned long marchid;
|
||||
unsigned long mimpid;
|
||||
};
|
||||
|
||||
DECLARE_PER_CPU(struct riscv_cpuinfo, riscv_cpuinfo);
|
||||
|
||||
DECLARE_PER_CPU(long, misaligned_access_speed);
|
||||
|
||||
#endif
|
13
arch/riscv/include/asm/hwprobe.h
Normal file
13
arch/riscv/include/asm/hwprobe.h
Normal file
@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright 2023 Rivos, Inc
|
||||
*/
|
||||
|
||||
#ifndef _ASM_HWPROBE_H
|
||||
#define _ASM_HWPROBE_H
|
||||
|
||||
#include <uapi/asm/hwprobe.h>
|
||||
|
||||
#define RISCV_HWPROBE_MAX_KEY 5
|
||||
|
||||
#endif
|
@ -10,6 +10,7 @@
|
||||
#ifndef _ASM_RISCV_SYSCALL_H
|
||||
#define _ASM_RISCV_SYSCALL_H
|
||||
|
||||
#include <asm/hwprobe.h>
|
||||
#include <uapi/linux/audit.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/err.h>
|
||||
@ -96,4 +97,7 @@ static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs)
|
||||
}
|
||||
|
||||
asmlinkage long sys_riscv_flush_icache(uintptr_t, uintptr_t, uintptr_t);
|
||||
|
||||
asmlinkage long sys_riscv_hwprobe(struct riscv_hwprobe *, size_t, size_t,
|
||||
unsigned long *, unsigned int);
|
||||
#endif /* _ASM_RISCV_SYSCALL_H */
|
||||
|
17
arch/riscv/include/asm/vdso/data.h
Normal file
17
arch/riscv/include/asm/vdso/data.h
Normal file
@ -0,0 +1,17 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __RISCV_ASM_VDSO_DATA_H
|
||||
#define __RISCV_ASM_VDSO_DATA_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <vdso/datapage.h>
|
||||
#include <asm/hwprobe.h>
|
||||
|
||||
struct arch_vdso_data {
|
||||
/* Stash static answers to the hwprobe queries when all CPUs are selected. */
|
||||
__u64 all_cpu_hwprobe_values[RISCV_HWPROBE_MAX_KEY + 1];
|
||||
|
||||
/* Boolean indicating all CPUs have the same static hwprobe values. */
|
||||
__u8 homogeneous_cpus;
|
||||
};
|
||||
|
||||
#endif /* __RISCV_ASM_VDSO_DATA_H */
|
@ -9,6 +9,12 @@
|
||||
#include <asm/csr.h>
|
||||
#include <uapi/linux/time.h>
|
||||
|
||||
/*
|
||||
* 32-bit land is lacking generic time vsyscalls as well as the legacy 32-bit
|
||||
* time syscalls like gettimeofday. Skip these definitions since on 32-bit.
|
||||
*/
|
||||
#ifdef CONFIG_GENERIC_TIME_VSYSCALL
|
||||
|
||||
#define VDSO_HAS_CLOCK_GETRES 1
|
||||
|
||||
static __always_inline
|
||||
@ -60,6 +66,8 @@ int clock_getres_fallback(clockid_t _clkid, struct __kernel_timespec *_ts)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_GENERIC_TIME_VSYSCALL */
|
||||
|
||||
static __always_inline u64 __arch_get_hw_counter(s32 clock_mode,
|
||||
const struct vdso_data *vd)
|
||||
{
|
||||
|
37
arch/riscv/include/uapi/asm/hwprobe.h
Normal file
37
arch/riscv/include/uapi/asm/hwprobe.h
Normal file
@ -0,0 +1,37 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Copyright 2023 Rivos, Inc
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_ASM_HWPROBE_H
|
||||
#define _UAPI_ASM_HWPROBE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* Interface for probing hardware capabilities from userspace, see
|
||||
* Documentation/riscv/hwprobe.rst for more information.
|
||||
*/
|
||||
struct riscv_hwprobe {
|
||||
__s64 key;
|
||||
__u64 value;
|
||||
};
|
||||
|
||||
#define RISCV_HWPROBE_KEY_MVENDORID 0
|
||||
#define RISCV_HWPROBE_KEY_MARCHID 1
|
||||
#define RISCV_HWPROBE_KEY_MIMPID 2
|
||||
#define RISCV_HWPROBE_KEY_BASE_BEHAVIOR 3
|
||||
#define RISCV_HWPROBE_BASE_BEHAVIOR_IMA (1 << 0)
|
||||
#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
|
||||
#define RISCV_HWPROBE_IMA_FD (1 << 0)
|
||||
#define RISCV_HWPROBE_IMA_C (1 << 1)
|
||||
#define RISCV_HWPROBE_KEY_CPUPERF_0 5
|
||||
#define RISCV_HWPROBE_MISALIGNED_UNKNOWN (0 << 0)
|
||||
#define RISCV_HWPROBE_MISALIGNED_EMULATED (1 << 0)
|
||||
#define RISCV_HWPROBE_MISALIGNED_SLOW (2 << 0)
|
||||
#define RISCV_HWPROBE_MISALIGNED_FAST (3 << 0)
|
||||
#define RISCV_HWPROBE_MISALIGNED_UNSUPPORTED (4 << 0)
|
||||
#define RISCV_HWPROBE_MISALIGNED_MASK (7 << 0)
|
||||
/* Increase RISCV_HWPROBE_MAX_KEY when adding items. */
|
||||
|
||||
#endif
|
@ -43,3 +43,12 @@
|
||||
#define __NR_riscv_flush_icache (__NR_arch_specific_syscall + 15)
|
||||
#endif
|
||||
__SYSCALL(__NR_riscv_flush_icache, sys_riscv_flush_icache)
|
||||
|
||||
/*
|
||||
* Allows userspace to query the kernel for CPU architecture and
|
||||
* microarchitecture details across a given set of CPUs.
|
||||
*/
|
||||
#ifndef __NR_riscv_hwprobe
|
||||
#define __NR_riscv_hwprobe (__NR_arch_specific_syscall + 14)
|
||||
#endif
|
||||
__SYSCALL(__NR_riscv_hwprobe, sys_riscv_hwprobe)
|
||||
|
@ -27,6 +27,8 @@ struct cpu_manufacturer_info_t {
|
||||
void (*patch_func)(struct alt_entry *begin, struct alt_entry *end,
|
||||
unsigned long archid, unsigned long impid,
|
||||
unsigned int stage);
|
||||
void (*feature_probe_func)(unsigned int cpu, unsigned long archid,
|
||||
unsigned long impid);
|
||||
};
|
||||
|
||||
static void __init_or_module riscv_fill_cpu_mfr_info(struct cpu_manufacturer_info_t *cpu_mfr_info)
|
||||
@ -41,6 +43,7 @@ static void __init_or_module riscv_fill_cpu_mfr_info(struct cpu_manufacturer_inf
|
||||
cpu_mfr_info->imp_id = sbi_get_mimpid();
|
||||
#endif
|
||||
|
||||
cpu_mfr_info->feature_probe_func = NULL;
|
||||
switch (cpu_mfr_info->vendor_id) {
|
||||
#ifdef CONFIG_ERRATA_SIFIVE
|
||||
case SIFIVE_VENDOR_ID:
|
||||
@ -50,6 +53,7 @@ static void __init_or_module riscv_fill_cpu_mfr_info(struct cpu_manufacturer_inf
|
||||
#ifdef CONFIG_ERRATA_THEAD
|
||||
case THEAD_VENDOR_ID:
|
||||
cpu_mfr_info->patch_func = thead_errata_patch_func;
|
||||
cpu_mfr_info->feature_probe_func = thead_feature_probe_func;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
@ -139,6 +143,20 @@ void riscv_alternative_fix_offsets(void *alt_ptr, unsigned int len,
|
||||
}
|
||||
}
|
||||
|
||||
/* Called on each CPU as it starts */
|
||||
void __init_or_module probe_vendor_features(unsigned int cpu)
|
||||
{
|
||||
struct cpu_manufacturer_info_t cpu_mfr_info;
|
||||
|
||||
riscv_fill_cpu_mfr_info(&cpu_mfr_info);
|
||||
if (!cpu_mfr_info.feature_probe_func)
|
||||
return;
|
||||
|
||||
cpu_mfr_info.feature_probe_func(cpu,
|
||||
cpu_mfr_info.arch_id,
|
||||
cpu_mfr_info.imp_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
@ -193,6 +211,7 @@ void __init apply_boot_alternatives(void)
|
||||
/* If called on non-boot cpu things could go wrong */
|
||||
WARN_ON(smp_processor_id() != 0);
|
||||
|
||||
probe_vendor_features(0);
|
||||
_apply_alternatives((struct alt_entry *)__alt_start,
|
||||
(struct alt_entry *)__alt_end,
|
||||
RISCV_ALTERNATIVES_BOOT);
|
||||
|
@ -22,7 +22,7 @@ targets := $(obj-compat_vdso) compat_vdso.so compat_vdso.so.dbg compat_vdso.lds
|
||||
obj-compat_vdso := $(addprefix $(obj)/, $(obj-compat_vdso))
|
||||
|
||||
obj-y += compat_vdso.o
|
||||
CPPFLAGS_compat_vdso.lds += -P -C -U$(ARCH)
|
||||
CPPFLAGS_compat_vdso.lds += -P -C -DCOMPAT_VDSO -U$(ARCH)
|
||||
|
||||
# Disable profiling and instrumentation for VDSO code
|
||||
GCOV_PROFILE := n
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/of.h>
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/csr.h>
|
||||
#include <asm/hwcap.h>
|
||||
#include <asm/sbi.h>
|
||||
@ -70,12 +71,7 @@ int riscv_of_parent_hartid(struct device_node *node, unsigned long *hartid)
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct riscv_cpuinfo {
|
||||
unsigned long mvendorid;
|
||||
unsigned long marchid;
|
||||
unsigned long mimpid;
|
||||
};
|
||||
static DEFINE_PER_CPU(struct riscv_cpuinfo, riscv_cpuinfo);
|
||||
DEFINE_PER_CPU(struct riscv_cpuinfo, riscv_cpuinfo);
|
||||
|
||||
unsigned long riscv_cached_mvendorid(unsigned int cpu_id)
|
||||
{
|
||||
|
@ -25,6 +25,9 @@ unsigned long elf_hwcap __read_mostly;
|
||||
/* Host ISA bitmap */
|
||||
static DECLARE_BITMAP(riscv_isa, RISCV_ISA_EXT_MAX) __read_mostly;
|
||||
|
||||
/* Performance information */
|
||||
DEFINE_PER_CPU(long, misaligned_access_speed);
|
||||
|
||||
/**
|
||||
* riscv_isa_extension_base() - Get base extension word
|
||||
*
|
||||
|
@ -168,6 +168,7 @@ asmlinkage __visible void smp_callin(void)
|
||||
notify_cpu_starting(curr_cpuid);
|
||||
numa_add_cpu(curr_cpuid);
|
||||
set_cpu_online(curr_cpuid, 1);
|
||||
probe_vendor_features(curr_cpuid);
|
||||
|
||||
/*
|
||||
* Remote TLB flushes are ignored while the CPU is offline, so emit
|
||||
|
@ -6,9 +6,15 @@
|
||||
*/
|
||||
|
||||
#include <linux/syscalls.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <asm/cpufeature.h>
|
||||
#include <asm/hwprobe.h>
|
||||
#include <asm/sbi.h>
|
||||
#include <asm/switch_to.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <asm-generic/mman-common.h>
|
||||
#include <vdso/vsyscall.h>
|
||||
|
||||
static long riscv_sys_mmap(unsigned long addr, unsigned long len,
|
||||
unsigned long prot, unsigned long flags,
|
||||
@ -69,3 +75,223 @@ SYSCALL_DEFINE3(riscv_flush_icache, uintptr_t, start, uintptr_t, end,
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The hwprobe interface, for allowing userspace to probe to see which features
|
||||
* are supported by the hardware. See Documentation/riscv/hwprobe.rst for more
|
||||
* details.
|
||||
*/
|
||||
static void hwprobe_arch_id(struct riscv_hwprobe *pair,
|
||||
const struct cpumask *cpus)
|
||||
{
|
||||
u64 id = -1ULL;
|
||||
bool first = true;
|
||||
int cpu;
|
||||
|
||||
for_each_cpu(cpu, cpus) {
|
||||
u64 cpu_id;
|
||||
|
||||
switch (pair->key) {
|
||||
case RISCV_HWPROBE_KEY_MVENDORID:
|
||||
cpu_id = riscv_cached_mvendorid(cpu);
|
||||
break;
|
||||
case RISCV_HWPROBE_KEY_MIMPID:
|
||||
cpu_id = riscv_cached_mimpid(cpu);
|
||||
break;
|
||||
case RISCV_HWPROBE_KEY_MARCHID:
|
||||
cpu_id = riscv_cached_marchid(cpu);
|
||||
break;
|
||||
}
|
||||
|
||||
if (first)
|
||||
id = cpu_id;
|
||||
|
||||
/*
|
||||
* If there's a mismatch for the given set, return -1 in the
|
||||
* value.
|
||||
*/
|
||||
if (id != cpu_id) {
|
||||
id = -1ULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pair->value = id;
|
||||
}
|
||||
|
||||
static u64 hwprobe_misaligned(const struct cpumask *cpus)
|
||||
{
|
||||
int cpu;
|
||||
u64 perf = -1ULL;
|
||||
|
||||
for_each_cpu(cpu, cpus) {
|
||||
int this_perf = per_cpu(misaligned_access_speed, cpu);
|
||||
|
||||
if (perf == -1ULL)
|
||||
perf = this_perf;
|
||||
|
||||
if (perf != this_perf) {
|
||||
perf = RISCV_HWPROBE_MISALIGNED_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (perf == -1ULL)
|
||||
return RISCV_HWPROBE_MISALIGNED_UNKNOWN;
|
||||
|
||||
return perf;
|
||||
}
|
||||
|
||||
static void hwprobe_one_pair(struct riscv_hwprobe *pair,
|
||||
const struct cpumask *cpus)
|
||||
{
|
||||
switch (pair->key) {
|
||||
case RISCV_HWPROBE_KEY_MVENDORID:
|
||||
case RISCV_HWPROBE_KEY_MARCHID:
|
||||
case RISCV_HWPROBE_KEY_MIMPID:
|
||||
hwprobe_arch_id(pair, cpus);
|
||||
break;
|
||||
/*
|
||||
* The kernel already assumes that the base single-letter ISA
|
||||
* extensions are supported on all harts, and only supports the
|
||||
* IMA base, so just cheat a bit here and tell that to
|
||||
* userspace.
|
||||
*/
|
||||
case RISCV_HWPROBE_KEY_BASE_BEHAVIOR:
|
||||
pair->value = RISCV_HWPROBE_BASE_BEHAVIOR_IMA;
|
||||
break;
|
||||
|
||||
case RISCV_HWPROBE_KEY_IMA_EXT_0:
|
||||
pair->value = 0;
|
||||
if (has_fpu())
|
||||
pair->value |= RISCV_HWPROBE_IMA_FD;
|
||||
|
||||
if (riscv_isa_extension_available(NULL, c))
|
||||
pair->value |= RISCV_HWPROBE_IMA_C;
|
||||
|
||||
break;
|
||||
|
||||
case RISCV_HWPROBE_KEY_CPUPERF_0:
|
||||
pair->value = hwprobe_misaligned(cpus);
|
||||
break;
|
||||
|
||||
/*
|
||||
* For forward compatibility, unknown keys don't fail the whole
|
||||
* call, but get their element key set to -1 and value set to 0
|
||||
* indicating they're unrecognized.
|
||||
*/
|
||||
default:
|
||||
pair->key = -1;
|
||||
pair->value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int do_riscv_hwprobe(struct riscv_hwprobe __user *pairs,
|
||||
size_t pair_count, size_t cpu_count,
|
||||
unsigned long __user *cpus_user,
|
||||
unsigned int flags)
|
||||
{
|
||||
size_t out;
|
||||
int ret;
|
||||
cpumask_t cpus;
|
||||
|
||||
/* Check the reserved flags. */
|
||||
if (flags != 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The interface supports taking in a CPU mask, and returns values that
|
||||
* are consistent across that mask. Allow userspace to specify NULL and
|
||||
* 0 as a shortcut to all online CPUs.
|
||||
*/
|
||||
cpumask_clear(&cpus);
|
||||
if (!cpu_count && !cpus_user) {
|
||||
cpumask_copy(&cpus, cpu_online_mask);
|
||||
} else {
|
||||
if (cpu_count > cpumask_size())
|
||||
cpu_count = cpumask_size();
|
||||
|
||||
ret = copy_from_user(&cpus, cpus_user, cpu_count);
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Userspace must provide at least one online CPU, without that
|
||||
* there's no way to define what is supported.
|
||||
*/
|
||||
cpumask_and(&cpus, &cpus, cpu_online_mask);
|
||||
if (cpumask_empty(&cpus))
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (out = 0; out < pair_count; out++, pairs++) {
|
||||
struct riscv_hwprobe pair;
|
||||
|
||||
if (get_user(pair.key, &pairs->key))
|
||||
return -EFAULT;
|
||||
|
||||
pair.value = 0;
|
||||
hwprobe_one_pair(&pair, &cpus);
|
||||
ret = put_user(pair.key, &pairs->key);
|
||||
if (ret == 0)
|
||||
ret = put_user(pair.value, &pairs->value);
|
||||
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
|
||||
static int __init init_hwprobe_vdso_data(void)
|
||||
{
|
||||
struct vdso_data *vd = __arch_get_k_vdso_data();
|
||||
struct arch_vdso_data *avd = &vd->arch_data;
|
||||
u64 id_bitsmash = 0;
|
||||
struct riscv_hwprobe pair;
|
||||
int key;
|
||||
|
||||
/*
|
||||
* Initialize vDSO data with the answers for the "all CPUs" case, to
|
||||
* save a syscall in the common case.
|
||||
*/
|
||||
for (key = 0; key <= RISCV_HWPROBE_MAX_KEY; key++) {
|
||||
pair.key = key;
|
||||
hwprobe_one_pair(&pair, cpu_online_mask);
|
||||
|
||||
WARN_ON_ONCE(pair.key < 0);
|
||||
|
||||
avd->all_cpu_hwprobe_values[key] = pair.value;
|
||||
/*
|
||||
* Smash together the vendor, arch, and impl IDs to see if
|
||||
* they're all 0 or any negative.
|
||||
*/
|
||||
if (key <= RISCV_HWPROBE_KEY_MIMPID)
|
||||
id_bitsmash |= pair.value;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the arch, vendor, and implementation ID are all the same across
|
||||
* all harts, then assume all CPUs are the same, and allow the vDSO to
|
||||
* answer queries for arbitrary masks. However if all values are 0 (not
|
||||
* populated) or any value returns -1 (varies across CPUs), then the
|
||||
* vDSO should defer to the kernel for exotic cpu masks.
|
||||
*/
|
||||
avd->homogeneous_cpus = (id_bitsmash > 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
arch_initcall_sync(init_hwprobe_vdso_data);
|
||||
|
||||
#endif /* CONFIG_MMU */
|
||||
|
||||
SYSCALL_DEFINE5(riscv_hwprobe, struct riscv_hwprobe __user *, pairs,
|
||||
size_t, pair_count, size_t, cpu_count, unsigned long __user *,
|
||||
cpus, unsigned int, flags)
|
||||
{
|
||||
return do_riscv_hwprobe(pairs, pair_count, cpu_count,
|
||||
cpus, flags);
|
||||
}
|
||||
|
@ -14,13 +14,7 @@
|
||||
#include <asm/page.h>
|
||||
#include <asm/vdso.h>
|
||||
#include <linux/time_namespace.h>
|
||||
|
||||
#ifdef CONFIG_GENERIC_TIME_VSYSCALL
|
||||
#include <vdso/datapage.h>
|
||||
#else
|
||||
struct vdso_data {
|
||||
};
|
||||
#endif
|
||||
|
||||
enum vvar_pages {
|
||||
VVAR_DATA_PAGE_OFFSET,
|
||||
|
@ -12,6 +12,8 @@ vdso-syms += vgettimeofday
|
||||
endif
|
||||
vdso-syms += getcpu
|
||||
vdso-syms += flush_icache
|
||||
vdso-syms += hwprobe
|
||||
vdso-syms += sys_hwprobe
|
||||
|
||||
# Files to link into the vdso
|
||||
obj-vdso = $(patsubst %, %.o, $(vdso-syms)) note.o
|
||||
@ -23,6 +25,8 @@ ifneq ($(c-gettimeofday-y),)
|
||||
CFLAGS_vgettimeofday.o += -fPIC -include $(c-gettimeofday-y)
|
||||
endif
|
||||
|
||||
CFLAGS_hwprobe.o += -fPIC
|
||||
|
||||
# Build rules
|
||||
targets := $(obj-vdso) vdso.so vdso.so.dbg vdso.lds
|
||||
obj-vdso := $(addprefix $(obj)/, $(obj-vdso))
|
||||
|
52
arch/riscv/kernel/vdso/hwprobe.c
Normal file
52
arch/riscv/kernel/vdso/hwprobe.c
Normal file
@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright 2023 Rivos, Inc
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <vdso/datapage.h>
|
||||
#include <vdso/helpers.h>
|
||||
|
||||
extern int riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, unsigned long *cpus,
|
||||
unsigned int flags);
|
||||
|
||||
/* Add a prototype to avoid -Wmissing-prototypes warning. */
|
||||
int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, unsigned long *cpus,
|
||||
unsigned int flags);
|
||||
|
||||
int __vdso_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, unsigned long *cpus,
|
||||
unsigned int flags)
|
||||
{
|
||||
const struct vdso_data *vd = __arch_get_vdso_data();
|
||||
const struct arch_vdso_data *avd = &vd->arch_data;
|
||||
bool all_cpus = !cpu_count && !cpus;
|
||||
struct riscv_hwprobe *p = pairs;
|
||||
struct riscv_hwprobe *end = pairs + pair_count;
|
||||
|
||||
/*
|
||||
* Defer to the syscall for exotic requests. The vdso has answers
|
||||
* stashed away only for the "all cpus" case. If all CPUs are
|
||||
* homogeneous, then this function can handle requests for arbitrary
|
||||
* masks.
|
||||
*/
|
||||
if ((flags != 0) || (!all_cpus && !avd->homogeneous_cpus))
|
||||
return riscv_hwprobe(pairs, pair_count, cpu_count, cpus, flags);
|
||||
|
||||
/* This is something we can handle, fill out the pairs. */
|
||||
while (p < end) {
|
||||
if (p->key <= RISCV_HWPROBE_MAX_KEY) {
|
||||
p->value = avd->all_cpu_hwprobe_values[p->key];
|
||||
|
||||
} else {
|
||||
p->key = -1;
|
||||
p->value = 0;
|
||||
}
|
||||
|
||||
p++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
15
arch/riscv/kernel/vdso/sys_hwprobe.S
Normal file
15
arch/riscv/kernel/vdso/sys_hwprobe.S
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (C) 2023 Rivos, Inc */
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/unistd.h>
|
||||
|
||||
.text
|
||||
ENTRY(riscv_hwprobe)
|
||||
.cfi_startproc
|
||||
li a7, __NR_riscv_hwprobe
|
||||
ecall
|
||||
ret
|
||||
|
||||
.cfi_endproc
|
||||
ENDPROC(riscv_hwprobe)
|
@ -82,6 +82,9 @@ VERSION
|
||||
#endif
|
||||
__vdso_getcpu;
|
||||
__vdso_flush_icache;
|
||||
#ifndef COMPAT_VDSO
|
||||
__vdso_riscv_hwprobe;
|
||||
#endif
|
||||
local: *;
|
||||
};
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ TARGETS += pstore
|
||||
TARGETS += ptrace
|
||||
TARGETS += openat2
|
||||
TARGETS += resctrl
|
||||
TARGETS += riscv
|
||||
TARGETS += rlimits
|
||||
TARGETS += rseq
|
||||
TARGETS += rtc
|
||||
|
58
tools/testing/selftests/riscv/Makefile
Normal file
58
tools/testing/selftests/riscv/Makefile
Normal file
@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Originally tools/testing/arm64/Makefile
|
||||
|
||||
# When ARCH not overridden for crosscompiling, lookup machine
|
||||
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
|
||||
|
||||
ifneq (,$(filter $(ARCH),riscv))
|
||||
RISCV_SUBTARGETS ?= hwprobe
|
||||
else
|
||||
RISCV_SUBTARGETS :=
|
||||
endif
|
||||
|
||||
CFLAGS := -Wall -O2 -g
|
||||
|
||||
# A proper top_srcdir is needed by KSFT(lib.mk)
|
||||
top_srcdir = $(realpath ../../../../)
|
||||
|
||||
# Additional include paths needed by kselftest.h and local headers
|
||||
CFLAGS += -I$(top_srcdir)/tools/testing/selftests/
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
|
||||
export CFLAGS
|
||||
export top_srcdir
|
||||
|
||||
all:
|
||||
@for DIR in $(RISCV_SUBTARGETS); do \
|
||||
BUILD_TARGET=$(OUTPUT)/$$DIR; \
|
||||
mkdir -p $$BUILD_TARGET; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \
|
||||
done
|
||||
|
||||
install: all
|
||||
@for DIR in $(RISCV_SUBTARGETS); do \
|
||||
BUILD_TARGET=$(OUTPUT)/$$DIR; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \
|
||||
done
|
||||
|
||||
run_tests: all
|
||||
@for DIR in $(RISCV_SUBTARGETS); do \
|
||||
BUILD_TARGET=$(OUTPUT)/$$DIR; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \
|
||||
done
|
||||
|
||||
# Avoid any output on non riscv on emit_tests
|
||||
emit_tests: all
|
||||
@for DIR in $(RISCV_SUBTARGETS); do \
|
||||
BUILD_TARGET=$(OUTPUT)/$$DIR; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \
|
||||
done
|
||||
|
||||
clean:
|
||||
@for DIR in $(RISCV_SUBTARGETS); do \
|
||||
BUILD_TARGET=$(OUTPUT)/$$DIR; \
|
||||
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$DIR $@; \
|
||||
done
|
||||
|
||||
.PHONY: all clean install run_tests emit_tests
|
10
tools/testing/selftests/riscv/hwprobe/Makefile
Normal file
10
tools/testing/selftests/riscv/hwprobe/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Copyright (C) 2021 ARM Limited
|
||||
# Originally tools/testing/arm64/abi/Makefile
|
||||
|
||||
TEST_GEN_PROGS := hwprobe
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
$(OUTPUT)/hwprobe: hwprobe.c sys_hwprobe.S
|
||||
$(CC) -o$@ $(CFLAGS) $(LDFLAGS) $^
|
90
tools/testing/selftests/riscv/hwprobe/hwprobe.c
Normal file
90
tools/testing/selftests/riscv/hwprobe/hwprobe.c
Normal file
@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include <stddef.h>
|
||||
#include <asm/hwprobe.h>
|
||||
|
||||
/*
|
||||
* Rather than relying on having a new enough libc to define this, just do it
|
||||
* ourselves. This way we don't need to be coupled to a new-enough libc to
|
||||
* contain the call.
|
||||
*/
|
||||
long riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||
size_t cpu_count, unsigned long *cpus, unsigned int flags);
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct riscv_hwprobe pairs[8];
|
||||
unsigned long cpus;
|
||||
long out;
|
||||
|
||||
/* Fake the CPU_SET ops. */
|
||||
cpus = -1;
|
||||
|
||||
/*
|
||||
* Just run a basic test: pass enough pairs to get up to the base
|
||||
* behavior, and then check to make sure it's sane.
|
||||
*/
|
||||
for (long i = 0; i < 8; i++)
|
||||
pairs[i].key = i;
|
||||
out = riscv_hwprobe(pairs, 8, 1, &cpus, 0);
|
||||
if (out != 0)
|
||||
return -1;
|
||||
for (long i = 0; i < 4; ++i) {
|
||||
/* Fail if the kernel claims not to recognize a base key. */
|
||||
if ((i < 4) && (pairs[i].key != i))
|
||||
return -2;
|
||||
|
||||
if (pairs[i].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR)
|
||||
continue;
|
||||
|
||||
if (pairs[i].value & RISCV_HWPROBE_BASE_BEHAVIOR_IMA)
|
||||
continue;
|
||||
|
||||
return -3;
|
||||
}
|
||||
|
||||
/*
|
||||
* This should also work with a NULL CPU set, but should not work
|
||||
* with an improperly supplied CPU set.
|
||||
*/
|
||||
out = riscv_hwprobe(pairs, 8, 0, 0, 0);
|
||||
if (out != 0)
|
||||
return -4;
|
||||
|
||||
out = riscv_hwprobe(pairs, 8, 0, &cpus, 0);
|
||||
if (out == 0)
|
||||
return -5;
|
||||
|
||||
out = riscv_hwprobe(pairs, 8, 1, 0, 0);
|
||||
if (out == 0)
|
||||
return -6;
|
||||
|
||||
/*
|
||||
* Check that keys work by providing one that we know exists, and
|
||||
* checking to make sure the resultig pair is what we asked for.
|
||||
*/
|
||||
pairs[0].key = RISCV_HWPROBE_KEY_BASE_BEHAVIOR;
|
||||
out = riscv_hwprobe(pairs, 1, 1, &cpus, 0);
|
||||
if (out != 0)
|
||||
return -7;
|
||||
if (pairs[0].key != RISCV_HWPROBE_KEY_BASE_BEHAVIOR)
|
||||
return -8;
|
||||
|
||||
/*
|
||||
* Check that an unknown key gets overwritten with -1,
|
||||
* but doesn't block elements after it.
|
||||
*/
|
||||
pairs[0].key = 0x5555;
|
||||
pairs[1].key = 1;
|
||||
pairs[1].value = 0xAAAA;
|
||||
out = riscv_hwprobe(pairs, 2, 0, 0, 0);
|
||||
if (out != 0)
|
||||
return -9;
|
||||
|
||||
if (pairs[0].key != -1)
|
||||
return -10;
|
||||
|
||||
if ((pairs[1].key != 1) || (pairs[1].value == 0xAAAA))
|
||||
return -11;
|
||||
|
||||
return 0;
|
||||
}
|
12
tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S
Normal file
12
tools/testing/selftests/riscv/hwprobe/sys_hwprobe.S
Normal file
@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (C) 2023 Rivos, Inc */
|
||||
|
||||
.text
|
||||
.global riscv_hwprobe
|
||||
riscv_hwprobe:
|
||||
# Put __NR_riscv_hwprobe in the syscall number register, then just shim
|
||||
# back the kernel's return. This doesn't do any sort of errno
|
||||
# handling, the caller can deal with it.
|
||||
li a7, 258
|
||||
ecall
|
||||
ret
|
Loading…
x
Reference in New Issue
Block a user