/*
* Copyright (c) 1996, 1997 Christopher G. Demetriou. All rights reserved.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Christopher G. Demetriou
* for the NetBSD Project.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
struct tty *scr_tty;
int scr_hold_screen; /* hold tty output */
int scr_flags;
#define SCR_OPEN 1 /* is it open? */
#define SCR_WAITACTIVE 2 /* someone waiting on activation */
#define SCR_GRAPHICS 4 /* graphics mode, no text (emulation) output */
#define SCR_DUMBFB 8 /* in use as a dumb fb (iff SCR_GRAPHICS) */
const struct wscons_syncops *scr_syncops;
void *scr_synccookie;
#ifdef WSDISPLAY_COMPAT_RAWKBD
int scr_rawkbd;
#endif
struct wsscreen *sc_scr[WSDISPLAY_MAXSCREEN];
int sc_focusidx; /* available only if sc_focus isn't null */
struct wsscreen *sc_focus;
struct wseventvar evar;
int sc_isconsole;
int sc_flags;
#define SC_SWITCHPENDING 1
#define SC_SWITCHERROR 2
#define SC_XATTACHED 4 /* X server active */
kmutex_t sc_flagsmtx; /* for flags, might also be used for focus */
kcondvar_t sc_flagscv;
int sc_screenwanted, sc_oldscreen; /* valid with SC_SWITCHPENDING */
KASSERT(scrdata->nscreens > 0);
if (name == NULL)
return scrdata->screens[0];
for (i = 0; i < scrdata->nscreens; i++) {
scr = scrdata->screens[i];
if (!strcmp(name, scr->name))
return scr;
}
return 0;
}
/*
* print info about attached screen
*/
static void
wsdisplay_addscreen_print(struct wsdisplay_softc *sc, int idx, int count)
{
aprint_verbose_dev(sc->sc_dev, "screen %d", idx);
if (count > 1)
aprint_verbose("-%d", idx + (count-1));
aprint_verbose(" added (%s", sc->sc_scr[idx]->scr_dconf->scrdata->name);
if (WSSCREEN_HAS_EMULATOR(sc->sc_scr[idx])) {
aprint_verbose(", %s emulation",
sc->sc_scr[idx]->scr_dconf->wsemul->name);
}
aprint_verbose(")\n");
}
static int
wsdisplay_addscreen(struct wsdisplay_softc *sc, int idx,
const char *screentype, const char *emul)
{
const struct wsscreen_descr *scrdesc;
struct wsscreen_descr *scrdescr2;
int error;
void *cookie;
int ccol, crow;
long defattr;
struct wsscreen *scr;
int s;
if (idx < 0 || idx >= WSDISPLAY_MAXSCREEN)
return EINVAL;
if (sc->sc_scr[idx] != NULL)
return EBUSY;
scrdesc = wsdisplay_screentype_pick(sc->sc_scrdata, screentype);
if (!scrdesc)
return ENXIO;
/*
* if this screen can resize we need to copy the descr so each screen
* gets its own
*/
if (scrdesc->capabilities & WSSCREEN_RESIZE) {
/* we want per screen wsscreen_descr */
scrdescr2 = malloc(sizeof(struct wsscreen_descr), M_DEVBUF, M_WAITOK);
memcpy(scrdescr2, scrdesc, sizeof(struct wsscreen_descr));
scrdescr2->capabilities |= WSSCREEN_FREE;
scrdesc = scrdescr2;
}
/* if no screen has focus yet, activate the first we get */
s = spltty();
if (!sc->sc_focus) {
(*sc->sc_accessops->show_screen)(sc->sc_accesscookie,
scr->scr_dconf->emulcookie,
0, 0, 0);
sc->sc_focusidx = idx;
sc->sc_focus = scr;
}
splx(s);
return 0;
}
/* locate the major number */
maj = cdevsw_lookup_major(&wsdisplay_cdevsw);
/* locate the screen index */
for (idx = 0; idx < WSDISPLAY_MAXSCREEN; idx++)
if (scr == sc->sc_scr[idx])
break;
#ifdef DIAGNOSTIC
if (idx == WSDISPLAY_MAXSCREEN)
panic("wsdisplay_forceclose: bad screen");
#endif
/*
* delete pointers, so neither device entries
* nor keyboard input can reference it anymore
*/
s = spltty();
if (sc->sc_focus == scr) {
sc->sc_focus = 0;
#ifdef WSDISPLAY_COMPAT_RAWKBD
wsdisplay_update_rawkbd(sc, 0);
#endif
}
sc->sc_scr[idx] = 0;
splx(s);
/*
* Wake up processes waiting for the screen to
* be activated. Sleepers must check whether
* the screen still exists.
*/
if (scr->scr_flags & SCR_WAITACTIVE)
wakeup(scr);
/* save a reference to the graphics screen */
cookie = scr->scr_dconf->emulcookie;
if (match->cf_loc[WSEMULDISPLAYDEVCF_CONSOLE] !=
WSEMULDISPLAYDEVCF_CONSOLE_DEFAULT) {
/*
* If console-ness of device specified, either match
* exactly (at high priority), or fail.
*/
if (match->cf_loc[WSEMULDISPLAYDEVCF_CONSOLE] != 0 &&
ap->console != 0)
return 10;
else
return 0;
}
/* If console-ness unspecified, it wins. */
return 1;
}
if (pnp)
aprint_normal("wsdisplay at %s", pnp);
#if 0 /* don't bother; it's ugly */
aprint_normal(" console %d", ap->console);
#endif
return UNCONF;
}
int
wsdisplay_emul_detach(device_t dev, int how)
{
struct wsdisplay_softc *sc = device_private(dev);
int flag, i, res;
flag = (how & DETACH_FORCE ? WSDISPLAY_DELSCR_FORCE : 0);
for (i = 0; i < WSDISPLAY_MAXSCREEN; i++)
if (sc->sc_scr[i]) {
res = wsdisplay_delscreen(sc, i, flag);
if (res)
return res;
}
sc->sc_flags |= SC_SWITCHPENDING;
sc->sc_flags &= ~SC_SWITCHERROR;
if (attach)
op = scr->scr_syncops->attach;
else
op = scr->scr_syncops->detach;
res = (*op)(scr->scr_synccookie, 1, wsdisplay_swdone_cb, sc);
if (res == EAGAIN) {
/* wait for callback */
mutex_enter(&sc->sc_flagsmtx);
while (sc->sc_flags & SC_SWITCHPENDING)
cv_wait_sig(&sc->sc_flagscv, &sc->sc_flagsmtx);
mutex_exit(&sc->sc_flagsmtx);
if (sc->sc_flags & SC_SWITCHERROR)
return EIO; /* XXX pass real error */
} else {
sc->sc_flags &= ~SC_SWITCHPENDING;
if (res)
return res;
}
if (attach)
sc->sc_flags |= SC_XATTACHED;
else
sc->sc_flags &= ~SC_XATTACHED;
return 0;
}
int
wsdisplay_handlex(int resume)
{
int i, res;
device_t dv;
for (i = 0; i < wsdisplay_cd.cd_ndevs; i++) {
dv = device_lookup(&wsdisplay_cd, i);
if (!dv)
continue;
res = wsdisplay_dosync(device_private(dv), resume);
if (res)
return res;
}
return 0;
}
if (sc->sc_flags & SC_XATTACHED) {
KASSERT(scr);
KASSERT(scr->scr_syncops);
}
#if 1
/*
* XXX X servers should have been detached earlier.
* pmf currently ignores our return value and suspends the system
* after device suspend failures. We try to avoid bigger damage
* and try to detach the X server here. This is not safe because
* other parts of the system which the X server deals with
* might already be suspended.
*/
if (sc->sc_flags & SC_XATTACHED) {
printf("%s: emergency X server detach\n", device_xname(dv));
wsdisplay_dosync(sc, 0);
}
#endif
return !(sc->sc_flags & SC_XATTACHED);
}
/*
* Set up a number of virtual screens if wanted. The
* WSDISPLAYIO_ADDSCREEN ioctl is more flexible, so this code
* is for special cases like installation kernels.
*/
for (i = start; i < wsdisplay_defaultscreens; i++) {
if (wsdisplay_addscreen(sc, i, 0, 0))
break;
}
if (i > start)
wsdisplay_addscreen_print(sc, start, i-start);
if (!pmf_device_register(sc->sc_dev, wsdisplay_suspend, NULL))
aprint_error_dev(sc->sc_dev,
"couldn't establish power handler\n");
}
void
wsdisplay_cnattach(const struct wsscreen_descr *type, void *cookie,
int ccol, int crow, long defattr)
{
const struct wsemul_ops *wsemul;
error = ((*tp->t_linesw->l_open)(dev, tp));
if (error)
return error;
if (newopen && WSSCREEN_HAS_EMULATOR(scr)) {
/* set window sizes as appropriate, and reset
the emulation */
tp->t_winsize.ws_row = scr->scr_dconf->scrdata->nrows;
tp->t_winsize.ws_col = scr->scr_dconf->scrdata->ncols;
/* wsdisplay_set_emulation() */
}
}
scr->scr_flags |= SCR_OPEN;
return 0;
}
int
wsdisplayclose(dev_t dev, int flag, int mode, struct lwp *l)
{
device_t dv;
struct wsdisplay_softc *sc;
struct tty *tp;
struct wsscreen *scr;
dv = device_lookup(&wsdisplay_cd, WSDISPLAYUNIT(dev));
sc = device_private(dv);
if (ISWSDISPLAYSTAT(dev)) {
wsevent_fini(&sc->evar);
return 0;
}
if (ISWSDISPLAYCTL(dev))
return 0;
if ((scr = sc->sc_scr[WSDISPLAYSCREEN(dev)]) == NULL)
return 0;
if (WSSCREEN_HAS_TTY(scr)) {
if (scr->scr_hold_screen) {
int s;
/* XXX RESET KEYBOARD LEDS, etc. */
s = spltty(); /* avoid conflict with keyboard */
wsdisplay_kbdholdscreen(dv, 0);
splx(s);
}
tp = scr->scr_tty;
(*tp->t_linesw->l_close)(tp, flag);
ttyclose(tp);
}
if (scr->scr_syncops)
(*scr->scr_syncops->destroy)(scr->scr_synccookie);
if (WSSCREEN_HAS_EMULATOR(scr)) {
scr->scr_flags &= ~SCR_GRAPHICS;
(*scr->scr_dconf->wsemul->reset)(scr->scr_dconf->wsemulcookie,
WSEMUL_RESET);
if (wsdisplay_clearonclose)
(*scr->scr_dconf->wsemul->reset)
(scr->scr_dconf->wsemulcookie,
WSEMUL_CLEARSCREEN);
}
#ifdef WSDISPLAY_COMPAT_RAWKBD
if (scr->scr_rawkbd) {
int kbmode = WSKBD_TRANSLATED;
(void)wsdisplay_internal_ioctl(sc, scr, WSKBDIO_SETMODE,
(void *)&kbmode, 0, l);
}
#endif
scr->scr_flags &= ~SCR_OPEN;
return 0;
}
int
wsdisplayread(dev_t dev, struct uio *uio, int flag)
{
struct wsdisplay_softc *sc;
struct tty *tp;
struct wsscreen *scr;
int error;
case WSDISPLAYIO_SMODE:
#define d (*(int *)data)
if (d != WSDISPLAYIO_MODE_EMUL &&
d != WSDISPLAYIO_MODE_MAPPED &&
d != WSDISPLAYIO_MODE_DUMBFB)
return EINVAL;
if (WSSCREEN_HAS_EMULATOR(scr)) {
scr->scr_flags &= ~SCR_GRAPHICS;
if (d == WSDISPLAYIO_MODE_MAPPED ||
d == WSDISPLAYIO_MODE_DUMBFB)
scr->scr_flags |= SCR_GRAPHICS |
((d == WSDISPLAYIO_MODE_DUMBFB) ? SCR_DUMBFB : 0);
} else if (d == WSDISPLAYIO_MODE_EMUL)
return EINVAL;
case WSDISPLAYIO_DGSCROLL:
usdp = (struct wsdisplay_scroll_data *)data;
ksdp = &sc->sc_scroll_values;
SETSCROLLLINES(usdp, ksdp, ksdp);
return 0;
#else
case WSDISPLAYIO_DSSCROLL:
case WSDISPLAYIO_DGSCROLL:
return ENODEV;
#endif
case WSDISPLAYIO_SFONT:
#define d ((struct wsdisplay_usefontdata *)data)
if (!sc->sc_accessops->load_font)
return EINVAL;
if (d->name) {
error = copyinstr(d->name, namebuf, sizeof(namebuf), 0);
if (error)
return error;
fd.name = namebuf;
} else
fd.name = 0;
fd.data = 0;
error = (*sc->sc_accessops->load_font)(sc->sc_accesscookie,
scr->scr_dconf->emulcookie, &fd);
if (!error && WSSCREEN_HAS_EMULATOR(scr)) {
(*scr->scr_dconf->wsemul->reset)
(scr->scr_dconf->wsemulcookie, WSEMUL_SYNCFONT);
if (scr->scr_dconf->wsemul->resize) {
(*scr->scr_dconf->wsemul->resize)
(scr->scr_dconf->wsemulcookie,
scr->scr_dconf->scrdata);
/* update the tty's size */
scr->scr_tty->t_winsize.ws_row =
scr->scr_dconf->scrdata->nrows;
scr->scr_tty->t_winsize.ws_col =
scr->scr_dconf->scrdata->ncols;
/* send SIGWINCH to the process group on our tty */
kpreempt_disable();
ttysig(scr->scr_tty, TTYSIG_PG1, SIGWINCH);
kpreempt_enable();
}
}
return error;
#undef d
#ifdef WSDISPLAY_CUSTOM_OUTPUT
case WSDISPLAYIO_GMSGATTRS:
#define d ((struct wsdisplay_msgattrs *)data)
(*scr->scr_dconf->wsemul->getmsgattrs)
(scr->scr_dconf->wsemulcookie, d);
return 0;
#undef d
case WSDISPLAYIO_SMSGATTRS: {
#define d ((struct wsdisplay_msgattrs *)data)
int i;
for (i = 0; i < WSDISPLAY_MAXSCREEN; i++)
if (sc->sc_scr[i] != NULL)
(*sc->sc_scr[i]->scr_dconf->wsemul->setmsgattrs)
(sc->sc_scr[i]->scr_dconf->wsemulcookie,
sc->sc_scr[i]->scr_dconf->scrdata,
d);
}
return 0;
#undef d
#else
case WSDISPLAYIO_GMSGATTRS:
case WSDISPLAYIO_SMSGATTRS:
return ENODEV;
#endif
case WSDISPLAYIO_SETVERSION:
return wsevent_setversion(&sc->evar, *(int *)data);
}
/*
* Drain output from ring buffer.
* The output will normally be in one contiguous chunk, but when the
* ring wraps, it will be in two pieces.. one at the end of the ring,
* the other at the start. For performance, rather than loop here,
* we output one chunk, see if there's another one, and if so, output
* it too.
*/
n = ndqb(&tp->t_outq, 0);
tbuf = tp->t_outq.c_cf;
if (!(scr->scr_flags & SCR_GRAPHICS)) {
KASSERT(WSSCREEN_HAS_EMULATOR(scr));
(*scr->scr_dconf->wsemul->output)(scr->scr_dconf->wsemulcookie,
tbuf, n, 0);
#ifdef WSDISPLAY_MULTICONS
if (wsdisplay_multicons_enable &&
scr->scr_dconf == &wsdisplay_console_conf &&
wsdisplay_ocn && wsdisplay_ocn->cn_putc) {
for (int i = 0; i < n; i++)
wsdisplay_ocn->cn_putc(
wsdisplay_ocn->cn_dev, tbuf[i]);
}
#endif
}
ndflush(&tp->t_outq, n);
s = spltty();
tp->t_state &= ~TS_BUSY;
/* Come back if there's more to do */
if (ttypull(tp)) {
tp->t_state |= TS_TIMEOUT;
callout_schedule(&tp->t_rstrt_ch, (hz > 128) ? (hz / 128) : 1);
}
splx(s);
}
void
wsdisplaystop(struct tty *tp, int flag)
{
int s;
s = spltty();
if (ISSET(tp->t_state, TS_BUSY))
if (!ISSET(tp->t_state, TS_TTSTOP))
SET(tp->t_state, TS_FLUSH);
splx(s);
}
/* Set line parameters. */
int
wsdisplayparam(struct tty *tp, struct termios *t)
{
if (v == NULL) /* console, before real attach */
return;
if (scr->scr_flags & SCR_GRAPHICS) /* XXX can't happen */
return;
if (!WSSCREEN_HAS_TTY(scr))
return;
tp = scr->scr_tty;
/*
* XXX bad hack to work around locking problems in tty.c:
* ttyinput() will try to lock again, causing deadlock.
* We assume that wsdisplay_emulinput() can only be called
* from within wsdisplaystart(), and thus the tty lock
* is already held. Use an entry point which doesn't lock.
*/
KASSERT(scr->scr_in_ttyoutput);
ifcn = tp->t_linesw->l_rint;
if (ifcn == ttyinput)
ifcn = ttyinput_wlock;
while (count-- > 0)
(*ifcn)(*data++, tp);
}
/*
* Calls from the keyboard interface.
*/
void
wsdisplay_kbdinput(device_t dv, keysym_t ks)
{
struct wsdisplay_softc *sc = device_private(dv);
struct wsscreen *scr;
const char *dp;
int count;
struct tty *tp;
KASSERT(sc != NULL);
scr = sc->sc_focus;
if (!scr || !WSSCREEN_HAS_TTY(scr))
return;
tp = scr->scr_tty;
if (KS_GROUP(ks) == KS_GROUP_Plain && KS_VALUE(ks) <= 0x7f)
(*tp->t_linesw->l_rint)(KS_VALUE(ks), tp);
else if (WSSCREEN_HAS_EMULATOR(scr)) {
count = (*scr->scr_dconf->wsemul->translate)
(scr->scr_dconf->wsemulcookie, ks, &dp);
while (count-- > 0)
(*tp->t_linesw->l_rint)((unsigned char)(*dp++), tp);
}
}
if (scr->scr_syncops) {
if (!(sc->sc_flags & SC_XATTACHED) ||
(sc->sc_isconsole && wsdisplay_cons_pollmode)) {
/* nothing to do here */
return wsdisplay_switch1(dv, 0, waitok);
}
res = (*scr->scr_syncops->detach)(scr->scr_synccookie, waitok,
wsdisplay_switch1_cb, dv);
if (res == EAGAIN) {
/* switch will be done asynchronously */
return 0;
}
} else if (scr->scr_flags & SCR_GRAPHICS) {
/* no way to save state */
res = EBUSY;
}