public inbox for gentoo-user@lists.gentoo.org
 help / color / mirror / Atom feed
From: Alan Mackenzie <acm@muc.de>
To: gentoo-user@lists.gentoo.org
Subject: Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
Date: Wed, 4 Oct 2023 13:16:44 +0000	[thread overview]
Message-ID: <ZR1lvIdRb-fd0a1n@ACM> (raw)
In-Reply-To: <Y91Y2zILfZafrHlY@ACM>

[-- Attachment #1: Type: text/plain, Size: 1947 bytes --]

Hello, Gentoo.

On Fri, Feb 03, 2023 at 18:56:27 +0000, Alan Mackenzie wrote:

The topic of this post is my kernel patch which enables soft scrolling
on Linux tty's with <shift><PgUp> and <shift><PgDn>, and also enables
the GPM mouse utility on those scrolled regions.

Currently, the patch I posted some months ago works on all 6.1.x
kernels, and very likely works on 6.2.x, too.  In kernel 6.3.1, some
significant refactoring was done by the kernel people, necessitating a
new version of the patch, called 6.3.11-GPM.20231004.diff.  I've tested
this on 6.3.11 and 6.5.5.

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

Just a warning: if you copy your kernel .config from a 6.1.x kernel, and
use make oldconfig to generate a new kernel for 6.3.x etc., you will
have to enable CONFIG_HID_SUPPORT, otherwise your USB keyboard and mouse
will be dead on bootup.  ;-(

The usual disclaimers apply, here.  If this patch breaks anything for
you, you get to join the pieces back together again.  But if this does
happen, please let me know, so that I can try to fix the bug.  My only
promise is that there's nothing malicious in the patch.

As well as 6.3.11-GPM.20231004.diff, I'm reposting
6.1.8-GPM.20230203.diff for anybody new here who wants to try it on the
current Gentoo 6.1.x kernel.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 6.3.11-GPM.20231004.diff --]
[-- Type: text/plain, Size: 49586 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 3c2ea9c098f7..51a3f0e7ffb5 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,107 +345,111 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int rows)
 {
-	u32 **uni_lines;
-	void *p;
-	unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
-
-	/* allocate everything in one go */
-	memsize = col_size * rows;
-	memsize += rows * sizeof(*uni_lines);
-	uni_lines = vzalloc(memsize);
-	if (!uni_lines)
-		return NULL;
-
-	/* initial line pointers */
-	p = uni_lines + rows;
-	for (i = 0; i < rows; i++) {
-		uni_lines[i] = p;
-		p += col_size;
-	}
+	uint32_t *p;
+	unsigned int new_size;	/* In 32-bit characters */
 
-	return uni_lines;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(u32 **uni_lines)
-{
-	vfree(uni_lines);
+	num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+	new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+	new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+	if (!p)
+		return -ENOMEM;
+	vc->vc_uniscr_buf = p;
+	vc->vc_uniscr_curr = p;
+	vc->vc_uniscr_char_size = new_size;
+	memset32(p, ' ', new_size); /* Probably redundant. */
+	return 0;
 }
 
-static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-	vc_uniscr_free(vc->vc_uni_lines);
-	vc->vc_uni_lines = new_uni_lines;
+	kvfree(vc->vc_uniscr_buf);
+	vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
 {
-	if (vc->vc_uni_lines)
-		vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr;
+		UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[x], ' ', nr);
 	}
 }
 
 static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		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)
 {
-	if (vc->vc_uni_lines)
-		memset32(&vc->vc_uni_lines[vc->state.y][x], ' ', nr);
+	if (vc->vc_uniscr_buf) {
+		uint32_t *ln = vc->vc_uniscr_curr;
+
+		UNISCR_PLUS(ln, vc->state.y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memset32(&ln[x], ' ', nr);
+	}
 }
 
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
 				  unsigned int nr)
 {
-	if (vc->vc_uni_lines)
-		while (nr--)
-			memset32(vc->vc_uni_lines[y++], ' ', vc->vc_cols);
-}
-
-/* juggling array rotation algorithm (complexity O(N), size complexity O(1)) */
-static void juggle_array(u32 **array, unsigned int size, unsigned int nr)
-{
-	unsigned int gcd_idx;
-
-	for (gcd_idx = 0; gcd_idx < gcd(nr, size); gcd_idx++) {
-		u32 *gcd_idx_val = array[gcd_idx];
-		unsigned int dst_idx = gcd_idx;
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		uint32_t *ln = vc->vc_uniscr_curr;
 
-		while (1) {
-			unsigned int src_idx = (dst_idx + nr) % size;
-			if (src_idx == gcd_idx)
-				break;
-
-			array[dst_idx] = array[src_idx];
-			dst_idx = src_idx;
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		while (nr--) {
+			memset32(ln, ' ', cols);
+			UNISCR_PLUS(ln, cols);
 		}
-
-		array[dst_idx] = gcd_idx_val;
 	}
 }
 
@@ -424,49 +457,52 @@ static void vc_uniscr_scroll(struct vc_data *vc, unsigned int top,
 			     unsigned int bottom, enum con_scroll dir,
 			     unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
-	unsigned int size = bottom - top;
-
-	if (!uni_lines)
-		return;
-
-	if (dir == SM_DOWN) {
-		juggle_array(&uni_lines[top], size, size - nr);
-		vc_uniscr_clear_lines(vc, top, nr);
-	} else {
-		juggle_array(&uni_lines[top], size, nr);
-		vc_uniscr_clear_lines(vc, bottom - nr, nr);
-	}
-}
-
-static void vc_uniscr_copy_area(u32 **dst_lines,
-				unsigned int dst_cols,
-				unsigned int dst_rows,
-				u32 **src_lines,
-				unsigned int src_cols,
-				unsigned int src_top_row,
-				unsigned int src_bot_row)
-{
-	unsigned int dst_row = 0;
-
-	if (!dst_lines)
-		return;
-
-	while (src_top_row < src_bot_row) {
-		u32 *src_line = src_lines[src_top_row];
-		u32 *dst_line = dst_lines[dst_row];
-
-		memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
-		if (dst_cols - src_cols)
-			memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-		src_top_row++;
-		dst_row++;
-	}
-	while (dst_row < dst_rows) {
-		u32 *dst_line = dst_lines[dst_row];
-
-		memset32(dst_line, ' ', dst_cols);
-		dst_row++;
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		unsigned int sz, /* number of rows being scrolled */
+			d,		  /* number of rows needing blanking */
+			clear;		  /* The number of the topmost row needing blanking. */
+		uint32_t *dest, *src;
+		unsigned int i;
+
+		if (dir == SM_UP /* && top == 0 */&& bottom == vc->vc_rows) {
+			UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows - nr) {
+			UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+		clear = top;
+		} else if (dir == SM_UP) {
+			sz = bottom - top;
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, top * cols);
+			dest = src;
+			UNISCR_MINUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				UNISCR_PLUS(src, cols);
+				UNISCR_PLUS(dest, cols);
+			}
+			d = nr;
+			clear = bottom - nr;
+		} else {
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, bottom * cols);
+			dest = src;
+			UNISCR_PLUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				UNISCR_MINUS(src, cols);
+				UNISCR_MINUS(dest, cols);
+				memcpy(dest, src, cols * sizeof(uint32_t));
+			}
+			d = nr;
+			clear = top;
+		}
+		if (d)
+			vc_uniscr_clear_lines(vc, clear, nr);
 	}
 }
 
