mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-11-11 02:04:11 +00:00
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:
parent
01a1a0f597
commit
8eeb7966aa
6 changed files with 128 additions and 53 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
35
src/hv.c
35
src/hv.c
|
@ -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) {
|
||||
|
|
5
src/hv.h
5
src/hv.h
|
@ -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);
|
||||
|
||||
|
|
51
src/hv_exc.c
51
src/hv_exc.c
|
@ -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);
|
||||
|
|
|
@ -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]);
|
||||
|
|
Loading…
Reference in a new issue