mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-11-25 16:10:16 +00:00
aic: Add support for multi-die AIC2 as seen on the M1 Ultra
Multi-die IRQs are coded as in the ADT: die * max_irq + num Signed-off-by: Janne Grunau <j@jannau.net>
This commit is contained in:
parent
524cb4a34f
commit
76fd226f7c
5 changed files with 154 additions and 73 deletions
121
src/aic.c
121
src/aic.c
|
@ -6,12 +6,15 @@
|
|||
#include "assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
u64 aic_base;
|
||||
|
||||
#define MASK_REG(x) (4 * ((x) >> 5))
|
||||
#define MASK_BIT(x) BIT((x)&GENMASK(4, 0))
|
||||
|
||||
static const struct aic_regs aic1_regs = {
|
||||
static struct aic aic1 = {
|
||||
.version = 1,
|
||||
.nr_die = 1,
|
||||
.max_die = 1,
|
||||
.regs =
|
||||
{
|
||||
.reg_size = AIC_REG_SIZE,
|
||||
.event = AIC_EVENT,
|
||||
.tgt_cpu = AIC_TARGET_CPU,
|
||||
|
@ -19,36 +22,81 @@ static const struct aic_regs aic1_regs = {
|
|||
.sw_clr = AIC_SW_CLR,
|
||||
.mask_set = AIC_MASK_SET,
|
||||
.mask_clr = AIC_MASK_CLR,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct aic_regs aic2_regs = {
|
||||
.reg_size = AIC2_REG_SIZE,
|
||||
.event = AIC2_EVENT,
|
||||
static struct aic aic2 = {
|
||||
.version = 2,
|
||||
.regs =
|
||||
{
|
||||
.config = AIC2_IRQ_CFG,
|
||||
.sw_set = AIC2_SW_SET,
|
||||
.sw_clr = AIC2_SW_CLR,
|
||||
.mask_set = AIC2_MASK_SET,
|
||||
.mask_clr = AIC2_MASK_CLR,
|
||||
},
|
||||
};
|
||||
|
||||
const struct aic_regs *aic_regs;
|
||||
struct aic *aic;
|
||||
|
||||
static void aic2_init(int node)
|
||||
static int aic2_init(int node)
|
||||
{
|
||||
int ret = ADT_GETPROP(adt, node, "aic-iack-offset", &aic->regs.event);
|
||||
if (ret < 0) {
|
||||
printf("AIC: failed to get property aic-iack-offset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 info1 = read32(aic->base + AIC2_INFO1);
|
||||
aic->nr_die = FIELD_GET(AIC2_INFO1_LAST_DIE, info1) + 1;
|
||||
aic->nr_irq = FIELD_GET(AIC2_INFO1_NR_IRQ, info1);
|
||||
|
||||
u32 info3 = read32(aic->base + AIC2_INFO3);
|
||||
aic->max_die = FIELD_GET(AIC2_INFO3_MAX_DIE, info3);
|
||||
aic->max_irq = FIELD_GET(AIC2_INFO3_MAX_IRQ, info3);
|
||||
|
||||
if (aic->nr_die > AIC_MAX_DIES) {
|
||||
printf("AIC: more dies than supported: %u\n", aic->max_die);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (aic->max_irq > AIC_MAX_HW_NUM) {
|
||||
printf("AIC: more IRQs than supported: %u\n", aic->max_irq);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const u64 start_off = aic->regs.config;
|
||||
u64 off = start_off + sizeof(u32) * aic->max_irq; /* IRQ_CFG */
|
||||
|
||||
aic->regs.sw_set = off;
|
||||
off += sizeof(u32) * (aic->max_irq >> 5); /* SW_SET */
|
||||
aic->regs.sw_clr = off;
|
||||
off += sizeof(u32) * (aic->max_irq >> 5); /* SW_CLR */
|
||||
aic->regs.mask_set = off;
|
||||
off += sizeof(u32) * (aic->max_irq >> 5); /* MASK_SET */
|
||||
aic->regs.mask_clr = off;
|
||||
off += sizeof(u32) * (aic->max_irq >> 5); /* MASK_CLR */
|
||||
off += sizeof(u32) * (aic->max_irq >> 5); /* HW_STATE */
|
||||
|
||||
aic->die_stride = off - start_off;
|
||||
aic->regs.reg_size = aic->regs.event + 4;
|
||||
|
||||
printf("AIC: AIC2 with %u/%u dies, %u/%u IRQs, reg_size:%05lx die_stride:%05x\n", aic->nr_die,
|
||||
aic->max_die, aic->nr_irq, aic->max_irq, aic->regs.reg_size, aic->die_stride);
|
||||
|
||||
u32 ext_intr_config_len;
|
||||
const u8 *ext_intr_config = adt_getprop(adt, node, "aic-ext-intr-cfg", &ext_intr_config_len);
|
||||
|
||||
if (ext_intr_config) {
|
||||
printf("AIC: Configuring %d external interrupts\n", ext_intr_config_len / 3);
|
||||
for (u32 i = 0; i < ext_intr_config_len; i += 3) {
|
||||
u16 irq = ext_intr_config[i] | (ext_intr_config[i + 1] << 8);
|
||||
u8 die = ext_intr_config[i + 1] >> 4;
|
||||
u16 irq = ext_intr_config[i] | ((ext_intr_config[i + 1] & 0xf) << 8);
|
||||
u8 target = ext_intr_config[i + 2];
|
||||
assert(irq < 0x1000); // Will probably need updating for multi-die
|
||||
mask32(aic_base + aic_regs->config + 4 * irq, AIC2_IRQ_CFG_TARGET,
|
||||
FIELD_PREP(AIC2_IRQ_CFG_TARGET, target));
|
||||
assert(die < aic->nr_die);
|
||||
assert(irq < aic->nr_irq);
|
||||
mask32(aic->base + aic->regs.config + die * aic->die_stride + 4 * irq,
|
||||
AIC2_IRQ_CFG_TARGET, FIELD_PREP(AIC2_IRQ_CFG_TARGET, target));
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void aic_init(void)
|
||||
|
@ -61,32 +109,45 @@ void aic_init(void)
|
|||
return;
|
||||
}
|
||||
|
||||
if (adt_get_reg(adt, path, "reg", 0, &aic_base, NULL)) {
|
||||
if (adt_is_compatible(adt, node, "aic,1")) {
|
||||
aic = &aic1;
|
||||
} else if (adt_is_compatible(adt, node, "aic,2")) {
|
||||
aic = &aic2;
|
||||
} else {
|
||||
printf("AIC: Error: Unsupported version\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (adt_get_reg(adt, path, "reg", 0, &aic->base, NULL)) {
|
||||
printf("Failed to get AIC reg property!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (adt_is_compatible(adt, node, "aic,1")) {
|
||||
printf("AIC: Version 1 @ 0x%lx\n", aic_base);
|
||||
aic_regs = &aic1_regs;
|
||||
} else if (adt_is_compatible(adt, node, "aic,2")) {
|
||||
printf("AIC: Version 2 @ 0x%lx\n", aic_base);
|
||||
aic_regs = &aic2_regs;
|
||||
aic2_init(node);
|
||||
} else {
|
||||
printf("AIC: Error: Unsupported version @ 0x%lx\n", aic_base);
|
||||
if (aic->version == 1) {
|
||||
printf("AIC: Version 1 @ 0x%lx\n", aic->base);
|
||||
aic->nr_irq = FIELD_GET(AIC_INFO_NR_HW, read32(aic->base + AIC_INFO));
|
||||
aic->max_irq = AIC1_MAX_IRQ;
|
||||
} else if (aic->version == 2) {
|
||||
printf("AIC: Version 2 @ 0x%lx\n", aic->base);
|
||||
int ret = aic2_init(node);
|
||||
if (ret < 0)
|
||||
aic = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void aic_set_sw(int irq, bool active)
|
||||
{
|
||||
u32 die = irq / aic->max_irq;
|
||||
irq = irq % aic->max_irq;
|
||||
if (active)
|
||||
write32(aic_base + aic_regs->sw_set + MASK_REG(irq), MASK_BIT(irq));
|
||||
write32(aic->base + aic->regs.sw_set + die * aic->die_stride + MASK_REG(irq),
|
||||
MASK_BIT(irq));
|
||||
else
|
||||
write32(aic_base + aic_regs->sw_clr + MASK_REG(irq), MASK_BIT(irq));
|
||||
write32(aic->base + aic->regs.sw_clr + die * aic->die_stride + MASK_REG(irq),
|
||||
MASK_BIT(irq));
|
||||
}
|
||||
|
||||
uint32_t aic_ack(void)
|
||||
{
|
||||
return read32(aic_base + aic_regs->event);
|
||||
return read32(aic->base + aic->regs.event);
|
||||
}
|
||||
|
|
17
src/aic.h
17
src/aic.h
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "types.h"
|
||||
|
||||
extern u64 aic_base;
|
||||
#define AIC_MAX_DIES 4
|
||||
|
||||
struct aic_regs {
|
||||
uint64_t reg_size;
|
||||
|
@ -18,7 +18,20 @@ struct aic_regs {
|
|||
uint64_t mask_clr;
|
||||
};
|
||||
|
||||
extern const struct aic_regs *aic_regs;
|
||||
struct aic {
|
||||
uint64_t base;
|
||||
uint32_t version;
|
||||
|
||||
uint32_t nr_irq;
|
||||
uint32_t nr_die;
|
||||
uint32_t max_irq;
|
||||
uint32_t max_die;
|
||||
uint32_t die_stride;
|
||||
|
||||
struct aic_regs regs;
|
||||
};
|
||||
|
||||
extern struct aic *aic;
|
||||
|
||||
void aic_init(void);
|
||||
void aic_set_sw(int irq, bool active);
|
||||
|
|
|
@ -19,21 +19,24 @@
|
|||
#define AIC_CPU_IPI_MASK_SET(cpu) (0x5024 + ((cpu) << 7))
|
||||
#define AIC_CPU_IPI_MASK_CLR(cpu) (0x5028 + ((cpu) << 7))
|
||||
|
||||
#define AIC2_REG_SIZE 0x10000
|
||||
#define AIC2_INFO 0x0004
|
||||
#define AIC2_INFO1 0x0004
|
||||
#define AIC2_INFO2 0x0008
|
||||
#define AIC2_INFO3 0x000c
|
||||
#define AIC2_LATENCY 0x0204
|
||||
#define AIC2_EVENT 0xc000
|
||||
#define AIC2_IRQ_CFG 0x2000
|
||||
#define AIC2_SW_SET 0x6000
|
||||
#define AIC2_SW_CLR 0x6200
|
||||
#define AIC2_MASK_SET 0x6400
|
||||
#define AIC2_MASK_CLR 0x6800
|
||||
|
||||
#define AIC2_IRQ_CFG_TARGET GENMASK(3, 0)
|
||||
|
||||
#define AIC_INFO_NR_HW GENMASK(15, 0)
|
||||
|
||||
#define AIC_EVENT_TYPE GENMASK(31, 16)
|
||||
#define AIC2_INFO1_NR_IRQ GENMASK(15, 0)
|
||||
#define AIC2_INFO1_LAST_DIE GENMASK(27, 24)
|
||||
|
||||
#define AIC2_INFO3_MAX_IRQ GENMASK(15, 0)
|
||||
#define AIC2_INFO3_MAX_DIE GENMASK(27, 24)
|
||||
|
||||
#define AIC_EVENT_DIE GENMASK(31, 24)
|
||||
#define AIC_EVENT_TYPE GENMASK(23, 16)
|
||||
#define AIC_EVENT_NUM GENMASK(15, 0)
|
||||
|
||||
#define AIC_EVENT_TYPE_HW 1
|
||||
|
@ -46,4 +49,5 @@
|
|||
#define AIC_IPI_OTHER BIT(0)
|
||||
#define AIC_IPI_SELF BIT(31)
|
||||
|
||||
#define AIC_MAX_HW_NUM (0x80 * 32)
|
||||
#define AIC1_MAX_IRQ 0x400
|
||||
#define AIC_MAX_HW_NUM (0x80 * 32) // max_irq of the M1 Max
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "exception.h"
|
||||
#include "aic.h"
|
||||
#include "aic_regs.h"
|
||||
#include "cpu_regs.h"
|
||||
#include "gxf.h"
|
||||
#include "iodev.h"
|
||||
|
@ -285,8 +286,9 @@ void exc_irq(u64 *regs)
|
|||
{
|
||||
u32 reason = aic_ack();
|
||||
|
||||
printf("Exception: IRQ (from %s) type: %d num: %d mpidr: %lx\n", get_exception_source(0),
|
||||
reason >> 16, reason & 0xffff, mrs(MPIDR_EL1));
|
||||
printf("Exception: IRQ (from %s) die: %lu type: %lu num: %lu mpidr: %lx\n",
|
||||
get_exception_source(0), FIELD_GET(AIC_EVENT_DIE, reason),
|
||||
FIELD_GET(AIC_EVENT_TYPE, reason), FIELD_GET(AIC_EVENT_NUM, reason), mrs(MPIDR_EL1));
|
||||
|
||||
UNUSED(regs);
|
||||
// print_regs(regs);
|
||||
|
|
41
src/hv_aic.c
41
src/hv_aic.c
|
@ -9,14 +9,14 @@
|
|||
|
||||
#define IRQTRACE_IRQ BIT(0)
|
||||
|
||||
static u32 trace_hw_num[AIC_MAX_HW_NUM / 32];
|
||||
static u32 trace_hw_num[AIC_MAX_DIES][AIC_MAX_HW_NUM / 32];
|
||||
|
||||
static void emit_irqtrace(u16 type, u16 num)
|
||||
static void emit_irqtrace(u16 die, u16 type, u16 num)
|
||||
{
|
||||
struct hv_evt_irqtrace evt = {
|
||||
.flags = IRQTRACE_IRQ,
|
||||
.type = type,
|
||||
.num = num,
|
||||
.num = die * aic->max_irq + num,
|
||||
};
|
||||
|
||||
hv_wdt_suspend();
|
||||
|
@ -29,17 +29,21 @@ static bool trace_aic_event(struct exc_info *ctx, u64 addr, u64 *val, bool write
|
|||
if (!hv_pa_rw(ctx, addr, val, write, width))
|
||||
return false;
|
||||
|
||||
if (addr != (aic_base + aic_regs->event) || write || width != 2) {
|
||||
if (addr != (aic->base + aic->regs.event) || write || width != 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
u16 type = (*val & AIC_EVENT_TYPE) >> 16;
|
||||
u16 num = *val & AIC_EVENT_NUM;
|
||||
u16 die = FIELD_GET(AIC_EVENT_DIE, *val);
|
||||
u16 type = FIELD_GET(AIC_EVENT_TYPE, *val);
|
||||
u16 num = FIELD_GET(AIC_EVENT_NUM, *val);
|
||||
|
||||
if (die > AIC_MAX_DIES)
|
||||
return true;
|
||||
|
||||
switch (type) {
|
||||
case AIC_EVENT_TYPE_HW:
|
||||
if (trace_hw_num[num / 32] & BIT(num & 31)) {
|
||||
emit_irqtrace(type, num);
|
||||
if (trace_hw_num[die][num / 32] & BIT(num & 31)) {
|
||||
emit_irqtrace(die, type, num);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -54,17 +58,19 @@ 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);
|
||||
u32 die = num / aic->max_irq;
|
||||
num %= AIC_MAX_HW_NUM;
|
||||
if (die >= aic->max_irq || num >= AIC_MAX_HW_NUM || count > AIC_MAX_HW_NUM - num) {
|
||||
printf("HV: invalid IRQ range: (%u, %u) for die %u\n", num, num + count, die);
|
||||
return false;
|
||||
}
|
||||
for (u32 n = num; n < num + count; n++) {
|
||||
switch (flags) {
|
||||
case IRQTRACE_IRQ:
|
||||
trace_hw_num[n / 32] |= BIT(n & 31);
|
||||
trace_hw_num[die][n / 32] |= BIT(n & 31);
|
||||
break;
|
||||
default:
|
||||
trace_hw_num[n / 32] &= ~(BIT(n & 31));
|
||||
trace_hw_num[die][n / 32] &= ~(BIT(n & 31));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -73,20 +79,15 @@ bool hv_trace_irq(u32 type, u32 num, u32 count, u32 flags)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!aic_base) {
|
||||
if (!aic) {
|
||||
printf("HV: AIC not initialized\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, aic_regs->reg_size);
|
||||
if (aic && !hooked) {
|
||||
hv_map_hook(aic->base, trace_aic_event, aic->regs.reg_size);
|
||||
hooked = true;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue