From 652a72d122ff2c9404c9122e98d035ed6efb7d36 Mon Sep 17 00:00:00 2001 From: Leon <541959102@qq.com> Date: Tue, 15 Oct 2024 01:00:48 +0800 Subject: [PATCH] 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 --- src/arch/riscv/linux/linux.hh | 96 +++++++ src/arch/riscv/linux/se_workload.cc | 386 ++++++++++++++++++++++++++++ 2 files changed, 482 insertions(+) diff --git a/src/arch/riscv/linux/linux.hh b/src/arch/riscv/linux/linux.hh index b2fbdd29f3..17281340d7 100644 --- a/src/arch/riscv/linux/linux.hh +++ b/src/arch/riscv/linux/linux.hh @@ -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 diff --git a/src/arch/riscv/linux/se_workload.cc b/src/arch/riscv/linux/se_workload.cc index 6caec283ed..d3015202b7 100644 --- a/src/arch/riscv/linux/se_workload.cc +++ b/src/arch/riscv/linux/se_workload.cc @@ -44,6 +44,8 @@ #include #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 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 +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 +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 +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(tc, pairs, pair_count, cpusetsize, + cpus_user, flags); + } + + return hwprobe_get_values(tc, pairs, pair_count, cpusetsize, + cpus_user, flags); +} + SyscallDescTable EmuLinux::syscallDescs64 = { { 0, "io_setup" }, { 1, "io_destroy" }, @@ -382,6 +766,7 @@ SyscallDescTable EmuLinux::syscallDescs64 = { { 241, "perf_event_open" }, { 242, "accept4" }, { 243, "recvmmsg" }, + { 258, "riscv_hwprobe", riscvHWProbeFunc }, { 260, "wait4", wait4Func }, { 261, "prlimit64", prlimitFunc }, { 262, "fanotify_init" }, @@ -748,6 +1133,7 @@ SyscallDescTable EmuLinux::syscallDescs32 = { { 241, "perf_event_open" }, { 242, "accept4" }, { 243, "recvmmsg" }, + { 258, "riscv_hwprobe", riscvHWProbeFunc }, { 260, "wait4", wait4Func }, { 261, "prlimit64", prlimitFunc }, { 262, "fanotify_init" },