/* * Copyright (c) 2017 Richard Braun. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IOAPIC_REG_VERSION 0x01 #define IOAPIC_REG_IOREDTBL 0x10 #define IOAPIC_VERSION_VERSION_MASK 0x000000ff #define IOAPIC_VERSION_VERSION_SHIFT 0 #define IOAPIC_VERSION_MAXREDIR_MASK 0x00ff0000 #define IOAPIC_VERSION_MAXREDIR_SHIFT 16 #define IOAPIC_ENTLOW_FIXED_DEST 0x00000 #define IOAPIC_ENTLOW_PHYS_DELIVERY 0x00000 #define IOAPIC_ENTLOW_ACTIVE_LOW 0x02000 #define IOAPIC_ENTLOW_LEVEL 0x08000 #define IOAPIC_ENTLOW_INTRMASK 0x10000 #define IOAPIC_MAX_ENTRIES 24 struct ioapic_map { uint8_t regsel; uint8_t _reserved0; uint8_t _reserved1; uint8_t _reserved2; uint32_t _reserved3; uint32_t _reserved4; uint32_t _reserved5; uint32_t win; }; /* * Interrupt source override descriptor. */ struct ioapic_iso { uint32_t gsi; uint8_t source; bool active_high; bool edge_triggered; }; struct ioapic { struct spinlock lock; unsigned int id; unsigned int apic_id; unsigned int version; volatile struct ioapic_map *map; unsigned int first_gsi; unsigned int last_gsi; }; static unsigned int ioapic_nr_devs; static struct ioapic_iso ioapic_isos[PIC_MAX_INTR + 1]; static unsigned int ioapic_nr_isos; static void ioapic_iso_init(struct ioapic_iso *iso, uint8_t source, uint32_t gsi, bool active_high, bool edge_triggered) { iso->source = source; iso->gsi = gsi; iso->active_high = active_high; iso->edge_triggered = edge_triggered; } static struct ioapic_iso * __init ioapic_alloc_iso(void) { struct ioapic_iso *iso; if (ioapic_nr_isos >= ARRAY_SIZE(ioapic_isos)) { log_err("ioapic: too many interrupt overrides"); return NULL; } iso = &ioapic_isos[ioapic_nr_isos]; ioapic_nr_isos++; return iso; } static struct ioapic_iso * ioapic_lookup_iso(unsigned int intr) { struct ioapic_iso *iso; unsigned int i; for (i = 0; i < ioapic_nr_isos; i++) { iso = &ioapic_isos[i]; if (intr == iso->source) { return iso; } } return NULL; } static uint32_t ioapic_read(struct ioapic *ioapic, uint8_t reg) { ioapic->map->regsel = reg; return ioapic->map->win; } static void ioapic_write(struct ioapic *ioapic, uint8_t reg, uint32_t value) { ioapic->map->regsel = reg; ioapic->map->win = value; } static void ioapic_write_entry_low(struct ioapic *ioapic, unsigned int id, uint32_t value) { assert(id < IOAPIC_MAX_ENTRIES); ioapic_write(ioapic, IOAPIC_REG_IOREDTBL + (id * 2), value); } static void ioapic_write_entry_high(struct ioapic *ioapic, unsigned int id, uint32_t value) { assert(id < IOAPIC_MAX_ENTRIES); ioapic_write(ioapic, IOAPIC_REG_IOREDTBL + (id * 2) + 1, value); } static void ioapic_intr(struct trap_frame *frame) { intr_handle(frame->vector - TRAP_INTR_FIRST); } static struct ioapic * __init ioapic_create(unsigned int apic_id, uintptr_t addr, unsigned int gsi_base) { struct ioapic *ioapic; unsigned int i, nr_gsis; uint32_t value; ioapic = kmem_alloc(sizeof(*ioapic)); if (ioapic == NULL) { panic("ioapic: unable to allocate memory for controller"); } spinlock_init(&ioapic->lock); ioapic->id = ioapic_nr_devs; ioapic->apic_id = apic_id; ioapic->first_gsi = gsi_base; ioapic->map = vm_kmem_map_pa(addr, sizeof(*ioapic->map), NULL, NULL); if (ioapic->map == NULL) { panic("ioapic: unable to map register window in kernel map"); } value = ioapic_read(ioapic, IOAPIC_REG_VERSION); ioapic->version = (value & IOAPIC_VERSION_VERSION_MASK) >> IOAPIC_VERSION_VERSION_SHIFT; nr_gsis = ((value & IOAPIC_VERSION_MAXREDIR_MASK) >> IOAPIC_VERSION_MAXREDIR_SHIFT) + 1; ioapic->last_gsi = ioapic->first_gsi + nr_gsis - 1; /* XXX This assumes that interrupts are mapped 1:1 to traps */ if (ioapic->last_gsi > (TRAP_INTR_LAST - TRAP_INTR_FIRST)) { panic("ioapic: invalid interrupt range"); } for (i = ioapic->first_gsi; i < ioapic->last_gsi; i++) { trap_register(TRAP_INTR_FIRST + i, ioapic_intr); } log_info("ioapic%u: version:%#x gsis:%u-%u", ioapic->id, ioapic->version, ioapic->first_gsi, ioapic->last_gsi); ioapic_nr_devs++; return ioapic; } static bool ioapic_has_gsi(const struct ioapic *ioapic, unsigned int gsi) { return ((gsi >= ioapic->first_gsi) && (gsi <= ioapic->last_gsi)); } static unsigned int ioapic_compute_id(const struct ioapic *ioapic, unsigned int gsi) { assert(ioapic_has_gsi(ioapic, gsi)); return gsi - ioapic->first_gsi; } static void ioapic_compute_entry(uint32_t *highp, uint32_t *lowp, unsigned int apic_id, unsigned int intr, bool active_high, bool edge_triggered) { assert(apic_id < 16); assert(intr < (TRAP_NR_VECTORS - TRAP_INTR_FIRST)); *highp = apic_id << 24; *lowp = (!edge_triggered ? IOAPIC_ENTLOW_LEVEL : 0) | (!active_high ? IOAPIC_ENTLOW_ACTIVE_LOW : 0) | IOAPIC_ENTLOW_PHYS_DELIVERY | IOAPIC_ENTLOW_FIXED_DEST | (TRAP_INTR_FIRST + intr); } static void ioapic_enable(void *priv, unsigned int intr, unsigned int cpu) { bool active_high, edge_triggered; const struct ioapic_iso *iso; uint32_t high, low, gsi; struct ioapic *ioapic; unsigned long flags; unsigned int id; iso = ioapic_lookup_iso(intr); /* XXX These are defaults that should work with architectural devices */ if (iso == NULL) { active_high = true; edge_triggered = true; gsi = intr; } else { active_high = iso->active_high; edge_triggered = iso->edge_triggered; gsi = iso->gsi; } ioapic = priv; id = ioapic_compute_id(ioapic, gsi); ioapic_compute_entry(&high, &low, cpu_apic_id(cpu), intr, active_high, edge_triggered); spinlock_lock_intr_save(&ioapic->lock, &flags); ioapic_write_entry_high(ioapic, id, high); ioapic_write_entry_low(ioapic, id, low); spinlock_unlock_intr_restore(&ioapic->lock, flags); } static void ioapic_disable(void *priv, unsigned int intr) { struct ioapic *ioapic; unsigned long flags; unsigned int id; ioapic = priv; id = ioapic_compute_id(ioapic, intr); spinlock_lock_intr_save(&ioapic->lock, &flags); ioapic_write_entry_low(ioapic, id, IOAPIC_ENTLOW_INTRMASK); spinlock_unlock_intr_restore(&ioapic->lock, flags); } static void ioapic_eoi(void *priv, unsigned int intr) { (void)priv; (void)intr; lapic_eoi(); } static const struct intr_ops ioapic_ops = { .enable = ioapic_enable, .disable = ioapic_disable, .eoi = ioapic_eoi, }; void __init ioapic_setup(void) { ioapic_nr_devs = 0; ioapic_nr_isos = 0; } void __init ioapic_register(unsigned int apic_id, uintptr_t addr, unsigned int gsi_base) { struct ioapic *ioapic; ioapic = ioapic_create(apic_id, addr, gsi_base); /* * XXX This assumes that any interrupt override source is included * in the GSI range. */ intr_register_ctl(&ioapic_ops, ioapic, ioapic->first_gsi, ioapic->last_gsi); } void __init ioapic_override(uint8_t source, uint32_t gsi, bool active_high, bool edge_triggered) { struct ioapic_iso *iso; iso = ioapic_alloc_iso(); if (iso == NULL) { return; } ioapic_iso_init(iso, source, gsi, active_high, edge_triggered); }