/* * mjs copyright */ /* * "Plug and Play" functionality. * * We use the PnP enumerators to obtain identifiers for installed hardware, * and the contents of a database to determine modules to be loaded to support * such hardware. */ #include #include #include STAILQ_HEAD(,pnpinfo) pnp_devices; static int pnp_devices_initted = 0; static void pnp_discard(void); static int pnp_readconf(char *path); static int pnp_scankernel(void); /* * Perform complete enumeration sweep, and load required module(s) if possible. */ COMMAND_SET(pnpscan, "pnpscan", "scan for PnP devices", pnp_scan); int pnp_scan(int argc, char *argv[]) { struct pnpinfo *pi; int hdlr; int verbose; int ch; if (pnp_devices_initted == 0) { STAILQ_INIT(&pnp_devices); pnp_devices_initted = 1; } verbose = 0; optind = 1; optreset = 1; while ((ch = getopt(argc, argv, "v")) != -1) { switch(ch) { case 'v': verbose = 1; break; case '?': default: /* getopt has already reported an error */ return(CMD_OK); } } /* forget anything we think we knew */ pnp_discard(); if (verbose) pager_open(); /* iterate over all of the handlers */ for (hdlr = 0; pnphandlers[hdlr] != NULL; hdlr++) { if (verbose) { pager_output("Probing "); pager_output(pnphandlers[hdlr]->pp_name); pager_output("...\n"); } pnphandlers[hdlr]->pp_enumerate(); } if (verbose) { for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) { pager_output(pi->pi_ident.stqh_first->id_ident); /* first ident should be canonical */ if (pi->pi_desc != NULL) { pager_output(" : "); pager_output(pi->pi_desc); } pager_output("\n"); } pager_close(); } return(CMD_OK); } /* * Try to load outstanding modules (eg. after disk change) */ int pnp_reload(char *fname) { struct pnpinfo *pi; char *modfname; /* find anything? */ if (pnp_devices.stqh_first != NULL) { /* check for kernel, assign modules handled by static drivers there */ if (pnp_scankernel()) { command_errmsg = "cannot load drivers until kernel loaded"; return(CMD_ERROR); } if (fname == NULL) { /* default paths */ pnp_readconf("/boot/pnpdata.local"); pnp_readconf("/boot/pnpdata"); } else { if (pnp_readconf("fname")) { sprintf(command_errbuf, "can't read PnP information from '%s'", fname); return(CMD_ERROR); } } /* try to load any modules that have been nominated */ for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) { /* Already loaded? */ if ((pi->pi_module != NULL) && (mod_findmodule(pi->pi_module, NULL) == NULL)) { modfname = malloc(strlen(pi->pi_module) + 4); sprintf(modfname, "%s.ko", pi->pi_module); /* XXX implicit knowledge of KLD module filenames */ if (mod_load(pi->pi_module, pi->pi_argc, pi->pi_argv)) printf("Could not load module '%s' for device '%s'\n", modfname, pi->pi_ident.stqh_first->id_ident); free(modfname); } } } return(CMD_OK); } /* * Throw away anything we think we know about PnP devices on (list) */ static void pnp_discard(void) { struct pnpinfo *pi; while (pnp_devices.stqh_first != NULL) { pi = pnp_devices.stqh_first; STAILQ_REMOVE_HEAD(&pnp_devices, pi_link); pnp_freeinfo(pi); } } /* * The PnP configuration database consists of a flat text file with * entries one per line. Valid lines are: * * # * * This line is a comment, and ignored. * * [] * * Entries following this line are for devices connected to the * bus , At least one such entry must be encountered * before identifiers are recognised. * * ident= rev= module= args= * * This line describes an identifier:module mapping. The 'ident' * and 'module' fields are required; the 'rev' field is currently * ignored (but should be used), and the 'args' field must come * last. * * Comments may be appended to lines; any character including or following * '#' on a line is ignored. */ static int pnp_readconf(char *path) { struct pnpinfo *pi; struct pnpident *id; int fd, line; char lbuf[128], *currbus, *ident, *revision, *module, *args; char *cp, *ep, *tp, c; /* try to open the file */ if ((fd = open(path, O_RDONLY)) >= 0) { line = 0; currbus = NULL; while (fgetstr(lbuf, sizeof(lbuf), fd) > 0) { line++; /* Find the first non-space character on the line */ for (cp = lbuf; (*cp != 0) && !isspace(*cp); cp++) ; /* keep/discard? */ if ((*cp == 0) || (*cp == '#')) continue; /* cut trailing comment? */ if ((ep = strchr(cp, '#')) != NULL) *ep = 0; /* bus declaration? */ if (*cp == '[') { if (((ep = strchr(cp, ']')) == NULL) || ((ep - cp) < 2)) { printf("%s line %d: bad bus specification\n", path, line); } else { if (currbus != NULL) free(currbus); *ep = 0; currbus = strdup(cp + 1); } continue; } /* XXX should we complain? */ if (currbus == NULL) continue; /* mapping */ for (ident = module = args = revision = NULL; *cp != 0;) { /* discard leading whitespace */ if (isspace(*cp)) { cp++; continue; } /* scan for terminator, separator */ for (ep = cp; (*ep != 0) && (*ep != '=') && !isspace(*ep); ep++) ; if (*ep == '=') { *ep = 0; for (tp = ep + 1; (*tp != 0) && !isspace(*tp); tp++) ; c = *tp; *tp = 0; if ((ident == NULL) && !strcmp(cp, "ident")) { ident = ep + 1; } else if ((revision == NULL) && !strcmp(cp, "revision")) { revision = ep + 1; } else if ((args == NULL) && !strcmp(cp, "args")) { *tp = c; while (*tp != 0) /* skip to end of string */ tp++; args = ep + 1; } else { /* XXX complain? */ } cp = tp; continue; } /* it's garbage or a keyword - ignore it for now */ cp = ep; } /* we must have at least ident and module set to be interesting */ if ((ident == NULL) || (module == NULL)) continue; /* * Loop looking for module/bus that might match this, but aren't already * assigned. * XXX no revision parse/test here yet. */ for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) { /* no driver assigned, bus matches OK */ if ((pi->pi_module == NULL) && !strcmp(pi->pi_handler->pp_name, currbus)) { /* scan idents, take first match */ for (id = pi->pi_ident.stqh_first; id != NULL; id = id->id_link.stqe_next) if (!strcmp(id->id_ident, ident)) break; /* find a match? */ if (id != NULL) { if (args != NULL) if (parse(&pi->pi_argc, &pi->pi_argv, args)) { printf("%s line %d: bad arguments\n", path, line); continue; } pi->pi_module = strdup(module); printf("use module '%s' for %s:%s\n", module, pi->pi_handler->pp_name, id->id_ident); } } } } close(fd); } return(CMD_OK); } static int pnp_scankernel(void) { return(CMD_OK); } /* * Add a unique identifier to (pi) */ void pnp_addident(struct pnpinfo *pi, char *ident) { struct pnpident *id; for (id = pi->pi_ident.stqh_first; id != NULL; id = id->id_link.stqe_next) if (!strcmp(id->id_ident, ident)) return; /* already have this one */ id = malloc(sizeof(struct pnpident)); id->id_ident = strdup(ident); STAILQ_INSERT_TAIL(&pi->pi_ident, id, id_link); } /* * Allocate a new pnpinfo struct */ struct pnpinfo * pnp_allocinfo(void) { struct pnpinfo *pi; pi = malloc(sizeof(struct pnpinfo)); bzero(pi, sizeof(struct pnpinfo)); STAILQ_INIT(&pi->pi_ident); return(pi); } /* * Release storage held by a pnpinfo struct */ void pnp_freeinfo(struct pnpinfo *pi) { struct pnpident *id; while (pi->pi_ident.stqh_first != NULL) { id = pi->pi_ident.stqh_first; STAILQ_REMOVE_HEAD(&pi->pi_ident, id_link); free(id->id_ident); free(id); } if (pi->pi_desc) free(pi->pi_desc); if (pi->pi_module) free(pi->pi_module); if (pi->pi_argv) free(pi->pi_argv); free(pi); } /* * Add a new pnpinfo struct to the list. */ void pnp_addinfo(struct pnpinfo *pi) { STAILQ_INSERT_TAIL(&pnp_devices, pi, pi_link); } /* * Format an EISA id as a string in standard ISA PnP format, AAAIIRR * where 'AAA' is the EISA vendor ID, II is the product ID and RR the revision ID. */ char * pnp_eisaformat(u_int8_t *data) { static char idbuf[8]; const char hextoascii[] = "0123456789abcdef"; idbuf[0] = '@' + ((data[0] & 0x7c) >> 2); idbuf[1] = '@' + (((data[0] & 0x3) << 3) + ((data[1] & 0xe0) >> 5)); idbuf[2] = '@' + (data[1] & 0x1f); idbuf[3] = hextoascii[(data[2] >> 4)]; idbuf[4] = hextoascii[(data[2] & 0xf)]; idbuf[5] = hextoascii[(data[3] >> 4)]; idbuf[6] = hextoascii[(data[3] & 0xf)]; idbuf[7] = 0; return(idbuf); }