--- a/sys/dev/misc/syscons/syscons.c +++ b/sys/dev/misc/syscons/syscons.c @@ -240,7 +240,7 @@ * occurs we try for up to 1/2 a second and then blow the spinlock away. * The asynchronous update thread makes this happen more often. */ -static void +void syscons_lock(void) { if (panicstr || shutdown_in_progress) { @@ -266,7 +266,7 @@ return(mtx_spinlock_try(&syscons_mtx)); } -static void +void syscons_unlock(void) { mtx_spinunlock(&syscons_mtx); @@ -3563,8 +3563,23 @@ sc_vtb_t old; lwkt_gettoken(&vga_token); - old = scp->vtb; sc_vtb_init(&new, VTB_MEMORY, scp->xsize, scp->ysize, NULL, wait); + /* + * Do not replace the current buffer with an invalid one. + */ + if (!(new.vtb_flags & VTB_VALID)) { + lwkt_reltoken(&vga_token); + return; + } + /* + * Serialize the swap (and content copy) against the tty output path + * (scstart()/sc_puts()) and the renderer, which run under + * syscons_lock(); vga_token alone does not exclude them and is + * released whenever this thread blocks. Destroy the old buffer + * outside the spinlock. + */ + syscons_lock(); + old = scp->vtb; if (!discard && (old.vtb_flags & VTB_VALID)) { /* retain the current cursor position and buffer contants */ scp->cursor_oldpos = scp->cursor_pos; @@ -3574,8 +3589,10 @@ */ sc_vtb_copy(&old, 0, &new, 0, scp->xsize*scp->ysize); scp->vtb = new; + syscons_unlock(); } else { scp->vtb = new; + syscons_unlock(); sc_vtb_destroy(&old); } --- a/sys/dev/misc/syscons/syscons.h +++ b/sys/dev/misc/syscons/syscons.h @@ -532,6 +532,8 @@ void sc_set_cursor_image(scr_stat *scp); int sc_clean_up(scr_stat *scp, int need_unlock); int sc_switch_scr(sc_softc_t *sc, u_int next_scr); +void syscons_lock(void); +void syscons_unlock(void); void sc_alloc_scr_buffer(scr_stat *scp, int wait, int discard); int sc_init_emulator(scr_stat *scp, char *name); void sc_paste(scr_stat *scp, u_char *p, int count); --- a/sys/dev/misc/syscons/schistory.c +++ b/sys/dev/misc/syscons/schistory.c @@ -90,9 +90,15 @@ /* make it at least as large as the screen size */ lines = imax(lines, scp->ysize); - /* remove the history buffer while we update it */ + /* + * Remove the history buffer while we update it. Serialize against + * the tty output path (scstart()/sc_puts() -> sc_hist_* writers), + * which holds syscons_lock(). + */ + syscons_lock(); history = prev_history = scp->history; scp->history = NULL; + syscons_unlock(); /* calculate the amount of lines to put back to extra_history_size */ delta = 0; @@ -136,14 +142,19 @@ if (ok) { /* - * Install new buffer, destroy previous buffer + * Install the new buffer under syscons_lock() so a + * concurrent sc_puts()/sc_hist_* writer cannot observe a + * half-installed ring; free the old one outside the + * spinlock. */ + syscons_lock(); + scp->history = history; + syscons_unlock(); if (prev_history != NULL) { extra_history_size += delta; sc_vtb_destroy(prev_history); kfree(prev_history, M_SYSCONS); } - scp->history = history; } else { /* * Destroy partially initialized new buffer, keep @@ -154,8 +165,10 @@ sc_vtb_destroy(history); kfree(history, M_SYSCONS); } + syscons_lock(); scp->history_pos = 0; scp->history = prev_history; + syscons_unlock(); } return 0; } --- a/sys/dev/misc/syscons/scvidctl.c +++ b/sys/dev/misc/syscons/scvidctl.c @@ -60,6 +60,8 @@ int prev_ysize; int new_ysize; int error; + sc_vtb_t new_vtb; + sc_vtb_t old_vtb; lwkt_gettoken(&vga_token); if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info)) { @@ -126,19 +128,38 @@ return ENODEV; } + /* + * Allocate the new shadow screen buffer FIRST and WITHOUT SLEEPING: + * this function holds only vga_token, which LWKT drops whenever the + * thread blocks; a sleeping allocation here would let a second mode + * change (or the tty output path) run against a half-updated scp. + */ + sc_vtb_init(&new_vtb, VTB_MEMORY, xsize, ysize, NULL, FALSE); + if (!(new_vtb.vtb_flags & VTB_VALID)) { + lwkt_reltoken(&vga_token); + return ENOMEM; + } + /* set up scp */ new_ysize = 0; #ifndef SC_NO_HISTORY if (scp->history != NULL) { + syscons_lock(); sc_hist_save(scp); + syscons_unlock(); new_ysize = sc_vtb_rows(scp->history); } #endif prev_ysize = scp->ysize; /* - * This is a kludge to fend off scrn_update() while we - * muck around with scp. XXX + * Commit the mode change under syscons_lock() so the tty output + * path (scstart()/sc_puts()) and the renderer, which hold that + * lock, cannot observe scp mid-update; vga_token serializes + * concurrent mode changes (there is no sleeping past this point). + * The UNKNOWN_MODE kludge to fend off scrn_update() while we muck + * around with scp stays. XXX */ + syscons_lock(); scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN; scp->status &= ~(GRAPHICS_MODE | PIXEL_MODE | MOUSE_VISIBLE); scp->mode = mode; @@ -152,13 +173,14 @@ scp->font = font; scp->font_height = fontsize; scp->font_width = 8; - - /* allocate buffers */ - sc_alloc_scr_buffer(scp, TRUE, TRUE); + old_vtb = scp->vtb; + scp->vtb = new_vtb; sc_init_emulator(scp, NULL); #ifndef SC_NO_CUTPASTE sc_alloc_cut_buffer(scp, FALSE); #endif + syscons_unlock(); + sc_vtb_destroy(&old_vtb); /* outside the spinlock */ #ifndef SC_NO_HISTORY sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE); #endif @@ -271,6 +293,8 @@ int prev_ysize; int new_ysize; int error; + sc_vtb_t new_vtb; + sc_vtb_t old_vtb; lwkt_gettoken(&vga_token); if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, scp->mode, &info)) { @@ -367,15 +391,31 @@ scp->ts = NULL; #endif + /* + * Allocate the new shadow screen buffer FIRST and WITHOUT SLEEPING; + * see sc_set_text_mode(). (sc_set_pixel_mode() is entered without + * vga_token held, so we take it for the commit.) + */ + lwkt_gettoken(&vga_token); + sc_vtb_init(&new_vtb, VTB_MEMORY, xsize, ysize, NULL, FALSE); + if (!(new_vtb.vtb_flags & VTB_VALID)) { + lwkt_reltoken(&vga_token); + return ENOMEM; + } + /* set up scp */ new_ysize = 0; #ifndef SC_NO_HISTORY if (scp->history != NULL) { + syscons_lock(); sc_hist_save(scp); + syscons_unlock(); new_ysize = sc_vtb_rows(scp->history); } #endif prev_ysize = scp->ysize; + /* commit under syscons_lock(); no sleeping past this point */ + syscons_lock(); scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN); scp->status &= ~(GRAPHICS_MODE | MOUSE_VISIBLE); scp->model = info.vi_mem_model; @@ -386,13 +426,15 @@ scp->font = font; scp->font_height = fontsize; scp->font_width = 8; - - /* allocate buffers */ - sc_alloc_scr_buffer(scp, TRUE, TRUE); + old_vtb = scp->vtb; + scp->vtb = new_vtb; sc_init_emulator(scp, NULL); #ifndef SC_NO_CUTPASTE sc_alloc_cut_buffer(scp, FALSE); #endif + syscons_unlock(); + sc_vtb_destroy(&old_vtb); /* outside the spinlock */ + lwkt_reltoken(&vga_token); #ifndef SC_NO_HISTORY sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE); #endif