freebsd-skq/sys/boot/common/pnp.c

358 lines
8.2 KiB
C
Raw Normal View History

/*
* 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 <stand.h>
#include <string.h>
#include <bootstrap.h>
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;
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 + 3));
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:
*
* # <text>
*
* This line is a comment, and ignored.
*
* [<name>]
*
* Entries following this line are for devices connected to the
* bus <name>, At least one such entry must be encountered
* before identifiers are recognised.
*
* ident=<identifier> rev=<revision> module=<module> args=<arguments>
*
* 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);
}