/* $NetBSD: pnpbios.c,v 1.73 2017/02/26 10:49:25 maya 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.73 2017/02/26 10:49:25 maya Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_pnpbios.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) aprint_normal x #else #define DPRINTF(x) #endif #ifdef PNPBIOSEVENTSDEBUG #define EDPRINTF(x) aprint_normal x #else #define EDPRINTF(x) #endif struct pnpbios_softc { device_t sc_dev; isa_chipset_tag_t sc_ic; lwp_t *sc_evthread; int sc_version; int sc_control; #ifdef PNPBIOSEVENTS uint8_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(uint8_t *, size_t *); static int pnpbios_setnode(int, int, const uint8_t *, size_t); #endif static int pnpbios_getnode(int, int *, uint8_t *, size_t); static int pnpbios_getnumnodes(int *, size_t *); #ifdef PNPBIOSEVENTS static int pnpbios_getdockinfo(struct pnpdockinfo *); static int pnpbios_getevent(uint16_t *); static void pnpbios_event_thread(void *); static int pnpbios_sendmessage(int); #endif /* configuration stuff */ static void * pnpbios_mapit(paddr_t, u_long, vm_prot_t); static void * pnpbios_find(void); static int pnpbios_match(device_t, cfdata_t, void *); static void pnpbios_attach(device_t, device_t, void *); static void pnpbios_printres(struct pnpresources *); static int pnpbios_print(void *aux, const char *); static void pnpbios_id_to_string(uint32_t, char *); static int pnpbios_attachnode(struct pnpbios_softc *, int, const uint8_t *, size_t, int); static int pnp_scan(const uint8_t **, size_t, struct pnpresources *, int); extern int pnpbioscall(int); static void pnpbios_enumerate(struct pnpbios_softc *); #ifdef PNPBIOSEVENTS static int pnpbios_update_dock_status(struct pnpbios_softc *); #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 const 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_NEW(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; char *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 void * pnpbios_find(void) { char *p, *c; uint8_t cksum; size_t structlen; for (p = (char *)ISA_HOLE_VADDR(0xf0000); p <= (char *)ISA_HOLE_VADDR(0xffff0); p += 16) { if (*(int *)p != PNPBIOS_SIGNATURE) continue; structlen = *(uint8_t *)(p + 5); if ((structlen < 0x21) || ((p + structlen - 1) > (char *)ISA_HOLE_VADDR(0xfffff))) continue; cksum = 0; for (c = p; c < p + structlen; c++) cksum += *(uint8_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(device_t parent, cfdata_t match, void *aux) { /* There can be only one! */ if (pnpbios_softc != NULL) return (0); return (pnpbios_enabled); } static void * pnpbios_mapit(paddr_t addr, u_long len, vm_prot_t prot) { paddr_t startpa, pa, endpa; vaddr_t startva, va; pa = startpa = x86_trunc_page(addr); endpa = x86_round_page(addr + len); va = startva = uvm_km_alloc(kernel_map, endpa - startpa, 0, UVM_KMF_VAONLY); if (!startva) return (0); for (; pa < endpa; pa += PAGE_SIZE, va += PAGE_SIZE) pmap_kenter_pa(va, pa, prot, 0); pmap_update(pmap_kernel()); return ((void *)(startva + (vaddr_t)(addr - startpa))); } static void pnpbios_attach(device_t parent, device_t self, void *aux) { struct pnpbios_softc *sc = device_private(self); struct pnpbios_attach_args *paa = aux; char *p; unsigned int codepbase, datapbase, evaddrp; void *codeva, *datava; extern char pnpbiostramp[], epnpbiostramp[]; int res, num, size; #ifdef PNPBIOSEVENTS int evtype; #endif aprint_naive("\n"); pnpbios_softc = sc; sc->sc_dev = self; sc->sc_ic = paa->paa_ic; p = pnpbios_find(); if (!p) panic("pnpbios_attach: disappeared"); sc->sc_version = *(uint8_t *)(p + 0x04); sc->sc_control = *(uint8_t *)(p + 0x06); evaddrp = *(uint32_t *)(p + 0x09); codepbase = *(uint32_t *)(p + 0x13); datapbase = *(uint32_t *)(p + 0x1d); pnpbios_entry = *(uint16_t *)(p + 0x11); if (pnpbiosverbose) { aprint_normal(": code %x, data %x, entry %x, control %x," " eventp %x\n%s", codepbase, datapbase, pnpbios_entry, sc->sc_control, (unsigned int)evaddrp, device_xname(self)); } #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) aprint_error_dev(self, "couldn't map event flag 0x%08x\n", 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) { aprint_error(": no vm for mapping\n"); return; } pnpbios_scratchbuf = malloc(PNPBIOS_BUFSIZE, M_DEVBUF, M_NOWAIT); setsegment(&gdtstore[GPNPBIOSCODE_SEL].sd, codeva, 0xffff, SDT_MEMERA, SEL_KPL, 0, 0); setsegment(&gdtstore[GPNPBIOSDATA_SEL].sd, datava, 0xffff, SDT_MEMRWA, SEL_KPL, 0, 0); setsegment(&gdtstore[GPNPBIOSSCRATCH_SEL].sd, pnpbios_scratchbuf, PNPBIOS_BUFSIZE - 1, SDT_MEMRWA, SEL_KPL, 0, 0); setsegment(&gdtstore[GPNPBIOSTRAMP_SEL].sd, pnpbiostramp, epnpbiostramp - pnpbiostramp - 1, SDT_MEMERA, SEL_KPL, 1, 0); res = pnpbios_getnumnodes(&num, &size); if (res) { aprint_error(": pnpbios_getnumnodes: error %d\n", res); return; } aprint_normal(": nodes %d, max len %d\n", num, size); #ifdef PNPBIOSEVENTS EDPRINTF(("%s: event flag vaddr 0x%08x\n", device_xname(self), (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(sc->sc_dev); if (kthread_create(PRI_NONE, 0, NULL, pnpbios_event_thread, sc, &sc->sc_evthread, "%s", device_xname(self))) panic("pnpbios: create event thread"); } } #endif } static void pnpbios_enumerate(struct pnpbios_softc *sc) { int res, num, i, size, idx, dynidx; struct pnpdevnode *dn; uint8_t *buf; res = pnpbios_getnumnodes(&num, &size); if (res) { aprint_error_dev(sc->sc_dev, "pnpbios_getnumnodes: error %d\n", res); return; } buf = malloc(size, M_DEVBUF, M_NOWAIT); if (buf == NULL) { aprint_error_dev(sc->sc_dev, "unable to allocate node buffer\n"); 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", device_xname(sc->sc_dev), idx)); dynidx = idx; res = pnpbios_getnode(PNP_CF_DEVCONF_STATIC, &idx, buf, size); if (res) { aprint_error_dev(sc->sc_dev, "index %d error %d " "getting static configuration\n", 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", device_xname(sc->sc_dev), dn->dn_handle)); continue; } res = pnpbios_getnode(PNP_CF_DEVCONF_DYNAMIC, &dynidx, buf, size); if (res) { aprint_error_dev(sc->sc_dev, "index %d error %d " "getting dynamic configuration\n", 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", device_xname(sc->sc_dev), dn->dn_handle)); continue; } } if (i != num) aprint_error_dev(sc->sc_dev, "got only %d nodes\n", i); if (idx != 0xff) aprint_error_dev(sc->sc_dev, "last index %d\n", 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", device_xname(sc->sc_dev)); } else if (res) { EDPRINTF(("%s: dockinfo failed 0x%02x\n", device_xname(sc->sc_dev), 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", device_xname(sc->sc_dev), 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 = "