@@ -478,7 +514,6 @@ static void vc_uniscr_copy_area(u32 **dst_lines,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-	u32 **uni_lines;
 	unsigned short *p;
 	int x, y, mask;
 
@@ -487,11 +522,10 @@ int vc_uniscr_check(struct vc_data *vc)
 	if (!vc->vc_utf)
 		return -ENODATA;
 
-	if (vc->vc_uni_lines)
+	if (vc->vc_uniscr_buf)
 		return 0;
 
-	uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-	if (!uni_lines)
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
 		return -ENOMEM;
 
 	/*
@@ -503,15 +537,14 @@ int vc_uniscr_check(struct vc_data *vc)
 	p = (unsigned short *)vc->vc_origin;
 	mask = vc->vc_hi_font_mask | 0xff;
 	for (y = 0; y < vc->vc_rows; y++) {
-		u32 *line = uni_lines[y];
+		uint32_t *line = vc->vc_uniscr_curr;
+		UNISCR_PLUS(line, y * vc->vc_cols);
 		for (x = 0; x < vc->vc_cols; x++) {
 			u16 glyph = scr_readw(p++) & mask;
 			line[x] = inverse_translate(vc, glyph, true);
 		}
 	}
 
-	vc->vc_uni_lines = uni_lines;
-
 	return 0;
 }
 
@@ -523,13 +556,24 @@ int vc_uniscr_check(struct vc_data *vc)
 void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 			 unsigned int row, unsigned int col, unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	uint32_t *pos;	       /* Position in the unicode buffer of col/row */
+#else
 	int offset = row * vc->vc_size_row + col * 2;
-	unsigned long pos;
+	unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
 
-	if (WARN_ON_ONCE(!uni_lines))
+	if (WARN_ON_ONCE(!vc->vc_uniscr_buf))
 		return;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	pos = vc->vc_uniscr_curr;
+	UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+	memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
 	pos = (unsigned long)screenpos(vc, offset, viewed);
 	if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
 		/*
@@ -539,24 +583,233 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		 */
 		row = (pos - vc->vc_origin) / vc->vc_size_row;
 		col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
-		memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+		memcpy(dest,
+		       (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+		       nr);
 	} else {
 		/*
-		 * Scrollback is active. For now let's simply backtranslate
-		 * the screen glyphs until the unicode screen buffer does
-		 * synchronize with console display drivers for a scrollback
-		 * buffer of its own.
+		 * Scrollback is active.  So hoik the unicode characters out
+		 * of the unicode circular buffer.
 		 */
-		u16 *p = (u16 *)pos;
-		int mask = vc->vc_hi_font_mask | 0xff;
-		u32 *uni_buf = dest;
-		while (nr--) {
-			u16 glyph = scr_readw(p++) & mask;
-			*uni_buf++ = inverse_translate(vc, glyph, true);
+		/* CAN'T HAPPEN!!!  (Hah hah!) */
+	}
+#endif
+}
+
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = console_soft_scrollback_size / vc->vc_size_row;
+	if (l > 5)
+	{
+		vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+		vc->vc_softback_top = vc->vc_softback_buf;
+	}
+	else
+		/* Smaller scrollback makes no sense, and 0 would screw
+		   the operation totally */
+		vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+	if (vc->vc_softback_lines)
+		concon_scrolldelta(vc, vc->vc_softback_lines);
+	return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+				long delta)
+{
+	int count = vc->vc_rows;
+	unsigned short *d, *s;
+	unsigned long n;
+	int line = 0;
+
+	if (!vc->vc_softback_lines)
+		vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+	d = (u16 *) vc->vc_softback_curr;
+	if (d == (u16 *) vc->vc_softback_in)
+		d = (u16 *) vc->vc_origin;
+	n = vc->vc_softback_curr + delta * vc->vc_size_row;
+	vc->vc_softback_lines -= delta;
+	if (delta < 0) {
+		if (vc->vc_softback_curr < vc->vc_softback_top
+		    && n < vc->vc_softback_buf) {
+			n += vc->vc_softback_end - vc->vc_softback_buf;
+			if (n < vc->vc_softback_top) {
+				vc->vc_softback_lines -=
+				    (vc->vc_softback_top - n) / vc->vc_size_row;
+				n = vc->vc_softback_top;
+			}
+		} else if (vc->vc_softback_curr >= vc->vc_softback_top
+			   && n < vc->vc_softback_top) {
+			vc->vc_softback_lines -=
+			    (vc->vc_softback_top - n) / vc->vc_size_row;
+			n = vc->vc_softback_top;
+		}
+	} else {
+		if (vc->vc_softback_curr > vc->vc_softback_in
+		    && n >= vc->vc_softback_end) {
+			n += vc->vc_softback_buf - vc->vc_softback_end;
+			if (n > vc->vc_softback_in) {
+				n = vc->vc_softback_in;
+				vc->vc_softback_lines = 0;
+			}
+		} else if (vc->vc_softback_curr <= vc->vc_softback_in
+			   && n > vc->vc_softback_in) {
+			n = vc->vc_softback_in;
+			vc->vc_softback_lines = 0;
+		}
+	}
+	if (n == vc->vc_softback_curr)
+		return;
+	vc->vc_softback_curr = n;
+	/* If we're not scrolled any more, restore the character to the cursor
+	 * position */
+	if (!vc->vc_softback_lines)
+		scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+	s = (u16 *) vc->vc_softback_curr;
+	if (s == (u16 *) vc->vc_softback_in)
+		s = (u16 *) vc->vc_origin;
+	while (count--) {
+		unsigned short *start;
+		unsigned short *le;
+		unsigned short c;
+		int x = 0;
+		unsigned short attr = 1;
+
+		start = s;
+		le = advance_row(s, 1);
+		/* Temporarily overwrite the character at the cursor position
+		 * with the one we actually want to see on the screen.  */
+		if (count == vc->vc_rows - vc->state.y - 1)
+		{
+			c = scr_readw((u16 *)(s + vc->state.x));
+			scr_writew(c, (u16 *)vc->vc_pos);
+			vc->vc_sw->con_putcs
+				(vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+		}
+		do {
+			c = scr_readw(s);
+			if (attr != (c & 0xff00)) {
+				attr = c & 0xff00;
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start;
+					start = s;
+				}
+			}
+			if (c == scr_readw(d)) {
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start + 1;
+					start = s + 1;
+				} else {
+					x++;
+					start++;
+				}
+			}
+			s++;
+			d++;
+		} while (s < le);
+		if (s > start)
+			vc->vc_sw->con_putcs(vc, start, s - start, line, x);
+		line++;
+		if (d == (u16 *) vc->vc_softback_end)
+			d = (u16 *) vc->vc_softback_buf;
+		if (d == (u16 *) vc->vc_softback_in)
+			d = (u16 *) vc->vc_origin;
+		if (s == (u16 *) vc->vc_softback_end)
+			s = (u16 *) vc->vc_softback_buf;
+		if (s == (u16 *) vc->vc_softback_in)
+			s = (u16 *) vc->vc_origin;
+	}
+}
+
+static inline void con_softback_note(struct vc_data *vc, int t,
+				     int count)
+{
+	unsigned short *p;
+
+	if (vc->vc_num != fg_console)
+		return;
+	p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+	while (count) {
+		scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+		count--;
+		p = advance_row(p, 1);
+		vc->vc_softback_in += vc->vc_size_row;
+		if (vc->vc_softback_in == vc->vc_softback_end)
+			vc->vc_softback_in = vc->vc_softback_buf;
+		if (vc->vc_softback_in == vc->vc_softback_top) {
+			vc->vc_softback_top += vc->vc_size_row;
+			if (vc->vc_softback_top == vc->vc_softback_end)
+				vc->vc_softback_top = vc->vc_softback_buf;
+		}
+	}
+	vc->vc_softback_curr = vc->vc_softback_in;
+}
+
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+	/* struct display *disp = &fb_display[fg_console]; */
+	/* int offset, limit, scrollback_old; */
+
+	if (vc->vc_softback_top) {
+		if (vc->vc_num != fg_console)
+			return;
+		if (vc->vc_mode != KD_TEXT || !lines)
+			return;
+#if 0
+		if (logo_shown >= 0) {
+			struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+			if (conp2->vc_top == logo_lines
+			    && conp2->vc_bottom == conp2->vc_rows)
+				conp2->vc_top = 0;
+			if (logo_shown == vc->vc_num) {
+				unsigned long p, q;
+				int i;
+
+				p = vc->vc_softback_in;
+				q = vc->vc_origin +
+				    logo_lines * vc->vc_size_row;
+				for (i = 0; i < logo_lines; i++) {
+					if (p == vc->vc_softback_top)
+						break;
+					if (p == vc->vc_softback_buf)
+						p = vc->vc_softback_end;
+					p -= vc->vc_size_row;
+					q -= vc->vc_size_row;
+					scr_memcpyw((u16 *) q, (u16 *) p,
+						    vc->vc_size_row);
+				}
+				vc->vc_softback_in = vc->vc_softback_curr = p;
+				update_region(vc, vc->vc_origin,
+					      logo_lines * vc->vc_cols);
+			}
+			logo_shown = FBCON_LOGO_CANSHOW;
 		}
+#endif
+		vc->vc_sw->con_cursor(vc, CM_ERASE /* | CM_SOFTBACK */);
+		con_redraw_softback(vc, /* disp, */ lines);
+		if (!vc->vc_softback_lines)
+			vc->vc_sw->con_cursor(vc, CM_DRAW /* | CM_SOFTBACK */);
 	}
 }
 
