Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
i386: hvf: Implement CPU kick
HVF doesn't have a CPU kick and without it it's not possible to perform
an action on CPU thread until a VMEXIT happens. The kick is also needed
for timely interrupt delivery.

Existing implementation of CPU kick sends SIG_IPI (aka SIGUSR1) to vCPU
thread, but it's different from what hv_vcpu_interrupt does. The latter
one results in invocation of mp_cpus_kick() in XNU kernel [1].

mp_cpus_kick() sends an IPI through the host LAPIC to the HVF vCPU.
And the kick interrupt leads to VM exit because "external-interrupt
exiting” VM-execution control is enabled for HVF.

hv_vcpu_interrupt() has no effect if it's delivered when vCPU is outside
of a guest, therefore to avoid kick loss it's complemented with a
SIG_IPI handler and zero VMX-preemption timer. If the kick happens
outside of hv_vcpu_run(), the signal handler will re-queue the kick by
setting exit_request. exit_request is cleared when the request is
satisfied, i.e. when vCPU thread returns with EXCP_INTERRUPT.

So we get the following scenarios time/location-wise for the kick:

1) vCPU thread is far away before hv_vcpu_run(), then exit_request is
   scheduled. As soon as vCPU thread approaches hv_vcpu_run(), the
   exit request is satisfied.

2) vCPU thread is about to enter the guest, then VMX-preemption timer is
   enabled to expedite immediate VM-exit. The VMX-preemption timer is
   then cleared in VM-exit handler, exit from vCPU thread is performed.

3) The guest is running, then hv_vcpu_run() is interrupted by
   hv_vcpu_interrupt() and vCPU thread quits.

4) vCPU thread has just made VM-exit, then exit_request is recorded and
   VMX-preemption timer is enabled but the exit request won't be
   satisfied until the next iteration of vCPU thread, no kick loss
   happens.

5) vCPU thread is far after hv_vcpu_run(), then exit_request is recorded
   and VMX-preemption timer is not enabled. The exit request will be
   satisfied on the next iteration of vCPU thread, like in 4). The kick
   is not lost.

6) If some external interrupt happens we can satisfy exit request and can
   clear VMX-preemption timer, i.e. kicks are coalesced with interrupts.

1. https://opensource.apple.com/source/xnu/xnu-6153.81.5/osfmk/i386/mp.c

Cc: Cameron Esfahani <dirty@apple.com>
Signed-off-by: Roman Bolshakov <r.bolshakov@yadro.com>
  • Loading branch information
Roman Bolshakov authored and pmj committed Oct 16, 2023
1 parent 35ead6f commit fadc716
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 6 deletions.
6 changes: 1 addition & 5 deletions accel/hvf/hvf-accel-ops.c
Expand Up @@ -312,10 +312,6 @@ static MemoryListener hvf_memory_listener = {
.log_sync = hvf_log_sync,
};

static void dummy_signal(int sig)
{
}

bool hvf_allowed;

static int hvf_accel_init(MachineState *ms)
Expand Down Expand Up @@ -390,7 +386,7 @@ static int hvf_init_vcpu(CPUState *cpu)
struct sigaction sigact;

memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = dummy_signal;
sigact.sa_handler = hvf_arch_handle_ipi;
sigaction(SIG_IPI, &sigact, NULL);

pthread_sigmask(SIG_BLOCK, NULL, &cpu->accel->unblock_ipi_mask);
Expand Down
1 change: 1 addition & 0 deletions include/sysemu/hvf_int.h
Expand Up @@ -66,5 +66,6 @@ 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);
void hvf_arch_handle_ipi(int sig);

#endif
4 changes: 4 additions & 0 deletions target/arm/hvf/hvf.c
Expand Up @@ -1723,6 +1723,10 @@ static void hvf_wait_for_ipi(CPUState *cpu, struct timespec *ts)
qemu_mutex_lock_iothread();
}

void hvf_arch_handle_ipi(int sig)
{
}

