#include "wsmux.h"
#include "wsdisplay.h"
#include "wskbd.h"
#include "wsmouse.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/ioctl.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 <dev/wscons/wsconsio.h>
#include <dev/wscons/wsksymdef.h>
#include <dev/wscons/wseventvar.h>
#include <dev/wscons/wsmuxvar.h>
#define WSMUX_MAXDEPTH 8
#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
int wsmux_mux_open(struct wsevsrc *, struct wseventvar *);
int wsmux_mux_close(struct wsevsrc *);
int wsmux_do_open(struct wsmux_softc *, struct wseventvar *);
void wsmux_do_close(struct wsmux_softc *);
#if NWSDISPLAY > 0
int wsmux_evsrc_set_display(struct device *, struct device *);
#else
#define wsmux_evsrc_set_display NULL
#endif
int wsmux_do_displayioctl(struct device *dev, u_long cmd, caddr_t data,
int flag, struct proc *p);
int wsmux_do_ioctl(struct device *, u_long, caddr_t,int,struct proc *);
int wsmux_add_mux(int, struct wsmux_softc *);
int wsmux_depth(struct wsmux_softc *);
void wsmuxattach(int);
void wsmux_detach_sc_locked(struct wsmux_softc *, struct wsevsrc *);
struct wssrcops wsmux_srcops = {
.type = WSMUX_MUX,
.dopen = wsmux_mux_open,
.dclose = wsmux_mux_close,
.dioctl = wsmux_do_ioctl,
.ddispioctl = wsmux_do_displayioctl,
.dsetdisplay = wsmux_evsrc_set_display,
};
struct rwlock wsmux_tree_lock = RWLOCK_INITIALIZER("wsmuxtreelk");
void
wsmuxattach(int n)
{
}
int nwsmux = 0;
struct wsmux_softc **wsmuxdevs = NULL;
struct wsmux_softc *
wsmux_getmux(int n)
{
struct wsmux_softc *sc;
struct wsmux_softc **new, **old;
int i;
if (n < 0 || n >= WSMUX_MAXDEV)
return (NULL);
if (n >= nwsmux) {
old = wsmuxdevs;
new = mallocarray(n + 1, sizeof (*wsmuxdevs),
M_DEVBUF, M_NOWAIT);
if (new == NULL) {
printf("wsmux_getmux: no memory for mux %d\n", n);
return (NULL);
}
if (old != NULL)
bcopy(old, new, nwsmux * sizeof(*wsmuxdevs));
for (i = nwsmux; i < (n + 1); i++)
new[i] = NULL;
if (old != NULL)
free(old, M_DEVBUF, nwsmux * sizeof(*wsmuxdevs));
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);
}
int
wsmuxopen(dev_t dev, int flags, int mode, struct proc *p)
{
struct wsmux_softc *sc;
struct wseventvar *evar;
int error, unit;
unit = minor(dev);
sc = wsmux_getmux(unit);
if (sc == NULL)
return (ENXIO);
DPRINTF(("%s: %s: sc=%p\n", __func__, sc->sc_base.me_dv.dv_xname, sc));
if ((flags & (FREAD | FWRITE)) == FWRITE) {
return (0);
}
if (sc->sc_base.me_parent != NULL) {
DPRINTF(("%s: detach\n", __func__));
wsmux_detach_sc(&sc->sc_base);
}
if (sc->sc_base.me_evp != NULL)
return (EBUSY);
evar = &sc->sc_base.me_evar;
if (wsevent_init(evar))
return (EBUSY);
#ifdef WSDISPLAY_COMPAT_RAWKBD
sc->sc_rawkbd = 0;
#endif
error = wsmux_do_open(sc, evar);
if (error)
wsevent_fini(evar);
return (error);
}
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_parent == NULL) {
printf("wsmux_mux_open: no parent\n");
return (EINVAL);
}
#endif
return (wsmux_do_open(sc, evar));
}
int
wsmux_do_open(struct wsmux_softc *sc, struct wseventvar *evar)
{
struct wsevsrc *me;
#ifdef DIAGNOSTIC
int error;
#endif
if (sc->sc_base.me_evp != NULL)
return (EBUSY);
sc->sc_base.me_evp = evar;
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("%s: %s: m=%p dev=%s\n", __func__,
sc->sc_base.me_dv.dv_xname, me,
me->me_dv.dv_xname));
#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;
}
error = wsevsrc_open(me, evar);
if (error) {
DPRINTF(("%s: open failed %d\n", __func__, error));
}
#else
(void)wsevsrc_open(me, evar);
#endif
}
rw_exit_read(&sc->sc_lock);
return (0);
}
int
wsmuxclose(dev_t dev, int flags, int mode, struct proc *p)
{
struct wsmux_softc *sc =
(struct wsmux_softc *)wsmuxdevs[minor(dev)];
struct wseventvar *evar = sc->sc_base.me_evp;
if ((flags & (FREAD | FWRITE)) == FWRITE)
return (0);
wsmux_do_close(sc);
sc->sc_base.me_evp = NULL;
wsevent_fini(evar);
return (0);
}
int
wsmux_mux_close(struct wsevsrc *me)
{
struct wsmux_softc *sc = (struct wsmux_softc *)me;
wsmux_do_close(sc);
sc->sc_base.me_evp = NULL;
return (0);
}
void
wsmux_do_close(struct wsmux_softc *sc)
{
struct wsevsrc *me;
DPRINTF(("%s: %s: sc=%p\n", __func__, sc->sc_base.me_dv.dv_xname, sc));
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("%s %s: m=%p dev=%s\n", __func__,
sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname));
#ifdef DIAGNOSTIC
if (me->me_parent != sc) {
printf("wsmuxclose: bad child=%p\n", me);
continue;
}
#endif
(void)wsevsrc_close(me);
}
rw_exit_read(&sc->sc_lock);
}
int
wsmuxread(dev_t dev, struct uio *uio, int flags)
{
struct wsmux_softc *sc = wsmuxdevs[minor(dev)];
struct wseventvar *evar;
int error;
evar = sc->sc_base.me_evp;
if (evar == NULL) {
#ifdef DIAGNOSTIC
printf("wsmuxread: not open\n");
#endif
return (EINVAL);
}
DPRINTFN(5, ("%s: %s event read evar=%p\n", __func__,
sc->sc_base.me_dv.dv_xname, evar));
error = wsevent_read(evar, uio, flags);
DPRINTFN(5, ("%s: %s event read ==> error=%d\n", __func__,
sc->sc_base.me_dv.dv_xname, error));
return (error);
}
int
wsmuxioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
return wsmux_do_ioctl(&wsmuxdevs[minor(dev)]->sc_base.me_dv, cmd, data, flag, p);
}
int
wsmux_do_ioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
struct proc *p)
{
struct wsmux_softc *sc = (struct wsmux_softc *)dv;
struct wsevsrc *me;
int error, ok;
int put, get, n;
struct wseventvar *evar;
struct wscons_event *ev;
struct wsmux_device_list *l;
DPRINTF(("%s: %s: enter sc=%p, cmd=%08lx\n", __func__,
sc->sc_base.me_dv.dv_xname, sc, cmd));
switch (cmd) {
case WSMUXIO_INJECTEVENT:
case WSMUXIO_ADD_DEVICE:
case WSMUXIO_REMOVE_DEVICE:
#ifdef WSDISPLAY_COMPAT_RAWKBD
case WSKBDIO_SETMODE:
#endif
if ((flag & FWRITE) == 0)
return (EACCES);
}
switch (cmd) {
case WSMUXIO_INJECTEVENT:
DPRINTF(("%s: inject\n", sc->sc_base.me_dv.dv_xname));
evar = sc->sc_base.me_evp;
if (evar == NULL) {
DPRINTF(("%s: event ignored\n", __func__));
return (0);
}
mtx_enter(&evar->ws_mtx);
get = evar->ws_get;
put = evar->ws_put;
ev = &evar->ws_q[put];
if (++put % WSEVENT_QSIZE == get) {
mtx_leave(&evar->ws_mtx);
return (ENOSPC);
}
if (put >= WSEVENT_QSIZE)
put = 0;
*ev = *(struct wscons_event *)data;
nanotime(&ev->time);
evar->ws_put = put;
mtx_leave(&evar->ws_mtx);
wsevent_wakeup(evar);
return (0);
case WSMUXIO_ADD_DEVICE:
#define d ((struct wsmux_device *)data)
DPRINTF(("%s: add type=%d, no=%d\n", sc->sc_base.me_dv.dv_xname,
d->type, d->idx));
if (d->idx < 0)
return (ENXIO);
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", sc->sc_base.me_dv.dv_xname,
d->type, d->idx));
rw_enter_write(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
if (me->me_ops->type == d->type &&
me->me_dv.dv_unit == d->idx) {
DPRINTF(("%s: detach\n", __func__));
wsmux_detach_sc_locked(sc, me);
rw_exit_write(&sc->sc_lock);
return (0);
}
}
rw_exit_write(&sc->sc_lock);
return (EINVAL);
#undef d
case WSMUXIO_LIST_DEVICES:
DPRINTF(("%s: list\n", sc->sc_base.me_dv.dv_xname));
l = (struct wsmux_device_list *)data;
n = 0;
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
if (n >= WSMUX_MAXDEV)
break;
l->devices[n].type = me->me_ops->type;
l->devices[n].idx = me->me_dv.dv_unit;
n++;
}
rw_exit_read(&sc->sc_lock);
l->ndevices = n;
return (0);
#ifdef WSDISPLAY_COMPAT_RAWKBD
case WSKBDIO_SETMODE:
sc->sc_rawkbd = *(int *)data;
DPRINTF(("%s: save rawkbd = %d\n", __func__, sc->sc_rawkbd));
break;
#endif
case FIOASYNC:
DPRINTF(("%s: FIOASYNC\n", sc->sc_base.me_dv.dv_xname));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
mtx_enter(&evar->ws_mtx);
evar->ws_async = *(int *)data != 0;
mtx_leave(&evar->ws_mtx);
return (0);
case FIOGETOWN:
case TIOCGPGRP:
DPRINTF(("%s: getown (%lu)\n", sc->sc_base.me_dv.dv_xname,
cmd));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
sigio_getown(&evar->ws_sigio, cmd, data);
return (0);
case FIOSETOWN:
case TIOCSPGRP:
DPRINTF(("%s: setown (%lu)\n", sc->sc_base.me_dv.dv_xname,
cmd));
evar = sc->sc_base.me_evp;
if (evar == NULL)
return (EINVAL);
return (sigio_setown(&evar->ws_sigio, cmd, data));
default:
DPRINTF(("%s: unknown\n", sc->sc_base.me_dv.dv_xname));
break;
}
if (sc->sc_base.me_evp == NULL
#if NWSDISPLAY > 0
&& sc->sc_displaydv == NULL
#endif
)
return (EACCES);
error = ENOTTY;
ok = 0;
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
#ifdef DIAGNOSTIC
if (me->me_parent != sc) {
printf("wsmux_do_ioctl: bad child %p\n", me);
continue;
}
#endif
error = wsevsrc_ioctl(me, cmd, data, flag, p);
DPRINTF(("%s: %s: me=%p dev=%s ==> %d\n", __func__,
sc->sc_base.me_dv.dv_xname, me, me->me_dv.dv_xname,
error));
if (!error)
ok = 1;
}
rw_exit_read(&sc->sc_lock);
if (ok)
error = 0;
return (error);
}
int
wsmuxkqfilter(dev_t dev, struct knote *kn)
{
struct wsmux_softc *sc = wsmuxdevs[minor(dev)];
if (sc->sc_base.me_evp == NULL)
return (ENXIO);
return (wsevent_kqfilter(sc->sc_base.me_evp, kn));
}
int
wsmux_add_mux(int unit, struct wsmux_softc *muxsc)
{
struct wsmux_softc *sc, *m;
int error;
int depth = 0;
sc = wsmux_getmux(unit);
if (sc == NULL)
return (ENXIO);
rw_enter_write(&wsmux_tree_lock);
DPRINTF(("%s: %s(%p) to %s(%p)\n", __func__,
sc->sc_base.me_dv.dv_xname, sc,
muxsc->sc_base.me_dv.dv_xname, muxsc));
if (sc->sc_base.me_parent != NULL || sc->sc_base.me_evp != NULL) {
error = EBUSY;
goto out;
}
for (m = muxsc; m != NULL; m = m->sc_base.me_parent) {
if (m == sc) {
error = EINVAL;
goto out;
}
depth++;
}
if (depth + wsmux_depth(sc) > WSMUX_MAXDEPTH) {
error = EBUSY;
goto out;
}
error = wsmux_attach_sc(muxsc, &sc->sc_base);
out:
rw_exit_write(&wsmux_tree_lock);
return (error);
}
struct wsmux_softc *
wsmux_create(const char *name, int unit)
{
struct wsmux_softc *sc;
DPRINTF(("%s: allocating\n", __func__));
sc = malloc(sizeof *sc, M_DEVBUF, M_NOWAIT | M_ZERO);
if (sc == NULL)
return (NULL);
TAILQ_INIT(&sc->sc_cld);
rw_init_flags(&sc->sc_lock, "wsmuxlk", RWL_DUPOK);
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_unit = unit;
sc->sc_base.me_ops = &wsmux_srcops;
sc->sc_kbd_layout = KB_NONE;
return (sc);
}
int
wsmux_attach_sc(struct wsmux_softc *sc, struct wsevsrc *me)
{
int error;
if (sc == NULL)
return (EINVAL);
rw_enter_write(&sc->sc_lock);
DPRINTF(("%s: %s(%p): type=%d\n", __func__,
sc->sc_base.me_dv.dv_xname, sc, me->me_ops->type));
#ifdef DIAGNOSTIC
if (me->me_parent != NULL) {
rw_exit_write(&sc->sc_lock);
printf("wsmux_attach_sc: busy\n");
return (EBUSY);
}
#endif
me->me_parent = sc;
TAILQ_INSERT_TAIL(&sc->sc_cld, me, me_next);
error = 0;
#if NWSDISPLAY > 0
if (sc->sc_displaydv != NULL) {
DPRINTF(("%s: %s: set display %p\n", __func__,
sc->sc_base.me_dv.dv_xname, sc->sc_displaydv));
if (me->me_ops->dsetdisplay != NULL) {
error = wsevsrc_set_display(me, sc->sc_displaydv);
if (error == EBUSY)
error = 0;
if (!error) {
#ifdef WSDISPLAY_COMPAT_RAWKBD
DPRINTF(("%s: %s set rawkbd=%d\n", __func__,
me->me_dv.dv_xname, sc->sc_rawkbd));
(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
&sc->sc_rawkbd, FWRITE, 0);
#endif
}
}
}
#endif
if (sc->sc_base.me_evp != NULL) {
error = wsevsrc_open(me, sc->sc_base.me_evp);
} else {
if (me->me_evp != NULL)
error = EBUSY;
}
if (error) {
me->me_parent = NULL;
TAILQ_REMOVE(&sc->sc_cld, me, me_next);
}
rw_exit_write(&sc->sc_lock);
DPRINTF(("%s: %s(%p) done, error=%d\n", __func__,
sc->sc_base.me_dv.dv_xname, sc, error));
return (error);
}
void
wsmux_detach_sc(struct wsevsrc *me)
{
struct wsmux_softc *sc = me->me_parent;
if (sc == NULL) {
printf("wsmux_detach_sc: %s has no parent\n",
me->me_dv.dv_xname);
return;
}
rw_enter_write(&sc->sc_lock);
wsmux_detach_sc_locked(sc, me);
rw_exit_write(&sc->sc_lock);
}
void
wsmux_detach_sc_locked(struct wsmux_softc *sc, struct wsevsrc *me)
{
rw_assert_wrlock(&sc->sc_lock);
DPRINTF(("%s: %s(%p) parent=%p\n", __func__,
me->me_dv.dv_xname, me, sc));
if (me->me_parent != sc) {
return;
}
#if NWSDISPLAY > 0
if (sc->sc_displaydv != NULL) {
if (me->me_ops->dsetdisplay != NULL)
(void)wsevsrc_set_display(me, NULL);
} else
#endif
if (me->me_evp != NULL) {
DPRINTF(("%s: close\n", __func__));
(void)wsevsrc_close(me);
}
TAILQ_REMOVE(&sc->sc_cld, me, me_next);
me->me_parent = NULL;
DPRINTF(("%s: done sc=%p\n", __func__, sc));
}
int
wsmux_do_displayioctl(struct device *dv, u_long cmd, caddr_t data, int flag,
struct proc *p)
{
struct wsmux_softc *sc = (struct wsmux_softc *)dv;
struct wsevsrc *me;
int error, ok;
DPRINTF(("%s: %s: sc=%p, cmd=%08lx\n", __func__,
sc->sc_base.me_dv.dv_xname, sc, cmd));
#ifdef WSDISPLAY_COMPAT_RAWKBD
if (cmd == WSKBDIO_SETMODE) {
sc->sc_rawkbd = *(int *)data;
DPRINTF(("%s: rawkbd = %d\n", __func__, sc->sc_rawkbd));
}
#endif
error = -1;
ok = 0;
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
DPRINTF(("%s: me=%p\n", __func__, 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, p);
DPRINTF(("%s: me=%p dev=%s ==> %d\n", __func__,
me, me->me_dv.dv_xname, error));
if (!error)
ok = 1;
}
}
rw_exit_read(&sc->sc_lock);
if (ok)
error = 0;
return (error);
}
#if NWSDISPLAY > 0
int
wsmux_evsrc_set_display(struct device *dv, struct device *displaydv)
{
struct wsmux_softc *sc = (struct wsmux_softc *)dv;
DPRINTF(("%s: %s: displaydv=%p\n", __func__,
sc->sc_base.me_dv.dv_xname, displaydv));
if (displaydv != NULL) {
if (sc->sc_displaydv != NULL)
return (EBUSY);
} else {
if (sc->sc_displaydv == NULL)
return (ENXIO);
}
return wsmux_set_display(sc, displaydv);
}
int
wsmux_set_display(struct wsmux_softc *sc, struct device *displaydv)
{
struct device *odisplaydv;
struct wsevsrc *me;
struct wsmux_softc *nsc = displaydv ? sc : NULL;
int error, ok;
rw_enter_read(&sc->sc_lock);
odisplaydv = sc->sc_displaydv;
sc->sc_displaydv = displaydv;
if (displaydv) {
DPRINTF(("%s: connecting to %s\n",
sc->sc_base.me_dv.dv_xname, displaydv->dv_xname));
}
ok = 0;
error = 0;
TAILQ_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 ? nsc->sc_displaydv : NULL);
DPRINTF(("%s: m=%p dev=%s error=%d\n", __func__,
me, me->me_dv.dv_xname, error));
if (!error) {
ok = 1;
#ifdef WSDISPLAY_COMPAT_RAWKBD
DPRINTF(("%s: %s set rawkbd=%d\n", __func__,
me->me_dv.dv_xname, sc->sc_rawkbd));
(void)wsevsrc_ioctl(me, WSKBDIO_SETMODE,
&sc->sc_rawkbd, FWRITE, 0);
#endif
}
}
}
if (ok)
error = 0;
if (displaydv == NULL) {
DPRINTF(("%s: disconnecting from %s\n",
sc->sc_base.me_dv.dv_xname, odisplaydv->dv_xname));
}
rw_exit_read(&sc->sc_lock);
return (error);
}
#endif
uint32_t
wsmux_get_layout(struct wsmux_softc *sc)
{
return sc->sc_kbd_layout;
}
void
wsmux_set_layout(struct wsmux_softc *sc, uint32_t layout)
{
if ((layout & (KB_DEFAULT | KB_NOENCODING)) == 0)
sc->sc_kbd_layout = layout;
}
int
wsmux_depth(struct wsmux_softc *sc)
{
struct wsevsrc *me;
int depth;
int maxdepth = 0;
rw_assert_anylock(&wsmux_tree_lock);
rw_enter_read(&sc->sc_lock);
TAILQ_FOREACH(me, &sc->sc_cld, me_next) {
if (me->me_ops->type != WSMUX_MUX)
continue;
depth = wsmux_depth((struct wsmux_softc *)me);
if (depth > maxdepth)
maxdepth = depth;
}
rw_exit_read(&sc->sc_lock);
return (maxdepth + 1);
}