Hello, Peter.

On Mon, Mar 11, 2024 at 10:47:43 +0000, Peter Humphrey wrote:
> On Wednesday, 24 January 2024 12:20:29 GMT Alan Mackenzie wrote:
> > Hello, Gentoo.

> > On Wed, Jan 24, 2024 at 10:00:37 +0000, Alan Mackenzie wrote:

> > [ .... ]

> > Please note the corrected subject line.  This version of the soft
> > scrolling patch is for kernel 6.6.13, or thereabouts.

> It works well here, Alan, up to kernel version 6.7.9, but one of the 15 or so 
> new kernel parameters (since 6.7.8) seems to cause it to fail.

> I've attached the reject file, /usr/src/linux-6.8.0-gentoo/drivers/tty/vt/
> vt.c.rej.

Thanks!

I've had a look at this, now.  The 6.6.13 patch appears to work on 6.7.x
(I tested it on 6.7.10 this morning).

For 6.8.1, a new patch is needed, see below.  Remember, to apply it, you
need smething like:

    $ patch -p1 < 6.8.1-GPS.20240402.diff

..  Have fun!

> -- 
> Regards,
> Peter.


diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 8967c3a0d916..f40ebbfb87de 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -217,7 +217,8 @@ static int vc_selection_store_chars(struct vc_data *vc, 
bool unicode)
                           unless non-space at end of line. */
                        if (obp != bp) {
                                bp = obp;
-                               *bp++ = '\r';
+                               if ((i + 2) < vc_sel.end) /* Don't add \r to 
the last line. */
+                                       *bp++ = '\r';
                        }
                        obp = bp;
                }
@@ -263,6 +264,11 @@ static int vc_do_selection(struct vc_data *vc, unsigned 
short mode, int ps,
                new_sel_start = rounddown(ps, vc->vc_size_row);
                new_sel_end = rounddown(pe, vc->vc_size_row) +
                        vc->vc_size_row - 2;
+               while ((new_sel_end > pe)
+                      && (is_space_on_vt (sel_pos (new_sel_end, unicode))))
+                       new_sel_end -= 2;
+               if (!((new_sel_end) % vc->vc_size_row))
+                       new_sel_end += 2;
                break;
        case TIOCL_SELPOINTER:
                highlight_pointer(pe);
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 38a765eadbe2..dd012e99e797 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -134,6 +134,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,8 +294,31 @@ static inline bool con_should_update(const struct vc_data 
*vc)
 static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
                bool viewed)
 {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       unsigned long softback_pos, scrolled_expanse;
+
+       if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+               return (unsigned short *)(vc->vc_origin + offset);
+       else {
+               if (vc->vc_softback_in >= vc->vc_softback_curr)
+                       scrolled_expanse = vc->vc_softback_in - 
vc->vc_softback_curr;
+               else
+                       scrolled_expanse = vc->vc_softback_in - 
vc->vc_softback_curr
+                               + vc->vc_softback_end - vc->vc_softback_buf;
+               if (offset >= scrolled_expanse)
+                       return (unsigned short *)(vc->vc_origin
+                                                 + (offset - 
scrolled_expanse));
+               else {
+                       softback_pos = vc->vc_softback_curr + offset;
+                       if (softback_pos >= vc->vc_softback_end)
+                               softback_pos -= vc->vc_softback_end
+                                       - vc->vc_softback_buf;
+               }
+       }
+       return (unsigned short *)softback_pos;
+#else
        unsigned short *p;
-       
+
        if (!viewed)
                p = (unsigned short *)(vc->vc_origin + offset);
        else if (!vc->vc_sw->con_screen_pos)
@@ -298,6 +326,7 @@ static inline unsigned short *screenpos(const struct 
vc_data *vc, int offset,
        else
                p = vc->vc_sw->con_screen_pos(vc, offset);
        return p;
+#endif
 }
 
 /* Called  from the keyboard irq path.. */
@@ -319,107 +348,111 @@ 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;
-
-       /* initial line pointers */
-       p = uni_lines + rows;
-       for (i = 0; i < rows; i++) {
-               uni_lines[i] = p;
-               p += col_size;
-       }
+       uint32_t *p;
+       unsigned int new_size;  /* In 32-bit characters */
 
-       return uni_lines;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(u32 **uni_lines)
-{
-       vfree(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_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-       vc_uniscr_free(vc->vc_uni_lines);
-       vc->vc_uni_lines = new_uni_lines;
+       kvfree(vc->vc_uniscr_buf);
+       vc->vc_uniscr_buf = NULL;
 }
 
 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;
+       uint32_t *pos;
+
+       if (vc->vc_uniscr_buf) {
+               pos = vc->vc_uniscr_curr;
+               UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.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_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;
        }
 }
 
@@ -427,49 +460,52 @@ 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 (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);
-       }
-}
-
-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];
-
-               memset32(dst_line, ' ', dst_cols);
-               dst_row++;
+       if (vc->vc_uniscr_buf) {
+               unsigned int cols = vc->vc_cols;
+               unsigned int sz, /* number of rows being scrolled */
+                       d,                /* number of rows needing blanking */
+                       clear;            /* The number of the topmost row 
needing blanking. */
+               uint32_t *dest, *src;
+               unsigned int i;
+
+               if (dir == SM_UP /* && top == 0 */&& bottom == vc->vc_rows) {
+                       UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+                       clear = vc->vc_rows - nr;
+               } else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows 
- nr) {
+                       UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+               clear = top;
+               } else if (dir == SM_UP) {
+                       sz = bottom - top;
+                       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);
+                       }
+                       d = nr;
+                       clear = bottom - nr;
+               } 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));
+                       }
+                       d = nr;
+                       clear = top;
+               }
+               if (d)
+                       vc_uniscr_clear_lines(vc, clear, nr);
        }
 }
 
