target-arm queue:
* Optimize codegen for MVE when predication not active * hvf: Add Apple Silicon support * hw/intc: Set GIC maintenance interrupt level to only 0 or 1 * Fix mishandling of MVE FPSCR.LTPSIZE reset for usermode emulator * elf2dmp: Fix coverity nits -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmFJ+sUZHHBldGVyLm1h eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3oxLEACJSAHzww9iuVa18kTr0A8L Zi5IXewym/XIBGI0dzSdcWm+KmwLSsWQCE267M8/wG+H4aPwkWKk5y9DjIUyvFd4 ozxgaAuQtIFZDlubZ1VdVo3b+cMfF0xLye8iIjeYUIDfoYAZzN9+KH8ctD0PWgLm 3FivfVjFzX5KnC2bOi6P13bzZxPoyQPExcQbtf85Whaujui40rPLrVsnl+6N3ulw sgBYUjT8jxECdBLh/kaSqt33WoAL23amIPfuZl/juNQwxtXYu+tCyFHtOxVNZ4Rt BOOYHgCDWJNKjdts2CA/YqITOyk44hMfRAYZKuSQAz1BVZVi2Z8FDgjTwAzc9inE /nB6EqT/67tF8lGUg0w9OnsGCSA6px3xUYUYrZk7efo7oGcyknsleDbuKE76UHE0 plFuUPZyc0jBGKdSwZGBwzc5UkA3qmcoN83HAl2jE/5piHTaqqyu7KSqMXymwAij WGOO5L2Svgwi+11BgfEFbPbgKOz+XwwvAMXj1drQm6Nf0hKt1YQYvH6oQQYoqjT+ bkUrejjOb04tyA50KTqz4gbj9s8B3/cPSuD3xWl390bDbP7jpgFOmffVj5A16wbx DL7rmwnghoOI3T+5uXXNQFA856XfuVWy1gtwpFzCYZu4hseNPw+MXKwj4YUfM/V3 QonDlN98W4C5M2u4CW09Zw== =vapC -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20210921' into staging target-arm queue: * Optimize codegen for MVE when predication not active * hvf: Add Apple Silicon support * hw/intc: Set GIC maintenance interrupt level to only 0 or 1 * Fix mishandling of MVE FPSCR.LTPSIZE reset for usermode emulator * elf2dmp: Fix coverity nits # gpg: Signature made Tue 21 Sep 2021 16:31:17 BST # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20210921: (27 commits) target/arm: Optimize MVE 1op-immediate insns target/arm: Optimize MVE VSLI and VSRI target/arm: Optimize MVE VSHLL and VMOVL target/arm: Optimize MVE VSHL, VSHR immediate forms target/arm: Optimize MVE VMVN target/arm: Optimize MVE VDUP target/arm: Optimize MVE VNEG, VABS target/arm: Optimize MVE arithmetic ops target/arm: Optimize MVE logic ops target/arm: Add TB flag for "MVE insns not predicated" target/arm: Enforce that FPDSCR.LTPSIZE is 4 on inbound migration target/arm: Avoid goto_tb if we're trying to exit to the main loop hvf: arm: Add rudimentary PMC support arm: Add Hypervisor.framework build target hvf: arm: Implement PSCI handling hvf: arm: Implement -cpu host arm/hvf: Add a WFI handler hvf: Add Apple Silicon support hvf: Introduce hvf_arch_init() callback hvf: Add execute to dirty log permission bitmap ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
81ceb36b96
@ -433,6 +433,11 @@ F: accel/accel-*.c
|
||||
F: accel/Makefile.objs
|
||||
F: accel/stubs/Makefile.objs
|
||||
|
||||
Apple Silicon HVF CPUs
|
||||
M: Alexander Graf <agraf@csgraf.de>
|
||||
S: Maintained
|
||||
F: target/arm/hvf/
|
||||
|
||||
X86 HVF CPUs
|
||||
M: Cameron Esfahani <dirty@apple.com>
|
||||
M: Roman Bolshakov <r.bolshakov@yadro.com>
|
||||
|
@ -60,6 +60,10 @@
|
||||
|
||||
HVFState *hvf_state;
|
||||
|
||||
#ifdef __aarch64__
|
||||
#define HV_VM_DEFAULT NULL
|
||||
#endif
|
||||
|
||||
/* Memory slots */
|
||||
|
||||
hvf_slot *hvf_find_overlap_slot(uint64_t start, uint64_t size)
|
||||
@ -239,12 +243,12 @@ static void hvf_set_dirty_tracking(MemoryRegionSection *section, bool on)
|
||||
if (on) {
|
||||
slot->flags |= HVF_SLOT_LOG;
|
||||
hv_vm_protect((uintptr_t)slot->start, (size_t)slot->size,
|
||||
HV_MEMORY_READ);
|
||||
HV_MEMORY_READ | HV_MEMORY_EXEC);
|
||||
/* stop tracking region*/
|
||||
} else {
|
||||
slot->flags &= ~HVF_SLOT_LOG;
|
||||
hv_vm_protect((uintptr_t)slot->start, (size_t)slot->size,
|
||||
HV_MEMORY_READ | HV_MEMORY_WRITE);
|
||||
HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC);
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,7 +328,8 @@ static int hvf_accel_init(MachineState *ms)
|
||||
|
||||
hvf_state = s;
|
||||
memory_listener_register(&hvf_memory_listener, &address_space_memory);
|
||||
return 0;
|
||||
|
||||
return hvf_arch_init();
|
||||
}
|
||||
|
||||
static void hvf_accel_class_init(ObjectClass *oc, void *data)
|
||||
@ -365,17 +370,20 @@ static int hvf_init_vcpu(CPUState *cpu)
|
||||
cpu->hvf = g_malloc0(sizeof(*cpu->hvf));
|
||||
|
||||
/* init cpu signals */
|
||||
sigset_t set;
|
||||
struct sigaction sigact;
|
||||
|
||||
memset(&sigact, 0, sizeof(sigact));
|
||||
sigact.sa_handler = dummy_signal;
|
||||
sigaction(SIG_IPI, &sigact, NULL);
|
||||
|
||||
pthread_sigmask(SIG_BLOCK, NULL, &set);
|
||||
sigdelset(&set, SIG_IPI);
|
||||
pthread_sigmask(SIG_BLOCK, NULL, &cpu->hvf->unblock_ipi_mask);
|
||||
sigdelset(&cpu->hvf->unblock_ipi_mask, SIG_IPI);
|
||||
|
||||
#ifdef __aarch64__
|
||||
r = hv_vcpu_create(&cpu->hvf->fd, (hv_vcpu_exit_t **)&cpu->hvf->exit, NULL);
|
||||
#else
|
||||
r = hv_vcpu_create((hv_vcpuid_t *)&cpu->hvf->fd, HV_VCPU_DEFAULT);
|
||||
#endif
|
||||
cpu->vcpu_dirty = 1;
|
||||
assert_hvf_ok(r);
|
||||
|
||||
@ -451,6 +459,7 @@ static void hvf_accel_ops_class_init(ObjectClass *oc, void *data)
|
||||
AccelOpsClass *ops = ACCEL_OPS_CLASS(oc);
|
||||
|
||||
ops->create_vcpu_thread = hvf_start_vcpu_thread;
|
||||
ops->kick_vcpu_thread = hvf_kick_vcpu_thread;
|
||||
|
||||
ops->synchronize_post_reset = hvf_cpu_synchronize_post_reset;
|
||||
ops->synchronize_post_init = hvf_cpu_synchronize_post_init;
|
||||
|
@ -25,21 +25,19 @@ int download_url(const char *name, const char *url)
|
||||
goto out_curl;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
if (curl_easy_perform(curl) != CURLE_OK) {
|
||||
err = 1;
|
||||
fclose(file);
|
||||
if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK
|
||||
|| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL) != CURLE_OK
|
||||
|| curl_easy_setopt(curl, CURLOPT_WRITEDATA, file) != CURLE_OK
|
||||
|| curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) != CURLE_OK
|
||||
|| curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK
|
||||
|| curl_easy_perform(curl) != CURLE_OK) {
|
||||
unlink(name);
|
||||
goto out_curl;
|
||||
fclose(file);
|
||||
err = 1;
|
||||
} else {
|
||||
err = fclose(file);
|
||||
}
|
||||
|
||||
err = fclose(file);
|
||||
|
||||
out_curl:
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
|
@ -215,6 +215,10 @@ out_symbols:
|
||||
|
||||
static int pdb_reader_ds_init(struct pdb_reader *r, PDB_DS_HEADER *hdr)
|
||||
{
|
||||
if (hdr->block_size == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(r->file_used, 0, sizeof(r->file_used));
|
||||
r->ds.header = hdr;
|
||||
r->ds.toc = pdb_ds_read(hdr, (uint32_t *)((uint8_t *)hdr +
|
||||
|
@ -417,8 +417,9 @@ static void gicv3_cpuif_virt_update(GICv3CPUState *cs)
|
||||
}
|
||||
}
|
||||
|
||||
if (cs->ich_hcr_el2 & ICH_HCR_EL2_EN) {
|
||||
maintlevel = maintenance_interrupt_state(cs);
|
||||
if ((cs->ich_hcr_el2 & ICH_HCR_EL2_EN) &&
|
||||
maintenance_interrupt_state(cs) != 0) {
|
||||
maintlevel = 1;
|
||||
}
|
||||
|
||||
trace_gicv3_cpuif_virt_set_irqs(gicv3_redist_affid(cs), fiqlevel,
|
||||
|
@ -11,7 +11,11 @@
|
||||
#ifndef HVF_INT_H
|
||||
#define HVF_INT_H
|
||||
|
||||
#ifdef __aarch64__
|
||||
#include <Hypervisor/Hypervisor.h>
|
||||
#else
|
||||
#include <Hypervisor/hv.h>
|
||||
#endif
|
||||
|
||||
/* hvf_slot flags */
|
||||
#define HVF_SLOT_LOG (1 << 0)
|
||||
@ -40,19 +44,25 @@ struct HVFState {
|
||||
int num_slots;
|
||||
|
||||
hvf_vcpu_caps *hvf_caps;
|
||||
uint64_t vtimer_offset;
|
||||
};
|
||||
extern HVFState *hvf_state;
|
||||
|
||||
struct hvf_vcpu_state {
|
||||
int fd;
|
||||
uint64_t fd;
|
||||
void *exit;
|
||||
bool vtimer_masked;
|
||||
sigset_t unblock_ipi_mask;
|
||||
};
|
||||
|
||||
void assert_hvf_ok(hv_return_t ret);
|
||||
int hvf_arch_init(void);
|
||||
int hvf_arch_init_vcpu(CPUState *cpu);
|
||||
void hvf_arch_vcpu_destroy(CPUState *cpu);
|
||||
int hvf_vcpu_exec(CPUState *);
|
||||
hvf_slot *hvf_find_overlap_slot(uint64_t, uint64_t);
|
||||
int hvf_put_registers(CPUState *);
|
||||
int hvf_get_registers(CPUState *);
|
||||
void hvf_kick_vcpu_thread(CPUState *cpu);
|
||||
|
||||
#endif
|
||||
|
@ -77,6 +77,13 @@ else
|
||||
endif
|
||||
|
||||
accelerator_targets = { 'CONFIG_KVM': kvm_targets }
|
||||
|
||||
if cpu in ['aarch64']
|
||||
accelerator_targets += {
|
||||
'CONFIG_HVF': ['aarch64-softmmu']
|
||||
}
|
||||
endif
|
||||
|
||||
if cpu in ['x86', 'x86_64', 'arm', 'aarch64']
|
||||
# i386 emulator provides xenpv machine type for multiple architectures
|
||||
accelerator_targets += {
|
||||
@ -2169,6 +2176,7 @@ if have_system or have_user
|
||||
'accel/tcg',
|
||||
'hw/core',
|
||||
'target/arm',
|
||||
'target/arm/hvf',
|
||||
'target/hppa',
|
||||
'target/i386',
|
||||
'target/i386/kvm',
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "sysemu/tcg.h"
|
||||
#include "sysemu/hw_accel.h"
|
||||
#include "kvm_arm.h"
|
||||
#include "hvf_arm.h"
|
||||
#include "disas/capstone.h"
|
||||
#include "fpu/softfloat.h"
|
||||
|
||||
@ -266,11 +267,24 @@ static void arm_cpu_reset(DeviceState *dev)
|
||||
}
|
||||
env->daif = PSTATE_D | PSTATE_A | PSTATE_I | PSTATE_F;
|
||||
|
||||
/* AArch32 has a hard highvec setting of 0xFFFF0000. If we are currently
|
||||
* executing as AArch32 then check if highvecs are enabled and
|
||||
* adjust the PC accordingly.
|
||||
*/
|
||||
if (A32_BANKED_CURRENT_REG_GET(env, sctlr) & SCTLR_V) {
|
||||
env->regs[15] = 0xFFFF0000;
|
||||
}
|
||||
|
||||
env->vfp.xregs[ARM_VFP_FPEXC] = 0;
|
||||
#endif
|
||||
|
||||
if (arm_feature(env, ARM_FEATURE_M)) {
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
uint32_t initial_msp; /* Loaded from 0x0 */
|
||||
uint32_t initial_pc; /* Loaded from 0x4 */
|
||||
uint8_t *rom;
|
||||
uint32_t vecbase;
|
||||
#endif
|
||||
|
||||
if (cpu_isar_feature(aa32_lob, cpu)) {
|
||||
/*
|
||||
@ -324,6 +338,8 @@ static void arm_cpu_reset(DeviceState *dev)
|
||||
env->v7m.fpccr[M_REG_S] = R_V7M_FPCCR_ASPEN_MASK |
|
||||
R_V7M_FPCCR_LSPEN_MASK | R_V7M_FPCCR_S_MASK;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
/* Unlike A/R profile, M profile defines the reset LR value */
|
||||
env->regs[14] = 0xffffffff;
|
||||
|
||||
@ -352,14 +368,19 @@ static void arm_cpu_reset(DeviceState *dev)
|
||||
env->regs[13] = initial_msp & 0xFFFFFFFC;
|
||||
env->regs[15] = initial_pc & ~1;
|
||||
env->thumb = initial_pc & 1;
|
||||
}
|
||||
|
||||
/* AArch32 has a hard highvec setting of 0xFFFF0000. If we are currently
|
||||
* executing as AArch32 then check if highvecs are enabled and
|
||||
* adjust the PC accordingly.
|
||||
*/
|
||||
if (A32_BANKED_CURRENT_REG_GET(env, sctlr) & SCTLR_V) {
|
||||
env->regs[15] = 0xFFFF0000;
|
||||
#else
|
||||
/*
|
||||
* For user mode we run non-secure and with access to the FPU.
|
||||
* The FPU context is active (ie does not need further setup)
|
||||
* and is owned by non-secure.
|
||||
*/
|
||||
env->v7m.secure = false;
|
||||
env->v7m.nsacr = 0xcff;
|
||||
env->v7m.cpacr[M_REG_NS] = 0xf0ffff;
|
||||
env->v7m.fpccr[M_REG_S] &=
|
||||
~(R_V7M_FPCCR_LSPEN_MASK | R_V7M_FPCCR_S_MASK);
|
||||
env->v7m.control[M_REG_S] |= R_V7M_CONTROL_FPCA_MASK;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* M profile requires that reset clears the exclusive monitor;
|
||||
@ -368,9 +389,6 @@ static void arm_cpu_reset(DeviceState *dev)
|
||||
*/
|
||||
arm_clear_exclusive(env);
|
||||
|
||||
env->vfp.xregs[ARM_VFP_FPEXC] = 0;
|
||||
#endif
|
||||
|
||||
if (arm_feature(env, ARM_FEATURE_PMSA)) {
|
||||
if (cpu->pmsav7_dregion > 0) {
|
||||
if (arm_feature(env, ARM_FEATURE_V8)) {
|
||||
@ -1095,8 +1113,8 @@ static void arm_cpu_initfn(Object *obj)
|
||||
cpu->psci_version = 1; /* By default assume PSCI v0.1 */
|
||||
cpu->kvm_target = QEMU_KVM_ARM_TARGET_NONE;
|
||||
|
||||
if (tcg_enabled()) {
|
||||
cpu->psci_version = 2; /* TCG implements PSCI 0.2 */
|
||||
if (tcg_enabled() || hvf_enabled()) {
|
||||
cpu->psci_version = 2; /* TCG and HVF implement PSCI 0.2 */
|
||||
}
|
||||
}
|
||||
|
||||
@ -1400,8 +1418,8 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
|
||||
* this is the first point where we can report it.
|
||||
*/
|
||||
if (cpu->host_cpu_probe_failed) {
|
||||
if (!kvm_enabled()) {
|
||||
error_setg(errp, "The 'host' CPU type can only be used with KVM");
|
||||
if (!kvm_enabled() && !hvf_enabled()) {
|
||||
error_setg(errp, "The 'host' CPU type can only be used with KVM or HVF");
|
||||
} else {
|
||||
error_setg(errp, "Failed to retrieve host CPU features");
|
||||
}
|
||||
@ -2061,15 +2079,19 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data)
|
||||
#endif /* CONFIG_TCG */
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KVM
|
||||
#if defined(CONFIG_KVM) || defined(CONFIG_HVF)
|
||||
static void arm_host_initfn(Object *obj)
|
||||
{
|
||||
ARMCPU *cpu = ARM_CPU(obj);
|
||||
|
||||
#ifdef CONFIG_KVM
|
||||
kvm_arm_set_cpu_features_from_host(cpu);
|
||||
if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
|
||||
aarch64_add_sve_properties(obj);
|
||||
}
|
||||
#else
|
||||
hvf_arm_set_cpu_features_from_host(cpu);
|
||||
#endif
|
||||
arm_cpu_post_init(obj);
|
||||
}
|
||||
|
||||
@ -2129,7 +2151,7 @@ static void arm_cpu_register_types(void)
|
||||
{
|
||||
type_register_static(&arm_cpu_type_info);
|
||||
|
||||
#ifdef CONFIG_KVM
|
||||
#if defined(CONFIG_KVM) || defined(CONFIG_HVF)
|
||||
type_register_static(&host_arm_cpu_type_info);
|
||||
#endif
|
||||
}
|
||||
|
@ -3015,6 +3015,8 @@ bool write_cpustate_to_list(ARMCPU *cpu, bool kvm_sync);
|
||||
#define ARM_CPU_TYPE_NAME(name) (name ARM_CPU_TYPE_SUFFIX)
|
||||
#define CPU_RESOLVING_TYPE TYPE_ARM_CPU
|
||||
|
||||
#define TYPE_ARM_HOST_CPU "host-" TYPE_ARM_CPU
|
||||
|
||||
#define cpu_signal_handler cpu_arm_signal_handler
|
||||
#define cpu_list arm_cpu_list
|
||||
|
||||
@ -3439,7 +3441,7 @@ typedef ARMCPU ArchCPU;
|
||||
* | TBFLAG_AM32 | +-----+----------+
|
||||
* | | |TBFLAG_M32|
|
||||
* +-------------+----------------+----------+
|
||||
* 31 23 5 4 0
|
||||
* 31 23 6 5 0
|
||||
*
|
||||
* Unless otherwise noted, these bits are cached in env->hflags.
|
||||
*/
|
||||
@ -3497,6 +3499,8 @@ FIELD(TBFLAG_M32, LSPACT, 2, 1) /* Not cached. */
|
||||
FIELD(TBFLAG_M32, NEW_FP_CTXT_NEEDED, 3, 1) /* Not cached. */
|
||||
/* Set if FPCCR.S does not match current security state */
|
||||
FIELD(TBFLAG_M32, FPCCR_S_WRONG, 4, 1) /* Not cached. */
|
||||
/* Set if MVE insns are definitely not predicated by VPR or LTPSIZE */
|
||||
FIELD(TBFLAG_M32, MVE_NO_PRED, 5, 1) /* Not cached. */
|
||||
|
||||
/*
|
||||
* Bit usage when in AArch64 state
|
||||
|
@ -1114,50 +1114,6 @@ static const ARMCPRegInfo v6_cp_reginfo[] = {
|
||||
REGINFO_SENTINEL
|
||||
};
|
||||
|
||||
/* Definitions for the PMU registers */
|
||||
#define PMCRN_MASK 0xf800
|
||||
#define PMCRN_SHIFT 11
|
||||
#define PMCRLC 0x40
|
||||
#define PMCRDP 0x20
|
||||
#define PMCRX 0x10
|
||||
#define PMCRD 0x8
|
||||
#define PMCRC 0x4
|
||||
#define PMCRP 0x2
|
||||
#define PMCRE 0x1
|
||||
/*
|
||||
* Mask of PMCR bits writeable by guest (not including WO bits like C, P,
|
||||
* which can be written as 1 to trigger behaviour but which stay RAZ).
|
||||
*/
|
||||
#define PMCR_WRITEABLE_MASK (PMCRLC | PMCRDP | PMCRX | PMCRD | PMCRE)
|
||||
|
||||
#define PMXEVTYPER_P 0x80000000
|
||||
#define PMXEVTYPER_U 0x40000000
|
||||
#define PMXEVTYPER_NSK 0x20000000
|
||||
#define PMXEVTYPER_NSU 0x10000000
|
||||
#define PMXEVTYPER_NSH 0x08000000
|
||||
#define PMXEVTYPER_M 0x04000000
|
||||
#define PMXEVTYPER_MT 0x02000000
|
||||
#define PMXEVTYPER_EVTCOUNT 0x0000ffff
|
||||
#define PMXEVTYPER_MASK (PMXEVTYPER_P | PMXEVTYPER_U | PMXEVTYPER_NSK | \
|
||||
PMXEVTYPER_NSU | PMXEVTYPER_NSH | \
|
||||
PMXEVTYPER_M | PMXEVTYPER_MT | \
|
||||
PMXEVTYPER_EVTCOUNT)
|
||||
|
||||
#define PMCCFILTR 0xf8000000
|
||||
#define PMCCFILTR_M PMXEVTYPER_M
|
||||
#define PMCCFILTR_EL0 (PMCCFILTR | PMCCFILTR_M)
|
||||
|
||||
static inline uint32_t pmu_num_counters(CPUARMState *env)
|
||||
{
|
||||
return (env->cp15.c9_pmcr & PMCRN_MASK) >> PMCRN_SHIFT;
|
||||
}
|
||||
|
||||
/* Bits allowed to be set/cleared for PMCNTEN* and PMINTEN* */
|
||||
static inline uint64_t pmu_counter_mask(CPUARMState *env)
|
||||
{
|
||||
return (1 << 31) | ((1 << pmu_num_counters(env)) - 1);
|
||||
}
|
||||
|
||||
typedef struct pm_event {
|
||||
uint16_t number; /* PMEVTYPER.evtCount is 16 bits wide */
|
||||
/* If the event is supported on this CPU (used to generate PMCEID[01]) */
|
||||
@ -13681,6 +13637,35 @@ static inline void assert_hflags_rebuild_correctly(CPUARMState *env)
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool mve_no_pred(CPUARMState *env)
|
||||
{
|
||||
/*
|
||||
* Return true if there is definitely no predication of MVE
|
||||
* instructions by VPR or LTPSIZE. (Returning false even if there
|
||||
* isn't any predication is OK; generated code will just be
|
||||
* a little worse.)
|
||||
* If the CPU does not implement MVE then this TB flag is always 0.
|
||||
*
|
||||
* NOTE: if you change this logic, the "recalculate s->mve_no_pred"
|
||||
* logic in gen_update_fp_context() needs to be updated to match.
|
||||
*
|
||||
* We do not include the effect of the ECI bits here -- they are
|
||||
* tracked in other TB flags. This simplifies the logic for
|
||||
* "when did we emit code that changes the MVE_NO_PRED TB flag
|
||||
* and thus need to end the TB?".
|
||||
*/
|
||||
if (cpu_isar_feature(aa32_mve, env_archcpu(env))) {
|
||||
return false;
|
||||
}
|
||||
if (env->v7m.vpr) {
|
||||
return false;
|
||||
}
|
||||
if (env->v7m.ltpsize < 4) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void cpu_get_tb_cpu_state(CPUARMState *env, target_ulong *pc,
|
||||
target_ulong *cs_base, uint32_t *pflags)
|
||||
{
|
||||
@ -13720,6 +13705,10 @@ void cpu_get_tb_cpu_state(CPUARMState *env, target_ulong *pc,
|
||||
if (env->v7m.fpccr[is_secure] & R_V7M_FPCCR_LSPACT_MASK) {
|
||||
DP_TBFLAG_M32(flags, LSPACT, 1);
|
||||
}
|
||||
|
||||
if (mve_no_pred(env)) {
|
||||
DP_TBFLAG_M32(flags, MVE_NO_PRED, 1);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Note that XSCALE_CPAR shares bits with VECSTRIDE.
|
||||
|
1278
target/arm/hvf/hvf.c
Normal file
1278
target/arm/hvf/hvf.c
Normal file
File diff suppressed because it is too large
Load Diff
3
target/arm/hvf/meson.build
Normal file
3
target/arm/hvf/meson.build
Normal file
@ -0,0 +1,3 @@
|
||||
arm_softmmu_ss.add(when: [hvf, 'CONFIG_HVF'], if_true: files(
|
||||
'hvf.c',
|
||||
))
|
11
target/arm/hvf/trace-events
Normal file
11
target/arm/hvf/trace-events
Normal file
@ -0,0 +1,11 @@
|
||||
hvf_unhandled_sysreg_read(uint64_t pc, uint32_t reg, uint32_t op0, uint32_t op1, uint32_t crn, uint32_t crm, uint32_t op2) "unhandled sysreg read at pc=0x%"PRIx64": 0x%08x (op0=%d op1=%d crn=%d crm=%d op2=%d)"
|
||||
hvf_unhandled_sysreg_write(uint64_t pc, uint32_t reg, uint32_t op0, uint32_t op1, uint32_t crn, uint32_t crm, uint32_t op2) "unhandled sysreg write at pc=0x%"PRIx64": 0x%08x (op0=%d op1=%d crn=%d crm=%d op2=%d)"
|
||||
hvf_inject_fiq(void) "injecting FIQ"
|
||||
hvf_inject_irq(void) "injecting IRQ"
|
||||
hvf_data_abort(uint64_t pc, uint64_t va, uint64_t pa, bool isv, bool iswrite, bool s1ptw, uint32_t len, uint32_t srt) "data abort: [pc=0x%"PRIx64" va=0x%016"PRIx64" pa=0x%016"PRIx64" isv=%d iswrite=%d s1ptw=%d len=%d srt=%d]"
|
||||
hvf_sysreg_read(uint32_t reg, uint32_t op0, uint32_t op1, uint32_t crn, uint32_t crm, uint32_t op2, uint64_t val) "sysreg read 0x%08x (op0=%d op1=%d crn=%d crm=%d op2=%d) = 0x%016"PRIx64
|
||||
hvf_sysreg_write(uint32_t reg, uint32_t op0, uint32_t op1, uint32_t crn, uint32_t crm, uint32_t op2, uint64_t val) "sysreg write 0x%08x (op0=%d op1=%d crn=%d crm=%d op2=%d, val=0x%016"PRIx64")"
|
||||
hvf_unknown_hvc(uint64_t x0) "unknown HVC! 0x%016"PRIx64
|
||||
hvf_unknown_smc(uint64_t x0) "unknown SMC! 0x%016"PRIx64
|
||||
hvf_exit(uint64_t syndrome, uint32_t ec, uint64_t pc) "exit: 0x%"PRIx64" [ec=0x%x pc=0x%"PRIx64"]"
|
||||
hvf_psci_call(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint32_t cpuid) "PSCI Call x0=0x%016"PRIx64" x1=0x%016"PRIx64" x2=0x%016"PRIx64" x3=0x%016"PRIx64" cpu=0x%x"
|
18
target/arm/hvf_arm.h
Normal file
18
target/arm/hvf_arm.h
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* QEMU Hypervisor.framework (HVF) support -- ARM specifics
|
||||
*
|
||||
* Copyright (c) 2021 Alexander Graf
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QEMU_HVF_ARM_H
|
||||
#define QEMU_HVF_ARM_H
|
||||
|
||||
#include "cpu.h"
|
||||
|
||||
void hvf_arm_set_cpu_features_from_host(struct ARMCPU *cpu);
|
||||
|
||||
#endif
|
@ -1226,4 +1226,48 @@ enum MVEECIState {
|
||||
/* All other values reserved */
|
||||
};
|
||||
|
||||
/* Definitions for the PMU registers */
|
||||
#define PMCRN_MASK 0xf800
|
||||
#define PMCRN_SHIFT 11
|
||||
#define PMCRLC 0x40
|
||||
#define PMCRDP 0x20
|
||||
#define PMCRX 0x10
|
||||
#define PMCRD 0x8
|
||||
#define PMCRC 0x4
|
||||
#define PMCRP 0x2
|
||||
#define PMCRE 0x1
|
||||
/*
|
||||
* Mask of PMCR bits writeable by guest (not including WO bits like C, P,
|
||||
* which can be written as 1 to trigger behaviour but which stay RAZ).
|
||||
*/
|
||||
#define PMCR_WRITEABLE_MASK (PMCRLC | PMCRDP | PMCRX | PMCRD | PMCRE)
|
||||
|
||||
#define PMXEVTYPER_P 0x80000000
|
||||
#define PMXEVTYPER_U 0x40000000
|
||||
#define PMXEVTYPER_NSK 0x20000000
|
||||
#define PMXEVTYPER_NSU 0x10000000
|
||||
#define PMXEVTYPER_NSH 0x08000000
|
||||
#define PMXEVTYPER_M 0x04000000
|
||||
#define PMXEVTYPER_MT 0x02000000
|
||||
#define PMXEVTYPER_EVTCOUNT 0x0000ffff
|
||||
#define PMXEVTYPER_MASK (PMXEVTYPER_P | PMXEVTYPER_U | PMXEVTYPER_NSK | \
|
||||
PMXEVTYPER_NSU | PMXEVTYPER_NSH | \
|
||||
PMXEVTYPER_M | PMXEVTYPER_MT | \
|
||||
PMXEVTYPER_EVTCOUNT)
|
||||
|
||||
#define PMCCFILTR 0xf8000000
|
||||
#define PMCCFILTR_M PMXEVTYPER_M
|
||||
#define PMCCFILTR_EL0 (PMCCFILTR | PMCCFILTR_M)
|
||||
|
||||
static inline uint32_t pmu_num_counters(CPUARMState *env)
|
||||
{
|
||||
return (env->cp15.c9_pmcr & PMCRN_MASK) >> PMCRN_SHIFT;
|
||||
}
|
||||
|
||||
/* Bits allowed to be set/cleared for PMCNTEN* and PMINTEN* */
|
||||
static inline uint64_t pmu_counter_mask(CPUARMState *env)
|
||||
{
|
||||
return (1 << 31) | ((1 << pmu_num_counters(env)) - 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -214,8 +214,6 @@ bool kvm_arm_create_scratch_host_vcpu(const uint32_t *cpus_to_try,
|
||||
*/
|
||||
void kvm_arm_destroy_scratch_host_vcpu(int *fdarray);
|
||||
|
||||
#define TYPE_ARM_HOST_CPU "host-" TYPE_ARM_CPU
|
||||
|
||||
/**
|
||||
* ARMHostCPUFeatures: information about the host CPU (identified
|
||||
* by asking the host kernel)
|
||||
|
@ -781,6 +781,19 @@ static int cpu_post_load(void *opaque, int version_id)
|
||||
hw_breakpoint_update_all(cpu);
|
||||
hw_watchpoint_update_all(cpu);
|
||||
|
||||
/*
|
||||
* TCG gen_update_fp_context() relies on the invariant that
|
||||
* FPDSCR.LTPSIZE is constant 4 for M-profile with the LOB extension;
|
||||
* forbid bogus incoming data with some other value.
|
||||
*/
|
||||
if (arm_feature(env, ARM_FEATURE_M) && cpu_isar_feature(aa32_lob, cpu)) {
|
||||
if (extract32(env->v7m.fpdscr[M_REG_NS],
|
||||
FPCR_LTPSIZE_SHIFT, FPCR_LTPSIZE_LENGTH) != 4 ||
|
||||
extract32(env->v7m.fpdscr[M_REG_S],
|
||||
FPCR_LTPSIZE_SHIFT, FPCR_LTPSIZE_LENGTH) != 4) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (!kvm_enabled()) {
|
||||
pmu_op_finish(&cpu->env);
|
||||
}
|
||||
|
@ -60,5 +60,7 @@ arm_softmmu_ss.add(files(
|
||||
'psci.c',
|
||||
))
|
||||
|
||||
subdir('hvf')
|
||||
|
||||
target_arch += {'arm': arm_ss}
|
||||
target_softmmu_arch += {'arm': arm_softmmu_ss}
|
||||
|
@ -95,7 +95,10 @@ static bool trans_VLLDM_VLSTM(DisasContext *s, arg_VLLDM_VLSTM *a)
|
||||
|
||||
clear_eci_state(s);
|
||||
|
||||
/* End the TB, because we have updated FP control bits */
|
||||
/*
|
||||
* End the TB, because we have updated FP control bits,
|
||||
* and possibly VPR or LTPSIZE.
|
||||
*/
|
||||
s->base.is_jmp = DISAS_UPDATE_EXIT;
|
||||
return true;
|
||||
}
|
||||
@ -397,6 +400,7 @@ static bool gen_M_fp_sysreg_write(DisasContext *s, int regno,
|
||||
store_cpu_field(control, v7m.control[M_REG_S]);
|
||||
tcg_gen_andi_i32(tmp, tmp, ~FPCR_NZCV_MASK);
|
||||
gen_helper_vfp_set_fpscr(cpu_env, tmp);
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
tcg_temp_free_i32(tmp);
|
||||
tcg_temp_free_i32(sfpa);
|
||||
break;
|
||||
@ -409,6 +413,7 @@ static bool gen_M_fp_sysreg_write(DisasContext *s, int regno,
|
||||
}
|
||||
tmp = loadfn(s, opaque, true);
|
||||
store_cpu_field(tmp, v7m.vpr);
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
break;
|
||||
case ARM_VFP_P0:
|
||||
{
|
||||
@ -418,6 +423,7 @@ static bool gen_M_fp_sysreg_write(DisasContext *s, int regno,
|
||||
tcg_gen_deposit_i32(vpr, vpr, tmp,
|
||||
R_V7M_VPR_P0_SHIFT, R_V7M_VPR_P0_LENGTH);
|
||||
store_cpu_field(vpr, v7m.vpr);
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
tcg_temp_free_i32(tmp);
|
||||
break;
|
||||
}
|
||||
|
@ -64,6 +64,16 @@ static TCGv_ptr mve_qreg_ptr(unsigned reg)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool mve_no_predication(DisasContext *s)
|
||||
{
|
||||
/*
|
||||
* Return true if we are executing the entire MVE instruction
|
||||
* with no predication or partial-execution, and so we can safely
|
||||
* use an inline TCG vector implementation.
|
||||
*/
|
||||
return s->eci == 0 && s->mve_no_pred;
|
||||
}
|
||||
|
||||
static bool mve_check_qreg_bank(DisasContext *s, int qmask)
|
||||
{
|
||||
/*
|
||||
@ -490,17 +500,22 @@ static bool trans_VDUP(DisasContext *s, arg_VDUP *a)
|
||||
return true;
|
||||
}
|
||||
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
rt = load_reg(s, a->rt);
|
||||
tcg_gen_dup_i32(a->size, rt, rt);
|
||||
gen_helper_mve_vdup(cpu_env, qd, rt);
|
||||
tcg_temp_free_ptr(qd);
|
||||
if (mve_no_predication(s)) {
|
||||
tcg_gen_gvec_dup_i32(a->size, mve_qreg_offset(a->qd), 16, 16, rt);
|
||||
} else {
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
tcg_gen_dup_i32(a->size, rt, rt);
|
||||
gen_helper_mve_vdup(cpu_env, qd, rt);
|
||||
tcg_temp_free_ptr(qd);
|
||||
}
|
||||
tcg_temp_free_i32(rt);
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool do_1op(DisasContext *s, arg_1op *a, MVEGenOneOpFn fn)
|
||||
static bool do_1op_vec(DisasContext *s, arg_1op *a, MVEGenOneOpFn fn,
|
||||
GVecGen2Fn vecfn)
|
||||
{
|
||||
TCGv_ptr qd, qm;
|
||||
|
||||
@ -514,16 +529,25 @@ static bool do_1op(DisasContext *s, arg_1op *a, MVEGenOneOpFn fn)
|
||||
return true;
|
||||
}
|
||||
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qm);
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qm);
|
||||
if (vecfn && mve_no_predication(s)) {
|
||||
vecfn(a->size, mve_qreg_offset(a->qd), mve_qreg_offset(a->qm), 16, 16);
|
||||
} else {
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qm);
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qm);
|
||||
}
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
#define DO_1OP(INSN, FN) \
|
||||
static bool do_1op(DisasContext *s, arg_1op *a, MVEGenOneOpFn fn)
|
||||
{
|
||||
return do_1op_vec(s, a, fn, NULL);
|
||||
}
|
||||
|
||||
#define DO_1OP_VEC(INSN, FN, VECFN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_1op *a) \
|
||||
{ \
|
||||
static MVEGenOneOpFn * const fns[] = { \
|
||||
@ -532,13 +556,15 @@ static bool do_1op(DisasContext *s, arg_1op *a, MVEGenOneOpFn fn)
|
||||
gen_helper_mve_##FN##w, \
|
||||
NULL, \
|
||||
}; \
|
||||
return do_1op(s, a, fns[a->size]); \
|
||||
return do_1op_vec(s, a, fns[a->size], VECFN); \
|
||||
}
|
||||
|
||||
#define DO_1OP(INSN, FN) DO_1OP_VEC(INSN, FN, NULL)
|
||||
|
||||
DO_1OP(VCLZ, vclz)
|
||||
DO_1OP(VCLS, vcls)
|
||||
DO_1OP(VABS, vabs)
|
||||
DO_1OP(VNEG, vneg)
|
||||
DO_1OP_VEC(VABS, vabs, tcg_gen_gvec_abs)
|
||||
DO_1OP_VEC(VNEG, vneg, tcg_gen_gvec_neg)
|
||||
DO_1OP(VQABS, vqabs)
|
||||
DO_1OP(VQNEG, vqneg)
|
||||
DO_1OP(VMAXA, vmaxa)
|
||||
@ -743,7 +769,7 @@ static bool trans_VREV64(DisasContext *s, arg_1op *a)
|
||||
|
||||
static bool trans_VMVN(DisasContext *s, arg_1op *a)
|
||||
{
|
||||
return do_1op(s, a, gen_helper_mve_vmvn);
|
||||
return do_1op_vec(s, a, gen_helper_mve_vmvn, tcg_gen_gvec_not);
|
||||
}
|
||||
|
||||
static bool trans_VABS_fp(DisasContext *s, arg_1op *a)
|
||||
@ -774,7 +800,8 @@ static bool trans_VNEG_fp(DisasContext *s, arg_1op *a)
|
||||
return do_1op(s, a, fns[a->size]);
|
||||
}
|
||||
|
||||
static bool do_2op(DisasContext *s, arg_2op *a, MVEGenTwoOpFn fn)
|
||||
static bool do_2op_vec(DisasContext *s, arg_2op *a, MVEGenTwoOpFn fn,
|
||||
GVecGen3Fn *vecfn)
|
||||
{
|
||||
TCGv_ptr qd, qn, qm;
|
||||
|
||||
@ -787,32 +814,47 @@ static bool do_2op(DisasContext *s, arg_2op *a, MVEGenTwoOpFn fn)
|
||||
return true;
|
||||
}
|
||||
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qn = mve_qreg_ptr(a->qn);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qn, qm);
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qn);
|
||||
tcg_temp_free_ptr(qm);
|
||||
if (vecfn && mve_no_predication(s)) {
|
||||
vecfn(a->size, mve_qreg_offset(a->qd), mve_qreg_offset(a->qn),
|
||||
mve_qreg_offset(a->qm), 16, 16);
|
||||
} else {
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qn = mve_qreg_ptr(a->qn);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qn, qm);
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qn);
|
||||
tcg_temp_free_ptr(qm);
|
||||
}
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
#define DO_LOGIC(INSN, HELPER) \
|
||||
static bool do_2op(DisasContext *s, arg_2op *a, MVEGenTwoOpFn *fn)
|
||||
{
|
||||
return do_2op_vec(s, a, fn, NULL);
|
||||
}
|
||||
|
||||
#define DO_LOGIC(INSN, HELPER, VECFN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2op *a) \
|
||||
{ \
|
||||
return do_2op(s, a, HELPER); \
|
||||
return do_2op_vec(s, a, HELPER, VECFN); \
|
||||
}
|
||||
|
||||
DO_LOGIC(VAND, gen_helper_mve_vand)
|
||||
DO_LOGIC(VBIC, gen_helper_mve_vbic)
|
||||
DO_LOGIC(VORR, gen_helper_mve_vorr)
|
||||
DO_LOGIC(VORN, gen_helper_mve_vorn)
|
||||
DO_LOGIC(VEOR, gen_helper_mve_veor)
|
||||
DO_LOGIC(VAND, gen_helper_mve_vand, tcg_gen_gvec_and)
|
||||
DO_LOGIC(VBIC, gen_helper_mve_vbic, tcg_gen_gvec_andc)
|
||||
DO_LOGIC(VORR, gen_helper_mve_vorr, tcg_gen_gvec_or)
|
||||
DO_LOGIC(VORN, gen_helper_mve_vorn, tcg_gen_gvec_orc)
|
||||
DO_LOGIC(VEOR, gen_helper_mve_veor, tcg_gen_gvec_xor)
|
||||
|
||||
DO_LOGIC(VPSEL, gen_helper_mve_vpsel)
|
||||
static bool trans_VPSEL(DisasContext *s, arg_2op *a)
|
||||
{
|
||||
/* This insn updates predication bits */
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
return do_2op(s, a, gen_helper_mve_vpsel);
|
||||
}
|
||||
|
||||
#define DO_2OP(INSN, FN) \
|
||||
#define DO_2OP_VEC(INSN, FN, VECFN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2op *a) \
|
||||
{ \
|
||||
static MVEGenTwoOpFn * const fns[] = { \
|
||||
@ -821,20 +863,22 @@ DO_LOGIC(VPSEL, gen_helper_mve_vpsel)
|
||||
gen_helper_mve_##FN##w, \
|
||||
NULL, \
|
||||
}; \
|
||||
return do_2op(s, a, fns[a->size]); \
|
||||
return do_2op_vec(s, a, fns[a->size], VECFN); \
|
||||
}
|
||||
|
||||
DO_2OP(VADD, vadd)
|
||||
DO_2OP(VSUB, vsub)
|
||||
DO_2OP(VMUL, vmul)
|
||||
#define DO_2OP(INSN, FN) DO_2OP_VEC(INSN, FN, NULL)
|
||||
|
||||
DO_2OP_VEC(VADD, vadd, tcg_gen_gvec_add)
|
||||
DO_2OP_VEC(VSUB, vsub, tcg_gen_gvec_sub)
|
||||
DO_2OP_VEC(VMUL, vmul, tcg_gen_gvec_mul)
|
||||
DO_2OP(VMULH_S, vmulhs)
|
||||
DO_2OP(VMULH_U, vmulhu)
|
||||
DO_2OP(VRMULH_S, vrmulhs)
|
||||
DO_2OP(VRMULH_U, vrmulhu)
|
||||
DO_2OP(VMAX_S, vmaxs)
|
||||
DO_2OP(VMAX_U, vmaxu)
|
||||
DO_2OP(VMIN_S, vmins)
|
||||
DO_2OP(VMIN_U, vminu)
|
||||
DO_2OP_VEC(VMAX_S, vmaxs, tcg_gen_gvec_smax)
|
||||
DO_2OP_VEC(VMAX_U, vmaxu, tcg_gen_gvec_umax)
|
||||
DO_2OP_VEC(VMIN_S, vmins, tcg_gen_gvec_smin)
|
||||
DO_2OP_VEC(VMIN_U, vminu, tcg_gen_gvec_umin)
|
||||
DO_2OP(VABD_S, vabds)
|
||||
DO_2OP(VABD_U, vabdu)
|
||||
DO_2OP(VHADD_S, vhadds)
|
||||
@ -1366,6 +1410,8 @@ static bool trans_VPNOT(DisasContext *s, arg_VPNOT *a)
|
||||
}
|
||||
|
||||
gen_helper_mve_vpnot(cpu_env);
|
||||
/* This insn updates predication bits */
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
@ -1475,7 +1521,8 @@ static bool trans_VADDLV(DisasContext *s, arg_VADDLV *a)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool do_1imm(DisasContext *s, arg_1imm *a, MVEGenOneOpImmFn *fn)
|
||||
static bool do_1imm(DisasContext *s, arg_1imm *a, MVEGenOneOpImmFn *fn,
|
||||
GVecGen2iFn *vecfn)
|
||||
{
|
||||
TCGv_ptr qd;
|
||||
uint64_t imm;
|
||||
@ -1491,17 +1538,29 @@ static bool do_1imm(DisasContext *s, arg_1imm *a, MVEGenOneOpImmFn *fn)
|
||||
|
||||
imm = asimd_imm_const(a->imm, a->cmode, a->op);
|
||||
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
fn(cpu_env, qd, tcg_constant_i64(imm));
|
||||
tcg_temp_free_ptr(qd);
|
||||
if (vecfn && mve_no_predication(s)) {
|
||||
vecfn(MO_64, mve_qreg_offset(a->qd), mve_qreg_offset(a->qd),
|
||||
imm, 16, 16);
|
||||
} else {
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
fn(cpu_env, qd, tcg_constant_i64(imm));
|
||||
tcg_temp_free_ptr(qd);
|
||||
}
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gen_gvec_vmovi(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t c, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
tcg_gen_gvec_dup_imm(vece, dofs, oprsz, maxsz, c);
|
||||
}
|
||||
|
||||
static bool trans_Vimm_1r(DisasContext *s, arg_1imm *a)
|
||||
{
|
||||
/* Handle decode of cmode/op here between VORR/VBIC/VMOV */
|
||||
MVEGenOneOpImmFn *fn;
|
||||
GVecGen2iFn *vecfn;
|
||||
|
||||
if ((a->cmode & 1) && a->cmode < 12) {
|
||||
if (a->op) {
|
||||
@ -1510,8 +1569,10 @@ static bool trans_Vimm_1r(DisasContext *s, arg_1imm *a)
|
||||
* so the VBIC becomes a logical AND operation.
|
||||
*/
|
||||
fn = gen_helper_mve_vandi;
|
||||
vecfn = tcg_gen_gvec_andi;
|
||||
} else {
|
||||
fn = gen_helper_mve_vorri;
|
||||
vecfn = tcg_gen_gvec_ori;
|
||||
}
|
||||
} else {
|
||||
/* There is one unallocated cmode/op combination in this space */
|
||||
@ -1520,12 +1581,13 @@ static bool trans_Vimm_1r(DisasContext *s, arg_1imm *a)
|
||||
}
|
||||
/* asimd_imm_const() sorts out VMVNI vs VMOVI for us */
|
||||
fn = gen_helper_mve_vmovi;
|
||||
vecfn = gen_gvec_vmovi;
|
||||
}
|
||||
return do_1imm(s, a, fn);
|
||||
return do_1imm(s, a, fn, vecfn);
|
||||
}
|
||||
|
||||
static bool do_2shift(DisasContext *s, arg_2shift *a, MVEGenTwoOpShiftFn fn,
|
||||
bool negateshift)
|
||||
static bool do_2shift_vec(DisasContext *s, arg_2shift *a, MVEGenTwoOpShiftFn fn,
|
||||
bool negateshift, GVecGen2iFn vecfn)
|
||||
{
|
||||
TCGv_ptr qd, qm;
|
||||
int shift = a->shift;
|
||||
@ -1548,39 +1610,82 @@ static bool do_2shift(DisasContext *s, arg_2shift *a, MVEGenTwoOpShiftFn fn,
|
||||
shift = -shift;
|
||||
}
|
||||
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qm, tcg_constant_i32(shift));
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qm);
|
||||
if (vecfn && mve_no_predication(s)) {
|
||||
vecfn(a->size, mve_qreg_offset(a->qd), mve_qreg_offset(a->qm),
|
||||
shift, 16, 16);
|
||||
} else {
|
||||
qd = mve_qreg_ptr(a->qd);
|
||||
qm = mve_qreg_ptr(a->qm);
|
||||
fn(cpu_env, qd, qm, tcg_constant_i32(shift));
|
||||
tcg_temp_free_ptr(qd);
|
||||
tcg_temp_free_ptr(qm);
|
||||
}
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
#define DO_2SHIFT(INSN, FN, NEGATESHIFT) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2shift *a) \
|
||||
{ \
|
||||
static MVEGenTwoOpShiftFn * const fns[] = { \
|
||||
gen_helper_mve_##FN##b, \
|
||||
gen_helper_mve_##FN##h, \
|
||||
gen_helper_mve_##FN##w, \
|
||||
NULL, \
|
||||
}; \
|
||||
return do_2shift(s, a, fns[a->size], NEGATESHIFT); \
|
||||
static bool do_2shift(DisasContext *s, arg_2shift *a, MVEGenTwoOpShiftFn fn,
|
||||
bool negateshift)
|
||||
{
|
||||
return do_2shift_vec(s, a, fn, negateshift, NULL);
|
||||
}
|
||||
|
||||
#define DO_2SHIFT_VEC(INSN, FN, NEGATESHIFT, VECFN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2shift *a) \
|
||||
{ \
|
||||
static MVEGenTwoOpShiftFn * const fns[] = { \
|
||||
gen_helper_mve_##FN##b, \
|
||||
gen_helper_mve_##FN##h, \
|
||||
gen_helper_mve_##FN##w, \
|
||||
NULL, \
|
||||
}; \
|
||||
return do_2shift_vec(s, a, fns[a->size], NEGATESHIFT, VECFN); \
|
||||
}
|
||||
|
||||
DO_2SHIFT(VSHLI, vshli_u, false)
|
||||
#define DO_2SHIFT(INSN, FN, NEGATESHIFT) \
|
||||
DO_2SHIFT_VEC(INSN, FN, NEGATESHIFT, NULL)
|
||||
|
||||
static void do_gvec_shri_s(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
/*
|
||||
* We get here with a negated shift count, and we must handle
|
||||
* shifts by the element size, which tcg_gen_gvec_sari() does not do.
|
||||
*/
|
||||
shift = -shift;
|
||||
if (shift == (8 << vece)) {
|
||||
shift--;
|
||||
}
|
||||
tcg_gen_gvec_sari(vece, dofs, aofs, shift, oprsz, maxsz);
|
||||
}
|
||||
|
||||
static void do_gvec_shri_u(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
/*
|
||||
* We get here with a negated shift count, and we must handle
|
||||
* shifts by the element size, which tcg_gen_gvec_shri() does not do.
|
||||
*/
|
||||
shift = -shift;
|
||||
if (shift == (8 << vece)) {
|
||||
tcg_gen_gvec_dup_imm(vece, dofs, oprsz, maxsz, 0);
|
||||
} else {
|
||||
tcg_gen_gvec_shri(vece, dofs, aofs, shift, oprsz, maxsz);
|
||||
}
|
||||
}
|
||||
|
||||
DO_2SHIFT_VEC(VSHLI, vshli_u, false, tcg_gen_gvec_shli)
|
||||
DO_2SHIFT(VQSHLI_S, vqshli_s, false)
|
||||
DO_2SHIFT(VQSHLI_U, vqshli_u, false)
|
||||
DO_2SHIFT(VQSHLUI, vqshlui_s, false)
|
||||
/* These right shifts use a left-shift helper with negated shift count */
|
||||
DO_2SHIFT(VSHRI_S, vshli_s, true)
|
||||
DO_2SHIFT(VSHRI_U, vshli_u, true)
|
||||
DO_2SHIFT_VEC(VSHRI_S, vshli_s, true, do_gvec_shri_s)
|
||||
DO_2SHIFT_VEC(VSHRI_U, vshli_u, true, do_gvec_shri_u)
|
||||
DO_2SHIFT(VRSHRI_S, vrshli_s, true)
|
||||
DO_2SHIFT(VRSHRI_U, vrshli_u, true)
|
||||
|
||||
DO_2SHIFT(VSRI, vsri, false)
|
||||
DO_2SHIFT(VSLI, vsli, false)
|
||||
DO_2SHIFT_VEC(VSRI, vsri, false, gen_gvec_sri)
|
||||
DO_2SHIFT_VEC(VSLI, vsli, false, gen_gvec_sli)
|
||||
|
||||
#define DO_2SHIFT_FP(INSN, FN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2shift *a) \
|
||||
@ -1646,16 +1751,67 @@ DO_2SHIFT_SCALAR(VQSHL_U_scalar, vqshli_u)
|
||||
DO_2SHIFT_SCALAR(VQRSHL_S_scalar, vqrshli_s)
|
||||
DO_2SHIFT_SCALAR(VQRSHL_U_scalar, vqrshli_u)
|
||||
|
||||
#define DO_VSHLL(INSN, FN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2shift *a) \
|
||||
{ \
|
||||
static MVEGenTwoOpShiftFn * const fns[] = { \
|
||||
gen_helper_mve_##FN##b, \
|
||||
gen_helper_mve_##FN##h, \
|
||||
}; \
|
||||
return do_2shift(s, a, fns[a->size], false); \
|
||||
#define DO_VSHLL(INSN, FN) \
|
||||
static bool trans_##INSN(DisasContext *s, arg_2shift *a) \
|
||||
{ \
|
||||
static MVEGenTwoOpShiftFn * const fns[] = { \
|
||||
gen_helper_mve_##FN##b, \
|
||||
gen_helper_mve_##FN##h, \
|
||||
}; \
|
||||
return do_2shift_vec(s, a, fns[a->size], false, do_gvec_##FN); \
|
||||
}
|
||||
|
||||
/*
|
||||
* For the VSHLL vector helpers, the vece is the size of the input
|
||||
* (ie MO_8 or MO_16); the helpers want to work in the output size.
|
||||
* The shift count can be 0..<input size>, inclusive. (0 is VMOVL.)
|
||||
*/
|
||||
static void do_gvec_vshllbs(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
unsigned ovece = vece + 1;
|
||||
unsigned ibits = vece == MO_8 ? 8 : 16;
|
||||
tcg_gen_gvec_shli(ovece, dofs, aofs, ibits, oprsz, maxsz);
|
||||
tcg_gen_gvec_sari(ovece, dofs, dofs, ibits - shift, oprsz, maxsz);
|
||||
}
|
||||
|
||||
static void do_gvec_vshllbu(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
unsigned ovece = vece + 1;
|
||||
tcg_gen_gvec_andi(ovece, dofs, aofs,
|
||||
ovece == MO_16 ? 0xff : 0xffff, oprsz, maxsz);
|
||||
tcg_gen_gvec_shli(ovece, dofs, dofs, shift, oprsz, maxsz);
|
||||
}
|
||||
|
||||
static void do_gvec_vshllts(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
unsigned ovece = vece + 1;
|
||||
unsigned ibits = vece == MO_8 ? 8 : 16;
|
||||
if (shift == 0) {
|
||||
tcg_gen_gvec_sari(ovece, dofs, aofs, ibits, oprsz, maxsz);
|
||||
} else {
|
||||
tcg_gen_gvec_andi(ovece, dofs, aofs,
|
||||
ovece == MO_16 ? 0xff00 : 0xffff0000, oprsz, maxsz);
|
||||
tcg_gen_gvec_sari(ovece, dofs, dofs, ibits - shift, oprsz, maxsz);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_gvec_vshlltu(unsigned vece, uint32_t dofs, uint32_t aofs,
|
||||
int64_t shift, uint32_t oprsz, uint32_t maxsz)
|
||||
{
|
||||
unsigned ovece = vece + 1;
|
||||
unsigned ibits = vece == MO_8 ? 8 : 16;
|
||||
if (shift == 0) {
|
||||
tcg_gen_gvec_shri(ovece, dofs, aofs, ibits, oprsz, maxsz);
|
||||
} else {
|
||||
tcg_gen_gvec_andi(ovece, dofs, aofs,
|
||||
ovece == MO_16 ? 0xff00 : 0xffff0000, oprsz, maxsz);
|
||||
tcg_gen_gvec_shri(ovece, dofs, dofs, ibits - shift, oprsz, maxsz);
|
||||
}
|
||||
}
|
||||
|
||||
DO_VSHLL(VSHLL_BS, vshllbs)
|
||||
DO_VSHLL(VSHLL_BU, vshllbu)
|
||||
DO_VSHLL(VSHLL_TS, vshllts)
|
||||
@ -1852,6 +2008,8 @@ static bool do_vcmp(DisasContext *s, arg_vcmp *a, MVEGenCmpFn *fn)
|
||||
/* VPT */
|
||||
gen_vpst(s, a->mask);
|
||||
}
|
||||
/* This insn updates predication bits */
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
@ -1883,6 +2041,8 @@ static bool do_vcmp_scalar(DisasContext *s, arg_vcmp_scalar *a,
|
||||
/* VPT */
|
||||
gen_vpst(s, a->mask);
|
||||
}
|
||||
/* This insn updates predication bits */
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ static inline long vfp_f16_offset(unsigned reg, bool top)
|
||||
* Generate code for M-profile lazy FP state preservation if needed;
|
||||
* this corresponds to the pseudocode PreserveFPState() function.
|
||||
*/
|
||||
static void gen_preserve_fp_state(DisasContext *s)
|
||||
static void gen_preserve_fp_state(DisasContext *s, bool skip_context_update)
|
||||
{
|
||||
if (s->v7m_lspact) {
|
||||
/*
|
||||
@ -128,6 +128,20 @@ static void gen_preserve_fp_state(DisasContext *s)
|
||||
* any further FP insns in this TB.
|
||||
*/
|
||||
s->v7m_lspact = false;
|
||||
/*
|
||||
* The helper might have zeroed VPR, so we do not know the
|
||||
* correct value for the MVE_NO_PRED TB flag any more.
|
||||
* If we're about to create a new fp context then that
|
||||
* will precisely determine the MVE_NO_PRED value (see
|
||||
* gen_update_fp_context()). Otherwise, we must:
|
||||
* - set s->mve_no_pred to false, so this instruction
|
||||
* is generated to use helper functions
|
||||
* - end the TB now, without chaining to the next TB
|
||||
*/
|
||||
if (skip_context_update || !s->v7m_new_fp_ctxt_needed) {
|
||||
s->mve_no_pred = false;
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,12 +183,19 @@ static void gen_update_fp_context(DisasContext *s)
|
||||
TCGv_i32 z32 = tcg_const_i32(0);
|
||||
store_cpu_field(z32, v7m.vpr);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need to arrange to end the TB, because the only
|
||||
* parts of FPSCR which we cache in the TB flags are the VECLEN
|
||||
* and VECSTRIDE, and those don't exist for M-profile.
|
||||
* We just updated the FPSCR and VPR. Some of this state is cached
|
||||
* in the MVE_NO_PRED TB flag. We want to avoid having to end the
|
||||
* TB here, which means we need the new value of the MVE_NO_PRED
|
||||
* flag to be exactly known here and the same for all executions.
|
||||
* Luckily FPDSCR.LTPSIZE is always constant 4 and the VPR is
|
||||
* always set to 0, so the new MVE_NO_PRED flag is always 1
|
||||
* if and only if we have MVE.
|
||||
*
|
||||
* (The other FPSCR state cached in TB flags is VECLEN and VECSTRIDE,
|
||||
* but those do not exist for M-profile, so are not relevant here.)
|
||||
*/
|
||||
s->mve_no_pred = dc_isar_feature(aa32_mve, s);
|
||||
|
||||
if (s->v8m_secure) {
|
||||
bits |= R_V7M_CONTROL_SFPA_MASK;
|
||||
@ -238,7 +259,7 @@ bool vfp_access_check_m(DisasContext *s, bool skip_context_update)
|
||||
/* Handle M-profile lazy FP state mechanics */
|
||||
|
||||
/* Trigger lazy-state preservation if necessary */
|
||||
gen_preserve_fp_state(s);
|
||||
gen_preserve_fp_state(s, skip_context_update);
|
||||
|
||||
if (!skip_context_update) {
|
||||
/* Update ownership of FP context and create new FP context if needed */
|
||||
|
@ -2610,8 +2610,40 @@ static inline void gen_jmp_tb(DisasContext *s, uint32_t dest, int tbno)
|
||||
/* An indirect jump so that we still trigger the debug exception. */
|
||||
gen_set_pc_im(s, dest);
|
||||
s->base.is_jmp = DISAS_JUMP;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
switch (s->base.is_jmp) {
|
||||
case DISAS_NEXT:
|
||||
case DISAS_TOO_MANY:
|
||||
case DISAS_NORETURN:
|
||||
/*
|
||||
* The normal case: just go to the destination TB.
|
||||
* NB: NORETURN happens if we generate code like
|
||||
* gen_brcondi(l);
|
||||
* gen_jmp();
|
||||
* gen_set_label(l);
|
||||
* gen_jmp();
|
||||
* on the second call to gen_jmp().
|
||||
*/
|
||||
gen_goto_tb(s, tbno, dest);
|
||||
break;
|
||||
case DISAS_UPDATE_NOCHAIN:
|
||||
case DISAS_UPDATE_EXIT:
|
||||
/*
|
||||
* We already decided we're leaving the TB for some other reason.
|
||||
* Avoid using goto_tb so we really do exit back to the main loop
|
||||
* and don't chain to another TB.
|
||||
*/
|
||||
gen_set_pc_im(s, dest);
|
||||
gen_goto_ptr();
|
||||
s->base.is_jmp = DISAS_NORETURN;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* We shouldn't be emitting code for a jump and also have
|
||||
* is_jmp set to one of the special cases like DISAS_SWI.
|
||||
*/
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
@ -8464,6 +8496,7 @@ static bool trans_DLS(DisasContext *s, arg_DLS *a)
|
||||
/* DLSTP: set FPSCR.LTPSIZE */
|
||||
tmp = tcg_const_i32(a->size);
|
||||
store_cpu_field(tmp, v7m.ltpsize);
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -8529,6 +8562,10 @@ static bool trans_WLS(DisasContext *s, arg_WLS *a)
|
||||
assert(ok);
|
||||
tmp = tcg_const_i32(a->size);
|
||||
store_cpu_field(tmp, v7m.ltpsize);
|
||||
/*
|
||||
* LTPSIZE updated, but MVE_NO_PRED will always be the same thing (0)
|
||||
* when we take this upcoming exit from this TB, so gen_jmp_tb() is OK.
|
||||
*/
|
||||
}
|
||||
gen_jmp_tb(s, s->base.pc_next, 1);
|
||||
|
||||
@ -8711,6 +8748,8 @@ static bool trans_VCTP(DisasContext *s, arg_VCTP *a)
|
||||
gen_helper_mve_vctp(cpu_env, masklen);
|
||||
tcg_temp_free_i32(masklen);
|
||||
tcg_temp_free_i32(rn_shifted);
|
||||
/* This insn updates predication bits */
|
||||
s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
|
||||
mve_update_eci(s);
|
||||
return true;
|
||||
}
|
||||
@ -9381,6 +9420,7 @@ static void arm_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
|
||||
dc->v7m_new_fp_ctxt_needed =
|
||||
EX_TBFLAG_M32(tb_flags, NEW_FP_CTXT_NEEDED);
|
||||
dc->v7m_lspact = EX_TBFLAG_M32(tb_flags, LSPACT);
|
||||
dc->mve_no_pred = EX_TBFLAG_M32(tb_flags, MVE_NO_PRED);
|
||||
} else {
|
||||
dc->debug_target_el = EX_TBFLAG_ANY(tb_flags, DEBUG_TARGET_EL);
|
||||
dc->sctlr_b = EX_TBFLAG_A32(tb_flags, SCTLR__B);
|
||||
|
@ -100,6 +100,8 @@ typedef struct DisasContext {
|
||||
bool align_mem;
|
||||
/* True if PSTATE.IL is set */
|
||||
bool pstate_il;
|
||||
/* True if MVE insns are definitely not predicated by VPR or LTPSIZE */
|
||||
bool mve_no_pred;
|
||||
/*
|
||||
* >= 0, a copy of PSTATE.BTYPE, which will be 0 without v8.5-BTI.
|
||||
* < 0, set by the current instruction.
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "sysemu/hvf.h"
|
||||
#include "sysemu/hvf_int.h"
|
||||
#include "sysemu/runstate.h"
|
||||
#include "sysemu/cpus.h"
|
||||
#include "hvf-i386.h"
|
||||
#include "vmcs.h"
|
||||
#include "vmx.h"
|
||||
@ -206,6 +207,16 @@ static inline bool apic_bus_freq_is_known(CPUX86State *env)
|
||||
return env->apic_bus_freq != 0;
|
||||
}
|
||||
|
||||
void hvf_kick_vcpu_thread(CPUState *cpu)
|
||||
{
|
||||
cpus_kick_thread(cpu);
|
||||
}
|
||||
|
||||
int hvf_arch_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hvf_arch_init_vcpu(CPUState *cpu)
|
||||
{
|
||||
X86CPU *x86cpu = X86_CPU(cpu);
|
||||
|
Loading…
Reference in New Issue
Block a user