arch-riscv: Add support for riscv hardware probing syscall (#1525)

This PR adds the support for riscv hardware probing syscall described in
[this](https://docs.kernel.org/arch/riscv/hwprobe.html). The
implementation logic refers to [linux
kernel](https://github.com/torvalds/linux/blob/master/arch/riscv/kernel/sys_hwprobe.c)
and
[qemu](https://github.com/qemu/qemu/blob/master/linux-user/syscall.c).
And passed the [RISC-V hwprobe
exmaple](https://github.com/cyyself/hwprobe) test.

Hope to be merged. Thanks.

Change-Id: Iab714974f0551fc451e0d6846c75a7153809a308

Co-authored-by: Zhibo Hong <hongzhibo@bytedance.com>
This commit is contained in:
Leon
2024-10-15 01:00:48 +08:00
committed by GitHub
parent 1edeeda881
commit 652a72d122
2 changed files with 482 additions and 0 deletions

View File

@@ -34,6 +34,7 @@
#include "arch/riscv/utility.hh"
#include "kern/linux/flag_tables.hh"
#include "kern/linux/linux.hh"
#include "base/bitfield.hh"
namespace gem5
{
@@ -42,6 +43,101 @@ class RiscvLinux : public Linux
{
public:
static const ByteOrder byteOrder = ByteOrder::little;
enum RiscvHwprobeKey
{
Mvendorid,
Marchid,
Mimpid,
BaseBehavior,
IMAExt0,
Cpuperf0,
ZicbozBlockSize,
HighestVirtAddress,
TimeCsrFreq,
MisalignedScalarPerf
};
/* Increase RISCV_HWPROBE_MAX_KEY when adding items. */
#define RISCV_HWPROBE_MAX_KEY 9
BitUnion64(key_base_behavior_t)
Bitfield<0> ima;
EndBitUnion(key_base_behavior_t)
BitUnion64(key_ima_ext_0_t)
Bitfield<49> ZAWRS;
Bitfield<48> ZCMOP;
Bitfield<47> ZCF;
Bitfield<46> ZCD;
Bitfield<45> ZCB;
Bitfield<44> ZCA;
Bitfield<43> ZIMOP;
Bitfield<42> ZVE64D;
Bitfield<41> ZVE64F;
Bitfield<40> ZVE64X;
Bitfield<39> ZVE32F;
Bitfield<38> ZVE32X;
Bitfield<37> ZIHINTPAUSE;
Bitfield<36> ZICOND;
Bitfield<35> ZACAS;
Bitfield<34> ZTSO;
Bitfield<33> ZFA;
Bitfield<32> ZVFHMIN;
Bitfield<31> ZVFH;
Bitfield<30> ZIHINTNTL;
Bitfield<29> ZFHMIN;
Bitfield<28> ZFH;
Bitfield<27> ZVKT;
Bitfield<26> ZVKSH;
Bitfield<25> ZVKSED;
Bitfield<24> ZVKNHB;
Bitfield<22> ZVKNHA;
Bitfield<21> ZVKNED;
Bitfield<20> ZVKG;
Bitfield<19> ZVKB;
Bitfield<18> ZVBC;
Bitfield<17> ZVBB;
Bitfield<16> ZKT;
Bitfield<15> ZKSH;
Bitfield<14> ZKSED;
Bitfield<13> ZKNH;
Bitfield<12> ZKNE;
Bitfield<11> ZKND;
Bitfield<10> ZBKX;
Bitfield<9> ZBKC;
Bitfield<8> ZBKB;
Bitfield<7> ZBC;
Bitfield<6> ZICBOZ;
Bitfield<5> ZBS;
Bitfield<4> ZBB;
Bitfield<3> ZBA;
Bitfield<2> V;
Bitfield<1> C;
Bitfield<0> FD;
EndBitUnion(key_ima_ext_0_t)
enum MisalignedScalarPerf
{
Unknown,
Emulated,
Slow,
Fast,
Unsupported
};
/* Flags */
#define RISCV_HWPROBE_WHICH_CPUS (1 << 0)
struct riscv_hwprobe {
int64_t key;
uint64_t value;
};
typedef struct cpumask {
size_t size;
uint64_t bits[];
} cpumask_t;
};
class RiscvLinux64 : public RiscvLinux, public OpenFlagTable<RiscvLinux64>

View File

@@ -44,6 +44,8 @@
#include <sys/syscall.h>
#include "arch/riscv/process.hh"
#include "arch/riscv/insts/static_inst.hh"
#include "arch/riscv/regs/misc.hh"
#include "base/loader/object_file.hh"
#include "base/trace.hh"
#include "cpu/thread_context.hh"
@@ -134,6 +136,388 @@ unameFunc32(SyscallDesc *desc, ThreadContext *tc, VPtr<Linux::utsname> name)
return 0;
}
static inline void
cpumask_set_cpu(unsigned int cpu, RiscvLinux::cpumask_t *dstp)
{
assert(cpu < dstp->size * 8);
auto &bits = dstp->bits[cpu / sizeof(uint64_t)];
bits = insertBits(bits, cpu % sizeof(uint64_t), 1);
}
static inline void
cpumask_clear_cpu(unsigned int cpu, RiscvLinux::cpumask_t *dstp)
{
assert(cpu < dstp->size * 8);
auto &bits = dstp->bits[cpu / sizeof(uint64_t)];
bits = insertBits(bits, cpu % sizeof(uint64_t), 0);
}
static inline bool
cpumask_test_cpu(unsigned int cpu, const RiscvLinux::cpumask_t *cpumask)
{
assert(cpu < cpumask->size * 8);
return bits(cpumask->bits[cpu / sizeof(uint64_t)], cpu % sizeof(uint64_t)) != 0;
}
static inline void
cpumask_and(RiscvLinux::cpumask_t *dstp, const RiscvLinux::cpumask_t *src1p,
const RiscvLinux::cpumask_t *src2p)
{
assert(dstp->size == src1p->size);
assert(dstp->size == src2p->size);
for (size_t i = 0; i < dstp->size / sizeof(dstp->bits[0]); i++) {
dstp->bits[i] = src1p->bits[i] & src2p->bits[i];
}
}
static inline bool
cpumask_empty(const RiscvLinux::cpumask_t *dstp)
{
for (size_t i = 0; i < dstp->size / sizeof(dstp->bits[0]); i++) {
if (dstp->bits[i] != 0) {
return false;
}
}
return true;
}
static inline void
cpumask_copy(RiscvLinux::cpumask_t *dstp, const RiscvLinux::cpumask_t *srcp)
{
assert(dstp->size == srcp->size);
memcpy(dstp->bits, srcp->bits, srcp->size);
}
static inline void
cpumask_clear(RiscvLinux::cpumask_t *dstp)
{
memset(dstp->bits, 0, dstp->size);
}
static inline RiscvLinux::cpumask_t *
cpumask_malloc(ThreadContext *tc)
{
RiscvLinux::cpumask_t *cpumask;
/* 8-bytes up-boundary alignment */
size_t size = (tc->getSystemPtr()->threads.size() + sizeof(cpumask->bits[0]) - 1) /
sizeof(cpumask->bits[0]) * sizeof(cpumask->bits[0]);
cpumask = (RiscvLinux::cpumask_t *)malloc(sizeof(cpumask->size) + size);
if (cpumask != nullptr) {
cpumask->size = size;
cpumask_clear(cpumask);
}
return cpumask;
}
static inline void
cpumask_free(RiscvLinux::cpumask_t *cpu_online_mask)
{
free(cpu_online_mask);
}
static inline bool
riscv_hwprobe_key_is_valid(int64_t key)
{
return key >= 0 && key <= RISCV_HWPROBE_MAX_KEY;
}
static inline bool
hwprobe_key_is_bitmask(int64_t key)
{
switch (key) {
case RiscvLinux::BaseBehavior:
case RiscvLinux::IMAExt0:
case RiscvLinux::Cpuperf0:
return true;
}
return false;
}
static inline bool
riscv_hwprobe_pair_cmp(RiscvLinux::riscv_hwprobe *pair,
RiscvLinux::riscv_hwprobe *other_pair)
{
if (pair->key != other_pair->key) {
return false;
}
if (hwprobe_key_is_bitmask(pair->key)) {
return (pair->value & other_pair->value) == other_pair->value;
}
return pair->value == other_pair->value;
}
static inline RiscvLinux::cpumask_t *
get_cpu_online_mask(ThreadContext *tc)
{
RiscvLinux::cpumask_t *cpu_online_mask = cpumask_malloc(tc);
if (cpu_online_mask != nullptr) {
for (int i = 0; i < tc->getSystemPtr()->threads.size(); i++) {
CPU_SET(i, (cpu_set_t *)&cpu_online_mask->bits);
}
}
return cpu_online_mask;
}
static void
hwprobe_one_pair(ThreadContext *tc, RiscvLinux::riscv_hwprobe *pair,
RiscvLinux::cpumask_t *cpus)
{
switch (pair->key) {
case RiscvLinux::Mvendorid:
pair->value = tc->readMiscRegNoEffect(CSRData.at(CSR_MVENDORID).physIndex);
break;
case RiscvLinux::Marchid:
pair->value = tc->readMiscRegNoEffect(CSRData.at(CSR_MARCHID).physIndex);
break;
case RiscvLinux::Mimpid:
pair->value = tc->readMiscRegNoEffect(CSRData.at(CSR_MIMPID).physIndex);
break;
case RiscvLinux::BaseBehavior:
{
MISA misa = tc->readMiscRegNoEffect(MISCREG_ISA);
RiscvLinux::key_base_behavior_t *base_behavior =
(RiscvLinux::key_base_behavior_t *)&pair->value;
if (misa.rvi && misa.rvm && misa.rva) {
base_behavior->ima = 1;
}
}
break;
case RiscvLinux::IMAExt0:
{
MISA misa = tc->readMiscRegNoEffect(MISCREG_ISA);
RiscvLinux::key_ima_ext_0_t *ext = (RiscvLinux::key_ima_ext_0_t *)&pair->value;
if (misa.rvf && misa.rvd) ext->FD = 1;
if (misa.rvc) ext->C = 1;
if (misa.rvv) ext->V = 1;
ext->ZBA = 1;
ext->ZBB = 1;
ext->ZBS = 1;
ext->ZICBOZ = 1;
ext->ZBC = 1;
ext->ZBKB = 1;
ext->ZBKC = 1;
ext->ZBKX = 1;
ext->ZKND = 1;
ext->ZKNE = 1;
ext->ZKNH = 1;
ext->ZKSED = 1;
ext->ZKSH = 1;
ext->ZKT = 1;
ext->ZFH = 1;
ext->ZFHMIN = 1;
ext->ZVFH = 1;
ext->ZVFHMIN = 1;
ext->ZICOND = 1;
ext->ZVE64D = 1;
ext->ZCB = 1;
ext->ZCD = 1;
ext->ZCF = 1;
}
break;
case RiscvLinux::Cpuperf0:
case RiscvLinux::MisalignedScalarPerf:
pair->value = RiscvLinux::Slow;
break;
case RiscvLinux::ZicbozBlockSize:
pair->value = tc->getSystemPtr()->cacheLineSize();
break;
case RiscvLinux::HighestVirtAddress:
pair->value = tc->getProcessPtr()->memState->getMmapEnd();
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;
}
}
template <class OS>
static int
hwprobe_get_values(ThreadContext *tc, VPtr<> pairs, typename OS::size_t pair_count,
typename OS::size_t cpusetsize, VPtr<> cpus_user, unsigned int flags)
{
/* Check the reserved flags. */
if (flags != 0) {
return -EINVAL;
}
RiscvLinux::cpumask_t *cpu_online_mask = get_cpu_online_mask(tc);
if (cpu_online_mask == nullptr) {
return -ENOMEM;
}
RiscvLinux::cpumask_t *cpus = cpumask_malloc(tc);
if (cpus == nullptr) {
cpumask_free(cpu_online_mask);
return -ENOMEM;
}
if (cpusetsize > cpu_online_mask->size) {
cpusetsize = cpu_online_mask->size;
}
RiscvLinux::riscv_hwprobe *pair;
BufferArg pairs_buf(pairs, sizeof(RiscvLinux::riscv_hwprobe) * pair_count);
/*
* 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.
*/
if (cpusetsize == 0 && !cpus_user) {
cpumask_copy(cpus, cpu_online_mask);
cpusetsize = cpu_online_mask->size;
} else {
BufferArg cpus_user_buf(cpus_user, cpusetsize);
cpus_user_buf.copyIn(SETranslatingPortProxy(tc));
cpu_online_mask->size = cpusetsize;
cpus->size = cpusetsize;
memcpy(cpus->bits, cpus_user_buf.bufferPtr(), cpusetsize);
/*
* 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)) {
cpumask_free(cpu_online_mask);
cpumask_free(cpus);
return -EINVAL;
}
}
pairs_buf.copyIn(SETranslatingPortProxy(tc));
pair = (RiscvLinux::riscv_hwprobe *)pairs_buf.bufferPtr();
for (size_t i = 0; i < pair_count; i++, pair++) {
pair->value = 0;
hwprobe_one_pair(tc, pair, cpus);
}
pairs_buf.copyOut(SETranslatingPortProxy(tc));
cpumask_free(cpu_online_mask);
cpumask_free(cpus);
return 0;
}
template <class OS>
static int
hwprobe_get_cpus(ThreadContext *tc, VPtr<> pairs, typename OS::size_t pair_count,
typename OS::size_t cpusetsize, VPtr<> cpus_user, unsigned int flags)
{
if (flags != RISCV_HWPROBE_WHICH_CPUS) {
return -EINVAL;
}
if (cpusetsize == 0 || !cpus_user) {
return -EINVAL;
}
RiscvLinux::cpumask_t *cpu_online_mask = get_cpu_online_mask(tc);
if (cpu_online_mask == nullptr) {
return -ENOMEM;
}
RiscvLinux::cpumask_t *cpus = cpumask_malloc(tc);
if (cpus == nullptr) {
cpumask_free(cpu_online_mask);
return -ENOMEM;
}
RiscvLinux::cpumask_t *one_cpu = cpumask_malloc(tc);
if (one_cpu == nullptr) {
cpumask_free(cpu_online_mask);
cpumask_free(cpus);
return -ENOMEM;
}
if (cpusetsize > cpu_online_mask->size) {
cpusetsize = cpu_online_mask->size;
}
RiscvLinux::riscv_hwprobe *pair;
BufferArg cpus_user_buf(cpus_user, cpusetsize);
cpus_user_buf.copyIn(SETranslatingPortProxy(tc));
memcpy(cpus->bits, cpus_user_buf.bufferPtr(), cpusetsize);
if (cpumask_empty(cpus)) {
cpumask_copy(cpus, cpu_online_mask);
cpusetsize = cpu_online_mask->size;
}
cpumask_and(cpus, cpus, cpu_online_mask);
BufferArg pairs_buf(pairs, sizeof(RiscvLinux::riscv_hwprobe) * pair_count);
pairs_buf.copyIn(SETranslatingPortProxy(tc));
pair = (RiscvLinux::riscv_hwprobe *)pairs_buf.bufferPtr();
for (size_t i = 0; i < pair_count; i++, pair++) {
if (!riscv_hwprobe_key_is_valid(pair->key)) {
*pair = (RiscvLinux::riscv_hwprobe){ .key = -1, .value = 0 };
memset(cpus_user_buf.bufferPtr(), 0, cpusetsize);
break;
}
RiscvLinux::riscv_hwprobe tmp =
(RiscvLinux::riscv_hwprobe){ .key = pair->key, .value = 0 };
for (int cpu = 0; cpu < cpusetsize * 8; cpu++) {
if (!cpumask_test_cpu(cpu, cpus)) {
continue;
}
cpumask_set_cpu(cpu, one_cpu);
hwprobe_one_pair(tc, &tmp, one_cpu);
if (!riscv_hwprobe_pair_cmp(&tmp, pair)) {
cpumask_clear_cpu(cpu, cpus);
}
cpumask_clear_cpu(cpu, one_cpu);
}
}
pairs_buf.copyOut(SETranslatingPortProxy(tc));
cpus_user_buf.copyOut(SETranslatingPortProxy(tc));
cpumask_free(cpu_online_mask);
cpumask_free(cpus);
cpumask_free(one_cpu);
return 0;
}
template <class OS>
static SyscallReturn
riscvHWProbeFunc(SyscallDesc *desc, ThreadContext *tc, VPtr<> pairs,
typename OS::size_t pair_count, typename OS::size_t cpusetsize,
VPtr<> cpus_user, unsigned int flags)
{
if (flags & RISCV_HWPROBE_WHICH_CPUS) {
return hwprobe_get_cpus<OS>(tc, pairs, pair_count, cpusetsize,
cpus_user, flags);
}
return hwprobe_get_values<OS>(tc, pairs, pair_count, cpusetsize,
cpus_user, flags);
}
SyscallDescTable<SEWorkload::SyscallABI64> EmuLinux::syscallDescs64 = {
{ 0, "io_setup" },
{ 1, "io_destroy" },
@@ -382,6 +766,7 @@ SyscallDescTable<SEWorkload::SyscallABI64> EmuLinux::syscallDescs64 = {
{ 241, "perf_event_open" },
{ 242, "accept4" },
{ 243, "recvmmsg" },
{ 258, "riscv_hwprobe", riscvHWProbeFunc<RiscvLinux64> },
{ 260, "wait4", wait4Func<RiscvLinux64> },
{ 261, "prlimit64", prlimitFunc<RiscvLinux64> },
{ 262, "fanotify_init" },
@@ -748,6 +1133,7 @@ SyscallDescTable<SEWorkload::SyscallABI32> EmuLinux::syscallDescs32 = {
{ 241, "perf_event_open" },
{ 242, "accept4" },
{ 243, "recvmmsg" },
{ 258, "riscv_hwprobe", riscvHWProbeFunc<RiscvLinux32> },
{ 260, "wait4", wait4Func<RiscvLinux32> },
{ 261, "prlimit64", prlimitFunc<RiscvLinux32> },
{ 262, "fanotify_init" },