@@ -481,7 +517,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;
 
@@ -490,11 +525,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;
 
        /*
@@ -506,15 +540,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;
 }
 
@@ -526,13 +559,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) {
                /*
@@ -542,24 +586,233 @@ 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 + delta * vc->vc_size_row;
+       vc->vc_softback_lines -= delta;
+       if (delta < 0) {
+               if (vc->vc_softback_curr < vc->vc_softback_top
+                   && n < vc->vc_softback_buf) {
+                       n += vc->vc_softback_end - vc->vc_softback_buf;
+                       if (n < vc->vc_softback_top) {
+                               vc->vc_softback_lines -=
+                                   (vc->vc_softback_top - n) / vc->vc_size_row;
+                               n = vc->vc_softback_top;
+                       }
+               } else if (vc->vc_softback_curr >= vc->vc_softback_top
+                          && n < vc->vc_softback_top) {
+                       vc->vc_softback_lines -=
+                           (vc->vc_softback_top - n) / vc->vc_size_row;
+                       n = vc->vc_softback_top;
+               }
+       } else {
+               if (vc->vc_softback_curr > vc->vc_softback_in
+                   && n >= vc->vc_softback_end) {
+                       n += vc->vc_softback_buf - vc->vc_softback_end;
+                       if (n > vc->vc_softback_in) {
+                               n = vc->vc_softback_in;
+                               vc->vc_softback_lines = 0;
+                       }
+               } else if (vc->vc_softback_curr <= vc->vc_softback_in
+                          && n > vc->vc_softback_in) {
+                       n = vc->vc_softback_in;
+                       vc->vc_softback_lines = 0;
                }
        }
+       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);
+               vc->vc_softback_in += vc->vc_size_row;
+               if (vc->vc_softback_in == vc->vc_softback_end)
+                       vc->vc_softback_in = vc->vc_softback_buf;
+               if (vc->vc_softback_in == vc->vc_softback_top) {
+                       vc->vc_softback_top += vc->vc_size_row;
+                       if (vc->vc_softback_top == vc->vc_softback_end)
+                               vc->vc_softback_top = vc->vc_softback_buf;
+               }
+       }
+       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, CM_ERASE /* | CM_SOFTBACK */);
+               con_redraw_softback(vc, /* disp, */ lines);
+               if (!vc->vc_softback_lines)
+                       vc->vc_sw->con_cursor(vc, 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)
@@ -571,6 +824,10 @@ static void con_scroll(struct vc_data *vc, unsigned int 
top,
                nr = rows - 1;
        if (bottom > vc->vc_rows || top >= bottom || nr < 1)
                return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (dir == SM_UP && vc->vc_softback_top)
+               con_softback_note (vc, top, nr);
+#endif
 
        vc_uniscr_scroll(vc, top, bottom, dir, nr);
        if (con_is_visible(vc) &&
@@ -588,6 +845,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;
@@ -631,6 +935,7 @@ static void do_update_region(struct vc_data *vc, unsigned 
long start, int count)
                }
        }
 }
