pnp.c revision 64187
1/*
2 * mjs copyright
3 *
4 * $FreeBSD: head/sys/boot/common/pnp.c 64187 2000-08-03 09:14:02Z jhb $
5 */
6/*
7 * "Plug and Play" functionality.
8 *
9 * We use the PnP enumerators to obtain identifiers for installed hardware,
10 * and the contents of a database to determine modules to be loaded to support
11 * such hardware.
12 */
13
14#include <stand.h>
15#include <string.h>
16#include <bootstrap.h>
17
18STAILQ_HEAD(,pnpinfo)	pnp_devices;
19static int		pnp_devices_initted = 0;
20
21static void		pnp_discard(void);
22
23/*
24 * Perform complete enumeration sweep
25 */
26
27COMMAND_SET(pnpscan, "pnpscan", "scan for PnP devices", pnp_scan);
28
29static int
30pnp_scan(int argc, char *argv[])
31{
32    struct pnpinfo	*pi;
33    int			hdlr;
34    int			verbose;
35    int			ch;
36
37    if (pnp_devices_initted == 0) {
38	STAILQ_INIT(&pnp_devices);
39	pnp_devices_initted = 1;
40    }
41
42    verbose = 0;
43    optind = 1;
44    optreset = 1;
45    while ((ch = getopt(argc, argv, "v")) != -1) {
46	switch(ch) {
47	case 'v':
48	    verbose = 1;
49	    break;
50	case '?':
51	default:
52	    /* getopt has already reported an error */
53	    return(CMD_OK);
54	}
55    }
56
57    /* forget anything we think we knew */
58    pnp_discard();
59
60    /* iterate over all of the handlers */
61    for (hdlr = 0; pnphandlers[hdlr] != NULL; hdlr++) {
62	if (verbose)
63	    printf("Probing %s...\n", pnphandlers[hdlr]->pp_name);
64	pnphandlers[hdlr]->pp_enumerate();
65    }
66    if (verbose) {
67	pager_open();
68	pager_output("PNP scan summary:\n");
69	for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) {
70	    pager_output(pi->pi_ident.stqh_first->id_ident);	/* first ident should be canonical */
71	    if (pi->pi_desc != NULL) {
72		pager_output(" : ");
73		pager_output(pi->pi_desc);
74	    }
75	    pager_output("\n");
76	}
77	pager_close();
78    }
79    return(CMD_OK);
80}
81
82#if 0
83/*
84 * Try to load outstanding modules (eg. after disk change)
85 */
86COMMAND_SET(pnpload, "pnpload", "load modules for PnP devices", pnp_load);
87
88static int
89pnp_load(int argc, char *argv[])
90{
91    struct pnpinfo	*pi;
92    char		*modfname;
93
94    /* find anything? */
95    if (pnp_devices.stqh_first != NULL) {
96
97	/* check for kernel, assign modules handled by static drivers there */
98	if (pnp_scankernel()) {
99	    command_errmsg = "cannot load drivers until kernel loaded";
100	    return(CMD_ERROR);
101	}
102	if (fname == NULL) {
103	    /* default paths */
104	    pnp_readconf("/boot/pnpdata.local");
105	    pnp_readconf("/boot/pnpdata");
106	} else {
107	    if (pnp_readconf(fname)) {
108		sprintf(command_errbuf, "can't read PnP information from '%s'", fname);
109		return(CMD_ERROR);
110	    }
111	}
112
113	/* try to load any modules that have been nominated */
114	for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) {
115	    /* Already loaded? */
116	    if ((pi->pi_module != NULL) && (file_findfile(pi->pi_module, NULL) == NULL)) {
117		modfname = malloc(strlen(pi->pi_module) + 4);
118		sprintf(modfname, "%s.ko", pi->pi_module);	/* XXX implicit knowledge of KLD module filenames */
119		if (mod_load(pi->pi_module, pi->pi_argc, pi->pi_argv))
120		    printf("Could not load module '%s' for device '%s'\n", modfname, pi->pi_ident.stqh_first->id_ident);
121		free(modfname);
122	    }
123	}
124    }
125    return(CMD_OK);
126}
127#endif
128/*
129 * Throw away anything we think we know about PnP devices.
130 */
131static void
132pnp_discard(void)
133{
134    struct pnpinfo	*pi;
135
136    while (pnp_devices.stqh_first != NULL) {
137	pi = pnp_devices.stqh_first;
138	STAILQ_REMOVE_HEAD(&pnp_devices, pi_link);
139	pnp_freeinfo(pi);
140    }
141}
142#if 0
143/*
144 * The PnP configuration database consists of a flat text file with
145 * entries one per line.  Valid lines are:
146 *
147 * # <text>
148 *
149 * 	This line is a comment, and ignored.
150 *
151 * [<name>]
152 *
153 *	Entries following this line are for devices connected to the
154 *	bus <name>, At least one such entry must be encountered
155 *	before identifiers are recognised.
156 *
157 * ident=<identifier> rev=<revision> module=<module> args=<arguments>
158 *
159 *	This line describes an identifier:module mapping.  The 'ident'
160 *	and 'module' fields are required; the 'rev' field is currently
161 *	ignored (but should be used), and the 'args' field must come
162 *	last.
163 *
164 * Comments may be appended to lines; any character including or following
165 * '#' on a line is ignored.
166 */
167static int
168pnp_readconf(char *path)
169{
170    struct pnpinfo	*pi;
171    struct pnpident	*id;
172    int			fd, line;
173    char		lbuf[128], *currbus, *ident, *revision, *module, *args;
174    char		*cp, *ep, *tp, c;
175
176    /* try to open the file */
177    if ((fd = open(path, O_RDONLY)) >= 0) {
178	line = 0;
179	currbus = NULL;
180
181	while (fgetstr(lbuf, sizeof(lbuf), fd) > 0) {
182	    line++;
183	    /* Find the first non-space character on the line */
184	    for (cp = lbuf; (*cp != 0) && !isspace(*cp); cp++)
185		;
186
187	    /* keep/discard? */
188	    if ((*cp == 0) || (*cp == '#'))
189		continue;
190
191	    /* cut trailing comment? */
192	    if ((ep = strchr(cp, '#')) != NULL)
193		*ep = 0;
194
195	    /* bus declaration? */
196	    if (*cp == '[') {
197		if (((ep = strchr(cp, ']')) == NULL) || ((ep - cp) < 2)) {
198		    printf("%s line %d: bad bus specification\n", path, line);
199		} else {
200		    if (currbus != NULL)
201			free(currbus);
202		    *ep = 0;
203		    currbus = strdup(cp + 1);
204		}
205		continue;
206	    }
207
208	    /* XXX should we complain? */
209	    if (currbus == NULL)
210		continue;
211
212	    /* mapping */
213	    for (ident = module = args = revision = NULL; *cp != 0;) {
214
215		/* discard leading whitespace */
216		if (isspace(*cp)) {
217		    cp++;
218		    continue;
219		}
220
221		/* scan for terminator, separator */
222		for (ep = cp; (*ep != 0) && (*ep != '=') && !isspace(*ep); ep++)
223		    ;
224
225		if (*ep == '=') {
226		    *ep = 0;
227		    for (tp = ep + 1; (*tp != 0) && !isspace(*tp); tp++)
228			;
229		    c = *tp;
230		    *tp = 0;
231		    if ((ident == NULL) && !strcmp(cp, "ident")) {
232			ident = ep + 1;
233		    } else if ((revision == NULL) && !strcmp(cp, "revision")) {
234			revision = ep + 1;
235		    } else if ((args == NULL) && !strcmp(cp, "args")) {
236			*tp = c;
237			while (*tp != 0)		/* skip to end of string */
238			    tp++;
239			args = ep + 1;
240		    } else {
241			/* XXX complain? */
242		    }
243		    cp = tp;
244		    continue;
245		}
246
247		/* it's garbage or a keyword - ignore it for now */
248		cp = ep;
249	    }
250
251	    /* we must have at least ident and module set to be interesting */
252	    if ((ident == NULL) || (module == NULL))
253		continue;
254
255	    /*
256	     * Loop looking for module/bus that might match this, but aren't already
257	     * assigned.
258	     * XXX no revision parse/test here yet.
259	     */
260	    for (pi = pnp_devices.stqh_first; pi != NULL; pi = pi->pi_link.stqe_next) {
261
262		/* no driver assigned, bus matches OK */
263		if ((pi->pi_module == NULL) &&
264		    !strcmp(pi->pi_handler->pp_name, currbus)) {
265
266		    /* scan idents, take first match */
267		    for (id = pi->pi_ident.stqh_first; id != NULL; id = id->id_link.stqe_next)
268			if (!strcmp(id->id_ident, ident))
269			    break;
270
271		    /* find a match? */
272		    if (id != NULL) {
273			if (args != NULL)
274			    if (parse(&pi->pi_argc, &pi->pi_argv, args)) {
275				printf("%s line %d: bad arguments\n", path, line);
276				continue;
277			    }
278			pi->pi_module = strdup(module);
279			printf("use module '%s' for %s:%s\n", module, pi->pi_handler->pp_name, id->id_ident);
280		    }
281		}
282	    }
283	}
284	close(fd);
285    }
286    return(CMD_OK);
287}
288
289static int
290pnp_scankernel(void)
291{
292    return(CMD_OK);
293}
294#endif
295/*
296 * Add a unique identifier to (pi)
297 */
298void
299pnp_addident(struct pnpinfo *pi, char *ident)
300{
301    struct pnpident	*id;
302
303    for (id = pi->pi_ident.stqh_first; id != NULL; id = id->id_link.stqe_next)
304	if (!strcmp(id->id_ident, ident))
305	    return;			/* already have this one */
306
307    id = malloc(sizeof(struct pnpident));
308    id->id_ident = strdup(ident);
309    STAILQ_INSERT_TAIL(&pi->pi_ident, id, id_link);
310}
311
312/*
313 * Allocate a new pnpinfo struct
314 */
315struct pnpinfo *
316pnp_allocinfo(void)
317{
318    struct pnpinfo	*pi;
319
320    pi = malloc(sizeof(struct pnpinfo));
321    bzero(pi, sizeof(struct pnpinfo));
322    STAILQ_INIT(&pi->pi_ident);
323    return(pi);
324}
325
326/*
327 * Release storage held by a pnpinfo struct
328 */
329void
330pnp_freeinfo(struct pnpinfo *pi)
331{
332    struct pnpident	*id;
333
334    while (pi->pi_ident.stqh_first != NULL) {
335	id = pi->pi_ident.stqh_first;
336	STAILQ_REMOVE_HEAD(&pi->pi_ident, id_link);
337	free(id->id_ident);
338	free(id);
339    }
340    if (pi->pi_desc)
341	free(pi->pi_desc);
342    if (pi->pi_module)
343	free(pi->pi_module);
344    if (pi->pi_argv)
345	free(pi->pi_argv);
346    free(pi);
347}
348
349/*
350 * Add a new pnpinfo struct to the list.
351 */
352void
353pnp_addinfo(struct pnpinfo *pi)
354{
355    STAILQ_INSERT_TAIL(&pnp_devices, pi, pi_link);
356}
357
358
359/*
360 * Format an EISA id as a string in standard ISA PnP format, AAAIIRR
361 * where 'AAA' is the EISA vendor ID, II is the product ID and RR the revision ID.
362 */
363char *
364pnp_eisaformat(u_int8_t *data)
365{
366    static char	idbuf[8];
367    const char	hextoascii[] = "0123456789abcdef";
368
369    idbuf[0] = '@' + ((data[0] & 0x7c) >> 2);
370    idbuf[1] = '@' + (((data[0] & 0x3) << 3) + ((data[1] & 0xe0) >> 5));
371    idbuf[2] = '@' + (data[1] & 0x1f);
372    idbuf[3] = hextoascii[(data[2] >> 4)];
373    idbuf[4] = hextoascii[(data[2] & 0xf)];
374    idbuf[5] = hextoascii[(data[3] >> 4)];
375    idbuf[6] = hextoascii[(data[3] & 0xf)];
376    idbuf[7] = 0;
377    return(idbuf);
378}
379
380