diff options
author | Owain Ainsworth <oga@cvs.openbsd.org> | 2010-03-30 17:40:56 +0000 |
---|---|---|
committer | Owain Ainsworth <oga@cvs.openbsd.org> | 2010-03-30 17:40:56 +0000 |
commit | 17f79ce6ab989e13c40b1b05780f81a5d5f2fe05 (patch) | |
tree | dd13f9c70165296d96946a2c3d66f8b4709ae64c /sys/dev/wscons | |
parent | f5972d809904e97326ca314c087612469e208db2 (diff) |
Prevent the apmd/x races for good.
When we hit suspend time, go through all wsdisplays on the system. if
they are in mode MAPPED, but not MODE_DUMBFB then if possible do a full
vt switch to a !mapped vt, and prevent switching back until resume time.
This has to be called from MD code because this involves userland
running so that X can run the vt switch signal handler. This way, any
case where we are using the "poke registers from userland" model, we
will not be on the hardware when we go down, so the kernel can actually
handle thing properly.
Tested on several acpi laptops (by kettenis@ and ian@), x40 (me and
beck@ at LEAST) and zaurus (me). Maybe others, but if so I forgot who at
this time..
Idea from deraadt somewhere over the Faroe Islands (I thought of a
similar thing myself a while ago). Much prompting from him. Ok and
comments miod@
Diffstat (limited to 'sys/dev/wscons')
-rw-r--r-- | sys/dev/wscons/wsdisplay.c | 112 | ||||
-rw-r--r-- | sys/dev/wscons/wsdisplayvar.h | 4 |
2 files changed, 114 insertions, 2 deletions
diff --git a/sys/dev/wscons/wsdisplay.c b/sys/dev/wscons/wsdisplay.c index ae36fbc7258..70153570124 100644 --- a/sys/dev/wscons/wsdisplay.c +++ b/sys/dev/wscons/wsdisplay.c @@ -1,4 +1,4 @@ -/* $OpenBSD: wsdisplay.c,v 1.97 2009/11/09 17:53:39 nicm Exp $ */ +/* $OpenBSD: wsdisplay.c,v 1.98 2010/03/30 17:40:55 oga Exp $ */ /* $NetBSD: wsdisplay.c,v 1.82 2005/02/27 00:27:52 perry Exp $ */ /* @@ -154,6 +154,8 @@ int wsdisplay_addscreen(struct wsdisplay_softc *, int, const char *, const char *); int wsdisplay_getscreen(struct wsdisplay_softc *, struct wsdisplay_addscreendata *); +void wsdisplay_resume_device(struct device *); +void wsdisplay_suspend_device(struct device *); void wsdisplay_shutdownhook(void *); void wsdisplay_addscreen_print(struct wsdisplay_softc *, int, int); void wsdisplay_closescreen(struct wsdisplay_softc *, struct wsscreen *); @@ -189,6 +191,7 @@ struct wsdisplay_softc { #define SC_SWITCHPENDING 0x01 #define SC_PASTE_AVAIL 0x02 int sc_screenwanted, sc_oldscreen; /* valid with SC_SWITCHPENDING */ + int sc_resumescreen; /* if set, can't switch until resume. */ #if NWSKBD > 0 struct wsevsrc *sc_input; @@ -683,6 +686,7 @@ wsdisplay_common_attach(struct wsdisplay_softc *sc, int console, int kbdmux, #endif /* NWSKBD > 0 */ sc->sc_isconsole = console; + sc->sc_resumescreen = WSDISPLAY_NULLSCREEN; if (console) { KASSERT(wsdisplay_console_initted); @@ -1798,6 +1802,13 @@ wsdisplay_switch(struct device *dev, int no, int waitok) s = spltty(); + while (sc->sc_resumescreen != WSDISPLAY_NULLSCREEN && res == 0) + res = tsleep(&sc->sc_resumescreen, PCATCH, "wsrestore", 0); + if (res) { + splx(s); + return (res); + } + if ((sc->sc_focus && no == sc->sc_focusidx) || (sc->sc_focus == NULL && no == WSDISPLAY_NULLSCREEN)) { splx(s); @@ -2152,6 +2163,105 @@ wsdisplay_switchtoconsole() } } +/* + * Deal with the xserver doing driver in userland and thus screwing up suspend + * and resume by switching away from it at suspend/resume time. + * + * these functions must be called from the MD suspend callback, since we may + * need to sleep if we have a user (probably an X server) on a vt. therefore + * this can't be a config_suspend() hook. + */ +void +wsdisplay_suspend(void) +{ + int i; + + for (i = 0; i < wsdisplay_cd.cd_ndevs; i++) + if (wsdisplay_cd.cd_devs[i] != NULL) + wsdisplay_suspend_device(wsdisplay_cd.cd_devs[i]); +} + +void +wsdisplay_suspend_device(struct device *dev) +{ + struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev; + struct wsscreen *scr; + int active, idx, ret = 0, s; + + if ((active = wsdisplay_getactivescreen(sc)) == WSDISPLAY_NULLSCREEN) + return; + + scr = sc->sc_scr[active]; + /* + * We want to switch out of graphics mode for the suspend, but + * only if we're in WSDISPLAY_MODE_MAPPED. + */ +retry: + idx = WSDISPLAY_MAXSCREEN; + if (scr->scr_flags & SCR_GRAPHICS && + (scr->scr_flags & SCR_DUMBFB) == 0) { + for (idx = 0; idx < WSDISPLAY_MAXSCREEN; idx++) { + if (sc->sc_scr[idx] == NULL || sc->sc_scr[idx] == scr) + continue; + + if ((sc->sc_scr[idx]->scr_flags & SCR_GRAPHICS) == 0) + break; + } + } + + /* if we don't have anything to switch to, we can't do anything */ + if (idx == WSDISPLAY_MAXSCREEN) + return; + + /* + * we do a lot of magic here because we need to know that the + * switch has completed before we return + */ + ret = wsdisplay_switch((struct device *)sc, idx, 1); + if (ret == EBUSY) { + /* XXX sleep on what's going on */ + goto retry; + } else if (ret) + return; + + s = spltty(); + sc->sc_resumescreen = active; /* block other vt switches until resume */ + splx(s); + /* + * This will either return ENXIO (invalid (shouldn't happen) or + * wsdisplay disappeared (problem solved)), or EINTR/ERESTART. + * Not much we can do about the latter since we can't return to + * userland. + */ + (void)wsscreen_switchwait(sc, idx); +} + +void +wsdisplay_resume(void) +{ + int i; + + for (i = 0; i < wsdisplay_cd.cd_ndevs; i++) + if (wsdisplay_cd.cd_devs[i] != NULL) + wsdisplay_resume_device(wsdisplay_cd.cd_devs[i]); +} + +void +wsdisplay_resume_device(struct device *dev) +{ + struct wsdisplay_softc *sc = (struct wsdisplay_softc *)dev; + int idx, s; + + if (sc->sc_resumescreen != WSDISPLAY_NULLSCREEN) { + s = spltty(); + idx = sc->sc_resumescreen; + sc->sc_resumescreen = WSDISPLAY_NULLSCREEN; + wakeup(&sc->sc_resumescreen); + splx(s); + (void)wsdisplay_switch((struct device *)sc, idx, 1); + } +} + #ifdef SCROLLBACK_SUPPORT void wsscrollback(void *arg, int op) diff --git a/sys/dev/wscons/wsdisplayvar.h b/sys/dev/wscons/wsdisplayvar.h index 9f9a09ebe89..d7038a12b40 100644 --- a/sys/dev/wscons/wsdisplayvar.h +++ b/sys/dev/wscons/wsdisplayvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: wsdisplayvar.h,v 1.23 2009/09/05 14:09:35 miod Exp $ */ +/* $OpenBSD: wsdisplayvar.h,v 1.24 2010/03/30 17:40:55 oga Exp $ */ /* $NetBSD: wsdisplayvar.h,v 1.30 2005/02/04 02:10:49 perry Exp $ */ /* @@ -209,6 +209,8 @@ int wsdisplay_cfg_ioctl(struct wsdisplay_softc *sc, */ #define WSDISPLAY_NULLSCREEN -1 void wsdisplay_switchtoconsole(void); +void wsdisplay_suspend(void); +void wsdisplay_resume(void); const struct wsscreen_descr * wsdisplay_screentype_pick(const struct wsscreen_list *, const char *); |