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