+#endif  /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
+
 static void con_scroll(struct vc_data *vc, unsigned int top,
 		       unsigned int bottom, enum con_scroll dir,
 		       unsigned int nr)
@@ -568,6 +821,10 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 		nr = rows - 1;
 	if (bottom > vc->vc_rows || top >= bottom || nr < 1)
 		return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (dir == SM_UP && vc->vc_softback_top)
+		con_softback_note (vc, top, nr);
+#endif
 
 	vc_uniscr_scroll(vc, top, bottom, dir, nr);
 	if (con_is_visible(vc) &&
@@ -585,6 +842,53 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 	scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+	unsigned int xx, yy, offset;
+	u16 *p;
+
+	unsigned long origin =
+		(start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+		? start >= vc->vc_softback_curr
+		  ? vc->vc_softback_curr
+		  : vc->vc_softback_curr
+		    - (vc->vc_softback_end - vc->vc_softback_buf)
+		: vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+	p = (u16 *) start;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
+	for(;;) {
+		u16 attrib = scr_readw(p) & 0xff00;
+		int startx = xx;
+		u16 *q = p;
+		while (xx < vc->vc_cols && count) {
+			if (attrib != (scr_readw(p) & 0xff00)) {
+				if (p > q)
+					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+				startx = xx;
+				q = p;
+				attrib = scr_readw(p) & 0xff00;
+			}
+			p++;
+			xx++;
+			count--;
+		}
+		if (p > q)
+			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+		if (p == (u16 *) vc->vc_softback_end)
+			p = (u16 *)vc->vc_softback_buf;
+		if (p == (u16 *) vc->vc_softback_in)
+			p = (u16 *)vc->vc_origin;
+		if (!count)
+			break;
+		xx = 0;
+		yy++;
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -628,6 +932,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)
 {
@@ -636,7 +941,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);
 	}
 }
 
