/* $NetBSD: mpbios.c,v 1.60 2012/11/27 20:32:58 jakllsch Exp $ */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by RedBack Networks Inc. * * Author: Bill Sommerfeld * * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * Copyright (c) 1999 Stefan Grefen * * 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. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY 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 AND 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. */ /* * Derived from FreeBSD's mp_machdep.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. */ /* * The Intel MP-stuff is just one way of x86 SMP systems * so only Intel MP specific stuff is here. */ #include __KERNEL_RCSID(0, "$NetBSD: mpbios.c,v 1.60 2012/11/27 20:32:58 jakllsch Exp $"); #include "acpica.h" #include "lapic.h" #include "ioapic.h" #include "opt_acpi.h" #include "opt_mpbios.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef X86_MPBIOS_SUPPORT_EISA #include /* for ELCR* def'ns */ #endif #if NACPICA > 0 extern int mpacpi_ncpu; extern int mpacpi_nioapic; #endif int mpbios_ncpu; int mpbios_nioapic; #include "pci.h" #if NPCI > 0 #include #include #endif #include "locators.h" /* descriptions of MP basetable entries */ struct mpbios_baseentry { uint8_t type; uint8_t length; uint16_t count; const char *name; }; static const char *loc_where[] = { "extended bios data area", "last page of base memory", "bios" }; struct mp_map { vaddr_t baseva; int vsize; paddr_t pa; paddr_t pg; int psize; }; struct dflt_conf_entry { const char *bus_type[2]; int flags; }; int mp_cpuprint(void *, const char *); int mp_ioapicprint(void *, const char *); static const void *mpbios_search(device_t, paddr_t, int, struct mp_map *); static inline int mpbios_cksum(const void *,int); static void mp_cfg_special_intr(const struct mpbios_int *, uint32_t *); static void mp_print_special_intr (int intr); static void mp_cfg_pci_intr(const struct mpbios_int *, uint32_t *); static void mp_print_pci_intr (int intr); #ifdef X86_MPBIOS_SUPPORT_EISA static void mp_print_eisa_intr (int intr); static void mp_cfg_eisa_intr(const struct mpbios_int *, uint32_t *); #endif static void mp_cfg_isa_intr(const struct mpbios_int *, uint32_t *); static void mp_print_isa_intr(int intr); static void mpbios_dflt_conf_cpu(device_t); static void mpbios_dflt_conf_bus(device_t, const struct dflt_conf_entry *); static void mpbios_dflt_conf_ioapic(device_t); static void mpbios_dflt_conf_int(device_t, const struct dflt_conf_entry *, const int *); static void mpbios_cpu(const uint8_t *, device_t); static void mpbios_bus(const uint8_t *, device_t); static void mpbios_ioapic(const uint8_t *, device_t); static void mpbios_int(const uint8_t *, int, struct mp_intr_map *); static const void *mpbios_map(paddr_t, int, struct mp_map *); static void mpbios_unmap(struct mp_map *); /* * globals to help us bounce our way through parsing the config table. */ static struct mp_map mp_cfg_table_map; static struct mp_map mp_fp_map; const struct mpbios_cth *mp_cth; const struct mpbios_fps *mp_fps; int mpbios_scanned; int mp_cpuprint(void *aux, const char *pnp) { struct cpu_attach_args *caa = aux; if (pnp) aprint_normal("cpu at %s", pnp); aprint_normal(" apid %d", caa->cpu_number); return (UNCONF); } int mp_ioapicprint(void *aux, const char *pnp) { struct apic_attach_args *aaa = aux; if (pnp) aprint_normal("ioapic at %s", pnp); aprint_normal(" apid %d", aaa->apic_id); return (UNCONF); } /* * Map a chunk of memory read-only and return an appropraitely * const'ed pointer. */ static const void * mpbios_map(paddr_t pa, int len, struct mp_map *handle) { paddr_t pgpa = x86_trunc_page(pa); paddr_t endpa = x86_round_page(pa + len); vaddr_t va = uvm_km_alloc(kernel_map, endpa - pgpa, 0, UVM_KMF_VAONLY); vaddr_t retva = va + (pa & PGOFSET); handle->pa = pa; handle->pg = pgpa; handle->psize = len; handle->baseva = va; handle->vsize = endpa-pgpa; do { pmap_kenter_pa(va, pgpa, VM_PROT_READ, 0); va += PAGE_SIZE; pgpa += PAGE_SIZE; } while (pgpa < endpa); pmap_update(pmap_kernel()); return (const void *)retva; } inline static void mpbios_unmap(struct mp_map *handle) { pmap_kremove(handle->baseva, handle->vsize); pmap_update(pmap_kernel()); uvm_km_free(kernel_map, handle->baseva, handle->vsize, UVM_KMF_VAONLY); } /* * Look for an Intel MP spec table, indicating SMP capable hardware. */ int mpbios_probe(device_t self) { paddr_t ebda, memtop; paddr_t cthpa; int cthlen; const uint8_t *mpbios_page; int scan_loc; struct mp_map t; /* If MP is disabled, don't use MPBIOS or the ioapics. */ if ((boothowto & RB_MD1) != 0) return 0; /* see if EBDA exists */ mpbios_page = mpbios_map (0, PAGE_SIZE, &t); ebda = *(const uint16_t *) (&mpbios_page[0x40e]); ebda <<= 4; memtop = *(const uint16_t *) (&mpbios_page[0x413]); memtop <<= 10; mpbios_page = NULL; mpbios_unmap(&t); scan_loc = 0; if (ebda && ebda < IOM_BEGIN ) { mp_fps = mpbios_search(self, ebda, 1024, &mp_fp_map); if (mp_fps != NULL) goto found; } scan_loc = 1; if (memtop && memtop <= IOM_BEGIN ) { mp_fps = mpbios_search(self, memtop - 1024, 1024, &mp_fp_map); if (mp_fps != NULL) goto found; } scan_loc = 2; mp_fps = mpbios_search(self, BIOS_BASE, BIOS_COUNT, &mp_fp_map); if (mp_fps != NULL) goto found; /* nothing found */ return 0; found: if (mp_verbose) aprint_verbose_dev(self, "MP floating pointer found in %s at 0x%jx\n", loc_where[scan_loc], (uintmax_t)mp_fp_map.pa); if (mp_fps->pap == 0) { if (mp_fps->mpfb1 == 0) { aprint_error_dev(self, "MP fps invalid: " "no default config and no configuration table\n"); goto err; } return 10; } cthpa = mp_fps->pap; mp_cth = mpbios_map (cthpa, sizeof (*mp_cth), &mp_cfg_table_map); cthlen = mp_cth->base_len; mpbios_unmap(&mp_cfg_table_map); mp_cth = mpbios_map (cthpa, cthlen, &mp_cfg_table_map); if (mp_verbose) aprint_verbose_dev(self, "MP config table at 0x%jx, %d bytes long\n", (uintmax_t)cthpa, cthlen); if (mp_cth->signature != MP_CT_SIG) { aprint_error_dev(self, "MP signature mismatch (%x vs %x)\n", MP_CT_SIG, mp_cth->signature); goto err; } if (mpbios_cksum(mp_cth, cthlen)) { aprint_error_dev(self, "MP Configuration Table checksum mismatch\n"); goto err; } return 10; err: if (mp_fps) { mp_fps = NULL; mpbios_unmap(&mp_fp_map); } if (mp_cth) { mp_cth = NULL; mpbios_unmap(&mp_cfg_table_map); } return 0; } /* * Simple byte checksum used on config tables. */ inline static int mpbios_cksum(const void *start, int len) { unsigned char res=0; const char *p = start; const char *end = p + len; while (p < end) res += *p++; return res; } /* * Look for the MP floating pointer signature in the given physical * address range. * * We map the memory, scan through it, and unmap it. * If we find it, remap the floating pointer structure and return it. */ const void * mpbios_search(device_t self, paddr_t start, int count, struct mp_map *map) { struct mp_map t; int i, len; const struct mpbios_fps *m; int end = count - sizeof(*m); const uint8_t *base = mpbios_map (start, count, &t); if (mp_verbose) aprint_verbose_dev(self, "scanning 0x%jx to 0x%jx for MP signature\n", (uintmax_t)start, (uintmax_t)(start+count-sizeof(*m))); for (i = 0; i <= end; i += 4) { m = (const struct mpbios_fps *)&base[i]; if ((m->signature == MP_FP_SIG) && ((len = m->length << 4) != 0) && mpbios_cksum(m, (m->length << 4)) == 0) { mpbios_unmap (&t); return mpbios_map (start+i, len, map); } } mpbios_unmap(&t); return 0; } /* * MP configuration table parsing. */ static struct mpbios_baseentry mp_conf[] = { {0, 20, 0, "cpu"}, {1, 8, 0, "bus"}, {2, 8, 0, "ioapic"}, {3, 8, 0, "ioint"}, {4, 8, 0, "lint"}, }; static struct mp_bus extint_bus = { "ExtINT", -1, mp_print_special_intr, mp_cfg_special_intr, NULL, 0, 0, NULL, 0 }; static struct mp_bus smi_bus = { "SMI", -1, mp_print_special_intr, mp_cfg_special_intr, NULL, 0, 0, NULL, 0 }; static struct mp_bus nmi_bus = { "NMI", -1, mp_print_special_intr, mp_cfg_special_intr, NULL, 0, 0, NULL, 0 }; /* * MP default configuration tables (acc. MP Specification Version 1.4) */ /* * Default configurations always feature two processors and the local APIC IDs * are assigned consecutively by hardware starting from zero (sec. 5). We set * I/O APIC ID to 2. */ #define DFLT_IOAPIC_ID 2 #define ELCR_INV 0x1 #define MCA_INV 0x2 #define IRQ_VAR 0x4 #define INTIN0_NC 0x8 static const struct dflt_conf_entry dflt_conf_tab[7] = { /* * Assume all systems with EISA and (modern) PCI chipsets have ELCRs * (to control external interrupt-level inverters). MCA systems must * use fixed inverters for INTIN1-INTIN15 (table 5-1; sec. 5.3.2). */ {{"ISA ", NULL }, 0 }, /* default config 1 */ {{"EISA ", NULL }, ELCR_INV | IRQ_VAR }, /* default config 2 */ {{"EISA ", NULL }, ELCR_INV }, /* default config 3 */ {{"MCA ", NULL }, MCA_INV }, /* default config 4 */ {{"ISA ", "PCI "}, ELCR_INV }, /* default config 5 */ {{"EISA ", "PCI "}, ELCR_INV }, /* default config 6 */ {{"MCA ", "PCI "}, MCA_INV | INTIN0_NC} /* default config 7 */ }; #define _ (-1) /* INTINx not connected (to a bus interrupt) */ static const int dflt_bus_irq_tab[2][16] = { /* Default configs 1,3-7 connect INTIN1-INTIN15 to bus interrupts. */ {_, 1, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, /* * Default config 2 connects neither timer IRQ0 nor DMA chaining to * INTIN2 and INTIN13 (sec. 5.3). */ {_, 1, _, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, _, 14, 15} }; #undef _ static const uint8_t dflt_lint_tab[2] = { MPS_INTTYPE_ExtINT, MPS_INTTYPE_NMI }; /* * 1st pass on BIOS's Intel MP specification table. * * initializes: * mp_ncpus = 1 * * determines: * cpu_apic_address (common to all CPUs) * ioapic_address[N] * mp_naps * mp_nbus * mp_napics * nintrs */ void mpbios_scan(device_t self, int *ncpup) { const uint8_t *position, *end; size_t i; int count; int type; int intr_cnt, cur_intr; paddr_t lapic_base; const struct dflt_conf_entry *dflt_conf; const int *dflt_bus_irq; const struct mpbios_int *iep; struct mpbios_int ie; aprint_normal_dev(self, "Intel MP Specification "); switch (mp_fps->spec_rev) { case 1: aprint_normal("(Version 1.1)"); break; case 4: aprint_normal("(Version 1.4)"); break; default: aprint_normal("(unrecognized rev %d)", mp_fps->spec_rev); } /* * looks like we've got a MP system. start setting up * infrastructure.. * XXX is this the right place?? */ #if NACPICA > 0 if (mpacpi_ncpu == 0) { #endif lapic_base = LAPIC_BASE; if (mp_cth != NULL) lapic_base = (paddr_t)mp_cth->apic_address; #if NLAPIC > 0 lapic_boot_init(lapic_base); #endif #if NACPICA > 0 } #endif /* check for use of 'default' configuration */ if (mp_fps->mpfb1 != 0) { if (mp_fps->mpfb1 > __arraycount(dflt_conf_tab)) panic("Unsupported MP default configuration %d\n", mp_fps->mpfb1); aprint_normal("\n"); aprint_normal_dev(self, "MP default configuration %d\n", mp_fps->mpfb1); dflt_conf = &dflt_conf_tab[mp_fps->mpfb1 - 1]; dflt_bus_irq = dflt_bus_irq_tab[(dflt_conf->flags & IRQ_VAR) != 0]; #if NACPICA > 0 if (mpacpi_ncpu == 0) #endif mpbios_dflt_conf_cpu(self); #if NACPICA > 0 if (mpacpi_nioapic == 0) #endif mpbios_dflt_conf_ioapic(self); /* * Walk the table once, counting items. */ mp_nbus = 0; for (i = 0; i < __arraycount(dflt_conf->bus_type); i++) { if (dflt_conf->bus_type[i] != NULL) mp_nbus++; } KASSERT(mp_nbus != 0); mp_busses = kmem_zalloc(sizeof(struct mp_bus) * mp_nbus, KM_SLEEP); KASSERT(mp_busses != NULL); /* INTIN0 */ intr_cnt = (dflt_conf->flags & INTIN0_NC) ? 0 : 1; /* INTINx */ for (i = 0; i < __arraycount(dflt_bus_irq_tab[0]); i++) { if (dflt_bus_irq[i] >= 0) intr_cnt++; } KASSERT(intr_cnt != 0); /* LINTINx */ for (i = 0; i < __arraycount(dflt_lint_tab); i++) intr_cnt++; mp_intrs = kmem_zalloc(sizeof(struct mp_intr_map) * intr_cnt, KM_SLEEP); KASSERT(mp_intrs != NULL); mp_nintr = intr_cnt; /* * Re-walk the table, recording info of interest. */ mpbios_dflt_conf_bus(self, dflt_conf); mpbios_dflt_conf_int(self, dflt_conf, dflt_bus_irq); } else { /* * should not happen; mp_probe returns 0 in this case, * but.. */ if (mp_cth == NULL) panic ("mpbios_scan: no config (can't happen?)"); printf(" (%8.8s %12.12s)\n", mp_cth->oem_id, mp_cth->product_id); /* * Walk the table once, counting items */ position = (const uint8_t *)(mp_cth); end = position + mp_cth->base_len; position += sizeof(*mp_cth); count = mp_cth->entry_count; intr_cnt = 0; while ((count--) && (position < end)) { type = *position; if (type >= MPS_MCT_NTYPES) { aprint_error_dev(self, "unknown entry type %x" " in MP config table\n", type); break; } mp_conf[type].count++; if (type == MPS_MCT_BUS) { const struct mpbios_bus *bp = (const struct mpbios_bus *)position; if (bp->bus_id >= mp_nbus) mp_nbus = bp->bus_id + 1; } /* * Count actual interrupt instances. * dst_apic_id of MPS_ALL_APICS means "wired to all * apics of this type". */ if (type == MPS_MCT_IOINT) { iep = (const struct mpbios_int *)position; if (iep->dst_apic_id == MPS_ALL_APICS) intr_cnt += mp_conf[MPS_MCT_IOAPIC].count; else intr_cnt++; } else if (type == MPS_MCT_LINT) intr_cnt++; position += mp_conf[type].length; } mp_busses = kmem_zalloc(sizeof(struct mp_bus)*mp_nbus, KM_SLEEP); KASSERT(mp_busses != NULL); mp_intrs = kmem_zalloc(sizeof(struct mp_intr_map)*intr_cnt, KM_SLEEP); KASSERT(mp_intrs != NULL); mp_nintr = intr_cnt; /* re-walk the table, recording info of interest */ position = (const uint8_t *) mp_cth + sizeof(*mp_cth); count = mp_cth->entry_count; cur_intr = 0; while ((count--) && (position < end)) { switch (type = *position) { case MPS_MCT_CPU: #if NACPICA > 0 /* ACPI has done this for us */ if (mpacpi_ncpu) break; #endif mpbios_cpu(position, self); break; case MPS_MCT_BUS: mpbios_bus(position, self); break; case MPS_MCT_IOAPIC: #if NACPICA > 0 /* ACPI has done this for us */ if (mpacpi_nioapic) break; #endif mpbios_ioapic(position, self); break; case MPS_MCT_IOINT: iep = (const struct mpbios_int *)position; ie = *iep; if (iep->dst_apic_id == MPS_ALL_APICS) { #if NIOAPIC > 0 struct ioapic_softc *sc; for (sc = ioapics ; sc != NULL; sc = sc->sc_next) { ie.dst_apic_id = sc->sc_apicid; mpbios_int((char *)&ie, type, &mp_intrs[cur_intr++]); } #endif } else { mpbios_int(position, type, &mp_intrs[cur_intr++]); } break; case MPS_MCT_LINT: mpbios_int(position, type, &mp_intrs[cur_intr]); cur_intr++; break; default: aprint_error_dev(self, "unknown entry type %x in MP config table\n", type); /* NOTREACHED */ return; } position += mp_conf[type].length; } if (mp_verbose && mp_cth->ext_len) aprint_verbose_dev(self, "MP WARNING: %d bytes of extended entries not examined\n", mp_cth->ext_len); } /* Clean up. */ mp_fps = NULL; mpbios_unmap (&mp_fp_map); if (mp_cth != NULL) { mp_cth = NULL; mpbios_unmap (&mp_cfg_table_map); } mpbios_scanned = 1; *ncpup = mpbios_ncpu; } static void mpbios_cpu(const uint8_t *ent, device_t self) { const struct mpbios_proc *entry = (const struct mpbios_proc *)ent; struct cpu_attach_args caa; int locs[CPUBUSCF_NLOCS]; /* XXX move this into the CPU attachment goo. */ /* check for usability */ if (!(entry->cpu_flags & PROCENTRY_FLAG_EN)) return; mpbios_ncpu++; /* check for BSP flag */ if (entry->cpu_flags & PROCENTRY_FLAG_BP) caa.cpu_role = CPU_ROLE_BP; else caa.cpu_role = CPU_ROLE_AP; caa.cpu_id = entry->apic_id; caa.cpu_number = entry->apic_id; caa.cpu_func = &mp_cpu_funcs; locs[CPUBUSCF_APID] = caa.cpu_number; config_found_sm_loc(self, "cpubus", locs, &caa, mp_cpuprint, config_stdsubmatch); } static void mpbios_dflt_conf_cpu(device_t self) { struct mpbios_proc mpp; /* mpp.type and mpp.apic_version are irrelevant for mpbios_cpu(). */ /* * Default configurations always feature two processors and the local * APIC IDs are assigned consecutively by hardware starting from zero * (sec. 5). Just determine the BSP (APIC ID). */ mpp.apic_id = cpu_info_primary.ci_cpuid; mpp.cpu_flags = PROCENTRY_FLAG_EN | PROCENTRY_FLAG_BP; mpbios_cpu((uint8_t *)&mpp, self); mpp.apic_id = 1 - cpu_info_primary.ci_cpuid; mpp.cpu_flags = PROCENTRY_FLAG_EN; mpbios_cpu((uint8_t *)&mpp, self); } static void mpbios_dflt_conf_bus(device_t self, const struct dflt_conf_entry *dflt_conf) { struct mpbios_bus mpb; size_t i; /* mpb.type is irrelevant for mpbios_bus(). */ mpb.bus_id = 0; for (i = 0; i < __arraycount(dflt_conf->bus_type); i++) { if (dflt_conf->bus_type[i] != NULL) { memcpy(mpb.bus_type, dflt_conf->bus_type[i], 6); mpbios_bus((u_int8_t *)&mpb, self); mpb.bus_id++; } } } static void mpbios_dflt_conf_ioapic(device_t self) { struct mpbios_ioapic mpio; /* mpio.type is irrelevant for mpbios_ioapic(). */ mpio.apic_id = DFLT_IOAPIC_ID; /* XXX Let ioapic driver read real APIC version... */ mpio.apic_version = 0; mpio.apic_flags = IOAPICENTRY_FLAG_EN; mpio.apic_address = (uint32_t)IOAPIC_BASE_DEFAULT; mpbios_ioapic((uint8_t *)&mpio, self); } static void mpbios_dflt_conf_int(device_t self, const struct dflt_conf_entry *dflt_conf, const int *dflt_bus_irq) { struct mpbios_int mpi; size_t i; int cur_intr; uint16_t level_inv; cur_intr = 0; /* * INTIN0 */ /* * 8259A INTR is connected to INTIN0 for default configs 1-6, but not * for default config 7 (sec. 5.3). */ if ((dflt_conf->flags & INTIN0_NC) == 0) { /* mpi.type is irrelevant for mpbios_int(). */ mpi.int_type = MPS_INTTYPE_ExtINT; mpi.int_flags = 0; mpi.src_bus_id = 0; mpi.src_bus_irq = 0; mpi.dst_apic_id = DFLT_IOAPIC_ID; mpi.dst_apic_int = 0; mpbios_int((u_int8_t *)&mpi, MPS_MCT_IOINT, &mp_intrs[cur_intr++]); } /* * INTINx */ /* mpi.type is irrelevant for mpbios_int(). */ mpi.int_type = MPS_INTTYPE_INT; /* * PCI interrupt lines appear as (E)ISA interrupt lines/on bus 0 * (sec. 5.2). */ mpi.src_bus_id = 0; mpi.dst_apic_id = DFLT_IOAPIC_ID; /* * Compliant systems must convert active-low, level-triggered interrupts * to active-high by external inverters before INTINx (table 5-1; * sec. 5.3.2). */ if (dflt_conf->flags & ELCR_INV) { /* Systems with ELCRs use them to control the inverters. */ level_inv = ((uint16_t)inb(ELCR1) << 8) | inb(ELCR0); } else if (dflt_conf->flags & MCA_INV) { /* MCA systems have fixed inverters. */ level_inv = 0xffffU; } else { level_inv = 0; } for (i = 0; i < __arraycount(dflt_bus_irq_tab[0]); i++) { if (dflt_bus_irq[i] >= 0) { mpi.src_bus_irq = (uint8_t)dflt_bus_irq[i]; if (level_inv & (1U << mpi.src_bus_irq)) mpi.int_flags = (MPS_INTTR_LEVEL << 2) | MPS_INTPO_ACTHI; else mpi.int_flags = 0; /* conforms to bus spec. */ mpi.dst_apic_int = (uint8_t)i; mpbios_int((u_int8_t *)&mpi, MPS_MCT_IOINT, &mp_intrs[cur_intr++]); } } /* * LINTINx */ /* mpi.type is irrelevant for mpbios_int(). */ mpi.int_flags = 0; mpi.src_bus_id = 0; mpi.src_bus_irq = 0; mpi.dst_apic_id = MPS_ALL_APICS; for (i = 0; i < __arraycount(dflt_lint_tab); i++) { mpi.int_type = dflt_lint_tab[i]; mpi.dst_apic_int = (uint8_t)i; mpbios_int((u_int8_t *)&mpi, MPS_MCT_LINT, &mp_intrs[cur_intr++]); } } /* * The following functions conspire to compute base ioapic redirection * table entry for a given interrupt line. * * Fill in: trigger mode, polarity, and possibly delivery mode. */ static void mp_cfg_special_intr(const struct mpbios_int *entry, uint32_t *redir) { /* * All of these require edge triggered, zero vector, * appropriate delivery mode. * see page 13 of the 82093AA datasheet. */ *redir &= ~IOAPIC_REDLO_DEL_MASK; *redir &= ~IOAPIC_REDLO_VECTOR_MASK; *redir &= ~IOAPIC_REDLO_LEVEL; switch (entry->int_type) { case MPS_INTTYPE_NMI: *redir |= (IOAPIC_REDLO_DEL_NMI<int_flags & 0x03; /* XXX magic */ int mpstrig = (entry->int_flags >> 2) & 0x03; /* XXX magic */ *redir &= ~IOAPIC_REDLO_DEL_MASK; switch (mpspo) { case MPS_INTPO_ACTHI: *redir &= ~IOAPIC_REDLO_ACTLO; break; case MPS_INTPO_DEF: case MPS_INTPO_ACTLO: *redir |= IOAPIC_REDLO_ACTLO; break; default: panic("unknown MPS interrupt polarity %d", mpspo); } if (entry->int_type != MPS_INTTYPE_INT) { mp_cfg_special_intr(entry, redir); return; } *redir |= (IOAPIC_REDLO_DEL_FIXED<int_flags & 0x03; /* XXX magic */ int mpstrig = (entry->int_flags >> 2) & 0x03; /* XXX magic */ *redir &= ~IOAPIC_REDLO_DEL_MASK; switch (mpspo) { case MPS_INTPO_DEF: case MPS_INTPO_ACTHI: *redir &= ~IOAPIC_REDLO_ACTLO; break; case MPS_INTPO_ACTLO: *redir |= IOAPIC_REDLO_ACTLO; break; default: panic("unknown MPS interrupt polarity %d", mpspo); } if (entry->int_type != MPS_INTTYPE_INT) { mp_cfg_special_intr(entry, redir); return; } *redir |= (IOAPIC_REDLO_DEL_FIXED<src_bus_id].mb_data & (1<src_bus_irq)) { *redir |= IOAPIC_REDLO_LEVEL; } else { *redir &= ~IOAPIC_REDLO_LEVEL; } break; default: panic("unknown MPS interrupt trigger %d", mpstrig); } } #endif static void mp_cfg_isa_intr(const struct mpbios_int *entry, uint32_t *redir) { int mpspo = entry->int_flags & 0x03; /* XXX magic */ int mpstrig = (entry->int_flags >> 2) & 0x03; /* XXX magic */ *redir &= ~IOAPIC_REDLO_DEL_MASK; switch (mpspo) { case MPS_INTPO_DEF: case MPS_INTPO_ACTHI: *redir &= ~IOAPIC_REDLO_ACTLO; break; case MPS_INTPO_ACTLO: *redir |= IOAPIC_REDLO_ACTLO; break; default: panic("unknown MPS interrupt polarity %d", mpspo); } if (entry->int_type != MPS_INTTYPE_INT) { mp_cfg_special_intr(entry, redir); return; } *redir |= (IOAPIC_REDLO_DEL_FIXED<>2)&0x1f, 'A' + (intr & 0x3)); } static void mp_print_isa_intr(int intr) { printf(" irq %d", intr); } #ifdef X86_MPBIOS_SUPPORT_EISA static void mp_print_eisa_intr(int intr) { printf(" EISA irq %d", intr); } #endif #define TAB_UNIT 4 #define TAB_ROUND(a) _TAB_ROUND(a, TAB_UNIT) #define _TAB_ROUND(a,u) (((a) + (u - 1)) & ~(u-1)) #define EXTEND_TAB(a,u) (!(_TAB_ROUND(a,u) == _TAB_ROUND((a+1),u))) static void mpbios_bus(const uint8_t *ent, device_t self) { const struct mpbios_bus *entry = (const struct mpbios_bus *)ent; int bus_id = entry->bus_id; aprint_verbose("mpbios: bus %d is type %6.6s\n", bus_id, entry->bus_type); #ifdef DIAGNOSTIC /* * This "should not happen" unless the table changes out * from underneath us */ if (bus_id >= mp_nbus) { panic("mpbios: bus number %d out of range?? (type %6.6s)\n", bus_id, entry->bus_type); } #endif mp_busses[bus_id].mb_intrs = NULL; if (memcmp(entry->bus_type, "PCI ", 6) == 0) { mp_busses[bus_id].mb_name = "pci"; mp_busses[bus_id].mb_idx = bus_id; mp_busses[bus_id].mb_intr_print = mp_print_pci_intr; mp_busses[bus_id].mb_intr_cfg = mp_cfg_pci_intr; #ifdef X86_MPBIOS_SUPPORT_EISA } else if (memcmp(entry->bus_type, "EISA ", 6) == 0) { mp_busses[bus_id].mb_name = "eisa"; mp_busses[bus_id].mb_idx = bus_id; mp_busses[bus_id].mb_intr_print = mp_print_eisa_intr; mp_busses[bus_id].mb_intr_cfg = mp_cfg_eisa_intr; mp_busses[bus_id].mb_data = inb(ELCR0) | (inb(ELCR1) << 8); if (mp_eisa_bus != -1) aprint_error("oops: multiple isa busses?\n"); else mp_eisa_bus = bus_id; #endif } else if (memcmp(entry->bus_type, "ISA ", 6) == 0) { mp_busses[bus_id].mb_name = "isa"; mp_busses[bus_id].mb_idx = 0; /* XXX */ mp_busses[bus_id].mb_intr_print = mp_print_isa_intr; mp_busses[bus_id].mb_intr_cfg = mp_cfg_isa_intr; if (mp_isa_bus != -1) printf("oops: multiple isa busses?\n"); else mp_isa_bus = bus_id; } else { aprint_error_dev(self, "unsupported bus type %6.6s\n", entry->bus_type); } } static void mpbios_ioapic(const uint8_t *ent, device_t self) { const struct mpbios_ioapic *entry = (const struct mpbios_ioapic *)ent; /* XXX let flags checking happen in ioapic driver.. */ if (!(entry->apic_flags & IOAPICENTRY_FLAG_EN)) return; mpbios_nioapic++; #if NIOAPIC > 0 { int locs[IOAPICBUSCF_NLOCS]; struct apic_attach_args aaa; aaa.apic_id = entry->apic_id; aaa.apic_version = entry->apic_version; aaa.apic_address = (paddr_t)entry->apic_address; aaa.apic_vecbase = -1; aaa.flags = (mp_fps->mpfb2 & 0x80) ? IOAPIC_PICMODE : IOAPIC_VWIRE; locs[IOAPICBUSCF_APID] = aaa.apic_id; config_found_sm_loc(self, "ioapicbus", locs, &aaa, mp_ioapicprint, config_stdsubmatch); } #endif } static const char inttype_fmt[] = "\177\020" "f\0\2type\0" "=\1NMI\0" "=\2SMI\0" "=\3ExtINT\0"; static const char flagtype_fmt[] = "\177\020" "f\0\2pol\0" "=\1Act Hi\0" "=\3Act Lo\0" "f\2\2trig\0" "=\1Edge\0" "=\3Level\0"; static void mpbios_int(const uint8_t *ent, int enttype, struct mp_intr_map *mpi) { const struct mpbios_int *entry = (const struct mpbios_int *)ent; struct ioapic_softc *sc = NULL; struct pic *sc2; struct mp_intr_map *altmpi; struct mp_bus *mpb; uint32_t id = entry->dst_apic_id; uint32_t pin = entry->dst_apic_int; uint32_t bus = entry->src_bus_id; uint32_t dev = entry->src_bus_irq; uint32_t type = entry->int_type; uint32_t flags = entry->int_flags; switch (type) { case MPS_INTTYPE_INT: mpb = &(mp_busses[bus]); break; case MPS_INTTYPE_ExtINT: mpb = &extint_bus; break; case MPS_INTTYPE_SMI: mpb = &smi_bus; break; case MPS_INTTYPE_NMI: mpb = &nmi_bus; break; default: panic("unknown MPS interrupt type %d", entry->int_type); } mpi->next = mpb->mb_intrs; mpb->mb_intrs = mpi; mpi->bus = mpb; mpi->bus_pin = dev; mpi->global_int = -1; mpi->type = type; mpi->flags = flags; mpi->redir = 0; if (mpb->mb_intr_cfg == NULL) { printf("mpbios: can't find bus %d for apic %d pin %d\n", bus, id, pin); return; } (*mpb->mb_intr_cfg)(entry, &mpi->redir); if (enttype == MPS_MCT_IOINT) { #if NIOAPIC > 0 sc = ioapic_find(id); #else sc = NULL; #endif if (sc == NULL) { printf("mpbios: can't find ioapic %d\n", id); return; } /* * XXX workaround for broken BIOSs that put the ACPI * global interrupt number in the entry, not the pin * number. */ if (pin >= sc->sc_apic_sz) { sc2 = intr_findpic(pin); if (sc2 && sc2->pic_ioapic != sc) { printf("mpbios: bad pin %d for apic %d\n", pin, id); return; } printf("mpbios: WARNING: pin %d for apic %d too high; " "assuming ACPI global int value\n", pin, id); pin -= sc->sc_apic_vecbase; } mpi->ioapic = (struct pic *)sc; mpi->ioapic_pin = pin; altmpi = sc->sc_pins[pin].ip_map; if (altmpi != NULL) { if ((altmpi->type != type) || (altmpi->flags != flags)) { printf("%s: conflicting map entries for pin %d\n", device_xname(sc->sc_dev), pin); } } else { sc->sc_pins[pin].ip_map = mpi; } } else { if (pin >= 2) printf("pin %d of local apic doesn't exist!\n", pin); else { mpi->ioapic = NULL; mpi->ioapic_pin = pin; mpi->cpu_id = id; } } mpi->ioapic_ih = APIC_INT_VIA_APIC | ((id<sc_dev) : "local apic", pin, mpb->mb_name); if (mpb->mb_idx != -1) printf("%d", mpb->mb_idx); (*(mpb->mb_intr_print))(dev); snprintb(buf, sizeof(buf), inttype_fmt, type); printf(" (type %s", buf); snprintb(buf, sizeof(buf), flagtype_fmt, flags); printf(" flags %s)\n", buf); } } #if NPCI > 0 int mpbios_pci_attach_hook(device_t parent, device_t self, struct pcibus_attach_args *pba) { struct mp_bus *mpb; if (mpbios_scanned == 0) return ENOENT; if (pba->pba_bus >= mp_isa_bus) { intr_add_pcibus(pba); return 0; } mpb = &mp_busses[pba->pba_bus]; if (mpb->mb_name != NULL) { if (strcmp(mpb->mb_name, "pci")) return EINVAL; } else mpb->mb_name = "pci"; if (mp_verbose) printf("\n%s: added to list as bus %d", device_xname(parent), pba->pba_bus); mpb->mb_dev = self; mpb->mb_pci_bridge_tag = pba->pba_bridgetag; mpb->mb_pci_chipset_tag = pba->pba_pc; return 0; } #endif