static void hvf_wfi(CPUState *cpu)
{
ARMCPU *arm_cpu = ARM_CPU(cpu);
Expand Down
1 change: 1 addition & 0 deletions target/i386/cpu.h
Expand Up @@ -1862,6 +1862,7 @@ typedef struct CPUArchState {
QemuMutex xen_timers_lock;
#endif
#if defined(CONFIG_HVF)
bool hvf_in_guest;
HVFX86LazyFlags hvf_lflags;
void *hvf_mmio_buf;
#endif
Expand Down
65 changes: 64 additions & 1 deletion target/i386/hvf/hvf.c
Expand Up @@ -181,6 +181,29 @@ static void init_tsc_freq(CPUX86State *env)
env->tsc_khz = tsc_freq / 1000; /* Hz to KHz */
}

void hvf_arch_handle_ipi(int sig)
{
if (!current_cpu) {
return;
}

/*
* skip object type check to avoid a deadlock in
* qemu_log/vfprintf/flockfile.
*/
X86CPU *x86_cpu = (X86CPU *)current_cpu;
CPUX86State *env = &x86_cpu->env;

qatomic_set(&current_cpu->exit_request, true);
/* Write cpu->exit_request before reading env->hvf_in_guest */
smp_mb();
if (qatomic_read(&env->hvf_in_guest)) {
wvmcs(current_cpu->accel->fd, VMCS_PIN_BASED_CTLS,
rvmcs(current_cpu->accel->fd, VMCS_PIN_BASED_CTLS)
| VMCS_PIN_BASED_CTLS_VMX_PREEMPT_TIMER);
}
}

static void init_apic_bus_freq(CPUX86State *env)
{
size_t length;
Expand Down Expand Up @@ -209,7 +232,27 @@ static inline bool apic_bus_freq_is_known(CPUX86State *env)

void hvf_kick_vcpu_thread(CPUState *cpu)
{
cpus_kick_thread(cpu);
hv_return_t hv_err;
int err;
hv_vcpuid_t vcpuid;

if (qatomic_read(&cpu->thread_kicked)) {
return;
}
qatomic_set_mb(&cpu->thread_kicked, true);

err = pthread_kill(cpu->thread->thread, SIG_IPI);
if (err) {
fprintf(stderr, "qemu:%s: %s\n", __func__, strerror(err));
exit(1);
}
vcpuid = cpu->accel->fd;
hv_err = hv_vcpu_interrupt(&vcpuid, 1);
if (hv_err) {
fprintf(stderr, "qemu:%s error %#x\n", __func__, err);
exit(1);
}

}

int hvf_arch_init(void)
Expand All @@ -227,6 +270,7 @@ int hvf_arch_init_vcpu(CPUState *cpu)
init_decoder();

hvf_state->hvf_caps = g_new0(struct hvf_vcpu_caps, 1);
env->hvf_in_guest = false;
env->hvf_mmio_buf = g_new(char, 4096);

if (x86cpu->vmware_cpuid_freq) {
Expand Down Expand Up @@ -284,6 +328,7 @@ int hvf_arch_init_vcpu(CPUState *cpu)
wvmcs(cpu->accel->fd, VMCS_EXCEPTION_BITMAP, 0); /* Double fault */

wvmcs(cpu->accel->fd, VMCS_TPR_THRESHOLD, 0);
wvmcs(cpu->accel->fd, VMCS_PREEMPTION_TIMER_VALUE, 0);

x86cpu = X86_CPU(cpu);
x86cpu->env.xsave_buf_len = 4096;
Expand Down Expand Up @@ -435,7 +480,20 @@ int hvf_vcpu_exec(CPUState *cpu)
return EXCP_HLT;
}

qatomic_set(&env->hvf_in_guest, true);
/* Read cpu->exit_request after writing env->hvf_in_guest */
smp_mb();
if (qatomic_read(&cpu->exit_request)) {
qatomic_set(&env->hvf_in_guest, false);
qemu_mutex_lock_iothread();
wvmcs(cpu->accel->fd, VMCS_PIN_BASED_CTLS,
rvmcs(cpu->accel->fd, VMCS_PIN_BASED_CTLS)
& ~VMCS_PIN_BASED_CTLS_VMX_PREEMPT_TIMER);
qatomic_set(&cpu->exit_request, false);
return EXCP_INTERRUPT;
}
hv_return_t r = hv_vcpu_run(cpu->accel->fd);
qatomic_store_release(&env->hvf_in_guest, false);
assert_hvf_ok(r);

/* handle VMEXIT */
Expand Down Expand Up @@ -582,7 +640,12 @@ int hvf_vcpu_exec(CPUState *cpu)
vmx_clear_nmi_window_exiting(cpu);
ret = EXCP_INTERRUPT;
break;
case EXIT_REASON_VMX_PREEMPT:
case EXIT_REASON_EXT_INTR:
wvmcs(cpu->accel->fd, VMCS_PIN_BASED_CTLS,
rvmcs(cpu->accel->fd, VMCS_PIN_BASED_CTLS)
& ~VMCS_PIN_BASED_CTLS_VMX_PREEMPT_TIMER);
qatomic_set(&cpu->exit_request, false);
/* force exit and allow io handling */
ret = EXCP_INTERRUPT;
break;
Expand Down
1 change: 1 addition & 0 deletions target/i386/hvf/vmcs.h
Expand Up @@ -349,6 +349,7 @@
#define VMCS_PIN_BASED_CTLS_EXTINT (1 << 0)
#define VMCS_PIN_BASED_CTLS_NMI (1 << 3)
#define VMCS_PIN_BASED_CTLS_VNMI (1 << 5)
#define VMCS_PIN_BASED_CTLS_VMX_PREEMPT_TIMER (1 << 6)

#define VMCS_PRI_PROC_BASED_CTLS_INT_WINDOW_EXITING (1 << 2)
#define VMCS_PRI_PROC_BASED_CTLS_TSC_OFFSET (1 << 3)
Expand Down

0 comments on commit fadc716

Please sign in to comment.