Hello, Peter.

I have another version of the patch which makes GPM usable on scrolled
consoles.  I'd like to think that it's now final.

On Sat, Jan 28, 2023 at 14:41:53 +0000, Peter Humphrey wrote:
> On Friday, 27 January 2023 22:31:17 GMT Alan Mackenzie wrote:
> > On Fri, Jan 27, 2023 at 12:24:41 +0000, Peter Humphrey wrote:

> --->8

> > > I've attached the reject files.
> > 
> > Thanks for these!  It looks like it'll probably be straightforward to
> > amend the patch for 6.1.8.  Are you currently running 6.1.8 as your main
> > kernel?

> Yes Alan, I'm running ~amd64, so 6.1.8 is the current kernel. I have 5.15.88 
> as a backup.

OK, I'm enclosing a patch for each version, 5.15.x and 6.1.y.  I'm not
aware of any outstanding bugs in these patches.  In particular, the
following have been fixed since the previous version:
(i) The synchronisation between the "mouse" character buffer and the
  contents of the screen is now 100%.  In particular, when changing
  fonts (e.g. at bootup), and when the Tux logos get removed from the
  screen, things continue working.
(ii) CONFIG_LOGO (i.e. the Tuxes on bootup) can now be freely configured
  without losing functionality.

Just a quick reminder of how to use these files for anybody else who
might be interested:
(i) cd /usr/src/linux-5.15.88-gentoo, or similar.  (Or
  ...-6.1.x-gentoo).
(ii) patch -p1 < 5.15.80-GPM.20230203.diff (or the other one).
(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) 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.

> -- 
> Regards,
> Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index b8f5bc19416d..5d5508209164 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];
 
 #ifndef VT_SINGLE_DRIVER
@@ -286,8 +291,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)
@@ -295,6 +323,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.. */
@@ -316,101 +345,96 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-#ifdef NO_VC_UNI_SCREEN
-/* this disables and optimizes related code away at compile time */
-#define get_vc_uniscr(vc) NULL
-#else
-#define get_vc_uniscr(vc) vc->vc_uni_screen
-#endif
-
 #define VC_UNI_SCREEN_DEBUG 0
 
 typedef uint32_t char32_t;
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
-struct uni_screen {
-       char32_t *lines[0];
-};
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static struct uni_screen *vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int 
rows)
 {
-       struct uni_screen *uniscr;
-       void *p;
-       unsigned int memsize, i;
+       uint32_t *p;
+       unsigned int new_size;  /* In 32-bit characters */
 
-       /* allocate everything in one go */
-       memsize = cols * rows * sizeof(char32_t);
-       memsize += rows * sizeof(char32_t *);
-       p = vzalloc(memsize);
-       if (!p)
-               return NULL;
-
-       /* initial line pointers */
-       uniscr = p;
-       p = uniscr->lines + rows;
-       for (i = 0; i < rows; i++) {
-               uniscr->lines[i] = p;
-               p += cols * sizeof(char32_t);
-       }
-       return uniscr;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(struct uni_screen *uniscr)
-{
-       vfree(uniscr);
+       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, struct uni_screen *new_uniscr)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-       vc_uniscr_free(vc->vc_uni_screen);
-       vc->vc_uni_screen = new_uniscr;
+       kvfree(vc->vc_uniscr_buf);
+       vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, char32_t uc)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+       uint32_t *pos;
 
-       if (uniscr)
-               uniscr->lines[vc->state.y][vc->state.x] = uc;
+       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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->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;
 
-               memcpy(&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
+               memcpy(&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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->lines[vc->state.y];
+       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);
        }
 }
@@ -418,77 +442,70 @@ static void vc_uniscr_clear_line(struct vc_data *vc, 
unsigned int x,
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
                                  unsigned int nr)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
+       if (vc->vc_uniscr_buf) {
                unsigned int cols = vc->vc_cols;
+               uint32_t *ln = vc->vc_uniscr_curr;
 
-               while (nr--)
-                       memset32(uniscr->lines[y++], ' ', cols);
+               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);
+               }
        }
 }
 
 static void vc_uniscr_scroll(struct vc_data *vc, unsigned int t, unsigned int 
b,
                             enum con_scroll dir, unsigned int nr)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               unsigned int i, j, k, sz, d, clear;
