vt(4): New bitblt_text variant making a copy before unlocking vt_buf

[Why]
In the DRM drivers and the integration with vt(4), we need to execute
DRM code outside of the vtbuf_lock. The reason is that this DRM code
acquires locks which can't be acquired when vtbuf_lock, an MTX_SPIN
mutex, is already held.

[How]
A vt(4) backend can now set the `vd_bitblt_after_vtbuf_unlock` flag to
true if it wants to be called outside of vt_buf_lock.

In this case, vt(4) uses an internal version of bitblt_text that uses
the `vd_drawn` arrays, plus a new `vd_pos_to_flush` array, to track
characters to draw/refresh. This internal version then uses the
backend's bitblt_bmp callback to draw the characters after vt_buf has
been unlocked.

Drawing borders and CPU logos is also deferred after the vt_buf lock is
released for the same reason.

We introduce another lock (a default mutex), only used when the
`vd_bitblt_after_vtbuf_unlock` flag is set, to replace part the role of
the vt_buf lock and manage concurrent calls to vt_flush().

The `SC_NO_CONSDRAWN` define is dropped because we now always need the
`vd_drawn` arrays.

Reviewed by:	manu
Approved by:	manu
Differential Revision:  https://reviews.freebsd.org/D42057
This commit is contained in:
Jean-Sébastien Pédron 2023-11-24 18:30:34 +01:00
parent 24d6f256f8
commit 162a2b8588
No known key found for this signature in database
GPG key ID: 39E99761A5FD94CC
2 changed files with 185 additions and 14 deletions

View file

