subr_autoconf.c revision 1.14
1/*	$OpenBSD: subr_autoconf.c,v 1.14 1996/11/21 12:47:15 mickey 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#ifdef __alpha__
60#include <machine/autoconf.h>
61#endif /* __alpha__ */
62
63/*
64 * Autoconfiguration subroutines.
65 */
66
67/*
68 * ioconf.c exports exactly two names: cfdata and cfroots.  All system
69 * devices and drivers are found via these tables.
70 */
71extern short cfroots[];
72
73#define	ROOT ((struct device *)NULL)
74
75struct matchinfo {
76	cfmatch_t fn;
77	struct	device *parent;
78	void	*match, *aux;
79	int	indirect, pri;
80};
81
82struct cftable_head allcftables;
83
84static struct cftable staticcftable = {
85	cfdata
86};
87
88static char *number __P((char *, int));
89static void mapply __P((struct matchinfo *, struct cfdata *));
90
91struct devicelist alldevs;		/* list of all devices */
92struct evcntlist allevents;		/* list of all event counters */
93
94/*
95 * Initialize autoconfiguration data structures.  This occurs before console
96 * initialization as that might require use of this subsystem.  Furthermore
97 * this means that malloc et al. isn't yet available.
98 */
99void
100config_init()
101{
102
103	TAILQ_INIT(&alldevs);
104	TAILQ_INIT(&allevents);
105	TAILQ_INIT(&allcftables);
106	TAILQ_INSERT_TAIL(&allcftables, &staticcftable, list);
107}
108
109/*
110 * Apply the matching function and choose the best.  This is used
111 * a few times and we want to keep the code small.
112 */
113static void
114mapply(m, cf)
115	register struct matchinfo *m;
116	register struct cfdata *cf;
117{
118	register int pri;
119	void *match;
120
121	if (m->indirect)
122		match = config_make_softc(m->parent, cf);
123	else
124		match = cf;
125
126	if (m->fn != NULL)
127		pri = (*m->fn)(m->parent, match, m->aux);
128	else {
129	        if (cf->cf_attach->ca_match == NULL) {
130			panic("mapply: no match function for '%s' device\n",
131			    cf->cf_driver->cd_name);
132		}
133		pri = (*cf->cf_attach->ca_match)(m->parent, match, m->aux);
134	}
135
136	if (pri > m->pri) {
137		if (m->indirect && m->match)
138			free(m->match, M_DEVBUF);
139		m->match = match;
140		m->pri = pri;
141	} else {
142		if (m->indirect)
143			free(match, M_DEVBUF);
144	}
145}
146
147/*
148 * Iterate over all potential children of some device, calling the given
149 * function (default being the child's match function) for each one.
150 * Nonzero returns are matches; the highest value returned is considered
151 * the best match.  Return the `found child' if we got a match, or NULL
152 * otherwise.  The `aux' pointer is simply passed on through.
153 *
154 * Note that this function is designed so that it can be used to apply
155 * an arbitrary function to all potential children (its return value
156 * can be ignored).
157 */
158void *
159config_search(fn, parent, aux)
160	cfmatch_t fn;
161	register struct device *parent;
162	void *aux;
163{
164	register struct cfdata *cf;
165	register short *p;
166	struct matchinfo m;
167	struct cftable *t;
168
169	m.fn = fn;
170	m.parent = parent;
171	m.match = NULL;
172	m.aux = aux;
173	m.indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
174	m.pri = 0;
175	for(t = allcftables.tqh_first; t; t = t->list.tqe_next){
176	  for (cf = t->tab; cf->cf_driver; cf++) {
177	    /*
178	     * Skip cf if no longer eligible, otherwise scan through
179	     * parents for one matching `parent', and try match function.
180	     */
181	    if (cf->cf_fstate == FSTATE_FOUND)
182	      continue;
183	    if (cf->cf_fstate == FSTATE_DNOTFOUND ||
184		cf->cf_fstate == FSTATE_DSTAR)
185	      continue;
186	    for (p = cf->cf_parents; *p >= 0; p++)
187	      if (parent->dv_cfdata == &(t->tab)[*p])
188		mapply(&m, cf);
189	  }
190	}
191	return (m.match);
192}
193
194/*
195 * Iterate over all potential children of some device, calling the given
196 * function for each one.
197 *
198 * Note that this function is designed so that it can be used to apply
199 * an arbitrary function to all potential children (its return value
200 * can be ignored).
201 */
202void
203config_scan(fn, parent)
204	cfscan_t fn;
205	register struct device *parent;
206{
207	register struct cfdata *cf;
208	register short *p;
209	void *match;
210	int indirect;
211	struct cftable *t;
212
213	indirect = parent && parent->dv_cfdata->cf_driver->cd_indirect;
214	for (t = allcftables.tqh_first; t; t = t->list.tqe_next) {
215	  for (cf = t->tab; cf->cf_driver; cf++) {
216	    /*
217	     * Skip cf if no longer eligible, otherwise scan through
218	     * parents for one matching `parent', and try match function.
219	     */
220	    if (cf->cf_fstate == FSTATE_FOUND)
221	      continue;
222	    if (cf->cf_fstate == FSTATE_DNOTFOUND ||
223		cf->cf_fstate == FSTATE_DSTAR)
224	      continue;
225	    for (p = cf->cf_parents; *p >= 0; p++)
226	      if (parent->dv_cfdata == &(t->tab)[*p]) {
227		if (indirect)
228		  match = config_make_softc(parent, cf);
229		else
230		  match = cf;
231		(*fn)(parent, match);
232	      }
233	  }
234	}
235}
236
237/*
238 * Find the given root device.
239 * This is much like config_search, but there is no parent.
240 */
241void *
242config_rootsearch(fn, rootname, aux)
243	register cfmatch_t fn;
244	register char *rootname;
245	register void *aux;
246{
247	register struct cfdata *cf;
248	register short *p;
249	struct matchinfo m;
250
251	m.fn = fn;
252	m.parent = ROOT;
253	m.match = NULL;
254	m.aux = aux;
255	m.indirect = 0;
256	m.pri = 0;
257	/*
258	 * Look at root entries for matching name.  We do not bother
259	 * with found-state here since only one root should ever be
260	 * searched (and it must be done first).
261	 */
262	for (p = cfroots; *p >= 0; p++) {
263		cf = &cfdata[*p];
264		if (strcmp(cf->cf_driver->cd_name, rootname) == 0)
265			mapply(&m, cf);
266	}
267	return (m.match);
268}
269
270char *msgs[3] = { "", " not configured\n", " unsupported\n" };
271
272/*
273 * The given `aux' argument describes a device that has been found
274 * on the given parent, but not necessarily configured.  Locate the
275 * configuration data for that device (using the submatch function
276 * provided, or using candidates' cd_match configuration driver
277 * functions) and attach it, and return true.  If the device was
278 * not configured, call the given `print' function and return 0.
279 */
280struct device *
281config_found_sm(parent, aux, print, submatch)
282	struct device *parent;
283	void *aux;
284	cfprint_t print;
285	cfmatch_t submatch;
286{
287	void *match;
288
289	if ((match = config_search(submatch, parent, aux)) != NULL)
290		return (config_attach(parent, match, aux, print));
291	if (print)
292		printf(msgs[(*print)(aux, parent->dv_xname)]);
293	return (NULL);
294}
295
296/*
297 * As above, but for root devices.
298 */
299struct device *
300config_rootfound(rootname, aux)
301	char *rootname;
302	void *aux;
303{
304	void *match;
305
306	if ((match = config_rootsearch((cfmatch_t)NULL, rootname, aux)) != NULL)
307		return (config_attach(ROOT, match, aux, (cfprint_t)NULL));
308	printf("root device %s not configured\n", rootname);
309	return (NULL);
310}
311
312/* just like sprintf(buf, "%d") except that it works from the end */
313static char *
314number(ep, n)
315	register char *ep;
316	register int n;
317{
318
319	*--ep = 0;
320	while (n >= 10) {
321		*--ep = (n % 10) + '0';
322		n /= 10;
323	}
324	*--ep = n + '0';
325	return (ep);
326}
327
328/*
329 * Attach a found device.  Allocates memory for device variables.
330 */
331struct device *
332config_attach(parent, match, aux, print)
333	register struct device *parent;
334	void *match;
335	register void *aux;
336	cfprint_t print;
337{
338	register struct cfdata *cf;
339	register struct device *dev;
340	register struct cfdriver *cd;
341	register struct cfattach *ca;
342	struct cftable *t;
343
344	if (parent && parent->dv_cfdata->cf_driver->cd_indirect) {
345		dev = match;
346		cf = dev->dv_cfdata;
347	} else {
348		cf = match;
349		dev = config_make_softc(parent, cf);
350	}
351
352	cd = cf->cf_driver;
353	ca = cf->cf_attach;
354	cd->cd_devs[cf->cf_unit] = dev;
355
356	if (cf->cf_fstate == FSTATE_STAR)
357		cf->cf_unit++;
358	else
359		cf->cf_fstate = FSTATE_FOUND;
360
361	TAILQ_INSERT_TAIL(&alldevs, dev, dv_list);
362
363	if (parent == ROOT)
364		printf("%s (root)", dev->dv_xname);
365	else {
366		printf("%s at %s", dev->dv_xname, parent->dv_xname);
367		if (print)
368			(void) (*print)(aux, (char *)0);
369	}
370
371	/*
372	 * Before attaching, clobber any unfound devices that are
373	 * otherwise identical, or bump the unit number on all starred
374	 * cfdata for this device.
375	 */
376	for (t = allcftables.tqh_first; t; t = t->list.tqe_next) {
377	  for (cf = t->tab; cf->cf_driver; cf++)
378	    if (cf->cf_driver == cd && cf->cf_unit == dev->dv_unit) {
379			if (cf->cf_fstate == FSTATE_NOTFOUND)
380				cf->cf_fstate = FSTATE_FOUND;
381			if (cf->cf_fstate == FSTATE_STAR)
382				cf->cf_unit++;
383	    }
384	}
385#ifdef __alpha__
386	device_register(dev, aux);
387#endif
388	(*ca->ca_attach)(parent, dev, aux);
389	return (dev);
390}
391
392struct device *
393config_make_softc(parent, cf)
394	struct device *parent;
395	struct cfdata *cf;
396{
397	register struct device *dev;
398	register struct cfdriver *cd;
399	register struct cfattach *ca;
400	register size_t lname, lunit;
401	register char *xunit;
402	char num[10];
403
404	cd = cf->cf_driver;
405	ca = cf->cf_attach;
406	if (ca->ca_devsize < sizeof(struct device))
407		panic("config_make_softc");
408
409	/* compute length of name and decimal expansion of unit number */
410	lname = strlen(cd->cd_name);
411	xunit = number(&num[sizeof num], cf->cf_unit);
412	lunit = &num[sizeof num] - xunit;
413	if (lname + lunit >= sizeof(dev->dv_xname))
414		panic("config_attach: device name too long");
415
416	/* get memory for all device vars */
417	dev = (struct device *)malloc(ca->ca_devsize, M_DEVBUF, M_NOWAIT);
418	if (!dev)
419	    panic("config_attach: memory allocation for device softc failed");
420	bzero(dev, ca->ca_devsize);
421	dev->dv_class = cd->cd_class;
422	dev->dv_cfdata = cf;
423	dev->dv_unit = cf->cf_unit;
424	bcopy(cd->cd_name, dev->dv_xname, lname);
425	bcopy(xunit, dev->dv_xname + lname, lunit);
426	dev->dv_parent = parent;
427
428	/* put this device in the devices array */
429	if (dev->dv_unit >= cd->cd_ndevs) {
430		/*
431		 * Need to expand the array.
432		 */
433		int old = cd->cd_ndevs, new;
434		void **nsp;
435
436		if (old == 0)
437			new = MINALLOCSIZE / sizeof(void *);
438		else
439			new = old * 2;
440		while (new <= dev->dv_unit)
441			new *= 2;
442		cd->cd_ndevs = new;
443		nsp = malloc(new * sizeof(void *), M_DEVBUF, M_NOWAIT);
444		if (nsp == 0)
445			panic("config_attach: %sing dev array",
446			    old != 0 ? "expand" : "creat");
447		bzero(nsp + old, (new - old) * sizeof(void *));
448		if (old != 0) {
449			bcopy(cd->cd_devs, nsp, old * sizeof(void *));
450			free(cd->cd_devs, M_DEVBUF);
451		}
452		cd->cd_devs = nsp;
453	}
454	if (cd->cd_devs[dev->dv_unit])
455		panic("config_attach: duplicate %s", dev->dv_xname);
456
457	return (dev);
458}
459
460/*
461 * Attach an event.  These must come from initially-zero space (see
462 * commented-out assignments below), but that occurs naturally for
463 * device instance variables.
464 */
465void
466evcnt_attach(dev, name, ev)
467	struct device *dev;
468	const char *name;
469	struct evcnt *ev;
470{
471
472#ifdef DIAGNOSTIC
473	if (strlen(name) >= sizeof(ev->ev_name))
474		panic("evcnt_attach");
475#endif
476	/* ev->ev_next = NULL; */
477	ev->ev_dev = dev;
478	/* ev->ev_count = 0; */
479	strcpy(ev->ev_name, name);
480	TAILQ_INSERT_TAIL(&allevents, ev, ev_list);
481}
482
483typedef int (*cond_predicate_t) __P((struct device*, void*));
484
485static int haschild __P((struct device *));
486static int detach_devices __P((cond_predicate_t, void *,
487			       config_detach_callback_t, void *));
488
489static int
490haschild(dev)
491	struct device *dev;
492{
493	struct device *d;
494
495	for (d = alldevs.tqh_first;
496	     d != NULL;
497	     d = d->dv_list.tqe_next) {
498		if (d->dv_parent == dev)
499			return(1);
500	}
501	return(0);
502}
503
504static int
505detach_devices(cond, condarg, callback, arg)
506	cond_predicate_t cond;
507	void *condarg;
508	config_detach_callback_t callback;
509	void *arg;
510{
511	struct device *d;
512	int alldone = 1;
513
514	/*
515	 * XXX should use circleq and run around the list backwards
516	 * to allow for predicates to match children.
517	 */
518	d = alldevs.tqh_first;
519	while (d != NULL) {
520		if ((*cond)(d, condarg)) {
521			struct cfdriver *drv = d->dv_cfdata->cf_driver;
522
523			/* device not busy? */
524			/* driver's detach routine decides, upper
525			   layer (eg bus dependent code) is notified
526			   via callback */
527#ifdef DEBUG
528			printf("trying to detach device %s (%p)\n",
529			       d->dv_xname, d);
530#endif
531			if (!haschild(d) &&
532			    d->dv_cfdata->cf_attach->ca_detach &&
533			    ((*(d->dv_cfdata->cf_attach->ca_detach))(d)) == 0) {
534				int needit, i;
535				struct device *help;
536
537				if (callback)
538					(*callback)(d, arg);
539
540				/* remove reference in driver's devicelist */
541				if ((d->dv_unit >= drv->cd_ndevs) ||
542				    (drv->cd_devs[d->dv_unit]!=d))
543					panic("bad unit in detach_devices");
544				drv->cd_devs[d->dv_unit] = NULL;
545
546				/* driver is not needed anymore? */
547				needit = 0;
548				for(i = 0; i<drv->cd_ndevs; i++)
549					if (drv->cd_devs[i])
550						needit = 1;
551
552				if (!needit) {
553					/* free devices array (alloc'd
554                                           in config_make_softc) */
555					free(drv->cd_devs, M_DEVBUF);
556					drv->cd_ndevs = 0;
557				}
558
559				/* remove entry in global device list */
560				help = d->dv_list.tqe_next;
561				TAILQ_REMOVE(&alldevs, d, dv_list);
562#ifdef DEBUG
563				printf("%s removed\n", d->dv_xname);
564#endif
565				d->dv_cfdata->cf_fstate = FSTATE_NOTFOUND;
566				/* free memory for dev data (alloc'd
567                                   in config_make_softc) */
568				free(d, M_DEVBUF);
569				d = help;
570				continue;
571			} else
572				alldone = 0;
573		}
574		d = d->dv_list.tqe_next;
575	}
576	return(!alldone);
577}
578
579int dev_matches_cfdata __P((struct device *dev, void *));
580
581int
582dev_matches_cfdata(dev, arg)
583	struct device *dev;
584	void *arg;
585{
586	struct cfdata *cfdata = arg;
587	return(/* device uses same driver ? */
588		(dev->dv_cfdata->cf_driver == cfdata->cf_driver)
589		/* device instance described by this cfdata? */
590		&& ((cfdata->cf_fstate == FSTATE_STAR)
591		    || ((cfdata->cf_fstate == FSTATE_FOUND)
592		        && (dev->dv_unit == cfdata->cf_unit)))
593		);
594}
595
596int
597config_detach(cf, callback, arg)
598	struct cfdata *cf;
599	config_detach_callback_t callback;
600	void *arg;
601{
602	return(detach_devices(dev_matches_cfdata, cf, callback, arg));
603}
604
605int
606attach_loadable(parentname, parentunit, cftable)
607	char *parentname;
608	int parentunit;
609	struct cftable *cftable;
610{
611	int found = 0;
612	struct device *d;
613
614	TAILQ_INSERT_TAIL(&allcftables, cftable, list);
615
616	for(d = alldevs.tqh_first; d != NULL; d = d->dv_list.tqe_next) {
617		struct cfdriver *drv = d->dv_cfdata->cf_driver;
618
619		if (strcmp(parentname, drv->cd_name) == NULL &&
620		    (parentunit == -1 || parentunit == d->dv_unit)) {
621			int s;
622
623			s = splhigh(); /* ??? */
624			found |= (*d->dv_cfdata->cf_attach->ca_reprobe)(d,
625			    &(cftable->tab[0]));
626			splx(s);
627		}
628	}
629	if (!found)
630		TAILQ_REMOVE(&allcftables, cftable, list);
631	return(found);
632}
633
634static int
635devcf_intable __P((struct device *, void *));
636
637static int
638devcf_intable(dev, arg)
639	struct device *dev;
640	void *arg;
641{
642	struct cftable *tbl = arg;
643	struct cfdata *cf;
644
645	for(cf = tbl->tab; cf->cf_driver; cf++) {
646		if (dev->dv_cfdata == cf)
647			return(1);
648	}
649	return(0);
650}
651
652int
653detach_loadable(cftable)
654	struct cftable *cftable;
655{
656	if (!detach_devices(devcf_intable, cftable, 0, 0))
657		return(0);
658	TAILQ_REMOVE(&allcftables, cftable, list);
659	return(1);
660}
661