-
-               sz = b - t;
-               clear = b - nr;
-               d = nr;
-               if (dir == SM_DOWN) {
+       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 /* && t == 0 */&& b == vc->vc_rows) {
+                       UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+                       clear = vc->vc_rows - nr;
+               } else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+                       UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
                        clear = t;
-                       d = sz - nr;
-               }
-               for (i = 0; i < gcd(d, sz); i++) {
-                       char32_t *tmp = uniscr->lines[t + i];
-                       j = i;
-                       while (1) {
-                               k = j + d;
-                               if (k >= sz)
-                                       k -= sz;
-                               if (k == i)
-                                       break;
-                               uniscr->lines[t + j] = uniscr->lines[t + k];
-                               j = k;
+               } else if (dir == SM_UP) {
+                       sz = b - t;
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, t * cols);
+                       dest = src;
+                       UNISCR_MINUS(dest, nr * cols);
+                       i = b - t;
+                       while (i--) {
+                               memcpy(dest, src, cols * sizeof(uint32_t));
+                               UNISCR_PLUS(src, cols);
+                               UNISCR_PLUS(dest, cols);
+                       }
+                       d = nr;
+                       clear = b - nr;
+               } else {
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, b * cols);
+                       dest = src;
+                       UNISCR_PLUS(dest, nr * cols);
+                       i = b - t;
+                       while (i--) {
+                               UNISCR_MINUS(src, cols);
+                               UNISCR_MINUS(dest, cols);
+                               memcpy(dest, src, cols * sizeof(uint32_t));
                        }
-                       uniscr->lines[t + j] = tmp;
+                       d = nr;
+                       clear = t;
                }
-               vc_uniscr_clear_lines(vc, clear, nr);
-       }
-}
-
-static void vc_uniscr_copy_area(struct uni_screen *dst,
-                               unsigned int dst_cols,
-                               unsigned int dst_rows,
-                               struct uni_screen *src,
-                               unsigned int src_cols,
-                               unsigned int src_top_row,
-                               unsigned int src_bot_row)
-{
-       unsigned int dst_row = 0;
-
-       if (!dst)
-               return;
-
-       while (src_top_row < src_bot_row) {
-               char32_t *src_line = src->lines[src_top_row];
-               char32_t *dst_line = dst->lines[dst_row];
-
-               memcpy(dst_line, src_line, src_cols * sizeof(char32_t));
-               if (dst_cols - src_cols)
-                       memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-               src_top_row++;
-               dst_row++;
-       }
-       while (dst_row < dst_rows) {
-               char32_t *dst_line = dst->lines[dst_row];
-
-               memset32(dst_line, ' ', dst_cols);
-               dst_row++;
+               if (d)
+                       vc_uniscr_clear_lines(vc, clear, nr);
        }
 }
 
@@ -500,7 +517,6 @@ static void vc_uniscr_copy_area(struct uni_screen *dst,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-       struct uni_screen *uniscr;
        unsigned short *p;
        int x, y, mask;
 
@@ -512,11 +528,10 @@ int vc_uniscr_check(struct vc_data *vc)
        if (!vc->vc_utf)
                return -ENODATA;
 
-       if (vc->vc_uni_screen)
+       if (vc->vc_uniscr_buf)
                return 0;
 
-       uniscr = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-       if (!uniscr)
+       if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
                return -ENOMEM;
 
        /*
@@ -528,14 +543,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++) {
-               char32_t *line = uniscr->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_screen = uniscr;
        return 0;
 }
 
@@ -547,12 +562,23 @@ 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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+#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
 
-       BUG_ON(!uniscr);
+       BUG_ON(!vc->vc_uniscr_buf);
 
+#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) {
                /*
@@ -562,60 +588,271 @@ 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, &uniscr->lines[row][col], nr * sizeof(char32_t));
+               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;
-               char32_t *uni_buf = dest;
-               while (nr--) {
-                       u16 glyph = scr_readw(p++) & mask;
-                       *uni_buf++ = inverse_translate(vc, glyph, true);
-               }
+               /* CAN'T HAPPEN!!!  (Hah hah!) */
        }
