#include "opt_cpu.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/memrange.h>
#include <sys/cons.h>
#include <sys/machintr.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include <vm/vm_kern.h>
#include <vm/vm_extern.h>
#include <sys/lock.h>
#include <vm/vm_map.h>
#include <machine/smp.h>
#include <machine_base/isa/isa_intr.h>
#include <machine_base/apic/apicreg.h>
#include <machine_base/apic/apicvar.h>
#include <machine/atomic.h>
#include <machine/cpufunc.h>
#include <machine/cputypes.h>
#include <machine_base/apic/lapic.h>
#include <machine_base/apic/ioapic.h>
#include <machine/psl.h>
#include <machine/segments.h>
#include <machine/tss.h>
#include <machine/specialreg.h>
#include <machine/globaldata.h>
#include <machine/pmap_inval.h>
#include <machine/mptable.h>
#include <machine/md_var.h>
#include <machine_base/icu/icu.h>
#include <machine_base/apic/ioapic_abi.h>
#include <machine/intr_machdep.h>
extern u_int base_memory;
extern u_long ebda_addr;
extern int imcr_present;
extern int naps;
static int force_enable = 0;
TUNABLE_INT("hw.lapic_force_enable", &force_enable);
#define BIOS_BASE (0xf0000)
#define BIOS_BASE2 (0xe0000)
#define BIOS_SIZE (0x10000)
#define BIOS_COUNT (BIOS_SIZE/4)
#define PROCENTRY_FLAG_EN 0x01
#define PROCENTRY_FLAG_BP 0x02
#define IOAPICENTRY_FLAG_EN 0x01
typedef struct MPFPS {
char signature[4];
u_int32_t pap;
u_char length;
u_char spec_rev;
u_char checksum;
u_char mpfb1;
u_char mpfb2;
u_char mpfb3;
u_char mpfb4;
u_char mpfb5;
} *mpfps_t;
typedef struct MPCTH {
char signature[4];
u_short base_table_length;
u_char spec_rev;
u_char checksum;
u_char oem_id[8];
u_char product_id[12];
u_int32_t oem_table_pointer;
u_short oem_table_size;
u_short entry_count;
u_int32_t apic_address;
u_short extended_table_length;
u_char extended_table_checksum;
u_char reserved;
} *mpcth_t;
typedef struct PROCENTRY {
u_char type;
u_char apic_id;
u_char apic_version;
u_char cpu_flags;
u_int32_t cpu_signature;
u_int32_t feature_flags;
u_int32_t reserved1;
u_int32_t reserved2;
} *proc_entry_ptr;
typedef struct BUSENTRY {
u_char type;
u_char bus_id;
char bus_type[6];
} *bus_entry_ptr;
typedef struct IOAPICENTRY {
u_char type;
u_char apic_id;
u_char apic_version;
u_char apic_flags;
u_int32_t apic_address;
} *io_apic_entry_ptr;
typedef struct INTENTRY {
u_char type;
u_char int_type;
u_short int_flags;
u_char src_bus_id;
u_char src_bus_irq;
u_char dst_apic_id;
u_char dst_apic_int;
} *int_entry_ptr;
typedef struct BASETABLE_ENTRY {
u_char type;
u_char length;
char name[16];
} basetable_entry;
struct mptable_pos {
mpfps_t mp_fps;
mpcth_t mp_cth;
vm_size_t mp_cth_mapsz;
};
#define MPTABLE_POS_USE_DEFAULT(mpt) \
((mpt)->mp_fps->mpfb1 != 0 || (mpt)->mp_cth == NULL)
struct mptable_bus {
int mb_id;
int mb_type;
TAILQ_ENTRY(mptable_bus) mb_link;
};
#define MPTABLE_BUS_ISA 0
#define MPTABLE_BUS_PCI 1
struct mptable_bus_info {
TAILQ_HEAD(, mptable_bus) mbi_list;
};
struct mptable_pci_int {
int mpci_bus;
int mpci_dev;
int mpci_pin;
int mpci_ioapic_idx;
int mpci_ioapic_pin;
TAILQ_ENTRY(mptable_pci_int) mpci_link;
};
struct mptable_ioapic {
int mio_idx;
int mio_apic_id;
uint32_t mio_addr;
int mio_gsi_base;
int mio_npin;
TAILQ_ENTRY(mptable_ioapic) mio_link;
};
typedef int (*mptable_iter_func)(void *, const void *, int);
static int mptable_iterate_entries(const mpcth_t,
mptable_iter_func, void *);
static int mptable_search(void);
static long mptable_search_sig(u_int32_t target, int count);
static int mptable_hyperthread_fixup(cpumask_t, int);
static int mptable_map(struct mptable_pos *);
static void mptable_unmap(struct mptable_pos *);
static void mptable_bus_info_alloc(const mpcth_t,
struct mptable_bus_info *);
static void mptable_bus_info_free(struct mptable_bus_info *);
static int mptable_lapic_probe(struct lapic_enumerator *);
static int mptable_lapic_enumerate(struct lapic_enumerator *);
static void mptable_lapic_default(void);
static int mptable_ioapic_probe(struct ioapic_enumerator *);
static void mptable_ioapic_enumerate(struct ioapic_enumerator *);
static basetable_entry basetable_entry_types[] =
{
{0, 20, "Processor"},
{1, 8, "Bus"},
{2, 8, "I/O APIC"},
{3, 8, "I/O INT"},
{4, 8, "Local INT"}
};
static vm_paddr_t mptable_fps_phyaddr;
static int mptable_use_default;
static TAILQ_HEAD(mptable_pci_int_list, mptable_pci_int) mptable_pci_int_list =
TAILQ_HEAD_INITIALIZER(mptable_pci_int_list);
static TAILQ_HEAD(mptable_ioapic_list, mptable_ioapic) mptable_ioapic_list =
TAILQ_HEAD_INITIALIZER(mptable_ioapic_list);
static void
mptable_probe(void)
{
struct mptable_pos mpt;
int error;
KKASSERT(mptable_fps_phyaddr == 0);
mptable_fps_phyaddr = mptable_search();
if (mptable_fps_phyaddr == 0)
return;
error = mptable_map(&mpt);
if (error) {
mptable_fps_phyaddr = 0;
return;
}
if (MPTABLE_POS_USE_DEFAULT(&mpt)) {
kprintf("MPTABLE: use default configuration\n");
mptable_use_default = 1;
}
if (mpt.mp_fps->mpfb2 & 0x80)
imcr_present = 1;
mptable_unmap(&mpt);
}
SYSINIT(mptable_probe, SI_BOOT2_PRESMP, SI_ORDER_FIRST, mptable_probe, 0);
static int
mptable_search(void)
{
long x;
u_int32_t target;
if (ebda_addr != 0) {
target = (u_int32_t)ebda_addr;
if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
return x;
} else {
target = (u_int32_t)((base_memory - 1) * 1024);
if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
return x;
}
target = (u_int32_t)BIOS_BASE;
if ((x = mptable_search_sig(target, BIOS_COUNT)) > 0)
return x;
target = (u_int32_t)BIOS_BASE2;
if ((x = mptable_search_sig(target, BIOS_COUNT)) > 0)
return x;
return 0;
}
static int
mptable_iterate_entries(const mpcth_t cth, mptable_iter_func func, void *arg)
{
int count, total_size;
const void *position;
KKASSERT(cth->base_table_length >= sizeof(struct MPCTH));
total_size = cth->base_table_length - sizeof(struct MPCTH);
position = (const uint8_t *)cth + sizeof(struct MPCTH);
count = cth->entry_count;
while (count--) {
int type, error;
KKASSERT(total_size >= 0);
if (total_size == 0) {
kprintf("invalid base MP table, "
"entry count and length mismatch\n");
return EINVAL;
}
type = *(const uint8_t *)position;
switch (type) {
case 0:
case 1:
case 2:
case 3:
case 4:
break;
default:
kprintf("unknown base MP table entry type %d\n", type);
return EINVAL;
}
if (total_size < basetable_entry_types[type].length) {
kprintf("invalid base MP table length, "
"does not contain all entries\n");
return EINVAL;
}
total_size -= basetable_entry_types[type].length;
error = func(arg, position, type);
if (error)
return error;
position = (const uint8_t *)position +
basetable_entry_types[type].length;
}
return 0;
}
#define MP_SIG 0x5f504d5f
#define NEXT(X) ((X) += 4)
static long
mptable_search_sig(u_int32_t target, int count)
{
vm_size_t map_size;
u_int32_t *addr;
int x, ret;
KKASSERT(target != 0);
map_size = count * sizeof(u_int32_t);
addr = pmap_mapdev((vm_paddr_t)target, map_size);
ret = 0;
for (x = 0; x < count; NEXT(x)) {
if (addr[x] == MP_SIG) {
ret = target + (x * sizeof(u_int32_t));
break;
}
}
pmap_unmapdev((vm_offset_t)addr, map_size);
return ret;
}
static int processor_entry (const struct PROCENTRY *entry, int cpu);
static int
mptable_hyperthread_fixup(cpumask_t id_mask, int cpu_count)
{
int i, id, lcpus_max, logical_cpus;
if ((cpu_feature & CPUID_HTT) == 0)
return 0;
lcpus_max = (cpu_procinfo & CPUID_HTT_CORES) >> 16;
if (lcpus_max <= 1)
return 0;
if (cpu_vendor_id == CPU_VENDOR_INTEL) {
for (i = 0; ; ++i) {
if ((1 << i) >= lcpus_max) {
lcpus_max = 1 << i;
break;
}
}
}
KKASSERT(cpu_count != 0);
if (cpu_count == lcpus_max) {
return 0;
} else if (cpu_count == 1) {
logical_cpus = lcpus_max;
} else {
int cur, prev, dist;
dist = cur = prev = -1;
for (id = 0; id < MAXCPU; ++id) {
if (CPUMASK_TESTBIT(id_mask, id) == 0)
continue;
cur = id;
if (prev >= 0) {
int new_dist = cur - prev;
if (dist < 0)
dist = new_dist;
if (dist != new_dist)
return 0;
}
prev = cur;
}
if (dist == 1)
return 0;
if (dist & (dist - 1))
return 0;
if (dist > lcpus_max)
logical_cpus = lcpus_max;
else
logical_cpus = dist;
}
for (id = 0; id < MAXCPU; id++) {
if (CPUMASK_TESTBIT(id_mask, id) == 0)
continue;
if (id % logical_cpus != 0)
return 0;
for (i = id + 1; i < id + logical_cpus; i++)
if (CPUMASK_TESTBIT(id_mask, i) != 0)
return 0;
}
return logical_cpus;
}
static int
mptable_map(struct mptable_pos *mpt)
{
mpfps_t fps = NULL;
mpcth_t cth = NULL;
vm_size_t cth_mapsz = 0;
KKASSERT(mptable_fps_phyaddr != 0);
bzero(mpt, sizeof(*mpt));
fps = pmap_mapdev(mptable_fps_phyaddr, sizeof(*fps));
if (fps->pap != 0) {
cth = pmap_mapdev(fps->pap, sizeof(*cth));
cth_mapsz = cth->base_table_length;
pmap_unmapdev((vm_offset_t)cth, sizeof(*cth));
if (cth_mapsz < sizeof(*cth)) {
kprintf("invalid base MP table length %d\n",
(int)cth_mapsz);
pmap_unmapdev((vm_offset_t)fps, sizeof(*fps));
return EINVAL;
}
cth = pmap_mapdev(fps->pap, cth_mapsz);
}
mpt->mp_fps = fps;
mpt->mp_cth = cth;
mpt->mp_cth_mapsz = cth_mapsz;
return 0;
}
static void
mptable_unmap(struct mptable_pos *mpt)
{
if (mpt->mp_cth != NULL) {
pmap_unmapdev((vm_offset_t)mpt->mp_cth, mpt->mp_cth_mapsz);
mpt->mp_cth = NULL;
mpt->mp_cth_mapsz = 0;
}
if (mpt->mp_fps != NULL) {
pmap_unmapdev((vm_offset_t)mpt->mp_fps, sizeof(*mpt->mp_fps));
mpt->mp_fps = NULL;
}
}
static int
processor_entry(const struct PROCENTRY *entry, int cpu)
{
KKASSERT(cpu > 0);
if (!(entry->cpu_flags & PROCENTRY_FLAG_EN))
return 0;
if (entry->cpu_flags & PROCENTRY_FLAG_BP) {
lapic_set_cpuid(0, entry->apic_id);
return 0;
}
else if (cpu < MAXCPU) {
lapic_set_cpuid(cpu, entry->apic_id);
return 1;
}
return 0;
}
static int
mptable_bus_info_callback(void *xarg, const void *pos, int type)
{
struct mptable_bus_info *bus_info = xarg;
const struct BUSENTRY *ent;
struct mptable_bus *bus;
if (type != 1)
return 0;
ent = pos;
TAILQ_FOREACH(bus, &bus_info->mbi_list, mb_link) {
if (bus->mb_id == ent->bus_id) {
kprintf("mptable_bus_info_alloc: duplicated bus id "
"(%d)\n", bus->mb_id);
return EINVAL;
}
}
bus = NULL;
if (strncmp(ent->bus_type, "PCI", 3) == 0) {
bus = kmalloc(sizeof(*bus), M_TEMP, M_WAITOK | M_ZERO);
bus->mb_type = MPTABLE_BUS_PCI;
} else if (strncmp(ent->bus_type, "ISA", 3) == 0) {
bus = kmalloc(sizeof(*bus), M_TEMP, M_WAITOK | M_ZERO);
bus->mb_type = MPTABLE_BUS_ISA;
}
if (bus != NULL) {
bus->mb_id = ent->bus_id;
TAILQ_INSERT_TAIL(&bus_info->mbi_list, bus, mb_link);
}
return 0;
}
static void
mptable_bus_info_alloc(const mpcth_t cth, struct mptable_bus_info *bus_info)
{
int error;
bzero(bus_info, sizeof(*bus_info));
TAILQ_INIT(&bus_info->mbi_list);
error = mptable_iterate_entries(cth, mptable_bus_info_callback, bus_info);
if (error)
mptable_bus_info_free(bus_info);
}
static void
mptable_bus_info_free(struct mptable_bus_info *bus_info)
{
struct mptable_bus *bus;
while ((bus = TAILQ_FIRST(&bus_info->mbi_list)) != NULL) {
TAILQ_REMOVE(&bus_info->mbi_list, bus, mb_link);
kfree(bus, M_TEMP);
}
}
struct mptable_lapic_cbarg1 {
int cpu_count;
int ht_fixup;
u_int ht_apicid_mask;
};
static int
mptable_lapic_pass1_callback(void *xarg, const void *pos, int type)
{
const struct PROCENTRY *ent;
struct mptable_lapic_cbarg1 *arg = xarg;
if (type != 0)
return 0;
ent = pos;
if ((ent->cpu_flags & PROCENTRY_FLAG_EN) == 0)
return 0;
arg->cpu_count++;
if (ent->apic_id < 64) {
arg->ht_apicid_mask |= 1UL << ent->apic_id;
} else if (arg->ht_fixup) {
kprintf("MPTABLE: lapic id > 64, disable HTT fixup\n");
arg->ht_fixup = 0;
}
return 0;
}
struct mptable_lapic_cbarg2 {
int cpu;
int logical_cpus;
int found_bsp;
};
static int
mptable_lapic_pass2_callback(void *xarg, const void *pos, int type)
{
const struct PROCENTRY *ent;
struct mptable_lapic_cbarg2 *arg = xarg;
if (type != 0)
return 0;
ent = pos;
if (ent->cpu_flags & PROCENTRY_FLAG_BP) {
KKASSERT(!arg->found_bsp);
arg->found_bsp = 1;
}
if (processor_entry(ent, arg->cpu))
arg->cpu++;
if (arg->logical_cpus) {
struct PROCENTRY proc;
int i;
bzero(&proc, sizeof(proc));
proc.type = 0;
proc.cpu_flags = (force_enable) ? PROCENTRY_FLAG_EN : ent->cpu_flags;
proc.apic_id = ent->apic_id;
for (i = 1; i < arg->logical_cpus; i++) {
proc.apic_id++;
processor_entry(&proc, arg->cpu);
arg->cpu++;
}
}
return 0;
}
static void
mptable_lapic_default(void)
{
int ap_apicid, bsp_apicid;
naps = 1;
lapic_map(DEFAULT_APIC_BASE);
bsp_apicid = LAPIC_READID;
ap_apicid = (bsp_apicid == 0) ? 1 : 0;
lapic_set_cpuid(0, bsp_apicid);
lapic_set_cpuid(1, ap_apicid);
}
static int
mptable_lapic_enumerate(struct lapic_enumerator *e)
{
struct mptable_pos mpt;
struct mptable_lapic_cbarg1 arg1;
struct mptable_lapic_cbarg2 arg2;
mpcth_t cth;
int error, logical_cpus = 0;
vm_paddr_t lapic_addr;
if (mptable_use_default) {
mptable_lapic_default();
return 0;
}
error = mptable_map(&mpt);
if (error)
panic("mptable_lapic_enumerate mptable_map failed");
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
cth = mpt.mp_cth;
lapic_addr = cth->apic_address;
KKASSERT(lapic_addr != 0);
bzero(&arg1, sizeof(arg1));
arg1.ht_fixup = 1;
error = mptable_iterate_entries(cth,
mptable_lapic_pass1_callback, &arg1);
if (error)
panic("mptable_iterate_entries(lapic_pass1) failed");
KKASSERT(arg1.cpu_count != 0);
if (arg1.ht_fixup) {
cpumask_t mask;
CPUMASK_ASSZERO(mask);
mask.ary[0] = arg1.ht_apicid_mask;
logical_cpus = mptable_hyperthread_fixup(mask, arg1.cpu_count);
if (logical_cpus != 0)
arg1.cpu_count *= logical_cpus;
}
naps = arg1.cpu_count - 1;
bzero(&arg2, sizeof(arg2));
arg2.cpu = 1;
arg2.logical_cpus = logical_cpus;
error = mptable_iterate_entries(cth,
mptable_lapic_pass2_callback, &arg2);
if (error)
panic("mptable_iterate_entries(lapic_pass2) failed");
KKASSERT(arg2.found_bsp);
lapic_map(lapic_addr);
mptable_unmap(&mpt);
return 0;
}
struct mptable_lapic_probe_cbarg {
int cpu_count;
int found_bsp;
};
static int
mptable_lapic_probe_callback(void *xarg, const void *pos, int type)
{
const struct PROCENTRY *ent;
struct mptable_lapic_probe_cbarg *arg = xarg;
if (type != 0)
return 0;
ent = pos;
if ((ent->cpu_flags & PROCENTRY_FLAG_EN) == 0)
return 0;
arg->cpu_count++;
if (ent->apic_id == APICID_MAX) {
kprintf("MPTABLE: invalid LAPIC apic id %d\n",
ent->apic_id);
return EINVAL;
}
if (ent->cpu_flags & PROCENTRY_FLAG_BP) {
if (arg->found_bsp) {
kprintf("more than one BSP in base MP table\n");
return EINVAL;
}
arg->found_bsp = 1;
}
return 0;
}
static int
mptable_lapic_probe(struct lapic_enumerator *e)
{
struct mptable_pos mpt;
struct mptable_lapic_probe_cbarg arg;
mpcth_t cth;
int error;
if (mptable_fps_phyaddr == 0)
return ENXIO;
if (mptable_use_default)
return 0;
error = mptable_map(&mpt);
if (error)
return error;
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
error = EINVAL;
cth = mpt.mp_cth;
if (cth->apic_address == 0)
goto done;
bzero(&arg, sizeof(arg));
error = mptable_iterate_entries(cth,
mptable_lapic_probe_callback, &arg);
if (!error) {
if (arg.cpu_count == 0) {
kprintf("MP table contains no processor entries\n");
error = EINVAL;
} else if (!arg.found_bsp) {
kprintf("MP table does not contains BSP entry\n");
error = EINVAL;
}
}
done:
mptable_unmap(&mpt);
return error;
}
static struct lapic_enumerator mptable_lapic_enumerator = {
.lapic_prio = LAPIC_ENUM_PRIO_MPTABLE,
.lapic_probe = mptable_lapic_probe,
.lapic_enumerate = mptable_lapic_enumerate
};
static void
mptable_lapic_enum_register(void)
{
lapic_enumerator_register(&mptable_lapic_enumerator);
}
SYSINIT(mptable_lapic, SI_BOOT2_PRESMP, SI_ORDER_ANY,
mptable_lapic_enum_register, 0);
static int
mptable_ioapic_list_callback(void *xarg, const void *pos, int type)
{
const struct IOAPICENTRY *ent;
struct mptable_ioapic *nioapic, *ioapic;
if (type != 2)
return 0;
ent = pos;
if ((ent->apic_flags & IOAPICENTRY_FLAG_EN) == 0)
return 0;
if (ent->apic_address == 0) {
kprintf("mptable_ioapic_create_list: zero IOAPIC addr\n");
return EINVAL;
}
if (ent->apic_id == APICID_MAX) {
kprintf("mptable_ioapic_create_list: "
"invalid IOAPIC apic id %d\n", ent->apic_id);
return EINVAL;
}
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
if (ioapic->mio_apic_id == ent->apic_id) {
kprintf("mptable_ioapic_create_list: duplicated "
"apic id %d\n", ioapic->mio_apic_id);
return EINVAL;
}
if (ioapic->mio_addr == ent->apic_address) {
kprintf("mptable_ioapic_create_list: overlapped "
"IOAPIC addr 0x%08x", ioapic->mio_addr);
return EINVAL;
}
}
nioapic = kmalloc(sizeof(*nioapic), M_DEVBUF, M_WAITOK | M_ZERO);
nioapic->mio_apic_id = ent->apic_id;
nioapic->mio_addr = ent->apic_address;
TAILQ_FOREACH_REVERSE(ioapic, &mptable_ioapic_list,
mptable_ioapic_list, mio_link) {
if (nioapic->mio_apic_id > ioapic->mio_apic_id) {
TAILQ_INSERT_AFTER(&mptable_ioapic_list,
ioapic, nioapic, mio_link);
break;
}
}
if (ioapic == NULL)
TAILQ_INSERT_HEAD(&mptable_ioapic_list, nioapic, mio_link);
return 0;
}
static void
mptable_ioapic_create_list(void)
{
struct mptable_ioapic *ioapic;
struct mptable_pos mpt;
int idx, error;
if (mptable_fps_phyaddr == 0)
return;
if (mptable_use_default) {
ioapic = kmalloc(sizeof(*ioapic), M_DEVBUF, M_WAITOK | M_ZERO);
ioapic->mio_idx = 0;
ioapic->mio_apic_id = 0;
ioapic->mio_addr = 0xfec00000;
TAILQ_INSERT_HEAD(&mptable_ioapic_list, ioapic, mio_link);
return;
}
error = mptable_map(&mpt);
if (error)
panic("mptable_ioapic_create_list: mptable_map failed");
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
error = mptable_iterate_entries(mpt.mp_cth,
mptable_ioapic_list_callback, NULL);
if (error) {
while ((ioapic = TAILQ_FIRST(&mptable_ioapic_list)) != NULL) {
TAILQ_REMOVE(&mptable_ioapic_list, ioapic, mio_link);
kfree(ioapic, M_DEVBUF);
}
goto done;
}
idx = 0;
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
ioapic->mio_idx = idx;
++idx;
}
done:
mptable_unmap(&mpt);
}
SYSINIT(mptable_ioapic_list, SI_BOOT2_PRESMP, SI_ORDER_SECOND,
mptable_ioapic_create_list, 0);
static int
mptable_pci_int_callback(void *xarg, const void *pos, int type)
{
const struct mptable_bus_info *bus_info = xarg;
const struct mptable_ioapic *ioapic;
const struct mptable_bus *bus;
struct mptable_pci_int *pci_int;
const struct INTENTRY *ent;
int pci_pin, pci_dev;
if (type != 3)
return 0;
ent = pos;
if (ent->int_type != 0)
return 0;
TAILQ_FOREACH(bus, &bus_info->mbi_list, mb_link) {
if (bus->mb_type == MPTABLE_BUS_PCI &&
bus->mb_id == ent->src_bus_id)
break;
}
if (bus == NULL)
return 0;
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
if (ioapic->mio_apic_id == ent->dst_apic_id)
break;
}
if (ioapic == NULL) {
if (bootverbose) {
static char intdis_warned[64];
int apic_id = ent->dst_apic_id;
int warn = 0;
if (apic_id < 0 || apic_id >= sizeof(intdis_warned)) {
warn = 1;
} else if (intdis_warned[apic_id] == 0) {
intdis_warned[apic_id] = 1;
warn = 1;
}
if (warn) {
kprintf("MPTABLE: warning PCI int dst apic id "
"%d does not exist\n", apic_id);
}
}
return 0;
}
pci_pin = ent->src_bus_irq & 0x3;
pci_dev = (ent->src_bus_irq >> 2) & 0x1f;
TAILQ_FOREACH(pci_int, &mptable_pci_int_list, mpci_link) {
if (pci_int->mpci_bus == ent->src_bus_id &&
pci_int->mpci_dev == pci_dev &&
pci_int->mpci_pin == pci_pin) {
if (pci_int->mpci_ioapic_idx == ioapic->mio_idx &&
pci_int->mpci_ioapic_pin == ent->dst_apic_int) {
kprintf("MPTABLE: warning duplicated "
"PCI int entry for "
"bus %d, dev %d, pin %d\n",
pci_int->mpci_bus,
pci_int->mpci_dev,
pci_int->mpci_pin);
return 0;
} else {
kprintf("mptable_pci_int_register: "
"conflict PCI int entry for "
"bus %d, dev %d, pin %d, "
"IOAPIC %d.%d -> %d.%d\n",
pci_int->mpci_bus,
pci_int->mpci_dev,
pci_int->mpci_pin,
pci_int->mpci_ioapic_idx,
pci_int->mpci_ioapic_pin,
ioapic->mio_idx,
ent->dst_apic_int);
return EINVAL;
}
}
}
pci_int = kmalloc(sizeof(*pci_int), M_DEVBUF, M_WAITOK | M_ZERO);
pci_int->mpci_bus = ent->src_bus_id;
pci_int->mpci_dev = pci_dev;
pci_int->mpci_pin = pci_pin;
pci_int->mpci_ioapic_idx = ioapic->mio_idx;
pci_int->mpci_ioapic_pin = ent->dst_apic_int;
TAILQ_INSERT_TAIL(&mptable_pci_int_list, pci_int, mpci_link);
return 0;
}
static void
mptable_pci_int_register(void)
{
struct mptable_bus_info bus_info;
const struct mptable_bus *bus;
struct mptable_pci_int *pci_int;
struct mptable_pos mpt;
int error, force_pci0, npcibus;
mpcth_t cth;
if (mptable_fps_phyaddr == 0)
return;
if (mptable_use_default)
return;
if (TAILQ_EMPTY(&mptable_ioapic_list))
return;
error = mptable_map(&mpt);
if (error)
panic("mptable_pci_int_register: mptable_map failed");
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
cth = mpt.mp_cth;
mptable_bus_info_alloc(cth, &bus_info);
if (TAILQ_EMPTY(&bus_info.mbi_list))
goto done;
force_pci0 = 0;
npcibus = 0;
TAILQ_FOREACH(bus, &bus_info.mbi_list, mb_link) {
if (bus->mb_type == MPTABLE_BUS_PCI)
++npcibus;
}
if (npcibus == 0) {
mptable_bus_info_free(&bus_info);
goto done;
} else if (npcibus == 1) {
force_pci0 = 1;
}
error = mptable_iterate_entries(cth,
mptable_pci_int_callback, &bus_info);
mptable_bus_info_free(&bus_info);
if (error) {
while ((pci_int = TAILQ_FIRST(&mptable_pci_int_list)) != NULL) {
TAILQ_REMOVE(&mptable_pci_int_list, pci_int, mpci_link);
kfree(pci_int, M_DEVBUF);
}
goto done;
}
if (force_pci0) {
TAILQ_FOREACH(pci_int, &mptable_pci_int_list, mpci_link)
pci_int->mpci_bus = 0;
}
done:
mptable_unmap(&mpt);
}
SYSINIT(mptable_pci, SI_BOOT2_PRESMP, SI_ORDER_ANY,
mptable_pci_int_register, 0);
struct mptable_ioapic_probe_cbarg {
const struct mptable_bus_info *bus_info;
};
static int
mptable_ioapic_probe_callback(void *xarg, const void *pos, int type)
{
struct mptable_ioapic_probe_cbarg *arg = xarg;
const struct mptable_ioapic *ioapic;
const struct mptable_bus *bus;
const struct INTENTRY *ent;
if (type != 3)
return 0;
ent = pos;
if (ent->int_type != 0)
return 0;
TAILQ_FOREACH(bus, &arg->bus_info->mbi_list, mb_link) {
if (bus->mb_type == MPTABLE_BUS_ISA &&
bus->mb_id == ent->src_bus_id)
break;
}
if (bus == NULL)
return 0;
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
if (ioapic->mio_apic_id == ent->dst_apic_id)
break;
}
if (ioapic == NULL) {
kprintf("MPTABLE: warning ISA int dst apic id %d "
"does not exist\n", ent->dst_apic_id);
return 0;
}
if (ent->src_bus_irq >= ISA_IRQ_CNT) {
kprintf("mptable_ioapic_probe: invalid ISA irq (%d)\n",
ent->src_bus_irq);
return EINVAL;
}
return 0;
}
static int
mptable_ioapic_probe(struct ioapic_enumerator *e)
{
struct mptable_ioapic_probe_cbarg arg;
struct mptable_bus_info bus_info;
struct mptable_pos mpt;
mpcth_t cth;
int error;
if (mptable_fps_phyaddr == 0)
return ENXIO;
if (mptable_use_default)
return 0;
if (TAILQ_EMPTY(&mptable_ioapic_list))
return ENXIO;
error = mptable_map(&mpt);
if (error)
panic("mptable_ioapic_probe: mptable_map failed");
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
cth = mpt.mp_cth;
mptable_bus_info_alloc(cth, &bus_info);
bzero(&arg, sizeof(arg));
arg.bus_info = &bus_info;
error = mptable_iterate_entries(cth,
mptable_ioapic_probe_callback, &arg);
mptable_bus_info_free(&bus_info);
mptable_unmap(&mpt);
return error;
}
struct mptable_ioapic_int_cbarg {
const struct mptable_bus_info *bus_info;
int ioapic_nint;
};
static int
mptable_ioapic_int_callback(void *xarg, const void *pos, int type)
{
struct mptable_ioapic_int_cbarg *arg = xarg;
const struct mptable_ioapic *ioapic;
const struct mptable_bus *bus;
const struct INTENTRY *ent;
int gsi;
if (type != 3)
return 0;
arg->ioapic_nint++;
ent = pos;
if (ent->int_type != 0)
return 0;
TAILQ_FOREACH(bus, &arg->bus_info->mbi_list, mb_link) {
if (bus->mb_type == MPTABLE_BUS_ISA &&
bus->mb_id == ent->src_bus_id)
break;
}
if (bus == NULL)
return 0;
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
if (ioapic->mio_apic_id == ent->dst_apic_id)
break;
}
if (ioapic == NULL) {
kprintf("MPTABLE: warning ISA int dst apic id %d "
"does not exist\n", ent->dst_apic_id);
return 0;
}
if (ent->dst_apic_int >= ioapic->mio_npin) {
panic("mptable_ioapic_enumerate: invalid I/O APIC "
"pin %d, should be < %d",
ent->dst_apic_int, ioapic->mio_npin);
}
gsi = ioapic->mio_gsi_base + ent->dst_apic_int;
if (ent->src_bus_irq != gsi) {
if (bootverbose) {
kprintf("MPTABLE: INTSRC irq %d -> GSI %d\n",
ent->src_bus_irq, gsi);
}
ioapic_intsrc(ent->src_bus_irq, gsi,
INTR_TRIGGER_EDGE, INTR_POLARITY_HIGH);
}
return 0;
}
static void
mptable_ioapic_enumerate(struct ioapic_enumerator *e)
{
struct mptable_bus_info bus_info;
struct mptable_ioapic *ioapic;
struct mptable_pos mpt;
mpcth_t cth;
int error;
KKASSERT(mptable_fps_phyaddr != 0);
KKASSERT(!TAILQ_EMPTY(&mptable_ioapic_list));
TAILQ_FOREACH(ioapic, &mptable_ioapic_list, mio_link) {
const struct mptable_ioapic *prev_ioapic;
uint32_t ver;
void *addr;
addr = ioapic_map(ioapic->mio_addr);
ver = ioapic_read(addr, IOAPIC_VER);
ioapic->mio_npin = ((ver & IOART_VER_MAXREDIR)
>> MAXREDIRSHIFT) + 1;
prev_ioapic = TAILQ_PREV(ioapic,
mptable_ioapic_list, mio_link);
if (prev_ioapic == NULL) {
ioapic->mio_gsi_base = 0;
} else {
ioapic->mio_gsi_base =
prev_ioapic->mio_gsi_base +
prev_ioapic->mio_npin;
}
ioapic_add(addr, ioapic->mio_gsi_base, ioapic->mio_npin);
if (bootverbose) {
kprintf("MPTABLE: IOAPIC addr 0x%08x, "
"apic id %d, idx %d, gsi base %d, npin %d\n",
ioapic->mio_addr,
ioapic->mio_apic_id,
ioapic->mio_idx,
ioapic->mio_gsi_base,
ioapic->mio_npin);
}
}
if (mptable_use_default) {
if (bootverbose)
kprintf("MPTABLE: INTSRC irq 0 -> GSI 2 (default)\n");
ioapic_intsrc(0, 2, INTR_TRIGGER_EDGE, INTR_POLARITY_HIGH);
return;
}
error = mptable_map(&mpt);
if (error)
panic("mptable_ioapic_probe: mptable_map failed");
KKASSERT(!MPTABLE_POS_USE_DEFAULT(&mpt));
cth = mpt.mp_cth;
mptable_bus_info_alloc(cth, &bus_info);
if (TAILQ_EMPTY(&bus_info.mbi_list)) {
if (bootverbose)
kprintf("MPTABLE: INTSRC irq 0 -> GSI 2 (no bus)\n");
ioapic_intsrc(0, 2, INTR_TRIGGER_EDGE, INTR_POLARITY_HIGH);
} else {
struct mptable_ioapic_int_cbarg arg;
bzero(&arg, sizeof(arg));
arg.bus_info = &bus_info;
error = mptable_iterate_entries(cth,
mptable_ioapic_int_callback, &arg);
if (error)
panic("mptable_ioapic_int failed");
if (arg.ioapic_nint == 0) {
if (bootverbose) {
kprintf("MPTABLE: INTSRC irq 0 -> GSI 2 "
"(no int)\n");
}
ioapic_intsrc(0, 2, INTR_TRIGGER_EDGE,
INTR_POLARITY_HIGH);
}
}
mptable_bus_info_free(&bus_info);
mptable_unmap(&mpt);
}
static struct ioapic_enumerator mptable_ioapic_enumerator = {
.ioapic_prio = IOAPIC_ENUM_PRIO_MPTABLE,
.ioapic_probe = mptable_ioapic_probe,
.ioapic_enumerate = mptable_ioapic_enumerate
};
static void
mptable_ioapic_enum_register(void)
{
ioapic_enumerator_register(&mptable_ioapic_enumerator);
}
SYSINIT(mptable_ioapic, SI_BOOT2_PRESMP, SI_ORDER_ANY,
mptable_ioapic_enum_register, 0);
void
mptable_pci_int_dump(void)
{
const struct mptable_pci_int *pci_int;
if (!bootverbose)
return;
TAILQ_FOREACH(pci_int, &mptable_pci_int_list, mpci_link) {
kprintf("MPTABLE: %d:%d INT%c -> IOAPIC %d.%d\n",
pci_int->mpci_bus,
pci_int->mpci_dev,
pci_int->mpci_pin + 'A',
pci_int->mpci_ioapic_idx,
pci_int->mpci_ioapic_pin);
}
}
int
mptable_pci_int_route(int bus, int dev, int pin, int intline)
{
const struct mptable_pci_int *pci_int;
int irq = -1;
KKASSERT(pin >= 1);
--pin;
TAILQ_FOREACH(pci_int, &mptable_pci_int_list, mpci_link) {
if (pci_int->mpci_bus == bus &&
pci_int->mpci_dev == dev &&
pci_int->mpci_pin == pin)
break;
}
if (pci_int != NULL) {
int gsi;
gsi = ioapic_gsi(pci_int->mpci_ioapic_idx,
pci_int->mpci_ioapic_pin);
if (gsi >= 0) {
irq = machintr_legacy_intr_find_bygsi(gsi,
INTR_TRIGGER_LEVEL, INTR_POLARITY_LOW);
}
}
if (irq < 0 && intline >= 0) {
kprintf("MPTABLE: fixed interrupt routing "
"for %d:%d INT%c\n", bus, dev, pin + 'A');
irq = machintr_legacy_intr_find(intline,
INTR_TRIGGER_LEVEL, INTR_POLARITY_LOW);
}
if (irq >= 0 && bootverbose) {
kprintf("MPTABLE: %d:%d INT%c routed to irq %d\n",
bus, dev, pin + 'A', irq);
}
return irq;
}