hv: add AIC interrupt tracing

Implemented by MMIO tracing of AIC's event register. Proposed by pipcet.

Signed-off-by: Janne Grunau <j@jannau.net>
This commit is contained in:
Janne Grunau 2021-06-05 22:05:17 +02:00 committed by Hector Martin
parent 012d8964f9
commit 88275b5cb5
9 changed files with 244 additions and 43 deletions

View file

@ -44,7 +44,7 @@ OBJECTS := \
fb.o font.o font_retina.o \
gxf.o gxf_asm.o \
heapblock.o \
hv.o hv_vm.o hv_exc.o hv_vuart.o hv_wdt.o hv_asm.o \
hv.o hv_vm.o hv_exc.o hv_vuart.o hv_wdt.o hv_asm.o hv_aic.o \
i2c.o \
iodev.o \
kboot.o \

View file

@ -30,6 +30,12 @@ EvtMMIOTrace = Struct(
"data" / Hex(Int64ul),
)
EvtIRQTrace = Struct(
"flags" / Int32ul,
"type" / Hex(Int16ul),
"num" / Int16ul,
)
class HV_EVENT(IntEnum):
HOOK_VM = 1
VTIMER = 2
@ -89,6 +95,9 @@ class HV:
GXF_ENTER_EL1: GXF_ENTER_EL12,
}
AIC_EVT_TYPE_HW = 1
IRQTRACE_IRQ = 1
def __init__(self, iface, proxy, utils):
self.iface = iface
self.p = proxy
@ -104,6 +113,7 @@ class HV:
self._in_handler = False
self._sigint_pending = False
self.vm_hooks = []
self.interrupt_map = {}
def unmap(self, ipa, size):
assert self.p.hv_map(ipa, 0, size, 0) >= 0
@ -129,6 +139,14 @@ class HV:
self.vm_hooks.append((read, write, ipa, kwargs))
assert self.p.hv_map(ipa, (index << 2) | t, size, 1) >= 0
def trace_irq(self, device, num, count, flags):
for n in range(num, num + count):
if flags & self.IRQTRACE_IRQ:
self.interrupt_map[n] = device
else:
self.interrupt_map.pop(n, None)
assert self.p.hv_trace_irq(self.AIC_EVT_TYPE_HW, num, count, flags) > 0
def addr(self, addr):
unslid_addr = addr + self.sym_offset
if addr < self.tba.virt_base or unslid_addr < self.macho.vmin:
@ -171,6 +189,13 @@ class HV:
print(f"[0x{evt.pc:016x}] MMIO: {t}.{1<<evt.flags.WIDTH:<2}{m} " +
f"0x{evt.addr:x} ({dev}, offset {evt.addr - zone.start:#04x}) = 0x{evt.data:x}")
def handle_irqtrace(self, data):
evt = EvtIRQTrace.parse(data)
if evt.type == self.AIC_EVT_TYPE_HW and evt.flags & self.IRQTRACE_IRQ:
dev = self.interrupt_map[int(evt.num)]
print(f"IRQ: {dev}: {evt.num}")
def handle_vm_hook(self, ctx):
data = self.iface.readstruct(ctx.data, VMProxyHookData)
@ -571,6 +596,7 @@ class HV:
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_event_handler(EVENT.MMIOTRACE, self.handle_mmiotrace)
self.iface.set_event_handler(EVENT.IRQTRACE, self.handle_irqtrace)
#self.map_sw(0x2_00000000,
#0x2_00000000 | self.SPTE_TRACE_READ | self.SPTE_TRACE_WRITE,
@ -610,6 +636,20 @@ class HV:
print(f"Pass: 0x{addr:x} [0x{size:x}] ({path})")
self.map_hw(addr, addr, size)
# trace IRQs
aic_phandle = getattr(self.adt["/arm-io/aic"], "AAPL,phandle")
for path in (
"/arm-io/usb-drd1",
"/arm-io/gpio",
):
node = self.adt[path]
if getattr(node, "interrupt-parent") != aic_phandle:
print(f"{path} has no direct AIC interrupts.")
continue
for irq in getattr(node, "interrupts"):
#self.trace_irq(node.name, irq, 1, self.IRQTRACE_IRQ)
pass
# Sync PMGR stuff
#self.map_sw(0x2_3b700000,
#0x2_3b700000 | self.SPTE_TRACE_READ | self.SPTE_TRACE_WRITE | self.SPTE_SYNC_TRACE,

View file

