root/sys/platform/pc64/x86_64/mptable.c
/*
 * Copyright (c) 1996, by Steve Passe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the developer may NOT be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/sys/i386/i386/mp_machdep.c,v 1.115.2.15 2003/03/14 21:22:35 jhb Exp $
 */

#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>   /* cngetc() */
#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>             /* setidt() */
#include <machine_base/icu/icu.h>       /* IPIs */
#include <machine_base/apic/ioapic_abi.h>
#include <machine/intr_machdep.h>       /* IPIs */

extern u_int    base_memory;            /* in kilobytes, see machdep.c */
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


/* MP Floating Pointer Structure */
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;

/* MP Configuration Table Header */
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;

/* descriptions of MP basetable entries */
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;        /* MPTABLE_BUS_ */
        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);

/*
 * Look for an Intel MP spec table (ie, SMP capable hardware).
 */
static int
mptable_search(void)
{
        long    x;
        u_int32_t target;
 
        /* see if EBDA exists */
        if (ebda_addr != 0) {
                /* search first 1K of EBDA */
                target = (u_int32_t)ebda_addr;
                if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
                        return x;
        } else {
                /* last 1K of base memory, effective 'top of base' passed in */
                target = (u_int32_t)((base_memory - 1) * 1024);
                if ((x = mptable_search_sig(target, 1024 / 4)) > 0)
                        return x;
        }

        /* search the BIOS */
        target = (u_int32_t)BIOS_BASE;
        if ((x = mptable_search_sig(target, BIOS_COUNT)) > 0)
                return x;

        /* search the extended BIOS */
        target = (u_int32_t)BIOS_BASE2;
        if ((x = mptable_search_sig(target, BIOS_COUNT)) > 0)
                return x;

        /* nothing found */
        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: /* processor_entry */
                case 1: /* bus_entry */
                case 2: /* io_apic_entry */
                case 3: /* int_entry */
                case 4: /* int_entry */
                        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;
}

/*
 * look for the MP spec signature
 */

/* string defined by the Intel MP Spec as identifying the MP table */
#define MP_SIG          0x5f504d5f      /* _MP_ */
#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) {
                        /* make array index a byte index */
                        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);

/*
 * Check if we should perform a hyperthreading "fix-up" to
 * enumerate any logical CPU's that aren't already listed
 * in the table.
 *
 * XXX: We assume that all of the physical CPUs in the
 * system have the same number of logical CPUs.
 *
 * XXX: We assume that APIC ID's are allocated such that
 * the APIC ID's for a physical processor are aligned
 * with the number of logical CPU's in the processor.
 */
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) {
                /*
                 * INSTRUCTION SET REFERENCE, A-M (#253666)
                 * Page 3-181, Table 3-20
                 * "The nearest power-of-2 integer that is not smaller
                 *  than EBX[23:16] is the number of unique initial APIC
                 *  IDs reserved for addressing different logical
                 *  processors in a physical package."
                 */
                for (i = 0; ; ++i) {
                        if ((1 << i) >= lcpus_max) {
                                lcpus_max = 1 << i;
                                break;
                        }
                }
        }

        KKASSERT(cpu_count != 0);
        if (cpu_count == lcpus_max) {
                /* We have nothing to fix */
                return 0;
        } else if (cpu_count == 1) {
                /* XXX this may be incorrect */
                logical_cpus = lcpus_max;
        } else {
                int cur, prev, dist;

                /*
                 * Calculate the distances between two nearest
                 * APIC IDs.  If all such distances are same,
                 * then it is the number of missing cpus that
                 * we are going to fill later.
                 */
                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;

                                /*
                                 * Make sure that all distances
                                 * between two nearest APIC IDs
                                 * are same.
                                 */
                                if (dist != new_dist)
                                        return 0;
                        }
                        prev = cur;
                }
                if (dist == 1)
                        return 0;

                /* Must be power of 2 */
                if (dist & (dist - 1))
                        return 0;

                /* Can't exceed CPU package capacity */
                if (dist > lcpus_max)
                        logical_cpus = lcpus_max;
                else
                        logical_cpus = dist;
        }

        /*
         * For each APIC ID of a CPU that is set in the mask,
         * scan the other candidate APIC ID's for this
         * physical processor.  If any of those ID's are
         * already in the table, then kill the fixup.
         */
        for (id = 0; id < MAXCPU; id++) {
                if (CPUMASK_TESTBIT(id_mask, id) == 0)
                        continue;
                /* First, make sure we are on a logical_cpus boundary. */
                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) {
                /*
                 * Map configuration table header to get
                 * the base table size
                 */
                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;
                }

                /*
                 * Map the base table
                 */
                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);

        /* check for usability */
        if (!(entry->cpu_flags & PROCENTRY_FLAG_EN))
                return 0;

        /* check for BSP flag */
        if (entry->cpu_flags & PROCENTRY_FLAG_BP) {
                lapic_set_cpuid(0, entry->apic_id);
                return 0;       /* its already been counted */
        }

        /* add another AP to list, if less than max number of CPUs */
        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;

                /*
                 * Create fake mptable processor entries
                 * and feed them to processor_entry() to
                 * enumerate the logical CPUs.
                 */
                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; /* exclude BSP */

        /* Map local apic before the id field is accessed */
        lapic_map(DEFAULT_APIC_BASE);

        bsp_apicid = LAPIC_READID;
        ap_apicid = (bsp_apicid == 0) ? 1 : 0;

        /* BSP */
        lapic_set_cpuid(0, bsp_apicid);
        /* one and only AP */
        lapic_set_cpuid(1, ap_apicid);
}

/*
 * Configure:
 *     naps
 *     APIC ID <-> CPU ID mappings
 */
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;
 
        /* Save local apic address */
        lapic_addr = cth->apic_address;
        KKASSERT(lapic_addr != 0);
 
        /*
         * Find out how many CPUs do we have
         */
        bzero(&arg1, sizeof(arg1));
        arg1.ht_fixup = 1; /* Apply ht fixup by default */

        error = mptable_iterate_entries(cth,
                    mptable_lapic_pass1_callback, &arg1);
        if (error)
                panic("mptable_iterate_entries(lapic_pass1) failed");
        KKASSERT(arg1.cpu_count != 0);
 
        /* See if we need to fixup HT logical CPUs. */
        /*
         * XXX fixup for cpus >= 32 ? XXX
         */
        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;      /* subtract the BSP */
 
        /*
         * Link logical CPU id to local apic id
         */
        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);

        /* Map local apic */
        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;

        /*
         * Create IOAPIC list in ascending order of APIC ID
         */
        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;        /* NOTE: any value is ok here */
                ioapic->mio_addr = 0xfec00000;  /* XXX magic number */

                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;
        }

        /*
         * Assign index number for each IOAPIC
         */
        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;
        }

        /* XXX magic number */
        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;  /* zero based */

        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;
}