Skip to content

Commit

Permalink
RISC-V: Add a syscall for HW probing
Browse files Browse the repository at this point in the history
We don't have enough space for these all in ELF_HWCAP{,2} and there's no
system call that quite does this, so let's just provide an arch-specific
one to probe for hardware capabilities.  This currently just provides
m{arch,imp,vendor}id, but with the key-value pairs we can pass more in
the future.

Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
---
I havn't run this yet.
  • Loading branch information
palmer-dabbelt committed Oct 13, 2022
1 parent 568035b commit e90cd87
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 2 deletions.
33 changes: 33 additions & 0 deletions Documentation/riscv/hwprobe.rst
@@ -0,0 +1,33 @@
.. 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 {
__u64 key, value;
};

long sys_riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
size_t base_key, size_t cpu_count, cpu_set_t *cpus,
unsigned long 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 and an
base, which is the first key that will be probed for. The CPU set is defined
by CPU_SET(3), the indicated features will be supported on all CPUs in the set.
There are currently no flags, this value must be zero for future compatibility.

On success the number of filled out pairs is returned, on failure a negative
error code is returned.

The following keys are defined:

* :RISCV_HWPROBE_KEY_MVENDORID:: Contains the value of :mvendorid:, as per the
ISA specifications.
* :RISCV_HWPROBE_KEY_MARCHID:: Contains the value of :marchid:, as per the ISA
specifications.
* :RISCV_HWPROBE_KEY_MIMPLID:: Contains the value of :mimplid:, as per the ISA
specifications.
1 change: 1 addition & 0 deletions Documentation/riscv/index.rst
Expand Up @@ -7,6 +7,7 @@ RISC-V architecture

boot-image-header
vm-layout
hwprobe
patch-acceptance

features
Expand Down
21 changes: 21 additions & 0 deletions arch/riscv/include/asm/cpufeature.h
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright 2022 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 cooresponding CSRs.
*/
struct riscv_cpuinfo {
unsigned long mvendorid;
unsigned long marchid;
unsigned long mimpid;
};

DECLARE_PER_CPU(struct riscv_cpuinfo, riscv_cpuinfo);

#endif
13 changes: 13 additions & 0 deletions arch/riscv/include/asm/hwprobe.h
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Copyright 2022 Rivos, Inc
*/

#ifndef _ASM_HWPROBE_H
#define _ASM_HWPROBE_H

#include <uapi/asm/hwprobe.h>

#define RISCV_HWPROBE_MAX_KEY 2

#endif
3 changes: 3 additions & 0 deletions arch/riscv/include/asm/syscall.h
Expand Up @@ -75,4 +75,7 @@ static inline int syscall_get_arch(struct task_struct *task)
}

asmlinkage long sys_riscv_flush_icache(uintptr_t, uintptr_t, uintptr_t);

asmlinkage long sys_riscv_hwprobe(uintptr_t, uintptr_t, uintptr_t, uintptr_t,
uintptr_t, uintptr_t);
#endif /* _ASM_RISCV_SYSCALL_H */
24 changes: 24 additions & 0 deletions arch/riscv/include/uapi/asm/hwprobe.h
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Copyright 2022 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 {
__u64 key, val;
};

#define RISCV_HWPROBE_KEY_MVENDORID 0
#define RISCV_HWPROBE_KEY_MARCHID 1
#define RISCV_HWPROBE_KEY_MIMPID 2
/* Increase RISCV_HWPROBE_MAX_KEY when adding items. */

#endif
8 changes: 8 additions & 0 deletions arch/riscv/include/uapi/asm/unistd.h
Expand Up @@ -43,3 +43,11 @@
#define __NR_riscv_flush_icache (__NR_arch_specific_syscall + 15)
#endif
__SYSCALL(__NR_riscv_flush_icache, sys_riscv_flush_icache)

/*
* Allows userspace to probe
*/
#ifndef __NR_riscv_hwprobe
#define __NR_riscv_hwprobe (__NR_arch_specific_syscall + 14)
#endif
__SYSCALL(__NR_riscv_hwprobe, sys_riscv_hwprobe)
45 changes: 44 additions & 1 deletion arch/riscv/kernel/cpu.c
Expand Up @@ -3,12 +3,16 @@
* Copyright (C) 2012 Regents of the University of California
*/

#include <linux/cpuhotplug.h>
#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/smp.h>
#include <asm/pgtable.h>
#include <asm/sbi.h>
#include <asm/smp.h>

