hv: Refactor CPU switch logic, make hv.cpu() not exit shell

Get rid of the hv_rearm() thing (which was always a bit dodgy) and
instead properly make sure that all CPUs rendezvous when needed and
switch the active proxy thread without ever exiting exception context.

The Python side can now switch proxy context (by waiting directly for
a proxy boot) without having to exit out of the hypervisor callback,
so cpu() now works as a normal Python method.

Add a cpus() iterator so you can do things like:

>>> for i in cpus(): bt()

Signed-off-by: Hector Martin <marcan@marcan.st>
This commit is contained in:
Hector Martin 2022-05-30 20:01:04 +09:00
parent 01a1a0f597
commit 8eeb7966aa
6 changed files with 128 additions and 53 deletions

View file

@ -39,6 +39,7 @@ class HV_EVENT(IntEnum):
VTIMER = 2
USER_INTERRUPT = 3
WDT_BARK = 4
CPU_SWITCH = 5
VMProxyHookData = Struct(
"flags" / RegAdapter(MMIOTraceFlags),
@ -149,6 +150,7 @@ class HV(Reloadable):
self.started = False
self.ctx = None
self.hvcall_handlers = {}
self.switching_context = False
def _reloadme(self):
super()._reloadme()
@ -373,6 +375,9 @@ class HV(Reloadable):
self.shell_locals["skip"] = self.skip
self.shell_locals["cont"] = self.cont
if self.ctx:
self.cpu() # Return to the original CPU to avoid confusing things
if ret == 1:
if needs_ret:
print("Cannot skip, return value required.")
@ -804,13 +809,36 @@ class HV(Reloadable):
if ctx.esr.EC == ESR_EC.DABORT_LOWER:
return self.handle_dabort(ctx)
def _load_context(self):
self._info_data = self.iface.readmem(self.exc_info, ExcInfo.sizeof())
self.ctx = ExcInfo.parse(self._info_data)
return self.ctx
def _commit_context(self):
new_info = ExcInfo.build(self.ctx)
if new_info != self._info_data:
self.iface.writemem(self.exc_info, new_info)
self._info_data = new_info
def handle_exception(self, reason, code, info):
self.exc_info = info
self.exc_reason = reason
if reason in (START.EXCEPTION_LOWER, START.EXCEPTION):
code = EXC(code)
elif reason == START.HV:
code = HV_EVENT(code)
self.exc_code = code
self.is_fault = reason == START.EXCEPTION_LOWER and code in (EXC.SYNC, EXC.SERROR)
# Nested context switch is handled by the caller
if self.switching_context:
self.switching_context = False
return
self._in_handler = True
info_data = self.iface.readmem(info, ExcInfo.sizeof())
self.exc_reason = reason
self.exc_code = code
self.ctx = ctx = ExcInfo.parse(info_data)
ctx = self._load_context()
self.exc_orig_cpu = self.ctx.cpu_id
handled = False
user_interrupt = False
@ -848,7 +876,7 @@ class HV(Reloadable):
self._sigint_pending = False
signal.signal(signal.SIGINT, self.default_sigint)
ret = shell.run_shell(self.shell_locals, "Entering hypervisor shell", "Returning from exception")
ret = self.run_shell("Entering hypervisor shell", "Returning from exception")
signal.signal(signal.SIGINT, self._handle_sigint)
if ret is None:
@ -856,11 +884,9 @@ class HV(Reloadable):
self.pt_update()
new_info = ExcInfo.build(self.ctx)
if new_info != info_data:
self.iface.writemem(info, new_info)
self._commit_context()
self.ctx = None
self.exc_orig_cpu = None
self.p.exit(ret)
self._in_handler = False
@ -912,6 +938,8 @@ class HV(Reloadable):
return True
def lower(self, step=False):
self.cpu() # Return to exception CPU
if not self._lower():
return
elif step:
@ -925,9 +953,35 @@ class HV(Reloadable):
self._stepping = True
raise shell.ExitConsole(EXC_RET.HANDLED)
def cpu(self, cpu):
self.p.hv_switch_cpu(cpu)
raise shell.ExitConsole(EXC_RET.HANDLED)
def _switch_context(self, exit=EXC_RET.HANDLED):
# Flush current CPU context out to HV
self._commit_context()
self.exc_info = None
self.ctx = None
self.switching_context = True
# Exit out of the proxy
self.p.exit(exit)
# Wait for next proxy entry
self.iface.wait_and_handle_boot()
if self.switching_context:
raise Exception(f"Failed to switch context")
# Fetch new context
self._load_context()
def cpu(self, cpu=None):
if cpu is None:
cpu = self.exc_orig_cpu
if cpu == self.ctx.cpu_id:
return
if not self.p.hv_switch_cpu(cpu):
raise ValueError(f"Invalid or inactive CPU #{cpu}")
self._switch_context()
if self.ctx.cpu_id != cpu:
raise Exception(f"Switching to CPU #{cpu} but ended on #{self.ctx.cpu_id}")
def add_hw_bp(self, vaddr):
for i, i_vaddr in enumerate(self._bps):
@ -989,6 +1043,11 @@ class HV(Reloadable):
lr = self.unpac(self.p.read64(lrp))
frame = self.p.read64(fpp)
def cpus(self):
for i in sorted(self.started_cpus):
self.cpu(i)
yield i
def patch_exception_handling(self):
if self.ctx.cpu_id != 0:
return
@ -1082,6 +1141,7 @@ class HV(Reloadable):
self.iface.set_handler(START.HV, HV_EVENT.HOOK_VM, self.handle_exception)
self.iface.set_handler(START.HV, HV_EVENT.VTIMER, self.handle_exception)
self.iface.set_handler(START.HV, HV_EVENT.WDT_BARK, self.handle_bark)
self.iface.set_handler(START.HV, HV_EVENT.CPU_SWITCH, self.handle_exception)
self.iface.set_event_handler(EVENT.MMIOTRACE, self.handle_mmiotrace)
self.iface.set_event_handler(EVENT.IRQTRACE, self.handle_irqtrace)
@ -1530,6 +1590,7 @@ class HV(Reloadable):
# Does not return
self.started = True
self.started_cpus.add(0)
self.p.hv_start(self.entry, self.guest_base + self.bootargs_off)
from . import trace

View file

@ -327,6 +327,9 @@ class UartInterface(Reloadable):
raise UartTimeout("Reconnection timed out")
print(" Connected")
def wait_and_handle_boot(self):
self.handle_boot(self.wait_boot())
def nop(self):
features = Feature.get_all()

View file

@ -219,16 +219,16 @@ void hv_rendezvous(void)
;
}
void hv_switch_cpu(int cpu)
bool hv_switch_cpu(int cpu)
{
if (cpu > MAX_CPUS || cpu < 0 || !hv_started_cpus[cpu]) {
printf("HV: CPU #%d is inactive or invalid\n", cpu);
return;
return false;
}
hv_rendezvous();
printf("HV: switching to CPU #%d\n", cpu);
hv_want_cpu = cpu;
hv_rearm();
hv_rendezvous();
return true;
}
void hv_write_hcr(u64 val)
@ -301,33 +301,6 @@ void hv_arm_tick(void)
msr(CNTP_CTL_EL0, CNTx_CTL_ENABLE);
}
void hv_rearm(void)
{
msr(CNTP_TVAL_EL0, 0);
msr(CNTP_CTL_EL0, CNTx_CTL_ENABLE);
}
bool hv_want_rendezvous(void)
{
return hv_want_cpu != -1;
}
void hv_do_rendezvous(struct exc_info *ctx)
{
if (hv_want_cpu == smp_id()) {
hv_want_cpu = -1;
hv_exc_proxy(ctx, START_HV, HV_USER_INTERRUPT, NULL);
} else if (hv_want_cpu != -1) {
// Unlock the HV so the target CPU can get into the proxy
spin_unlock(&bhl);
while (hv_want_cpu != -1)
sysop("dmb sy");
spin_lock(&bhl);
// Make sure we tick at least once more before running the guest
hv_rearm();
}
}
void hv_maybe_exit(void)
{
if (hv_should_exit) {

View file

@ -44,6 +44,7 @@ typedef enum _hv_entry_type {
HV_VTIMER,
HV_USER_INTERRUPT,
HV_WDT_BARK,
HV_CPU_SWITCH,
} hv_entry_type;
/* VM */
@ -95,11 +96,9 @@ void hv_init(void);
void hv_start(void *entry, u64 regs[4]);
void hv_start_secondary(int cpu, void *entry, u64 regs[4]);
void hv_rendezvous(void);
void hv_switch_cpu(int cpu);
bool hv_switch_cpu(int cpu);
void hv_arm_tick(void);
void hv_rearm(void);
bool hv_want_rendezvous(void);
void hv_do_rendezvous(struct exc_info *ctx);
void hv_maybe_exit(void);
void hv_tick(struct exc_info *ctx);

View file

@ -37,10 +37,12 @@ static u64 stolen_time = 0;
static u64 exc_entry_time;
extern u32 hv_cpus_in_guest;
extern int hv_want_cpu;
static bool time_stealing = true;
void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type, void *extra)
static void _hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type,
void *extra)
{
int from_el = FIELD_GET(SPSR_M, ctx->spsr) >> 2;
@ -77,17 +79,53 @@ void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type
u64 lost = mrs(CNTPCT_EL0) - entry_time;
stolen_time += lost;
}
return;
break;
case EXC_EXIT_GUEST:
hv_rendezvous();
spin_unlock(&bhl);
hv_exit_guest();
hv_exit_guest(); // does not return
default:
printf("Guest exception not handled, rebooting.\n");
print_regs(ctx->regs, 0);
flush_and_reboot();
flush_and_reboot(); // does not return
}
}
static void hv_maybe_switch_cpu(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type,
void *extra)
{
while (hv_want_cpu != -1) {
if (hv_want_cpu == smp_id()) {
hv_want_cpu = -1;
_hv_exc_proxy(ctx, reason, type, extra);
} else {
// Unlock the HV so the target CPU can get into the proxy
spin_unlock(&bhl);
while (hv_want_cpu != -1)
sysop("dmb sy");
spin_lock(&bhl);
}
}
}
void hv_exc_proxy(struct exc_info *ctx, uartproxy_boot_reason_t reason, u32 type, void *extra)
{
/*
* If we end up in the proxy due to an event while the host is trying to switch CPUs,
* handle it as a CPU switch first. We still tell the host the real reason code, though.
*/
hv_maybe_switch_cpu(ctx, reason, type, extra);
/* Handle the actual exception */
_hv_exc_proxy(ctx, reason, type, extra);
/*
* If as part of handling this exception we want to switch CPUs, handle it without returning
* to the guest.
*/
hv_maybe_switch_cpu(ctx, reason, type, extra);
}
void hv_set_time_stealing(bool enabled)
{
time_stealing = enabled;
@ -386,7 +424,7 @@ void hv_exc_fiq(struct exc_info *ctx)
tick = true;
}
if (mrs(TPIDR_EL2) != 0 && !(mrs(ISR_EL1) & 0x40) && !hv_want_rendezvous()) {
if (mrs(TPIDR_EL2) != 0 && !(mrs(ISR_EL1) & 0x40) && hv_want_cpu == -1) {
// Secondary CPU and it was just a timer tick (or spurious), so just update FIQs
hv_update_fiq();
hv_arm_tick();
@ -433,7 +471,8 @@ void hv_exc_fiq(struct exc_info *ctx)
msr(SYS_IMP_APL_IPI_SR_EL1, IPI_SR_PENDING);
sysop("isb");
}
hv_do_rendezvous(ctx);
hv_maybe_switch_cpu(ctx, START_HV, HV_CPU_SWITCH, NULL);
// Handles guest timers
hv_exc_exit(ctx);

View file

@ -459,7 +459,7 @@ int proxy_process(ProxyRequest *request, ProxyReply *reply)
hv_start_secondary(request->args[0], (void *)request->args[1], &request->args[2]);
break;
case P_HV_SWITCH_CPU:
hv_switch_cpu(request->args[0]);
reply->retval = hv_switch_cpu(request->args[0]);
break;
case P_HV_SET_TIME_STEALING:
hv_set_time_stealing(request->args[0]);