@@ -697,51 +1005,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);
 }
 
@@ -871,8 +1196,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;
@@ -933,7 +1267,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;
 	}
 
@@ -948,7 +1281,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");
@@ -961,7 +1293,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);
 		/*
@@ -976,9 +1307,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);
@@ -1056,7 +1397,6 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
@@ -1091,16 +1431,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);
@@ -1121,6 +1475,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
@@ -1141,12 +1564,19 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 {
 	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
 	unsigned long end;
-	unsigned int old_rows, old_row_size, first_copied_row;
-	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+	unsigned int old_rows, old_size_row, first_copied_row;
+	unsigned int new_cols, new_rows, new_size_row, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	u32 **new_uniscr = NULL;
-
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned short *d;
+	unsigned long old_softback_buf, new_softback_buf, tmp;
+	unsigned long old_softback_end, new_softback_end;
+	unsigned int old_scrolled_rows, new_scrolled_rows;
+	unsigned int count, copied_scrolled_rows;
+	void *temp_new_softback_buf;
+#endif
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!vc)
@@ -1160,8 +1590,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) {
 		/*
@@ -1189,61 +1619,91 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	if (!newscreen)
 		return -ENOMEM;
 
-	if (vc->vc_uni_lines) {
-		new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
-		if (!new_uniscr) {
-			kfree(newscreen);
-			return -ENOMEM;
-		}
-	}
-
 	if (vc_is_sel(vc))
 		clear_selection();
 
 	old_rows = vc->vc_rows;
-	old_row_size = vc->vc_size_row;
+	old_size_row = vc->vc_size_row;
 
 	err = resize_screen(vc, new_cols, new_rows, user);
 	if (err) {
 		kfree(newscreen);
-		vc_uniscr_free(new_uniscr);
+		vc_uniscr_free(vc);
+		vc->vc_uniscr_buf = old_uniscr;
 		return err;
 	}
 
-	vc->vc_rows = new_rows;
-	vc->vc_cols = new_cols;
-	vc->vc_size_row = new_row_size;
-	vc->vc_screenbuf_size = new_screen_size;
-
-	rlth = min(old_row_size, new_row_size);
-	rrem = new_row_size - rlth;
+	rlth = min(old_size_row, new_size_row);
+	rrem = new_size_row - rlth;
 	old_origin = vc->vc_origin;
 	new_origin = (long) newscreen;
 	new_scr_end = new_origin + new_screen_size;
 
 	if (vc->state.y > new_rows) {
-		if (old_rows - vc->state.y < new_rows) {
+		if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+			if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+				con_softback_note(vc, vc->vc_top,
+						  old_rows - new_rows - vc->vc_top);
+#endif
+				vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+						 old_rows - new_rows - vc->vc_top);
+			}
 			/*
 			 * Cursor near the bottom, copy contents from the
 			 * bottom of buffer
 			 */
 			first_copied_row = (old_rows - new_rows);
 		} else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+			con_softback_note(vc, vc->vc_top,
+					  vc->state.y - new_rows/2);
+#endif
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 vc->state.y - new_rows/2);
 			/*
 			 * Cursor is in no man's land, copy 1/2 screenful
 			 * from the top and bottom of cursor position
 			 */
 			first_copied_row = (vc->state.y - new_rows/2);
 		}
