/* $NetBSD: x86_autoconf.c,v 1.59 2011/03/08 02:57:00 macallan Exp $ */ /*- * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * William Jolitz. * * 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. Neither the name of the University 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 THE REGENTS 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 REGENTS 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. * * @(#)autoconf.c 7.1 (Berkeley) 5/9/91 */ #include __KERNEL_RCSID(0, "$NetBSD: x86_autoconf.c,v 1.59 2011/03/08 02:57:00 macallan Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acpica.h" #include "pci.h" #include "isa.h" #include "genfb.h" #include "wsdisplay.h" #include "opt_vga.h" #include "opt_ddb.h" #ifdef DDB #include #include #include #endif #if NACPICA > 0 #include #endif #ifdef VGA_POST #include #endif #include #if NPCI > 0 #include #endif #include #include #if NPCI > 0 #include #endif #include #if NPCI > 0 static struct genfb_colormap_callback gfb_cb; static struct genfb_pmf_callback pmf_cb; static struct genfb_mode_callback mode_cb; #ifdef VGA_POST static struct vga_post *vga_posth = NULL; #endif #endif #if NGENFB > 0 && NACPICA > 0 && defined(VGA_POST) extern int acpi_md_vbios_reset; extern int acpi_md_vesa_modenum; #endif struct disklist *x86_alldisks; int x86_ndisks; #if NPCI > 0 static void x86_genfb_set_mapreg(void *opaque, int index, int r, int g, int b) { outb(0x3c0 + VGA_DAC_ADDRW, index); outb(0x3c0 + VGA_DAC_PALETTE, (uint8_t)r >> 2); outb(0x3c0 + VGA_DAC_PALETTE, (uint8_t)g >> 2); outb(0x3c0 + VGA_DAC_PALETTE, (uint8_t)b >> 2); } static bool x86_genfb_setmode(struct genfb_softc *sc, int newmode) { #if NGENFB > 0 static int curmode = WSDISPLAYIO_MODE_EMUL; switch (newmode) { case WSDISPLAYIO_MODE_EMUL: x86_genfb_mtrr_init(sc->sc_fboffset, sc->sc_height * sc->sc_stride); #if NACPICA > 0 && defined(VGA_POST) if (curmode != newmode) { if (vga_posth != NULL && acpi_md_vesa_modenum != 0) { vga_post_set_vbe(vga_posth, acpi_md_vesa_modenum); } } #endif break; } curmode = newmode; #endif return true; } static bool x86_genfb_suspend(device_t dev, const pmf_qual_t *qual) { return true; } static bool x86_genfb_resume(device_t dev, const pmf_qual_t *qual) { #if NGENFB > 0 struct pci_genfb_softc *psc = device_private(dev); #if NACPICA > 0 && defined(VGA_POST) if (vga_posth != NULL && acpi_md_vbios_reset == 2) { vga_post_call(vga_posth); if (acpi_md_vesa_modenum != 0) vga_post_set_vbe(vga_posth, acpi_md_vesa_modenum); } #endif genfb_restore_palette(&psc->sc_gen); #endif return true; } #endif static void handle_wedges(device_t dv, int par) { if (config_handle_wedges(dv, par) == 0) return; booted_device = dv; booted_partition = par; } static int is_valid_disk(device_t dv) { if (device_class(dv) != DV_DISK) return (0); return (device_is_a(dv, "dk") || device_is_a(dv, "sd") || device_is_a(dv, "wd") || device_is_a(dv, "ld") || device_is_a(dv, "ed")); } /* * XXX Ugly bit of code. But, this is the only safe time that the * match between BIOS disks and native disks can be done. */ static void matchbiosdisks(void) { struct btinfo_biosgeom *big; struct bi_biosgeom_entry *be; device_t dv; deviter_t di; int i, ck, error, m, n; struct vnode *tv; char mbr[DEV_BSIZE]; int dklist_size; int numbig; big = lookup_bootinfo(BTINFO_BIOSGEOM); numbig = big ? big->num : 0; /* First, count all native disks. */ for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { if (is_valid_disk(dv)) x86_ndisks++; } deviter_release(&di); dklist_size = sizeof(struct disklist) + (x86_ndisks - 1) * sizeof(struct nativedisk_info); /* XXX M_TEMP is wrong */ x86_alldisks = malloc(dklist_size, M_TEMP, M_NOWAIT | M_ZERO); if (x86_alldisks == NULL) return; x86_alldisks->dl_nnativedisks = x86_ndisks; x86_alldisks->dl_nbiosdisks = numbig; for (i = 0; i < numbig; i++) { x86_alldisks->dl_biosdisks[i].bi_dev = big->disk[i].dev; x86_alldisks->dl_biosdisks[i].bi_sec = big->disk[i].sec; x86_alldisks->dl_biosdisks[i].bi_head = big->disk[i].head; x86_alldisks->dl_biosdisks[i].bi_cyl = big->disk[i].cyl; x86_alldisks->dl_biosdisks[i].bi_lbasecs = big->disk[i].totsec; x86_alldisks->dl_biosdisks[i].bi_flags = big->disk[i].flags; #ifdef GEOM_DEBUG #ifdef notyet printf("disk %x: flags %x, interface %x, device %llx\n", big->disk[i].dev, big->disk[i].flags, big->disk[i].interface_path, big->disk[i].device_path); #endif #endif } /* XXX Code duplication from findroot(). */ n = -1; for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { if (device_class(dv) != DV_DISK) continue; #ifdef GEOM_DEBUG printf("matchbiosdisks: trying to match (%s) %s\n", device_xname(dv), device_cfdata(dv)->cf_name); #endif if (is_valid_disk(dv)) { n++; snprintf(x86_alldisks->dl_nativedisks[n].ni_devname, sizeof(x86_alldisks->dl_nativedisks[n].ni_devname), "%s", device_xname(dv)); if ((tv = opendisk(dv)) == NULL) continue; error = vn_rdwr(UIO_READ, tv, mbr, DEV_BSIZE, 0, UIO_SYSSPACE, 0, NOCRED, NULL, NULL); VOP_CLOSE(tv, FREAD, NOCRED); if (error) { #ifdef GEOM_DEBUG printf("matchbiosdisks: %s: MBR read failure\n", device_xname(dv)); #endif continue; } for (ck = i = 0; i < DEV_BSIZE; i++) ck += mbr[i]; for (m = i = 0; i < numbig; i++) { be = &big->disk[i]; #ifdef GEOM_DEBUG printf("match %s with %d " "dev ck %x bios ck %x\n", device_xname(dv), i, ck, be->cksum); #endif if (be->flags & BI_GEOM_INVALID) continue; if (be->cksum == ck && memcmp(&mbr[MBR_PART_OFFSET], be->dosparts, MBR_PART_COUNT * sizeof(struct mbr_partition)) == 0) { #ifdef GEOM_DEBUG printf("matched BIOS disk %x with %s\n", be->dev, device_xname(dv)); #endif x86_alldisks->dl_nativedisks[n]. ni_biosmatches[m++] = i; } } x86_alldisks->dl_nativedisks[n].ni_nmatches = m; vput(tv); } } deviter_release(&di); } /* * Helper function for findroot(): * Return non-zero if wedge device matches bootinfo. */ static int match_bootwedge(device_t dv, struct btinfo_bootwedge *biw) { MD5_CTX ctx; struct vnode *tmpvn; int error; uint8_t bf[DEV_BSIZE]; uint8_t hash[16]; int found = 0; daddr_t blk; uint64_t nblks; /* * If the boot loader didn't specify the sector, abort. */ if (biw->matchblk == -1) return (0); if ((tmpvn = opendisk(dv)) == NULL) return 0; MD5Init(&ctx); for (blk = biw->matchblk, nblks = biw->matchnblks; nblks != 0; nblks--, blk++) { error = vn_rdwr(UIO_READ, tmpvn, (void *) bf, sizeof(bf), blk * DEV_BSIZE, UIO_SYSSPACE, 0, NOCRED, NULL, NULL); if (error) { printf("findroot: unable to read block %" PRId64 " " "of dev %s (%d)\n", blk, device_xname(dv), error); goto closeout; } MD5Update(&ctx, bf, sizeof(bf)); } MD5Final(hash, &ctx); /* Compare with the provided hash. */ found = memcmp(biw->matchhash, hash, sizeof(hash)) == 0; closeout: VOP_CLOSE(tmpvn, FREAD, NOCRED); vput(tmpvn); return (found); } /* * Helper function for findroot(): * Return non-zero if disk device matches bootinfo. */ static int match_bootdisk(device_t dv, struct btinfo_bootdisk *bid) { struct vnode *tmpvn; int error; struct disklabel label; int found = 0; if (device_is_a(dv, "dk")) return 0; /* * A disklabel is required here. The boot loader doesn't refuse * to boot from a disk without a label, but this is normally not * wanted. */ if (bid->labelsector == -1) return (0); if ((tmpvn = opendisk(dv)) == NULL) return 0; error = VOP_IOCTL(tmpvn, DIOCGDINFO, &label, FREAD, NOCRED); if (error) { /* * XXX Can't happen -- open() would have errored out * or faked one up. */ printf("findroot: can't get label for dev %s (%d)\n", device_xname(dv), error); goto closeout; } /* Compare with our data. */ if (label.d_type == bid->label.type && label.d_checksum == bid->label.checksum && strncmp(label.d_packname, bid->label.packname, 16) == 0) found = 1; closeout: VOP_CLOSE(tmpvn, FREAD, NOCRED); vput(tmpvn); return (found); } /* * Attempt to find the device from which we were booted. If we can do so, * and not instructed not to do so, change rootdev to correspond to the * load device. */ static void findroot(void) { struct btinfo_rootdevice *biv; struct btinfo_bootdisk *bid; struct btinfo_bootwedge *biw; struct btinfo_biosgeom *big; device_t dv; deviter_t di; if (booted_device) return; if (lookup_bootinfo(BTINFO_NETIF) != NULL) { /* * We got netboot interface information, but device_register() * failed to match it to a configured device. Boot disk * information cannot be present at the same time, so give * up. */ printf("findroot: netboot interface not found.\n"); return; } if ((biv = lookup_bootinfo(BTINFO_ROOTDEVICE)) != NULL) { for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { cfdata_t cd; size_t len; if (device_class(dv) != DV_DISK) continue; cd = device_cfdata(dv); len = strlen(cd->cf_name); if (strncmp(cd->cf_name, biv->devname, len) == 0 && biv->devname[len] - '0' == cd->cf_unit) { handle_wedges(dv, biv->devname[len + 1] - 'a'); break; } } deviter_release(&di); if (dv != NULL) return; } if ((biw = lookup_bootinfo(BTINFO_BOOTWEDGE)) != NULL) { /* * Scan all disk devices for ones that match the passed data. * Don't break if one is found, to get possible multiple * matches - for problem tracking. Use the first match anyway * because lower devices numbers are more likely to be the * boot device. */ for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { if (device_class(dv) != DV_DISK) continue; if (is_valid_disk(dv)) { /* * Don't trust BIOS device numbers, try * to match the information passed by the * boot loader instead. */ if ((biw->biosdev & 0x80) == 0 || match_bootwedge(dv, biw) == 0) continue; goto bootwedge_found; } continue; bootwedge_found: if (booted_device) { printf("WARNING: double match for boot " "device (%s, %s)\n", device_xname(booted_device), device_xname(dv)); continue; } dkwedge_set_bootwedge(dv, biw->startblk, biw->nblks); } deviter_release(&di); if (booted_wedge) return; } if ((bid = lookup_bootinfo(BTINFO_BOOTDISK)) != NULL) { /* * Scan all disk devices for ones that match the passed data. * Don't break if one is found, to get possible multiple * matches - for problem tracking. Use the first match anyway * because lower device numbers are more likely to be the * boot device. */ for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { if (device_class(dv) != DV_DISK) continue; if (device_is_a(dv, "fd")) { /* * Assume the configured unit number matches * the BIOS device number. (This is the old * behavior.) Needs some ideas how to handle * the BIOS's "swap floppy drive" options. */ /* XXX device_unit() abuse */ if ((bid->biosdev & 0x80) != 0 || device_unit(dv) != bid->biosdev) continue; goto bootdisk_found; } if (is_valid_disk(dv)) { /* * Don't trust BIOS device numbers, try * to match the information passed by the * boot loader instead. */ if ((bid->biosdev & 0x80) == 0 || match_bootdisk(dv, bid) == 0) continue; goto bootdisk_found; } continue; bootdisk_found: if (booted_device) { printf("WARNING: double match for boot " "device (%s, %s)\n", device_xname(booted_device), device_xname(dv)); continue; } handle_wedges(dv, bid->partition); } deviter_release(&di); if (booted_device) return; /* * No booted device found; check CD-ROM boot at last. * * Our bootloader assumes CD-ROM boot if biosdev is larger * than the number of hard drives recognized by the BIOS. * The number of drives can be found in BTINFO_BIOSGEOM here. * * See src/sys/arch/i386/stand/boot/devopen.c and * src/sys/arch/i386/stand/lib/bootinfo_biosgeom.c . */ if ((big = lookup_bootinfo(BTINFO_BIOSGEOM)) != NULL && bid->biosdev > 0x80 + big->num) { /* * XXX * There is no proper way to detect which unit is * recognized as a bootable CD-ROM drive by the BIOS. * Assume the first unit is the one. */ for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST); dv != NULL; dv = deviter_next(&di)) { if (device_class(dv) == DV_DISK && device_is_a(dv, "cd")) { booted_device = dv; booted_partition = 0; break; } } deviter_release(&di); } } } void cpu_rootconf(void) { findroot(); matchbiosdisks(); if (booted_wedge) { KASSERT(booted_device != NULL); aprint_normal("boot device: %s (%s)\n", device_xname(booted_wedge), device_xname(booted_device)); setroot(booted_wedge, 0); } else { aprint_normal("boot device: %s\n", booted_device ? device_xname(booted_device) : ""); setroot(booted_device, booted_partition); } } void device_register(device_t dev, void *aux) { #if NPCI > 0 static bool found_console = false; #endif /* * Handle network interfaces here, the attachment information is * not available driver-independently later. * * For disks, there is nothing useful available at attach time. */ if (device_class(dev) == DV_IFNET) { struct btinfo_netif *bin = lookup_bootinfo(BTINFO_NETIF); if (bin == NULL) return; /* * We don't check the driver name against the device name * passed by the boot ROM. The ROM should stay usable if * the driver becomes obsolete. The physical attachment * information (checked below) must be sufficient to * idenfity the device. */ if (bin->bus == BI_BUS_ISA && device_is_a(device_parent(dev), "isa")) { struct isa_attach_args *iaa = aux; /* Compare IO base address */ /* XXXJRT What about multiple IO addrs? */ if (iaa->ia_nio > 0 && bin->addr.iobase == iaa->ia_io[0].ir_addr) goto found; } #if NPCI > 0 if (bin->bus == BI_BUS_PCI && device_is_a(device_parent(dev), "pci")) { struct pci_attach_args *paa = aux; int b, d, f; /* * Calculate BIOS representation of: * * * * and compare. */ pci_decompose_tag(paa->pa_pc, paa->pa_tag, &b, &d, &f); if (bin->addr.tag == ((b << 8) | (d << 3) | f)) goto found; } #endif /* NPCI > 0 */ } #if NISA > 0 && NACPICA > 0 #if notyet if (device_is_a(dev, "isa") && acpi_active) { if (!(AcpiGbl_FADT.BootFlags & ACPI_FADT_LEGACY_DEVICES)) prop_dictionary_set_bool(device_properties(dev), "no-legacy-devices", true); } #endif #endif /* NISA > 0 && NACPICA > 0 */ #if NPCI > 0 if (device_parent(dev) && device_is_a(device_parent(dev), "pci") && found_console == false) { struct btinfo_framebuffer *fbinfo; struct pci_attach_args *pa = aux; prop_dictionary_t dict; if (PCI_CLASS(pa->pa_class) == PCI_CLASS_DISPLAY) { #if NWSDISPLAY > 0 && NGENFB > 0 extern struct vcons_screen x86_genfb_console_screen; struct rasops_info *ri; ri = &x86_genfb_console_screen.scr_ri; #endif fbinfo = lookup_bootinfo(BTINFO_FRAMEBUFFER); dict = device_properties(dev); /* * framebuffer drivers other than genfb can work * without the address property */ if (fbinfo != NULL) { if (fbinfo->physaddr != 0) { prop_dictionary_set_uint32(dict, "width", fbinfo->width); prop_dictionary_set_uint32(dict, "height", fbinfo->height); prop_dictionary_set_uint8(dict, "depth", fbinfo->depth); prop_dictionary_set_uint16(dict, "linebytes", fbinfo->stride); prop_dictionary_set_uint64(dict, "address", fbinfo->physaddr); #if NWSDISPLAY > 0 && NGENFB > 0 if (ri->ri_bits != NULL) { prop_dictionary_set_uint64(dict, "virtual_address", (vaddr_t)ri->ri_bits); } #endif } #if notyet prop_dictionary_set_bool(dict, "splash", fbinfo->flags & BI_FB_SPLASH ? true : false); #endif if (fbinfo->depth == 8) { gfb_cb.gcc_cookie = NULL; gfb_cb.gcc_set_mapreg = x86_genfb_set_mapreg; prop_dictionary_set_uint64(dict, "cmap_callback", (uint64_t)&gfb_cb); } if (fbinfo->physaddr != 0) { mode_cb.gmc_setmode = x86_genfb_setmode; prop_dictionary_set_uint64(dict, "mode_callback", (uint64_t)&mode_cb); } #if NWSDISPLAY > 0 && NGENFB > 0 if (device_is_a(dev, "genfb")) { x86_genfb_set_console_dev(dev); #ifdef DDB db_trap_callback = x86_genfb_ddb_trap_callback; #endif } #endif } prop_dictionary_set_bool(dict, "is_console", true); prop_dictionary_set_bool(dict, "clear-screen", false); #if NWSDISPLAY > 0 && NGENFB > 0 prop_dictionary_set_uint16(dict, "cursor-row", x86_genfb_console_screen.scr_ri.ri_crow); #endif #if notyet prop_dictionary_set_bool(dict, "splash", fbinfo->flags & BI_FB_SPLASH ? true : false); #endif pmf_cb.gpc_suspend = x86_genfb_suspend; pmf_cb.gpc_resume = x86_genfb_resume; prop_dictionary_set_uint64(dict, "pmf_callback", (uint64_t)&pmf_cb); #ifdef VGA_POST vga_posth = vga_post_init(pa->pa_bus, pa->pa_device, pa->pa_function); #endif found_console = true; return; } } #endif return; found: if (booted_device) { /* XXX should be a panic() */ printf("WARNING: double match for boot device (%s, %s)\n", device_xname(booted_device), device_xname(dev)); return; } booted_device = dev; }