#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ioapic.c,v 1.66 2022/10/06 06:42:46 msaitoh Exp $");
#include "opt_ddb.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <uvm/uvm_extern.h>
#include <machine/isa_machdep.h>
#include <machine/i82093reg.h>
#include <machine/i82093var.h>
#include <machine/i82489reg.h>
#include <machine/i82489var.h>
#include <machine/i8259.h>
#include <machine/mpbiosvar.h>
#include <machine/pio.h>
#include <machine/pmap.h>
#include <machine/lock.h>
#include "acpica.h"
#include "opt_mpbios.h"
#include "opt_acpi.h"
#if !defined(MPBIOS) && NACPICA == 0
#error "ioapic needs at least one of the MPBIOS or ACPI options"
#endif
int ioapic_match(device_t, cfdata_t, void *);
void ioapic_attach(device_t, device_t, void *);
extern int x86_mem_add_mapping(bus_addr_t, bus_size_t,
int, bus_space_handle_t *);
void ioapic_hwmask(struct pic *, int);
void ioapic_hwunmask(struct pic *, int);
bool ioapic_trymask(struct pic *, int);
static void ioapic_addroute(struct pic *, struct cpu_info *, int, int, int);
static void ioapic_delroute(struct pic *, struct cpu_info *, int, int, int);
struct ioapic_softc *ioapics;
int nioapics = 0;
static int ioapic_vecbase;
static inline u_long
ioapic_lock(struct ioapic_softc *sc)
{
u_long flags;
flags = x86_read_psl();
x86_disable_intr();
__cpu_simple_lock(&sc->sc_pic.pic_lock);
return flags;
}
static inline void
ioapic_unlock(struct ioapic_softc *sc, u_long flags)
{
__cpu_simple_unlock(&sc->sc_pic.pic_lock);
x86_write_psl(flags);
}
#ifndef _IOAPIC_CUSTOM_RW
static inline uint32_t
ioapic_read_ul(struct ioapic_softc *sc, int regid)
{
uint32_t val;
*(sc->sc_reg) = regid;
val = *sc->sc_data;
return val;
}
static inline void
ioapic_write_ul(struct ioapic_softc *sc, int regid, uint32_t val)
{
*(sc->sc_reg) = regid;
*(sc->sc_data) = val;
}
#endif
static inline uint32_t
ioapic_read(struct ioapic_softc *sc, int regid)
{
uint32_t val;
u_long flags;
flags = ioapic_lock(sc);
val = ioapic_read_ul(sc, regid);
ioapic_unlock(sc, flags);
return val;
}
static inline void
ioapic_write(struct ioapic_softc *sc, int regid, int val)
{
u_long flags;
flags = ioapic_lock(sc);
ioapic_write_ul(sc, regid, val);
ioapic_unlock(sc, flags);
}
struct ioapic_softc *
ioapic_find(int apicid)
{
struct ioapic_softc *sc;
if (apicid == MPS_ALL_APICS) {
if (nioapics <= 1)
return ioapics;
panic("unsupported: all-ioapics interrupt with >1 ioapic");
}
for (sc = ioapics; sc != NULL; sc = sc->sc_next)
if (sc->sc_pic.pic_apicid == apicid)
return sc;
return NULL;
}
struct ioapic_softc *
ioapic_find_bybase(int vec)
{
struct ioapic_softc *sc;
for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
if (vec >= sc->sc_pic.pic_vecbase &&
vec < (sc->sc_pic.pic_vecbase + sc->sc_apic_sz))
return sc;
}
return NULL;
}
static inline void
ioapic_add(struct ioapic_softc *sc)
{
struct ioapic_softc **scp;
sc->sc_next = NULL;
for (scp = &ioapics; *scp != NULL; scp = &(*scp)->sc_next)
;
*scp = sc;
nioapics++;
}
void
ioapic_print_redir(struct ioapic_softc *sc, const char *why, int pin)
{
uint32_t redirlo = ioapic_read(sc, IOAPIC_REDLO(pin));
uint32_t redirhi = ioapic_read(sc, IOAPIC_REDHI(pin));
apic_format_redir(device_xname(sc->sc_dev), why, pin,
APIC_VECTYPE_IOAPIC, redirhi, redirlo);
}
CFATTACH_DECL_NEW(ioapic, sizeof(struct ioapic_softc),
ioapic_match, ioapic_attach, NULL, NULL);
int
ioapic_match(device_t parent, cfdata_t match, void *aux)
{
return 1;
}
void
ioapic_attach(device_t parent, device_t self, void *aux)
{
struct ioapic_softc *sc = device_private(self);
struct apic_attach_args *aaa = (struct apic_attach_args *)aux;
int apic_id;
uint32_t ver_sz;
int i;
sc->sc_dev = self;
sc->sc_flags = aaa->flags;
sc->sc_pic.pic_apicid = aaa->apic_id;
sc->sc_pic.pic_name = device_xname(self);
sc->sc_pic.pic_ioapic = sc;
aprint_naive("\n");
if (ioapic_find(aaa->apic_id) != NULL) {
aprint_error(": duplicate apic id (ignored)\n");
return;
}
aprint_verbose(": pa 0x%jx", (uintmax_t)aaa->apic_address);
#ifndef _IOAPIC_CUSTOM_RW
{
bus_space_handle_t bh;
if (x86_mem_add_mapping(aaa->apic_address, PAGE_SIZE, 0, &bh) != 0) {
aprint_error(": map failed\n");
return;
}
sc->sc_reg = (volatile uint32_t *)(bh + IOAPIC_REG);
sc->sc_data = (volatile uint32_t *)(bh + IOAPIC_DATA);
}
#endif
sc->sc_pa = aaa->apic_address;
sc->sc_pic.pic_type = PIC_IOAPIC;
__cpu_simple_lock_init(&sc->sc_pic.pic_lock);
sc->sc_pic.pic_hwmask = ioapic_hwmask;
sc->sc_pic.pic_hwunmask = ioapic_hwunmask;
sc->sc_pic.pic_addroute = ioapic_addroute;
sc->sc_pic.pic_delroute = ioapic_delroute;
sc->sc_pic.pic_trymask = ioapic_trymask;
sc->sc_pic.pic_edge_stubs = ioapic_edge_stubs;
sc->sc_pic.pic_level_stubs = ioapic_level_stubs;
sc->sc_pic.pic_intr_get_devname = x86_intr_get_devname;
sc->sc_pic.pic_intr_get_assigned = x86_intr_get_assigned;
sc->sc_pic.pic_intr_get_count = x86_intr_get_count;
apic_id = (ioapic_read(sc, IOAPIC_ID) & IOAPIC_ID_MASK)
>> IOAPIC_ID_SHIFT;
ver_sz = ioapic_read(sc, IOAPIC_VER);
if (ver_sz == 0xffffffff) {
aprint_error(": failed to read version/size\n");
goto out;
}
ioapic_add(sc);
sc->sc_apic_vers = (ver_sz & IOAPIC_VER_MASK) >> IOAPIC_VER_SHIFT;
sc->sc_apic_sz = (ver_sz & IOAPIC_MAX_MASK) >> IOAPIC_MAX_SHIFT;
sc->sc_apic_sz++;
if (aaa->apic_vecbase != -1)
sc->sc_pic.pic_vecbase = aaa->apic_vecbase;
else {
sc->sc_pic.pic_vecbase = ioapic_vecbase;
ioapic_vecbase += sc->sc_apic_sz;
}
if (mp_verbose) {
printf(", %s mode",
aaa->flags & IOAPIC_PICMODE ? "PIC" : "virtual wire");
}
aprint_verbose(", version 0x%x, %d pins", sc->sc_apic_vers,
sc->sc_apic_sz);
aprint_normal("\n");
sc->sc_pins = malloc(sizeof(struct ioapic_pin) * sc->sc_apic_sz,
M_DEVBUF, M_WAITOK);
for (i = 0; i < sc->sc_apic_sz; i++) {
uint32_t redlo, redhi;
sc->sc_pins[i].ip_next = NULL;
sc->sc_pins[i].ip_map = NULL;
sc->sc_pins[i].ip_vector = 0;
sc->sc_pins[i].ip_type = IST_NONE;
redlo = IOAPIC_REDLO_MASK;
if (i >= 16)
redlo |= IOAPIC_REDLO_LEVEL | IOAPIC_REDLO_ACTLO;
redhi = (cpu_info_primary.ci_cpuid << IOAPIC_REDHI_DEST_SHIFT);
ioapic_write(sc, IOAPIC_REDHI(i), redhi);
ioapic_write(sc, IOAPIC_REDLO(i), redlo);
}
if (apic_id != sc->sc_pic.pic_apicid) {
aprint_debug_dev(sc->sc_dev,
"apid is misconfigured (%d != %d)\n",
apic_id, sc->sc_pic.pic_apicid);
ioapic_write(sc, IOAPIC_ID,
(ioapic_read(sc, IOAPIC_ID) & ~IOAPIC_ID_MASK)
| (sc->sc_pic.pic_apicid << IOAPIC_ID_SHIFT));
apic_id = (ioapic_read(sc, IOAPIC_ID) & IOAPIC_ID_MASK)
>> IOAPIC_ID_SHIFT;
if (apic_id != sc->sc_pic.pic_apicid)
aprint_error_dev(sc->sc_dev,
"can't remap apid from %d to %d\n",
apic_id, sc->sc_pic.pic_apicid);
else
aprint_debug_dev(sc->sc_dev, "remapped to apic %d\n",
sc->sc_pic.pic_apicid);
}
out:
if (!pmf_device_register(self, NULL, NULL))
aprint_error_dev(self, "couldn't establish power handler\n");
#if 0
if (mp_verbose)
for (i = 0; i < sc->sc_apic_sz; i++)
ioapic_print_redir(sc, "boot", i);
#endif
}
static void
apic_set_redir(struct ioapic_softc *sc, int pin, int idt_vec,
struct cpu_info *ci)
{
uint32_t redlo;
uint32_t redhi;
int delmode;
struct ioapic_pin *pp;
struct mp_intr_map *map;
pp = &sc->sc_pins[pin];
map = pp->ip_map;
redlo = map == NULL ? IOAPIC_REDLO_MASK : map->redir;
redhi = 0;
delmode = (redlo & IOAPIC_REDLO_DEL_MASK) >> IOAPIC_REDLO_DEL_SHIFT;
if (delmode == IOAPIC_REDLO_DEL_FIXED ||
delmode == IOAPIC_REDLO_DEL_LOPRI) {
if (pp->ip_type == IST_NONE) {
redlo |= IOAPIC_REDLO_MASK;
} else {
redhi = (ci->ci_cpuid << IOAPIC_REDHI_DEST_SHIFT);
redlo |= (idt_vec & 0xff);
redlo |= IOAPIC_REDLO_DEL_FIXED
<< IOAPIC_REDLO_DEL_SHIFT;
redlo &= ~IOAPIC_REDLO_DSTMOD;
if (pp->ip_type == IST_LEVEL)
redlo |= IOAPIC_REDLO_LEVEL;
else
redlo &= ~IOAPIC_REDLO_LEVEL;
if ((map != NULL)
&& ((map->flags & 3) == MPS_INTPO_DEF)) {
if (pp->ip_type == IST_LEVEL)
redlo |= IOAPIC_REDLO_ACTLO;
else
redlo &= ~IOAPIC_REDLO_ACTLO;
}
}
}
ioapic_write(sc, IOAPIC_REDHI(pin), redhi);
ioapic_write(sc, IOAPIC_REDLO(pin), redlo);
if (mp_verbose)
ioapic_print_redir(sc, "int", pin);
}
void
ioapic_enable(void)
{
if (ioapics == NULL)
return;
i8259_setmask(0xffff);
if (ioapics->sc_flags & IOAPIC_PICMODE) {
aprint_debug_dev(ioapics->sc_dev,
"writing to IMCR to disable pics\n");
outb(IMCR_ADDR, IMCR_REGISTER);
outb(IMCR_DATA, IMCR_APIC);
}
}
void
ioapic_reenable(void)
{
int p, apic_id;
struct ioapic_softc *sc;
if (ioapics == NULL)
return;
aprint_normal("%s reenabling\n", device_xname(ioapics->sc_dev));
for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
apic_id = (ioapic_read(sc, IOAPIC_ID) & IOAPIC_ID_MASK)
>> IOAPIC_ID_SHIFT;
if (apic_id != sc->sc_pic.pic_apicid) {
ioapic_write(sc, IOAPIC_ID,
(ioapic_read(sc, IOAPIC_ID) & ~IOAPIC_ID_MASK)
| (sc->sc_pic.pic_apicid << IOAPIC_ID_SHIFT));
}
for (p = 0; p < sc->sc_apic_sz; p++)
apic_set_redir(sc, p, sc->sc_pins[p].ip_vector,
sc->sc_pins[p].ip_cpu);
}
ioapic_enable();
}
void
ioapic_hwmask(struct pic *pic, int pin)
{
uint32_t redlo;
struct ioapic_softc *sc = pic->pic_ioapic;
u_long flags;
flags = ioapic_lock(sc);
redlo = ioapic_read_ul(sc, IOAPIC_REDLO(pin));
redlo |= IOAPIC_REDLO_MASK;
redlo &= ~IOAPIC_REDLO_RIRR;
ioapic_write_ul(sc, IOAPIC_REDLO(pin), redlo);
ioapic_unlock(sc, flags);
}
bool
ioapic_trymask(struct pic *pic, int pin)
{
uint32_t redlo;
struct ioapic_softc *sc = pic->pic_ioapic;
u_long flags;
bool rv;
flags = ioapic_lock(sc);
redlo = ioapic_read_ul(sc, IOAPIC_REDLO(pin));
redlo |= IOAPIC_REDLO_MASK;
ioapic_write_ul(sc, IOAPIC_REDLO(pin), redlo);
redlo = ioapic_read_ul(sc, IOAPIC_REDLO(pin));
if ((redlo & (IOAPIC_REDLO_RIRR | IOAPIC_REDLO_DELSTS)) != 0) {
redlo &= ~IOAPIC_REDLO_MASK;
ioapic_write_ul(sc, IOAPIC_REDLO(pin), redlo);
rv = false;
} else {
rv = true;
}
ioapic_unlock(sc, flags);
return rv;
}
void
ioapic_hwunmask(struct pic *pic, int pin)
{
uint32_t redlo;
struct ioapic_softc *sc = pic->pic_ioapic;
u_long flags;
flags = ioapic_lock(sc);
redlo = ioapic_read_ul(sc, IOAPIC_REDLO(pin));
redlo &= ~(IOAPIC_REDLO_MASK | IOAPIC_REDLO_RIRR);
ioapic_write_ul(sc, IOAPIC_REDLO(pin), redlo);
ioapic_unlock(sc, flags);
}
static void
ioapic_addroute(struct pic *pic, struct cpu_info *ci, int pin,
int idtvec, int type)
{
struct ioapic_softc *sc = pic->pic_ioapic;
struct ioapic_pin *pp;
pp = &sc->sc_pins[pin];
pp->ip_type = type;
pp->ip_vector = idtvec;
pp->ip_cpu = ci;
apic_set_redir(sc, pin, idtvec, ci);
}
static void
ioapic_delroute(struct pic *pic, struct cpu_info *ci, int pin,
int idtvec, int type)
{
ioapic_hwmask(pic, pin);
}
#ifdef DDB
void ioapic_dump(void);
void ioapic_dump_raw(void);
void
ioapic_dump(void)
{
struct ioapic_softc *sc;
struct ioapic_pin *ip;
int p;
for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
for (p = 0; p < sc->sc_apic_sz; p++) {
ip = &sc->sc_pins[p];
if (ip->ip_type != IST_NONE)
ioapic_print_redir(sc, "dump", p);
}
}
}
void
ioapic_dump_raw(void)
{
struct ioapic_softc *sc;
int i;
uint32_t reg;
for (sc = ioapics; sc != NULL; sc = sc->sc_next) {
printf("Register dump of %s\n", device_xname(sc->sc_dev));
i = 0;
do {
if (i % 0x08 == 0)
printf("%02x", i);
reg = ioapic_read(sc, i);
printf(" %08x", (u_int)reg);
if (++i % 0x08 == 0)
printf("\n");
} while (i < IOAPIC_REDTBL + (sc->sc_apic_sz * 2));
}
}
#endif