@ -165,6 +165,9 @@ struct vt_device {
term_char_t *vd_drawn; /* (?) Most recent char drawn. */
term_color_t *vd_drawnfg; /* (?) Most recent fg color drawn. */
term_color_t *vd_drawnbg; /* (?) Most recent bg color drawn. */
struct mtx vd_flush_lock; /* (?) vt_flush() lock. */
bool *vd_pos_to_flush;/* (?) Positions to flush. */
};
#define VD_PASTEBUF(vd) ((vd)->vd_pastebuf.vpb_buf)
@ -175,6 +178,14 @@ struct vt_device {
#define VT_UNLOCK(vd) mtx_unlock(&(vd)->vd_lock)
#define VT_LOCK_ASSERT(vd, what) mtx_assert(&(vd)->vd_lock, what)
#define VT_FLUSH_LOCK(vd) \
if ((vd)->vd_driver->vd_bitblt_after_vtbuf_unlock) \
mtx_lock(&(vd)->vd_flush_lock)
#define VT_FLUSH_UNLOCK(vd) \
if ((vd)->vd_driver->vd_bitblt_after_vtbuf_unlock) \
mtx_unlock(&(vd)->vd_flush_lock)
void vt_resume(struct vt_device *vd);
void vt_resume_flush_timer(struct vt_window *vw, int ms);
void vt_suspend(struct vt_device *vd);
@ -376,6 +387,14 @@ struct vt_driver {
#define VD_PRIORITY_DUMB 10
#define VD_PRIORITY_GENERIC 100
#define VD_PRIORITY_SPECIFIC 1000
/*
* Should vd_bitblt_text() be called after unlocking vtbuf? If true,
* characters are copied before vd_bitblt_bmp() is called.
*
* This is only valid when the default bitblt_text callback is used.
*/
bool vd_bitblt_after_vtbuf_unlock;
};
/*

View file

@ -202,11 +202,10 @@ SET_DECLARE(vt_drv_set, struct vt_driver);
static struct terminal vt_consterm;
static struct vt_window vt_conswindow;
#ifndef SC_NO_CONSDRAWN
static term_char_t vt_consdrawn[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
static term_color_t vt_consdrawnfg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
static term_color_t vt_consdrawnbg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
#endif
static bool vt_cons_pos_to_flush[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
struct vt_device vt_consdev = {
.vd_driver = NULL,
.vd_softc = NULL,
@ -228,11 +227,11 @@ struct vt_device vt_consdev = {
.vd_mcursor_bg = TC_BLACK,
#endif
#ifndef SC_NO_CONSDRAWN
.vd_drawn = vt_consdrawn,
.vd_drawnfg = vt_consdrawnfg,
.vd_drawnbg = vt_consdrawnbg,
#endif
.vd_pos_to_flush = vt_cons_pos_to_flush,
};
static term_char_t vt_constextbuf[(_VTDEFW) * (VBF_DEFAULT_HISTORY_SIZE)];
static term_char_t *vt_constextbufrows[VBF_DEFAULT_HISTORY_SIZE];
@ -292,6 +291,7 @@ vt_update_static(void *dummy)
printf("VT: init without driver.\n");
mtx_init(&main_vd->vd_lock, "vtdev", NULL, MTX_DEF);
mtx_init(&main_vd->vd_flush_lock, "vtdev flush", NULL, MTX_DEF);
cv_init(&main_vd->vd_winswitch, "vtwswt");
}
@ -1348,6 +1348,122 @@ vt_set_border(struct vt_device *vd, const term_rect_t *area,
vd->vd_height - 1, 1, c);
}
static void
vt_flush_to_buffer(struct vt_device *vd,
const struct vt_window *vw, const term_rect_t *area)
{
unsigned int col, row;
term_char_t c;
term_color_t fg, bg;
size_t z;
for (row = area->tr_begin.tp_row; row < area->tr_end.tp_row; ++row) {
for (col = area->tr_begin.tp_col; col < area->tr_end.tp_col;
++col) {
z = row * PIXEL_WIDTH(VT_FB_MAX_WIDTH) + col;
if (z >= PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) *
PIXEL_WIDTH(VT_FB_MAX_WIDTH))
continue;
c = VTBUF_GET_FIELD(&vw->vw_buf, row, col);
vt_determine_colors(c,
VTBUF_ISCURSOR(&vw->vw_buf, row, col), &fg, &bg);
if (vd->vd_drawn && (vd->vd_drawn[z] == c) &&
vd->vd_drawnfg && (vd->vd_drawnfg[z] == fg) &&
vd->vd_drawnbg && (vd->vd_drawnbg[z] == bg)) {
vd->vd_pos_to_flush[z] = false;
continue;
}
vd->vd_pos_to_flush[z] = true;
if (vd->vd_drawn)
vd->vd_drawn[z] = c;
if (vd->vd_drawnfg)
vd->vd_drawnfg[z] = fg;
if (vd->vd_drawnbg)
vd->vd_drawnbg[z] = bg;
}
}
}
static void
vt_bitblt_buffer(struct vt_device *vd, const struct vt_window *vw,
const term_rect_t *area)
{
unsigned int col, row, x, y;
struct vt_font *vf;
term_char_t c;
term_color_t fg, bg;
const uint8_t *pattern;
size_t z;
vf = vw->vw_font;
for (row = area->tr_begin.tp_row; row < area->tr_end.tp_row; ++row) {
for (col = area->tr_begin.tp_col; col < area->tr_end.tp_col;
++col) {
x = col * vf->vf_width +
vw->vw_draw_area.tr_begin.tp_col;
y = row * vf->vf_height +
vw->vw_draw_area.tr_begin.tp_row;
z = row * PIXEL_WIDTH(VT_FB_MAX_WIDTH) + col;
if (z >= PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) *
PIXEL_WIDTH(VT_FB_MAX_WIDTH))
continue;
if (!vd->vd_pos_to_flush[z])
continue;
c = vd->vd_drawn[z];
fg = vd->vd_drawnfg[z];
bg = vd->vd_drawnbg[z];
pattern = vtfont_lookup(vf, c);
vd->vd_driver->vd_bitblt_bmp(vd, vw,
pattern, NULL, vf->vf_width, vf->vf_height,
x, y, fg, bg);
}
}
#ifndef SC_NO_CUTPASTE
if (!vd->vd_mshown)
return;
term_rect_t drawn_area;
drawn_area.tr_begin.tp_col = area->tr_begin.tp_col * vf->vf_width;
drawn_area.tr_begin.tp_row = area->tr_begin.tp_row * vf->vf_height;
drawn_area.tr_end.tp_col = area->tr_end.tp_col * vf->vf_width;
drawn_area.tr_end.tp_row = area->tr_end.tp_row * vf->vf_height;
if (vt_is_cursor_in_area(vd, &drawn_area)) {
vd->vd_driver->vd_bitblt_bmp(vd, vw,
vd->vd_mcursor->map, vd->vd_mcursor->mask,
vd->vd_mcursor->width, vd->vd_mcursor->height,
vd->vd_mx_drawn + vw->vw_draw_area.tr_begin.tp_col,
vd->vd_my_drawn + vw->vw_draw_area.tr_begin.tp_row,
vd->vd_mcursor_fg, vd->vd_mcursor_bg);
}
#endif
}
static void
vt_draw_decorations(struct vt_device *vd)
{
struct vt_window *vw;
const teken_attr_t *a;
vw = vd->vd_curwindow;
a = teken_get_curattr(&vw->vw_terminal->tm_emulator);
vt_set_border(vd, &vw->vw_draw_area, a->ta_bgcolor);
if (vt_draw_logo_cpus)
vtterm_draw_cpu_logos(vd);
}
static int
vt_flush(struct vt_device *vd)
{
@ -1357,6 +1473,7 @@ vt_flush(struct vt_device *vd)
#ifndef SC_NO_CUTPASTE
int cursor_was_shown, cursor_moved;
#endif
bool needs_refresh;
if (inside_vt_flush && KERNEL_PANICKED())
return (0);
@ -1372,8 +1489,9 @@ vt_flush(struct vt_device *vd)
if (((vd->vd_flags & VDF_TEXTMODE) == 0) && (vf == NULL))
return (0);
vtbuf_lock(&vw->vw_buf);
VT_FLUSH_LOCK(vd);
vtbuf_lock(&vw->vw_buf);
inside_vt_flush = true;
#ifndef SC_NO_CUTPASTE
@ -1417,29 +1535,63 @@ vt_flush(struct vt_device *vd)
vtbuf_undirty(&vw->vw_buf, &tarea);
/* Force a full redraw when the screen contents might be invalid. */
needs_refresh = false;
if (vd->vd_flags & (VDF_INVALID | VDF_SUSPENDED)) {
const teken_attr_t *a;
needs_refresh = true;
vd->vd_flags &= ~VDF_INVALID;
a = teken_get_curattr(&vw->vw_terminal->tm_emulator);
vt_set_border(vd, &vw->vw_draw_area, a->ta_bgcolor);
vt_termrect(vd, vf, &tarea);
if (vd->vd_driver->vd_invalidate_text)
vd->vd_driver->vd_invalidate_text(vd, &tarea);
if (vt_draw_logo_cpus)
vtterm_draw_cpu_logos(vd);
}
if (tarea.tr_begin.tp_col < tarea.tr_end.tp_col) {
vd->vd_driver->vd_bitblt_text(vd, vw, &tarea);
inside_vt_flush = false;
vtbuf_unlock(&vw->vw_buf);
if (vd->vd_driver->vd_bitblt_after_vtbuf_unlock) {
/*
* When `vd_bitblt_after_vtbuf_unlock` is set to true,
* we first remember the characters to redraw. They are
* already copied to the `vd_drawn` arrays.
*
* We then unlock vt_buf and proceed with the actual
* drawing using the backend driver.
*/
vt_flush_to_buffer(vd, vw, &tarea);
vtbuf_unlock(&vw->vw_buf);
vt_bitblt_buffer(vd, vw, &tarea);
if (needs_refresh)
vt_draw_decorations(vd);
/*
* We can reset `inside_vt_flush` after unlocking vtbuf
* here because we also hold vt_flush_lock in this code
* path.
*/
inside_vt_flush = false;
} else {
/*
* When `vd_bitblt_after_vtbuf_unlock` is false, we use
* the backend's `vd_bitblt_text` callback directly.
*/
vd->vd_driver->vd_bitblt_text(vd, vw, &tarea);
if (needs_refresh)
vt_draw_decorations(vd);
inside_vt_flush = false;
vtbuf_unlock(&vw->vw_buf);
}
VT_FLUSH_UNLOCK(vd);
return (1);
}
inside_vt_flush = false;
vtbuf_unlock(&vw->vw_buf);
VT_FLUSH_UNLOCK(vd);
return (0);
}