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