Re: Soft scrolling on framebuffer consoles - with GPM Handling - revised for kernel 7.0.14-r1 (etc.)
Alan Mackenzie <[email protected]> Thu, 2 Jul 2026 18:56:07 +0000
| Newsgroups | gmane.linux.gentoo.user |
|---|---|
| Message-ID | <[email protected]> |
--RU1IxUBZOAXVxHh3
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Hello again, Gentoo.
On Mon, May 11, 2026 at 14:00:58 +0000, Alan Mackenzie wrote:
> Now for the latest kernel, 6.18.28!
Well, kernel 7.0.14-r1 has just appeared, and my most recent patch won't
apply to it cleanly.
So here is a version 7.0.14-GPM-NO-MD.20260702.diff, for that new
release. Hopefully it will will apply cleanly to quite a few subsequent
releases.
As always, the purpose of this patch is to enable <Shift><PageUp> and
<Shift><PageDown> to scroll the Linux console. It also enables the GPM
mouse utility on such scrolled areas.
The most recent recommendation was to put this patch into
/etc/portage/patches/sys-kernels/gentoo-sources/. If you do this, you'll
need first to remove any previous version of the patch from there.
It would be nice to be able to use .../gentoo-sources:7.0/ for the newest
version and .../gentoo-sources:6.18/ for the 6.18.* versions, but the
portage patch mechanism doesn't have this flexibility; a patch there is
applied either to all kernel versions, or a single specific version, or
(what amounts to the same thing for gentoo-sources) a single specific
slot.
I have tested 7.0.14-GPM-NO-MD.20260702.diff on my own setup, and it
works there.
As always, you are responsible for your own system, so that if this patch
breaks anything I can only offer apologies (and attempt to fix it on being
given details). As always, my only promises are there's nothing
malicious in the patch, and also that LLMs weren't used at any stage of
its production.
> On Tue, Mar 17, 2026 at 21:07:38 +0000, Alan Mackenzie wrote:
> > On Sun, Feb 22, 2026 at 21:31:48 +0000, Alan Mackenzie wrote:
> > > The topic of this post is my kernel patch which enables soft scrolling
> > > on Linux tty's with <shift><PgUp> and <shift><PgDn>, and also enables
> > > the GPM mouse utility on those scrolled regions.
[ .... ]
> > > To use the patch:
> > > (i) cd /usr/src/linux-6.12.28-gentoo, or similar.
> > > (ii) patch -p1 < 6.12.28-GPM.20260511.diff.
> > > (iii) Configure the kernel as normal. Additionally, under Device
> > > drivers/Graphic Support/Console display driver support, enable
> > > CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK, set the buffer size to taste
> > > (it's default is 128 kB but I use 512 kB, personally) and accept the
> > > default enablement of CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM.
> > > (iv) Build the kernel and install it into your boot manager.
> > > (v) Reboot and enjoy! You can now use GPM in scrolled consoles.
> > There's some doubt as whether the patch in its current state works with
> > the nvidia-drivers package, in particular version 580.xx. Dale has been
> > having problems here, as has somebody who contacted me via private mail.
> > If anybody else with an nVidia graphics card tries the patch out with
> > nvidia-drivers (as contrasted with the nouveau driver), I'd be grateful
> > to hear from them whether or not the virtual terminals work. Thanks!
> Again, I still have doubts about whether the patch works on nvidia
> graphics boards with the nvidia-drivers package. If anybody has such a
> setup and tries the patch, please let me know whether and how well it
> works. Thanks!
The above still applies. If you try the patch out with nvidia-drivers,
please let me no how well it works/doesn't work. Thanks!
> > > If anybody still needs them, I've still got the patches for some earlier
> > > kernel versions too.
> Have fun!
--
Alan Mackenzie (Nuremberg, Germany).
--RU1IxUBZOAXVxHh3
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=7.0.14-GPM-NO-MD.20260702.diff
diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 07d3b93975d3..43b24aa8d484 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -42,21 +42,20 @@ static struct vc_selection {
char *buffer;
unsigned int buf_len;
volatile int start; /* cleared by clear_selection */
- int end;
+ int end; /* Note: this points to the last char, not to after it. */
+ bool mouse_at_e;
+ unsigned short prev_x;
} vc_sel = {
.lock = __MUTEX_INITIALIZER(vc_sel.lock),
.start = -1,
};
+static bool unicode;
+static unsigned int size_row;
+
/* clear_selection, highlight and highlight_pointer can be called
from interrupt (via scrollback/front) */
-/* set reverse video on characters s-e of console with selection. */
-static inline void highlight(const int s, const int e)
-{
- invert_screen(vc_sel.cons, s, e-s+2, true);
-}
-
/* use complementary color to show the pointer */
static inline void highlight_pointer(const int where)
{
@@ -64,7 +63,7 @@ static inline void highlight_pointer(const int where)
}
static u32
-sel_pos(int n, bool unicode)
+sel_pos(int n)
{
if (unicode)
return screen_glyph_unicode(vc_sel.cons, n / 2);
@@ -72,6 +71,61 @@ sel_pos(int n, bool unicode)
false);
}
+static int last_char_pos(int line_end, int limit)
+{
+ int pos = line_end;
+ while ((pos >= limit) && is_space_on_vt(sel_pos(pos)))
+ pos -= 2;
+ return pos;
+}
+
+/* set reverse video on characters s-e of console with selection. */
+static void highlight(const int s, const int e)
+{
+ bool mouse_at_e = (vc_sel.start != -1) && vc_sel.mouse_at_e;
+ int bol = s;
+ int eol = rounddown(s, size_row) + size_row - 2;
+ int pos;
+
+ if (mouse_at_e) {
+ int prev_e = s - 2; /* For the + 2 in vc_do_selection. */
+ int low_mouse_beg = rounddown(prev_e, size_row);
+ int high_mouse_beg = rounddown(e, size_row);
+ if (low_mouse_beg != high_mouse_beg) {
+ /* Unhighlight any trailing space on the previous line
+ * (moving down), or rehighlight it (moving up). */
+ int low_mouse_end = low_mouse_beg + size_row - 2;
+
+ pos = last_char_pos(low_mouse_end,
+ max(vc_sel.start, low_mouse_beg)) + 2;
+ if (prev_e >= pos)
+ invert_screen(vc_sel.cons, pos,
+ prev_e - pos + 2, true);
+ }
+ }
+
+ while (eol < e) {
+ if ((pos = last_char_pos(eol, bol)) >= bol)
+ invert_screen(vc_sel.cons, bol, pos - bol + 2, true);
+ bol = eol + 2;
+ eol += size_row;
+ }
+ /* Last line: Firstly, are we (un)highlighting the entire selection? */
+ if ((vc_sel.start == -1) || ((s == vc_sel.start && e == vc_sel.end)) ||
+ /* .... or trailing space at the end of the selection? */
+ (eol >= vc_sel.end))
+ /* YES: so highlight the entire last line up to E. */
+ invert_screen(vc_sel.cons, bol, e - bol + 2, true);
+ else {
+ /* NO: Don't highlight the trailing spaces. */
+ pos = last_char_pos(eol, bol);
+ if (pos > e)
+ pos = e;
+ if (pos >= bol)
+ invert_screen(vc_sel.cons, bol, pos - bol + 2, true);
+ }
+}
+
/**
* clear_selection - remove current selection
*
@@ -207,15 +261,23 @@ int set_selection_user(const struct tiocl_selection __user *sel,
return set_selection_kernel(&v, tty);
}
-static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
+static int vc_selection_store_chars(void)
{
char *bp, *obp;
unsigned int i;
/* Allocate a new buffer before freeing the old one ... */
/* chars can take up to 4 bytes with unicode */
- bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1, unicode ? 4 : 1,
- GFP_KERNEL | __GFP_NOWARN);
+ if (vc_sel.end >= vc_sel.start)
+ bp = kmalloc_array((vc_sel.end - vc_sel.start) / 2 + 1,
+ unicode ? 4 : 1,
+ GFP_KERNEL | __GFP_NOWARN);
+ else { /* Happens with TIOCL_WORDSEL on a blank line. */
+ kfree(vc_sel.buffer);
+ vc_sel.buffer = NULL;
+ vc_sel.buf_len = 0;
+ return 0;
+ }
if (!bp) {
printk(KERN_WARNING "selection: kmalloc() failed\n");
clear_selection();
@@ -226,16 +288,17 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
obp = bp;
for (i = vc_sel.start; i <= vc_sel.end; i += 2) {
- u32 c = sel_pos(i, unicode);
+ u32 c = sel_pos(i);
if (unicode)
bp += store_utf8(c, bp);
else
*bp++ = c;
if (!is_space_on_vt(c))
obp = bp;
- if (!((i + 2) % vc->vc_size_row)) {
+ if (!((i + 2) % size_row) &&
+ (i + 2) < vc_sel.end) {
/* strip trailing blanks from line and add newline,
- unless non-space at end of line. */
+ unless non-space at end of line or on last line. */
if (obp != bp) {
bp = obp;
*bp++ = '\r';
@@ -248,11 +311,10 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
return 0;
}
-static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
- int pe)
+static int vc_do_selection(unsigned short mode, int ps, int pe)
{
int new_sel_start, new_sel_end, spc;
- bool unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
+ int pe_line_start, term_space;
switch (mode) {
case TIOCL_SELCHAR: /* character-by-character selection */
@@ -260,30 +322,37 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
new_sel_end = pe;
break;
case TIOCL_SELWORD: /* word-by-word selection */
- spc = is_space_on_vt(sel_pos(ps, unicode));
+ spc = is_space_on_vt(sel_pos(ps));
for (new_sel_start = ps; ; ps -= 2) {
- if ((spc && !is_space_on_vt(sel_pos(ps, unicode))) ||
- (!spc && !inword(sel_pos(ps, unicode))))
+ if ((spc && !is_space_on_vt(sel_pos(ps))) ||
+ (!spc && !inword(sel_pos(ps))))
break;
new_sel_start = ps;
- if (!(ps % vc->vc_size_row))
+ if (!(ps % size_row))
break;
}
- spc = is_space_on_vt(sel_pos(pe, unicode));
+ spc = is_space_on_vt(sel_pos(pe));
+ if (spc) term_space = pe;
for (new_sel_end = pe; ; pe += 2) {
- if ((spc && !is_space_on_vt(sel_pos(pe, unicode))) ||
- (!spc && !inword(sel_pos(pe, unicode))))
+ if ((spc && !is_space_on_vt(sel_pos(pe))) ||
+ (!spc && !inword(sel_pos(pe))))
break;
new_sel_end = pe;
- if (!((pe + 2) % vc->vc_size_row))
+ if (!((pe + 2) % size_row))
break;
}
+ /* Don't highlight trailing space after the mouse on the last
+ * line. */
+ if (spc &&
+ !((pe + 2) % size_row))
+ new_sel_end = last_char_pos(term_space, ps);
break;
case TIOCL_SELLINE: /* line-by-line selection */
- new_sel_start = rounddown(ps, vc->vc_size_row);
- new_sel_end = rounddown(pe, vc->vc_size_row) +
- vc->vc_size_row - 2;
+ new_sel_start = rounddown(ps, size_row);
+ pe_line_start = rounddown(pe, size_row);
+ new_sel_end = last_char_pos(pe_line_start + size_row - 2,
+ new_sel_start); /* Can be < BOL. */
break;
case TIOCL_SELPOINTER:
highlight_pointer(pe);
@@ -295,17 +364,6 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
/* remove the pointer */
highlight_pointer(-1);
- /* select to end of line if on trailing space */
- if (new_sel_end > new_sel_start &&
- !atedge(new_sel_end, vc->vc_size_row) &&
- is_space_on_vt(sel_pos(new_sel_end, unicode))) {
- for (pe = new_sel_end + 2; ; pe += 2)
- if (!is_space_on_vt(sel_pos(pe, unicode)) ||
- atedge(pe, vc->vc_size_row))
- break;
- if (is_space_on_vt(sel_pos(pe, unicode)))
- new_sel_end = pe;
- }
if (vc_sel.start == -1) /* no current selection */
highlight(new_sel_start, new_sel_end);
else if (new_sel_start == vc_sel.start)
@@ -324,22 +382,32 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
else /* contract from left */
highlight(vc_sel.start, new_sel_start - 2);
}
- else /* some other case; start selection from scratch */
+ else /* The mouse has been moved through the starting point or we
+ * have word or line selection. */
{
- clear_selection();
+ /* Restore vc_sel.mouse_at_e to its previous value to
+ * unhighlight the previous selection. */
+ vc_sel.mouse_at_e = !vc_sel.mouse_at_e;
+ highlight(vc_sel.start, vc_sel.end);
+ vc_sel.mouse_at_e = !vc_sel.mouse_at_e;
+
+ vc_sel.start = new_sel_start;
+ vc_sel.end = new_sel_end;
highlight(new_sel_start, new_sel_end);
}
vc_sel.start = new_sel_start;
vc_sel.end = new_sel_end;
- return vc_selection_store_chars(vc, unicode);
+ return vc_selection_store_chars();
}
static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
struct tty_struct *tty)
{
int ps, pe;
+ int res;
+ size_row = vc->vc_size_row;
poke_blanked_console();
if (v->sel_mode == TIOCL_SELCLEAR) {
@@ -348,6 +416,20 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
return 0;
}
+ /* Heuristically correct any strange values from GPM. When the mouse
+ * is dragged off the left hand edge, GPM reports it as being at the
+ * end of the previous line. When it is dragged off the bottom edge,
+ * it is reported as being at the end of the last line. */
+ if ((v->xe == vc->vc_cols) && (vc_sel.start != -1)) {
+ if (v->ye == vc->vc_rows) {
+ if (vc_sel.prev_x <= (vc->vc_cols - 20))
+ v->xe = vc_sel.prev_x;
+ } else if (vc_sel.prev_x <= (vc->vc_cols >> 1)) {
+ v->xe = 1;
+ v->ye++;
+ }
+ }
+
/* Historically 0 => max value */
v->xs = umin(v->xs - 1, vc->vc_cols - 1);
v->ys = umin(v->ys - 1, vc->vc_rows - 1);
@@ -359,17 +441,27 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
return 0;
}
- ps = v->ys * vc->vc_size_row + (v->xs << 1);
- pe = v->ye * vc->vc_size_row + (v->xe << 1);
- if (ps > pe) /* make vc_sel.start <= vc_sel.end */
+ vc_sel.mouse_at_e = true;
+ ps = v->ys * size_row + (v->xs << 1);
+ pe = v->ye * size_row + (v->xe << 1);
+ if (ps > pe) { /* make vc_sel.start <= vc_sel.end */
swap(ps, pe);
+ vc_sel.mouse_at_e = false;
+ }
if (vc_sel.cons != vc) {
clear_selection();
vc_sel.cons = vc;
}
- return vc_do_selection(vc, v->sel_mode, ps, pe);
+ unicode = vt_do_kdgkbmode(fg_console) == K_UNICODE;
+
+ res = vc_do_selection(v->sel_mode, ps, pe);
+ if ((vc_sel.start != -1) &&
+ ((v->sel_mode == TIOCL_SELCHAR) || (v->sel_mode == TIOCL_SELWORD) ||
+ (v->sel_mode == TIOCL_SELLINE)))
+ vc_sel.prev_x = v->xe + 1; /* Convert back to 1-based. */
+ return res;
}
int set_selection_kernel(struct tiocl_selection *v, struct tty_struct *tty)
diff --git a/drivers/tty/vt/vt.c.orig b/drivers/tty/vt/vt.c
index a111719..aa7915d 100755
--- a/drivers/tty/vt/vt.c.orig
+++ b/drivers/tty/vt/vt.c
@@ -133,6 +133,11 @@ const struct consw *conswitchp;
#define DEFAULT_BELL_DURATION (HZ/8)
#define DEFAULT_CURSOR_BLINK_MS 200
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static unsigned int console_soft_scrollback_size =
+ 1024 * CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE;
+#endif
+
struct vc vc_cons [MAX_NR_CONSOLES];
EXPORT_SYMBOL(vc_cons);
@@ -289,9 +294,27 @@ static inline bool con_should_update(const struct vc_data *vc)
static inline u16 *screenpos(const struct vc_data *vc, unsigned int offset,
bool viewed)
{
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ unsigned long softback_pos = vc->vc_softback_curr;
+ unsigned long scrolled_expanse;
+
+ if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+ return (unsigned short *)(vc->vc_origin + offset);
+ else {
+ scrolled_expanse = SOFTBACK_GAP(vc->vc_softback_in,
+ vc->vc_softback_curr);
+ if (offset >= scrolled_expanse)
+ return (unsigned short *)(vc->vc_origin
+ + (offset - scrolled_expanse));
+ else
+ SOFTBACK_PLUS(softback_pos, offset);
+ }
+ return (unsigned short *)softback_pos;
+#else
unsigned long origin = viewed ? vc->vc_visible_origin : vc->vc_origin;
return (u16 *)(origin + offset);
+#endif
}
static void con_putc(struct vc_data *vc, u16 ca, unsigned int y, unsigned int x)
@@ -321,107 +344,116 @@ void schedule_console_callback(void)
* Code to manage unicode-based screen buffers
*/
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
-static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int rows)
{
- u32 **uni_lines;
- void *p;
- unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
-
- /* allocate everything in one go */
- memsize = col_size * rows;
- memsize += rows * sizeof(*uni_lines);
- uni_lines = vzalloc(memsize);
- if (!uni_lines)
- return NULL;
+ uint32_t *p;
+ unsigned int new_size; /* In 32-bit characters */
- /* initial line pointers */
- p = uni_lines + rows;
- for (i = 0; i < rows; i++) {
- uni_lines[i] = p;
- p += col_size;
- }
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ unsigned int num_scrollback_rows;
- return uni_lines;
+ num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+ new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+ new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+ p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+ if (!p)
+ return -ENOMEM;
+ vc->vc_uniscr_buf = p;
+ vc->vc_uniscr_curr = p;
+ vc->vc_uniscr_char_size = new_size;
+ memset32(p, ' ', new_size); /* Probably redundant. */
+ return 0;
}
-static void vc_uniscr_free(u32 **uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
{
- vfree(uni_lines);
+ kvfree(vc->vc_uniscr_buf);
+ vc->vc_uniscr_buf = NULL;
}
-static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_putc_xy(struct vc_data *vc, int x, int y, u32 uc)
{
- vc_uniscr_free(vc->vc_uni_lines);
- vc->vc_uni_lines = new_uni_lines;
+ uint32_t *pos;
+
+ if (vc->vc_uniscr_buf) {
+ pos = vc->vc_uniscr_curr;
+ UNISCR_PLUS(pos, y * vc->vc_cols + x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ *pos = uc;
+ }
}
static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
{
- if (vc->vc_uni_lines)
- vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+ vc_uniscr_putc_xy(vc, vc->state.x, vc->state.y, uc);
}
static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
{
- if (vc->vc_uni_lines) {
- u32 *ln = vc->vc_uni_lines[vc->state.y];
- unsigned int x = vc->state.x, cols = vc->vc_cols;
+ unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+ uint32_t *ln = vc->vc_uniscr_curr;
- memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+ if (vc->vc_uniscr_buf) {
+ UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(uint32_t));
memset32(&ln[x], ' ', nr);
}
}
static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
{
- if (vc->vc_uni_lines) {
- u32 *ln = vc->vc_uni_lines[vc->state.y];
- unsigned int x = vc->state.x, cols = vc->vc_cols;
+ unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+ uint32_t *ln = vc->vc_uniscr_curr;
- memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+ if (vc->vc_uniscr_buf) {
+ UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(uint32_t));
memset32(&ln[cols - nr], ' ', nr);
}
}
+/* FIXME!!! We need to check that NR never goes beyond the current line end. !!! */
static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
unsigned int nr)
{
- if (vc->vc_uni_lines)
- memset32(&vc->vc_uni_lines[vc->state.y][x], ' ', nr);
+ if (vc->vc_uniscr_buf) {
+ uint32_t *ln = vc->vc_uniscr_curr;
+
+ UNISCR_PLUS(ln, vc->state.y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ memset32(&ln[x], ' ', nr);
+ }
}
static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
unsigned int nr)
{
- if (vc->vc_uni_lines)
- while (nr--)
- memset32(vc->vc_uni_lines[y++], ' ', vc->vc_cols);
-}
-
-/* juggling array rotation algorithm (complexity O(N), size complexity O(1)) */
-static void juggle_array(u32 **array, unsigned int size, unsigned int nr)
-{
- unsigned int gcd_idx;
-
- for (gcd_idx = 0; gcd_idx < gcd(nr, size); gcd_idx++) {
- u32 *gcd_idx_val = array[gcd_idx];
- unsigned int dst_idx = gcd_idx;
+ if (vc->vc_uniscr_buf) {
+ unsigned int cols = vc->vc_cols;
+ uint32_t *ln = vc->vc_uniscr_curr;
- while (1) {
- unsigned int src_idx = (dst_idx + nr) % size;
- if (src_idx == gcd_idx)
- break;
-
- array[dst_idx] = array[src_idx];
- dst_idx = src_idx;
+ UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ while (nr--) {
+ memset32(ln, ' ', cols);
+ UNISCR_PLUS(ln, cols);
}
-
- array[dst_idx] = gcd_idx_val;
}
}
@@ -429,59 +461,58 @@ static void vc_uniscr_scroll(struct vc_data *vc, unsigned int top,
unsigned int bottom, enum con_scroll dir,
unsigned int nr)
{
- u32 **uni_lines = vc->vc_uni_lines;
- unsigned int size = bottom - top;
-
- if (!uni_lines)
- return;
+ if (vc->vc_uniscr_buf) {
+ unsigned int cols = vc->vc_cols;
+ uint32_t *dest, *src;
+ unsigned int i;
- if (dir == SM_DOWN) {
- juggle_array(&uni_lines[top], size, size - nr);
- vc_uniscr_clear_lines(vc, top, nr);
- } else {
- juggle_array(&uni_lines[top], size, nr);
- vc_uniscr_clear_lines(vc, bottom - nr, nr);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ if (dir == SM_UP && bottom == vc->vc_rows) {
+ UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+ } else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows - nr) {
+ UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+ } else
+#endif
+ if (dir == SM_UP) {
+ src = vc->vc_uniscr_curr;
+ UNISCR_PLUS(src, top * cols);
+ dest = src;
+ UNISCR_MINUS(dest, nr * cols);
+ i = bottom - top;
+ while (i--) {
+ memcpy(dest, src, cols * sizeof(uint32_t));
+ UNISCR_PLUS(src, cols);
+ UNISCR_PLUS(dest, cols);
+ }
+ } else {
+ src = vc->vc_uniscr_curr;
+ UNISCR_PLUS(src, bottom * cols);
+ dest = src;
+ UNISCR_PLUS(dest, nr * cols);
+ i = bottom - top;
+ while (i--) {
+ UNISCR_MINUS(src, cols);
+ UNISCR_MINUS(dest, cols);
+ memcpy(dest, src, cols * sizeof(uint32_t));
+ }
+ }
}
}
static u32 vc_uniscr_getc(struct vc_data *vc, int relative_pos)
{
- int pos = vc->state.x + vc->vc_need_wrap + relative_pos;
-
- if (vc->vc_uni_lines && in_range(pos, 0, vc->vc_cols))
- return vc->vc_uni_lines[vc->state.y][pos];
- return 0;
-}
-
-static void vc_uniscr_copy_area(u32 **dst_lines,
- unsigned int dst_cols,
- unsigned int dst_rows,
- u32 **src_lines,
- unsigned int src_cols,
- unsigned int src_top_row,
- unsigned int src_bot_row)
-{
- unsigned int dst_row = 0;
-
- if (!dst_lines)
- return;
-
- while (src_top_row < src_bot_row) {
- u32 *src_line = src_lines[src_top_row];
- u32 *dst_line = dst_lines[dst_row];
-
- memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
- if (dst_cols - src_cols)
- memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
- src_top_row++;
- dst_row++;
- }
- while (dst_row < dst_rows) {
- u32 *dst_line = dst_lines[dst_row];
+ uint32_t *pos;
+ int xx = vc->state.x + vc->vc_need_wrap + relative_pos;
- memset32(dst_line, ' ', dst_cols);
- dst_row++;
+ if (vc->vc_uniscr_buf && in_range(xx, 0, vc->vc_cols)) {
+ pos = vc->vc_uniscr_curr;
+ UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + xx);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+ return *pos;
}
+ return 0;
}
/*
@@ -492,7 +523,6 @@ static void vc_uniscr_copy_area(u32 **dst_lines,
*/
int vc_uniscr_check(struct vc_data *vc)
{
- u32 **uni_lines;
unsigned short *p;
int x, y, mask;
@@ -501,11 +531,10 @@ int vc_uniscr_check(struct vc_data *vc)
if (!vc->vc_utf)
return -ENODATA;
- if (vc->vc_uni_lines)
+ if (vc->vc_uniscr_buf)
return 0;
- uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
- if (!uni_lines)
+ if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
return -ENOMEM;
/*
@@ -517,15 +546,14 @@ int vc_uniscr_check(struct vc_data *vc)
p = (unsigned short *)vc->vc_origin;
mask = vc->vc_hi_font_mask | 0xff;
for (y = 0; y < vc->vc_rows; y++) {
- u32 *line = uni_lines[y];
+ uint32_t *line = vc->vc_uniscr_curr;
+ UNISCR_PLUS(line, y * vc->vc_cols);
for (x = 0; x < vc->vc_cols; x++) {
u16 glyph = scr_readw(p++) & mask;
line[x] = inverse_translate(vc, glyph, true);
}
}
- vc->vc_uni_lines = uni_lines;
-
return 0;
}
@@ -537,13 +565,24 @@ int vc_uniscr_check(struct vc_data *vc)
void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
unsigned int row, unsigned int col, unsigned int nr)
{
- u32 **uni_lines = vc->vc_uni_lines;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ uint32_t *pos; /* Position in the unicode buffer of col/row */
+#else
int offset = row * vc->vc_size_row + col * 2;
- unsigned long pos;
+ unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
- if (WARN_ON_ONCE(!uni_lines))
+ if (WARN_ON_ONCE(!vc->vc_uniscr_buf))
return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ pos = vc->vc_uniscr_curr;
+ UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+ memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
pos = (unsigned long)screenpos(vc, offset, viewed);
if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
/*
@@ -553,24 +592,238 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
*/
row = (pos - vc->vc_origin) / vc->vc_size_row;
col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
- memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+ memcpy(dest,
+ (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+ nr);
} else {
/*
- * Scrollback is active. For now let's simply backtranslate
- * the screen glyphs until the unicode screen buffer does
- * synchronize with console display drivers for a scrollback
- * buffer of its own.
+ * Scrollback is active. So hoik the unicode characters out
+ * of the unicode circular buffer.
*/
- u16 *p = (u16 *)pos;
- int mask = vc->vc_hi_font_mask | 0xff;
- u32 *uni_buf = dest;
- while (nr--) {
- u16 glyph = scr_readw(p++) & mask;
- *uni_buf++ = inverse_translate(vc, glyph, true);
+ /* CAN'T HAPPEN!!! (Hah hah!) */
+ }
+#endif
+}
+
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+ int l = console_soft_scrollback_size / vc->vc_size_row;
+ if (l > 5)
+ {
+ vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+ vc->vc_softback_top = vc->vc_softback_buf;
+ }
+ else
+ /* Smaller scrollback makes no sense, and 0 would screw
+ the operation totally */
+ vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+ if (vc->vc_softback_lines)
+ concon_scrolldelta(vc, vc->vc_softback_lines);
+ return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+ long delta)
+{
+ int count = vc->vc_rows;
+ unsigned short *d, *s;
+ unsigned long n;
+ int line = 0;
+
+ if (!vc->vc_softback_lines)
+ vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+ d = (u16 *) vc->vc_softback_curr;
+ if (d == (u16 *) vc->vc_softback_in)
+ d = (u16 *) vc->vc_origin;
+
+ n = vc->vc_softback_curr;
+ if (delta < 0) {
+ SOFTBACK_MINUS(n, -delta * vc->vc_size_row);
+ if (!SOFTBACK_WITHIN(n, vc->vc_softback_top, vc->vc_softback_curr)) {
+ /* We've hit the upper boundary of the softback buffer. */
+ delta += SOFTBACK_GAP(vc->vc_softback_top, n) / vc->vc_size_row;
+ n = vc->vc_softback_top;
+ }
+ }
+ else {
+ SOFTBACK_PLUS(n, delta * vc->vc_size_row);
+ if (!SOFTBACK_WITHIN(n, vc->vc_softback_curr, vc->vc_softback_in)) {
+ /* We're back to unscrolled again. */
+ delta -= SOFTBACK_GAP(n, vc->vc_softback_in) / vc->vc_size_row;
+ n = vc->vc_softback_in;
+ }
+ }
+ vc->vc_softback_lines -= delta;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ {
+ unsigned int nr;
+ int top, bottom;
+ enum con_scroll dir;
+
+ if (delta < 0) {
+ dir = SM_DOWN;
+ nr = -delta;
+ bottom = vc->vc_rows + delta;
+ top = 0;
+ } else {
+ dir = SM_UP;
+ nr = delta;
+ bottom = vc->vc_bottom;
+ top = delta + vc->vc_top;
+ }
+ vc_uniscr_scroll(vc, top, bottom, dir, nr);
+ }
+#endif
+ if (n == vc->vc_softback_curr)
+ return;
+ vc->vc_softback_curr = n;
+ /* If we're not scrolled any more, restore the character to the cursor
+ * position */
+ if (!vc->vc_softback_lines)
+ scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+ s = (u16 *) vc->vc_softback_curr;
+ if (s == (u16 *) vc->vc_softback_in)
+ s = (u16 *) vc->vc_origin;
+ while (count--) {
+ unsigned short *start;
+ unsigned short *le;
+ unsigned short c;
+ int x = 0;
+ unsigned short attr = 1;
+
+ start = s;
+ le = advance_row(s, 1);
+ /* Temporarily overwrite the character at the cursor position
+ * with the one we actually want to see on the screen. */
+ if (count == vc->vc_rows - vc->state.y - 1)
+ {
+ c = scr_readw((u16 *)(s + vc->state.x));
+ scr_writew(c, (u16 *)vc->vc_pos);
+ vc->vc_sw->con_putcs
+ (vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+ }
+ do {
+ c = scr_readw(s);
+ if (attr != (c & 0xff00)) {
+ attr = c & 0xff00;
+ if (s > start) {
+ vc->vc_sw->con_putcs(
+ vc, start, s - start,
+ line, x);
+ x += s - start;
+ start = s;
+ }
+ }
+ if (c == scr_readw(d)) {
+ if (s > start) {
+ vc->vc_sw->con_putcs(
+ vc, start, s - start,
+ line, x);
+ x += s - start + 1;
+ start = s + 1;
+ } else {
+ x++;
+ start++;
+ }
+ }
+ s++;
+ d++;
+ } while (s < le);
+ if (s > start) {
+ vc->vc_sw->con_putcs(vc, start, s - start, line, x);
}
+
+ line++;
+ if (d == (u16 *) vc->vc_softback_end)
+ d = (u16 *) vc->vc_softback_buf;
+ if (d == (u16 *) vc->vc_softback_in)
+ d = (u16 *) vc->vc_origin;
+ if (s == (u16 *) vc->vc_softback_end)
+ s = (u16 *) vc->vc_softback_buf;
+ if (s == (u16 *) vc->vc_softback_in)
+ s = (u16 *) vc->vc_origin;
+ }
+}
+
+static inline void con_softback_note(struct vc_data *vc, int t,
+ int count)
+{
+ unsigned short *p;
+
+ if (vc->vc_num != fg_console)
+ return;
+ p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+ while (count) {
+ scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+ count--;
+ p = advance_row(p, 1);
+ SOFTBACK_PLUS(vc->vc_softback_in, vc->vc_size_row);
+ if (vc->vc_softback_in == vc->vc_softback_top)
+ SOFTBACK_PLUS(vc->vc_softback_top, vc->vc_size_row);
+ }
+ vc->vc_softback_curr = vc->vc_softback_in;
+}
+
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+ /* struct display *disp = &fb_display[fg_console]; */
+ /* int offset, limit, scrollback_old; */
+
+ if (vc->vc_softback_top) {
+ if (vc->vc_num != fg_console)
+ return;
+ if (vc->vc_mode != KD_TEXT || !lines)
+ return;
+#if 0
+ if (logo_shown >= 0) {
+ struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+ if (conp2->vc_top == logo_lines
+ && conp2->vc_bottom == conp2->vc_rows)
+ conp2->vc_top = 0;
+ if (logo_shown == vc->vc_num) {
+ unsigned long p, q;
+ int i;
+
+ p = vc->vc_softback_in;
+ q = vc->vc_origin +
+ logo_lines * vc->vc_size_row;
+ for (i = 0; i < logo_lines; i++) {
+ if (p == vc->vc_softback_top)
+ break;
+ if (p == vc->vc_softback_buf)
+ p = vc->vc_softback_end;
+ p -= vc->vc_size_row;
+ q -= vc->vc_size_row;
+ scr_memcpyw((u16 *) q, (u16 *) p,
+ vc->vc_size_row);
+ }
+ vc->vc_softback_in = vc->vc_softback_curr = p;
+ update_region(vc, vc->vc_origin,
+ logo_lines * vc->vc_cols);
+ }
+ logo_shown = FBCON_LOGO_CANSHOW;
+ }
+#endif
+ vc->vc_sw->con_cursor(vc, false /* CM_ERASE */ /* | CM_SOFTBACK */);
+ con_redraw_softback(vc, /* disp, */ lines);
+ if (!vc->vc_softback_lines)
+ vc->vc_sw->con_cursor(vc, true /* CM_DRAW */ /* | CM_SOFTBACK */);
}
}
+#endif /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
+
static void con_scroll(struct vc_data *vc, unsigned int top,
unsigned int bottom, enum con_scroll dir,
unsigned int nr)
@@ -582,8 +835,16 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
nr = rows - 1;
if (bottom > vc->vc_rows || top >= bottom || nr < 1)
return;
-
vc_uniscr_scroll(vc, top, bottom, dir, nr);
+
+ if (dir == SM_UP
+ && bottom == vc->vc_rows) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (vc->vc_softback_top)
+ con_softback_note (vc, top, nr);
+#endif
+ vc_uniscr_clear_lines(vc, vc->vc_rows - nr, nr);
+ }
if (con_is_visible(vc) &&
vc->vc_sw->con_scroll(vc, top, bottom, dir, nr))
return;
@@ -599,6 +860,53 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+ unsigned int xx, yy, offset;
+ u16 *p;
+
+ unsigned long origin =
+ (start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+ ? start >= vc->vc_softback_curr
+ ? vc->vc_softback_curr
+ : vc->vc_softback_curr
+ - (vc->vc_softback_end - vc->vc_softback_buf)
+ : vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+ p = (u16 *) start;
+ offset = (start - origin) / 2;
+ xx = offset % vc->vc_cols;
+ yy = offset / vc->vc_cols;
+
+ for(;;) {
+ u16 attrib = scr_readw(p) & 0xff00;
+ int startx = xx;
+ u16 *q = p;
+ while (xx < vc->vc_cols && count) {
+ if (attrib != (scr_readw(p) & 0xff00)) {
+ if (p > q)
+ vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+ startx = xx;
+ q = p;
+ attrib = scr_readw(p) & 0xff00;
+ }
+ p++;
+ xx++;
+ count--;
+ }
+ if (p > q)
+ vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+ if (p == (u16 *) vc->vc_softback_end)
+ p = (u16 *)vc->vc_softback_buf;
+ if (p == (u16 *) vc->vc_softback_in)
+ p = (u16 *)vc->vc_origin;
+ if (!count)
+ break;
+ xx = 0;
+ yy++;
+ }
+}
+#else
static void do_update_region(struct vc_data *vc, unsigned long start, int count)
{
unsigned int xx, yy, offset;
@@ -632,6 +940,7 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
yy++;
}
}
+#endif
void update_region(struct vc_data *vc, unsigned long start, int count)
{
@@ -640,7 +949,10 @@ void update_region(struct vc_data *vc, unsigned long start, int count)
if (con_should_update(vc)) {
hide_cursor(vc);
do_update_region(vc, start, count);
- set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (!vc->vc_softback_lines)
+#endif
+ set_cursor(vc);
}
}
EXPORT_SYMBOL(update_region);
@@ -702,51 +1014,68 @@ static void update_attr(struct vc_data *vc)
}
/* Note: inverting the screen twice should revert to the original state */
+/* OFFSET is the offset in bytes (not 16-bit characters), COUNT is a byte
+ * count (not a character count). */
void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
{
u16 *p;
+ int row_offset, bytes_left_in_row;
+ int row_count;
WARN_CONSOLE_UNLOCKED();
count /= 2;
- p = screenpos(vc, offset, viewed);
- if (vc->vc_sw->con_invert_region) {
- vc->vc_sw->con_invert_region(vc, p, count);
- } else {
- u16 *q = p;
- int cnt = count;
- u16 a;
-
- if (!vc->vc_can_do_color) {
- while (cnt--) {
- a = scr_readw(q);
- a ^= 0x0800;
- scr_writew(a, q);
- q++;
- }
- } else if (vc->vc_hi_font_mask == 0x100) {
- while (cnt--) {
- a = scr_readw(q);
- a = (a & 0x11ff) |
- ((a & 0xe000) >> 4) |
- ((a & 0x0e00) << 4);
- scr_writew(a, q);
- q++;
- }
+ row_offset = offset;
+ bytes_left_in_row = vc->vc_size_row - (row_offset % vc->vc_size_row);
+ row_count = (count < bytes_left_in_row / 2)
+ ? count
+ : bytes_left_in_row / 2;
+
+ while (count) {
+ p = screenpos(vc, row_offset, viewed);
+ if (vc->vc_sw->con_invert_region) {
+ vc->vc_sw->con_invert_region(vc, p, row_count);
} else {
- while (cnt--) {
- a = scr_readw(q);
- a = (a & 0x88ff) |
- ((a & 0x7000) >> 4) |
- ((a & 0x0700) << 4);
- scr_writew(a, q);
- q++;
+ u16 *q = p;
+ int cnt = row_count;
+ u16 a;
+
+ if (!vc->vc_can_do_color) {
+ while (cnt--) {
+ a = scr_readw(q);
+ a ^= 0x0800;
+ scr_writew(a, q);
+ q++;
+ }
+ } else if (vc->vc_hi_font_mask == 0x100) {
+ while (cnt--) {
+ a = scr_readw(q);
+ a = (a & 0x11ff) |
+ ((a & 0xe000) >> 4) |
+ ((a & 0x0e00) << 4);
+ scr_writew(a, q);
+ q++;
+ }
+ } else {
+ while (cnt--) {
+ a = scr_readw(q);
+ a = (a & 0x88ff) |
+ ((a & 0x7000) >> 4) |
+ ((a & 0x0700) << 4);
+ scr_writew(a, q);
+ q++;
+ }
}
}
- }
- if (con_should_update(vc))
- do_update_region(vc, (unsigned long) p, count);
+ if (con_should_update(vc))
+ do_update_region(vc, (unsigned long) p, row_count);
+ row_offset += 2 * row_count;
+ count -= row_count;
+ row_count = (count >= vc->vc_cols)
+ ? vc->vc_cols
+ : count;
+ }
notify_update(vc);
}
@@ -875,8 +1204,17 @@ static void set_origin(struct vc_data *vc)
WARN_CONSOLE_UNLOCKED();
if (!con_is_visible(vc) ||
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ (
+ !concon_set_origin (vc) &&
+ (
+#endif
!vc->vc_sw->con_set_origin ||
- !vc->vc_sw->con_set_origin(vc))
+ !vc->vc_sw->con_set_origin(vc)
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ ))
+#endif
+ )
vc->vc_origin = (unsigned long)vc->vc_screenbuf;
vc->vc_visible_origin = vc->vc_origin;
vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
@@ -900,14 +1238,19 @@ static void flush_scrollback(struct vc_data *vc)
if (!con_is_visible(vc))
return;
+ hide_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+ vc->vc_softback_buf;
+#else
/*
* The legacy way for flushing the scrollback buffer is to use a side
* effect of the con_switch method. We do it only on the foreground
* console as background consoles have no scrollback buffers in that
* case and we obviously don't want to switch to them.
*/
- hide_cursor(vc);
vc->vc_sw->con_switch(vc);
+#endif
set_cursor(vc);
}
@@ -934,7 +1277,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
if (!vc) {
/* strange ... */
- /* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
return;
}
@@ -949,7 +1291,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
hide_cursor(old_vc);
if (!con_is_visible(old_vc)) {
save_screen(old_vc);
- set_origin(old_vc);
}
if (tty0dev)
sysfs_notify(&tty0dev->kobj, NULL, "active");
@@ -962,7 +1303,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
bool update;
int old_was_color = vc->vc_can_do_color;
- set_origin(vc);
update = vc->vc_sw->con_switch(vc);
set_palette(vc);
/*
@@ -977,9 +1317,19 @@ void redraw_screen(struct vc_data *vc, int is_switch)
}
if (update && vc->vc_mode != KD_GRAPHICS)
- do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+ do_update_region(vc,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ vc->vc_softback_lines
+ ? vc->vc_softback_curr
+ :
+#endif
+ vc->vc_origin,
+ vc->vc_screenbuf_size / 2);
}
- set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (!vc->vc_softback_lines)
+#endif
+ set_cursor(vc);
if (is_switch) {
vt_set_leds_compute_shiftstate();
notify_update(vc);
@@ -1100,9 +1450,23 @@ int vc_allocate(unsigned int currcons) /* return 0 on success */
global_cursor_default = 1;
vc_init(vc, 1);
+ if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows) != 0)
+ goto err_free;
vcs_make_sysfs(currcons);
atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, ¶m);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ err = -ENOMEM;
+ vc->vc_softback_buf =
+ (unsigned long)vzalloc(console_soft_scrollback_size);
+ if (!vc->vc_softback_buf)
+ goto err_free;
+ vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+ vc->vc_softback_buf;
+ vc->vc_softback_lines = 0;
+ con_update_softback(vc);
+ vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+#endif
return 0;
err_free:
visual_deinit(vc);
@@ -1123,6 +1487,152 @@ static inline int resize_screen(struct vc_data *vc, int width, int height,
return err;
}
+static int vc_copy_uniscr_to_new_area (struct vc_data *vc,
+ unsigned int old_cols,
+ unsigned int old_rows,
+ unsigned int new_cols,
+ unsigned int new_rows)
+{
+ uint32_t *old_uniscr_curr = vc->vc_uniscr_curr,
+ *old_uniscr_buf = vc->vc_uniscr_buf;
+ unsigned int old_uniscr_char_size = vc->vc_uniscr_char_size;
+ unsigned int new_lines;
+ unsigned int copy_cols;
+ uint32_t *dest, *src;
+ int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ unsigned int new_uniscr_rows;
+ unsigned int old_lines;
+ unsigned long tmp;
+
+ tmp = SOFTBACK_GAP(vc->vc_softback_in, vc->vc_softback_top);
+ old_lines = tmp / vc->vc_size_row + old_rows;
+
+ if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+ return res;
+
+ new_uniscr_rows = vc->vc_uniscr_char_size / new_cols + new_rows;
+ new_lines = min(old_lines, new_uniscr_rows);
+ copy_cols = min(old_cols, new_cols);
+
+ dest = vc->vc_uniscr_curr;
+ if (new_lines > old_rows) {
+ dest -= (new_lines - old_rows) * new_cols;
+ while (dest < vc->vc_uniscr_buf) /* Could happen twice. */
+ dest += vc->vc_uniscr_char_size;
+ }
+ src = old_uniscr_curr;
+ if (new_lines > old_rows) {
+ src -= (new_lines - old_rows) * old_cols;
+ while (src < old_uniscr_buf)
+ src += old_uniscr_char_size;
+ }
+#else
+ if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+ return res;
+
+ new_lines = min(old_rows, new_rows);
+ copy_cols = min(old_cols, new_cols);
+ dest = vc->vc_uniscr_curr;
+ src = old_uniscr_curr;
+#endif
+ if (old_uniscr_buf) {
+ while (new_lines--) {
+ memcpy(dest, src, copy_cols * sizeof(uint32_t));
+ if (new_cols > old_cols)
+ memset32(dest + old_cols, ' ',
+ new_cols - old_cols);
+ UNISCR_PLUS(dest, new_cols);
+ src += old_cols;
+ if (src >= old_uniscr_buf + old_uniscr_char_size)
+ src -= old_uniscr_char_size;
+ }
+ kvfree(old_uniscr_buf);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static int vc_copy_scrollback_to_new_area(
+ struct vc_data *vc, unsigned int old_cols,
+ unsigned int new_cols)
+{
+ unsigned long old_softback_buf, new_softback_buf;
+ unsigned long old_softback_end, new_softback_end;
+ unsigned long old_origin, new_origin;
+ unsigned long rlth, rrem; /* why long?? 2026-05-06 */
+ unsigned long tmp;
+ unsigned int old_scrolled_rows, new_scrolled_rows;
+ unsigned int old_size_row = old_cols * 2;
+ unsigned int new_size_row = new_cols * 2;
+ unsigned int copied_scrolled_rows, count;
+ void *temp_new_softback_buf;
+ unsigned short *d;
+
+ /* concon_set_origin(vc); */
+ old_softback_buf = vc->vc_softback_buf;
+ old_softback_end = vc->vc_softback_end;
+ /* vc->vc_softback_size = console_soft_scrollback_size; */
+ temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+ new_softback_buf = (unsigned long)temp_new_softback_buf;
+ if (!new_softback_buf)
+ return -ENOMEM;
+ new_softback_end = new_softback_buf + console_soft_scrollback_size;
+ d = (unsigned short *)new_softback_buf;
+ while (d != (u16 *)new_softback_end) {
+ scr_writew (0x0020, d);
+ d++;
+ }
+ tmp = SOFTBACK_GAP(vc->vc_softback_in, vc->vc_softback_top);
+ old_scrolled_rows = tmp / old_size_row;
+ new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+ copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+
+ rlth = min(old_size_row, new_size_row);
+ rrem = new_size_row - rlth;
+
+ new_origin = new_softback_buf;
+ if (copied_scrolled_rows) {
+ old_origin = vc->vc_softback_in;
+ SOFTBACK_MINUS(old_origin, copied_scrolled_rows * old_size_row);
+ count = copied_scrolled_rows;
+
+ while (count--) {
+ scr_memcpyw((unsigned short *) new_origin,
+ (unsigned short *) old_origin, rlth);
+ if (rrem)
+ scr_memsetw((void *)(new_origin + rlth),
+ vc->vc_video_erase_char, rrem);
+ old_origin += old_size_row;
+ if (old_origin >= old_softback_end)
+ old_origin -= old_softback_end
+ - old_softback_buf;
+ new_origin += new_size_row;
+ }
+ }
+
+ vc->vc_softback_buf = new_softback_buf;
+ vc->vc_softback_end = new_softback_buf
+ + new_scrolled_rows * new_size_row;
+ if (copied_scrolled_rows) {
+ if (new_origin >= vc->vc_softback_end)
+ new_origin -= vc->vc_softback_end - vc->vc_softback_buf;
+ vc->vc_softback_in = new_origin;
+ } else
+ vc->vc_softback_in = new_softback_buf;
+ vc->vc_softback_top = new_softback_buf;
+ if (copied_scrolled_rows
+ && (new_origin == new_softback_buf))
+ vc->vc_softback_top += new_size_row;
+ vc->vc_softback_curr = vc->vc_softback_in;
+ vc->vc_softback_lines = 0; /* Probably redundant. */
+ kvfree((void *)old_softback_buf);
+
+ return 0;
+}
+#endif
+
/**
* vc_do_resize - resizing method for the tty
* @tty: tty being resized
@@ -1145,14 +1655,15 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
unsigned long end;
unsigned int old_rows, old_row_size, first_copied_row;
unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+ unsigned int old_cols;
unsigned short *oldscreen, *newscreen;
- u32 **new_uniscr = NULL;
-
+ uint32_t *old_uniscr = vc->vc_uniscr_buf;
WARN_CONSOLE_UNLOCKED();
if (cols > VC_MAXCOL || lines > VC_MAXROW)
return -EINVAL;
+ old_cols = vc->vc_cols;
new_cols = (cols ? cols : vc->vc_cols);
new_rows = (lines ? lines : vc->vc_rows);
new_row_size = new_cols << 1;
@@ -1184,14 +1695,6 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
if (!newscreen)
return -ENOMEM;
- if (vc->vc_uni_lines) {
- new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
- if (!new_uniscr) {
- kfree(newscreen);
- return -ENOMEM;
- }
- }
-
if (vc_is_sel(vc))
clear_selection();
@@ -1201,15 +1704,11 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
err = resize_screen(vc, new_cols, new_rows, from_user);
if (err) {
kfree(newscreen);
- vc_uniscr_free(new_uniscr);
+ vc_uniscr_free(vc);
+ vc->vc_uniscr_buf = old_uniscr;
return err;
}
- vc->vc_rows = new_rows;
- vc->vc_cols = new_cols;
- vc->vc_size_row = new_row_size;
- vc->vc_screenbuf_size = new_screen_size;
-
rlth = min(old_row_size, new_row_size);
rrem = new_row_size - rlth;
old_origin = vc->vc_origin;
@@ -1217,13 +1716,27 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
new_scr_end = new_origin + new_screen_size;
if (vc->state.y > new_rows) {
- if (old_rows - vc->state.y < new_rows) {
+ if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+ if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ con_softback_note(vc, vc->vc_top,
+ old_rows - new_rows - vc->vc_top);
+#endif
+ vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+ old_rows - new_rows - vc->vc_top);
+ }
/*
* Cursor near the bottom, copy contents from the
* bottom of buffer
*/
first_copied_row = (old_rows - new_rows);
} else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ con_softback_note(vc, vc->vc_top,
+ vc->state.y - new_rows/2);
+#endif
+ vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+ vc->state.y - new_rows/2);
/*
* Cursor is in no man's land, copy 1/2 screenful
* from the top and bottom of cursor position
@@ -1235,10 +1748,14 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
first_copied_row = 0;
end = old_origin + old_row_size * min(old_rows, new_rows);
- vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
- vc->vc_uni_lines, rlth/2, first_copied_row,
- min(old_rows, new_rows));
- vc_uniscr_set(vc, new_uniscr);
+ if ((err = vc_copy_uniscr_to_new_area(vc, vc->vc_cols, vc->vc_rows,
+ new_cols, new_rows)) != 0)
+ return err;
+
+ vc->vc_cols = new_cols;
+ vc->vc_rows = new_rows;
+ vc->vc_size_row = new_row_size;
+ vc->vc_screenbuf_size = new_screen_size;
update_attr(vc);
@@ -1254,6 +1771,12 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
if (new_scr_end > new_origin)
scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
new_scr_end - new_origin);
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if ((err = vc_copy_scrollback_to_new_area(vc, old_cols, vc->vc_cols))
+ != 0)
+ return err;
+#endif
oldscreen = vc->vc_screenbuf;
vc->vc_screenbuf = newscreen;
vc->vc_screenbuf_size = new_screen_size;
@@ -1338,15 +1861,15 @@ struct vc_data *vc_deallocate(unsigned int currcons)
visual_deinit(vc);
con_free_unimap(vc);
put_pid(vc->vt_pid);
- vc_uniscr_set(vc, NULL);
kfree(vc->vc_screenbuf);
+ vc->vc_uniscr_buf = NULL;
vc_cons[currcons].d = NULL;
if (vc->vc_saved_screen != NULL) {
kfree(vc->vc_saved_screen);
vc->vc_saved_screen = NULL;
}
- vc_uniscr_free(vc->vc_saved_uni_lines);
- vc->vc_saved_uni_lines = NULL;
+ vc_uniscr_free(vc);
+ vc->vc_uniscr_buf = NULL;
}
return vc;
}
@@ -1892,8 +2415,29 @@ static void enter_alt_screen(struct vc_data *vc)
vc->vc_saved_screen = kmemdup((u16 *)vc->vc_origin, size, GFP_KERNEL);
if (vc->vc_saved_screen == NULL)
return;
- vc->vc_saved_uni_lines = vc->vc_uni_lines;
- vc->vc_uni_lines = NULL;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ vc->vc_saved_softback_buf = (unsigned long)
+ kmemdup((u16 *)vc->vc_softback_buf,
+ console_soft_scrollback_size, GFP_KERNEL);
+ if (!vc->vc_saved_softback_buf) {
+ kfree(vc->vc_saved_screen);
+ vc->vc_saved_screen = NULL;
+ return;
+ }
+ vc->vc_saved_softback_end_offset = vc->vc_softback_end -
+ vc->vc_softback_buf;
+ vc->vc_saved_softback_in_offset = vc->vc_softback_in -
+ vc->vc_softback_buf;
+ vc->vc_saved_softback_top_offset = vc->vc_softback_top -
+ vc->vc_softback_buf;
+ vc->vc_saved_softback_curr_offset = vc->vc_softback_curr -
+ vc->vc_softback_buf;
+#endif
+ vc->vc_saved_uniscr_curr = vc->vc_uniscr_curr;
+ vc->vc_saved_uniscr_buf = vc->vc_uniscr_buf;
+ vc->vc_saved_uniscr_char_size = vc->vc_uniscr_char_size;
+ vc->vc_uniscr_buf = NULL;
+ vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
vc->vc_saved_rows = vc->vc_rows;
vc->vc_saved_cols = vc->vc_cols;
save_cur(vc);
@@ -1915,6 +2459,24 @@ static void leave_alt_screen(struct vc_data *vc)
dest = ((u16 *)vc->vc_origin) + r * vc->vc_cols;
memcpy(dest, src, 2 * cols);
}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ memcpy((u16 *)vc->vc_softback_buf,
+ (u16 *)vc->vc_saved_softback_buf,
+ console_soft_scrollback_size);
+ kfree((void *)vc->vc_saved_softback_buf);
+ vc->vc_saved_softback_buf = 0;
+ vc->vc_softback_end = vc->vc_saved_softback_end_offset +
+ vc->vc_softback_buf;
+ vc->vc_softback_in = vc->vc_saved_softback_in_offset +
+ vc->vc_softback_buf;
+ vc->vc_softback_top = vc->vc_saved_softback_top_offset +
+ vc->vc_softback_buf;
+ vc->vc_softback_curr = vc->vc_saved_softback_curr_offset +
+ vc->vc_softback_buf;
+#endif
+ vc->vc_uniscr_buf = vc->vc_saved_uniscr_buf;
+ vc->vc_uniscr_curr = vc->vc_saved_uniscr_curr;
+ vc->vc_uniscr_char_size = vc->vc_saved_uniscr_char_size;
/*
* If the console was resized while in the alternate screen,
* resize the saved unicode buffer to the current dimensions.
@@ -1922,19 +2484,24 @@ static void leave_alt_screen(struct vc_data *vc)
* buffer to be freed and vc_uni_lines to be lazily rebuilt
* via vc_uniscr_check() when next needed.
*/
- if (vc->vc_saved_uni_lines &&
- (vc->vc_saved_rows != vc->vc_rows ||
+ if ((vc->vc_saved_rows != vc->vc_rows ||
vc->vc_saved_cols != vc->vc_cols)) {
- u32 **new_uniscr = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-
- if (new_uniscr)
- vc_uniscr_copy_area(new_uniscr, vc->vc_cols, vc->vc_rows,
- vc->vc_saved_uni_lines, cols, 0, rows);
- vc_uniscr_free(vc->vc_saved_uni_lines);
- vc->vc_saved_uni_lines = new_uniscr;
+ if (vc_copy_uniscr_to_new_area(
+ vc, vc->vc_saved_cols, vc->vc_saved_rows,
+ vc->vc_cols, vc->vc_rows)) {
+ kfree(vc->vc_uniscr_buf);
+ vc->vc_uniscr_buf = NULL;
+ return;
+ }
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (vc_copy_scrollback_to_new_area (vc, vc->vc_saved_cols,
+ vc->vc_cols)) {
+ kfree((u16 *)vc->vc_softback_buf);
+ vc->vc_softback_buf = 0;
+ return;
+ }
+#endif
}
- vc_uniscr_set(vc, vc->vc_saved_uni_lines);
- vc->vc_saved_uni_lines = NULL;
restore_cur(vc);
/* Update the entire screen */
if (con_should_update(vc))
@@ -2257,8 +2824,7 @@ static void reset_terminal(struct vc_data *vc, int do_clear)
if (vc->vc_saved_screen != NULL) {
kfree(vc->vc_saved_screen);
vc->vc_saved_screen = NULL;
- vc_uniscr_free(vc->vc_saved_uni_lines);
- vc->vc_saved_uni_lines = NULL;
+ vc_uniscr_free(vc);
vc->vc_saved_rows = 0;
vc->vc_saved_cols = 0;
}
@@ -3231,6 +3797,12 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
param.vc = vc;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ /* Undo any soft scrolling - <Alt><Fn> and <Shift><PgUp/Down> do
+ not pass through this function. */
+ concon_set_origin (vc);
+#endif
+
while (!tty->flow.stopped && count) {
u8 orig = *buf;
buf++;
@@ -3444,7 +4016,11 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
}
if (cnt && con_is_visible(vc))
vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
- set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (!vc->vc_softback_lines)
+#endif
+ set_cursor(vc);
+
notify_update(vc);
quit:
@@ -3658,7 +4234,10 @@ static void con_flush_chars(struct tty_struct *tty)
return;
guard(console_lock)();
- set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (!vc->vc_softback_lines)
+#endif
+ set_cursor(vc);
}
/*
@@ -3677,6 +4256,14 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
vc = vc_cons[currcons].d;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ con_update_softback(vc);
+ if (!vc->vc_uniscr_buf)
+ ret = vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+ if (ret)
+ return ret;
+#endif
+
/* Still being freed */
if (vc->port.tty)
return -ERESTARTSYS;
@@ -3817,6 +4404,7 @@ static int __init con_init(void)
/* Assuming vc->vc_{cols,rows,screenbuf_size} are sane here. */
vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
vc_init(vc, currcons || !vc->vc_sw->con_save_screen);
+ vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
}
currcons = fg_console = 0;
master_display_fg = vc = vc_cons[currcons].d;
@@ -4712,7 +5300,10 @@ void do_unblank_screen(int leaving_gfx)
if (console_blank_hook)
console_blank_hook(0);
set_palette(vc);
- set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ if (!vc->vc_softback_lines)
+#endif
+ set_cursor(vc);
vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
notify_update(vc);
}
@@ -5013,12 +5604,14 @@ EXPORT_SYMBOL_GPL(screen_glyph);
u32 screen_glyph_unicode(const struct vc_data *vc, int n)
{
- u32 **uni_lines = vc->vc_uni_lines;
+ int y = n / vc->vc_cols, x = n % vc->vc_cols;
+ uint32_t *ln = vc->vc_uniscr_curr;
- if (uni_lines)
- return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
-
- return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+ if (vc->vc_uniscr_curr) {
+ UNISCR_PLUS(ln, y * vc->vc_cols);
+ return ln[x];
+ }
+ return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
}
EXPORT_SYMBOL_GPL(screen_glyph_unicode);
@@ -5052,6 +5645,10 @@ u16 vcs_scr_readw(const struct vc_data *vc, const u16 *org)
void vcs_scr_writew(struct vc_data *vc, u16 val, u16 *org)
{
+ unsigned ch = inverse_translate (vc, val & (0xff | vc->vc_hi_font_mask),
+ true);
+ if (ch)
+ vc_uniscr_putc(vc, ch);
scr_writew(val, org);
if ((unsigned long)org == vc->vc_pos) {
softcursor_original = -1;
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index 12f54480f57f..56be84b054e0 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -100,6 +100,44 @@ config FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
If unsure, select n.
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ bool "Enable Scrollback Buffer in System RAM"
+ depends on FB=y && FRAMEBUFFER_CONSOLE
+ default y
+ help
+ This option creates a scrollback buffer for each framebuffer console.
+ These buffers are allocated dynamically during initialisation.
+
+ If you want this feature, say 'Y' here and enter in
+ FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+ allocate for each buffer. If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+ int "Scrollback Buffer Size (in KB)"
+ depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ range 1 1024
+ default "128"
+ help
+ Enter the amount of System RAM in kilobytes to allocate for the
+ scrollback buffer of each framebuffer console. Each character
+ position on the video takes 2 bytes of storage. 128kB will give you
+ approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ bool "Enable a working GPM for scrolled back scrollback buffer in System RAM"
+ depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ default y
+ help
+ This option buffers up Unicode characters corresponding to the glyphs
+ displayed by the scrollback buffer. This enables the GPM mouse driver
+ (or similar) to copy characters from a scrolled back buffer.
+
+ A buffer is created for each framebuffer console, this buffer being
+ approximately twice as big as the buffer size specified in
+ FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+ If unsure, say 'Y'.
+
config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
bool "Map the console to the primary display device"
depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7453377f3433..c8659a89c7b8 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -639,6 +639,28 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
erase,
vc->vc_size_row * logo_lines);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ {
+ uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+ unsigned int i = vc->vc_rows - logo_lines;
+
+ UNISCR_PLUS(d, vc->vc_rows * vc->vc_cols);
+ UNISCR_PLUS(s, (vc->vc_rows - logo_lines) * vc->vc_cols);
+ while (i--) {
+ UNISCR_MINUS(d, vc->vc_cols);
+ UNISCR_MINUS(s, vc->vc_cols);
+ memcpy(d, s, vc->vc_cols * sizeof (uint32_t));
+ }
+ i = logo_lines;
+ d = vc->vc_uniscr_curr;
+ while (i--) {
+ memset32(d, ' ', vc->vc_cols);
+ UNISCR_PLUS(d, vc->vc_cols);
+ }
+ vc->vc_uniscr_curr = d;
+ }
+#endif
+
if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
fbcon_clear_margins(vc, 0);
update_screen(vc);
@@ -1290,6 +1312,25 @@ static void __fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx,
* bitmap stretched into the margin area.
*/
fbcon_clear_margins(vc, 0);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+ {
+ uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+ unsigned int i = vc->vc_rows - logo_lines;
+
+ UNISCR_PLUS(d, (vc->vc_rows - logo_lines) * vc->vc_cols);
+ UNISCR_PLUS(s, (vc->vc_rows - 2 * logo_lines) * vc->vc_cols);
+ while (i--) {
+ UNISCR_MINUS(d, vc->vc_cols);
+ UNISCR_MINUS(s, vc->vc_cols);
+ memcpy (d, s, vc->vc_cols * sizeof (uint32_t));
+ }
+ i = logo_lines;
+ while (i--) {
+ UNISCR_MINUS(d, vc->vc_cols);
+ memset32(d, ' ', vc->vc_cols);
+ }
+ }
+#endif
}
fg = get_color(vc, info, vc->vc_video_erase_char, 1);
@@ -2097,6 +2138,7 @@ static bool fbcon_switch(struct vc_data *vc)
struct fbcon_ops *ops;
struct fbcon_display *p = &fb_display[vc->vc_num];
struct fb_var_screeninfo var;
+ unsigned short *d, *s;
int i, ret, prev_console;
info = fbcon_info_from_console(vc->vc_num);
@@ -2106,8 +2148,21 @@ static bool fbcon_switch(struct vc_data *vc)
struct vc_data *conp2 = vc_cons[logo_shown].d;
if (conp2->vc_top == logo_lines
- && conp2->vc_bottom == conp2->vc_rows)
+ && conp2->vc_bottom == conp2->vc_rows) {
+ /* Scroll the bottom part of the screen up to fill the
+ * logo lines. */
+ i = conp2->vc_bottom - conp2->vc_top;
+ d = (unsigned short *)conp2->vc_origin;
+ s = (unsigned short *)(conp2->vc_origin + logo_lines * conp2->vc_size_row);
+ while (i--) {
+ scr_memcpyw(d, s, conp2->vc_size_row);
+ d += conp2->vc_cols;
+ s += conp2->vc_cols;
+ }
+ scr_memsetw(d, conp2->vc_video_erase_char,
+ conp2->vc_size_row * logo_lines);
conp2->vc_top = 0;
+ }
logo_shown = FBCON_LOGO_CANSHOW;
}
@@ -3200,6 +3255,9 @@ static const struct consw fb_con = {
.con_font_get = fbcon_get_font,
.con_font_default = fbcon_set_def_font,
.con_set_palette = fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ .con_scrolldelta = concon_scrolldelta,
+#endif
.con_invert_region = fbcon_invert_region,
.con_resize = fbcon_resize,
.con_debug_enter = fbcon_debug_enter,
diff --git a/include/linux/console_struct.h.orig b/include/linux/console_struct.h
index d5ca855..c164bec 100755
--- a/include/linux/console_struct.h.orig
+++ b/include/linux/console_struct.h
@@ -109,6 +109,22 @@ struct vc_data {
unsigned short *vc_screenbuf; /* In-memory character/attribute buffer */
unsigned int vc_screenbuf_size;
unsigned char vc_mode; /* KD_TEXT, ... */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+ unsigned long vc_softback_buf; /* Address of scrollback buffer. */
+ unsigned long vc_softback_end; /* (Just past) end of buffer. */
+ unsigned long vc_softback_in; /* Head pointer into circular buffer. */
+ unsigned long vc_softback_top; /* Tail pointer into circular buffer. */
+ unsigned long vc_softback_curr; /* Pos in vc_screenbuf or vc_softback_buf
+ corresponding to visible screen. */
+ int vc_softback_lines; /* Number of lines currently scrolled. */
+ unsigned short vc_char_at_pos; /* Char at vc_pos when no soft scroll */
+
+ unsigned long vc_saved_softback_buf;
+ unsigned long vc_saved_softback_end_offset;
+ unsigned long vc_saved_softback_in_offset;
+ unsigned long vc_saved_softback_top_offset;
+ unsigned long vc_saved_softback_curr_offset;
+#endif
/* attributes for all characters on screen */
unsigned char vc_attr; /* Current attributes */
unsigned char vc_def_color; /* Default colors */
@@ -158,11 +174,17 @@ struct vc_data {
struct vc_data **vc_display_fg; /* [!] Ptr to var holding fg console for this display */
struct uni_pagedict *uni_pagedict;
struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict variable for this console */
- u32 **vc_uni_lines; /* unicode screen content */
+ /* u32 **vc_uni_lines; /\* unicode screen content *\/ */
u16 *vc_saved_screen;
- u32 **vc_saved_uni_lines;
+ /* u32 **vc_saved_uni_lines; */
unsigned int vc_saved_cols;
unsigned int vc_saved_rows;
+ uint32_t *vc_uniscr_buf; /* Address of unicode screen content */
+ uint32_t *vc_saved_uniscr_buf;
+ unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit chars */
+ unsigned int vc_saved_uniscr_char_size;
+ uint32_t *vc_uniscr_curr; /* Pos of first char of (unscrolled) screen */
+ uint32_t *vc_saved_uniscr_curr;
/* additional information is in vt_kern.h */
};
@@ -197,4 +219,48 @@ extern void vc_SAK(struct work_struct *work);
bool con_is_visible(const struct vc_data *vc);
+/* Macros for wraparound in the softback buffer. POS must be an unsigned long
+ * representing an address within the confines of vc->vc_softback_buf and
+ * vc->vc_softback_end. OFFSET must be a positive distance measured in bytes,
+ * which is small enough when added to POS won't be too far outside the buffer.
+ */
+#define SOFTBACK_PLUS(pos,offset) \
+ do { \
+ pos += offset; \
+ if (pos >= vc->vc_softback_end) \
+ pos -= (vc->vc_softback_end - vc->vc_softback_buf); \
+ } while (0)
+#define SOFTBACK_MINUS(pos,offset) \
+ do { \
+ pos -= offset; \
+ if (pos < vc->vc_softback_buf) \
+ pos += (vc->vc_softback_end - vc->vc_softback_buf); \
+ } while (0)
+#define SOFTBACK_GAP(big,little) \
+ ((big >= little) \
+ ? (big - little) \
+ : (big - little + (vc->vc_softback_end - vc->vc_softback_buf)))
+#define SOFTBACK_WITHIN(pos,low,high) \
+ (((low) <= (high)) \
+ ? ((pos) >= (low) && (pos) < (high)) \
+ : ((pos) >= (low) || (pos) < (high)))
+
+/* Macros for wraparound in the uniscr buffer. POS must be a uint32_t pointer
+ * variable which is expected to be within the confines of vc->vc_uniscr_buf
+ * and vc->vc_uniscr_buf + vc->vc_uniscr_char_size. OFFSET must be a positive
+ * distance measured in uint32_t's, which when added to or subtracted from POS
+ * won't be too far away from the buffer. */
+#define UNISCR_PLUS(pos,offset) \
+ do { \
+ pos += offset; \
+ if (pos >= vc->vc_uniscr_buf + vc->vc_uniscr_char_size) \
+ pos -= vc->vc_uniscr_char_size; \
+ } while (0)
+#define UNISCR_MINUS(pos,offset) \
+ do { \
+ pos -= offset; \
+ if (pos < vc->vc_uniscr_buf) \
+ pos += vc->vc_uniscr_char_size; \
+ } while (0)
+
#endif /* _LINUX_CONSOLE_STRUCT_H */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index d008c3d0a9bb..14a436e8dd3d 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -121,6 +121,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
/* vt.c */
void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
void change_console(struct vc_data *new_vc);
void reset_vc(struct vc_data *vc);
int do_unbind_con_driver(const struct consw *csw, int first, int last,
--RU1IxUBZOAXVxHh3--