+#endif
 }
 
 /* this is for validation and debugging only */
 static void vc_uniscr_debug_check(struct vc_data *vc)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-       unsigned short *p;
-       int x, y, mask;
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       /* struct uni_screen *uniscr = get_vc_uniscr(vc); */
+       /* unsigned short *p; */
+       /* int x, y, mask; */
+#endif
 
-       if (!VC_UNI_SCREEN_DEBUG || !uniscr)
+       if (!VC_UNI_SCREEN_DEBUG || !vc->vc_uniscr_buf)
                return;
 
        WARN_CONSOLE_UNLOCKED();
 
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
        /*
         * Make sure our unicode screen translates into the same glyphs
         * as the actual screen. This is brutal indeed.
         */
-       p = (unsigned short *)vc->vc_origin;
-       mask = vc->vc_hi_font_mask | 0xff;
-       for (y = 0; y < vc->vc_rows; y++) {
-               char32_t *line = uniscr->lines[y];
-               for (x = 0; x < vc->vc_cols; x++) {
-                       u16 glyph = scr_readw(p++) & mask;
-                       char32_t uc = line[x];
-                       int tc = conv_uni_to_pc(vc, uc);
-                       if (tc == -4)
-                               tc = conv_uni_to_pc(vc, 0xfffd);
-                       if (tc == -4)
-                               tc = conv_uni_to_pc(vc, '?');
-                       if (tc != glyph)
-                               pr_err_ratelimited(
-                                       "%s: mismatch at %d,%d: glyph=%#x 
tc=%#x\n",
-                                       __func__, x, y, glyph, tc);
+       /* p = (unsigned short *)vc->vc_origin; */
+       /* mask = vc->vc_hi_font_mask | 0xff; */
+       /* for (y = 0; y < vc->vc_rows; y++) { */
+       /*      char32_t *line = uniscr->lines[y]; */
+       /*      for (x = 0; x < vc->vc_cols; x++) { */
+       /*              u16 glyph = scr_readw(p++) & mask; */
+       /*              char32_t uc = line[x]; */
+       /*              int tc = conv_uni_to_pc(vc, uc); */
+       /*              if (tc == -4) */
+       /*                      tc = conv_uni_to_pc(vc, 0xfffd); */
+       /*              if (tc == -4) */
+       /*                      tc = conv_uni_to_pc(vc, '?'); */
+       /*              if (tc != glyph) */
+       /*                      pr_err_ratelimited( */
+       /*                              "%s: mismatch at %d,%d: glyph=%#x 
tc=%#x\n", */
+       /*                              __func__, x, y, glyph, tc); */
+       /*      } */
+       /* } */
+#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 t, unsigned int b,
                enum con_scroll dir, unsigned int nr)
@@ -626,6 +863,10 @@ static void con_scroll(struct vc_data *vc, unsigned int t, 
unsigned int b,
                nr = b - t - 1;
        if (b > vc->vc_rows || t >= b || nr < 1)
                return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (dir == SM_UP && vc->vc_softback_top)
+               con_softback_note (vc, t, nr);
+#endif
        vc_uniscr_scroll(vc, t, b, dir, nr);
        if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, dir, nr))
                return;
@@ -641,6 +882,53 @@ static void con_scroll(struct vc_data *vc, unsigned int t, 
unsigned int b,
        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;
@@ -684,6 +972,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)
 {
@@ -692,7 +981,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);
        }
 }
 
@@ -753,51 +1045,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);
 }
 
@@ -927,8 +1236,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;
@@ -989,7 +1307,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;
        }
 
@@ -1004,7 +1321,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");
@@ -1017,7 +1333,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);
                /*
@@ -1032,9 +1347,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);
@@ -1112,7 +1437,6 @@ int vc_allocate(unsigned int currcons)    /* return 0 on 
success */
        int err;
 
        WARN_CONSOLE_UNLOCKED();
-
        if (currcons >= MAX_NR_CONSOLES)
                return -ENXIO;
 
@@ -1147,16 +1471,30 @@ int vc_allocate(unsigned int currcons)  /* return 0 on 
success */
        vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_KERNEL);
        if (!vc->vc_screenbuf)
                goto err_free;