/*
* Returns the hart ID of the given device tree node, or -ENODEV if the node
Expand Down Expand Up @@ -67,7 +71,46 @@ int riscv_of_parent_hartid(struct device_node *node, unsigned long *hartid)
return -1;
}

DEFINE_PER_CPU(struct riscv_cpuinfo, riscv_cpuinfo);

static int riscv_cpuinfo_starting(unsigned int cpu)
{
struct riscv_cpuinfo *ci = this_cpu_ptr(&riscv_cpuinfo);

#if IS_ENABLED(CONFIG_RISCV_SBI)
ci->mvendorid = sbi_spec_is_0_1() ? 0 : sbi_get_mvendorid();
ci->marchid = sbi_spec_is_0_1() ? 0 : sbi_get_marchid();
ci->mimpid = sbi_spec_is_0_1() ? 0 : sbi_get_mimpid();
#elif IS_ENABLED(CONFIG_RISCV_M_MODE)
ci->mvendorid = csr_read(CSR_MVENDORID);
ci->marchid = csr_read(CSR_MARCHID);
ci->mimpid = csr_read(CSR_MIMPID);
#else
ci->mvendorid = 0;
ci->marchid = 0;
ci->mimpid = 0;
#endif

return 0;
}

static int __init riscv_cpuinfo_init(void)
{
int ret;

ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/cpuinfo:starting",
riscv_cpuinfo_starting, NULL);
if (ret < 0) {
pr_err("cpuinfo: failed to register hotplug callbacks.\n");
return ret;
}

return 0;
}
device_initcall(riscv_cpuinfo_init);

#ifdef CONFIG_PROC_FS

#define __RISCV_ISA_EXT_DATA(UPROP, EXTID) \
{ \
.uprop = #UPROP, \
Expand Down
118 changes: 117 additions & 1 deletion arch/riscv/kernel/sys_riscv.c
Expand Up @@ -6,8 +6,11 @@
*/

#include <linux/syscalls.h>
#include <asm/unistd.h>
#include <asm/cacheflush.h>
#include <asm/cpufeature.h>
#include <asm/hwprobe.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <asm-generic/mman-common.h>

static long riscv_sys_mmap(unsigned long addr, unsigned long len,
Expand Down Expand Up @@ -72,3 +75,116 @@ SYSCALL_DEFINE3(riscv_flush_icache, uintptr_t, start, uintptr_t, end,

return 0;
}

static long set_hwprobe(struct riscv_hwprobe __user *pair, u64 key, u64 val)
{
long ret;

ret = put_user(key, &pair->key);
if (ret < 0)
return ret;
ret = put_user(val, &pair->val);
if (ret < 0)
return ret;

return 0;
}

static long hwprobe_mid(struct riscv_hwprobe __user *pair, size_t key,
cpumask_t *cpus)
{
long cpu, id;
bool first, valid;

first = true;
valid = false;
for_each_cpu(cpu, cpus) {
struct riscv_cpuinfo * ci = per_cpu_ptr(&riscv_cpuinfo, cpu);
long cpu_id;

switch (key) {
case RISCV_HWPROBE_KEY_MVENDORID:
cpu_id = ci->mvendorid;
break;
case RISCV_HWPROBE_KEY_MIMPID:
cpu_id = ci->mimpid;
break;
case RISCV_HWPROBE_KEY_MARCHID:
cpu_id = ci->marchid;
break;
}

if (first) {
id = cpu_id;
valid = true;
}

if (id != cpu_id)
valid = false;
}

/*
* put_user() returns 0 on success, so use 1 to indicate it wasn't
* called and we should skip having incremented the output.
*/
if (!valid)
return 1;

return set_hwprobe(pair, key, id);
}

static
long do_riscv_hwprobe(struct riscv_hwprobe __user *pairs, long pair_count,
long key_offset, long cpu_count,
unsigned long __user *cpus_user, unsigned long flags)
{
size_t out, k;
long ret;
struct cpumask cpus;

/* Check the reserved flags. */
if (flags != 0)
return -EINVAL;

/*
* The only supported values must be the same on all CPUs, but check to
* make sure userspace at least tried to provide something here for
* future compatibility.
*/
cpumask_clear(&cpus);
if (cpu_count > cpumask_size())
cpu_count = cpumask_size();
ret = copy_from_user(&cpus, cpus_user, cpu_count);
if (!ret)
return -EFAULT;

out = 0;
k = key_offset;
while (out < pair_count && k < RISCV_HWPROBE_MAX_KEY) {
long ret;

switch (k) {
case RISCV_HWPROBE_KEY_MVENDORID:
case RISCV_HWPROBE_KEY_MARCHID:
case RISCV_HWPROBE_KEY_MIMPID:
ret = hwprobe_mid(pairs + out, k, &cpus);
break;
}

if (ret < 0)
return ret;
if (ret == 0)
out++;
}

return out;

}

SYSCALL_DEFINE6(riscv_hwprobe, uintptr_t, pairs, uintptr_t, pair_count,
uintptr_t, offset, uintptr_t, cpu_count, uintptr_t, cpus,
uintptr_t, flags)
{
return do_riscv_hwprobe((void __user *)pairs, pair_count, offset,
cpu_count, (void __user *)cpus, flags);
}

0 comments on commit e90cd87

Please sign in to comment.