-		old_origin += first_copied_row * old_row_size;
+		old_origin += first_copied_row * old_size_row;
 	} else
 		first_copied_row = 0;
-	end = old_origin + old_row_size * min(old_rows, new_rows);
+	end = old_origin + old_size_row * min(old_rows, new_rows);
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    vc->vc_uni_lines, rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_cols, new_rows)) != 0)
+		return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	concon_set_origin(vc);
+	old_softback_buf = vc->vc_softback_buf;
+	old_softback_end = vc->vc_softback_end;
+	vc->vc_softback_size = console_soft_scrollback_size;
+	temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+	new_softback_buf = (unsigned long)temp_new_softback_buf;
+	if (!new_softback_buf)
+		return -ENOMEM;
+	new_softback_end = new_softback_buf + console_soft_scrollback_size;
+	d = (unsigned short *)new_softback_buf;
+	while (d != (u16 *)new_softback_end) {
+		scr_writew (0x0020, d);
+		d++;
+	}
+	if (vc->vc_softback_top <= vc->vc_softback_in)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_scrolled_rows = tmp / vc->vc_size_row;
+	new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+	copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
+
+	vc->vc_cols = new_cols;
+	vc->vc_rows = new_rows;
+	vc->vc_size_row = new_size_row;
+	vc->vc_screenbuf_size = new_screen_size;
 
 	update_attr(vc);
 
