xref: /netbsd-src/sys/dev/wscons/wsmux.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
1 /*	$NetBSD: wsmux.c,v 1.35 2003/09/23 09:16:07 simonb Exp $	*/
2 
3 /*
4  * Copyright (c) 1998 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * Author: Lennart Augustsson <augustss@carlstedt.se>
8  *         Carlstedt Research & Technology
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 /*
40  * wscons mux device.
41  *
42  * The mux device is a collection of real mice and keyboards and acts as
43  * a merge point for all the events from the different real devices.
44  */
45 
46 #include <sys/cdefs.h>
47 __KERNEL_RCSID(0, "$NetBSD: wsmux.c,v 1.35 2003/09/23 09:16:07 simonb Exp $");
48 
49 #include "wsdisplay.h"
50 #include "wsmux.h"
51 #include "wskbd.h"
52 #include "wsmouse.h"
53 
54 #include <sys/param.h>
55 #include <sys/conf.h>
56 #include <sys/ioctl.h>
57 #include <sys/fcntl.h>
58 #include <sys/kernel.h>
59 #include <sys/malloc.h>
60 #include <sys/proc.h>
61 #include <sys/queue.h>
62 #include <sys/syslog.h>
63 #include <sys/systm.h>
64 #include <sys/tty.h>
65 #include <sys/signalvar.h>
66 #include <sys/device.h>
67 
68 #include "opt_wsdisplay_compat.h"
69 
70 #include <dev/wscons/wsconsio.h>
71 #include <dev/wscons/wseventvar.h>
72 #include <dev/wscons/wscons_callbacks.h>
73 #include <dev/wscons/wsmuxvar.h>
74 
75 #ifdef WSMUX_DEBUG
76 #define DPRINTF(x)	if (wsmuxdebug) printf x
77 #define DPRINTFN(n,x)	if (wsmuxdebug > (n)) printf x
78 int	wsmuxdebug = 0;
79 #else
80 #define DPRINTF(x)
81 #define DPRINTFN(n,x)
82 #endif
83 
84 /*
85  * The wsmux pseudo device is used to multiplex events from several wsmouse,
86  * wskbd, and/or wsmux devices together.
87  * The devices connected together form a tree with muxes in the interior
88  * and real devices (mouse and kbd) at the leaves.  The special case of
89  * a tree with one node (mux or other) is supported as well.
90  * Only the device at the root of the tree can be opened (if a non-root
91  * device is opened the subtree rooted at that point is severed from the
92  * containing tree).  When the root is opened it allocates a wseventvar
93  * struct which all the nodes in the tree will send their events too.
94  * An ioctl() performed on the root is propagated to all the nodes.
95  * There are also ioctl() operations to add and remove nodes from a tree.
96  */
97 
98 static int wsmux_mux_open(struct wsevsrc *, struct wseventvar *);
99 static int wsmux_mux_close(struct wsevsrc *);
100 
101 static void wsmux_do_open(struct wsmux_softc *, struct wseventvar *);
102 
103 static void wsmux_do_close(struct wsmux_softc *);
104 #if NWSDISPLAY > 0
105 static int wsmux_evsrc_set_display(struct device *, struct wsevsrc *);
106 #else
107 #define wsmux_evsrc_set_display NULL
108 #endif
109 
110 static int wsmux_do_displayioctl(struct device *dev, u_long cmd,
111 				 caddr_t data, int flag, struct proc *p);
112 static int wsmux_do_ioctl(struct device *, u_long, caddr_t,int,struct proc *);
113 
114 static int wsmux_add_mux(int, struct wsmux_softc *);
115 
116 void wsmuxattach(int);
117 
118 #define WSMUXDEV(n) ((n) & 0x7f)
119 #define WSMUXCTL(n) ((n) & 0x80)
120 
121 dev_type_open(wsmuxopen);
122 dev_type_close(wsmuxclose);
123 dev_type_read(wsmuxread);
124 dev_type_ioctl(wsmuxioctl);
125 dev_type_poll(wsmuxpoll);
126 dev_type_kqfilter(wsmuxkqfilter);
127 
128 const struct cdevsw wsmux_cdevsw = {
129 	wsmuxopen, wsmuxclose, wsmuxread, nowrite, wsmuxioctl,
130 	nostop, notty, wsmuxpoll, nommap, wsmuxkqfilter,
131 };
132 
133 struct wssrcops wsmux_srcops = {
134 	WSMUX_MUX,
135 	wsmux_mux_open, wsmux_mux_close, wsmux_do_ioctl, wsmux_do_displayioctl,
136 	wsmux_evsrc_set_display
137 };
138 
139 /* From upper level */
140 void
141 wsmuxattach(int n)
142 {
143 }
144 
145 /* Keep track of all muxes that have been allocated */
146 static int nwsmux = 0;
147 static struct wsmux_softc **wsmuxdevs;
148 
149 /* Return mux n, create if necessary */
150 struct wsmux_softc *
151 wsmux_getmux(int n)
152 {
153 	struct wsmux_softc *sc;
154 	int i;
155 	void *new;
156 
157 	n = WSMUXDEV(n);	/* limit range */
158 
159 	/* Make sure there is room for mux n in the table */
160 	if (n >= nwsmux) {
161 		i = nwsmux;
162 		nwsmux = n + 1;
163 		if (i != 0)
164 			new = realloc(wsmuxdevs, nwsmux * sizeof (*wsmuxdevs),
165 				      M_DEVBUF, M_NOWAIT);
166 		else
167 			new = malloc(nwsmux * sizeof (*wsmuxdevs),
168 				     M_DEVBUF, M_NOWAIT);
169 		if (new == NULL) {
170 			printf("wsmux_getmux: no memory for mux %d\n", n);
171 			return (NULL);
172 		}
173 		wsmuxdevs = new;
174 		for (; i < nwsmux; i++)
175 			wsmuxdevs[i] = NULL;
176 	}
177 
178 	sc = wsmuxdevs[n];
179 	if (sc == NULL) {
180 		sc = wsmux_create("wsmux", n);
181 		if (sc == NULL)
182 			printf("wsmux: attach out of memory\n");
183 		wsmuxdevs[n] = sc;
184 	}
185 	return (sc);
186 }
187 
188 /*
189  * open() of the pseudo device from device table.
190  */
191 int
192 wsmuxopen(dev_t dev, int flags, int mode, struct proc *p)
193 {
194 	struct wsmux_softc *sc;
195 	struct wseventvar *evar;
196 	int minr, unit;
197 
198 	minr = minor(dev);
199 	unit = WSMUXDEV(minr);
200 	sc = wsmux_getmux(unit);
201 	if (sc == NULL)
202 		return (ENXIO);
203 
204 	DPRINTF(("wsmuxopen: %s: sc=%p p=%p\n", sc->sc_base.me_dv.dv_xname,
205 		 sc, p));
206 
207 	if (WSMUXCTL(minr)) {
208 		/* This is the control device which does not allow reads. */
209 		if (flags & FREAD)
210 			return (EINVAL);
211 		return (0);
212 	}
213 	if ((flags & (FREAD | FWRITE)) == FWRITE)
214 		/* Allow write only open */
215 		return (0);
216 
217 	if (sc->sc_base.me_parent != NULL) {
218 		/* Grab the mux out of the greedy hands of the parent mux. */
219 		DPRINTF(("wsmuxopen: detach\n"));
220 		wsmux_detach_sc(&sc->sc_base);
221 	}
222 
223 	if (sc->sc_base.me_evp != NULL)
224 		/* Already open. */
225 		return (EBUSY);
226 
227 	evar = &sc->sc_base.me_evar;
228 	wsevent_init(evar);
229 	evar->io = p;
230 #ifdef WSDISPLAY_COMPAT_RAWKBD
231 	sc->sc_rawkbd = 0;
232 #endif
233 
234 	wsmux_do_open(sc, evar);
235 
236 	return (0);
237 }
238 
239 /*
240  * Open of a mux via the parent mux.
241  */
242 int
243 wsmux_mux_open(struct wsevsrc *me, struct wseventvar *evar)
244 {
245 	struct wsmux_softc *sc = (struct wsmux_softc *)me;
246 
247 #ifdef DIAGNOSTIC
248 	if (sc->sc_base.me_evp != NULL) {
249 		printf("wsmux_mux_open: busy\n");
250 		return (EBUSY);
251 	}
252 	if (sc->sc_base.me_parent == NULL) {
253 		printf("wsmux_mux_open: no parent\n");
254 		return (EINVAL);
255 	}
256 #endif
257 
258 	wsmux_do_open(sc, evar);
259 
260 	return (0);
261 }
262 
263 /* Common part of opening a mux. */
264 void
265 wsmux_do_open(struct wsmux_softc *sc, struct wseventvar *evar)
266 {
267 	struct wsevsrc *me;
268 
269 	sc->sc_base.me_evp = evar; /* remember event variable, mark as open */
270 
271 	/* Open all children. */
272 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
273 		DPRINTF(("wsmuxopen: %s: m=%p dev=%s\n",
274 			 sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname));
275 #ifdef DIAGNOSTIC
276 		if (me->me_evp != NULL) {
277 			printf("wsmuxopen: dev already in use\n");
278 			continue;
279 		}
280 		if (me->me_parent != sc) {
281 			printf("wsmux_do_open: bad child=%p\n", me);
282 			continue;
283 		}
284 		{
285 		int error = wsevsrc_open(me, evar);
286 		if (error) {
287 			DPRINTF(("wsmuxopen: open failed %d\n", error));
288 		}
289 		}
290 #else
291 		/* ignore errors, failing children will not be marked open */
292 		(void)wsevsrc_open(me, evar);
293 #endif
294 	}
295 }
296 
297 /*
298  * close() of the pseudo device from device table.
299  */
300 int
301 wsmuxclose(dev_t dev, int flags, int mode, struct proc *p)
302 {
303 	int minr = minor(dev);
304 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
305 	struct wseventvar *evar = sc->sc_base.me_evp;
306 
307 	if (WSMUXCTL(minr))
308 		/* control device */
309 		return (0);
310 	if (evar == NULL)
311 		/* Not open for read */
312 		return (0);
313 
314 	wsmux_do_close(sc);
315 	sc->sc_base.me_evp = NULL;
316 	wsevent_fini(evar);
317 	return (0);
318 }
319 
320 /*
321  * Close of a mux via the parent mux.
322  */
323 int
324 wsmux_mux_close(struct wsevsrc *me)
325 {
326 	me->me_evp = NULL;
327 	wsmux_do_close((struct wsmux_softc *)me);
328 	return (0);
329 }
330 
331 /* Common part of closing a mux. */
332 void
333 wsmux_do_close(struct wsmux_softc *sc)
334 {
335 	struct wsevsrc *me;
336 
337 	DPRINTF(("wsmuxclose: %s: sc=%p\n", sc->sc_base.me_dv.dv_xname, sc));
338 
339 	/* Close all the children. */
340 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
341 		DPRINTF(("wsmuxclose %s: m=%p dev=%s\n",
342 			 sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname));
343 #ifdef DIAGNOSTIC
344 		if (me->me_parent != sc) {
345 			printf("wsmuxclose: bad child=%p\n", me);
346 			continue;
347 		}
348 #endif
349 		(void)wsevsrc_close(me);
350 		me->me_evp = NULL;
351 	}
352 }
353 
354 /*
355  * read() of the pseudo device from device table.
356  */
357 int
358 wsmuxread(dev_t dev, struct uio *uio, int flags)
359 {
360 	int minr = minor(dev);
361 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
362 	struct wseventvar *evar;
363 	int error;
364 
365 	if (WSMUXCTL(minr)) {
366 		/* control device */
367 		return (EINVAL);
368 	}
369 
370 	evar = sc->sc_base.me_evp;
371 	if (evar == NULL) {
372 #ifdef DIAGNOSTIC
373 		/* XXX can we get here? */
374 		printf("wsmuxread: not open\n");
375 #endif
376 		return (EINVAL);
377 	}
378 
379 	DPRINTFN(5,("wsmuxread: %s event read evar=%p\n",
380 		    sc->sc_base.me_dv.dv_xname, evar));
381 	error = wsevent_read(evar, uio, flags);
382 	DPRINTFN(5,("wsmuxread: %s event read ==> error=%d\n",
383 		    sc->sc_base.me_dv.dv_xname, error));
384 	return (error);
385 }
386 
387 /*
388  * ioctl of the pseudo device from device table.
389  */
390 int
391 wsmuxioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
392 {
393 	int u = WSMUXDEV(minor(dev));
394 
395 	return wsmux_do_ioctl(&wsmuxdevs[u]->sc_base.me_dv, cmd, data, flag, p);
396 }
397 
398 /*
399  * ioctl of a mux via the parent mux, continuation of wsmuxioctl().
400  */
401 int
402 wsmux_do_ioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
403 	       struct proc *p)
404 {
405 	struct wsmux_softc *sc = (struct wsmux_softc *)dv;
406 	struct wsevsrc *me;
407 	int error, ok;
408 	int s, put, get, n;
409 	struct wseventvar *evar;
410 	struct wscons_event *ev;
411 	struct timeval thistime;
412 	struct wsmux_device_list *l;
413 
414 	DPRINTF(("wsmux_do_ioctl: %s: enter sc=%p, cmd=%08lx\n",
415 		 sc->sc_base.me_dv.dv_xname, sc, cmd));
416 
417 	switch (cmd) {
418 	case WSMUXIO_INJECTEVENT:
419 		/* Inject an event, e.g., from moused. */
420 		DPRINTF(("%s: inject\n", sc->sc_base.me_dv.dv_xname));
421 
422 		evar = sc->sc_base.me_evp;
423 		if (evar == NULL) {
424 			/* No event sink, so ignore it. */
425 			DPRINTF(("wsmux_do_ioctl: event ignored\n"));
426 			return (0);
427 		}
428 
429 		s = spltty();
430 		get = evar->get;
431 		put = evar->put;
432 		ev = &evar->q[put];
433 		if (++put % WSEVENT_QSIZE == get) {
434 			put--;
435 			splx(s);
436 			return (ENOSPC);
437 		}
438 		if (put >= WSEVENT_QSIZE)
439 			put = 0;
440 		*ev = *(struct wscons_event *)data;
441 		microtime(&thistime);
442 		TIMEVAL_TO_TIMESPEC(&thistime, &ev->time);
443 		evar->put = put;
444 		WSEVENT_WAKEUP(evar);
445 		splx(s);
446 		return (0);
447 	case WSMUXIO_ADD_DEVICE:
448 #define d ((struct wsmux_device *)data)
449 		DPRINTF(("%s: add type=%d, no=%d\n", sc->sc_base.me_dv.dv_xname,
450 			 d->type, d->idx));
451 		switch (d->type) {
452 #if NWSMOUSE > 0
453 		case WSMUX_MOUSE:
454 			return (wsmouse_add_mux(d->idx, sc));
455 #endif
456 #if NWSKBD > 0
457 		case WSMUX_KBD:
458 			return (wskbd_add_mux(d->idx, sc));
459 #endif
460 		case WSMUX_MUX:
461 			return (wsmux_add_mux(d->idx, sc));
462 		default:
463 			return (EINVAL);
464 		}
465 	case WSMUXIO_REMOVE_DEVICE:
466 		DPRINTF(("%s: rem type=%d, no=%d\n", sc->sc_base.me_dv.dv_xname,
467 			 d->type, d->idx));
468 		/* Locate the device */
469 		CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
470 			if (me->me_ops->type == d->type &&
471 			    me->me_dv.dv_unit == d->idx) {
472 				DPRINTF(("wsmux_do_ioctl: detach\n"));
473 				wsmux_detach_sc(me);
474 				return (0);
475 			}
476 		}
477 		return (EINVAL);
478 #undef d
479 
480 	case WSMUXIO_LIST_DEVICES:
481 		DPRINTF(("%s: list\n", sc->sc_base.me_dv.dv_xname));
482 		l = (struct wsmux_device_list *)data;
483 		n = 0;
484 		CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
485 			if (n >= WSMUX_MAXDEV)
486 				break;
487 			l->devices[n].type = me->me_ops->type;
488 			l->devices[n].idx = me->me_dv.dv_unit;
489 			n++;
490 		}
491 		l->ndevices = n;
492 		return (0);
493 #ifdef WSDISPLAY_COMPAT_RAWKBD
494 	case WSKBDIO_SETMODE:
495 		sc->sc_rawkbd = *(int *)data;
496 		DPRINTF(("wsmux_do_ioctl: save rawkbd = %d\n", sc->sc_rawkbd));
497 		break;
498 #endif
499 	case FIONBIO:
500 		DPRINTF(("%s: FIONBIO\n", sc->sc_base.me_dv.dv_xname));
501 		return (0);
502 
503 	case FIOASYNC:
504 		DPRINTF(("%s: FIOASYNC\n", sc->sc_base.me_dv.dv_xname));
505 		evar = sc->sc_base.me_evp;
506 		if (evar == NULL)
507 			return (EINVAL);
508 		evar->async = *(int *)data != 0;
509 		return (0);
510 	case FIOSETOWN:
511 		DPRINTF(("%s: FIOSETOWN\n", sc->sc_base.me_dv.dv_xname));
512 		evar = sc->sc_base.me_evp;
513 		if (evar == NULL)
514 			return (EINVAL);
515 		if (-*(int *)data != evar->io->p_pgid
516 		    && *(int *)data != evar->io->p_pid)
517 			return (EPERM);
518 		return (0);
519 	case TIOCSPGRP:
520 		DPRINTF(("%s: TIOCSPGRP\n", sc->sc_base.me_dv.dv_xname));
521 		evar = sc->sc_base.me_evp;
522 		if (evar == NULL)
523 			return (EINVAL);
524 		if (*(int *)data != evar->io->p_pgid)
525 			return (EPERM);
526 		return (0);
527 	default:
528 		DPRINTF(("%s: unknown\n", sc->sc_base.me_dv.dv_xname));
529 		break;
530 	}
531 
532 	if (sc->sc_base.me_evp == NULL
533 #if NWSDISPLAY > 0
534 	    && sc->sc_base.me_dispdv == NULL
535 #endif
536 	    )
537 		return (EACCES);
538 
539 	/* Return 0 if any of the ioctl() succeeds, otherwise the last error */
540 	error = 0;
541 	ok = 0;
542 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
543 #ifdef DIAGNOSTIC
544 		/* XXX check evp? */
545 		if (me->me_parent != sc) {
546 			printf("wsmux_do_ioctl: bad child %p\n", me);
547 			continue;
548 		}
549 #endif
550 		error = wsevsrc_ioctl(me, cmd, data, flag, p);
551 		DPRINTF(("wsmux_do_ioctl: %s: me=%p dev=%s ==> %d\n",
552 			 sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname,
553 			 error));
554 		if (!error)
555 			ok = 1;
556 	}
557 	if (ok)
558 		error = 0;
559 
560 	return (error);
561 }
562 
563 /*
564  * poll() of the pseudo device from device table.
565  */
566 int
567 wsmuxpoll(dev_t dev, int events, struct proc *p)
568 {
569 	int minr = minor(dev);
570 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
571 
572 	if (WSMUXCTL(minr)) {
573 		/* control device */
574 		return (EINVAL);
575 	}
576 
577 	if (sc->sc_base.me_evp == NULL) {
578 #ifdef DIAGNOSTIC
579 		printf("wsmuxpoll: not open\n");
580 #endif
581 		return (EACCES);
582 	}
583 
584 	return (wsevent_poll(sc->sc_base.me_evp, events, p));
585 }
586 
587 /*
588  * kqfilter() of the pseudo device from device table.
589  */
590 int
591 wsmuxkqfilter(dev_t dev, struct knote *kn)
592 {
593 	int minr = minor(dev);
594 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
595 
596 	if (WSMUXCTL(minr)) {
597 		/* control device */
598 		return (1);
599 	}
600 
601 	if (sc->sc_base.me_evp == NULL) {
602 #ifdef DIAGNOSTIC
603 		printf("wsmuxkqfilter: not open\n");
604 #endif
605 		return (1);
606 	}
607 
608 	return (wsevent_kqfilter(sc->sc_base.me_evp, kn));
609 }
610 
611 /*
612  * Add mux unit as a child to muxsc.
613  */
614 int
615 wsmux_add_mux(int unit, struct wsmux_softc *muxsc)
616 {
617 	struct wsmux_softc *sc, *m;
618 
619 	sc = wsmux_getmux(unit);
620 	if (sc == NULL)
621 		return (ENXIO);
622 
623 	DPRINTF(("wsmux_add_mux: %s(%p) to %s(%p)\n",
624 		 sc->sc_base.me_dv.dv_xname, sc, muxsc->sc_base.me_dv.dv_xname,
625 		 muxsc));
626 
627 	if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL)
628 		return (EBUSY);
629 
630 	/* The mux we are adding must not be an ancestor of itself. */
631 	for (m = muxsc; m != NULL ; m = m->sc_base.me_parent)
632 		if (m == sc)
633 			return (EINVAL);
634 
635 	return (wsmux_attach_sc(muxsc, &sc->sc_base));
636 }
637 
638 /* Create a new mux softc. */
639 struct wsmux_softc *
640 wsmux_create(const char *name, int unit)
641 {
642 	struct wsmux_softc *sc;
643 
644 	DPRINTF(("wsmux_create: allocating\n"));
645 	sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT|M_ZERO);
646 	if (sc == NULL)
647 		return (NULL);
648 	CIRCLEQ_INIT(&sc->sc_cld);
649 	snprintf(sc->sc_base.me_dv.dv_xname, sizeof sc->sc_base.me_dv.dv_xname,
650 		 "%s%d", name, unit);
651 	sc->sc_base.me_dv.dv_unit = unit;
652 	sc->sc_base.me_ops = &wsmux_srcops;
653 	return (sc);
654 }
655 
656 /* Attach me as a child to sc. */
657 int
658 wsmux_attach_sc(struct wsmux_softc *sc, struct wsevsrc *me)
659 {
660 	int error;
661 
662 	if (sc == NULL)
663 		return (EINVAL);
664 
665 	DPRINTF(("wsmux_attach_sc: %s(%p): type=%d\n",
666 		 sc->sc_base.me_dv.dv_xname, sc, me->me_ops->type));
667 
668 #ifdef DIAGNOSTIC
669 	if (me->me_parent != NULL) {
670 		printf("wsmux_attach_sc: busy\n");
671 		return (EBUSY);
672 	}
673 #endif
674 	me->me_parent = sc;
675 	CIRCLEQ_INSERT_TAIL(&sc->sc_cld, me, me_next);
676 
677 	error = 0;
678 #if NWSDISPLAY > 0
679 	if (sc->sc_base.me_dispdv != NULL) {
680 		/* This is a display mux, so attach the new device to it. */
681 		DPRINTF(("wsmux_attach_sc: %s: set display %p\n",
682 			 sc->sc_base.me_dv.dv_xname, sc->sc_base.me_dispdv));
683 		if (me->me_ops->dsetdisplay != NULL) {
684 			error = wsevsrc_set_display(me, &sc->sc_base);
685 			/* Ignore that the console already has a display. */
686 			if (error == EBUSY)
687 				error = 0;
688 			if (!error) {
689 #ifdef WSDISPLAY_COMPAT_RAWKBD
690 				DPRINTF(("wsmux_attach_sc: %s set rawkbd=%d\n",
691 					 me->me_dv.dv_xname, sc->sc_rawkbd));
692 				(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
693 						    &sc->sc_rawkbd, 0, 0);
694 #endif
695 			}
696 		}
697 	}
698 #endif
699 	if (sc->sc_base.me_evp != NULL) {
700 		/* Mux is open, so open the new subdevice */
701 		DPRINTF(("wsmux_attach_sc: %s: calling open of %s\n",
702 			 sc->sc_base.me_dv.dv_xname, me->me_dv.dv_xname));
703 		error = wsevsrc_open(me, sc->sc_base.me_evp);
704 	} else {
705 		DPRINTF(("wsmux_attach_sc: %s not open\n",
706 			 sc->sc_base.me_dv.dv_xname));
707 	}
708 
709 	if (error) {
710 		me->me_parent = NULL;
711 		CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
712 	}
713 
714 	DPRINTF(("wsmux_attach_sc: %s(%p) done, error=%d\n",
715 		 sc->sc_base.me_dv.dv_xname, sc, error));
716 	return (error);
717 }
718 
719 /* Remove me from the parent. */
720 void
721 wsmux_detach_sc(struct wsevsrc *me)
722 {
723 	struct wsmux_softc *sc = me->me_parent;
724 
725 	DPRINTF(("wsmux_detach_sc: %s(%p) parent=%p\n",
726 		 me->me_dv.dv_xname, me, sc));
727 
728 #ifdef DIAGNOSTIC
729 	if (sc == NULL) {
730 		printf("wsmux_detach_sc: %s has no parent\n",
731 		       me->me_dv.dv_xname);
732 		return;
733 	}
734 #endif
735 
736 #if NWSDISPLAY > 0
737 	if (sc->sc_base.me_dispdv != NULL) {
738 		if (me->me_ops->dsetdisplay != NULL)
739 			/* ignore error, there's nothing we can do */
740 			(void)wsevsrc_set_display(me, NULL);
741 	} else
742 #endif
743 		if (me->me_evp != NULL) {
744 		DPRINTF(("wsmux_detach_sc: close\n"));
745 		/* mux device is open, so close multiplexee */
746 		(void)wsevsrc_close(me);
747 	}
748 
749 	CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
750 	me->me_parent = NULL;
751 
752 	DPRINTF(("wsmux_detach_sc: done sc=%p\n", sc));
753 }
754 
755 /*
756  * Display ioctl() of a mux via the parent mux.
757  */
758 int
759 wsmux_do_displayioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
760 		      struct proc *p)
761 {
762 	struct wsmux_softc *sc = (struct wsmux_softc *)dv;
763 	struct wsevsrc *me;
764 	int error, ok;
765 
766 	DPRINTF(("wsmux_displayioctl: %s: sc=%p, cmd=%08lx\n",
767 		 sc->sc_base.me_dv.dv_xname, sc, cmd));
768 
769 #ifdef WSDISPLAY_COMPAT_RAWKBD
770 	if (cmd == WSKBDIO_SETMODE) {
771 		sc->sc_rawkbd = *(int *)data;
772 		DPRINTF(("wsmux_displayioctl: rawkbd = %d\n", sc->sc_rawkbd));
773 	}
774 #endif
775 
776 	/*
777 	 * Return 0 if any of the ioctl() succeeds, otherwise the last error.
778 	 * Return EPASSTHROUGH if no mux component accepts the ioctl.
779 	 */
780 	error = EPASSTHROUGH;
781 	ok = 0;
782 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
783 		DPRINTF(("wsmux_displayioctl: me=%p\n", me));
784 #ifdef DIAGNOSTIC
785 		if (me->me_parent != sc) {
786 			printf("wsmux_displayioctl: bad child %p\n", me);
787 			continue;
788 		}
789 #endif
790 		if (me->me_ops->ddispioctl != NULL) {
791 			error = wsevsrc_display_ioctl(me, cmd, data, flag, p);
792 			DPRINTF(("wsmux_displayioctl: me=%p dev=%s ==> %d\n",
793 				 me, me->me_dv.dv_xname, error));
794 			if (!error)
795 				ok = 1;
796 		}
797 	}
798 	if (ok)
799 		error = 0;
800 
801 	return (error);
802 }
803 
804 #if NWSDISPLAY > 0
805 /*
806  * Set display of a mux via the parent mux.
807  */
808 int
809 wsmux_evsrc_set_display(struct device *dv, struct wsevsrc *ame)
810 {
811 	struct wsmux_softc *muxsc = (struct wsmux_softc *)ame;
812 	struct wsmux_softc *sc = (struct wsmux_softc *)dv;
813 	struct device *displaydv = muxsc ? muxsc->sc_base.me_dispdv : NULL;
814 
815 	DPRINTF(("wsmux_set_display: %s: displaydv=%p\n",
816 		 sc->sc_base.me_dv.dv_xname, displaydv));
817 
818 	if (displaydv != NULL) {
819 		if (sc->sc_base.me_dispdv != NULL)
820 			return (EBUSY);
821 	} else {
822 		if (sc->sc_base.me_dispdv == NULL)
823 			return (ENXIO);
824 	}
825 
826 	return wsmux_set_display(sc, displaydv);
827 }
828 
829 int
830 wsmux_set_display(struct wsmux_softc *sc, struct device *displaydv)
831 {
832 	struct device *odisplaydv;
833 	struct wsevsrc *me;
834 	struct wsmux_softc *nsc = displaydv ? sc : NULL;
835 	int error, ok;
836 
837 	odisplaydv = sc->sc_base.me_dispdv;
838 	sc->sc_base.me_dispdv = displaydv;
839 
840 	if (displaydv)
841 		printf("%s: connecting to %s\n",
842 		       sc->sc_base.me_dv.dv_xname, displaydv->dv_xname);
843 	ok = 0;
844 	error = 0;
845 	CIRCLEQ_FOREACH(me, &sc->sc_cld,me_next) {
846 #ifdef DIAGNOSTIC
847 		if (me->me_parent != sc) {
848 			printf("wsmux_set_display: bad child parent %p\n", me);
849 			continue;
850 		}
851 #endif
852 		if (me->me_ops->dsetdisplay != NULL) {
853 			error = wsevsrc_set_display(me, &nsc->sc_base);
854 			DPRINTF(("wsmux_set_display: m=%p dev=%s error=%d\n",
855 				 me, me->me_dv.dv_xname, error));
856 			if (!error) {
857 				ok = 1;
858 #ifdef WSDISPLAY_COMPAT_RAWKBD
859 				DPRINTF(("wsmux_set_display: %s set rawkbd=%d\n",
860 					 me->me_dv.dv_xname, sc->sc_rawkbd));
861 				(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
862 						    &sc->sc_rawkbd, 0, 0);
863 #endif
864 			}
865 		}
866 	}
867 	if (ok)
868 		error = 0;
869 
870 	if (displaydv == NULL)
871 		printf("%s: disconnecting from %s\n",
872 		       sc->sc_base.me_dv.dv_xname, odisplaydv->dv_xname);
873 
874 	return (error);
875 }
876 #endif /* NWSDISPLAY > 0 */
877