-
        /* If no drivers have overridden us and the user didn't pass a
           boot option, default to displaying the cursor */
        if (global_cursor_default == -1)
                global_cursor_default = 1;
 
        vc_init(vc, vc->vc_rows, vc->vc_cols, 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);
@@ -1177,6 +1515,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
@@ -1197,12 +1604,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;
-       struct uni_screen *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)
@@ -1216,8 +1630,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) {
                /*
@@ -1245,61 +1659,91 @@ static int vc_do_resize(struct tty_struct *tty, struct 
vc_data *vc,
        if (!newscreen)
                return -ENOMEM;
 
-       if (get_vc_uniscr(vc)) {
-               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);
+
+       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_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-                           get_vc_uniscr(vc), rlth/2, first_copied_row,
-                           min(old_rows, new_rows));
-       vc_uniscr_set(vc, new_uniscr);
+       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);
 
@@ -1309,17 +1753,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;
@@ -1400,8 +1887,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;
@@ -1646,7 +2133,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;
@@ -1658,7 +2145,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;
 }
 
@@ -2928,6 +3415,12 @@ static int do_con_write(struct tty_struct *tty, const 
unsigned char *buf, int co
 
        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++;
@@ -3094,11 +3587,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;
@@ -3143,7 +3633,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:
@@ -3364,7 +3858,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();
 }
@@ -3385,6 +3883,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;
@@ -3440,7 +3946,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);
@@ -3526,6 +4032,7 @@ static int __init con_init(void)
                vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
                vc_init(vc, vc->vc_rows, vc->vc_cols,
                        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;
@@ -4140,7 +4647,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;
@@ -4441,7 +4948,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);
@@ -4741,10 +5251,16 @@ EXPORT_SYMBOL_GPL(screen_glyph);
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+       int y = n / vc->vc_cols, x = n % vc->vc_cols;
+       uint32_t *ln = vc->vc_uniscr_curr;
 
-       if (uniscr)
-               return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+       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 fcc46380e7c9..1abba103d7da 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -98,6 +98,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 e035a63bbe5b..824225747aef 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -633,6 +633,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);
@@ -1273,6 +1295,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 */
@@ -2080,6 +2121,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 = registered_fb[con2fb_map[vc->vc_num]];
@@ -2089,8 +2131,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;
        }
 
@@ -3152,6 +3207,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 d5b9c8d40c18..d8d159e75f39 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -110,6 +110,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 */
@@ -159,7 +170,9 @@ struct vc_data {
        struct vc_data **vc_display_fg;         /* [!] Ptr to var holding fg 
console for this display */
        struct uni_pagedir *vc_uni_pagedir;
        struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir 
variable for this console */
-       struct uni_screen *vc_uni_screen;       /* 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 */
 };
 
@@ -194,4 +207,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 b5ab452fca5b..1a11c9868f8e 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -115,6 +115,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,
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 981d2bfcf9a5..cb9342fb0a3a 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];
 
 #ifndef VT_SINGLE_DRIVER
@@ -286,8 +291,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)
@@ -295,6 +323,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.. */
@@ -316,101 +345,96 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-#ifdef NO_VC_UNI_SCREEN
-/* this disables and optimizes related code away at compile time */
-#define get_vc_uniscr(vc) NULL
-#else
-#define get_vc_uniscr(vc) vc->vc_uni_screen
-#endif
-
 #define VC_UNI_SCREEN_DEBUG 0
 
 typedef uint32_t char32_t;
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
-struct uni_screen {
-       char32_t *lines[0];
-};
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static struct uni_screen *vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int 
rows)
 {
-       struct uni_screen *uniscr;
-       void *p;
-       unsigned int memsize, i;
+       uint32_t *p;
+       unsigned int new_size;  /* In 32-bit characters */
 
-       /* allocate everything in one go */
-       memsize = cols * rows * sizeof(char32_t);
-       memsize += rows * sizeof(char32_t *);
-       p = vzalloc(memsize);
-       if (!p)
-               return NULL;
-
-       /* initial line pointers */
-       uniscr = p;
-       p = uniscr->lines + rows;
-       for (i = 0; i < rows; i++) {
-               uniscr->lines[i] = p;
-               p += cols * sizeof(char32_t);
-       }
-       return uniscr;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(struct uni_screen *uniscr)
-{
-       vfree(uniscr);
+       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, struct uni_screen *new_uniscr)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-       vc_uniscr_free(vc->vc_uni_screen);
-       vc->vc_uni_screen = new_uniscr;
+       kvfree(vc->vc_uniscr_buf);
+       vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, char32_t uc)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+       uint32_t *pos;
 