@@ -1253,17 +1713,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;
@@ -1344,8 +1847,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;
@@ -1590,7 +2093,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;
@@ -1602,7 +2105,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;
 }
 
@@ -2872,6 +3375,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++;
@@ -3037,11 +3546,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;
@@ -3086,7 +3592,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:
@@ -3313,7 +3823,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();
 }
@@ -3334,6 +3848,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;
@@ -3389,7 +3911,7 @@ static void con_cleanup(struct tty_struct *tty)
 	tty_port_put(&vc->port);
 }
 
-static int default_color           = 7; /* white */
+static int default_color	   = 7; /* white */
 static int default_italic_color    = 2; // green (ASCII)
 static int default_underline_color = 3; // cyan (ASCII)
 module_param_named(color, default_color, int, S_IRUGO | S_IWUSR);
@@ -3475,6 +3997,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;
@@ -4088,7 +4611,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;
@@ -4389,7 +4912,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);
@@ -4702,12 +5228,17 @@ EXPORT_SYMBOL_GPL(screen_glyph);
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
+	int y = n / vc->vc_cols, x = n % vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-	if (uni_lines)
-		return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
-
-	return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+	if (vc->vc_uniscr_curr) {
+		UNISCR_PLUS(ln, y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_softback_lines * vc->vc_cols);
+#endif
+		return ln[x];
+	}
+	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
 
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index 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 eb565a10e5cd..b3c4c7a64c00 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);
@@ -1257,6 +1279,25 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
 		 * bitmap stretched into the margin area.
 		 */
 		fbcon_clear_margins(vc, 0);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		{
+			uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+			unsigned int i = vc->vc_rows - logo_lines;
+
+			UNISCR_PLUS(d, (vc->vc_rows - logo_lines)  * vc->vc_cols);
+			UNISCR_PLUS(s, (vc->vc_rows - 2 * logo_lines) * vc->vc_cols);
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				UNISCR_MINUS(s, vc->vc_cols);
+				memcpy (d, s, vc->vc_cols * sizeof (uint32_t));
+			}
+			i = logo_lines;
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				memset32(d, ' ', vc->vc_cols);
+			}
+		}
+#endif
 	}
 
 	/* Split blits that cross physical y_wrap boundary */
