xref: /openbsd-src/sys/kern/subr_autoconf.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
1 /*	$OpenBSD: subr_autoconf.c,v 1.22 1999/01/11 05:12:23 millert Exp $	*/
2 /*	$NetBSD: subr_autoconf.c,v 1.21 1996/04/04 06:06:18 cgd Exp $	*/
3 
4 /*
5  * Copyright (c) 1992, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This software was developed by the Computer Systems Engineering group
9  * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
10  * contributed to Berkeley.
11  *
12  * All advertising materials mentioning features or use of this software
13  * must display the following acknowledgement:
14  *	This product includes software developed by the University of
15  *	California, Lawrence Berkeley Laboratories.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in the
24  *    documentation and/or other materials provided with the distribution.
25  * 3. All advertising materials mentioning features or use of this software
26  *    must display the following acknowledgement:
27  *	This product includes software developed by the University of
28  *	California, Berkeley and its contributors.
29  * 4. Neither the name of the University nor the names of its contributors
30  *    may be used to endorse or promote products derived from this software
31  *    without specific prior written permission.
32  *
33  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
34  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
37  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
38  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
39  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
41  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
42  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43  * SUCH DAMAGE.
44  *
45  * from: Header: subr_autoconf.c,v 1.12 93/02/01 19:31:48 torek Exp  (LBL)
46  *
47  *	@(#)subr_autoconf.c	8.1 (Berkeley) 6/10/93
48  */
49 
50 #include <sys/param.h>
51 #include <sys/device.h>
52 #include <sys/malloc.h>
53 #include <sys/systm.h>
54 #include <machine/limits.h>
55 /* Extra stuff from Matthias Drochner <drochner@zelux6.zel.kfa-juelich.de> */
56 #include <sys/queue.h>
57 
58 /* Bleh!  Need device_register proto */
59 #if defined(__alpha__) || defined(hp300)
60 #include <machine/autoconf.h>
61 #endif /* __alpha__ || hp300 */
62 
63 /*
64  * Autoconfiguration subroutines.
65  */
66 
67 typedef int (*cond_predicate_t) __P((struct device *, void *));
68 
69 /*
70  * ioconf.c exports exactly two names: cfdata and cfroots.  All system
71  * devices and drivers are found via these tables.
72  */
73 extern short cfroots[];
74 
75 #define	ROOT ((struct device *)NULL)
76 
77 struct matchinfo {
78 	cfmatch_t fn;
79 	struct	device *parent;
80 	void	*match, *aux;
81 	int	indirect, pri;
82 };
83 
84 struct cftable_head allcftables;
85 
86 static struct cftable staticcftable = {
87 	cfdata
88 };
89 
90 #ifndef AUTOCONF_VERBOSE
91 #define AUTOCONF_VERBOSE 0
92 #endif /* AUTOCONF_VERBOSE */
93 int autoconf_verbose = AUTOCONF_VERBOSE;	/* trace probe calls */
94 
95 static char *number __P((char *, int));
96 static void mapply __P((struct matchinfo *, struct cfdata *));
97 static int haschild __P((struct device *));
98 int	detach_devices __P((cond_predicate_t, void *,
99 	    config_detach_callback_t, void *));
100 int	dev_matches_cfdata __P((struct device *dev, void *));
101 int	parentdev_matches_cfdata __P((struct device *dev, void *));
102 
103 
104 struct devicelist alldevs;		/* list of all devices */
105 struct evcntlist allevents;		/* list of all event counters */
106 
107 /*
108  * Initialize autoconfiguration data structures.  This occurs before console
109  * initialization as that might require use of this subsystem.  Furthermore
110  * this means that malloc et al. isn't yet available.
111  */
112 void
113 config_init()
114 {
115 
116 	TAILQ_INIT(&alldevs);
117 	TAILQ_INIT(&allevents);
118 	TAILQ_INIT(&allcftables);
119 	TAILQ_INSERT_TAIL(&allcftables, &staticcftable, list);
120 }
121 
122 /*
123  * Apply the matching function and choose the best.  This is used
124  * a few times and we want to keep the code small.
125  */
126 static void
127 mapply(m, cf)
128 	register struct matchinfo *m;
129 	register struct cfdata *cf;
130 {
131 	register int pri;
132 	void *match;
133 
134 	if (m->indirect)
135 		match = config_make_softc(m->parent, cf);
136 	else
137 		match = cf;
138 
139 	if (autoconf_verbose) {
140 		printf(">>> probing for %s", cf->cf_driver->cd_name);
141 		if (cf->cf_fstate == FSTATE_STAR)
142 			printf("*\n");
143 		else
144 			printf("%d\n", cf->cf_unit);
145 	}
146 	if (m->fn != NULL)
147 		pri = (*m->fn)(m->parent, match, m->aux);
148 	else {
149 	        if (cf->cf_attach->ca_match == NULL) {
150 			panic("mapply: no match function for '%s' device",
151 			    cf->cf_driver->cd_name);
152 		}
153 		pri = (*cf->cf_attach->ca_match)(m->parent, match, m->aux);
154 	}
155 	if (autoconf_verbose)
156 		printf(">>> %s probe returned %d\n", cf->cf_driver->cd_name,
157 		    pri);
158 
159 	if (pri > m->pri) {
160 		if (m->indirect && m->match)
161 			free(m->match, M_DEVBUF);
162 		m->match = match;
163 		m->pri = pri;
164 	} else {
165 		if (m->indirect)
166 			free(match, M_DEVBUF);
167 	}
168 }
169 
170 /*
171  * Iterate over all potential children of some device, calling the given
172  * function (default being the child's match function) for each one.
173  * Nonzero returns are matches; the highest value returned is considered
174  * the best match.  Return the `found child' if we got a match, or NULL
175  * otherwise.  The `aux' pointer is simply passed on through.
176  *
177  * Note that this function is designed so that it can be used to apply
178  * an arbitrary function to all potential children (its return value
179  * can be ignored).
180  */
181 void *
182 config_search(fn, parent, aux)
183 	cfmatch_t fn;
184 	register struct device *parent;
185 	void *aux;
186 {
187 	register struct cfdata *cf;
188 	register short *p;
189 	struct matchinfo m;
190 	struct cftable *t;
191 
192 	m.fn = fn;
193 	m.parent = parent;
194 	m.match = NULL;
195 	m.aux = aux;
196 	m.indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
197 	m.pri = 0;
198 	for(t = allcftables.tqh_first; t; t = t->list.tqe_next) {
199 		for (cf = t->tab; cf->cf_driver; cf++) {
200 			/*
201 			 * Skip cf if no longer eligible, otherwise scan
202 			 * through parents for one matching `parent',
203 			 * and try match function.
204 			 */
205 			if (cf->cf_fstate == FSTATE_FOUND)
206 				continue;
207 			if (cf->cf_fstate == FSTATE_DNOTFOUND ||
208 			    cf->cf_fstate == FSTATE_DSTAR)
209 				continue;
210 			for (p = cf->cf_parents; *p >= 0; p++)
211 				if (parent->dv_cfdata == &(t->tab)[*p])
212 					mapply(&m, cf);
213 		}
214 	}
215 	if (autoconf_verbose) {
216 		if (m.match)
217 			printf(">>> %s probe won\n",
218 			    ((struct cfdata *)m.match)->cf_driver->cd_name);
219 		else
220 			printf(">>> no winning probe\n");
221 	}
222 	return (m.match);
223 }
224 
225 /*
226  * Iterate over all potential children of some device, calling the given
227  * function for each one.
228  *
229  * Note that this function is designed so that it can be used to apply
230  * an arbitrary function to all potential children (its return value
231  * can be ignored).
232  */
233 void
234 config_scan(fn, parent)
235 	cfscan_t fn;
236 	register struct device *parent;
237 {
238 	register struct cfdata *cf;
239 	register short *p;
240 	void *match;
241 	int indirect;
242 	struct cftable *t;
243 
244 	indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
245 	for (t = allcftables.tqh_first; t; t = t->list.tqe_next) {
246 		for (cf = t->tab; cf->cf_driver; cf++) {
247 			/*
248 			 * Skip cf if no longer eligible, otherwise scan
249 			 * through parents for one matching `parent',
250 			 * and try match function.
251 			 */
252 			if (cf->cf_fstate == FSTATE_FOUND)
253 				continue;
254 			if (cf->cf_fstate == FSTATE_DNOTFOUND ||
255 			    cf->cf_fstate == FSTATE_DSTAR)
256 				continue;
257 			for (p = cf->cf_parents; *p >= 0; p++)
258 				if (parent->dv_cfdata == &(t->tab)[*p]) {
259 					match = indirect?
260 					    config_make_softc(parent, cf) :
261 					    (void *)cf;
262 					(*fn)(parent, match);
263 				}
264 		}
265 	}
266 }
267 
268 /*
269  * Find the given root device.
270  * This is much like config_search, but there is no parent.
271  */
272 void *
273 config_rootsearch(fn, rootname, aux)
274 	register cfmatch_t fn;
275 	register char *rootname;
276 	register void *aux;
277 {
278 	register struct cfdata *cf;
279 	register short *p;
280 	struct matchinfo m;
281 
282 	m.fn = fn;
283 	m.parent = ROOT;
284 	m.match = NULL;
285 	m.aux = aux;
286 	m.indirect = 0;
287 	m.pri = 0;
288 	/*
289 	 * Look at root entries for matching name.  We do not bother
290 	 * with found-state here since only one root should ever be
291 	 * searched (and it must be done first).
292 	 */
293 	for (p = cfroots; *p >= 0; p++) {
294 		cf = &cfdata[*p];
295 		if (strcmp(cf->cf_driver->cd_name, rootname) == 0)
296 			mapply(&m, cf);
297 	}
298 	return (m.match);
299 }
300 
301 char *msgs[3] = { "", " not configured\n", " unsupported\n" };
302 
303 /*
304  * The given `aux' argument describes a device that has been found
305  * on the given parent, but not necessarily configured.  Locate the
306  * configuration data for that device (using the submatch function
307  * provided, or using candidates' cd_match configuration driver
308  * functions) and attach it, and return true.  If the device was
309  * not configured, call the given `print' function and return 0.
310  */
311 struct device *
312 config_found_sm(parent, aux, print, submatch)
313 	struct device *parent;
314 	void *aux;
315 	cfprint_t print;
316 	cfmatch_t submatch;
317 {
318 	void *match;
319 
320 	if ((match = config_search(submatch, parent, aux)) != NULL)
321 		return (config_attach(parent, match, aux, print));
322 	if (print)
323 		printf(msgs[(*print)(aux, parent->dv_xname)]);
324 	return (NULL);
325 }
326 
327 /*
328  * As above, but for root devices.
329  */
330 struct device *
331 config_rootfound(rootname, aux)
332 	char *rootname;
333 	void *aux;
334 {
335 	void *match;
336 
337 	if ((match = config_rootsearch((cfmatch_t)NULL, rootname, aux)) != NULL)
338 		return (config_attach(ROOT, match, aux, (cfprint_t)NULL));
339 	printf("root device %s not configured\n", rootname);
340 	return (NULL);
341 }
342 
343 /* just like sprintf(buf, "%d") except that it works from the end */
344 static char *
345 number(ep, n)
346 	register char *ep;
347 	register int n;
348 {
349 
350 	*--ep = 0;
351 	while (n >= 10) {
352 		*--ep = (n % 10) + '0';
353 		n /= 10;
354 	}
355 	*--ep = n + '0';
356 	return (ep);
357 }
358 
359 /*
360  * Attach a found device.  Allocates memory for device variables.
361  */
362 struct device *
363 config_attach(parent, match, aux, print)
364 	register struct device *parent;
365 	void *match;
366 	register void *aux;
367 	cfprint_t print;
368 {
369 	register struct cfdata *cf;
370 	register struct device *dev;
371 	register struct cfdriver *cd;
372 	register struct cfattach *ca;
373 	struct cftable *t;
374 
375 	if (parent && parent->dv_cfdata->cf_driver->cd_indirect) {
376 		dev = match;
377 		cf = dev->dv_cfdata;
378 	} else {
379 		cf = match;
380 		dev = config_make_softc(parent, cf);
381 	}
382 
383 	cd = cf->cf_driver;
384 	ca = cf->cf_attach;
385 
386 	cd->cd_devs[dev->dv_unit] = dev;
387 
388 	/*
389 	 * If this is a "STAR" device and we used the last unit, prepare for
390 	 * another one.
391 	 */
392 	if (cf->cf_fstate == FSTATE_STAR) {
393 		if (dev->dv_unit == cf->cf_unit)
394 			cf->cf_unit++;
395 	} else
396 		cf->cf_fstate = FSTATE_FOUND;
397 
398 	TAILQ_INSERT_TAIL(&alldevs, dev, dv_list);
399 
400 	if (parent == ROOT)
401 		printf("%s (root)", dev->dv_xname);
402 	else {
403 		printf("%s at %s", dev->dv_xname, parent->dv_xname);
404 		if (print)
405 			(void) (*print)(aux, (char *)0);
406 	}
407 
408 	/*
409 	 * Before attaching, clobber any unfound devices that are
410 	 * otherwise identical, or bump the unit number on all starred
411 	 * cfdata for this device.
412 	 */
413 	for (t = allcftables.tqh_first; t; t = t->list.tqe_next) {
414 		for (cf = t->tab; cf->cf_driver; cf++)
415 			if (cf->cf_driver == cd &&
416 			    cf->cf_unit == dev->dv_unit) {
417 				if (cf->cf_fstate == FSTATE_NOTFOUND)
418 					cf->cf_fstate = FSTATE_FOUND;
419 				if (cf->cf_fstate == FSTATE_STAR)
420 					cf->cf_unit++;
421 			}
422 	}
423 #if defined(__alpha__) || defined(hp300)
424 	device_register(dev, aux);
425 #endif
426 	(*ca->ca_attach)(parent, dev, aux);
427 	return (dev);
428 }
429 
430 struct device *
431 config_make_softc(parent, cf)
432 	struct device *parent;
433 	struct cfdata *cf;
434 {
435 	register struct device *dev;
436 	register struct cfdriver *cd;
437 	register struct cfattach *ca;
438 	register size_t lname, lunit;
439 	register char *xunit;
440 	char num[10];
441 
442 	cd = cf->cf_driver;
443 	ca = cf->cf_attach;
444 	if (ca->ca_devsize < sizeof(struct device))
445 		panic("config_make_softc");
446 
447 	/* get memory for all device vars */
448 	dev = (struct device *)malloc(ca->ca_devsize, M_DEVBUF, M_NOWAIT);
449 	if (!dev)
450 		panic("config_make_softc: allocation for device softc failed");
451 	bzero(dev, ca->ca_devsize);
452 	dev->dv_class = cd->cd_class;
453 	dev->dv_cfdata = cf;
454 
455 	/* If this is a STAR device, search for a free unit number */
456 	if (cf->cf_fstate == FSTATE_STAR) {
457 		for (dev->dv_unit = cf->cf_starunit1;
458 		    dev->dv_unit < cf->cf_unit; dev->dv_unit++)
459 			if (cd->cd_ndevs == 0 ||
460 			    cd->cd_devs[dev->dv_unit] == NULL)
461 				break;
462 	} else
463 		dev->dv_unit = cf->cf_unit;
464 
465 	/* compute length of name and decimal expansion of unit number */
466 	lname = strlen(cd->cd_name);
467 	xunit = number(&num[sizeof num], dev->dv_unit);
468 	lunit = &num[sizeof num] - xunit;
469 	if (lname + lunit >= sizeof(dev->dv_xname))
470 		panic("config_make_softc: device name too long");
471 
472 	bcopy(cd->cd_name, dev->dv_xname, lname);
473 	bcopy(xunit, dev->dv_xname + lname, lunit);
474 	dev->dv_parent = parent;
475 
476 	/* put this device in the devices array */
477 	if (dev->dv_unit >= cd->cd_ndevs) {
478 		/*
479 		 * Need to expand the array.
480 		 */
481 		int old = cd->cd_ndevs, new;
482 		void **nsp;
483 
484 		if (old == 0)
485 			new = MINALLOCSIZE / sizeof(void *);
486 		else
487 			new = old * 2;
488 		while (new <= dev->dv_unit)
489 			new *= 2;
490 		cd->cd_ndevs = new;
491 		nsp = malloc(new * sizeof(void *), M_DEVBUF, M_NOWAIT);
492 		if (nsp == 0)
493 			panic("config_make_softc: %sing dev array",
494 			    old != 0 ? "expand" : "creat");
495 		bzero(nsp + old, (new - old) * sizeof(void *));
496 		if (old != 0) {
497 			bcopy(cd->cd_devs, nsp, old * sizeof(void *));
498 			free(cd->cd_devs, M_DEVBUF);
499 		}
500 		cd->cd_devs = nsp;
501 	}
502 	if (cd->cd_devs[dev->dv_unit])
503 		panic("config_make_softc: duplicate %s", dev->dv_xname);
504 
505 	return (dev);
506 }
507 
508 /*
509  * Attach an event.  These must come from initially-zero space (see
510  * commented-out assignments below), but that occurs naturally for
511  * device instance variables.
512  */
513 void
514 evcnt_attach(dev, name, ev)
515 	struct device *dev;
516 	const char *name;
517 	struct evcnt *ev;
518 {
519 
520 #ifdef DIAGNOSTIC
521 	if (strlen(name) >= sizeof(ev->ev_name))
522 		panic("evcnt_attach");
523 #endif
524 	/* ev->ev_next = NULL; */
525 	ev->ev_dev = dev;
526 	/* ev->ev_count = 0; */
527 	strcpy(ev->ev_name, name);
528 	TAILQ_INSERT_TAIL(&allevents, ev, ev_list);
529 }
530 
531 static int
532 haschild(dev)
533 	struct device *dev;
534 {
535 	struct device *d;
536 
537 	for (d = alldevs.tqh_first; d != NULL; d = d->dv_list.tqe_next) {
538 		if (d->dv_parent == dev)
539 			return(1);
540 	}
541 	return(0);
542 }
543 
544 int
545 detach_devices(cond, condarg, callback, arg)
546 	cond_predicate_t cond;
547 	void *condarg;
548 	config_detach_callback_t callback;
549 	void *arg;
550 {
551 	struct device *d;
552 	int alldone = 1;
553 
554 	/*
555 	 * XXX should use circleq and run around the list backwards
556 	 * to allow for predicates to match children.
557 	 */
558 	d = alldevs.tqh_first;
559 	while (d != NULL) {
560 		if ((*cond)(d, condarg)) {
561 			struct cfdriver *drv = d->dv_cfdata->cf_driver;
562 
563 			/* device not busy? */
564 			/* driver's detach routine decides, upper
565 			   layer (eg bus dependent code) is notified
566 			   via callback */
567 #ifdef DEBUG
568 			printf("trying to detach device %s (%p)\n",
569 			       d->dv_xname, d);
570 #endif
571 			if (!haschild(d) &&
572 			    d->dv_cfdata->cf_attach->ca_detach &&
573 			    ((*(d->dv_cfdata->cf_attach->ca_detach))(d)) == 0) {
574 				int needit, i;
575 				struct device *help;
576 
577 				if (callback)
578 					(*callback)(d, arg);
579 
580 				/* remove reference in driver's devicelist */
581 				if ((d->dv_unit >= drv->cd_ndevs) ||
582 				    (drv->cd_devs[d->dv_unit]!=d))
583 					panic("bad unit in detach_devices");
584 				drv->cd_devs[d->dv_unit] = NULL;
585 
586 				/* driver is not needed anymore? */
587 				needit = 0;
588 				for(i = 0; i<drv->cd_ndevs; i++)
589 					if (drv->cd_devs[i])
590 						needit = 1;
591 
592 				if (!needit) {
593 					/* free devices array (alloc'd
594                                            in config_make_softc) */
595 					free(drv->cd_devs, M_DEVBUF);
596 					drv->cd_ndevs = 0;
597 				}
598 
599 				/* remove entry in global device list */
600 				help = d->dv_list.tqe_next;
601 				TAILQ_REMOVE(&alldevs, d, dv_list);
602 #ifdef DEBUG
603 				printf("%s removed\n", d->dv_xname);
604 #endif
605 				if (d->dv_cfdata->cf_fstate == FSTATE_FOUND)
606 					d->dv_cfdata->cf_fstate =
607 					    FSTATE_NOTFOUND;
608 				/* free memory for dev data (alloc'd
609                                    in config_make_softc) */
610 				free(d, M_DEVBUF);
611 				d = help;
612 				continue;
613 			} else
614 				alldone = 0;
615 		}
616 		d = d->dv_list.tqe_next;
617 	}
618 	return (!alldone);
619 }
620 
621 int
622 dev_matches_cfdata(dev, arg)
623 	struct device *dev;
624 	void *arg;
625 {
626 	struct cfdata *cfdata = arg;
627 	return(/* device uses same driver ? */
628 		(dev->dv_cfdata->cf_driver == cfdata->cf_driver)
629 		/* device instance described by this cfdata? */
630 		&& ((cfdata->cf_fstate == FSTATE_STAR)
631 		    || ((cfdata->cf_fstate == FSTATE_FOUND)
632 		        && (dev->dv_unit == cfdata->cf_unit)))
633 		);
634 }
635 
636 int
637 parentdev_matches_cfdata(dev, arg)
638 	struct device *dev;
639 	void *arg;
640 {
641 	return (dev->dv_parent ? dev_matches_cfdata(dev->dv_parent, arg) : 0);
642 }
643 
644 int
645 config_detach(cf, callback, arg)
646 	struct cfdata *cf;
647 	config_detach_callback_t callback;
648 	void *arg;
649 {
650 	return (detach_devices(dev_matches_cfdata, cf, callback, arg));
651 }
652 
653 int
654 config_detach_children(cf, callback, arg)
655 	struct cfdata *cf;
656 	config_detach_callback_t callback;
657 	void *arg;
658 {
659 	return (detach_devices(parentdev_matches_cfdata, cf, callback, arg));
660 }
661 
662 int
663 attach_loadable(parentname, parentunit, cftable)
664 	char *parentname;
665 	int parentunit;
666 	struct cftable *cftable;
667 {
668 	int found = 0;
669 	struct device *d;
670 
671 	TAILQ_INSERT_TAIL(&allcftables, cftable, list);
672 
673 	for(d = alldevs.tqh_first; d != NULL; d = d->dv_list.tqe_next) {
674 		struct cfdriver *drv = d->dv_cfdata->cf_driver;
675 
676 		if (strcmp(parentname, drv->cd_name) == NULL &&
677 		    (parentunit == -1 || parentunit == d->dv_unit)) {
678 			int s;
679 
680 			s = splhigh(); /* ??? */
681 			found |= (*d->dv_cfdata->cf_attach->ca_reprobe)(d,
682 			    &(cftable->tab[0]));
683 			splx(s);
684 		}
685 	}
686 	if (!found)
687 		TAILQ_REMOVE(&allcftables, cftable, list);
688 	return(found);
689 }
690 
691 static int
692 devcf_intable __P((struct device *, void *));
693 
694 static int
695 devcf_intable(dev, arg)
696 	struct device *dev;
697 	void *arg;
698 {
699 	struct cftable *tbl = arg;
700 	struct cfdata *cf;
701 
702 	for(cf = tbl->tab; cf->cf_driver; cf++) {
703 		if (dev->dv_cfdata == cf)
704 			return(1);
705 	}
706 	return(0);
707 }
708 
709 int
710 detach_loadable(cftable)
711 	struct cftable *cftable;
712 {
713 	if (!detach_devices(devcf_intable, cftable, 0, 0))
714 		return(0);
715 	TAILQ_REMOVE(&allcftables, cftable, list);
716 	return(1);
717 }
718