@ -102,6 +102,7 @@ class EXC(IntEnum):
class EVENT(IntEnum):
MMIOTRACE = 1
IRQTRACE = 2
class EXC_RET(IntEnum):
UNHANDLED = 1
@ -584,6 +585,7 @@ class M1N1Proxy:
P_HV_TRANSLATE = 0xc03
P_HV_PT_WALK = 0xc04
P_HV_MAP_VUART = 0xc05
P_HV_TRACE_IRQ = 0xc06
P_FB_INIT = 0xd00
P_FB_SHUTDOWN = 0xd01
@ -945,6 +947,8 @@ class M1N1Proxy:
return self.request(self.P_HV_PT_WALK, addr)
def hv_map_vuart(self, base, iodev):
return self.request(self.P_HV_MAP_VUART, base, iodev)
def hv_trace_irq(self, evt_type, num, count, flags):
return self.request(self.P_HV_TRACE_IRQ, evt_type, num, count, flags)
def fb_init(self):
return self.request(self.P_FB_INIT)

View file

@ -21,6 +21,12 @@ struct hv_evt_mmiotrace {
u64 data;
};
struct hv_evt_irqtrace {
u32 flags;
u16 type;
u16 num;
};
struct hv_vm_proxy_hook_data {
u32 flags;
u32 id;
@ -46,6 +52,12 @@ int hv_map_proxy_hook(u64 from, u64 id, u64 size);
u64 hv_translate(u64 addr, bool s1only, bool w);
u64 hv_pt_walk(u64 addr);
bool hv_handle_dabort(u64 *regs);
bool hv_pa_write(u64 addr, u64 *val, int width);
bool hv_pa_read(u64 addr, u64 *val, int width);
bool hv_pa_rw(u64 addr, u64 *val, bool write, int width);
/* AIC events through tracing the MMIO event address */
bool hv_trace_irq(u32 type, u32 num, u32 count, u32 flags);
/* Virtual peripherals */
void hv_map_vuart(u64 base, iodev_id_t iodev);

119
src/hv_aic.c Normal file
View file

@ -0,0 +1,119 @@
/* SPDX-License-Identifier: MIT */
#include "adt.h"
#include "hv.h"
#include "uartproxy.h"
#include "utils.h"
#define AIC_INFO 0x0004
#define AIC_INFO_NR_HW GENMASK(15, 0)
#define AIC_EVENT 0x2004
#define AIC_EVENT_TYPE GENMASK(31, 16)
#define AIC_EVENT_NUM GENMASK(15, 0)
#define AIC_EVENT_TYPE_HW 1
#define AIC_EVENT_TYPE_IPI 4
#define AIC_EVENT_IPI_OTHER 1
#define AIC_EVENT_IPI_SELF 2
#define AIC_MAX_HW_NUM (28 * 32)
#define IRQTRACE_IRQ BIT(0)
static u64 aic_base;
static u32 trace_hw_num[AIC_MAX_HW_NUM / 32];
static void emit_irqtrace(u16 type, u16 num)
{
struct hv_evt_irqtrace evt = {
.flags = IRQTRACE_IRQ,
.type = type,
.num = num,
};
hv_wdt_suspend();
uartproxy_send_event(EVT_IRQTRACE, &evt, sizeof(evt));
hv_wdt_resume();
}
static bool trace_aic_event(u64 addr, u64 *val, bool write, int width)
{
if (!hv_pa_rw(addr, val, write, width))
return false;
if (addr != (aic_base + AIC_EVENT) || write || width != 2) {
return true;
}
u16 type = (*val & AIC_EVENT_TYPE) >> 16;
u16 num = *val & AIC_EVENT_NUM;
switch (type) {
case AIC_EVENT_TYPE_HW:
if (trace_hw_num[num / 32] & BIT(num & 31)) {
emit_irqtrace(type, num);
}
break;
default:
// ignore
break;
}
return true;
}
bool hv_trace_irq(u32 type, u32 num, u32 count, u32 flags)
{
dprintf("HV: hv_trace_irq type: %u start: %u num: %u flags: 0x%x\n", type, num, count, flags);
if (type == AIC_EVENT_TYPE_HW) {
if (num >= AIC_MAX_HW_NUM || count > AIC_MAX_HW_NUM - num) {
printf("HV: invalid IRQ range: (%u, %u)\n", num, num + count);
return false;
}
for (u32 n = num; n < num + count; n++) {
switch (flags) {
case IRQTRACE_IRQ:
trace_hw_num[n / 32] |= BIT(n & 31);
break;
default:
trace_hw_num[n / 32] &= ~(BIT(n & 31));
break;
}
}
} else {
printf("HV: not handling AIC event type: 0x%02x num: %u\n", type, num);
return false;
}
if (!aic_base) {
static const char path[] = "/arm-io/aic";
int adt_path[8];
int node = adt_path_offset_trace(adt, path, adt_path);
if (node < 0) {
printf("HV: Error getting %s node\n", path);
return false;
}
if (adt_get_reg(adt, adt_path, "reg", 0, &aic_base, NULL) < 0) {
printf("HV: Error getting AIC base address.\n");
return false;
}
}
static bool hooked = false;
if (aic_base && !hooked) {
u32 nr_hw = FIELD_GET(AIC_INFO_NR_HW, read32(aic_base + AIC_INFO));
if (nr_hw > AIC_MAX_HW_NUM) {
printf("HV: AIC supports more IRQs than expected! nr_hw: %u\n", nr_hw);
return false;
}
hv_map_hook(aic_base, trace_aic_event, 0x4000);
hooked = true;
}
return true;
}

View file

@ -636,6 +636,64 @@ static void emit_mmiotrace(u64 pc, u64 addr, u64 *data, u64 width, u64 flags, bo
}
}
bool hv_pa_write(u64 addr, u64 *val, int width)
{
switch (width) {
case 0:
write8(addr, val[0]);
return true;
case 1:
write16(addr, val[0]);
return true;
case 2:
write32(addr, val[0]);
return true;
case 3:
write64(addr, val[0]);
return true;
case 4:
write64(addr, val[0]);
write64(addr + 8, val[1]);
return true;
default:
dprintf("HV: unsupported write width %ld\n", width);
return false;
}
}
bool hv_pa_read(u64 addr, u64 *val, int width)
{
switch (width) {
case 0:
val[0] = read8(addr);
return true;
case 1:
val[0] = read16(addr);
return true;
case 2:
val[0] = read32(addr);
return true;
case 3:
val[0] = read64(addr);
return true;
case 4:
val[0] = read64(addr);
val[1] = read64(addr + 8);
return true;
default:
dprintf("HV: unsupported read width %ld\n", width);
return false;
}
}
bool hv_pa_rw(u64 addr, u64 *val, bool write, int width)
{
if (write)
return hv_pa_write(addr, val, width);
else
return hv_pa_read(addr, val, width);
}
bool hv_handle_dabort(u64 *regs)
{
hv_wdt_breadcrumb('0');
@ -711,27 +769,8 @@ bool hv_handle_dabort(u64 *regs)
hv_wdt_breadcrumb('5');
dprintf("HV: SPTE_MAP[W] @0x%lx 0x%lx -> 0x%lx (w=%d): 0x%lx\n", elr_pa, far, paddr,
1 << width, val[0]);
switch (width) {
case 0:
write8(paddr, val[0]);
break;
case 1:
write16(paddr, val[0]);
break;
case 2:
write32(paddr, val[0]);
break;
case 3:
write64(paddr, val[0]);
break;
case 4:
write64(paddr, val[0]);
write64(paddr + 8, val[1]);
break;
default:
dprintf("HV: unsupported width %ld\n", width);
return false;
}
if (!hv_pa_write(paddr, val, width))
return false;
break;
case SPTE_HOOK: {
hv_wdt_breadcrumb('6');
@ -773,27 +812,8 @@ bool hv_handle_dabort(u64 *regs)
// fallthrough
case SPTE_MAP:
hv_wdt_breadcrumb('4');
switch (width) {
case 0:
val[0] = read8(paddr);
break;
case 1:
val[0] = read16(paddr);
break;
case 2:
val[0] = read32(paddr);
break;
case 3:
val[0] = read64(paddr);
break;
case 4:
val[0] = read64(paddr);
val[1] = read64(paddr + 8);
break;
default:
dprintf("HV: unsupported width %ld\n", width);
return false;
}
if (!hv_pa_read(paddr, val, width))
return false;
dprintf("HV: SPTE_MAP[R] @0x%lx 0x%lx -> 0x%lx (w=%d): 0x%lx\n", elr_pa, far, paddr,
1 << width, val[0]);
break;

View file

@ -427,6 +427,10 @@ int proxy_process(ProxyRequest *request, ProxyReply *reply)
case P_HV_MAP_VUART:
hv_map_vuart(request->args[0], request->args[1]);
break;
case P_HV_TRACE_IRQ:
reply->retval = hv_trace_irq(request->args[0], request->args[1], request->args[2],
request->args[3]);
break;
case P_FB_INIT:
fb_init();

View file

@ -118,6 +118,7 @@ typedef enum {
P_HV_TRANSLATE,
P_HV_PT_WALK,
P_HV_MAP_VUART,
P_HV_TRACE_IRQ,
P_FB_INIT = 0xd00,
P_FB_SHUTDOWN,

View file

@ -30,6 +30,7 @@ typedef enum _uartproxy_exc_ret_t {
typedef enum _uartproxy_event_type_t {
EVT_MMIOTRACE = 1,
EVT_IRQTRACE = 2,
} uartproxy_event_type_t;
struct uartproxy_exc_info {