+#endif
 
 void update_region(struct vc_data *vc, unsigned long start, int count)
 {
@@ -639,7 +944,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);
@@ -701,51 +1009,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)
 {
        unsigned short *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 +1200,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;
@@ -937,7 +1271,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;
        }
 
@@ -952,7 +1285,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");
@@ -965,7 +1297,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
                int update;
                int old_was_color = vc->vc_can_do_color;
 
-               set_origin(vc);
                update = vc->vc_sw->con_switch(vc);
                set_palette(vc);
                /*
@@ -980,9 +1311,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);
@@ -1061,7 +1402,6 @@ int vc_allocate(unsigned int currcons)    /* return 0 on 
success */
        int err;
 
        WARN_CONSOLE_UNLOCKED();
-
        if (currcons >= MAX_NR_CONSOLES)
                return -ENXIO;
 
@@ -1103,9 +1443,24 @@ 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, &param);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       vc->vc_softback_size = console_soft_scrollback_size;
+       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);
@@ -1126,6 +1481,75 @@ 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 new_cols,
+                                      unsigned int new_rows)
+{
+       unsigned int old_rows = vc->vc_rows, old_cols = vc->vc_cols;
+        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;
+
+       if (vc->vc_softback_in >= vc->vc_softback_top)
+               tmp = vc->vc_softback_in - vc->vc_softback_top;
+       else
+               tmp = vc->vc_softback_in - vc->vc_softback_top
+                       + vc->vc_softback_end - vc->vc_softback_buf;
+       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;
+}
+
 /**
  *     vc_do_resize    -       resizing method for the tty
  *     @tty: tty being resized
@@ -1146,12 +1570,19 @@ static int vc_do_resize(struct tty_struct *tty, struct 
vc_data *vc,
 {
        unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
        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_rows, old_size_row, first_copied_row;
+       unsigned int new_cols, new_rows, new_size_row, new_screen_size;
        unsigned int user;
        unsigned short *oldscreen, *newscreen;
-       u32 **new_uniscr = NULL;
-
+       uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       unsigned short *d;
+       unsigned long old_softback_buf, new_softback_buf, tmp;
+       unsigned long old_softback_end, new_softback_end;
+       unsigned int old_scrolled_rows, new_scrolled_rows;
+       unsigned int count, copied_scrolled_rows;
+       void *temp_new_softback_buf;
+#endif
        WARN_CONSOLE_UNLOCKED();
 
        if (!vc)
@@ -1165,8 +1596,8 @@ static int vc_do_resize(struct tty_struct *tty, struct 
vc_data *vc,
 
        new_cols = (cols ? cols : vc->vc_cols);
        new_rows = (lines ? lines : vc->vc_rows);
-       new_row_size = new_cols << 1;
-       new_screen_size = new_row_size * new_rows;
+       new_size_row = new_cols << 1;
+       new_screen_size = new_size_row * new_rows;
 
        if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
                /*
@@ -1194,61 +1625,91 @@ 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();
 
        old_rows = vc->vc_rows;
-       old_row_size = vc->vc_size_row;
+       old_size_row = vc->vc_size_row;
 
        err = resize_screen(vc, new_cols, new_rows, 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;
+       rlth = min(old_size_row, new_size_row);
+       rrem = new_size_row - rlth;
        old_origin = vc->vc_origin;
        new_origin = (long) newscreen;
        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
                         */
                        first_copied_row = (vc->state.y - new_rows/2);
                }
