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