@@ -2064,6 +2105,7 @@ static int fbcon_switch(struct vc_data *vc)
 	struct fbcon_ops *ops;
 	struct fbcon_display *p = &fb_display[vc->vc_num];
 	struct fb_var_screeninfo var;
+	unsigned short *d, *s;
 	int i, ret, prev_console;
 
 	info = fbcon_info_from_console(vc->vc_num);
@@ -2073,8 +2115,21 @@ static int fbcon_switch(struct vc_data *vc)
 		struct vc_data *conp2 = vc_cons[logo_shown].d;
 
 		if (conp2->vc_top == logo_lines
-		    && conp2->vc_bottom == conp2->vc_rows)
+		    && conp2->vc_bottom == conp2->vc_rows) {
+			/* Scroll the bottom part of the screen up to fill the
+			 * logo lines. */
+			i = conp2->vc_bottom - conp2->vc_top;
+			d = (unsigned short *)conp2->vc_origin;
+			s = (unsigned short *)(conp2->vc_origin + logo_lines * conp2->vc_size_row);
+			while (i--) {
+				scr_memcpyw(d, s, conp2->vc_size_row);
+				d += conp2->vc_cols;
+				s += conp2->vc_cols;
+			}
+			scr_memsetw(d, conp2->vc_video_erase_char,
+				    conp2->vc_size_row * logo_lines);
 			conp2->vc_top = 0;
+		}
 		logo_shown = FBCON_LOGO_CANSHOW;
 	}
 
@@ -3164,6 +3219,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index 539f1cd45309..9d87a3fa6ed3 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -109,6 +109,17 @@ struct vc_data {
 	unsigned short	*vc_screenbuf;		/* In-memory character/attribute buffer */
 	unsigned int	vc_screenbuf_size;
 	unsigned char	vc_mode;		/* KD_TEXT, ... */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned int    vc_softback_size;	/* Size in bytes of scrollback buffer. */
+	unsigned long   vc_softback_buf;	/* Address of scrollback buffer. */
+	unsigned long   vc_softback_end;	/* (Just past) end of buffer. */
+	unsigned long   vc_softback_in;		/* Head pointer into circular buffer. */
+	unsigned long   vc_softback_top;	/* Tail pointer into circular buffer. */
+	unsigned long   vc_softback_curr;	/* Pos in vc_screenbuf or vc_softback_buf
+						   corresponding to visible screen. */
+	int             vc_softback_lines;	/* Number of lines currently scrolled. */
+	unsigned short  vc_char_at_pos;		/* Char at vc_pos when no soft scroll */
+#endif
 	/* attributes for all characters on screen */
 	unsigned char	vc_attr;		/* Current attributes */
 	unsigned char	vc_def_color;		/* Default colors */
@@ -158,7 +169,9 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedict *uni_pagedict;
 	struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict variable for this console */
-	u32 **vc_uni_lines;			/* unicode screen content */
+	uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+	unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit chars */
+	uint32_t *vc_uniscr_curr;	/* Pos of first char of (unscrolled) screen */
 	/* additional information is in vt_kern.h */
 };
 