-       if (uniscr)
-               uniscr->lines[vc->state.y][vc->state.x] = uc;
+       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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->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;
 
-               memcpy(&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
+               memcpy(&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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               char32_t *ln = uniscr->lines[vc->state.y];
+       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);
        }
 }
@@ -418,77 +442,70 @@ static void vc_uniscr_clear_line(struct vc_data *vc, 
unsigned int x,
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
                                  unsigned int nr)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
+       if (vc->vc_uniscr_buf) {
                unsigned int cols = vc->vc_cols;
+               uint32_t *ln = vc->vc_uniscr_curr;
 
-               while (nr--)
-                       memset32(uniscr->lines[y++], ' ', cols);
+               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);
+               }
        }
 }
 
 static void vc_uniscr_scroll(struct vc_data *vc, unsigned int t, unsigned int 
b,
                             enum con_scroll dir, unsigned int nr)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-       if (uniscr) {
-               unsigned int i, j, k, sz, d, clear;
-
-               sz = b - t;
-               clear = b - nr;
-               d = nr;
-               if (dir == SM_DOWN) {
+       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 /* && t == 0 */&& b == vc->vc_rows) {
+                       UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
+                       clear = vc->vc_rows - nr;
+               } else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+                       UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+                       d = nr;
                        clear = t;
-                       d = sz - nr;
-               }
-               for (i = 0; i < gcd(d, sz); i++) {
-                       char32_t *tmp = uniscr->lines[t + i];
-                       j = i;
-                       while (1) {
-                               k = j + d;
-                               if (k >= sz)
-                                       k -= sz;
-                               if (k == i)
-                                       break;
-                               uniscr->lines[t + j] = uniscr->lines[t + k];
-                               j = k;
+               } else if (dir == SM_UP) {
+                       sz = b - t;
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, t * cols);
+                       dest = src;
+                       UNISCR_MINUS(dest, nr * cols);
+                       i = b - t;
+                       while (i--) {
+                               memcpy(dest, src, cols * sizeof(uint32_t));
+                               UNISCR_PLUS(src, cols);
+                               UNISCR_PLUS(dest, cols);
+                       }
+                       d = nr;
+                       clear = b - nr;
+               } else {
+                       src = vc->vc_uniscr_curr;
+                       UNISCR_PLUS(src, b * cols);
+                       dest = src;
+                       UNISCR_PLUS(dest, nr * cols);
+                       i = b - t;
+                       while (i--) {
+                               UNISCR_MINUS(src, cols);
+                               UNISCR_MINUS(dest, cols);
+                               memcpy(dest, src, cols * sizeof(uint32_t));
                        }
-                       uniscr->lines[t + j] = tmp;
+                       d = nr;
+                       clear = t;
                }
-               vc_uniscr_clear_lines(vc, clear, nr);
-       }
-}
-
-static void vc_uniscr_copy_area(struct uni_screen *dst,
-                               unsigned int dst_cols,
-                               unsigned int dst_rows,
-                               struct uni_screen *src,
-                               unsigned int src_cols,
-                               unsigned int src_top_row,
-                               unsigned int src_bot_row)
-{
-       unsigned int dst_row = 0;
-
-       if (!dst)
-               return;
-
-       while (src_top_row < src_bot_row) {
-               char32_t *src_line = src->lines[src_top_row];
-               char32_t *dst_line = dst->lines[dst_row];
-
-               memcpy(dst_line, src_line, src_cols * sizeof(char32_t));
-               if (dst_cols - src_cols)
-                       memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-               src_top_row++;
-               dst_row++;
-       }
-       while (dst_row < dst_rows) {
-               char32_t *dst_line = dst->lines[dst_row];
-
-               memset32(dst_line, ' ', dst_cols);
-               dst_row++;
+               if (d)
+                       vc_uniscr_clear_lines(vc, clear, nr);
        }
 }
 
