/* * Scan the resource data in the node's data area for compatible device IDs * and descriptions. */ static void biospnp_scanresdata(struct pnpinfo *pi, struct pnp_devNode *dn) { int tag, i, rlen, dlen; u_int8_t *p; char *str; p = dn->dn_data; /* point to resource data */ dlen = dn->dn_size - (p - (u_int8_t *)dn); /* length of resource data */ for (i = 0; i < dlen; i+= rlen) { tag = p[i]; i++; if (PNP_RES_TYPE(tag) == 0) { rlen = PNP_SRES_LEN(tag); /* small resource */ switch (PNP_SRES_NUM(tag)) { case COMP_DEVICE_ID: /* got a compatible device ID */ pnp_addident(pi, pnp_eisaformat(p + i)); break; case END_TAG: return; } } else { /* large resource */ rlen = *(u_int16_t *)(p + i); i += sizeof(u_int16_t); switch(PNP_LRES_NUM(tag)) { case ID_STRING_ANSI: str = malloc(rlen + 1); bcopy(p + i, str, rlen); str[rlen] = 0; if (pi->pi_desc == NULL) { pi->pi_desc = str; } else { free(str); } break; } } } }
u_char *pnp_scan_resources(device_t dev, u_char *resources, int len, struct isa_config *config, int ldn, pnp_scan_cb *cb) { u_char *p; u_char tag; int l; p = resources; while (len > 0) { tag = *p++; len--; if (PNP_RES_TYPE(tag) == 0) { /* small resource */ l = PNP_SRES_LEN(tag); if (len < l) break; if ((*cb)(dev, tag, p, l, config, ldn)) return (p + l); if (PNP_SRES_NUM(tag) == PNP_TAG_END) return (p + l); } else { /* large resource */ if (len < 2) break; l = I16(p); p += 2; len -= 2; if (len < l) break; if ((*cb)(dev, tag, p, l, config, ldn)) return (p + l); } p += l; len -= l; } return NULL; }
static int pnp_parse_desc(device_t dev, u_char tag, u_char *res, int len, struct isa_config *config, int ldn) { char buf[100]; u_int32_t id; u_int32_t compat_id; int temp; id = isa_get_logicalid(dev); if (PNP_RES_TYPE(tag) == 0) { /* Small resource */ switch (PNP_SRES_NUM(tag)) { case PNP_TAG_VERSION: case PNP_TAG_VENDOR: /* these descriptors are quietly ignored */ break; case PNP_TAG_LOGICAL_DEVICE: case PNP_TAG_START_DEPENDANT: case PNP_TAG_END_DEPENDANT: if (bootverbose) pnp_printf(id, "unexpected small tag %d\n", PNP_SRES_NUM(tag)); /* shouldn't happen; quit now */ return (1); case PNP_TAG_COMPAT_DEVICE: /* * Got a compatible device id resource. * Should keep a list of compat ids in the device. */ bcopy(res, &compat_id, 4); if (isa_get_compatid(dev) == 0) isa_set_compatid(dev, compat_id); break; case PNP_TAG_IRQ_FORMAT: if (config->ic_nirq == ISA_NIRQ) { pnp_printf(id, "too many irqs\n"); return (1); } if (I16(res) == 0) { /* a null descriptor */ config->ic_irqmask[config->ic_nirq] = 0; config->ic_nirq++; break; } if (bootverbose) pnp_printf(id, "adding irq mask %#02x\n", I16(res)); config->ic_irqmask[config->ic_nirq] = I16(res); config->ic_nirq++; break; case PNP_TAG_DMA_FORMAT: if (config->ic_ndrq == ISA_NDRQ) { pnp_printf(id, "too many drqs\n"); return (1); } if (res[0] == 0) { /* a null descriptor */ config->ic_drqmask[config->ic_ndrq] = 0; config->ic_ndrq++; break; } if (bootverbose) pnp_printf(id, "adding dma mask %#02x\n", res[0]); config->ic_drqmask[config->ic_ndrq] = res[0]; config->ic_ndrq++; break; case PNP_TAG_IO_RANGE: if (config->ic_nport == ISA_NPORT) { pnp_printf(id, "too many ports\n"); return (1); } if (res[6] == 0) { /* a null descriptor */ config->ic_port[config->ic_nport].ir_start = 0; config->ic_port[config->ic_nport].ir_end = 0; config->ic_port[config->ic_nport].ir_size = 0; config->ic_port[config->ic_nport].ir_align = 0; config->ic_nport++; break; } if (bootverbose) { pnp_printf(id, "adding io range " "%#x-%#x, size=%#x, " "align=%#x\n", I16(res + 1), I16(res + 3) + res[6]-1, res[6], res[5]); } config->ic_port[config->ic_nport].ir_start = I16(res + 1); config->ic_port[config->ic_nport].ir_end = I16(res + 3) + res[6] - 1; config->ic_port[config->ic_nport].ir_size = res[6]; if (res[5] == 0) { /* Make sure align is at least one */ res[5] = 1; } config->ic_port[config->ic_nport].ir_align = res[5]; config->ic_nport++; pnp_check_quirks(isa_get_vendorid(dev), isa_get_logicalid(dev), ldn, config); break; case PNP_TAG_IO_FIXED: if (config->ic_nport == ISA_NPORT) { pnp_printf(id, "too many ports\n"); return (1); } if (res[2] == 0) { /* a null descriptor */ config->ic_port[config->ic_nport].ir_start = 0; config->ic_port[config->ic_nport].ir_end = 0; config->ic_port[config->ic_nport].ir_size = 0; config->ic_port[config->ic_nport].ir_align = 0; config->ic_nport++; break; } if (bootverbose) { pnp_printf(id, "adding fixed io range " "%#x-%#x, size=%#x, " "align=%#x\n", I16(res), I16(res) + res[2] - 1, res[2], 1); } config->ic_port[config->ic_nport].ir_start = I16(res); config->ic_port[config->ic_nport].ir_end = I16(res) + res[2] - 1; config->ic_port[config->ic_nport].ir_size = res[2]; config->ic_port[config->ic_nport].ir_align = 1; config->ic_nport++; break; case PNP_TAG_END: if (bootverbose) pnp_printf(id, "end config\n"); return (1); default: /* Skip this resource */ pnp_printf(id, "unexpected small tag %d\n", PNP_SRES_NUM(tag)); break; } } else { /* Large resource */ switch (PNP_LRES_NUM(tag)) { case PNP_TAG_ID_UNICODE: case PNP_TAG_LARGE_VENDOR: /* these descriptors are quietly ignored */ break; case PNP_TAG_ID_ANSI: if (len > sizeof(buf) - 1) len = sizeof(buf) - 1; bcopy(res, buf, len); /* * Trim trailing spaces and garbage. */ while (len > 0 && buf[len - 1] <= ' ') len--; buf[len] = '\0'; device_set_desc_copy(dev, buf); break; case PNP_TAG_MEMORY_RANGE: if (config->ic_nmem == ISA_NMEM) { pnp_printf(id, "too many memory ranges\n"); return (1); } if (I16(res + 7) == 0) { /* a null descriptor */ config->ic_mem[config->ic_nmem].ir_start = 0; config->ic_mem[config->ic_nmem].ir_end = 0; config->ic_mem[config->ic_nmem].ir_size = 0; config->ic_mem[config->ic_nmem].ir_align = 0; config->ic_nmem++; break; } if (bootverbose) { temp = I16(res + 7) << 8; pnp_printf(id, "adding memory range " "%#x-%#x, size=%#x, " "align=%#x\n", I16(res + 1) << 8, (I16(res + 3) << 8) + temp - 1, temp, I16(res + 5)); } config->ic_mem[config->ic_nmem].ir_start = I16(res + 1) << 8; config->ic_mem[config->ic_nmem].ir_end = (I16(res + 3) << 8) + (I16(res + 7) << 8) - 1; config->ic_mem[config->ic_nmem].ir_size = I16(res + 7) << 8; config->ic_mem[config->ic_nmem].ir_align = I16(res + 5); if (!config->ic_mem[config->ic_nmem].ir_align) config->ic_mem[config->ic_nmem].ir_align = 0x10000; config->ic_nmem++; break; case PNP_TAG_MEMORY32_RANGE: if (config->ic_nmem == ISA_NMEM) { pnp_printf(id, "too many memory ranges\n"); return (1); } if (I32(res + 13) == 0) { /* a null descriptor */ config->ic_mem[config->ic_nmem].ir_start = 0; config->ic_mem[config->ic_nmem].ir_end = 0; config->ic_mem[config->ic_nmem].ir_size = 0; config->ic_mem[config->ic_nmem].ir_align = 0; config->ic_nmem++; break; } if (bootverbose) { pnp_printf(id, "adding memory32 range " "%#x-%#x, size=%#x, " "align=%#x\n", I32(res + 1), I32(res + 5) + I32(res + 13) - 1, I32(res + 13), I32(res + 9)); } config->ic_mem[config->ic_nmem].ir_start = I32(res + 1); config->ic_mem[config->ic_nmem].ir_end = I32(res + 5) + I32(res + 13) - 1; config->ic_mem[config->ic_nmem].ir_size = I32(res + 13); config->ic_mem[config->ic_nmem].ir_align = I32(res + 9); config->ic_nmem++; break; case PNP_TAG_MEMORY32_FIXED: if (config->ic_nmem == ISA_NMEM) { pnp_printf(id, "too many memory ranges\n"); return (1); } if (I32(res + 5) == 0) { /* a null descriptor */ config->ic_mem[config->ic_nmem].ir_start = 0; config->ic_mem[config->ic_nmem].ir_end = 0; config->ic_mem[config->ic_nmem].ir_size = 0; config->ic_mem[config->ic_nmem].ir_align = 0; break; } if (bootverbose) { pnp_printf(id, "adding fixed memory32 range " "%#x-%#x, size=%#x\n", I32(res + 1), I32(res + 1) + I32(res + 5) - 1, I32(res + 5)); } config->ic_mem[config->ic_nmem].ir_start = I32(res + 1); config->ic_mem[config->ic_nmem].ir_end = I32(res + 1) + I32(res + 5) - 1; config->ic_mem[config->ic_nmem].ir_size = I32(res + 5); config->ic_mem[config->ic_nmem].ir_align = 1; config->ic_nmem++; break; default: /* Skip this resource */ pnp_printf(id, "unexpected large tag %d\n", PNP_SRES_NUM(tag)); break; } } return (0); }
void pnp_parse_resources(device_t dev, u_char *resources, int len, int ldn) { struct isa_config *configs; struct isa_config *config; device_t parent; int priorities[1 + MAXDEP]; u_char *start; u_char *p; u_char tag; u_int32_t id; int ncfgs; int l; int i; parent = device_get_parent(dev); id = isa_get_logicalid(dev); configs = (struct isa_config *)malloc(sizeof(*configs)*(1 + MAXDEP), M_DEVBUF, M_NOWAIT | M_ZERO); if (configs == NULL) { device_printf(parent, "No memory to parse PNP data\n"); return; } config = &configs[0]; priorities[0] = 0; ncfgs = 1; p = resources; start = NULL; while (len > 0) { tag = *p++; len--; if (PNP_RES_TYPE(tag) == 0) { /* Small resource */ l = PNP_SRES_LEN(tag); if (len < l) { len = 0; continue; } len -= l; switch (PNP_SRES_NUM(tag)) { case PNP_TAG_START_DEPENDANT: if (start != NULL) { /* * Copy the common resources first, * then parse the "dependent" resources. */ pnp_merge_resources(dev, &configs[0], config); pnp_parse_dependant(dev, start, p - start - 1, config, ldn); } start = p + l; if (ncfgs > MAXDEP) { device_printf(parent, "too many dependant configs (%d)\n", MAXDEP); len = 0; break; } config = &configs[ncfgs]; /* * If the priority is not specified, * then use the default of 'acceptable' */ if (l > 0) priorities[ncfgs] = p[0]; else priorities[ncfgs] = 1; if (bootverbose) pnp_printf(id, "start dependent (%d)\n", priorities[ncfgs]); ncfgs++; break; case PNP_TAG_END_DEPENDANT: if (start == NULL) { device_printf(parent, "malformed resources\n"); len = 0; break; } /* * Copy the common resources first, * then parse the "dependent" resources. */ pnp_merge_resources(dev, &configs[0], config); pnp_parse_dependant(dev, start, p - start - 1, config, ldn); start = NULL; if (bootverbose) pnp_printf(id, "end dependent\n"); /* * Back to the common part; clear it * as its contents has already been copied * to each dependant. */ config = &configs[0]; bzero(config, sizeof(*config)); break; case PNP_TAG_END: if (start != NULL) { device_printf(parent, "malformed resources\n"); } len = 0; break; default: if (start != NULL) /* defer parsing a dependent section */ break; if (pnp_parse_desc(dev, tag, p, l, config, ldn)) len = 0; break; } p += l; } else { /* Large resource */ if (len < 2) { len = 0; break; } l = I16(p); p += 2; len -= 2; if (len < l) { len = 0; break; } len -= l; if (start == NULL && pnp_parse_desc(dev, tag, p, l, config, ldn)) { len = 0; break; } p += l; } } if (ncfgs == 1) { /* Single config without dependants */ ISA_ADD_CONFIG(parent, dev, priorities[0], &configs[0]); free(configs, M_DEVBUF); return; } for (i = 1; i < ncfgs; i++) { /* * Merge the remaining part of the common resources, * if any. Strictly speaking, there shouldn't be common/main * resources after the END_DEPENDENT tag. */ pnp_merge_resources(dev, &configs[0], &configs[i]); ISA_ADD_CONFIG(parent, dev, priorities[i], &configs[i]); } free(configs, M_DEVBUF); }
/* * Quiz the PnP BIOS, build a list of PNP IDs and resource data. */ static int pnpbios_identify(driver_t *driver, device_t parent) { struct PnPBIOS_table *pt = PnPBIOStable; struct bios_args args; struct pnp_sysdev *pd; struct pnp_sysdevargs *pda; uint16_t ndevs, bigdev; int error, currdev; uint8_t *devnodebuf, tag; uint32_t *devid, *compid; int idx, left; device_t dev; /* * Umm, we aren't going to rescan the PnP BIOS to look for new additions. */ if (device_get_state(parent) == DS_ATTACHED) return (0); /* no PnP BIOS information */ if (pt == NULL) return (ENXIO); /* ACPI already active */ if (devclass_get_softc(devclass_find("ACPI"), 0) != NULL) return (ENXIO); bzero(&args, sizeof(args)); args.seg.code16.base = BIOS_PADDRTOVADDR(pt->pmentrybase); args.seg.code16.limit = 0xffff; /* XXX ? */ args.seg.data.base = BIOS_PADDRTOVADDR(pt->pmdataseg); args.seg.data.limit = 0xffff; args.entry = pt->pmentryoffset; if ((error = bios16(&args, PNP_COUNT_DEVNODES, &ndevs, &bigdev)) || (args.r.eax & 0xff)) kprintf("pnpbios: error %d/%x getting device count/size limit\n", error, args.r.eax); ndevs &= 0xff; /* clear high byte garbage */ if (bootverbose) kprintf("pnpbios: %d devices, largest %d bytes\n", ndevs, bigdev); devnodebuf = kmalloc(bigdev + (sizeof(struct pnp_sysdevargs) - sizeof(struct pnp_sysdev)), M_DEVBUF, M_INTWAIT); pda = (struct pnp_sysdevargs *)devnodebuf; pd = &pda->node; for (currdev = 0, left = ndevs; (currdev != 0xff) && (left > 0); left--) { bzero(pd, bigdev); pda->next = currdev; /* get current configuration */ if ((error = bios16(&args, PNP_GET_DEVNODE, &pda->next, &pda->node, 1))) { kprintf("pnpbios: error %d making BIOS16 call\n", error); break; } if (bootverbose) kprintf("pnp_get_devnode cd=%d nxt=%d size=%d handle=%d devid=%08x type=%02x%02x%02x, attrib=%04x\n", currdev, pda->next, pd->size, pd->handle, pd->devid, pd->type[0], pd->type[1], pd->type[2], pd->attrib); if ((error = (args.r.eax & 0xff))) { if (bootverbose) kprintf("pnpbios: %s 0x%x fetching node %d\n", error & 0x80 ? "error" : "warning", error, currdev); if (error & 0x80) break; } currdev = pda->next; if (pd->size < sizeof(struct pnp_sysdev)) { kprintf("pnpbios: bogus system node data, aborting scan\n"); break; } /* * Ignore PICs so that we don't have to worry about the PICs * claiming IRQs to prevent their use. The PIC drivers * already ensure that invalid IRQs are not used. */ if (!strcmp(pnp_eisaformat(pd->devid), "PNP0000")) /* ISA PIC */ continue; if (!strcmp(pnp_eisaformat(pd->devid), "PNP0003")) /* APIC */ continue; /* Add the device and parse its resources */ dev = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP, NULL, -1); isa_set_vendorid(dev, pd->devid); isa_set_logicalid(dev, pd->devid); /* * It appears that some PnP BIOS doesn't allow us to re-enable * the embedded system device once it is disabled. We shall * mark all system device nodes as "cannot be disabled", regardless * of actual settings in the device attribute byte. XXX */ #if 0 isa_set_configattr(dev, ((pd->attrib & PNPATTR_NODISABLE) ? 0 : ISACFGATTR_CANDISABLE) | ((!(pd->attrib & PNPATTR_NOCONFIG) && PNPATTR_CONFIG(pd->attrib) != PNPATTR_CONFIG_STATIC) ? ISACFGATTR_DYNAMIC : 0)); #endif isa_set_configattr(dev, (!(pd->attrib & PNPATTR_NOCONFIG) && PNPATTR_CONFIG(pd->attrib) != PNPATTR_CONFIG_STATIC) ? ISACFGATTR_DYNAMIC : 0); ISA_SET_CONFIG_CALLBACK(parent, dev, pnpbios_set_config, 0); pnp_parse_resources(dev, &pd->devdata[0], pd->size - sizeof(struct pnp_sysdev), 0); if (!device_get_desc(dev)) device_set_desc_copy(dev, pnp_eisaformat(pd->devid)); /* Find device IDs */ devid = &pd->devid; compid = NULL; /* look for a compatible device ID too */ left = pd->size - sizeof(struct pnp_sysdev); idx = 0; while (idx < left) { tag = pd->devdata[idx++]; if (PNP_RES_TYPE(tag) == 0) { /* Small resource */ switch (PNP_SRES_NUM(tag)) { case PNP_TAG_COMPAT_DEVICE: compid = (uint32_t *)(pd->devdata + idx); if (bootverbose) kprintf("pnpbios: node %d compat ID 0x%08x\n", pd->handle, *compid); /* FALLTHROUGH */ case PNP_TAG_END: idx = left; break; default: idx += PNP_SRES_LEN(tag); break; } } else /* Large resource, skip it */ idx += *(uint16_t *)(pd->devdata + idx) + 2; } if (bootverbose) { kprintf("pnpbios: handle %d device ID %s (%08x)", pd->handle, pnp_eisaformat(*devid), *devid); if (compid != NULL) kprintf(" compat ID %s (%08x)", pnp_eisaformat(*compid), *compid); kprintf("\n"); } } return (0); }