-               old_origin += first_copied_row * old_row_size;
+               old_origin += first_copied_row * old_size_row;
        } else
                first_copied_row = 0;
-       end = old_origin + old_row_size * min(old_rows, new_rows);
+       end = old_origin + old_size_row * 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, new_cols, new_rows)) != 0)
+               return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       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++;
+       }
+       if (vc->vc_softback_top <= vc->vc_softback_in)
+               tmp = vc->vc_softback_in - vc->vc_softback_top;
+       else
+               tmp = vc->vc_softback_in - vc->vc_softback_top
+                       + vc->vc_softback_end - vc->vc_softback_buf;
+       old_scrolled_rows = tmp / vc->vc_size_row;
+       new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+       copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
+
+       vc->vc_cols = new_cols;
+       vc->vc_rows = new_rows;
+       vc->vc_size_row = new_size_row;
+       vc->vc_screenbuf_size = new_screen_size;
 
        update_attr(vc);
 
@@ -1258,17 +1719,60 @@ static int vc_do_resize(struct tty_struct *tty, struct 
vc_data *vc,
                if (rrem)
                        scr_memsetw((void *)(new_origin + rlth),
                                    vc->vc_video_erase_char, rrem);
-               old_origin += old_row_size;
-               new_origin += new_row_size;
+               old_origin += old_size_row;
+               new_origin += new_size_row;
        }
        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
+       new_origin = new_softback_buf;
+       if (copied_scrolled_rows) {
+               old_origin = vc->vc_softback_in
+                       - copied_scrolled_rows * old_size_row;
+               if (old_origin < vc->vc_softback_buf)
+                       old_origin += vc->vc_softback_end
+                               - vc->vc_softback_buf;
+               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;
+               }
+       }
+#endif
        oldscreen = vc->vc_screenbuf;
        vc->vc_screenbuf = newscreen;
        vc->vc_screenbuf_size = new_screen_size;
        set_origin(vc);
        kfree(oldscreen);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       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);
+#endif
 
        /* do part of a reset_terminal() */
        vc->vc_top = 0;
@@ -1350,8 +1854,8 @@ 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;
        }
        return vc;
@@ -1600,7 +2104,7 @@ struct rgb { u8 r; u8 g; u8 b; };
 
 static void rgb_from_256(int i, struct rgb *c)
 {
-       if (i < 8) {            /* Standard colours. */
+       if (i < 8) {        /* Standard colours. */
                c->r = i&1 ? 0xaa : 0x00;
                c->g = i&2 ? 0xaa : 0x00;
                c->b = i&4 ? 0xaa : 0x00;
@@ -1612,7 +2116,7 @@ static void rgb_from_256(int i, struct rgb *c)
                c->r = (i - 16) / 36 * 85 / 2;
                c->g = (i - 16) / 6 % 6 * 85 / 2;
                c->b = (i - 16) % 6 * 85 / 2;
-       } else                  /* Grayscale ramp. */
+       } else            /* Grayscale ramp. */
                c->r = c->g = c->b = i * 10 - 2312;
 }
 