@@ -500,7 +517,6 @@ static void vc_uniscr_copy_area(struct uni_screen *dst,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-       struct uni_screen *uniscr;
        unsigned short *p;
        int x, y, mask;
 
@@ -512,11 +528,10 @@ int vc_uniscr_check(struct vc_data *vc)
        if (!vc->vc_utf)
                return -ENODATA;
 
-       if (vc->vc_uni_screen)
+       if (vc->vc_uniscr_buf)
                return 0;
 
-       uniscr = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-       if (!uniscr)
+       if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
                return -ENOMEM;
 
        /*
@@ -528,14 +543,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++) {
-               char32_t *line = uniscr->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_screen = uniscr;
        return 0;
 }
 
@@ -547,12 +562,23 @@ 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)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+#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
 
-       BUG_ON(!uniscr);
+       BUG_ON(!vc->vc_uniscr_buf);
 
+#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) {
                /*
@@ -562,60 +588,271 @@ 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, &uniscr->lines[row][col], nr * sizeof(char32_t));
+               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;
-               char32_t *uni_buf = dest;
-               while (nr--) {
-                       u16 glyph = scr_readw(p++) & mask;
-                       *uni_buf++ = inverse_translate(vc, glyph, true);
-               }
+               /* CAN'T HAPPEN!!!  (Hah hah!) */
        }
+#endif
 }
 
 /* this is for validation and debugging only */
 static void vc_uniscr_debug_check(struct vc_data *vc)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
-       unsigned short *p;
-       int x, y, mask;
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       /* struct uni_screen *uniscr = get_vc_uniscr(vc); */
+       /* unsigned short *p; */
+       /* int x, y, mask; */
+#endif
 
-       if (!VC_UNI_SCREEN_DEBUG || !uniscr)
+       if (!VC_UNI_SCREEN_DEBUG || !vc->vc_uniscr_buf)
                return;
 
        WARN_CONSOLE_UNLOCKED();
 
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
        /*
         * Make sure our unicode screen translates into the same glyphs
         * as the actual screen. This is brutal indeed.
         */
-       p = (unsigned short *)vc->vc_origin;
-       mask = vc->vc_hi_font_mask | 0xff;
-       for (y = 0; y < vc->vc_rows; y++) {
-               char32_t *line = uniscr->lines[y];
-               for (x = 0; x < vc->vc_cols; x++) {
-                       u16 glyph = scr_readw(p++) & mask;
-                       char32_t uc = line[x];
-                       int tc = conv_uni_to_pc(vc, uc);
-                       if (tc == -4)
-                               tc = conv_uni_to_pc(vc, 0xfffd);
-                       if (tc == -4)
-                               tc = conv_uni_to_pc(vc, '?');
-                       if (tc != glyph)
-                               pr_err_ratelimited(
-                                       "%s: mismatch at %d,%d: glyph=%#x 
tc=%#x\n",
-                                       __func__, x, y, glyph, tc);
+       /* p = (unsigned short *)vc->vc_origin; */
+       /* mask = vc->vc_hi_font_mask | 0xff; */
+       /* for (y = 0; y < vc->vc_rows; y++) { */
+       /*      char32_t *line = uniscr->lines[y]; */
+       /*      for (x = 0; x < vc->vc_cols; x++) { */
+       /*              u16 glyph = scr_readw(p++) & mask; */
+       /*              char32_t uc = line[x]; */
+       /*              int tc = conv_uni_to_pc(vc, uc); */
+       /*              if (tc == -4) */
+       /*                      tc = conv_uni_to_pc(vc, 0xfffd); */
+       /*              if (tc == -4) */
+       /*                      tc = conv_uni_to_pc(vc, '?'); */
+       /*              if (tc != glyph) */
+       /*                      pr_err_ratelimited( */
+       /*                              "%s: mismatch at %d,%d: glyph=%#x 
tc=%#x\n", */
+       /*                              __func__, x, y, glyph, tc); */
+       /*      } */
+       /* } */
+#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 t, unsigned int b,
                enum con_scroll dir, unsigned int nr)
@@ -626,6 +863,10 @@ static void con_scroll(struct vc_data *vc, unsigned int t, 
unsigned int b,
                nr = b - t - 1;
        if (b > vc->vc_rows || t >= b || nr < 1)
                return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       if (dir == SM_UP && vc->vc_softback_top)
+               con_softback_note (vc, t, nr);
+#endif
        vc_uniscr_scroll(vc, t, b, dir, nr);
        if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, dir, nr))
                return;