@@ -193,4 +206,22 @@ extern void vc_SAK(struct work_struct *work);
 
 bool con_is_visible(const struct vc_data *vc);
 
+/* Macros for wraparound in the uniscr buffer.  POS must be a uint32_t pointer
+ * variable which is expected to be within the confines of vc->vc_uniscr_buf
+ * and vc->vc_uniscr_buf + vc->vc_uniscr_char_size.  OFFSET must be a positive
+ * distance measured in uint32_t's, which when added to or subtracted from POS
+ * won't be too far away from the buffer. */
+#define UNISCR_PLUS(pos,offset)						\
+	do {								\
+		pos += offset;						\
+		if (pos >= vc->vc_uniscr_buf + vc->vc_uniscr_char_size) \
+			pos -= vc->vc_uniscr_char_size;			\
+	} while (0)
+#define UNISCR_MINUS(pos,offset)			\
+	do {						\
+		pos -= offset;				\
+		if (pos < vc->vc_uniscr_buf)		\
+			pos += vc->vc_uniscr_char_size; \
+	} while (0)
+
 #endif /* _LINUX_CONSOLE_STRUCT_H */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index c1f5aebef170..97ecca06eceb 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -114,6 +114,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

[-- Attachment #3: 6.1.8-GPM.20230203.diff --]
[-- Type: text/plain, Size: 51765 bytes --]

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	other threads:[~2023-10-04 13:16 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-07 19:46 [gentoo-user] Soft scrolling on framebuffer consoles - New versions of the patches Alan Mackenzie
2021-10-08  7:37 ` Peter Humphrey
2022-12-12 18:23 ` [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch Alan Mackenzie
2022-12-12 19:29   ` Mike Civil
2022-12-12 19:43     ` Alan Mackenzie
2022-12-13  3:44   ` Peter Humphrey
2022-12-14 10:53     ` Alan Mackenzie
2022-12-29 19:50       ` [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling Alan Mackenzie
2022-12-31  9:42         ` Peter Humphrey
2022-12-31 14:08           ` Alan Mackenzie
2022-12-31 15:47             ` Peter Humphrey
2022-12-31 16:13               ` Alan Mackenzie
2022-12-31 21:49                 ` David Rosenbaum
2023-01-01 15:13                 ` Alan Mackenzie
2023-01-01 15:38                   ` Peter Humphrey
2023-01-26 20:28                   ` Alan Mackenzie
2023-01-27 12:24                     ` Peter Humphrey
2023-01-27 22:31                       ` Alan Mackenzie
2023-01-28 14:41                         ` Peter Humphrey
2023-02-03 18:56                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New (?final) " Alan Mackenzie
2023-10-04 13:16                             ` Alan Mackenzie [this message]
2023-10-04 14:41                               ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Peter Humphrey
2023-10-04 17:08                               ` Jorge Almeida
2023-10-04 18:59                                 ` Alan Mackenzie
2023-10-04 19:02                                   ` Jorge Almeida
2024-01-24 10:00                             ` Alan Mackenzie
2024-01-24 12:20                               ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.6 [was 6.3] onwards Alan Mackenzie
2024-03-11 10:47                                 ` Peter Humphrey
2024-04-04  8:05                                   ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.8.1 onwards Alan Mackenzie
2024-01-24 14:08                               ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Peter Humphrey
2023-09-09 15:21                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling David Rosenbaum

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ZR1lvIdRb-fd0a1n@ACM \
    --to=acm@muc.de \
    --cc=gentoo-user@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox