xref: /netbsd/sys/dev/wscons/wsmux.c (revision c4a72b64)
1 /*	$NetBSD: wsmux.c,v 1.31 2002/10/23 09:14:09 jdolecek 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.31 2002/10/23 09:14:09 jdolecek 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 TIOCSPGRP:
511 		DPRINTF(("%s: TIOCSPGRP\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 			return (EPERM);
517 		return (0);
518 	default:
519 		DPRINTF(("%s: unknown\n", sc->sc_base.me_dv.dv_xname));
520 		break;
521 	}
522 
523 	if (sc->sc_base.me_evp == NULL
524 #if NWSDISPLAY > 0
525 	    && sc->sc_base.me_dispdv == NULL
526 #endif
527 	    )
528 		return (EACCES);
529 
530 	/* Return 0 if any of the ioctl() succeeds, otherwise the last error */
531 	error = 0;
532 	ok = 0;
533 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
534 #ifdef DIAGNOSTIC
535 		/* XXX check evp? */
536 		if (me->me_parent != sc) {
537 			printf("wsmux_do_ioctl: bad child %p\n", me);
538 			continue;
539 		}
540 #endif
541 		error = wsevsrc_ioctl(me, cmd, data, flag, p);
542 		DPRINTF(("wsmux_do_ioctl: %s: me=%p dev=%s ==> %d\n",
543 			 sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname,
544 			 error));
545 		if (!error)
546 			ok = 1;
547 	}
548 	if (ok)
549 		error = 0;
550 
551 	return (error);
552 }
553 
554 /*
555  * poll() of the pseudo device from device table.
556  */
557 int
558 wsmuxpoll(dev_t dev, int events, struct proc *p)
559 {
560 	int minr = minor(dev);
561 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
562 
563 	if (WSMUXCTL(minr)) {
564 		/* control device */
565 		return (EINVAL);
566 	}
567 
568 	if (sc->sc_base.me_evp == NULL) {
569 #ifdef DIAGNOSTIC
570 		printf("wsmuxpoll: not open\n");
571 #endif
572 		return (EACCES);
573 	}
574 
575 	return (wsevent_poll(sc->sc_base.me_evp, events, p));
576 }
577 
578 /*
579  * kqfilter() of the pseudo device from device table.
580  */
581 int
582 wsmuxkqfilter(dev_t dev, struct knote *kn)
583 {
584 	int minr = minor(dev);
585 	struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
586 
587 	if (WSMUXCTL(minr)) {
588 		/* control device */
589 		return (1);
590 	}
591 
592 	if (sc->sc_base.me_evp == NULL) {
593 #ifdef DIAGNOSTIC
594 		printf("wsmuxkqfilter: not open\n");
595 #endif
596 		return (1);
597 	}
598 
599 	return (wsevent_kqfilter(sc->sc_base.me_evp, kn));
600 }
601 
602 /*
603  * Add mux unit as a child to muxsc.
604  */
605 int
606 wsmux_add_mux(int unit, struct wsmux_softc *muxsc)
607 {
608 	struct wsmux_softc *sc, *m;
609 
610 	sc = wsmux_getmux(unit);
611 	if (sc == NULL)
612 		return (ENXIO);
613 
614 	DPRINTF(("wsmux_add_mux: %s(%p) to %s(%p)\n",
615 		 sc->sc_base.me_dv.dv_xname, sc, muxsc->sc_base.me_dv.dv_xname,
616 		 muxsc));
617 
618 	if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL)
619 		return (EBUSY);
620 
621 	/* The mux we are adding must not be an ancestor of itself. */
622 	for (m = muxsc; m != NULL ; m = m->sc_base.me_parent)
623 		if (m == sc)
624 			return (EINVAL);
625 
626 	return (wsmux_attach_sc(muxsc, &sc->sc_base));
627 }
628 
629 /* Create a new mux softc. */
630 struct wsmux_softc *
631 wsmux_create(const char *name, int unit)
632 {
633 	struct wsmux_softc *sc;
634 
635 	DPRINTF(("wsmux_create: allocating\n"));
636 	sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT|M_ZERO);
637 	if (sc == NULL)
638 		return (NULL);
639 	CIRCLEQ_INIT(&sc->sc_cld);
640 	snprintf(sc->sc_base.me_dv.dv_xname, sizeof sc->sc_base.me_dv.dv_xname,
641 		 "%s%d", name, unit);
642 	sc->sc_base.me_dv.dv_unit = unit;
643 	sc->sc_base.me_ops = &wsmux_srcops;
644 	return (sc);
645 }
646 
647 /* Attach me as a child to sc. */
648 int
649 wsmux_attach_sc(struct wsmux_softc *sc, struct wsevsrc *me)
650 {
651 	int error;
652 
653 	if (sc == NULL)
654 		return (EINVAL);
655 
656 	DPRINTF(("wsmux_attach_sc: %s(%p): type=%d\n",
657 		 sc->sc_base.me_dv.dv_xname, sc, me->me_ops->type));
658 
659 #ifdef DIAGNOSTIC
660 	if (me->me_parent != NULL) {
661 		printf("wsmux_attach_sc: busy\n");
662 		return (EBUSY);
663 	}
664 #endif
665 	me->me_parent = sc;
666 	CIRCLEQ_INSERT_TAIL(&sc->sc_cld, me, me_next);
667 
668 	error = 0;
669 #if NWSDISPLAY > 0
670 	if (sc->sc_base.me_dispdv != NULL) {
671 		/* This is a display mux, so attach the new device to it. */
672 		DPRINTF(("wsmux_attach_sc: %s: set display %p\n",
673 			 sc->sc_base.me_dv.dv_xname, sc->sc_base.me_dispdv));
674 		if (me->me_ops->dsetdisplay != NULL) {
675 			error = wsevsrc_set_display(me, &sc->sc_base);
676 			/* Ignore that the console already has a display. */
677 			if (error == EBUSY)
678 				error = 0;
679 			if (!error) {
680 #ifdef WSDISPLAY_COMPAT_RAWKBD
681 				DPRINTF(("wsmux_attach_sc: %s set rawkbd=%d\n",
682 					 me->me_dv.dv_xname, sc->sc_rawkbd));
683 				(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
684 						    &sc->sc_rawkbd, 0, 0);
685 #endif
686 			}
687 		}
688 	}
689 #endif
690 	if (sc->sc_base.me_evp != NULL) {
691 		/* Mux is open, so open the new subdevice */
692 		DPRINTF(("wsmux_attach_sc: %s: calling open of %s\n",
693 			 sc->sc_base.me_dv.dv_xname, me->me_dv.dv_xname));
694 		error = wsevsrc_open(me, sc->sc_base.me_evp);
695 	} else {
696 		DPRINTF(("wsmux_attach_sc: %s not open\n",
697 			 sc->sc_base.me_dv.dv_xname));
698 	}
699 
700 	if (error) {
701 		me->me_parent = NULL;
702 		CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
703 	}
704 
705 	DPRINTF(("wsmux_attach_sc: %s(%p) done, error=%d\n",
706 		 sc->sc_base.me_dv.dv_xname, sc, error));
707 	return (error);
708 }
709 
710 /* Remove me from the parent. */
711 void
712 wsmux_detach_sc(struct wsevsrc *me)
713 {
714 	struct wsmux_softc *sc = me->me_parent;
715 
716 	DPRINTF(("wsmux_detach_sc: %s(%p) parent=%p\n",
717 		 me->me_dv.dv_xname, me, sc));
718 
719 #ifdef DIAGNOSTIC
720 	if (sc == NULL) {
721 		printf("wsmux_detach_sc: %s has no parent\n",
722 		       me->me_dv.dv_xname);
723 		return;
724 	}
725 #endif
726 
727 #if NWSDISPLAY > 0
728 	if (sc->sc_base.me_dispdv != NULL) {
729 		if (me->me_ops->dsetdisplay != NULL)
730 			/* ignore error, there's nothing we can do */
731 			(void)wsevsrc_set_display(me, NULL);
732 	} else
733 #endif
734 		if (me->me_evp != NULL) {
735 		DPRINTF(("wsmux_detach_sc: close\n"));
736 		/* mux device is open, so close multiplexee */
737 		(void)wsevsrc_close(me);
738 	}
739 
740 	CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
741 	me->me_parent = NULL;
742 
743 	DPRINTF(("wsmux_detach_sc: done sc=%p\n", sc));
744 }
745 
746 /*
747  * Display ioctl() of a mux via the parent mux.
748  */
749 int
750 wsmux_do_displayioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
751 		      struct proc *p)
752 {
753 	struct wsmux_softc *sc = (struct wsmux_softc *)dv;
754 	struct wsevsrc *me;
755 	int error, ok;
756 
757 	DPRINTF(("wsmux_displayioctl: %s: sc=%p, cmd=%08lx\n",
758 		 sc->sc_base.me_dv.dv_xname, sc, cmd));
759 
760 #ifdef WSDISPLAY_COMPAT_RAWKBD
761 	if (cmd == WSKBDIO_SETMODE) {
762 		sc->sc_rawkbd = *(int *)data;
763 		DPRINTF(("wsmux_displayioctl: rawkbd = %d\n", sc->sc_rawkbd));
764 	}
765 #endif
766 
767 	/*
768 	 * Return 0 if any of the ioctl() succeeds, otherwise the last error.
769 	 * Return EPASSTHROUGH if no mux component accepts the ioctl.
770 	 */
771 	error = EPASSTHROUGH;
772 	ok = 0;
773 	CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
774 		DPRINTF(("wsmux_displayioctl: me=%p\n", me));
775 #ifdef DIAGNOSTIC
776 		if (me->me_parent != sc) {
777 			printf("wsmux_displayioctl: bad child %p\n", me);
778 			continue;
779 		}
780 #endif
781 		if (me->me_ops->ddispioctl != NULL) {
782 			error = wsevsrc_display_ioctl(me, cmd, data, flag, p);
783 			DPRINTF(("wsmux_displayioctl: me=%p dev=%s ==> %d\n",
784 				 me, me->me_dv.dv_xname, error));
785 			if (!error)
786 				ok = 1;
787 		}
788 	}
789 	if (ok)
790 		error = 0;
791 
792 	return (error);
793 }
794 
795 #if NWSDISPLAY > 0
796 /*
797  * Set display of a mux via the parent mux.
798  */
799 int
800 wsmux_evsrc_set_display(struct device *dv, struct wsevsrc *ame)
801 {
802 	struct wsmux_softc *muxsc = (struct wsmux_softc *)ame;
803 	struct wsmux_softc *sc = (struct wsmux_softc *)dv;
804 	struct device *displaydv = muxsc ? muxsc->sc_base.me_dispdv : NULL;
805 
806 	DPRINTF(("wsmux_set_display: %s: displaydv=%p\n",
807 		 sc->sc_base.me_dv.dv_xname, displaydv));
808 
809 	if (displaydv != NULL) {
810 		if (sc->sc_base.me_dispdv != NULL)
811 			return (EBUSY);
812 	} else {
813 		if (sc->sc_base.me_dispdv == NULL)
814 			return (ENXIO);
815 	}
816 
817 	return wsmux_set_display(sc, displaydv);
818 }
819 
820 int
821 wsmux_set_display(struct wsmux_softc *sc, struct device *displaydv)
822 {
823 	struct device *odisplaydv;
824 	struct wsevsrc *me;
825 	struct wsmux_softc *nsc = displaydv ? sc : NULL;
826 	int error, ok;
827 
828 	odisplaydv = sc->sc_base.me_dispdv;
829 	sc->sc_base.me_dispdv = displaydv;
830 
831 	if (displaydv)
832 		printf("%s: connecting to %s\n",
833 		       sc->sc_base.me_dv.dv_xname, displaydv->dv_xname);
834 	ok = 0;
835 	error = 0;
836 	CIRCLEQ_FOREACH(me, &sc->sc_cld,me_next) {
837 #ifdef DIAGNOSTIC
838 		if (me->me_parent != sc) {
839 			printf("wsmux_set_display: bad child parent %p\n", me);
840 			continue;
841 		}
842 #endif
843 		if (me->me_ops->dsetdisplay != NULL) {
844 			error = wsevsrc_set_display(me, &nsc->sc_base);
845 			DPRINTF(("wsmux_set_display: m=%p dev=%s error=%d\n",
846 				 me, me->me_dv.dv_xname, error));
847 			if (!error) {
848 				ok = 1;
849 #ifdef WSDISPLAY_COMPAT_RAWKBD
850 				DPRINTF(("wsmux_set_display: %s set rawkbd=%d\n",
851 					 me->me_dv.dv_xname, sc->sc_rawkbd));
852 				(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
853 						    &sc->sc_rawkbd, 0, 0);
854 #endif
855 			}
856 		}
857 	}
858 	if (ok)
859 		error = 0;
860 
861 	if (displaydv == NULL)
862 		printf("%s: disconnecting from %s\n",
863 		       sc->sc_base.me_dv.dv_xname, odisplaydv->dv_xname);
864 
865 	return (error);
866 }
867 #endif /* NWSDISPLAY > 0 */
868