@@ -641,6 +882,53 @@ static void con_scroll(struct vc_data *vc, unsigned int t, 
unsigned int b,
        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;
@@ -684,6 +972,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)
 {
@@ -692,7 +981,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);
        }
 }
 
@@ -753,51 +1045,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);
 }
 
@@ -927,8 +1236,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;
@@ -989,7 +1307,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;
        }
 
@@ -1004,7 +1321,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");
@@ -1017,7 +1333,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);
                /*
@@ -1032,9 +1347,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);
@@ -1112,7 +1437,6 @@ int vc_allocate(unsigned int currcons)    /* return 0 on 
success */
        int err;
 
        WARN_CONSOLE_UNLOCKED();
-
        if (currcons >= MAX_NR_CONSOLES)
                return -ENXIO;
 
@@ -1147,16 +1471,30 @@ int vc_allocate(unsigned int currcons)  /* return 0 on 
success */
        vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_KERNEL);
        if (!vc->vc_screenbuf)
                goto err_free;
-
        /* If no drivers have overridden us and the user didn't pass a
           boot option, default to displaying the cursor */
        if (global_cursor_default == -1)
                global_cursor_default = 1;
 
        vc_init(vc, vc->vc_rows, vc->vc_cols, 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);
@@ -1177,6 +1515,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
@@ -1197,12 +1604,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;
-       struct uni_screen *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)
@@ -1216,8 +1630,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) {
                /*
@@ -1245,61 +1659,91 @@ static int vc_do_resize(struct tty_struct *tty, struct 
vc_data *vc,
        if (!newscreen)
                return -ENOMEM;
 
-       if (get_vc_uniscr(vc)) {
-               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);
+
+       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_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-                           get_vc_uniscr(vc), rlth/2, first_copied_row,
-                           min(old_rows, new_rows));
-       vc_uniscr_set(vc, new_uniscr);
+       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);
 
@@ -1309,17 +1753,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;
@@ -1400,8 +1887,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;
@@ -1646,7 +2133,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;
@@ -1658,7 +2145,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;
 }
 
@@ -2928,6 +3415,12 @@ static int do_con_write(struct tty_struct *tty, const 
unsigned char *buf, int co
 
        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++;
@@ -3094,11 +3587,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;
@@ -3143,7 +3633,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:
@@ -3364,7 +3858,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();
 }
@@ -3385,6 +3883,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;
@@ -3440,7 +3946,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);
@@ -3526,6 +4032,7 @@ static int __init con_init(void)
                vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
                vc_init(vc, vc->vc_rows, vc->vc_cols,
                        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;
@@ -4139,7 +4646,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;
@@ -4440,7 +4947,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);
@@ -4740,11 +5250,17 @@ EXPORT_SYMBOL_GPL(screen_glyph);
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-       struct uni_screen *uniscr = get_vc_uniscr(vc);
+       int y = n / vc->vc_cols, x = n % vc->vc_cols;
+       uint32_t *ln = vc->vc_uniscr_curr;
 
-       if (uniscr)
-               return uniscr->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 22cea5082ac4..7d56ea0018fc 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -99,6 +99,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 14a7d404062c..7b523d586e22 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -609,6 +609,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);
@@ -1262,6 +1284,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 */
@@ -2069,6 +2110,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);
@@ -2078,8 +2120,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;
        }
 
@@ -3171,6 +3226,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 1518568aaf0f..f928509e3773 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -110,6 +110,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 */
@@ -159,7 +170,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 */
-       struct uni_screen *vc_uni_screen;       /* 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 */
 };
 
@@ -194,4 +207,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