@@ -2882,6 +3386,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) {
                int orig = *buf;
                buf++;
@@ -3047,11 +3557,8 @@ static void vt_console_print(struct console *co, const 
char *b, unsigned count)
        if (kmsg_console && vc_cons_allocated(kmsg_console - 1))
                vc = vc_cons[kmsg_console - 1].d;
 
-       if (!vc_cons_allocated(fg_console)) {
-               /* impossible */
-               /* printk("vt_console_print: tty %d not allocated ??\n", 
currcons+1); */
+       if (!vc_cons_allocated(fg_console))
                goto quit;
-       }
 
        if (vc->vc_mode != KD_TEXT)
                goto quit;
@@ -3096,7 +3603,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:
@@ -3320,7 +3831,11 @@ static void con_flush_chars(struct tty_struct *tty)
        /* if we race with con_close(), vt may be null */
        console_lock();
        vc = tty->driver_data;
-       if (vc)
+       if (vc
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+           && !vc->vc_softback_lines
+#endif
+             )
                set_cursor(vc);
        console_unlock();
 }
@@ -3341,6 +3856,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)
+               goto unlock;
+#endif
+
        /* Still being freed */
        if (vc->port.tty) {
                ret = -ERESTARTSYS;
@@ -3396,7 +3919,7 @@ static void con_cleanup(struct tty_struct *tty)
        tty_port_put(&vc->port);
 }
 
-static int default_color           = 7; /* white */
+static int default_color          = 7; /* white */
 static int default_italic_color    = 2; // green (ASCII)
 static int default_underline_color = 3; // cyan (ASCII)
 module_param_named(color, default_color, int, S_IRUGO | S_IWUSR);
@@ -3475,6 +3998,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;
@@ -4088,7 +4612,7 @@ static int do_register_con_driver(const struct consw 
*csw, int first, int last)
                        con_driver->desc = desc;
                        con_driver->node = i;
                        con_driver->flag = CON_DRIVER_FLAG_MODULE |
-                                          CON_DRIVER_FLAG_INIT;
+                                          CON_DRIVER_FLAG_INIT;
                        con_driver->first = first;
                        con_driver->last = last;
                        retval = 0;
@@ -4385,7 +4909,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);
 }
 EXPORT_SYMBOL(do_unblank_screen);
@@ -4698,12 +5225,17 @@ 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);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+               UNISCR_MINUS(ln, vc->vc_softback_lines * vc->vc_cols);
+#endif
+               return ln[x];
+       }
+       return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
 
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index bc31db6ef7d2..dc76e4852347 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -101,6 +101,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 46823c2e2ba1..e95244e01c94 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -611,6 +611,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);
@@ -1258,6 +1280,25 @@ static void fbcon_clear(struct vc_data *vc, int sy, int 
sx, int height,
                 * 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
        }
 
        /* Split blits that cross physical y_wrap boundary */
@@ -2064,6 +2105,7 @@ static int 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);
@@ -2073,8 +2115,21 @@ static int 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;
        }
 
@@ -3162,6 +3217,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_screen_pos         = fbcon_screen_pos,
        .con_getxy              = fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index 539f1cd45309..9d87a3fa6ed3 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -109,6 +109,17 @@ 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 int    vc_softback_size;       /* Size in bytes of scrollback 
buffer. */
+       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 */
+#endif
        /* attributes for all characters on screen */
        unsigned char   vc_attr;                /* Current attributes */
        unsigned char   vc_def_color;           /* Default colors */
@@ -158,7 +169,9 @@ 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 */
+       uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+       unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit 
chars */
+       uint32_t *vc_uniscr_curr;       /* Pos of first char of (unscrolled) 
screen */
        /* additional information is in vt_kern.h */
 };
 
@@ -193,4 +206,22 @@ extern void vc_SAK(struct work_struct *work);
 
 bool con_is_visible(const struct vc_data *vc);
 
+/* 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 c1f5aebef170..97ecca06eceb 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -114,6 +114,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,

Reply via email to