NetBSD/sys/dev/wscons/wsmux.c
jmcneill d35e9908e8 If there are no devices attached to the mux and software does
WSxxxIO_SETVERSION, the change doesn't actually get applied to the event
source as wsmux hands off processing of these ioctls to attached devices.
Handle these ioctls directly from the mux device instead of passing them
through, to correct an issue that prevented keyboards and mice from working
in X if there were zero devices attached when the X server started.
2013-03-18 11:40:39 +00:00

894 lines
22 KiB
C

/* $NetBSD: wsmux.c,v 1.55 2013/03/18 11:40:39 jmcneill Exp $ */
/*
* Copyright (c) 1998, 2005 The NetBSD Foundation, Inc.
* All rights reserved.
*
* Author: Lennart Augustsson <lennart@augustsson.net>
* Carlstedt Research & Technology
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* wscons mux device.
*
* The mux device is a collection of real mice and keyboards and acts as
* a merge point for all the events from the different real devices.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: wsmux.c,v 1.55 2013/03/18 11:40:39 jmcneill Exp $");
#include "opt_compat_netbsd.h"
#include "opt_modular.h"
#include "wsdisplay.h"
#include "wsmux.h"
#include "wskbd.h"
#include "wsmouse.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/tty.h>
#include <sys/signalvar.h>
#include <sys/device.h>
#include "opt_wsdisplay_compat.h"
#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wseventvar.h>
#include <dev/wscons/wscons_callbacks.h>
#include <dev/wscons/wsmuxvar.h>
#ifdef WSMUX_DEBUG
#define DPRINTF(x) if (wsmuxdebug) printf x
#define DPRINTFN(n,x) if (wsmuxdebug > (n)) printf x
int wsmuxdebug = 0;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif
/*
* The wsmux pseudo device is used to multiplex events from several wsmouse,
* wskbd, and/or wsmux devices together.
* The devices connected together form a tree with muxes in the interior
* and real devices (mouse and kbd) at the leaves. The special case of
* a tree with one node (mux or other) is supported as well.
* Only the device at the root of the tree can be opened (if a non-root
* device is opened the subtree rooted at that point is severed from the
* containing tree). When the root is opened it allocates a wseventvar
* struct which all the nodes in the tree will send their events too.
* An ioctl() performed on the root is propagated to all the nodes.
* There are also ioctl() operations to add and remove nodes from a tree.
*/
static int wsmux_mux_open(struct wsevsrc *, struct wseventvar *);
static int wsmux_mux_close(struct wsevsrc *);
static void wsmux_do_open(struct wsmux_softc *, struct wseventvar *);
static void wsmux_do_close(struct wsmux_softc *);
#if NWSDISPLAY > 0
static int wsmux_evsrc_set_display(device_t, struct wsevsrc *);
#else
#define wsmux_evsrc_set_display NULL
#endif
static int wsmux_do_displayioctl(device_t dev, u_long cmd,
void *data, int flag, struct lwp *l);
static int wsmux_do_ioctl(device_t, u_long, void *,int,struct lwp *);
static int wsmux_add_mux(int, struct wsmux_softc *);
void wsmuxattach(int);
#define WSMUXDEV(n) ((n) & 0x7f)
#define WSMUXCTL(n) ((n) & 0x80)
dev_type_open(wsmuxopen);
dev_type_close(wsmuxclose);
dev_type_read(wsmuxread);
dev_type_ioctl(wsmuxioctl);
dev_type_poll(wsmuxpoll);
dev_type_kqfilter(wsmuxkqfilter);
const struct cdevsw wsmux_cdevsw = {
wsmuxopen, wsmuxclose, wsmuxread, nowrite, wsmuxioctl,
nostop, notty, wsmuxpoll, nommap, wsmuxkqfilter, D_OTHER
};
struct wssrcops wsmux_srcops = {
WSMUX_MUX,
wsmux_mux_open, wsmux_mux_close, wsmux_do_ioctl, wsmux_do_displayioctl,
wsmux_evsrc_set_display
};
/* From upper level */
void
wsmuxattach(int n)
{
}
/* Keep track of all muxes that have been allocated */
static struct wsmux_softc **wsmuxdevs = NULL;
static int nwsmux = 0;
/* Return mux n, create if necessary */
struct wsmux_softc *
wsmux_getmux(int n)
{
struct wsmux_softc *sc;
n = WSMUXDEV(n); /* limit range */
/* Make sure there is room for mux n in the table */
if (n >= nwsmux) {
void *new;
new = realloc(wsmuxdevs, (n + 1) * sizeof(*wsmuxdevs),
M_DEVBUF, M_ZERO | M_NOWAIT);
if (new == NULL) {
printf("wsmux_getmux: no memory for mux %d\n", n);
return NULL;
}
wsmuxdevs = new;
nwsmux = n + 1;
}
sc = wsmuxdevs[n];
if (sc == NULL) {
sc = wsmux_create("wsmux", n);
if (sc == NULL)
printf("wsmux: attach out of memory\n");
wsmuxdevs[n] = sc;
}
return (sc);
}
/*
* open() of the pseudo device from device table.
*/
int
wsmuxopen(dev_t dev, int flags, int mode, struct lwp *l)
{
struct wsmux_softc *sc;
struct wseventvar *evar;
int minr, unit;
minr = minor(dev);
unit = WSMUXDEV(minr);
sc = wsmux_getmux(unit);
if (sc == NULL)
return (ENXIO);
DPRINTF(("wsmuxopen: %s: sc=%p l=%p\n",
device_xname(sc->sc_base.me_dv), sc, l));
if (WSMUXCTL(minr)) {
/* This is the control device which does not allow reads. */
if (flags & FREAD)
return (EINVAL);
return (0);
}
if ((flags & (FREAD | FWRITE)) == FWRITE)
/* Allow write only open */
return (0);
if (sc->sc_base.me_parent != NULL) {
/* Grab the mux out of the greedy hands of the parent mux. */
DPRINTF(("wsmuxopen: detach\n"));
wsmux_detach_sc(&sc->sc_base);
}
if (sc->sc_base.me_evp != NULL)
/* Already open. */
return (EBUSY);
evar = &sc->sc_base.me_evar;
wsevent_init(evar, l->l_proc);
#ifdef WSDISPLAY_COMPAT_RAWKBD
sc->sc_rawkbd = 0;
#endif
wsmux_do_open(sc, evar);
return (0);
}
/*
* Open of a mux via the parent mux.
*/
int
wsmux_mux_open(struct wsevsrc *me, struct wseventvar *evar)
{
struct wsmux_softc *sc = (struct wsmux_softc *)me;
#ifdef DIAGNOSTIC
if (sc->sc_base.me_evp != NULL) {
printf("wsmux_mux_open: busy\n");
return (EBUSY);
}
if (sc->sc_base.me_parent == NULL) {
printf("wsmux_mux_open: no parent\n");
return (EINVAL);
}
#endif
wsmux_do_open(sc, evar);
return (0);
}
/* Common part of opening a mux. */
void
wsmux_do_open(struct wsmux_softc *sc, struct wseventvar *evar)
{
struct wsevsrc *me;
sc->sc_base.me_evp = evar; /* remember event variable, mark as open */
/* Open all children. */
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("wsmuxopen: %s: m=%p dev=%s\n",
device_xname(sc->sc_base.me_dv), me,
device_xname(me->me_dv)));
#ifdef DIAGNOSTIC
if (me->me_evp != NULL) {
printf("wsmuxopen: dev already in use\n");
continue;
}
if (me->me_parent != sc) {
printf("wsmux_do_open: bad child=%p\n", me);
continue;
}
{
int error = wsevsrc_open(me, evar);
if (error) {
DPRINTF(("wsmuxopen: open failed %d\n", error));
}
}
#else
/* ignore errors, failing children will not be marked open */
(void)wsevsrc_open(me, evar);
#endif
}
}
/*
* close() of the pseudo device from device table.
*/
int
wsmuxclose(dev_t dev, int flags, int mode,
struct lwp *l)
{
int minr = minor(dev);
struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
struct wseventvar *evar = sc->sc_base.me_evp;
if (WSMUXCTL(minr))
/* control device */
return (0);
if (evar == NULL)
/* Not open for read */
return (0);
wsmux_do_close(sc);
sc->sc_base.me_evp = NULL;
wsevent_fini(evar);
return (0);
}
/*
* Close of a mux via the parent mux.
*/
int
wsmux_mux_close(struct wsevsrc *me)
{
me->me_evp = NULL;
wsmux_do_close((struct wsmux_softc *)me);
return (0);
}
/* Common part of closing a mux. */
void
wsmux_do_close(struct wsmux_softc *sc)
{
struct wsevsrc *me;
DPRINTF(("wsmuxclose: %s: sc=%p\n",
device_xname(sc->sc_base.me_dv), sc));
/* Close all the children. */
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("wsmuxclose %s: m=%p dev=%s\n",
device_xname(sc->sc_base.me_dv), me,
device_xname(me->me_dv)));
#ifdef DIAGNOSTIC
if (me->me_parent != sc) {
printf("wsmuxclose: bad child=%p\n", me);
continue;
}
#endif
(void)wsevsrc_close(me);
me->me_evp = NULL;
}
}
/*
* read() of the pseudo device from device table.
*/
int
wsmuxread(dev_t dev, struct uio *uio, int flags)
{
int minr = minor(dev);
struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
struct wseventvar *evar;
int error;
if (WSMUXCTL(minr)) {
/* control device */
return (EINVAL);
}
evar = sc->sc_base.me_evp;
if (evar == NULL) {
#ifdef DIAGNOSTIC
/* XXX can we get here? */
printf("wsmuxread: not open\n");
#endif
return (EINVAL);
}
DPRINTFN(5,("wsmuxread: %s event read evar=%p\n",
device_xname(sc->sc_base.me_dv), evar));
error = wsevent_read(evar, uio, flags);
DPRINTFN(5,("wsmuxread: %s event read ==> error=%d\n",
device_xname(sc->sc_base.me_dv), error));
return (error);
}
/*
* ioctl of the pseudo device from device table.
*/
int
wsmuxioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
int u = WSMUXDEV(minor(dev));
return wsmux_do_ioctl(wsmuxdevs[u]->sc_base.me_dv, cmd, data, flag, l);
}
/*
* ioctl of a mux via the parent mux, continuation of wsmuxioctl().
*/
int
wsmux_do_ioctl(device_t dv, u_long cmd, void *data, int flag,
struct lwp *lwp)
{
struct wsmux_softc *sc = device_private(dv);
struct wsevsrc *me;
int error, ok;
int s, n;
struct wseventvar *evar;
struct wscons_event event;
struct wsmux_device_list *l;
DPRINTF(("wsmux_do_ioctl: %s: enter sc=%p, cmd=%08lx\n",
device_xname(sc->sc_base.me_dv), sc, cmd));
switch (cmd) {
#if defined(COMPAT_50) || defined(MODULAR)
case WSMUXIO_OINJECTEVENT:
#endif /* defined(COMPAT_50) || defined(MODULAR) */
case WSMUXIO_INJECTEVENT:
/* Inject an event, e.g., from moused. */
DPRINTF(("%s: inject\n", device_xname(sc->sc_base.me_dv)));
evar = sc->sc_base.me_evp;
if (evar == NULL) {
/* No event sink, so ignore it. */
DPRINTF(("wsmux_do_ioctl: event ignored\n"));
return (0);
}
s = spltty();
event.type = ((struct wscons_event *)data)->type;
event.value = ((struct wscons_event *)data)->value;
error = wsevent_inject(evar, &event, 1);
splx(s);
return error;
case WSMUXIO_ADD_DEVICE:
#define d ((struct wsmux_device *)data)
DPRINTF(("%s: add type=%d, no=%d\n",
device_xname(sc->sc_base.me_dv), d->type, d->idx));
switch (d->type) {
#if NWSMOUSE > 0
case WSMUX_MOUSE:
return (wsmouse_add_mux(d->idx, sc));
#endif
#if NWSKBD > 0
case WSMUX_KBD:
return (wskbd_add_mux(d->idx, sc));
#endif
case WSMUX_MUX:
return (wsmux_add_mux(d->idx, sc));
default:
return (EINVAL);
}
case WSMUXIO_REMOVE_DEVICE:
DPRINTF(("%s: rem type=%d, no=%d\n",
device_xname(sc->sc_base.me_dv), d->type, d->idx));
/* Locate the device */
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
if (me->me_ops->type == d->type &&
device_unit(me->me_dv) == d->idx) {
DPRINTF(("wsmux_do_ioctl: detach\n"));
wsmux_detach_sc(me);
return (0);
}
}
return (EINVAL);
#undef d
case WSMUXIO_LIST_DEVICES:
DPRINTF(("%s: list\n", device_xname(sc->sc_base.me_dv)));
l = (struct wsmux_device_list *)data;
n = 0;
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
if (n >= WSMUX_MAXDEV)
break;
l->devices[n].type = me->me_ops->type;
l->devices[n].idx = device_unit(me->me_dv);
n++;
}
l->ndevices = n;
return (0);
#ifdef WSDISPLAY_COMPAT_RAWKBD
case WSKBDIO_SETMODE:
sc->sc_rawkbd = *(int *)data;
DPRINTF(("wsmux_do_ioctl: save rawkbd = %d\n", sc->sc_rawkbd));
break;
#endif
case WSKBDIO_SETVERSION:
case WSMOUSEIO_SETVERSION:
case WSDISPLAYIO_SETVERSION:
DPRINTF(("%s: WSxxxIO_SETVERSION\n", device_xname(sc->sc_base.me_dv)));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
return wsevent_setversion(evar, *(int *)data);
case FIONBIO:
DPRINTF(("%s: FIONBIO\n", device_xname(sc->sc_base.me_dv)));
return (0);
case FIOASYNC:
DPRINTF(("%s: FIOASYNC\n", device_xname(sc->sc_base.me_dv)));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
evar->async = *(int *)data != 0;
return (0);
case FIOSETOWN:
DPRINTF(("%s: FIOSETOWN\n", device_xname(sc->sc_base.me_dv)));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
if (-*(int *)data != evar->io->p_pgid
&& *(int *)data != evar->io->p_pid)
return (EPERM);
return (0);
case TIOCSPGRP:
DPRINTF(("%s: TIOCSPGRP\n", device_xname(sc->sc_base.me_dv)));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
if (*(int *)data != evar->io->p_pgid)
return (EPERM);
return (0);
default:
DPRINTF(("%s: unknown\n", device_xname(sc->sc_base.me_dv)));
break;
}
if (sc->sc_base.me_evp == NULL
#if NWSDISPLAY > 0
&& sc->sc_base.me_dispdv == NULL
#endif
)
return (EACCES);
/* Return 0 if any of the ioctl() succeeds, otherwise the last error */
error = 0;
ok = 0;
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
#ifdef DIAGNOSTIC
/* XXX check evp? */
if (me->me_parent != sc) {
printf("wsmux_do_ioctl: bad child %p\n", me);
continue;
}
#endif
error = wsevsrc_ioctl(me, cmd, data, flag, lwp);
DPRINTF(("wsmux_do_ioctl: %s: me=%p dev=%s ==> %d\n",
device_xname(sc->sc_base.me_dv), me,
device_xname(me->me_dv), error));
if (!error)
ok = 1;
}
if (ok) {
error = 0;
if (cmd == WSKBDIO_SETENCODING) {
sc->sc_kbd_layout = *((kbd_t *)data);
}
}
return (error);
}
/*
* poll() of the pseudo device from device table.
*/
int
wsmuxpoll(dev_t dev, int events, struct lwp *l)
{
int minr = minor(dev);
struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
if (WSMUXCTL(minr)) {
/* control device */
return (0);
}
if (sc->sc_base.me_evp == NULL) {
#ifdef DIAGNOSTIC
printf("wsmuxpoll: not open\n");
#endif
return (POLLHUP);
}
return (wsevent_poll(sc->sc_base.me_evp, events, l));
}
/*
* kqfilter() of the pseudo device from device table.
*/
int
wsmuxkqfilter(dev_t dev, struct knote *kn)
{
int minr = minor(dev);
struct wsmux_softc *sc = wsmuxdevs[WSMUXDEV(minr)];
if (WSMUXCTL(minr)) {
/* control device */
return (1);
}
if (sc->sc_base.me_evp == NULL) {
#ifdef DIAGNOSTIC
printf("wsmuxkqfilter: not open\n");
#endif
return (1);
}
return (wsevent_kqfilter(sc->sc_base.me_evp, kn));
}
/*
* Add mux unit as a child to muxsc.
*/
int
wsmux_add_mux(int unit, struct wsmux_softc *muxsc)
{
struct wsmux_softc *sc, *m;
sc = wsmux_getmux(unit);
if (sc == NULL)
return (ENXIO);
DPRINTF(("wsmux_add_mux: %s(%p) to %s(%p)\n",
device_xname(sc->sc_base.me_dv), sc,
device_xname(muxsc->sc_base.me_dv), muxsc));
if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL)
return (EBUSY);
/* The mux we are adding must not be an ancestor of itself. */
for (m = muxsc; m != NULL ; m = m->sc_base.me_parent)
if (m == sc)
return (EINVAL);
return (wsmux_attach_sc(muxsc, &sc->sc_base));
}
/* Create a new mux softc. */
struct wsmux_softc *
wsmux_create(const char *name, int unit)
{
struct wsmux_softc *sc;
/* XXX This is wrong -- should use autoconfiguraiton framework */
DPRINTF(("wsmux_create: allocating\n"));
sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT|M_ZERO);
if (sc == NULL)
return (NULL);
sc->sc_base.me_dv = malloc(sizeof(struct device), M_DEVBUF, M_NOWAIT|M_ZERO);
if (sc->sc_base.me_dv == NULL) {
free(sc, M_DEVBUF);
return NULL;
}
CIRCLEQ_INIT(&sc->sc_cld);
snprintf(sc->sc_base.me_dv->dv_xname, sizeof sc->sc_base.me_dv->dv_xname,
"%s%d", name, unit);
sc->sc_base.me_dv->dv_private = sc;
sc->sc_base.me_dv->dv_unit = unit;
sc->sc_base.me_ops = &wsmux_srcops;
sc->sc_kbd_layout = KB_NONE;
return (sc);
}
/* Attach me as a child to sc. */
int
wsmux_attach_sc(struct wsmux_softc *sc, struct wsevsrc *me)
{
int error;
if (sc == NULL)
return (EINVAL);
DPRINTF(("wsmux_attach_sc: %s(%p): type=%d\n",
device_xname(sc->sc_base.me_dv), sc, me->me_ops->type));
#ifdef DIAGNOSTIC
if (me->me_parent != NULL) {
printf("wsmux_attach_sc: busy\n");
return (EBUSY);
}
#endif
me->me_parent = sc;
CIRCLEQ_INSERT_TAIL(&sc->sc_cld, me, me_next);
error = 0;
#if NWSDISPLAY > 0
if (sc->sc_base.me_dispdv != NULL) {
/* This is a display mux, so attach the new device to it. */
DPRINTF(("wsmux_attach_sc: %s: set display %p\n",
device_xname(sc->sc_base.me_dv),
sc->sc_base.me_dispdv));
if (me->me_ops->dsetdisplay != NULL) {
error = wsevsrc_set_display(me, &sc->sc_base);
/* Ignore that the console already has a display. */
if (error == EBUSY)
error = 0;
if (!error) {
#ifdef WSDISPLAY_COMPAT_RAWKBD
DPRINTF(("wsmux_attach_sc: %s set rawkbd=%d\n",
device_xname(me->me_dv),
sc->sc_rawkbd));
(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
&sc->sc_rawkbd, 0, 0);
#endif
if (sc->sc_kbd_layout != KB_NONE)
(void)wsevsrc_ioctl(me,
WSKBDIO_SETENCODING,
&sc->sc_kbd_layout, FWRITE, 0);
}
}
}
#endif
if (sc->sc_base.me_evp != NULL) {
/* Mux is open, so open the new subdevice */
DPRINTF(("wsmux_attach_sc: %s: calling open of %s\n",
device_xname(sc->sc_base.me_dv),
device_xname(me->me_dv)));
error = wsevsrc_open(me, sc->sc_base.me_evp);
} else {
DPRINTF(("wsmux_attach_sc: %s not open\n",
device_xname(sc->sc_base.me_dv)));
}
if (error) {
me->me_parent = NULL;
CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
}
DPRINTF(("wsmux_attach_sc: %s(%p) done, error=%d\n",
device_xname(sc->sc_base.me_dv), sc, error));
return (error);
}
/* Remove me from the parent. */
void
wsmux_detach_sc(struct wsevsrc *me)
{
struct wsmux_softc *sc = me->me_parent;
DPRINTF(("wsmux_detach_sc: %s(%p) parent=%p\n",
device_xname(me->me_dv), me, sc));
#ifdef DIAGNOSTIC
if (sc == NULL) {
printf("wsmux_detach_sc: %s has no parent\n",
device_xname(me->me_dv));
return;
}
#endif
#if NWSDISPLAY > 0
if (sc->sc_base.me_dispdv != NULL) {
if (me->me_ops->dsetdisplay != NULL)
/* ignore error, there's nothing we can do */
(void)wsevsrc_set_display(me, NULL);
} else
#endif
if (me->me_evp != NULL) {
DPRINTF(("wsmux_detach_sc: close\n"));
/* mux device is open, so close multiplexee */
(void)wsevsrc_close(me);
}
CIRCLEQ_REMOVE(&sc->sc_cld, me, me_next);
me->me_parent = NULL;
DPRINTF(("wsmux_detach_sc: done sc=%p\n", sc));
}
/*
* Display ioctl() of a mux via the parent mux.
*/
int
wsmux_do_displayioctl(device_t dv, u_long cmd, void *data, int flag,
struct lwp *l)
{
struct wsmux_softc *sc = device_private(dv);
struct wsevsrc *me;
int error, ok;
DPRINTF(("wsmux_displayioctl: %s: sc=%p, cmd=%08lx\n",
device_xname(sc->sc_base.me_dv), sc, cmd));
#ifdef WSDISPLAY_COMPAT_RAWKBD
if (cmd == WSKBDIO_SETMODE) {
sc->sc_rawkbd = *(int *)data;
DPRINTF(("wsmux_displayioctl: rawkbd = %d\n", sc->sc_rawkbd));
}
#endif
/*
* Return 0 if any of the ioctl() succeeds, otherwise the last error.
* Return EPASSTHROUGH if no mux component accepts the ioctl.
*/
error = EPASSTHROUGH;
ok = 0;
CIRCLEQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("wsmux_displayioctl: me=%p\n", me));
#ifdef DIAGNOSTIC
if (me->me_parent != sc) {
printf("wsmux_displayioctl: bad child %p\n", me);
continue;
}
#endif
if (me->me_ops->ddispioctl != NULL) {
error = wsevsrc_display_ioctl(me, cmd, data, flag, l);
DPRINTF(("wsmux_displayioctl: me=%p dev=%s ==> %d\n",
me, device_xname(me->me_dv), error));
if (!error)
ok = 1;
}
}
if (ok)
error = 0;
return (error);
}
#if NWSDISPLAY > 0
/*
* Set display of a mux via the parent mux.
*/
int
wsmux_evsrc_set_display(device_t dv, struct wsevsrc *ame)
{
struct wsmux_softc *muxsc = (struct wsmux_softc *)ame;
struct wsmux_softc *sc = device_private(dv);
device_t displaydv = muxsc ? muxsc->sc_base.me_dispdv : NULL;
DPRINTF(("wsmux_set_display: %s: displaydv=%p\n",
device_xname(sc->sc_base.me_dv), displaydv));
if (displaydv != NULL) {
if (sc->sc_base.me_dispdv != NULL)
return (EBUSY);
} else {
if (sc->sc_base.me_dispdv == NULL)
return (ENXIO);
}
return wsmux_set_display(sc, displaydv);
}
int
wsmux_set_display(struct wsmux_softc *sc, device_t displaydv)
{
device_t odisplaydv;
struct wsevsrc *me;
struct wsmux_softc *nsc = displaydv ? sc : NULL;
int error, ok;
odisplaydv = sc->sc_base.me_dispdv;
sc->sc_base.me_dispdv = displaydv;
if (displaydv)
aprint_verbose_dev(sc->sc_base.me_dv, "connecting to %s\n",
device_xname(displaydv));
ok = 0;
error = 0;
CIRCLEQ_FOREACH(me, &sc->sc_cld,me_next) {
#ifdef DIAGNOSTIC
if (me->me_parent != sc) {
printf("wsmux_set_display: bad child parent %p\n", me);
continue;
}
#endif
if (me->me_ops->dsetdisplay != NULL) {
error = wsevsrc_set_display(me, &nsc->sc_base);
DPRINTF(("wsmux_set_display: m=%p dev=%s error=%d\n",
me, device_xname(me->me_dv), error));
if (!error) {
ok = 1;
#ifdef WSDISPLAY_COMPAT_RAWKBD
DPRINTF(("wsmux_set_display: %s set rawkbd=%d\n",
device_xname(me->me_dv), sc->sc_rawkbd));
(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
&sc->sc_rawkbd, 0, 0);
#endif
}
}
}
if (ok)
error = 0;
if (displaydv == NULL)
aprint_verbose("%s: disconnecting from %s\n",
device_xname(sc->sc_base.me_dv),
device_xname(odisplaydv));
return (error);
}
#endif /* NWSDISPLAY > 0 */