/* $NetBSD: pnpbios.c,v 1.45 2005/02/03 20:08:44 perry Exp $ */ /* * Copyright (c) 2000 Jason R. Thorpe. All rights reserved. * Copyright (c) 2000 Christian E. Hopps. All rights reserved. * Copyright (c) 1999 * Matthias Drochner. 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. 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 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. */ /* * PnP BIOS documentation is available at the following locations. * * http://www.microsoft.com/hwdev/download/respec/pnpbios.zip * http://www.microsoft.com/hwdev/download/respec/biosclar.zip * http://www.microsoft.com/hwdev/download/resources/specs/devids.txt * * PNPBIOSEVENTS is unfinished. After coding what I did I discovered * I had no platforms to test on so someone else will need to finish * it. I didn't want to toss the code though */ #include __KERNEL_RCSID(0, "$NetBSD: pnpbios.c,v 1.45 2005/02/03 20:08:44 perry Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_pnpbiosverbose.h" #include "locators.h" #ifdef PNPBIOSVERBOSE int pnpbiosverbose = 1; #else int pnpbiosverbose = 0; #endif #ifdef PNPBIOSDEBUG #ifdef PNPBIOSDEBUG_VALUE int pnpbiosdebug = PNPBIOSDEBUG_VALUE; #else int pnpbiosdebug = 1; #endif #define DPRINTF(x) if (pnpbiosdebug) printf x #else #define DPRINTF(x) #endif #ifdef PNPBIOSEVENTSDEBUG #define EDPRINTF(x) printf x #else #define EDPRINTF(x) #endif struct pnpbios_softc { struct device sc_dev; isa_chipset_tag_t sc_ic; struct proc *sc_evthread; int sc_version; int sc_control; #ifdef PNPBIOSEVENTS u_int8_t *sc_evaddr; int sc_threadrun; int sc_docked; #endif }; #define PNPGET4(p) ((p)[0] + ((p)[1] << 8) + \ ((p)[2] << 16) + ((p)[3] << 24)) /* bios calls */ #if 0 /* XXX these are not called */ static int pnpbios_getapmtable(u_int8_t *tab, size_t *len); static int pnpbios_setnode(int flags, int idx, const u_int8_t *buf, size_t len); #endif static int pnpbios_getnode(int flags, int *idxp, u_int8_t *buf, size_t len); static int pnpbios_getnumnodes(int *nump, size_t *sizep); #ifdef PNPBIOSEVENTS static int pnpbios_getdockinfo(struct pnpdockinfo *di); static void pnpbios_create_event_thread(void *arg); static int pnpbios_getevent(u_int16_t *event); static void pnpbios_event_thread(void *arg); static int pnpbios_sendmessage(int msg); #endif /* configuration stuff */ static caddr_t pnpbios_mapit(u_long addr, u_long len, int prot); static caddr_t pnpbios_find(void); static int pnpbios_match(struct device *parent, struct cfdata *match, void *aux); static void pnpbios_attach(struct device *parent, struct device *self, void *aux); static void pnpbios_printres(struct pnpresources *r); static int pnpbios_print(void *aux, const char *pnp); static void pnpbios_id_to_string(u_int32_t pnpid, char *s); static int pnpbios_attachnode(struct pnpbios_softc *sc, int idx, const u_int8_t *buf, size_t len, int matchonly); static int pnp_scan(const u_int8_t **bufp, size_t maxlen, struct pnpresources *pnpresources, int in_depends); static int pnpbios_submatch(struct device *, struct cfdata *, const locdesc_t *, void *); extern int pnpbioscall(int); static void pnpbios_enumerate(struct pnpbios_softc *sc); #ifdef PNPBIOSEVENTS static int pnpbios_update_dock_status(struct pnpbios_softc *sc); #endif /* scanning functions */ static int pnp_compatid(struct pnpresources *, const void *, size_t); static int pnp_newirq(struct pnpresources *, const void *, size_t); static int pnp_newdma(struct pnpresources *, const void *, size_t); static int pnp_newioport(struct pnpresources *, const void *, size_t); static int pnp_newfixedioport(struct pnpresources *, const void *, size_t); #ifdef PNPBIOSDEBUG static int pnp_debugdump(struct pnpresources *, const void *, size_t); #endif /* * small ressource types (beginning with 1) */ static struct{ int (*handler)(struct pnpresources *, const void *, size_t); int minlen, maxlen; } smallrescs[] = { {0, 2, 2}, /* PnP version number */ {0, 5, 6}, /* logical device id */ {pnp_compatid, 4, 4}, /* compatible device id */ {pnp_newirq, 2, 3}, /* irq descriptor */ {pnp_newdma, 2, 2}, /* DMA descriptor */ {0, 0, 1}, /* start dep */ {0, 0, 0}, /* end dep */ {pnp_newioport, 7, 7}, /* io descriptor */ {pnp_newfixedioport, 3, 3}, /* fixed io descriptor */ {0, -1, -1}, /* reserved */ {0, -1, -1}, {0, -1, -1}, {0, -1, -1}, {0, 1, 7}, /* vendor defined */ {0, 1, 1} /* end */ }; CFATTACH_DECL(pnpbios, sizeof(struct pnpbios_softc), pnpbios_match, pnpbios_attach, NULL, NULL); /* * Private stack and return value buffer. Spec (1.0a, ch. 4.3) says that * 1024 bytes must be available to the BIOS function. */ #define PNPBIOS_BUFSIZE 4096 int pnpbios_enabled = 1; size_t pnpbios_entry; caddr_t pnpbios_scratchbuf; /* * There can be only one of these, and the i386 ISA code needs to * reference this. */ struct pnpbios_softc *pnpbios_softc; #define PNPBIOS_SIGNATURE ('$' | ('P' << 8) | ('n' << 16) | ('P' << 24)) static caddr_t pnpbios_find(void) { caddr_t p, c; u_int8_t cksum; size_t structlen; for (p = (caddr_t)ISA_HOLE_VADDR(0xf0000); p <= (caddr_t)ISA_HOLE_VADDR(0xffff0); p += 16) { if (*(int *)p != PNPBIOS_SIGNATURE) continue; structlen = *(u_int8_t *)(p + 5); if ((structlen < 0x21) || ((p + structlen - 1) > (caddr_t)ISA_HOLE_VADDR(0xfffff))) continue; cksum = 0; for (c = p; c < p + structlen; c++) cksum += *(u_int8_t *)c; if (cksum != 0) continue; if (*(char *)(p + 4) != 0x10) { printf("unknown version %x\n", *(char *)(p + 4)); continue; } return (p); } return (0); } int pnpbios_probe(void) { return (pnpbios_find() != 0); } static int pnpbios_match(struct device *parent, struct cfdata *match, void *aux) { /* There can be only one! */ if (pnpbios_softc != NULL) return (0); return (pnpbios_enabled); } static caddr_t pnpbios_mapit(u_long addr, u_long len, int prot) { u_long startpa, pa, endpa; vaddr_t startva, va; pa = startpa = x86_trunc_page(addr); endpa = x86_round_page(addr + len); va = startva = uvm_km_valloc(kernel_map, endpa - startpa); if (!startva) return (0); for (; pa < endpa; pa += PAGE_SIZE, va += PAGE_SIZE) pmap_kenter_pa(va, pa, prot); pmap_update(pmap_kernel()); return ((caddr_t)(startva + (addr - startpa))); } static void pnpbios_attach(struct device *parent, struct device *self, void *aux) { struct pnpbios_softc *sc = (struct pnpbios_softc *)self; struct pnpbios_attach_args *paa = aux; caddr_t p; unsigned int codepbase, datapbase, evaddrp; caddr_t codeva, datava; extern char pnpbiostramp[], epnpbiostramp[]; int res, num, size; #ifdef PNPBIOSEVENTS int evtype; #endif pnpbios_softc = sc; sc->sc_ic = paa->paa_ic; p = pnpbios_find(); if (!p) panic("pnpbios_attach: disappeared"); sc->sc_version = *(u_int8_t *)(p + 0x04); sc->sc_control = *(u_int8_t *)(p + 0x06); evaddrp = *(u_int32_t *)(p + 0x09); codepbase = *(u_int32_t *)(p + 0x13); datapbase = *(u_int32_t *)(p + 0x1d); pnpbios_entry = *(u_int16_t *)(p + 0x11); if (pnpbiosverbose) { printf(": code %x, data %x, entry %x, control %x eventp %x\n%s", codepbase, datapbase, pnpbios_entry, sc->sc_control, (int)evaddrp, self->dv_xname); } #ifdef PNPBIOSEVENTS /* if we have an event mechnism queue a thread to deal with them */ evtype = (sc->sc_control & PNP_IC_CONTORL_EVENT_MASK); if (evtype == PNP_IC_CONTROL_EVENT_POLL) { sc->sc_evaddr = pnpbios_mapit(evaddrp, PAGE_SIZE, VM_PROT_READ | VM_PROT_WRITE); if (!sc->sc_evaddr) printf("%s: couldn't map event flag 0x%08x\n", sc->sc_dev.dv_xname, evaddrp); } #endif codeva = pnpbios_mapit(codepbase, 0x10000, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE); datava = pnpbios_mapit(datapbase, 0x10000, VM_PROT_READ | VM_PROT_WRITE); if (codeva == 0 || datava == 0) { printf("no vm for mapping\n"); return; } pnpbios_scratchbuf = malloc(PNPBIOS_BUFSIZE, M_DEVBUF, M_NOWAIT); setsegment(&gdt[GPNPBIOSCODE_SEL].sd, codeva, 0xffff, SDT_MEMERA, SEL_KPL, 0, 0); setsegment(&gdt[GPNPBIOSDATA_SEL].sd, datava, 0xffff, SDT_MEMRWA, SEL_KPL, 0, 0); setsegment(&gdt[GPNPBIOSSCRATCH_SEL].sd, pnpbios_scratchbuf, PNPBIOS_BUFSIZE - 1, SDT_MEMRWA, SEL_KPL, 0, 0); setsegment(&gdt[GPNPBIOSTRAMP_SEL].sd, pnpbiostramp, epnpbiostramp - pnpbiostramp - 1, SDT_MEMERA, SEL_KPL, 1, 0); res = pnpbios_getnumnodes(&num, &size); if (res) { printf("pnpbios_getnumnodes: error %d\n", res); return; } printf(": nodes %d, max len %d\n", num, size); #ifdef PNPBIOSEVENTS EDPRINTF(("%s: event flag vaddr 0x%08x\n", sc->sc_dev.dv_xname, (int)sc->sc_evaddr)); /* Set initial dock status. */ sc->sc_docked = -1; (void) pnpbios_update_dock_status(sc); #endif /* Enumerate the device nodes. */ pnpbios_enumerate(sc); #ifdef PNPBIOSEVENTS /* if we have an event mechnism queue a thread to deal with them */ /* XXX need to update with irq if we do that */ if (evtype != PNP_IC_CONTROL_EVENT_NONE) { if (evtype != PNP_IC_CONTROL_EVENT_POLL || sc->sc_evaddr) { sc->sc_threadrun = 1; config_pending_incr(); kthread_create(pnpbios_create_event_thread, sc); } } #endif } static void pnpbios_enumerate(struct pnpbios_softc *sc) { int res, num, i, size, idx, dynidx; struct pnpdevnode *dn; u_int8_t *buf; res = pnpbios_getnumnodes(&num, &size); if (res) { printf("%s: pnpbios_getnumnodes: error %d\n", sc->sc_dev.dv_xname, res); return; } buf = malloc(size, M_DEVBUF, M_NOWAIT); if (buf == NULL) { printf("%s: unable to allocate node buffer\n", sc->sc_dev.dv_xname); return; } /* * Loop through the list of indices getting data and match/attaching * each as appropriate. * * Unfortunately, some BIOSes seem to have fatal bugs getting the * dynamic (i.e. currently active) configuration, for instance some * Sony VAIO laptops, including the PCG-Z505HE. They don't have such a * problem with that static (i.e. next boot time) configuration, * however. The workaround is to get the static configuration for all * indices, and only get dynamic configuration for devices where the * match is positive. * * This seems to work conveniently as the indices that cause * crashes (and it seems to vary from machine to machine) do not * seem to be for devices that NetBSD's pnpbios supports. */ idx = 0; for (i = 0; i < num && idx != 0xff; i++) { DPRINTF(("%s: getting info for index %d\n", sc->sc_dev.dv_xname, idx)); dynidx = idx; res = pnpbios_getnode(PNP_CF_DEVCONF_STATIC, &idx, buf, size); if (res) { printf("%s: index %d error %d " "getting static configuration\n", sc->sc_dev.dv_xname, idx, res); continue; } dn = (struct pnpdevnode *)buf; if (!pnpbios_attachnode(sc, dn->dn_handle, buf, dn->dn_size, 1)) { DPRINTF(("%s handle %d: no match from static config\n", sc->sc_dev.dv_xname, dn->dn_handle)); continue; } res = pnpbios_getnode(PNP_CF_DEVCONF_DYNAMIC, &dynidx, buf, size); if (res) { printf("%s: index %d error %d " "getting dynamic configuration\n", sc->sc_dev.dv_xname, dynidx, res); continue; } dn = (struct pnpdevnode *)buf; if (!pnpbios_attachnode(sc, dn->dn_handle, buf, dn->dn_size, 0)) { DPRINTF(("%s handle %d: no match from dynamic config\n", sc->sc_dev.dv_xname, dn->dn_handle)); continue; } } if (i != num) printf("%s: got only %d nodes\n", sc->sc_dev.dv_xname, i); if (idx != 0xff) printf("%s: last index %d\n", sc->sc_dev.dv_xname, idx); free(buf, M_DEVBUF); } #ifdef PNPBIOSEVENTS static int pnpbios_update_dock_status(struct pnpbios_softc *sc) { struct pnpdockinfo di; const char *when, *style; int res, odocked = sc->sc_docked; res = pnpbios_getdockinfo(&di); if (res == PNP_RC_SYSTEM_NOT_DOCKED) { sc->sc_docked = 0; if (odocked != sc->sc_docked) printf("%s: not docked\n", sc->sc_dev.dv_xname); } else if (res) { EDPRINTF(("%s: dockinfo failed 0x%02x\n", sc->sc_dev.dv_xname, res)); } else { sc->sc_docked = 1; if (odocked != sc->sc_docked) { char idstr[8]; pnpbios_id_to_string(di.di_id, idstr); printf("%s: dock id %s", sc->sc_dev.dv_xname, idstr); if (pnpbiosverbose) { if (di.di_serial != -1) printf(", serial number %d", di.di_serial); } switch (di.di_cap & PNP_DI_DOCK_STYLE_MASK) { case PNP_DI_DOCK_STYLE_SUPRISE: style = "surprise"; break; case PNP_DI_DOCK_STYLE_VCR: style = "controlled"; break; default: style = "