public inbox for gentoo-user@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-user] Soft scrolling on framebuffer consoles - New versions of the patches.
@ 2021-10-07 19:46 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
  0 siblings, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2021-10-07 19:46 UTC (permalink / raw
  To: gentoo-user

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

Hello, Gentoo.

Here are the latest versions of my soft scrolling patch for the kernel
lines 5.10.xx and 5.14.xx.  They fix bugs where a PC would crash if it
was initialised with a 25x80 console and later changed to a full
resolution frame buffer screen.  It also now works (so I am told) for
kernels initialised with parameters like vga=791.

To use one of these (the right one for your kernel version) do:

(i) cd /usr/src/linux-5.10.61-gentoo, or similar.  Extract the right
  patch file to that directory.  (Don't worry about having > 61 for a
  kernel version.  Just use it!)
(ii) patch -p1 < 5.10.61-scroll.20211007.diff.
(iii) Configure the kernel in your usual way.  The extra items added by
  the patch are CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK and
  CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE, to be found in make
  menuconfig under Device Drivers/Graphic Support/Console display driver
  support.  The help items for these should explain them adequately.
(iv) Build the kernel.
(v) Put the new kernel into your usual boot manager.
(vi) Reboot and enjoy!

As far as I know this patch should be safe (apart from the mysterious
alleged security problem which caused the soft scrolling to be removed
from the kernel in the first place).  There is certainly nothing
malicous in it.  But if it does break anything for you, you get to keep
the pieces.  But if anything does go wrong, please tell me about it
(preferably here on the list, but by private email if you'd prefer), so
that I can try and fix it.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 5.10.61-scroll.20211007.diff --]
[-- Type: text/plain, Size: 20887 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 06757b1d4aec..c2061813c825 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
@@ -287,7 +292,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -616,6 +621,218 @@ static void vc_uniscr_debug_check(struct vc_data *vc)
 	}
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = vc->vc_softback_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 +843,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 +862,56 @@ 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;
+	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++;
+		/* if (vc->vc_sw->con_getxy) { */
+		/* 	p = (u16 *)start; */
+		/* 	start = vc->vc_sw->con_getxy(vc, start, NULL, NULL); */
+		/* } */
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -684,6 +955,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 +964,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);
 	}
 }
 
@@ -927,8 +1202,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;
@@ -1004,7 +1288,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 +1300,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 +1314,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) {
 		set_leds();
 		compute_shiftstate();
@@ -1110,15 +1402,68 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 {
 	struct vt_notifier_param param;
 	struct vc_data *vc;
+	unsigned long new_end;
+	unsigned long new_in, new_top;
+	unsigned long in_residue;
+	unsigned short *d;
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
 	if (vc_cons[currcons].d)
+	{
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		vc = vc_cons[currcons].d;
+		if (!vc->vc_softback_size) {
+			/* vc was partially initialized by __init. */
+			vc->vc_softback_size = console_soft_scrollback_size;
+			vc->vc_softback_buf =
+				(unsigned long)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+			if (vc->vc_softback_buf) {
+				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);
+			}
+		}
+		if (vc->vc_softback_buf
+		    && (vc->vc_softback_end - vc->vc_softback_buf) % vc->vc_size_row) {
+			new_end = vc->vc_softback_buf + vc->vc_softback_size
+				- (vc->vc_softback_size % vc->vc_size_row);
+			new_top = vc->vc_softback_top
+				- (vc->vc_softback_top - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			in_residue = (vc->vc_softback_in - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			new_in = vc->vc_softback_in
+				+ (in_residue ? vc->vc_size_row - in_residue : 0);
+			if (new_in == new_end)
+				new_in = vc->vc_softback_buf;
+			if (new_top == new_in)
+				new_top += vc->vc_size_row;
+			else if ((new_in > new_top
+				  && (new_in - new_top <= vc->vc_size_row)))
+				new_top += 2 * vc->vc_size_row;
+			if (new_top >= new_end)
+				new_top -= (new_end - vc->vc_softback_buf);
+			vc->vc_softback_lines = 0;
+			vc->vc_softback_curr = new_in;
+
+			d = (u16 *)vc->vc_softback_in;
+			vc->vc_softback_end = new_end;
+			vc->vc_softback_in = new_in;
+			vc->vc_softback_top = new_top;
+			while (d != (u16 *)new_in) {
+				scr_writew (0x0020, d); /* How about non-VGA? */
+				if (++d == (u16 *)new_end)
+					d = (u16 *)vc->vc_softback_buf;
+			}
+		}
+#endif
 		return 0;
+	}
 
 	/* due to the granularity of kmalloc, we waste some memory here */
 	/* the alloc is done in two steps, to optimize the common situation
@@ -1158,6 +1503,18 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	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)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+	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);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1630,7 +1987,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;
@@ -1642,7 +1999,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;
 }
 
@@ -2889,6 +3246,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->stopped && count) {
 		int orig = *buf;
 		buf++;
@@ -3104,7 +3467,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:
@@ -3332,7 +3699,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();
 }
@@ -3353,6 +3724,10 @@ 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);
+#endif
+
 	/* Still being freed */
 	if (vc->port.tty) {
 		ret = -ERESTARTSYS;
@@ -3408,7 +3783,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);
@@ -4109,7 +4484,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;
@@ -4410,7 +4785,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);
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index ee33b8ec62bb..b8aab2c58178 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -78,6 +78,29 @@ config FRAMEBUFFER_CONSOLE
 	help
 	  Low-level framebuffer-based console driver.
 
+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_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 42c72d051158..05a8eb440c51 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -3087,6 +3087,9 @@ static const struct consw fb_con = {
 	.con_font_default	= fbcon_set_def_font,
 	.con_font_copy 		= fbcon_copy_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index d5b9c8d40c18..acc277e73e32 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 */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index 349e39c3ab60..d35ec391c33c 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -127,6 +127,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: 5.14.5-scroll.20211007.diff --]
[-- Type: text/plain, Size: 20909 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index ef981d3b7bb4..b772f1a2810f 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
@@ -287,7 +292,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -616,6 +621,218 @@ static void vc_uniscr_debug_check(struct vc_data *vc)
 	}
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = vc->vc_softback_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 +843,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 +862,56 @@ 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;
+	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++;
+		/* if (vc->vc_sw->con_getxy) { */
+		/* 	p = (u16 *)start; */
+		/* 	start = vc->vc_sw->con_getxy(vc, start, NULL, NULL); */
+		/* } */
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -684,6 +955,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 +964,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);
 	}
 }
 
@@ -927,8 +1202,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;
@@ -1004,7 +1288,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 +1300,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 +1314,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);
@@ -1109,15 +1401,68 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 {
 	struct vt_notifier_param param;
 	struct vc_data *vc;
+	unsigned long new_end;
+	unsigned long new_in, new_top;
+	unsigned long in_residue;
+	unsigned short *d;
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
 	if (vc_cons[currcons].d)
+	{
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		vc = vc_cons[currcons].d;
+		if (!vc->vc_softback_size) {
+			/* vc was partially initialized by __init. */
+			vc->vc_softback_size = console_soft_scrollback_size;
+			vc->vc_softback_buf =
+				(unsigned long)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+			if (vc->vc_softback_buf) {
+				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);
+			}
+		}
+		if (vc->vc_softback_buf
+		    && (vc->vc_softback_end - vc->vc_softback_buf) % vc->vc_size_row) {
+			new_end = vc->vc_softback_buf + vc->vc_softback_size
+				- (vc->vc_softback_size % vc->vc_size_row);
+			new_top = vc->vc_softback_top
+				- (vc->vc_softback_top - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			in_residue = (vc->vc_softback_in - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			new_in = vc->vc_softback_in
+				+ (in_residue ? vc->vc_size_row - in_residue : 0);
+			if (new_in == new_end)
+				new_in = vc->vc_softback_buf;
+			if (new_top == new_in)
+				new_top += vc->vc_size_row;
+			else if ((new_in > new_top
+				  && (new_in - new_top <= vc->vc_size_row)))
+				new_top += 2 * vc->vc_size_row;
+			if (new_top >= new_end)
+				new_top -= (new_end - vc->vc_softback_buf);
+			vc->vc_softback_lines = 0;
+			vc->vc_softback_curr = new_in;
+
+			d = (u16 *)vc->vc_softback_in;
+			vc->vc_softback_end = new_end;
+			vc->vc_softback_in = new_in;
+			vc->vc_softback_top = new_top;
+			while (d != (u16 *)new_in) {
+				scr_writew (0x0020, d); /* How about non-VGA? */
+				if (++d == (u16 *)new_end)
+					d = (u16 *)vc->vc_softback_buf;
+			}
+		}
+#endif
 		return 0;
+	}
 
 	/* due to the granularity of kmalloc, we waste some memory here */
 	/* the alloc is done in two steps, to optimize the common situation
@@ -1157,6 +1502,18 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	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)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+	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);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1629,7 +1986,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;
@@ -1641,7 +1998,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;
 }
 
@@ -2888,6 +3245,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++;
@@ -3103,7 +3466,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:
@@ -3324,7 +3691,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();
 }
@@ -3345,6 +3716,10 @@ 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);
+#endif
+
 	/* Still being freed */
 	if (vc->port.tty) {
 		ret = -ERESTARTSYS;
@@ -3400,7 +3775,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);
@@ -4100,7 +4475,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;
@@ -4401,7 +4776,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);
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index 840d9813b0bc..7e1b4925a91a 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -78,6 +78,29 @@ config FRAMEBUFFER_CONSOLE
 	help
 	  Low-level framebuffer-based console driver.
 
+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_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 22bb3892f6bd..c89cc8c605fc 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -3053,6 +3053,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index d5b9c8d40c18..acc277e73e32 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 */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index 0da94a6dee15..63b83ffbef95 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -115,6 +115,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New versions of the patches.
  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
  1 sibling, 0 replies; 31+ messages in thread
From: Peter Humphrey @ 2021-10-08  7:37 UTC (permalink / raw
  To: gentoo-user

On Thursday, 7 October 2021 20:46:51 BST Alan Mackenzie wrote:

> Here are the latest versions of my soft scrolling patch for the kernel
> lines 5.10.xx and 5.14.xx.  They fix bugs where a PC would crash if it
> was initialised with a 25x80 console and later changed to a full
> resolution frame buffer screen.  It also now works (so I am told) for
> kernels initialised with parameters like vga=791.

--->8

Thank you for your efforts on all our behalf Alan.

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch.
  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 ` Alan Mackenzie
  2022-12-12 19:29   ` Mike Civil
  2022-12-13  3:44   ` Peter Humphrey
  1 sibling, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-12 18:23 UTC (permalink / raw
  To: gentoo-user

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

Hello, Gentoo.

Here is the latest version of my soft scrolling patch for the gentoo
kernel version 5.15.80.  It will surely work on any reasonably recent
kernel version, and also on future versions.  The new version doesn't
add any new functionality, it just patches the kernel giving rise to
fewer messages from the patch utility.

The purpose of the patch is to enable you to scroll a linux console text
display up and down by half a screen by typing <shift><down> and
<shift><up>.

To use this patch, proceed thusly:

(i) cd /usr/src/linux-5.15.80, or similar.  Get the attached patch file,
  5.15.80-scroll-20221212.diff into this directory.
(ii) patch -p1 < 5.15.80-scroll-20221212.diff.
(iii) Configure the kernel in your usual way.  The extra items added by
  the patch are CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK and
  CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE, to be found in make
  menuconfig under Device Drivers/Graphic Support/Console display driver
  support.  The help items for these should explain them adequately.
(iv) Build the kernel.
(v) Put the new kernel into your usual boot manager.
(vi) Reboot and enjoy!

As far as I know this patch should be safe (apart from the mysterious
alleged security problem which caused the soft scrolling to be removed
from the kernel in the first place).  There is certainly nothing
malicious in it.  But if it does break anything for you, you get to keep
the pieces.  But if anything does go wrong, please tell me about it
(preferably here on the list, but by private email if you'd prefer), so
that I can try and fix it.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 5.15.80-scroll.20221212.diff --]
[-- Type: text/plain, Size: 20925 bytes --]

* Unmerged path .config
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index b8f5bc19416d..d7aadca5107d 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
@@ -287,7 +292,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -616,6 +621,218 @@ static void vc_uniscr_debug_check(struct vc_data *vc)
 	}
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = vc->vc_softback_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 +843,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 +862,56 @@ 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;
+	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++;
+		/* if (vc->vc_sw->con_getxy) { */
+		/* 	p = (u16 *)start; */
+		/* 	start = vc->vc_sw->con_getxy(vc, start, NULL, NULL); */
+		/* } */
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -684,6 +955,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 +964,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);
 	}
 }
 
@@ -927,8 +1202,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;
@@ -1004,7 +1288,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 +1300,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 +1314,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);
@@ -1109,15 +1401,68 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 {
 	struct vt_notifier_param param;
 	struct vc_data *vc;
+	unsigned long new_end;
+	unsigned long new_in, new_top;
+	unsigned long in_residue;
+	unsigned short *d;
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
 	if (vc_cons[currcons].d)
+	{
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		vc = vc_cons[currcons].d;
+		if (!vc->vc_softback_size) {
+			/* vc was partially initialized by __init. */
+			vc->vc_softback_size = console_soft_scrollback_size;
+			vc->vc_softback_buf =
+				(unsigned long)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+			if (vc->vc_softback_buf) {
+				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);
+			}
+		}
+		if (vc->vc_softback_buf
+		    && (vc->vc_softback_end - vc->vc_softback_buf) % vc->vc_size_row) {
+			new_end = vc->vc_softback_buf + vc->vc_softback_size
+				- (vc->vc_softback_size % vc->vc_size_row);
+			new_top = vc->vc_softback_top
+				- (vc->vc_softback_top - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			in_residue = (vc->vc_softback_in - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			new_in = vc->vc_softback_in
+				+ (in_residue ? vc->vc_size_row - in_residue : 0);
+			if (new_in == new_end)
+				new_in = vc->vc_softback_buf;
+			if (new_top == new_in)
+				new_top += vc->vc_size_row;
+			else if ((new_in > new_top
+				  && (new_in - new_top <= vc->vc_size_row)))
+				new_top += 2 * vc->vc_size_row;
+			if (new_top >= new_end)
+				new_top -= (new_end - vc->vc_softback_buf);
+			vc->vc_softback_lines = 0;
+			vc->vc_softback_curr = new_in;
+
+			d = (u16 *)vc->vc_softback_in;
+			vc->vc_softback_end = new_end;
+			vc->vc_softback_in = new_in;
+			vc->vc_softback_top = new_top;
+			while (d != (u16 *)new_in) {
+				scr_writew (0x0020, d); /* How about non-VGA? */
+				if (++d == (u16 *)new_end)
+					d = (u16 *)vc->vc_softback_buf;
+			}
+		}
+#endif
 		return 0;
+	}
 
 	/* due to the granularity of kmalloc, we waste some memory here */
 	/* the alloc is done in two steps, to optimize the common situation
@@ -1157,6 +1502,18 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	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)kzalloc(vc->vc_softback_size, GFP_KERNEL);
+	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);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1646,7 +2003,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 +2015,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 +3285,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++;
@@ -3143,7 +3506,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 +3731,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 +3756,10 @@ 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);
+#endif
+
 	/* Still being freed */
 	if (vc->port.tty) {
 		ret = -ERESTARTSYS;
@@ -3440,7 +3815,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);
@@ -4140,7 +4515,7 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
 			con_driver->desc = desc;
 			con_driver->node = i;
 			con_driver->flag = CON_DRIVER_FLAG_MODULE |
-			                   CON_DRIVER_FLAG_INIT;
+					   CON_DRIVER_FLAG_INIT;
 			con_driver->first = first;
 			con_driver->last = last;
 			retval = 0;
@@ -4441,7 +4816,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);
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index fcc46380e7c9..4a343877d70c 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -98,6 +98,29 @@ 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_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index e035a63bbe5b..a4fab71c5853 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -3152,6 +3152,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index d5b9c8d40c18..acc277e73e32 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 */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index b5ab452fca5b..1a11c9868f8e 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -115,6 +115,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch.
  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
  1 sibling, 1 reply; 31+ messages in thread
From: Mike Civil @ 2022-12-12 19:29 UTC (permalink / raw
  To: gentoo-user

Many thanks for this.

I can confirm that it patches, compiles and works on 6.1 :)




^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch.
  2022-12-12 19:29   ` Mike Civil
@ 2022-12-12 19:43     ` Alan Mackenzie
  0 siblings, 0 replies; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-12 19:43 UTC (permalink / raw
  To: gentoo-user

Hello, Mike.

On Mon, Dec 12, 2022 at 19:29:43 +0000, Mike Civil wrote:
> Many thanks for this.

> I can confirm that it patches, compiles and works on 6.1 :)

Thanks for the testing!  6.1 _is_ up to date.  :-)

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch.
  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-13  3:44   ` Peter Humphrey
  2022-12-14 10:53     ` Alan Mackenzie
  1 sibling, 1 reply; 31+ messages in thread
From: Peter Humphrey @ 2022-12-13  3:44 UTC (permalink / raw
  To: gentoo-user

On Monday, 12 December 2022 18:23:28 GMT Alan Mackenzie wrote:

> Here is the latest version of my soft scrolling patch for the gentoo
> kernel version 5.15.80.  It will surely work on any reasonably recent
> kernel version, and also on future versions.  The new version doesn't
> add any new functionality, it just patches the kernel giving rise to
> fewer messages from the patch utility.

You've done it again! What a fine effort. It's saved me much wailing and 
gnashing of teeth.

Is there any chance of enabling gpm to work with it? That would save yet more 
generations of tooth enamel.  :)

Seriously, though, it's just wonderful as it is, and we all owe you a debt.

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch.
  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
  0 siblings, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-14 10:53 UTC (permalink / raw
  To: gentoo-user

Hello, Peter.

On Tue, Dec 13, 2022 at 03:44:49 +0000, Peter Humphrey wrote:
> On Monday, 12 December 2022 18:23:28 GMT Alan Mackenzie wrote:

> > Here is the latest version of my soft scrolling patch for the gentoo
> > kernel version 5.15.80.  It will surely work on any reasonably recent
> > kernel version, and also on future versions.  The new version doesn't
> > add any new functionality, it just patches the kernel giving rise to
> > fewer messages from the patch utility.

> You've done it again! What a fine effort. It's saved me much wailing and 
> gnashing of teeth.

Thanks for that!

> Is there any chance of enabling gpm to work with it? That would save
> yet more generations of tooth enamel.  :)

Yes, that's been annoying me for some time, too - when the screen is
scrolled, and one tries to mark a portion of text with GPM, it gets the
text that was on the screen before the scroll, and corrupts the display
of the text.

I had a look at it last night, and the problem is that the kernel
doesn't currently store the 32-bit unicode characters which have been
scrolled off the screen - just the 8-bit character glyph codes together
with the 8-bit colour information.  So it would triple the amount of
information stored for each character position.  That surely shouldn't
be a problem on today's machines, though - even with
FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE set to 512 kB, that would only
be 1.5 MB per console.

So yes, this should be doable.  It's definitely more than a day's work,
almost certainly less than a month's.  I'll see what I can manage.

> Seriously, though, it's just wonderful as it is, and we all owe you a debt.

Thanks again!

> -- 
> Regards,
> Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2022-12-14 10:53     ` Alan Mackenzie
@ 2022-12-29 19:50       ` Alan Mackenzie
  2022-12-31  9:42         ` Peter Humphrey
  0 siblings, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-29 19:50 UTC (permalink / raw
  To: gentoo-user

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

Hello again, Peter.

On Wed, Dec 14, 2022 at 10:53:07 +0000, Alan Mackenzie wrote:
> On Tue, Dec 13, 2022 at 03:44:49 +0000, Peter Humphrey wrote:
> > On Monday, 12 December 2022 18:23:28 GMT Alan Mackenzie wrote:

> > > Here is the latest version of my soft scrolling patch for the gentoo
> > > kernel version 5.15.80.  It will surely work on any reasonably recent
> > > kernel version, and also on future versions.  The new version doesn't
> > > add any new functionality, it just patches the kernel giving rise to
> > > fewer messages from the patch utility.

> > You've done it again! What a fine effort. It's saved me much wailing and 
> > gnashing of teeth.

> Thanks for that!

> > Is there any chance of enabling gpm to work with it? That would save
> > yet more generations of tooth enamel.  :)

> Yes, that's been annoying me for some time, too - when the screen is
> scrolled, and one tries to mark a portion of text with GPM, it gets the
> text that was on the screen before the scroll, and corrupts the display
> of the text.

> I had a look at it last night, and the problem is that the kernel
> doesn't currently store the 32-bit unicode characters which have been
> scrolled off the screen - just the 8-bit character glyph codes together
> with the 8-bit colour information.  So it would triple the amount of
> information stored for each character position.  That surely shouldn't
> be a problem on today's machines, though - even with
> FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE set to 512 kB, that would only
> be 1.5 MB per console.

> So yes, this should be doable.  It's definitely more than a day's work,
> almost certainly less than a month's.  I'll see what I can manage.

> > Seriously, though, it's just wonderful as it is, and we all owe you a
> > debt.

> Thanks again!

I've got a first version of a patch which attempts to handle GPM in
conjunction with scrolling.  It's imperfect - for example, I can't get it
to select anything the first 66 lines of the console's displayed boot-up
messages.  Nevertheless it may be useful.

In particular, moving the mouse over an already scrolled area no longer
corrupts that area.  Also, one can select text from a scrolled area.

To use it, please apply the supplied patch ON TOP OF the patch I posted
here on 12th December.  Proceed as documented in that post, up until
configuring the kernel - in Device drivers/Graphic support/Console
display driver support, there's an extra item "Enable a working GPM for
scrolled back scrollback buffer in System RAM" which should be enabled by
default.  Check this is set up properly.  Then build and install the
kernel.  Then reboot into it and try it out.

Note that it uses ~twice as much memory as the previous scrolling
buffers, giving a total of ~three times as much memory used.  This
shouldn't really be a problem on a modern machine with 16GB or 32GB of
RAM.

Please let me know of any problems you encounter with this new facility,
so that I can try to fix them.  Thanks!

> > -- 
> > Regards,
> > Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: diff.20221229b.diff --]
[-- Type: text/plain, Size: 26731 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index d7aadca5107d..c453c6031098 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -291,6 +291,28 @@ 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)
+		return (unsigned short *)(vc->vc_origin + offset);
+	else {
+		scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr;
+		if (scrolled_expanse < 0)
+			scrolled_expanse += 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)
@@ -300,6 +322,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.. */
@@ -321,101 +344,106 @@ 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;
 
-	/* allocate everything in one go */
-	memsize = cols * rows * sizeof(char32_t);
-	memsize += rows * sizeof(char32_t *);
-	p = vzalloc(memsize);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	vc->vc_uniscr_char_size =
+		cols * rows
+		+ ((vc->vc_softback_end - vc->vc_softback_buf) / 2);
+#else
+	vc->vc_uniscr_char_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * vc->vc_uniscr_char_size);
+	vc->vc_uniscr_buf = p;
 	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;
-}
-
-static void vc_uniscr_free(struct uni_screen *uniscr)
-{
-	vfree(uniscr);
+		return -ENOMEM;
+	vc->vc_uniscr_curr = p;
+	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;
+	kfree(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);
-
-	if (uniscr)
-		uniscr->lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr
+			+ vc->state.y * vc->vc_cols
+			+ vc->state.x;
+		if (pos >= vc_uniscr_buf_end(vc))
+			pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		pos -= vc->vc_softback_lines * vc->vc_cols;
+		if (pos < vc->vc_uniscr_buf)
+			pos += vc->vc_uniscr_char_size;
+#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 + y * cols;
 
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 + y * cols;
 
-		memcpy(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 + vc->state.y * vc->vc_cols;
 
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
 		memset32(&ln[x], ' ', nr);
 	}
 }
@@ -423,77 +451,72 @@ 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;
-
-		while (nr--)
-			memset32(uniscr->lines[y++], ' ', cols);
+		uint32_t *ln = vc->vc_uniscr_curr + y * cols;
+
+		/* if (ln >= vc_uniscr_buf_end(vc)) /\* We probably don't need this FIXME!!! STOUGH!!! *\/ */
+		/* 	ln -= vc->vc_uniscr_char_size; */ /* Commented out 2022-12-29.  STOUGH!!!  */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
+		while (nr--) {
+			if (ln >= vc_uniscr_buf_end(vc))
+				ln -= vc->vc_uniscr_char_size;
+			memset32(ln, ' ', cols);
+			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) {
-			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;
+	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) {
+			vc->vc_uniscr_curr += nr * cols;
+			if (vc->vc_uniscr_curr >= vc_uniscr_buf_end(vc))
+				vc->vc_uniscr_curr -= vc->vc_uniscr_char_size;
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+			vc->vc_uniscr_curr -= nr * cols;
+			if (vc->vc_uniscr_curr < vc->vc_uniscr_buf)
+				vc->vc_uniscr_curr += vc->vc_uniscr_char_size;
+			d = 0;
+		} else {
+			sz = b - t;
+			src = vc->vc_uniscr_curr + t * cols;
+			if (dir == SM_UP) {
+				dest = vc->vc_uniscr_curr - nr * cols;
+				clear = b - nr;
+				d = nr;
+			} else {
+				dest = vc->vc_uniscr_curr + nr * cols;
+				clear = t;
+				d = sz - nr;
+			}
+			i = nr;
+			while (i--) {
+				if (dest >= vc_uniscr_buf_end(vc))
+					dest -= vc->vc_uniscr_char_size;
+				if (src >= vc_uniscr_buf_end(vc))
+					src -= vc->vc_uniscr_char_size;
+				dest += cols;
+				src += cols;
 			}
-			uniscr->lines[t + j] = tmp;
 		}
-		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);
 	}
 }
 
@@ -505,7 +528,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;
 
@@ -517,11 +539,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;
 
 	/*
@@ -533,14 +554,15 @@ 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 + y * vc->vc_cols;
+		if (line >= vc_uniscr_buf_end(vc))
+			line -= vc->vc_uniscr_char_size;
 		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;
 }
 
@@ -552,12 +574,26 @@ 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 + row * vc->vc_cols + col);
+	if (pos >= vc_uniscr_buf_end(vc))
+		pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	pos -= vc->vc_softback_lines * vc->vc_cols;
+	if (pos < vc->vc_uniscr_buf)
+		pos += vc->vc_uniscr_char_size;
+#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) {
 		/*
@@ -567,58 +603,57 @@ 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
@@ -874,11 +909,12 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 		  ? vc->vc_softback_curr
 		  : vc->vc_softback_curr
 		    - (vc->vc_softback_end - vc->vc_softback_buf)
-		: vc->vc_origin;
+		: 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;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
 	for(;;) {
 		u16 attrib = scr_readw(p) & 0xff00;
 		int startx = xx;
@@ -905,10 +941,6 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 			break;
 		xx = 0;
 		yy++;
-		/* if (vc->vc_sw->con_getxy) { */
-		/* 	p = (u16 *)start; */
-		/* 	start = vc->vc_sw->con_getxy(vc, start, NULL, NULL); */
-		/* } */
 	}
 }
 #else
@@ -1028,51 +1060,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);
 }
 
@@ -1273,7 +1322,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;
 	}
 
@@ -1401,11 +1449,13 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 {
 	struct vt_notifier_param param;
 	struct vc_data *vc;
+	int err;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
 	unsigned long new_end;
 	unsigned long new_in, new_top;
 	unsigned long in_residue;
 	unsigned short *d;
-	int err;
+#endif
 
 	WARN_CONSOLE_UNLOCKED();
 	if (currcons >= MAX_NR_CONSOLES)
@@ -1425,6 +1475,7 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 					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);
 			}
 		}
 		if (vc->vc_softback_buf
@@ -1513,6 +1564,7 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 		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:
@@ -1534,6 +1586,68 @@ 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_rows,
+				       unsigned int new_cols)
+{
+	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;
+	uint32_t *dest, *src;
+	int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int new_uniscr_rows;
+	unsigned int old_lines;
+	long tmp;
+
+	tmp = vc->vc_softback_curr - vc->vc_softback_top;
+	if (tmp < 0)
+		tmp += 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_softback_end - vc->vc_softback_buf)
+		/ vc->vc_size_row
+		+ new_rows;	/* STOUGH, Added, 2022-12-28 */
+	new_lines = min(old_lines, new_uniscr_rows);
+	dest = vc->vc_uniscr_curr - (new_lines - new_rows) * new_cols;
+	if (dest < vc->vc_uniscr_buf)
+		dest += vc->vc_uniscr_char_size;
+	src = old_uniscr_curr - (old_lines - old_rows) * old_cols;
+	if (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);
+	dest = vc->vc_uniscr_curr;
+	src = old_uniscr_curr;
+#endif
+	if (old_uniscr_buf) {
+		while (new_lines--) {
+			memcpy(dest, src, old_cols * sizeof(uint32_t));
+			if (new_cols > old_cols)
+				memset32(dest + old_cols, ' ',
+					 new_cols - old_cols);
+			dest += new_cols;
+			if (dest >= vc->vc_uniscr_buf
+			    + vc->vc_uniscr_char_size)
+				dest -= vc->vc_uniscr_char_size;
+			src += old_cols;
+			if (src >= old_uniscr_buf + old_uniscr_char_size)
+				src -= old_uniscr_char_size;
+		}
+		kfree(old_uniscr_buf);
+	}
+	return 0;
+}
+
 /**
  *	vc_do_resize	-	resizing method for the tty
  *	@tty: tty being resized
@@ -1558,7 +1672,7 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	struct uni_screen *new_uniscr = NULL;
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
 
 	WARN_CONSOLE_UNLOCKED();
 
@@ -1602,14 +1716,6 @@ 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();
 
@@ -1619,10 +1725,14 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	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;
 	}
 
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_rows, new_cols)) != 0)
+		return err;
+
 	vc->vc_rows = new_rows;
 	vc->vc_cols = new_cols;
 	vc->vc_size_row = new_row_size;
@@ -1653,11 +1763,6 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		first_copied_row = 0;
 	end = old_origin + old_row_size * min(old_rows, new_rows);
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    get_vc_uniscr(vc), rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
-
 	update_attr(vc);
 
 	while (old_origin < end) {
@@ -1757,8 +1862,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;
@@ -3457,11 +3562,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;
@@ -3758,6 +3860,10 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
 
 #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 */
@@ -5119,10 +5225,19 @@ 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 + y * vc->vc_cols;
 
-	if (uniscr)
-		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+	if (vc->vc_uniscr_curr) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 4a343877d70c..1abba103d7da 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -121,6 +121,21 @@ config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
 	  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/include/linux/console_struct.h b/include/linux/console_struct.h
index acc277e73e32..1d5d944ffaa7 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -170,7 +170,9 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedir *vc_uni_pagedir;
 	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
-	struct uni_screen *vc_uni_screen;	/* unicode screen content */
+	uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+	uint32_t vc_uniscr_char_size;	/* Size of *vc-uniscr_buf in 32-bit chars */
+	uint32_t *vc_uniscr_curr;	/* Pos of first char on (currently scrolled) screen */
 	/* additional information is in vt_kern.h */
 };
 

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  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
  0 siblings, 1 reply; 31+ messages in thread
From: Peter Humphrey @ 2022-12-31  9:42 UTC (permalink / raw
  To: gentoo-user

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

Morning Alan,

On Thursday, 29 December 2022 19:50:18 GMT Alan Mackenzie wrote:

> I've got a first version of a patch which attempts to handle GPM in
> conjunction with scrolling.  It's imperfect - for example, I can't get it
> to select anything the first 66 lines of the console's displayed boot-up
> messages.  Nevertheless it may be useful.
> 
> In particular, moving the mouse over an already scrolled area no longer
> corrupts that area.  Also, one can select text from a scrolled area.
> 
> To use it, please apply the supplied patch ON TOP OF the patch I posted
> here on 12th December.  Proceed as documented in that post, up until
> configuring the kernel - in Device drivers/Graphic support/Console
> display driver support, there's an extra item "Enable a working GPM for
> scrolled back scrollback buffer in System RAM" which should be enabled by
> default.  Check this is set up properly.  Then build and install the
> kernel.  Then reboot into it and try it out.
> 
> Note that it uses ~twice as much memory as the previous scrolling
> buffers, giving a total of ~three times as much memory used.  This
> shouldn't really be a problem on a modern machine with 16GB or 32GB of
> RAM.
> 
> Please let me know of any problems you encounter with this new facility,
> so that I can try to fix them.  Thanks!

Bad news, I'm afraid. After following your instructions carefully, the new 
kernel hangs early in the boot process. The first time I tried it it seemed to 
be just as the first HID message had been displayed; the second time I didn't 
see that (could be my eyes).

I've attached a photo of the console.


-- 
Regards,
Peter.

[-- Attachment #2: PXL_20221231_093123131.jpg --]
[-- Type: image/jpeg, Size: 3369294 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2022-12-31  9:42         ` Peter Humphrey
@ 2022-12-31 14:08           ` Alan Mackenzie
  2022-12-31 15:47             ` Peter Humphrey
  0 siblings, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-31 14:08 UTC (permalink / raw
  To: gentoo-user

Hello, Peter,

thanks for the reply.  The photo was extremely helpful!

On Sat, Dec 31, 2022 at 09:42:54 +0000, Peter Humphrey wrote:
> Morning Alan,

> On Thursday, 29 December 2022 19:50:18 GMT Alan Mackenzie wrote:

> > I've got a first version of a patch which attempts to handle GPM in
> > conjunction with scrolling.  It's imperfect - for example, I can't get it
> > to select anything the first 66 lines of the console's displayed boot-up
> > messages.  Nevertheless it may be useful.

[ .... ]

> > Please let me know of any problems you encounter with this new facility,
> > so that I can try to fix them.  Thanks!

> Bad news, I'm afraid. After following your instructions carefully, the
> new kernel hangs early in the boot process. The first time I tried it
> it seemed to be just as the first HID message had been displayed; the
> second time I didn't see that (could be my eyes).

> I've attached a photo of the console.

The interesting bit of the photo is right at the end, where the INIT:
version 3.05 booting appears.  The corresponding section from my
(successful) boot is:

[    4.007014] Write protecting the kernel read-only data: 24576k
[    4.008379] Freeing unused kernel image (text/rodata gap) memory: 2036K
[    4.009611] Freeing unused kernel image (rodata/data gap) memory: 1364K
[    4.010670] Run /sbin/init as init process
INIT: version 3.05 booting
[    4.041455] setfont (884) used greatest stack depth: 13584 bytes left
[    4.048471] init-early.sh (882) used greatest stack depth: 12888 bytes left

   OpenRC 0.42.1 is starting up Gentoo Linux (x86_64)

 * Mounting /proc ...
 * Mounting /run ...
 * /run/openrc: creating directory
 * /run/lock: creating directory
 * /run/lock: correcting owner

What I see is a message saying something about init-early.sh being
called.  I don't know what, exactly, is calling it, but I tracked the
file down to /lib/rc/sh/init-early.sh.  It is a part of rc-init, despite
the message being printed with a kernel style time stamp.  This message
doesn't appear in your photo.  I'm guessing init-early.sh was called,
but never returned.

init-early.sh does two things: it sets a console font (see the message
about setfont) and sets a key mapping.  It does that via symbolic links
to /etc/init.d.  The console font is more interesting, here.  It
installs the font which is in /lib/rc/console/font.

What I'm thinking here is that you might be installing a font which is
bigger than the 8x16 standard that you appear to be booting with.  To
check this, would you please do:

    # file /lib/rc/console/font

, which should return a message like:

    /lib/rc/console/font: Linux/i386 PC Screen Font v1 data, 256 characters, Unicode directory, 8x16

What is the size of this font, here (where it says 8x16 for my font)?
The reason I ask is, I've got a horrible suspicion that one of the C
functions which copies screen data when the screen size is changed can
only copy to a same sized or (possibly) _bigger_ screen (i.e. with a
smaller font).  If this is indeed the case, it might explain why you're
seeing a hang, here.

> -- 
> Regards,
> Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2022-12-31 14:08           ` Alan Mackenzie
@ 2022-12-31 15:47             ` Peter Humphrey
  2022-12-31 16:13               ` Alan Mackenzie
  0 siblings, 1 reply; 31+ messages in thread
From: Peter Humphrey @ 2022-12-31 15:47 UTC (permalink / raw
  To: gentoo-user

Hello Alan,
On Saturday, 31 December 2022 14:08:43 GMT you wrote:

> What I'm thinking here is that you might be installing a font which is
> bigger than the 8x16 standard that you appear to be booting with.  To
> check this, would you please do:
> 
>     # file /lib/rc/console/font
> 
> , which should return a message like:
> 
>     /lib/rc/console/font: Linux/i386 PC Screen Font v1 data, 256 characters,
> Unicode directory, 8x16
> 
> What is the size of this font, here (where it says 8x16 for my font)?
> The reason I ask is, I've got a horrible suspicion that one of the C
> functions which copies screen data when the screen size is changed can
> only copy to a same sized or (possibly) _bigger_ screen (i.e. with a
> smaller font).  If this is indeed the case, it might explain why you're
> seeing a hang, here.

I think you've put your finger on it:

$ file /lib/rc/console/font
/lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters, 
Unicode directory, 22x11

I use consolefont="ter-122n" from the terminus-font package. It's a long time 
since I was able to read a high-resolution screen in its native resolution.

Is there some way I can get the UEFI BIOS to boot with that font, or a larger 
one? Or perhaps let the system boot without setting a font and then changing 
it later?

Neither of those looks easy to do. I'd better have a good root through the 
BIOS options to start with.

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  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
  0 siblings, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2022-12-31 16:13 UTC (permalink / raw
  To: gentoo-user

Hello again, Peter.

On Sat, Dec 31, 2022 at 15:47:01 +0000, Peter Humphrey wrote:
> Hello Alan,
> On Saturday, 31 December 2022 14:08:43 GMT you wrote:

> > What I'm thinking here is that you might be installing a font which is
> > bigger than the 8x16 standard that you appear to be booting with.  To
> > check this, would you please do:

> >     # file /lib/rc/console/font

> > , which should return a message like:

> >     /lib/rc/console/font: Linux/i386 PC Screen Font v1 data, 256 characters,
> > Unicode directory, 8x16

> > What is the size of this font, here (where it says 8x16 for my font)?
> > The reason I ask is, I've got a horrible suspicion that one of the C
> > functions which copies screen data when the screen size is changed can
> > only copy to a same sized or (possibly) _bigger_ screen (i.e. with a
> > smaller font).  If this is indeed the case, it might explain why you're
> > seeing a hang, here.

> I think you've put your finger on it:

> $ file /lib/rc/console/font
> /lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters, 
> Unicode directory, 22x11

> I use consolefont="ter-122n" from the terminus-font package. It's a long time 
> since I was able to read a high-resolution screen in its native resolution.

> Is there some way I can get the UEFI BIOS to boot with that font, or a larger 
> one? Or perhaps let the system boot without setting a font and then changing 
> it later?

Probably, but it would be better if I just fixed the bug(s) in my changes to
the kernel.  Changing font size is something one should be able to do.

> Neither of those looks easy to do. I'd better have a good root through the 
> BIOS options to start with.

A happy new year to you (and everybody else here), and give me somewhere
between a few hours and a few days, and this bug should get fixed.

Again, thanks for such effective testing!

> -- 
> Regards,
> Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2022-12-31 16:13               ` Alan Mackenzie
@ 2022-12-31 21:49                 ` David Rosenbaum
  2023-01-01 15:13                 ` Alan Mackenzie
  1 sibling, 0 replies; 31+ messages in thread
From: David Rosenbaum @ 2022-12-31 21:49 UTC (permalink / raw
  To: gentoo-user

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

Thanks
Dave

On Sat, Dec 31, 2022, 11:13 AM Alan Mackenzie <acm@muc.de> wrote:

> Hello again, Peter.
>
> On Sat, Dec 31, 2022 at 15:47:01 +0000, Peter Humphrey wrote:
> > Hello Alan,
> > On Saturday, 31 December 2022 14:08:43 GMT you wrote:
>
> > > What I'm thinking here is that you might be installing a font which is
> > > bigger than the 8x16 standard that you appear to be booting with.  To
> > > check this, would you please do:
>
> > >     # file /lib/rc/console/font
>
> > > , which should return a message like:
>
> > >     /lib/rc/console/font: Linux/i386 PC Screen Font v1 data, 256
> characters,
> > > Unicode directory, 8x16
>
> > > What is the size of this font, here (where it says 8x16 for my font)?
> > > The reason I ask is, I've got a horrible suspicion that one of the C
> > > functions which copies screen data when the screen size is changed can
> > > only copy to a same sized or (possibly) _bigger_ screen (i.e. with a
> > > smaller font).  If this is indeed the case, it might explain why you're
> > > seeing a hang, here.
>
> > I think you've put your finger on it:
>
> > $ file /lib/rc/console/font
> > /lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters,
> > Unicode directory, 22x11
>
> > I use consolefont="ter-122n" from the terminus-font package. It's a long
> time
> > since I was able to read a high-resolution screen in its native
> resolution.
>
> > Is there some way I can get the UEFI BIOS to boot with that font, or a
> larger
> > one? Or perhaps let the system boot without setting a font and then
> changing
> > it later?
>
> Probably, but it would be better if I just fixed the bug(s) in my changes
> to
> the kernel.  Changing font size is something one should be able to do.
>
> > Neither of those looks easy to do. I'd better have a good root through
> the
> > BIOS options to start with.
>
> A happy new year to you (and everybody else here), and give me somewhere
> between a few hours and a few days, and this bug should get fixed.
>
> Again, thanks for such effective testing!
>
> > --
> > Regards,
> > Peter.
>
> --
> Alan Mackenzie (Nuremberg, Germany).
>
>

[-- Attachment #2: Type: text/html, Size: 2775 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  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
  1 sibling, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2023-01-01 15:13 UTC (permalink / raw
  To: gentoo-user

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

Hello, Peter.

On Sat, Dec 31, 2022 at 16:13:23 +0000, Alan Mackenzie wrote:
> On Sat, Dec 31, 2022 at 15:47:01 +0000, Peter Humphrey wrote:
> > Hello Alan,
> > On Saturday, 31 December 2022 14:08:43 GMT you wrote:

[ .... ]

> > I think you've put your finger on it:

> > $ file /lib/rc/console/font
> > /lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters, 
> > Unicode directory, 22x11

> > I use consolefont="ter-122n" from the terminus-font package. It's a long time 
> > since I was able to read a high-resolution screen in its native resolution.

That's a nice font.  I could get used to it if I wasn't so attached to
the 8x16 font.

> > Is there some way I can get the UEFI BIOS to boot with that font, or a larger 
> > one? Or perhaps let the system boot without setting a font and then changing 
> > it later?

> Probably, but it would be better if I just fixed the bug(s) in my changes to
> the kernel.  Changing font size is something one should be able to do.

OK, the bug was that I was trying to free memory by calling the wrong
kernel function kfree, when it should have been kvfree.  With that
correction, the kernel now boots in 11x22, at least for me.

> > Neither of those looks easy to do. I'd better have a good root through the 
> > BIOS options to start with.

> A happy new year to you (and everybody else here), and give me somewhere
> between a few hours and a few days, and this bug should get fixed.

The included patch is still imperfect.  When booting in 11x22, it doesn't
handle the early boot messages at all well.  Also, I'm a little confused
by what a low-level scroll function is meant to do - sometimes, scrolling
happens when you type a CR, and want a line on the screen to be space
filled.  Other times, you type <shift><PgUp> and don't want any space
filling to happen.  So I'm not convinced that scrolling, invoked by, say,
an editor program, will work correctly.

> Again, thanks for such effective testing!

So, please try the attached patch, which is "at the same level" as my
patch from three days ago.  For anybody who wants to try it new, I'm
repeating the instructions from that post:

>>>> To use it, please apply the supplied patch ON TOP OF the patch I
>>>> posted here on 12th December.  Proceed as documented in that post,
>>>> up until configuring the kernel - in Device drivers/Graphic
>>>> support/Console display driver support, there's an extra item
>>>> "Enable a working GPM for scrolled back scrollback buffer in System
>>>> RAM" which should be enabled by default.  Check this is set up
>>>> properly.  Then build and install the kernel.  Then reboot into it
>>>> and try it out.

> > -- 
> > Regards,
> > Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: diff.20230101.diff --]
[-- Type: text/plain, Size: 26673 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index d7aadca5107d..7bea89f03e75 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -291,6 +291,28 @@ 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)
+		return (unsigned short *)(vc->vc_origin + offset);
+	else {
+		scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr;
+		if (scrolled_expanse < 0)
+			scrolled_expanse += 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)
@@ -300,6 +322,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.. */
@@ -321,101 +344,106 @@ 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;
 
-	/* allocate everything in one go */
-	memsize = cols * rows * sizeof(char32_t);
-	memsize += rows * sizeof(char32_t *);
-	p = vzalloc(memsize);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	vc->vc_uniscr_char_size =
+		cols * rows
+		+ ((vc->vc_softback_end - vc->vc_softback_buf) / 2);
+#else
+	vc->vc_uniscr_char_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * vc->vc_uniscr_char_size);
+	vc->vc_uniscr_buf = p;
 	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;
-}
-
-static void vc_uniscr_free(struct uni_screen *uniscr)
-{
-	vfree(uniscr);
+		return -ENOMEM;
+	vc->vc_uniscr_curr = p;
+	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);
-
-	if (uniscr)
-		uniscr->lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr
+			+ vc->state.y * vc->vc_cols
+			+ vc->state.x;
+		if (pos >= vc_uniscr_buf_end(vc))
+			pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		pos -= vc->vc_softback_lines * vc->vc_cols;
+		if (pos < vc->vc_uniscr_buf)
+			pos += vc->vc_uniscr_char_size;
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr + y * cols;
 
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
-
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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);
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr + y * cols;
 
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
-
-		memcpy(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 + vc->state.y * vc->vc_cols;
 
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
 		memset32(&ln[x], ' ', nr);
 	}
 }
@@ -423,77 +451,71 @@ 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 + y * cols;
 
-		while (nr--)
-			memset32(uniscr->lines[y++], ' ', cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
+		while (nr--) {
+			if (ln >= vc_uniscr_buf_end(vc))
+				ln -= vc->vc_uniscr_char_size;
+			memset32(ln, ' ', cols);
+			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) {
-			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;
+	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) {
+			vc->vc_uniscr_curr += nr * cols;
+			if (vc->vc_uniscr_curr >= vc_uniscr_buf_end(vc))
+				vc->vc_uniscr_curr -= vc->vc_uniscr_char_size;
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+			vc->vc_uniscr_curr -= nr * cols;
+			if (vc->vc_uniscr_curr < vc->vc_uniscr_buf)
+				vc->vc_uniscr_curr += vc->vc_uniscr_char_size;
+			d = 0;
+		} else {
+			sz = b - t;
+			src = vc->vc_uniscr_curr + t * cols;
+			if (dir == SM_UP) {
+				dest = vc->vc_uniscr_curr - nr * cols;
+				clear = b - nr;
+				d = nr;
+			} else {
+				dest = vc->vc_uniscr_curr + nr * cols;
+				clear = t;
+				d = sz - nr;
+			}
+			i = nr;
+			while (i--) {
+				if (dest >= vc_uniscr_buf_end(vc))
+					dest -= vc->vc_uniscr_char_size;
+				if (src >= vc_uniscr_buf_end(vc))
+					src -= vc->vc_uniscr_char_size;
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				dest += cols;
+				src += cols;
 			}
-			uniscr->lines[t + j] = tmp;
 		}
-		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);
 	}
 }
 
@@ -505,7 +527,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;
 
@@ -517,11 +538,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;
 
 	/*
@@ -533,14 +553,15 @@ 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 + y * vc->vc_cols;
+		if (line >= vc_uniscr_buf_end(vc))
+			line -= vc->vc_uniscr_char_size;
 		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;
 }
 
@@ -552,12 +573,26 @@ 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 + row * vc->vc_cols + col);
+	if (pos >= vc_uniscr_buf_end(vc))
+		pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	pos -= vc->vc_softback_lines * vc->vc_cols;
+	if (pos < vc->vc_uniscr_buf)
+		pos += vc->vc_uniscr_char_size;
+#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) {
 		/*
@@ -567,58 +602,57 @@ 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
@@ -874,11 +908,12 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 		  ? vc->vc_softback_curr
 		  : vc->vc_softback_curr
 		    - (vc->vc_softback_end - vc->vc_softback_buf)
-		: vc->vc_origin;
+		: 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;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
 	for(;;) {
 		u16 attrib = scr_readw(p) & 0xff00;
 		int startx = xx;
@@ -905,10 +940,6 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 			break;
 		xx = 0;
 		yy++;
-		/* if (vc->vc_sw->con_getxy) { */
-		/* 	p = (u16 *)start; */
-		/* 	start = vc->vc_sw->con_getxy(vc, start, NULL, NULL); */
-		/* } */
 	}
 }
 #else
@@ -1028,51 +1059,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);
 }
 
@@ -1273,7 +1321,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;
 	}
 
@@ -1401,11 +1448,13 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 {
 	struct vt_notifier_param param;
 	struct vc_data *vc;
+	int err;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
 	unsigned long new_end;
 	unsigned long new_in, new_top;
 	unsigned long in_residue;
 	unsigned short *d;
-	int err;
+#endif
 
 	WARN_CONSOLE_UNLOCKED();
 	if (currcons >= MAX_NR_CONSOLES)
@@ -1425,6 +1474,7 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 					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);
 			}
 		}
 		if (vc->vc_softback_buf
@@ -1513,6 +1563,7 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 		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:
@@ -1534,6 +1585,72 @@ 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_rows,
+				       unsigned int new_cols)
+{
+	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;
+	long tmp;
+
+	tmp = vc->vc_softback_curr - vc->vc_softback_top;
+	if (tmp < 0)
+		tmp += 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_softback_end - vc->vc_softback_buf)
+		/ vc->vc_size_row
+		+ new_rows;
+	new_lines = min(old_lines, new_uniscr_rows);
+	copy_cols = min(old_cols, new_cols);
+
+	dest = vc->vc_uniscr_curr - (new_lines - new_rows) * new_cols;
+	if (dest < vc->vc_uniscr_buf)
+		dest += vc->vc_uniscr_char_size;
+	src = old_uniscr_curr - (old_lines - old_rows) * old_cols;
+	if (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);
+			dest += new_cols;
+			if (dest >= vc->vc_uniscr_buf
+			    + vc->vc_uniscr_char_size)
+				dest -= vc->vc_uniscr_char_size;
+			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
@@ -1558,7 +1675,7 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	struct uni_screen *new_uniscr = NULL;
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
 
 	WARN_CONSOLE_UNLOCKED();
 
@@ -1602,14 +1719,6 @@ 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();
 
@@ -1619,10 +1728,14 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	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;
 	}
 
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_rows, new_cols)) != 0)
+		return err;
+
 	vc->vc_rows = new_rows;
 	vc->vc_cols = new_cols;
 	vc->vc_size_row = new_row_size;
@@ -1653,11 +1766,6 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		first_copied_row = 0;
 	end = old_origin + old_row_size * min(old_rows, new_rows);
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    get_vc_uniscr(vc), rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
-
 	update_attr(vc);
 
 	while (old_origin < end) {
@@ -1757,8 +1865,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;
@@ -3457,11 +3565,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;
@@ -3758,6 +3863,10 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
 
 #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 */
@@ -5119,10 +5228,19 @@ 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 + y * vc->vc_cols;
 
-	if (uniscr)
-		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+	if (vc->vc_uniscr_curr) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 4a343877d70c..1abba103d7da 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -121,6 +121,21 @@ config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
 	  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/include/linux/console_struct.h b/include/linux/console_struct.h
index acc277e73e32..1d5d944ffaa7 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -170,7 +170,9 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedir *vc_uni_pagedir;
 	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
-	struct uni_screen *vc_uni_screen;	/* unicode screen content */
+	uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+	uint32_t vc_uniscr_char_size;	/* Size of *vc-uniscr_buf in 32-bit chars */
+	uint32_t *vc_uniscr_curr;	/* Pos of first char on (currently scrolled) screen */
 	/* additional information is in vt_kern.h */
 };
 

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2023-01-01 15:13                 ` Alan Mackenzie
@ 2023-01-01 15:38                   ` Peter Humphrey
  2023-01-26 20:28                   ` Alan Mackenzie
  1 sibling, 0 replies; 31+ messages in thread
From: Peter Humphrey @ 2023-01-01 15:38 UTC (permalink / raw
  To: gentoo-user

On Sunday, 1 January 2023 15:13:02 GMT Alan Mackenzie wrote:

> > > $ file /lib/rc/console/font
> > > /lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters,
> > > Unicode directory, 22x11
> > > 
> > > I use consolefont="ter-122n" from the terminus-font package. It's a long
> > > time since I was able to read a high-resolution screen in its native
> > > resolution.
> That's a nice font.  I could get used to it if I wasn't so attached to
> the 8x16 font.

[OT]
Yes, I like it. I'd like it even better if I could redefine the zero character 
without the central dot (which I believe is an American affectation) because 
the central dot makes the 0 resemble an 8 too closely. Instead I'd change the 
outline shape. Then Os and 0s would more resemble their usual printed and GUI 
forms. I know this wouldn't be possible on smaller sizes, but it should be 
possible at 11x22.

In fact I did try doing that once with a font editor, but it couldn't handle 
the whole font set properly. Perhaps I should look into it again.
[/OT]

--->8

> The included patch is still imperfect.  When booting in 11x22, it doesn't
> handle the early boot messages at all well.  Also, I'm a little confused
> by what a low-level scroll function is meant to do - sometimes, scrolling
> happens when you type a CR, and want a line on the screen to be space
> filled.  Other times, you type <shift><PgUp> and don't want any space
> filling to happen.  So I'm not convinced that scrolling, invoked by, say,
> an editor program, will work correctly.
> 

Just a quick test shows it to work here. If I find anything I'll raise a flag.

Many thanks again, Alan.

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  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
  1 sibling, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2023-01-26 20:28 UTC (permalink / raw
  To: gentoo-user

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

Hello, Peter.

On Sun, Jan 01, 2023 at 15:13:02 +0000, Alan Mackenzie wrote:
> On Sat, Dec 31, 2022 at 16:13:23 +0000, Alan Mackenzie wrote:
> > On Sat, Dec 31, 2022 at 15:47:01 +0000, Peter Humphrey wrote:
> > > Hello Alan,
> > > On Saturday, 31 December 2022 14:08:43 GMT you wrote:

> [ .... ]

> > > I think you've put your finger on it:

> > > $ file /lib/rc/console/font
> > > /lib/rc/console/font: Linux/i386 PC Screen Font v2 data, 256 characters, 
> > > Unicode directory, 22x11

> > > I use consolefont="ter-122n" from the terminus-font package. It's a long time 
> > > since I was able to read a high-resolution screen in its native resolution.

> That's a nice font.  I could get used to it if I wasn't so attached to
> the 8x16 font.

[ .... ]

> The included patch is still imperfect.  When booting in 11x22, it doesn't
> handle the early boot messages at all well.  Also, I'm a little confused
> by what a low-level scroll function is meant to do - sometimes, scrolling
> happens when you type a CR, and want a line on the screen to be space
> filled.  Other times, you type <shift><PgUp> and don't want any space
> filling to happen.  So I'm not convinced that scrolling, invoked by, say,
> an editor program, will work correctly.

OK, I'm now including a revised patch which is less imperfect.  ;-)

To use it, please apply it to the vanilla gentoo-sources 5.15.88 (or
similar) with the usual:

    patch -p1 < 5.15.80-GPM.20230126.diff

, followed by configuring in CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
and CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM, and setting an
appropriate scrollback buffer size as already done.  The ..._GPM is new,
but is enabled by default.  Build and install the kernel, then reboot
into it, to try it out.

I think it will work better than the previous patch.

> >>>> To use it, please apply the supplied patch ON TOP OF the patch I
> >>>> posted here on 12th December.  Proceed as documented in that post,
> >>>> up until configuring the kernel - in Device drivers/Graphic
> >>>> support/Console display driver support, there's an extra item
> >>>> "Enable a working GPM for scrolled back scrollback buffer in System
> >>>> RAM" which should be enabled by default.  Check this is set up
> >>>> properly.  Then build and install the kernel.  Then reboot into it
> >>>> and try it out.

Again, on any problems please let me know and I'll try to fix them.  As
ever, there are no guarantees, etc., etc., etc.  My only promise is that
there's no malicious code in the patch.

> > > -- 
> > > Regards,
> > > Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 5.15.80-GPM.20230126.diff --]
[-- Type: text/plain, Size: 51315 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index b8f5bc19416d..58332d1dd923 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,38 @@ 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 {
+		/* OLD STOUGH, 2023-01-26 */
+		/* scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr; */
+		/* if (scrolled_expanse < 0) */
+		/* 	scrolled_expanse += vc->vc_softback_end */
+		/* 		- vc->vc_softback_buf; */
+		/* NEW STOUGH, 2023-01-26 */
+		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;
+		/* END OF NEW STOUGH */
+		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 +330,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 +352,110 @@ 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);
-
-	if (uniscr)
-		uniscr->lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr
+			+ vc->state.y * vc->vc_cols
+			+ vc->state.x;
+		if (pos >= vc_uniscr_buf_end(vc))
+			pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		pos -= vc->vc_softback_lines * vc->vc_cols;
+		if (pos < vc->vc_uniscr_buf)
+			pos += vc->vc_uniscr_char_size;
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr + y * cols;
 
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
-
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 + y * cols;
 
-		memcpy(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#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 + vc->state.y * vc->vc_cols;
 
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
 		memset32(&ln[x], ' ', nr);
 	}
 }
@@ -418,77 +463,88 @@ 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 + y * cols;
 
-		while (nr--)
-			memset32(uniscr->lines[y++], ' ', cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
+		while (nr--) {
+			if (ln >= vc_uniscr_buf_end(vc))
+				ln -= vc->vc_uniscr_char_size;
+			memset32(ln, ' ', cols);
+			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) {
-			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;
+	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) {
+			vc->vc_uniscr_curr += nr * cols;
+			if (vc->vc_uniscr_curr >= vc_uniscr_buf_end(vc))
+				vc->vc_uniscr_curr -= vc->vc_uniscr_char_size;
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+			vc->vc_uniscr_curr -= nr * cols;
+			if (vc->vc_uniscr_curr < vc->vc_uniscr_buf)
+				vc->vc_uniscr_curr += vc->vc_uniscr_char_size;
+			d = 0;
+		} else if (dir == SM_UP) {
+			sz = b - t;
+			src = vc->vc_uniscr_curr + t * cols;
+			if (src >= vc_uniscr_buf_end(vc))
+				src -= vc->vc_uniscr_char_size;
+			dest = src - nr * cols;
+			if (dest < vc->vc_uniscr_buf)
+				dest += vc->vc_uniscr_char_size;
+			i = b - t;
+			while (i--) {
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				src += cols;
+				if (src >= vc_uniscr_buf_end(vc))
+					src -= vc->vc_uniscr_char_size;
+				dest += cols;
+				if (dest >= vc_uniscr_buf_end(vc))
+					dest -= vc->vc_uniscr_char_size;
 			}
-			uniscr->lines[t + j] = tmp;
+			d = nr;
+			clear = b - nr;
+		} else {
+			src = vc->vc_uniscr_curr + b * cols;
+			if (src >= vc_uniscr_buf_end(vc))
+				src -= vc->vc_uniscr_char_size;
+			dest = src + nr * cols;
+			if (dest >= vc_uniscr_buf_end(vc))
+				dest -= vc->vc_uniscr_char_size;
+			i = b - t;
+			while (i--) {
+				src -= cols;
+				if (src < vc->vc_uniscr_buf)
+					src += vc->vc_uniscr_char_size;
+				dest -= cols;
+				if (dest < vc->vc_uniscr_buf)
+					dest += vc->vc_uniscr_char_size;
+				memcpy(dest, src, cols * sizeof(uint32_t));
+			}
+			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 +556,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 +567,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 +582,15 @@ 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 + y * vc->vc_cols;
+		if (line >= vc_uniscr_buf_end(vc))
+			line -= vc->vc_uniscr_char_size;
 		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 +602,26 @@ 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 + row * vc->vc_cols + col);
+	if (pos >= vc_uniscr_buf_end(vc))
+		pos -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	pos -= vc->vc_softback_lines * vc->vc_cols;
+	if (pos < vc->vc_uniscr_buf)
+		pos += vc->vc_uniscr_char_size;
+#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 +631,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 = vc->vc_softback_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 +906,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 +925,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 +1015,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 +1024,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 +1088,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 +1279,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 +1350,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 +1364,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 +1376,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 +1390,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);
@@ -1110,14 +1478,70 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	struct vt_notifier_param param;
 	struct vc_data *vc;
 	int err;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned long new_end;
+	unsigned long new_in, new_top;
+	unsigned long in_residue;
+	unsigned short *d;
+#endif
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
 	if (vc_cons[currcons].d)
+	{
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		vc = vc_cons[currcons].d;
+		if (!vc->vc_softback_size) {
+			/* vc was partially initialized by __init. */
+			vc->vc_softback_size = console_soft_scrollback_size;
+			vc->vc_softback_buf =
+				(unsigned long)/*k*/vzalloc(vc->vc_softback_size/*, GFP_KERNEL*/);
+			if (vc->vc_softback_buf) {
+				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);
+			}
+		}
+		if (vc->vc_softback_buf
+		    && (vc->vc_softback_end - vc->vc_softback_buf) % vc->vc_size_row) {
+			new_end = vc->vc_softback_buf + vc->vc_softback_size
+				- (vc->vc_softback_size % vc->vc_size_row);
+			new_top = vc->vc_softback_top
+				- (vc->vc_softback_top - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			in_residue = (vc->vc_softback_in - vc->vc_softback_buf)
+				% vc->vc_size_row;
+			new_in = vc->vc_softback_in
+				+ (in_residue ? vc->vc_size_row - in_residue : 0);
+			if (new_in == new_end)
+				new_in = vc->vc_softback_buf;
+			if (new_top == new_in)
+				new_top += vc->vc_size_row;
+			else if ((new_in > new_top
+				  && (new_in - new_top <= vc->vc_size_row)))
+				new_top += 2 * vc->vc_size_row;
+			if (new_top >= new_end)
+				new_top -= (new_end - vc->vc_softback_buf);
+			vc->vc_softback_lines = 0;
+			vc->vc_softback_curr = new_in;
+
+			d = (u16 *)vc->vc_softback_in;
+			vc->vc_softback_end = new_end;
+			vc->vc_softback_in = new_in;
+			vc->vc_softback_top = new_top;
+			while (d != (u16 *)new_in) {
+				scr_writew (0x0020, d); /* How about non-VGA? */
+				if (++d == (u16 *)new_end)
+					d = (u16 *)vc->vc_softback_buf;
+			}
+		}
+#endif
 		return 0;
+	}
 
 	/* due to the granularity of kmalloc, we waste some memory here */
 	/* the alloc is done in two steps, to optimize the common situation
@@ -1157,6 +1581,19 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	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)/*k*/vzalloc(vc->vc_softback_size/*, GFP_KERNEL*/);
+	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 +1614,78 @@ 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 > new_rows) {
+		dest -= (new_lines - new_rows) * new_cols;
+		if (dest < vc->vc_uniscr_buf)
+			dest += vc->vc_uniscr_char_size;
+	}		
+	src = old_uniscr_curr;
+	if (new_lines > new_rows) {
+		src -= (new_lines - new_rows) * old_cols;
+		if (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);
+			dest += new_cols;
+			if (dest >= vc->vc_uniscr_buf
+			    + vc->vc_uniscr_char_size)
+				dest -= vc->vc_uniscr_char_size;
+			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 +1706,18 @@ 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;
-
+	unsigned short *oldscreen, *newscreen, *d;
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	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 +1731,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 +1760,93 @@ 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;
-		}
+#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(vc->vc_softback_size);
+	new_softback_buf = (unsigned long)temp_new_softback_buf;
+	if (!new_softback_buf)
+		return -ENOMEM;
+	new_softback_end = new_softback_buf + vc->vc_softback_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 = vc->vc_softback_size / new_size_row;
+	copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
 
 	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) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+			con_softback_note(vc, 0,
+					  old_rows - new_rows);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 old_rows - new_rows);
+#endif
+#endif
 			/*
 			 * 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, 0,
+					  vc->state.y - new_rows/2);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 vc->state.y - new_rows/2);
+#endif
+#endif
 			/*
 			 * 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,
-			    get_vc_uniscr(vc), 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;
+
+	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 +1856,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 /* (unsigned long)newscreen STOUGH, 2023-01-26 */;
+	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 +1990,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 +2236,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 +2248,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 +3518,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 +3690,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 +3736,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 +3961,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 +3986,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 +4049,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);
@@ -4140,7 +4749,7 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
 			con_driver->desc = desc;
 			con_driver->node = i;
 			con_driver->flag = CON_DRIVER_FLAG_MODULE |
-			                   CON_DRIVER_FLAG_INIT;
+					   CON_DRIVER_FLAG_INIT;
 			con_driver->first = first;
 			con_driver->last = last;
 			retval = 0;
@@ -4441,7 +5050,10 @@ void do_unblank_screen(int leaving_gfx)
 	if (console_blank_hook)
 		console_blank_hook(0);
 	set_palette(vc);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
 }
 EXPORT_SYMBOL(do_unblank_screen);
@@ -4741,10 +5353,19 @@ 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 + y * vc->vc_cols;
 
-	if (uniscr)
-		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+	if (vc->vc_uniscr_curr) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
+		return ln[x];
+	}
 	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index fcc46380e7c9..1abba103d7da 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -98,6 +98,44 @@ config FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
 
 	  If unsure, select n.
 
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	bool "Enable Scrollback Buffer in System RAM"
+	depends on FB=y && FRAMEBUFFER_CONSOLE
+	default y
+	help
+	  This option creates a scrollback buffer for each framebuffer console.
+          These buffers are allocated dynamically during initialisation.
+
+	  If you want this feature, say 'Y' here and enter in
+          FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+          allocate for each buffer.  If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+	int "Scrollback Buffer Size (in KB)"
+	depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	range 1 1024
+	default "128"
+	help
+	  Enter the amount of System RAM in kilobytes to allocate for the
+          scrollback buffer of each framebuffer console.  Each character
+	  position on the video takes 2 bytes of storage.  128kB will give you
+	  approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       bool "Enable a working GPM for scrolled back scrollback buffer in System RAM"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       default y
+       help
+         This option buffers up Unicode characters corresponding to the glyphs
+         displayed by the scrollback buffer.  This enables the GPM mouse driver
+         (or similar) to copy characters from a scrolled back buffer.
+
+         A buffer is created for each framebuffer console, this buffer being
+         approximately twice as big as the buffer size specified in
+         FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+         If unsure, say 'Y'.
+
 config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index e035a63bbe5b..a4fab71c5853 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -3152,6 +3152,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index d5b9c8d40c18..281da9b3797f 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,11 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedir *vc_uni_pagedir;
 	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
-	struct uni_screen *vc_uni_screen;	/* unicode screen content */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	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 */
+#endif
 	/* additional information is in vt_kern.h */
 };
 
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index b5ab452fca5b..1a11c9868f8e 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -115,6 +115,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2023-01-26 20:28                   ` Alan Mackenzie
@ 2023-01-27 12:24                     ` Peter Humphrey
  2023-01-27 22:31                       ` Alan Mackenzie
  0 siblings, 1 reply; 31+ messages in thread
From: Peter Humphrey @ 2023-01-27 12:24 UTC (permalink / raw
  To: gentoo-user

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

On Thursday, 26 January 2023 20:28:36 GMT Alan Mackenzie wrote:

--->8

> Again, on any problems please let me know and I'll try to fix them.  As
> ever, there are no guarantees, etc., etc., etc.  My only promise is that
> there's no malicious code in the patch.

Good news! Well, partly...  :)

I applied the new patch to 5.15.88 and it seems fine. I'll have to test it in 
use and let you know.

Patching 6.1.8 failed, though, whereas the previous 5.15.80-scroll.
20221212.diff succeeded:

# patch -p1 < /usr/local/src/5.15.80-GPM.20230126.diff
patching file drivers/tty/vt/vt.c
Hunk #37 succeeded at 4748 (offset -1 lines).
Hunk #38 succeeded at 5049 (offset -1 lines).
Hunk #39 FAILED at 5353.
1 out of 39 hunks FAILED -- saving rejects to file drivers/tty/vt/vt.c.rej
patching file drivers/video/console/Kconfig
Hunk #1 succeeded at 99 (offset 1 line).
patching file drivers/video/fbdev/core/fbcon.c
Hunk #1 succeeded at 3171 (offset 19 lines).
patching file include/linux/console_struct.h
Hunk #2 FAILED at 170.
1 out of 2 hunks FAILED -- saving rejects to file include/linux/
console_struct.h.rej
patching file include/linux/vt_kern.h
Hunk #1 succeeded at 114 (offset -1 lines).

I've attached the reject files.

-- 
Regards,
Peter.

[-- Attachment #2: vt.c.rej --]
[-- Type: text/x-reject, Size: 796 bytes --]

--- drivers/tty/vt/vt.c
+++ drivers/tty/vt/vt.c
@@ -5353,10 +5965,19 @@ 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 + y * vc->vc_cols;
 
-	if (uniscr)
-		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+	if (vc->vc_uniscr_curr) {
+		if (ln >= vc_uniscr_buf_end(vc))
+			ln -= vc->vc_uniscr_char_size;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		ln -= vc->vc_softback_lines * vc->vc_cols;
+		if (ln < vc->vc_uniscr_buf)
+			ln += vc->vc_uniscr_char_size;
+#endif
+		return ln[x];
+	}
 	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);

[-- Attachment #3: console_struct.h.rej --]
[-- Type: text/x-reject, Size: 748 bytes --]

--- include/linux/console_struct.h
+++ include/linux/console_struct.h
@@ -170,7 +181,11 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedir *vc_uni_pagedir;
 	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
-	struct uni_screen *vc_uni_screen;	/* unicode screen content */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	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 */
+#endif
 	/* additional information is in vt_kern.h */
 };
 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2023-01-27 12:24                     ` Peter Humphrey
@ 2023-01-27 22:31                       ` Alan Mackenzie
  2023-01-28 14:41                         ` Peter Humphrey
  0 siblings, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2023-01-27 22:31 UTC (permalink / raw
  To: gentoo-user

Hello, Peter.

On Fri, Jan 27, 2023 at 12:24:41 +0000, Peter Humphrey wrote:
> On Thursday, 26 January 2023 20:28:36 GMT Alan Mackenzie wrote:

> --->8

> > Again, on any problems please let me know and I'll try to fix them.  As
> > ever, there are no guarantees, etc., etc., etc.  My only promise is that
> > there's no malicious code in the patch.

> Good news! Well, partly...  :)

> I applied the new patch to 5.15.88 and it seems fine. I'll have to test it in 
> use and let you know.

I have since found a few bugs in that patch.  Mainly they're to do with
losing 18 lines[*] GPM-ability at the earliest boot messages, when
changing font size, and things like that.

[*] 18 lines is the difference between 49 lines (from an 11x22 font such
as ter-122n) and 67 lines (from an 8x16 font like lat1-16) which fit on
the screen.

I've also discovered that when CONFIG_LOGO (i.e. displaying Tux at boot
up) is enabled, all boot messages which preceded the Tuxes become
invisible to GPM, and they cannot be selected.  I don't (yet) know why
this is happening, but the obvious workaround is to disable CONFIG_LOGO
in one's kernel configuration.

I've found yet another bug which it's taken me a few hours to determine
is nothing to do with me.  It goes like this: boot up in an 11x22 font
such as ter-122n, and move the mouse on that screen.  Run the command
setfont lat1-16, which will resize the existing text.  The GPM mouse is
now restricted to the 174x49 rectangle in the top left of the screen.
This is the bug.  To free it, switch to a different tty and move the
mouse there.  On returning to the first tty, the mouse can now be moved
over the entire screen.

The cause of this bug is that GPM does not connect up with screen
resizing events.  The only time it determines a tty's size is when it
detects the tty has been switched.  Not a big bug, but somewhat
annoying.

> Patching 6.1.8 failed, though, whereas the previous 5.15.80-scroll.
> 20221212.diff succeeded:

I haven't actually looked at any versions except for 5.15.80 and 5.15.88
so far.

> # patch -p1 < /usr/local/src/5.15.80-GPM.20230126.diff
> patching file drivers/tty/vt/vt.c
> Hunk #37 succeeded at 4748 (offset -1 lines).
> Hunk #38 succeeded at 5049 (offset -1 lines).
> Hunk #39 FAILED at 5353.
> 1 out of 39 hunks FAILED -- saving rejects to file drivers/tty/vt/vt.c.rej
> patching file drivers/video/console/Kconfig
> Hunk #1 succeeded at 99 (offset 1 line).
> patching file drivers/video/fbdev/core/fbcon.c
> Hunk #1 succeeded at 3171 (offset 19 lines).
> patching file include/linux/console_struct.h
> Hunk #2 FAILED at 170.
> 1 out of 2 hunks FAILED -- saving rejects to file include/linux/
> console_struct.h.rej
> patching file include/linux/vt_kern.h
> Hunk #1 succeeded at 114 (offset -1 lines).

> I've attached the reject files.

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

Anyhow, I'll probably post another patch with corrections in the next
day or two.  Thanks for the testing!

> -- 
> Regards,
> Peter.

> --- drivers/tty/vt/vt.c
> +++ drivers/tty/vt/vt.c
> @@ -5353,10 +5965,19 @@ 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 + y * vc->vc_cols;
>  
> -	if (uniscr)
> -		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
> +	if (vc->vc_uniscr_curr) {
> +		if (ln >= vc_uniscr_buf_end(vc))
> +			ln -= vc->vc_uniscr_char_size;
> +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
> +		ln -= vc->vc_softback_lines * vc->vc_cols;
> +		if (ln < vc->vc_uniscr_buf)
> +			ln += vc->vc_uniscr_char_size;
> +#endif
> +		return ln[x];
> +	}
>  	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
>  }
>  EXPORT_SYMBOL_GPL(screen_glyph_unicode);

> --- include/linux/console_struct.h
> +++ include/linux/console_struct.h
> @@ -170,7 +181,11 @@ struct vc_data {
>  	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
>  	struct uni_pagedir *vc_uni_pagedir;
>  	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
> -	struct uni_screen *vc_uni_screen;	/* unicode screen content */
> +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
> +	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 */
> +#endif
>  	/* additional information is in vt_kern.h */
>  };
>  

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  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-09-09 15:21                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling David Rosenbaum
  0 siblings, 2 replies; 31+ messages in thread
From: Peter Humphrey @ 2023-01-28 14:41 UTC (permalink / raw
  To: gentoo-user

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

--->8

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

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

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New (?final) version of the patch - with GPM handling.
  2023-01-28 14:41                         ` Peter Humphrey
@ 2023-02-03 18:56                           ` Alan Mackenzie
  2023-10-04 13:16                             ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Alan Mackenzie
  2024-01-24 10:00                             ` Alan Mackenzie
  2023-09-09 15:21                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling David Rosenbaum
  1 sibling, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2023-02-03 18:56 UTC (permalink / raw
  To: gentoo-user

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

Hello, Peter.

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

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

> --->8

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

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

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

Just a quick reminder of how to use these files for anybody else who
might be interested:
(i) cd /usr/src/linux-5.15.88-gentoo, or similar.  (Or
  ...-6.1.x-gentoo).
(ii) patch -p1 < 5.15.80-GPM.20230203.diff (or the other one).
(iii) Configure the kernel as normal.  Additionally, under Device
  drivers/Graphic Support/Console display driver support, enable
  CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK, set the buffer size to
  taste (it's default is 128 kB) and accept the default enablement of
  CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM.
(iv) Build the kernel and install it into your boot manager.
(v) Reboot and enjoy!  You can now use GPM in scrolled consoles.

> -- 
> Regards,
> Peter.

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 5.15.80-GPM.20230203.diff --]
[-- Type: text/plain, Size: 51703 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index b8f5bc19416d..5d5508209164 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -134,6 +134,11 @@ const struct consw *conswitchp;
 #define DEFAULT_BELL_DURATION	(HZ/8)
 #define DEFAULT_CURSOR_BLINK_MS	200
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static unsigned int console_soft_scrollback_size =
+	1024 * CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE;
+#endif
+
 struct vc vc_cons [MAX_NR_CONSOLES];
 
 #ifndef VT_SINGLE_DRIVER
@@ -286,8 +291,31 @@ static inline bool con_should_update(const struct vc_data *vc)
 static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned long softback_pos, scrolled_expanse;
+
+	if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+		return (unsigned short *)(vc->vc_origin + offset);
+	else {
+		if (vc->vc_softback_in >= vc->vc_softback_curr)
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr;
+		else
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr
+				+ vc->vc_softback_end - vc->vc_softback_buf;
+		if (offset >= scrolled_expanse)
+			return (unsigned short *)(vc->vc_origin
+						  + (offset - scrolled_expanse));
+		else {
+			softback_pos = vc->vc_softback_curr + offset;
+			if (softback_pos >= vc->vc_softback_end)
+				softback_pos -= vc->vc_softback_end
+					- vc->vc_softback_buf;
+		}
+	}
+	return (unsigned short *)softback_pos;
+#else
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -295,6 +323,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 	else
 		p = vc->vc_sw->con_screen_pos(vc, offset);
 	return p;
+#endif
 }
 
 /* Called  from the keyboard irq path.. */
@@ -316,101 +345,96 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-#ifdef NO_VC_UNI_SCREEN
-/* this disables and optimizes related code away at compile time */
-#define get_vc_uniscr(vc) NULL
-#else
-#define get_vc_uniscr(vc) vc->vc_uni_screen
-#endif
-
 #define VC_UNI_SCREEN_DEBUG 0
 
 typedef uint32_t char32_t;
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
-struct uni_screen {
-	char32_t *lines[0];
-};
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static struct uni_screen *vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int rows)
 {
-	struct uni_screen *uniscr;
-	void *p;
-	unsigned int memsize, i;
+	uint32_t *p;
+	unsigned int new_size;	/* In 32-bit characters */
 
-	/* allocate everything in one go */
-	memsize = cols * rows * sizeof(char32_t);
-	memsize += rows * sizeof(char32_t *);
-	p = vzalloc(memsize);
-	if (!p)
-		return NULL;
-
-	/* initial line pointers */
-	uniscr = p;
-	p = uniscr->lines + rows;
-	for (i = 0; i < rows; i++) {
-		uniscr->lines[i] = p;
-		p += cols * sizeof(char32_t);
-	}
-	return uniscr;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(struct uni_screen *uniscr)
-{
-	vfree(uniscr);
+	num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+	new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+	new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+	if (!p)
+		return -ENOMEM;
+	vc->vc_uniscr_buf = p;
+	vc->vc_uniscr_curr = p;
+	vc->vc_uniscr_char_size = new_size;
+	memset32(p, ' ', new_size); /* Probably redundant. */
+	return 0;
 }
 
-static void vc_uniscr_set(struct vc_data *vc, struct uni_screen *new_uniscr)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-	vc_uniscr_free(vc->vc_uni_screen);
-	vc->vc_uni_screen = new_uniscr;
+	kvfree(vc->vc_uniscr_buf);
+	vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, char32_t uc)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
+	uint32_t *pos;
 
-	if (uniscr)
-		uniscr->lines[vc->state.y][vc->state.x] = uc;
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr;
+		UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[x], ' ', nr);
 	}
 }
 
 static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memcpy(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memcpy(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[cols - nr], ' ', nr);
 	}
 }
 
+/* FIXME!!!  We need to check that NR never goes beyond the current line end.  !!! */
 static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
 				 unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-	if (uniscr) {
-		char32_t *ln = uniscr->lines[vc->state.y];
+	if (vc->vc_uniscr_buf) {
+		uint32_t *ln = vc->vc_uniscr_curr;
 
+		UNISCR_PLUS(ln, vc->state.y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
 		memset32(&ln[x], ' ', nr);
 	}
 }
@@ -418,77 +442,70 @@ static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
 				  unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-	if (uniscr) {
+	if (vc->vc_uniscr_buf) {
 		unsigned int cols = vc->vc_cols;
+		uint32_t *ln = vc->vc_uniscr_curr;
 
-		while (nr--)
-			memset32(uniscr->lines[y++], ' ', cols);
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		while (nr--) {
+			memset32(ln, ' ', cols);
+			UNISCR_PLUS(ln, cols);
+		}
 	}
 }
 
 static void vc_uniscr_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 			     enum con_scroll dir, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-
-	if (uniscr) {
-		unsigned int i, j, k, sz, d, clear;
-
-		sz = b - t;
-		clear = b - nr;
-		d = nr;
-		if (dir == SM_DOWN) {
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		unsigned int sz, /* number of rows being scrolled */
+			d,		  /* number of rows needing blanking */
+			clear;		  /* The number of the topmost row needing blanking. */
+		uint32_t *dest, *src;
+		unsigned int i;
+
+		if (dir == SM_UP /* && t == 0 */&& b == vc->vc_rows) {
+			UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && t == 0 && b == vc->vc_rows - nr) {
+			UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
 			clear = t;
-			d = sz - nr;
-		}
-		for (i = 0; i < gcd(d, sz); i++) {
-			char32_t *tmp = uniscr->lines[t + i];
-			j = i;
-			while (1) {
-				k = j + d;
-				if (k >= sz)
-					k -= sz;
-				if (k == i)
-					break;
-				uniscr->lines[t + j] = uniscr->lines[t + k];
-				j = k;
+		} else if (dir == SM_UP) {
+			sz = b - t;
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, t * cols);
+			dest = src;
+			UNISCR_MINUS(dest, nr * cols);
+			i = b - t;
+			while (i--) {
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				UNISCR_PLUS(src, cols);
+				UNISCR_PLUS(dest, cols);
+			}
+			d = nr;
+			clear = b - nr;
+		} else {
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, b * cols);
+			dest = src;
+			UNISCR_PLUS(dest, nr * cols);
+			i = b - t;
+			while (i--) {
+				UNISCR_MINUS(src, cols);
+				UNISCR_MINUS(dest, cols);
+				memcpy(dest, src, cols * sizeof(uint32_t));
 			}
-			uniscr->lines[t + j] = tmp;
+			d = nr;
+			clear = t;
 		}
-		vc_uniscr_clear_lines(vc, clear, nr);
-	}
-}
-
-static void vc_uniscr_copy_area(struct uni_screen *dst,
-				unsigned int dst_cols,
-				unsigned int dst_rows,
-				struct uni_screen *src,
-				unsigned int src_cols,
-				unsigned int src_top_row,
-				unsigned int src_bot_row)
-{
-	unsigned int dst_row = 0;
-
-	if (!dst)
-		return;
-
-	while (src_top_row < src_bot_row) {
-		char32_t *src_line = src->lines[src_top_row];
-		char32_t *dst_line = dst->lines[dst_row];
-
-		memcpy(dst_line, src_line, src_cols * sizeof(char32_t));
-		if (dst_cols - src_cols)
-			memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-		src_top_row++;
-		dst_row++;
-	}
-	while (dst_row < dst_rows) {
-		char32_t *dst_line = dst->lines[dst_row];
-
-		memset32(dst_line, ' ', dst_cols);
-		dst_row++;
+		if (d)
+			vc_uniscr_clear_lines(vc, clear, nr);
 	}
 }
 
@@ -500,7 +517,6 @@ static void vc_uniscr_copy_area(struct uni_screen *dst,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-	struct uni_screen *uniscr;
 	unsigned short *p;
 	int x, y, mask;
 
@@ -512,11 +528,10 @@ int vc_uniscr_check(struct vc_data *vc)
 	if (!vc->vc_utf)
 		return -ENODATA;
 
-	if (vc->vc_uni_screen)
+	if (vc->vc_uniscr_buf)
 		return 0;
 
-	uniscr = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-	if (!uniscr)
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
 		return -ENOMEM;
 
 	/*
@@ -528,14 +543,14 @@ int vc_uniscr_check(struct vc_data *vc)
 	p = (unsigned short *)vc->vc_origin;
 	mask = vc->vc_hi_font_mask | 0xff;
 	for (y = 0; y < vc->vc_rows; y++) {
-		char32_t *line = uniscr->lines[y];
+		uint32_t *line = vc->vc_uniscr_curr;
+		UNISCR_PLUS(line, y * vc->vc_cols);
 		for (x = 0; x < vc->vc_cols; x++) {
 			u16 glyph = scr_readw(p++) & mask;
 			line[x] = inverse_translate(vc, glyph, true);
 		}
 	}
 
-	vc->vc_uni_screen = uniscr;
 	return 0;
 }
 
@@ -547,12 +562,23 @@ int vc_uniscr_check(struct vc_data *vc)
 void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 			 unsigned int row, unsigned int col, unsigned int nr)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	uint32_t *pos;	       /* Position in the unicode buffer of col/row */
+#else
 	int offset = row * vc->vc_size_row + col * 2;
-	unsigned long pos;
+	unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
 
-	BUG_ON(!uniscr);
+	BUG_ON(!vc->vc_uniscr_buf);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	pos = vc->vc_uniscr_curr;
+	UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+	memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
 	pos = (unsigned long)screenpos(vc, offset, viewed);
 	if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
 		/*
@@ -562,60 +588,271 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		 */
 		row = (pos - vc->vc_origin) / vc->vc_size_row;
 		col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
-		memcpy(dest, &uniscr->lines[row][col], nr * sizeof(char32_t));
+		memcpy(dest,
+		       (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+		       nr);
 	} else {
 		/*
-		 * Scrollback is active. For now let's simply backtranslate
-		 * the screen glyphs until the unicode screen buffer does
-		 * synchronize with console display drivers for a scrollback
-		 * buffer of its own.
+		 * Scrollback is active.  So hoik the unicode characters out
+		 * of the unicode circular buffer.
 		 */
-		u16 *p = (u16 *)pos;
-		int mask = vc->vc_hi_font_mask | 0xff;
-		char32_t *uni_buf = dest;
-		while (nr--) {
-			u16 glyph = scr_readw(p++) & mask;
-			*uni_buf++ = inverse_translate(vc, glyph, true);
-		}
+		/* CAN'T HAPPEN!!!  (Hah hah!) */
 	}
+#endif
 }
 
 /* this is for validation and debugging only */
 static void vc_uniscr_debug_check(struct vc_data *vc)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
-	unsigned short *p;
-	int x, y, mask;
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	/* struct uni_screen *uniscr = get_vc_uniscr(vc); */
+	/* unsigned short *p; */
+	/* int x, y, mask; */
+#endif
 
-	if (!VC_UNI_SCREEN_DEBUG || !uniscr)
+	if (!VC_UNI_SCREEN_DEBUG || !vc->vc_uniscr_buf)
 		return;
 
 	WARN_CONSOLE_UNLOCKED();
 
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
 	/*
 	 * Make sure our unicode screen translates into the same glyphs
 	 * as the actual screen. This is brutal indeed.
 	 */
-	p = (unsigned short *)vc->vc_origin;
-	mask = vc->vc_hi_font_mask | 0xff;
-	for (y = 0; y < vc->vc_rows; y++) {
-		char32_t *line = uniscr->lines[y];
-		for (x = 0; x < vc->vc_cols; x++) {
-			u16 glyph = scr_readw(p++) & mask;
-			char32_t uc = line[x];
-			int tc = conv_uni_to_pc(vc, uc);
-			if (tc == -4)
-				tc = conv_uni_to_pc(vc, 0xfffd);
-			if (tc == -4)
-				tc = conv_uni_to_pc(vc, '?');
-			if (tc != glyph)
-				pr_err_ratelimited(
-					"%s: mismatch at %d,%d: glyph=%#x tc=%#x\n",
-					__func__, x, y, glyph, tc);
+	/* p = (unsigned short *)vc->vc_origin; */
+	/* mask = vc->vc_hi_font_mask | 0xff; */
+	/* for (y = 0; y < vc->vc_rows; y++) { */
+	/* 	char32_t *line = uniscr->lines[y]; */
+	/* 	for (x = 0; x < vc->vc_cols; x++) { */
+	/* 		u16 glyph = scr_readw(p++) & mask; */
+	/* 		char32_t uc = line[x]; */
+	/* 		int tc = conv_uni_to_pc(vc, uc); */
+	/* 		if (tc == -4) */
+	/* 			tc = conv_uni_to_pc(vc, 0xfffd); */
+	/* 		if (tc == -4) */
+	/* 			tc = conv_uni_to_pc(vc, '?'); */
+	/* 		if (tc != glyph) */
+	/* 			pr_err_ratelimited( */
+	/* 				"%s: mismatch at %d,%d: glyph=%#x tc=%#x\n", */
+	/* 				__func__, x, y, glyph, tc); */
+	/* 	} */
+	/* } */
+#endif
+}
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = console_soft_scrollback_size / vc->vc_size_row;
+	if (l > 5)
+	{
+		vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+		vc->vc_softback_top = vc->vc_softback_buf;
+	}
+	else
+		/* Smaller scrollback makes no sense, and 0 would screw
+		   the operation totally */
+		vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+	if (vc->vc_softback_lines)
+		concon_scrolldelta(vc, vc->vc_softback_lines);
+	return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+				long delta)
+{
+	int count = vc->vc_rows;
+	unsigned short *d, *s;
+	unsigned long n;
+	int line = 0;
+
+	if (!vc->vc_softback_lines)
+		vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+	d = (u16 *) vc->vc_softback_curr;
+	if (d == (u16 *) vc->vc_softback_in)
+		d = (u16 *) vc->vc_origin;
+	n = vc->vc_softback_curr + delta * vc->vc_size_row;
+	vc->vc_softback_lines -= delta;
+	if (delta < 0) {
+		if (vc->vc_softback_curr < vc->vc_softback_top
+		    && n < vc->vc_softback_buf) {
+			n += vc->vc_softback_end - vc->vc_softback_buf;
+			if (n < vc->vc_softback_top) {
+				vc->vc_softback_lines -=
+				    (vc->vc_softback_top - n) / vc->vc_size_row;
+				n = vc->vc_softback_top;
+			}
+		} else if (vc->vc_softback_curr >= vc->vc_softback_top
+			   && n < vc->vc_softback_top) {
+			vc->vc_softback_lines -=
+			    (vc->vc_softback_top - n) / vc->vc_size_row;
+			n = vc->vc_softback_top;
+		}
+	} else {
+		if (vc->vc_softback_curr > vc->vc_softback_in
+		    && n >= vc->vc_softback_end) {
+			n += vc->vc_softback_buf - vc->vc_softback_end;
+			if (n > vc->vc_softback_in) {
+				n = vc->vc_softback_in;
+				vc->vc_softback_lines = 0;
+			}
+		} else if (vc->vc_softback_curr <= vc->vc_softback_in
+			   && n > vc->vc_softback_in) {
+			n = vc->vc_softback_in;
+			vc->vc_softback_lines = 0;
+		}
+	}
+	if (n == vc->vc_softback_curr)
+		return;
+	vc->vc_softback_curr = n;
+	/* If we're not scrolled any more, restore the character to the cursor
+	 * position */
+	if (!vc->vc_softback_lines)
+		scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+	s = (u16 *) vc->vc_softback_curr;
+	if (s == (u16 *) vc->vc_softback_in)
+		s = (u16 *) vc->vc_origin;
+	while (count--) {
+		unsigned short *start;
+		unsigned short *le;
+		unsigned short c;
+		int x = 0;
+		unsigned short attr = 1;
+
+		start = s;
+		le = advance_row(s, 1);
+		/* Temporarily overwrite the character at the cursor position
+		 * with the one we actually want to see on the screen.  */
+		if (count == vc->vc_rows - vc->state.y - 1)
+		{
+			c = scr_readw((u16 *)(s + vc->state.x));
+			scr_writew(c, (u16 *)vc->vc_pos);
+			vc->vc_sw->con_putcs
+				(vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+		}
+		do {
+			c = scr_readw(s);
+			if (attr != (c & 0xff00)) {
+				attr = c & 0xff00;
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start;
+					start = s;
+				}
+			}
+			if (c == scr_readw(d)) {
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start + 1;
+					start = s + 1;
+				} else {
+					x++;
+					start++;
+				}
+			}
+			s++;
+			d++;
+		} while (s < le);
+		if (s > start)
+			vc->vc_sw->con_putcs(vc, start, s - start, line, x);
+		line++;
+		if (d == (u16 *) vc->vc_softback_end)
+			d = (u16 *) vc->vc_softback_buf;
+		if (d == (u16 *) vc->vc_softback_in)
+			d = (u16 *) vc->vc_origin;
+		if (s == (u16 *) vc->vc_softback_end)
+			s = (u16 *) vc->vc_softback_buf;
+		if (s == (u16 *) vc->vc_softback_in)
+			s = (u16 *) vc->vc_origin;
+	}
+}
+
+static inline void con_softback_note(struct vc_data *vc, int t,
+				     int count)
+{
+	unsigned short *p;
+
+	if (vc->vc_num != fg_console)
+		return;
+	p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+	while (count) {
+		scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+		count--;
+		p = advance_row(p, 1);
+		vc->vc_softback_in += vc->vc_size_row;
+		if (vc->vc_softback_in == vc->vc_softback_end)
+			vc->vc_softback_in = vc->vc_softback_buf;
+		if (vc->vc_softback_in == vc->vc_softback_top) {
+			vc->vc_softback_top += vc->vc_size_row;
+			if (vc->vc_softback_top == vc->vc_softback_end)
+				vc->vc_softback_top = vc->vc_softback_buf;
 		}
 	}
+	vc->vc_softback_curr = vc->vc_softback_in;
 }
 
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+	/* struct display *disp = &fb_display[fg_console]; */
+	/* int offset, limit, scrollback_old; */
+
+	if (vc->vc_softback_top) {
+		if (vc->vc_num != fg_console)
+			return;
+		if (vc->vc_mode != KD_TEXT || !lines)
+			return;
+#if 0
+		if (logo_shown >= 0) {
+			struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+			if (conp2->vc_top == logo_lines
+			    && conp2->vc_bottom == conp2->vc_rows)
+				conp2->vc_top = 0;
+			if (logo_shown == vc->vc_num) {
+				unsigned long p, q;
+				int i;
+
+				p = vc->vc_softback_in;
+				q = vc->vc_origin +
+				    logo_lines * vc->vc_size_row;
+				for (i = 0; i < logo_lines; i++) {
+					if (p == vc->vc_softback_top)
+						break;
+					if (p == vc->vc_softback_buf)
+						p = vc->vc_softback_end;
+					p -= vc->vc_size_row;
+					q -= vc->vc_size_row;
+					scr_memcpyw((u16 *) q, (u16 *) p,
+						    vc->vc_size_row);
+				}
+				vc->vc_softback_in = vc->vc_softback_curr = p;
+				update_region(vc, vc->vc_origin,
+					      logo_lines * vc->vc_cols);
+			}
+			logo_shown = FBCON_LOGO_CANSHOW;
+		}
+#endif
+		vc->vc_sw->con_cursor(vc, CM_ERASE /* | CM_SOFTBACK */);
+		con_redraw_softback(vc, /* disp, */ lines);
+		if (!vc->vc_softback_lines)
+			vc->vc_sw->con_cursor(vc, CM_DRAW /* | CM_SOFTBACK */);
+	}
+}
+
+#endif  /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
 
 static void con_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 		enum con_scroll dir, unsigned int nr)
@@ -626,6 +863,10 @@ static void con_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 		nr = b - t - 1;
 	if (b > vc->vc_rows || t >= b || nr < 1)
 		return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (dir == SM_UP && vc->vc_softback_top)
+		con_softback_note (vc, t, nr);
+#endif
 	vc_uniscr_scroll(vc, t, b, dir, nr);
 	if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, dir, nr))
 		return;
@@ -641,6 +882,53 @@ static void con_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
 	scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+	unsigned int xx, yy, offset;
+	u16 *p;
+
+	unsigned long origin =
+		(start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+		? start >= vc->vc_softback_curr
+		  ? vc->vc_softback_curr
+		  : vc->vc_softback_curr
+		    - (vc->vc_softback_end - vc->vc_softback_buf)
+		: vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+	p = (u16 *) start;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
+	for(;;) {
+		u16 attrib = scr_readw(p) & 0xff00;
+		int startx = xx;
+		u16 *q = p;
+		while (xx < vc->vc_cols && count) {
+			if (attrib != (scr_readw(p) & 0xff00)) {
+				if (p > q)
+					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+				startx = xx;
+				q = p;
+				attrib = scr_readw(p) & 0xff00;
+			}
+			p++;
+			xx++;
+			count--;
+		}
+		if (p > q)
+			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+		if (p == (u16 *) vc->vc_softback_end)
+			p = (u16 *)vc->vc_softback_buf;
+		if (p == (u16 *) vc->vc_softback_in)
+			p = (u16 *)vc->vc_origin;
+		if (!count)
+			break;
+		xx = 0;
+		yy++;
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -684,6 +972,7 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 		}
 	}
 }
+#endif
 
 void update_region(struct vc_data *vc, unsigned long start, int count)
 {
@@ -692,7 +981,10 @@ void update_region(struct vc_data *vc, unsigned long start, int count)
 	if (con_should_update(vc)) {
 		hide_cursor(vc);
 		do_update_region(vc, start, count);
-		set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		if (!vc->vc_softback_lines)
+#endif
+			set_cursor(vc);
 	}
 }
 
@@ -753,51 +1045,68 @@ static void update_attr(struct vc_data *vc)
 }
 
 /* Note: inverting the screen twice should revert to the original state */
+/* OFFSET is the offset in bytes (not 16-bit characters), COUNT is a byte
+ * count (not a character count).  */
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 {
 	unsigned short *p;
+	int row_offset, bytes_left_in_row;
+	int row_count;
 
 	WARN_CONSOLE_UNLOCKED();
 
 	count /= 2;
-	p = screenpos(vc, offset, viewed);
-	if (vc->vc_sw->con_invert_region) {
-		vc->vc_sw->con_invert_region(vc, p, count);
-	} else {
-		u16 *q = p;
-		int cnt = count;
-		u16 a;
-
-		if (!vc->vc_can_do_color) {
-			while (cnt--) {
-			    a = scr_readw(q);
-			    a ^= 0x0800;
-			    scr_writew(a, q);
-			    q++;
-			}
-		} else if (vc->vc_hi_font_mask == 0x100) {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x11ff) |
-				   ((a & 0xe000) >> 4) |
-				   ((a & 0x0e00) << 4);
-				scr_writew(a, q);
-				q++;
-			}
+	row_offset = offset;
+	bytes_left_in_row = vc->vc_size_row - (row_offset % vc->vc_size_row);
+	row_count = (count < bytes_left_in_row / 2)
+		? count
+		: bytes_left_in_row / 2;
+
+	while (count) {
+		p = screenpos(vc, row_offset, viewed);
+		if (vc->vc_sw->con_invert_region) {
+			vc->vc_sw->con_invert_region(vc, p, row_count);
 		} else {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x88ff) |
-				   ((a & 0x7000) >> 4) |
-				   ((a & 0x0700) << 4);
-				scr_writew(a, q);
-				q++;
+			u16 *q = p;
+			int cnt = row_count;
+			u16 a;
+
+			if (!vc->vc_can_do_color) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a ^= 0x0800;
+					scr_writew(a, q);
+					q++;
+				}
+			} else if (vc->vc_hi_font_mask == 0x100) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x11ff) |
+						((a & 0xe000) >> 4) |
+						((a & 0x0e00) << 4);
+					scr_writew(a, q);
+					q++;
+				}
+			} else {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x88ff) |
+						((a & 0x7000) >> 4) |
+						((a & 0x0700) << 4);
+					scr_writew(a, q);
+					q++;
+				}
 			}
 		}
-	}
 
-	if (con_should_update(vc))
-		do_update_region(vc, (unsigned long) p, count);
+		if (con_should_update(vc))
+			do_update_region(vc, (unsigned long) p, row_count);
+		row_offset += 2 * row_count;
+		count -= row_count;
+		row_count = (count >= vc->vc_cols)
+			? vc->vc_cols
+			: count;
+	}
 	notify_update(vc);
 }
 
@@ -927,8 +1236,17 @@ static void set_origin(struct vc_data *vc)
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!con_is_visible(vc) ||
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	    (
+	     !concon_set_origin (vc) &&
+	     (
+#endif
 	    !vc->vc_sw->con_set_origin ||
-	    !vc->vc_sw->con_set_origin(vc))
+	    !vc->vc_sw->con_set_origin(vc)
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		     ))
+#endif
+					  )
 		vc->vc_origin = (unsigned long)vc->vc_screenbuf;
 	vc->vc_visible_origin = vc->vc_origin;
 	vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
@@ -989,7 +1307,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 
 	if (!vc) {
 		/* strange ... */
-		/* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
 		return;
 	}
 
@@ -1004,7 +1321,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		hide_cursor(old_vc);
 		if (!con_is_visible(old_vc)) {
 			save_screen(old_vc);
-			set_origin(old_vc);
 		}
 		if (tty0dev)
 			sysfs_notify(&tty0dev->kobj, NULL, "active");
@@ -1017,7 +1333,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		int update;
 		int old_was_color = vc->vc_can_do_color;
 
-		set_origin(vc);
 		update = vc->vc_sw->con_switch(vc);
 		set_palette(vc);
 		/*
@@ -1032,9 +1347,19 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		}
 
 		if (update && vc->vc_mode != KD_GRAPHICS)
-			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+			do_update_region(vc,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+					 vc->vc_softback_lines
+					 ? vc->vc_softback_curr
+					 :
+#endif
+					   vc->vc_origin,
+					 vc->vc_screenbuf_size / 2);
 	}
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	if (is_switch) {
 		vt_set_leds_compute_shiftstate();
 		notify_update(vc);
@@ -1112,7 +1437,6 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
@@ -1147,16 +1471,30 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_KERNEL);
 	if (!vc->vc_screenbuf)
 		goto err_free;
-
 	/* If no drivers have overridden us and the user didn't pass a
 	   boot option, default to displaying the cursor */
 	if (global_cursor_default == -1)
 		global_cursor_default = 1;
 
 	vc_init(vc, vc->vc_rows, vc->vc_cols, 1);
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows) != 0)
+		goto err_free;
 	vcs_make_sysfs(currcons);
 	atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_size = console_soft_scrollback_size;
+	err = -ENOMEM;
+	vc->vc_softback_buf =
+		(unsigned long)vzalloc(console_soft_scrollback_size);
+	if (!vc->vc_softback_buf)
+		goto err_free;
+	vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+		vc->vc_softback_buf;
+	vc->vc_softback_lines = 0;
+	con_update_softback(vc);
+	vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1177,6 +1515,75 @@ static inline int resize_screen(struct vc_data *vc, int width, int height,
 	return err;
 }
 
+static int vc_copy_uniscr_to_new_area (struct vc_data *vc,
+				       unsigned int new_cols,
+				       unsigned int new_rows)
+{
+	unsigned int old_rows = vc->vc_rows, old_cols = vc->vc_cols;
+        uint32_t *old_uniscr_curr = vc->vc_uniscr_curr,
+		*old_uniscr_buf = vc->vc_uniscr_buf;
+	unsigned int old_uniscr_char_size = vc->vc_uniscr_char_size;
+	unsigned int new_lines;
+	unsigned int copy_cols;
+	uint32_t *dest, *src;
+	int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int new_uniscr_rows;
+	unsigned int old_lines;
+	unsigned long tmp;
+
+	if (vc->vc_softback_in >= vc->vc_softback_top)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_lines = tmp / vc->vc_size_row + old_rows;
+
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_uniscr_rows = vc->vc_uniscr_char_size / new_cols + new_rows;
+	new_lines = min(old_lines, new_uniscr_rows);
+	copy_cols = min(old_cols, new_cols);
+
+	dest = vc->vc_uniscr_curr;
+	if (new_lines > old_rows) {
+		dest -= (new_lines - old_rows) * new_cols;
+		while (dest < vc->vc_uniscr_buf) /* Could happen twice.  */
+			dest += vc->vc_uniscr_char_size;
+	}
+	src = old_uniscr_curr;
+	if (new_lines > old_rows) {
+		src -= (new_lines - old_rows) * old_cols;
+		while (src < old_uniscr_buf)
+			src += old_uniscr_char_size;
+	}
+#else
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_lines = min(old_rows, new_rows);
+	copy_cols = min(old_cols, new_cols);
+	dest = vc->vc_uniscr_curr;
+	src = old_uniscr_curr;
+#endif
+	if (old_uniscr_buf) {
+		while (new_lines--) {
+			memcpy(dest, src, copy_cols * sizeof(uint32_t));
+			if (new_cols > old_cols)
+				memset32(dest + old_cols, ' ',
+					 new_cols - old_cols);
+			UNISCR_PLUS(dest, new_cols);
+			src += old_cols;
+			if (src >= old_uniscr_buf + old_uniscr_char_size)
+				src -= old_uniscr_char_size;
+		}
+		kvfree(old_uniscr_buf);
+	}
+	return 0;
+}
+
 /**
  *	vc_do_resize	-	resizing method for the tty
  *	@tty: tty being resized
@@ -1197,12 +1604,19 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 {
 	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
 	unsigned long end;
-	unsigned int old_rows, old_row_size, first_copied_row;
-	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+	unsigned int old_rows, old_size_row, first_copied_row;
+	unsigned int new_cols, new_rows, new_size_row, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	struct uni_screen *new_uniscr = NULL;
-
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned short *d;
+	unsigned long old_softback_buf, new_softback_buf, tmp;
+	unsigned long old_softback_end, new_softback_end;
+	unsigned int old_scrolled_rows, new_scrolled_rows;
+	unsigned int count, copied_scrolled_rows;
+	void *temp_new_softback_buf;
+#endif
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!vc)
@@ -1216,8 +1630,8 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 
 	new_cols = (cols ? cols : vc->vc_cols);
 	new_rows = (lines ? lines : vc->vc_rows);
-	new_row_size = new_cols << 1;
-	new_screen_size = new_row_size * new_rows;
+	new_size_row = new_cols << 1;
+	new_screen_size = new_size_row * new_rows;
 
 	if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
 		/*
@@ -1245,61 +1659,91 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	if (!newscreen)
 		return -ENOMEM;
 
-	if (get_vc_uniscr(vc)) {
-		new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
-		if (!new_uniscr) {
-			kfree(newscreen);
-			return -ENOMEM;
-		}
-	}
-
 	if (vc_is_sel(vc))
 		clear_selection();
 
 	old_rows = vc->vc_rows;
-	old_row_size = vc->vc_size_row;
+	old_size_row = vc->vc_size_row;
 
 	err = resize_screen(vc, new_cols, new_rows, user);
 	if (err) {
 		kfree(newscreen);
-		vc_uniscr_free(new_uniscr);
+		vc_uniscr_free(vc);
+		vc->vc_uniscr_buf = old_uniscr;
 		return err;
 	}
 
-	vc->vc_rows = new_rows;
-	vc->vc_cols = new_cols;
-	vc->vc_size_row = new_row_size;
-	vc->vc_screenbuf_size = new_screen_size;
-
-	rlth = min(old_row_size, new_row_size);
-	rrem = new_row_size - rlth;
+	rlth = min(old_size_row, new_size_row);
+	rrem = new_size_row - rlth;
 	old_origin = vc->vc_origin;
 	new_origin = (long) newscreen;
 	new_scr_end = new_origin + new_screen_size;
 
 	if (vc->state.y > new_rows) {
-		if (old_rows - vc->state.y < new_rows) {
+		if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+			if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+				con_softback_note(vc, vc->vc_top,
+						  old_rows - new_rows - vc->vc_top);
+#endif
+				vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+						 old_rows - new_rows - vc->vc_top);
+			}
 			/*
 			 * Cursor near the bottom, copy contents from the
 			 * bottom of buffer
 			 */
 			first_copied_row = (old_rows - new_rows);
 		} else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+			con_softback_note(vc, vc->vc_top,
+					  vc->state.y - new_rows/2);
+#endif
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 vc->state.y - new_rows/2);
 			/*
 			 * Cursor is in no man's land, copy 1/2 screenful
 			 * from the top and bottom of cursor position
 			 */
 			first_copied_row = (vc->state.y - new_rows/2);
 		}
-		old_origin += first_copied_row * old_row_size;
+		old_origin += first_copied_row * old_size_row;
 	} else
 		first_copied_row = 0;
-	end = old_origin + old_row_size * min(old_rows, new_rows);
+	end = old_origin + old_size_row * min(old_rows, new_rows);
+
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_cols, new_rows)) != 0)
+		return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	concon_set_origin(vc);
+	old_softback_buf = vc->vc_softback_buf;
+	old_softback_end = vc->vc_softback_end;
+	vc->vc_softback_size = console_soft_scrollback_size;
+	temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+	new_softback_buf = (unsigned long)temp_new_softback_buf;
+	if (!new_softback_buf)
+		return -ENOMEM;
+	new_softback_end = new_softback_buf + console_soft_scrollback_size;
+	d = (unsigned short *)new_softback_buf;
+	while (d != (u16 *)new_softback_end) {
+		scr_writew (0x0020, d);
+		d++;
+	}
+	if (vc->vc_softback_top <= vc->vc_softback_in)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_scrolled_rows = tmp / vc->vc_size_row;
+	new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+	copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    get_vc_uniscr(vc), rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
+	vc->vc_cols = new_cols;
+	vc->vc_rows = new_rows;
+	vc->vc_size_row = new_size_row;
+	vc->vc_screenbuf_size = new_screen_size;
 
 	update_attr(vc);
 
@@ -1309,17 +1753,60 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		if (rrem)
 			scr_memsetw((void *)(new_origin + rlth),
 				    vc->vc_video_erase_char, rrem);
-		old_origin += old_row_size;
-		new_origin += new_row_size;
+		old_origin += old_size_row;
+		new_origin += new_size_row;
 	}
 	if (new_scr_end > new_origin)
 		scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
 			    new_scr_end - new_origin);
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	new_origin = new_softback_buf;
+	if (copied_scrolled_rows) {
+		old_origin = vc->vc_softback_in
+			- copied_scrolled_rows * old_size_row;
+		if (old_origin < vc->vc_softback_buf)
+			old_origin += vc->vc_softback_end
+				- vc->vc_softback_buf;
+		count = copied_scrolled_rows;
+
+		while (count--) {
+			scr_memcpyw((unsigned short *) new_origin,
+				    (unsigned short *) old_origin, rlth);
+			if (rrem)
+				scr_memsetw((void *)(new_origin + rlth),
+					    vc->vc_video_erase_char, rrem);
+			old_origin += old_size_row;
+			if (old_origin >= old_softback_end)
+				old_origin -= old_softback_end
+					- old_softback_buf;
+			new_origin += new_size_row;
+		}
+	}
+#endif
 	oldscreen = vc->vc_screenbuf;
 	vc->vc_screenbuf = newscreen;
 	vc->vc_screenbuf_size = new_screen_size;
 	set_origin(vc);
 	kfree(oldscreen);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_buf = new_softback_buf;
+	vc->vc_softback_end = new_softback_buf
+		+ new_scrolled_rows * new_size_row;
+	if (copied_scrolled_rows) {
+		if (new_origin >= vc->vc_softback_end)
+			new_origin -= vc->vc_softback_end - vc->vc_softback_buf;
+		vc->vc_softback_in = new_origin;
+	} else
+		vc->vc_softback_in = new_softback_buf;
+	vc->vc_softback_top = new_softback_buf;
+	if (copied_scrolled_rows
+	    && (new_origin == new_softback_buf))
+		vc->vc_softback_top += new_size_row;
+	vc->vc_softback_curr = vc->vc_softback_in;
+	vc->vc_softback_lines = 0; /* Probably redundant. */
+	kvfree((void *)old_softback_buf);
+#endif
 
 	/* do part of a reset_terminal() */
 	vc->vc_top = 0;
@@ -1400,8 +1887,8 @@ struct vc_data *vc_deallocate(unsigned int currcons)
 		visual_deinit(vc);
 		con_free_unimap(vc);
 		put_pid(vc->vt_pid);
-		vc_uniscr_set(vc, NULL);
 		kfree(vc->vc_screenbuf);
+		vc->vc_uniscr_buf = NULL;
 		vc_cons[currcons].d = NULL;
 	}
 	return vc;
@@ -1646,7 +2133,7 @@ struct rgb { u8 r; u8 g; u8 b; };
 
 static void rgb_from_256(int i, struct rgb *c)
 {
-	if (i < 8) {            /* Standard colours. */
+	if (i < 8) {	    /* Standard colours. */
 		c->r = i&1 ? 0xaa : 0x00;
 		c->g = i&2 ? 0xaa : 0x00;
 		c->b = i&4 ? 0xaa : 0x00;
@@ -1658,7 +2145,7 @@ static void rgb_from_256(int i, struct rgb *c)
 		c->r = (i - 16) / 36 * 85 / 2;
 		c->g = (i - 16) / 6 % 6 * 85 / 2;
 		c->b = (i - 16) % 6 * 85 / 2;
-	} else                  /* Grayscale ramp. */
+	} else		  /* Grayscale ramp. */
 		c->r = c->g = c->b = i * 10 - 2312;
 }
 
@@ -2928,6 +3415,12 @@ static int do_con_write(struct tty_struct *tty, const unsigned char *buf, int co
 
 	param.vc = vc;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	/* Undo any soft scrolling - <Alt><Fn> and <Shift><PgUp/Down> do
+	   not pass through this function.  */
+	concon_set_origin (vc);
+#endif
+
 	while (!tty->flow.stopped && count) {
 		int orig = *buf;
 		buf++;
@@ -3094,11 +3587,8 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	if (kmsg_console && vc_cons_allocated(kmsg_console - 1))
 		vc = vc_cons[kmsg_console - 1].d;
 
-	if (!vc_cons_allocated(fg_console)) {
-		/* impossible */
-		/* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
+	if (!vc_cons_allocated(fg_console))
 		goto quit;
-	}
 
 	if (vc->vc_mode != KD_TEXT)
 		goto quit;
@@ -3143,7 +3633,11 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	}
 	if (cnt && con_is_visible(vc))
 		vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
+
 	notify_update(vc);
 
 quit:
@@ -3364,7 +3858,11 @@ static void con_flush_chars(struct tty_struct *tty)
 	/* if we race with con_close(), vt may be null */
 	console_lock();
 	vc = tty->driver_data;
-	if (vc)
+	if (vc
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	    && !vc->vc_softback_lines
+#endif
+	      )
 		set_cursor(vc);
 	console_unlock();
 }
@@ -3385,6 +3883,14 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
 
 	vc = vc_cons[currcons].d;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	con_update_softback(vc);
+	if (!vc->vc_uniscr_buf)
+		ret = vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+	if (ret)
+		goto unlock;
+#endif
+
 	/* Still being freed */
 	if (vc->port.tty) {
 		ret = -ERESTARTSYS;
@@ -3440,7 +3946,7 @@ static void con_cleanup(struct tty_struct *tty)
 	tty_port_put(&vc->port);
 }
 
-static int default_color           = 7; /* white */
+static int default_color	   = 7; /* white */
 static int default_italic_color    = 2; // green (ASCII)
 static int default_underline_color = 3; // cyan (ASCII)
 module_param_named(color, default_color, int, S_IRUGO | S_IWUSR);
@@ -3526,6 +4032,7 @@ static int __init con_init(void)
 		vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
 		vc_init(vc, vc->vc_rows, vc->vc_cols,
 			currcons || !vc->vc_sw->con_save_screen);
+		vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
 	}
 	currcons = fg_console = 0;
 	master_display_fg = vc = vc_cons[currcons].d;
@@ -4140,7 +4647,7 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
 			con_driver->desc = desc;
 			con_driver->node = i;
 			con_driver->flag = CON_DRIVER_FLAG_MODULE |
-			                   CON_DRIVER_FLAG_INIT;
+					   CON_DRIVER_FLAG_INIT;
 			con_driver->first = first;
 			con_driver->last = last;
 			retval = 0;
@@ -4441,7 +4948,10 @@ void do_unblank_screen(int leaving_gfx)
 	if (console_blank_hook)
 		console_blank_hook(0);
 	set_palette(vc);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
 }
 EXPORT_SYMBOL(do_unblank_screen);
@@ -4741,10 +5251,16 @@ EXPORT_SYMBOL_GPL(screen_glyph);
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-	struct uni_screen *uniscr = get_vc_uniscr(vc);
+	int y = n / vc->vc_cols, x = n % vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-	if (uniscr)
-		return uniscr->lines[n / vc->vc_cols][n % vc->vc_cols];
+	if (vc->vc_uniscr_curr) {
+		UNISCR_PLUS(ln, y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_softback_lines * vc->vc_cols);
+#endif
+		return ln[x];
+	}
 	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index fcc46380e7c9..1abba103d7da 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -98,6 +98,44 @@ config FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
 
 	  If unsure, select n.
 
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	bool "Enable Scrollback Buffer in System RAM"
+	depends on FB=y && FRAMEBUFFER_CONSOLE
+	default y
+	help
+	  This option creates a scrollback buffer for each framebuffer console.
+          These buffers are allocated dynamically during initialisation.
+
+	  If you want this feature, say 'Y' here and enter in
+          FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+          allocate for each buffer.  If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+	int "Scrollback Buffer Size (in KB)"
+	depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	range 1 1024
+	default "128"
+	help
+	  Enter the amount of System RAM in kilobytes to allocate for the
+          scrollback buffer of each framebuffer console.  Each character
+	  position on the video takes 2 bytes of storage.  128kB will give you
+	  approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       bool "Enable a working GPM for scrolled back scrollback buffer in System RAM"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       default y
+       help
+         This option buffers up Unicode characters corresponding to the glyphs
+         displayed by the scrollback buffer.  This enables the GPM mouse driver
+         (or similar) to copy characters from a scrolled back buffer.
+
+         A buffer is created for each framebuffer console, this buffer being
+         approximately twice as big as the buffer size specified in
+         FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+         If unsure, say 'Y'.
+
 config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index e035a63bbe5b..824225747aef 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -633,6 +633,28 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 		    erase,
 		    vc->vc_size_row * logo_lines);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	{
+		uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+		unsigned int i = vc->vc_rows - logo_lines;
+
+		UNISCR_PLUS(d, vc->vc_rows * vc->vc_cols);
+		UNISCR_PLUS(s, (vc->vc_rows - logo_lines) * vc->vc_cols);
+		while (i--) {
+			UNISCR_MINUS(d, vc->vc_cols);
+			UNISCR_MINUS(s, vc->vc_cols);
+			memcpy(d, s, vc->vc_cols * sizeof (uint32_t));
+		}
+		i = logo_lines;
+		d = vc->vc_uniscr_curr;
+		while (i--) {
+			memset32(d, ' ', vc->vc_cols);
+			UNISCR_PLUS(d, vc->vc_cols);
+		}
+		vc->vc_uniscr_curr = d;
+	}
+#endif
+
 	if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
 		fbcon_clear_margins(vc, 0);
 		update_screen(vc);
@@ -1273,6 +1295,25 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
 		 * bitmap stretched into the margin area.
 		 */
 		fbcon_clear_margins(vc, 0);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		{
+			uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+			unsigned int i = vc->vc_rows - logo_lines;
+
+			UNISCR_PLUS(d, (vc->vc_rows - logo_lines)  * vc->vc_cols);
+			UNISCR_PLUS(s, (vc->vc_rows - 2 * logo_lines) * vc->vc_cols);
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				UNISCR_MINUS(s, vc->vc_cols);
+				memcpy (d, s, vc->vc_cols * sizeof (uint32_t));
+			}
+			i = logo_lines;
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				memset32(d, ' ', vc->vc_cols);
+			}
+		}
+#endif
 	}
 
 	/* Split blits that cross physical y_wrap boundary */
@@ -2080,6 +2121,7 @@ static int fbcon_switch(struct vc_data *vc)
 	struct fbcon_ops *ops;
 	struct fbcon_display *p = &fb_display[vc->vc_num];
 	struct fb_var_screeninfo var;
+	unsigned short *d, *s;
 	int i, ret, prev_console;
 
 	info = registered_fb[con2fb_map[vc->vc_num]];
@@ -2089,8 +2131,21 @@ static int fbcon_switch(struct vc_data *vc)
 		struct vc_data *conp2 = vc_cons[logo_shown].d;
 
 		if (conp2->vc_top == logo_lines
-		    && conp2->vc_bottom == conp2->vc_rows)
+		    && conp2->vc_bottom == conp2->vc_rows) {
+			/* Scroll the bottom part of the screen up to fill the
+			 * logo lines. */
+			i = conp2->vc_bottom - conp2->vc_top;
+			d = (unsigned short *)conp2->vc_origin;
+			s = (unsigned short *)(conp2->vc_origin + logo_lines * conp2->vc_size_row);
+			while (i--) {
+				scr_memcpyw(d, s, conp2->vc_size_row);
+				d += conp2->vc_cols;
+				s += conp2->vc_cols;
+			}
+			scr_memsetw(d, conp2->vc_video_erase_char,
+				    conp2->vc_size_row * logo_lines);
 			conp2->vc_top = 0;
+		}
 		logo_shown = FBCON_LOGO_CANSHOW;
 	}
 
@@ -3152,6 +3207,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index d5b9c8d40c18..d8d159e75f39 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -110,6 +110,17 @@ struct vc_data {
 	unsigned short	*vc_screenbuf;		/* In-memory character/attribute buffer */
 	unsigned int	vc_screenbuf_size;
 	unsigned char	vc_mode;		/* KD_TEXT, ... */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned int    vc_softback_size;	/* Size in bytes of scrollback buffer. */
+	unsigned long   vc_softback_buf;	/* Address of scrollback buffer. */
+	unsigned long   vc_softback_end;	/* (Just past) end of buffer. */
+	unsigned long   vc_softback_in;		/* Head pointer into circular buffer. */
+	unsigned long   vc_softback_top;	/* Tail pointer into circular buffer. */
+	unsigned long   vc_softback_curr;	/* Pos in vc_screenbuf or vc_softback_buf
+						   corresponding to visible screen. */
+	int             vc_softback_lines;	/* Number of lines currently scrolled. */
+	unsigned short  vc_char_at_pos;		/* Char at vc_pos when no soft scroll */
+#endif
 	/* attributes for all characters on screen */
 	unsigned char	vc_attr;		/* Current attributes */
 	unsigned char	vc_def_color;		/* Default colors */
@@ -159,7 +170,9 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedir *vc_uni_pagedir;
 	struct uni_pagedir **vc_uni_pagedir_loc; /* [!] Location of uni_pagedir variable for this console */
-	struct uni_screen *vc_uni_screen;	/* unicode screen content */
+	uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+	unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit chars */
+	uint32_t *vc_uniscr_curr;	/* Pos of first char of (unscrolled) screen */
 	/* additional information is in vt_kern.h */
 };
 
@@ -194,4 +207,22 @@ extern void vc_SAK(struct work_struct *work);
 
 bool con_is_visible(const struct vc_data *vc);
 
+/* Macros for wraparound in the uniscr buffer.  POS must be a uint32_t pointer
+ * variable which is expected to be within the confines of vc->vc_uniscr_buf
+ * and vc->vc_uniscr_buf + vc->vc_uniscr_char_size.  OFFSET must be a positive
+ * distance measured in uint32_t's, which when added to or subtracted from POS
+ * won't be too far away from the buffer. */
+#define UNISCR_PLUS(pos,offset)						\
+	do {								\
+		pos += offset;						\
+		if (pos >= vc->vc_uniscr_buf + vc->vc_uniscr_char_size) \
+			pos -= vc->vc_uniscr_char_size;			\
+	} while (0)
+#define UNISCR_MINUS(pos,offset)			\
+	do {						\
+		pos -= offset;				\
+		if (pos < vc->vc_uniscr_buf)		\
+			pos += vc->vc_uniscr_char_size; \
+	} while (0)
+
 #endif /* _LINUX_CONSOLE_STRUCT_H */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index b5ab452fca5b..1a11c9868f8e 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -115,6 +115,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

[-- 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,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - New version of the patch - with GPM handling.
  2023-01-28 14:41                         ` Peter Humphrey
  2023-02-03 18:56                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New (?final) " Alan Mackenzie
@ 2023-09-09 15:21                           ` David Rosenbaum
  1 sibling, 0 replies; 31+ messages in thread
From: David Rosenbaum @ 2023-09-09 15:21 UTC (permalink / raw
  To: gentoo-user

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

hanks

On Sat, Jan 28, 2023 at 9:42 AM Peter Humphrey <peter@prh.myzen.co.uk>
wrote:

> On Friday, 27 January 2023 22:31:17 GMT Alan Mackenzie wrote:
> > On Fri, Jan 27, 2023 at 12:24:41 +0000, Peter Humphrey wrote:
>
> --->8
>
> > > I've attached the reject files.
> >
> > Thanks for these!  It looks like it'll probably be straightforward to
> > amend the patch for 6.1.8.  Are you currently running 6.1.8 as your main
> > kernel?
>
> Yes Alan, I'm running ~amd64, so 6.1.8 is the current kernel. I have
> 5.15.88
> as a backup.
>
> --
> Regards,
> Peter.
>
>
>
>
>

[-- Attachment #2: Type: text/html, Size: 959 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-02-03 18:56                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New (?final) " Alan Mackenzie
@ 2023-10-04 13:16                             ` Alan Mackenzie
  2023-10-04 14:41                               ` Peter Humphrey
  2023-10-04 17:08                               ` Jorge Almeida
  2024-01-24 10:00                             ` Alan Mackenzie
  1 sibling, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2023-10-04 13:16 UTC (permalink / raw
  To: gentoo-user

[-- 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,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-10-04 13:16                             ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Alan Mackenzie
@ 2023-10-04 14:41                               ` Peter Humphrey
  2023-10-04 17:08                               ` Jorge Almeida
  1 sibling, 0 replies; 31+ messages in thread
From: Peter Humphrey @ 2023-10-04 14:41 UTC (permalink / raw
  To: gentoo-user

On Wednesday, 4 October 2023 14:16:44 BST Alan Mackenzie wrote:
> 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.

Many thanks again Alan. What a stalwart you are!

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-10-04 13:16                             ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Alan Mackenzie
  2023-10-04 14:41                               ` Peter Humphrey
@ 2023-10-04 17:08                               ` Jorge Almeida
  2023-10-04 18:59                                 ` Alan Mackenzie
  1 sibling, 1 reply; 31+ messages in thread
From: Jorge Almeida @ 2023-10-04 17:08 UTC (permalink / raw
  To: gentoo-user

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

6.3.11-GPM.20231004.diff works fine with 6.5.5 (vanilla from kernel.org)

>
>

[-- Attachment #2: Type: text/html, Size: 320 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-10-04 17:08                               ` Jorge Almeida
@ 2023-10-04 18:59                                 ` Alan Mackenzie
  2023-10-04 19:02                                   ` Jorge Almeida
  0 siblings, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2023-10-04 18:59 UTC (permalink / raw
  To: gentoo-user

Hello, Jorge.

On Wed, Oct 04, 2023 at 18:08:20 +0100, Jorge Almeida wrote:
>    6.3.11-GPM.20231004.diff works fine with 6.5.5 (vanilla from
>    [1]kernel.org)

Thanks for doing this testing.

> 
> References
> 
>    1. http://kernel.org/

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-10-04 18:59                                 ` Alan Mackenzie
@ 2023-10-04 19:02                                   ` Jorge Almeida
  0 siblings, 0 replies; 31+ messages in thread
From: Jorge Almeida @ 2023-10-04 19:02 UTC (permalink / raw
  To: gentoo-user

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

On Wed, Oct 4, 2023 at 7:59 PM Alan Mackenzie <acm@muc.de> wrote:

>
> On Wed, Oct 04, 2023 at 18:08:20 +0100, Jorge Almeida wrote:
> >    6.3.11-GPM.20231004.diff works fine with 6.5.5 (vanilla from
> >    [1]kernel.org)
>
> Thanks for doing this testing.
>

Thank _you_ for making the patch :)

[-- Attachment #2: Type: text/html, Size: 714 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  2023-02-03 18:56                           ` [gentoo-user] Soft scrolling on framebuffer consoles - New (?final) " Alan Mackenzie
  2023-10-04 13:16                             ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Alan Mackenzie
@ 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-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
  1 sibling, 2 replies; 31+ messages in thread
From: Alan Mackenzie @ 2024-01-24 10:00 UTC (permalink / raw
  To: gentoo-user

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

Hello, Gentoo.

[ First an older post, but without quoting >s on the lines. ]

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).

I've adapted this patch for kernel 6.6.13.  All of the parent post still
applies, with the exception of the version numbers.

The main patch for the new kernel is 6.6.13-GPM.20240123.diff.

Additionally I've attached a smaller patch which fixes what to me was a
little glitch in the gpm utility.  This was that on a triple click to
select a whole line (or sequence of lines), the (last) line got a
linefeed appended to it.  The effect was that if you wanted to hoik a
line out of some screen output and execute that from the bash command
line, you couldn't edit it first.  So with this patch, you no longer get
that annoying linefeed, and the highlighting is adjusted accordingly.
This patch is (as far as I'm aware, without testing) independent of the
main GPM patch.

If anybody still needs them, I've still got the patches for the 6.1
kernel, and the 6.3 and 6.5 kernels, and one or two earlier kernels too.

Have fun!

-- 
Alan Mackenzie (Nuremberg, Germany).


[-- Attachment #2: 6.6.13-GPM.20240123.diff --]
[-- Type: text/plain, Size: 49355 bytes --]

diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 5c47f77804f0..3a901537d5b6 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -134,6 +134,11 @@ const struct consw *conswitchp;
 #define DEFAULT_BELL_DURATION	(HZ/8)
 #define DEFAULT_CURSOR_BLINK_MS	200
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static unsigned int console_soft_scrollback_size =
+	1024 * CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE;
+#endif
+
 struct vc vc_cons [MAX_NR_CONSOLES];
 EXPORT_SYMBOL(vc_cons);
 
@@ -289,8 +294,31 @@ static inline bool con_should_update(const struct vc_data *vc)
 static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned long softback_pos, scrolled_expanse;
+
+	if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+		return (unsigned short *)(vc->vc_origin + offset);
+	else {
+		if (vc->vc_softback_in >= vc->vc_softback_curr)
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr;
+		else
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr
+				+ vc->vc_softback_end - vc->vc_softback_buf;
+		if (offset >= scrolled_expanse)
+			return (unsigned short *)(vc->vc_origin
+						  + (offset - scrolled_expanse));
+		else {
+			softback_pos = vc->vc_softback_curr + offset;
+			if (softback_pos >= vc->vc_softback_end)
+				softback_pos -= vc->vc_softback_end
+					- vc->vc_softback_buf;
+		}
+	}
+	return (unsigned short *)softback_pos;
+#else
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -298,6 +326,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 	else
 		p = vc->vc_sw->con_screen_pos(vc, offset);
 	return p;
+#endif
 }
 
 /* Called  from the keyboard irq path.. */
@@ -319,107 +348,111 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int rows)
 {
-	u32 **uni_lines;
-	void *p;
-	unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
-
-	/* allocate everything in one go */
-	memsize = col_size * rows;
-	memsize += rows * sizeof(*uni_lines);
-	uni_lines = vzalloc(memsize);
-	if (!uni_lines)
-		return NULL;
-
-	/* initial line pointers */
-	p = uni_lines + rows;
-	for (i = 0; i < rows; i++) {
-		uni_lines[i] = p;
-		p += col_size;
-	}
+	uint32_t *p;
+	unsigned int new_size;	/* In 32-bit characters */
 
-	return uni_lines;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(u32 **uni_lines)
-{
-	vfree(uni_lines);
+	num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+	new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+	new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+	if (!p)
+		return -ENOMEM;
+	vc->vc_uniscr_buf = p;
+	vc->vc_uniscr_curr = p;
+	vc->vc_uniscr_char_size = new_size;
+	memset32(p, ' ', new_size); /* Probably redundant. */
+	return 0;
 }
 
-static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-	vc_uniscr_free(vc->vc_uni_lines);
-	vc->vc_uni_lines = new_uni_lines;
+	kvfree(vc->vc_uniscr_buf);
+	vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
 {
-	if (vc->vc_uni_lines)
-		vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr;
+		UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[x], ' ', nr);
 	}
 }
 
 static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		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;
 	}
 }
 
@@ -427,49 +460,52 @@ static void vc_uniscr_scroll(struct vc_data *vc, unsigned int top,
 			     unsigned int bottom, enum con_scroll dir,
 			     unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
-	unsigned int size = bottom - top;
-
-	if (!uni_lines)
-		return;
-
-	if (dir == SM_DOWN) {
-		juggle_array(&uni_lines[top], size, size - nr);
-		vc_uniscr_clear_lines(vc, top, nr);
-	} else {
-		juggle_array(&uni_lines[top], size, nr);
-		vc_uniscr_clear_lines(vc, bottom - nr, nr);
-	}
-}
-
-static void vc_uniscr_copy_area(u32 **dst_lines,
-				unsigned int dst_cols,
-				unsigned int dst_rows,
-				u32 **src_lines,
-				unsigned int src_cols,
-				unsigned int src_top_row,
-				unsigned int src_bot_row)
-{
-	unsigned int dst_row = 0;
-
-	if (!dst_lines)
-		return;
-
-	while (src_top_row < src_bot_row) {
-		u32 *src_line = src_lines[src_top_row];
-		u32 *dst_line = dst_lines[dst_row];
-
-		memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
-		if (dst_cols - src_cols)
-			memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-		src_top_row++;
-		dst_row++;
-	}
-	while (dst_row < dst_rows) {
-		u32 *dst_line = dst_lines[dst_row];
-
-		memset32(dst_line, ' ', dst_cols);
-		dst_row++;
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		unsigned int sz, /* number of rows being scrolled */
+			d,		  /* number of rows needing blanking */
+			clear;		  /* The number of the topmost row needing blanking. */
+		uint32_t *dest, *src;
+		unsigned int i;
+
+		if (dir == SM_UP /* && top == 0 */&& bottom == vc->vc_rows) {
+			UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows - nr) {
+			UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+		clear = top;
+		} else if (dir == SM_UP) {
+			sz = bottom - top;
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, top * cols);
+			dest = src;
+			UNISCR_MINUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				UNISCR_PLUS(src, cols);
+				UNISCR_PLUS(dest, cols);
+			}
+			d = nr;
+			clear = bottom - nr;
+		} else {
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, bottom * cols);
+			dest = src;
+			UNISCR_PLUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				UNISCR_MINUS(src, cols);
+				UNISCR_MINUS(dest, cols);
+				memcpy(dest, src, cols * sizeof(uint32_t));
+			}
+			d = nr;
+			clear = top;
+		}
+		if (d)
+			vc_uniscr_clear_lines(vc, clear, nr);
 	}
 }
 
@@ -481,7 +517,6 @@ static void vc_uniscr_copy_area(u32 **dst_lines,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-	u32 **uni_lines;
 	unsigned short *p;
 	int x, y, mask;
 
@@ -490,11 +525,10 @@ int vc_uniscr_check(struct vc_data *vc)
 	if (!vc->vc_utf)
 		return -ENODATA;
 
-	if (vc->vc_uni_lines)
+	if (vc->vc_uniscr_buf)
 		return 0;
 
-	uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-	if (!uni_lines)
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
 		return -ENOMEM;
 
 	/*
@@ -506,15 +540,14 @@ int vc_uniscr_check(struct vc_data *vc)
 	p = (unsigned short *)vc->vc_origin;
 	mask = vc->vc_hi_font_mask | 0xff;
 	for (y = 0; y < vc->vc_rows; y++) {
-		u32 *line = uni_lines[y];
+		uint32_t *line = vc->vc_uniscr_curr;
+		UNISCR_PLUS(line, y * vc->vc_cols);
 		for (x = 0; x < vc->vc_cols; x++) {
 			u16 glyph = scr_readw(p++) & mask;
 			line[x] = inverse_translate(vc, glyph, true);
 		}
 	}
 
-	vc->vc_uni_lines = uni_lines;
-
 	return 0;
 }
 
@@ -526,13 +559,24 @@ int vc_uniscr_check(struct vc_data *vc)
 void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 			 unsigned int row, unsigned int col, unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	uint32_t *pos;	       /* Position in the unicode buffer of col/row */
+#else
 	int offset = row * vc->vc_size_row + col * 2;
-	unsigned long pos;
+	unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
 
-	if (WARN_ON_ONCE(!uni_lines))
+	if (WARN_ON_ONCE(!vc->vc_uniscr_buf))
 		return;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	pos = vc->vc_uniscr_curr;
+	UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+	memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
 	pos = (unsigned long)screenpos(vc, offset, viewed);
 	if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
 		/*
@@ -542,24 +586,233 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		 */
 		row = (pos - vc->vc_origin) / vc->vc_size_row;
 		col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
-		memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+		memcpy(dest,
+		       (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+		       nr);
 	} else {
 		/*
-		 * Scrollback is active. For now let's simply backtranslate
-		 * the screen glyphs until the unicode screen buffer does
-		 * synchronize with console display drivers for a scrollback
-		 * buffer of its own.
+		 * Scrollback is active.  So hoik the unicode characters out
+		 * of the unicode circular buffer.
 		 */
-		u16 *p = (u16 *)pos;
-		int mask = vc->vc_hi_font_mask | 0xff;
-		u32 *uni_buf = dest;
-		while (nr--) {
-			u16 glyph = scr_readw(p++) & mask;
-			*uni_buf++ = inverse_translate(vc, glyph, true);
+		/* CAN'T HAPPEN!!!  (Hah hah!) */
+	}
+#endif
+}
+
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = console_soft_scrollback_size / vc->vc_size_row;
+	if (l > 5)
+	{
+		vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+		vc->vc_softback_top = vc->vc_softback_buf;
+	}
+	else
+		/* Smaller scrollback makes no sense, and 0 would screw
+		   the operation totally */
+		vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+	if (vc->vc_softback_lines)
+		concon_scrolldelta(vc, vc->vc_softback_lines);
+	return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+				long delta)
+{
+	int count = vc->vc_rows;
+	unsigned short *d, *s;
+	unsigned long n;
+	int line = 0;
+
+	if (!vc->vc_softback_lines)
+		vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+	d = (u16 *) vc->vc_softback_curr;
+	if (d == (u16 *) vc->vc_softback_in)
+		d = (u16 *) vc->vc_origin;
+	n = vc->vc_softback_curr + delta * vc->vc_size_row;
+	vc->vc_softback_lines -= delta;
+	if (delta < 0) {
+		if (vc->vc_softback_curr < vc->vc_softback_top
+		    && n < vc->vc_softback_buf) {
+			n += vc->vc_softback_end - vc->vc_softback_buf;
+			if (n < vc->vc_softback_top) {
+				vc->vc_softback_lines -=
+				    (vc->vc_softback_top - n) / vc->vc_size_row;
+				n = vc->vc_softback_top;
+			}
+		} else if (vc->vc_softback_curr >= vc->vc_softback_top
+			   && n < vc->vc_softback_top) {
+			vc->vc_softback_lines -=
+			    (vc->vc_softback_top - n) / vc->vc_size_row;
+			n = vc->vc_softback_top;
+		}
+	} else {
+		if (vc->vc_softback_curr > vc->vc_softback_in
+		    && n >= vc->vc_softback_end) {
+			n += vc->vc_softback_buf - vc->vc_softback_end;
+			if (n > vc->vc_softback_in) {
+				n = vc->vc_softback_in;
+				vc->vc_softback_lines = 0;
+			}
+		} else if (vc->vc_softback_curr <= vc->vc_softback_in
+			   && n > vc->vc_softback_in) {
+			n = vc->vc_softback_in;
+			vc->vc_softback_lines = 0;
 		}
 	}
+	if (n == vc->vc_softback_curr)
+		return;
+	vc->vc_softback_curr = n;
+	/* If we're not scrolled any more, restore the character to the cursor
+	 * position */
+	if (!vc->vc_softback_lines)
+		scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+	s = (u16 *) vc->vc_softback_curr;
+	if (s == (u16 *) vc->vc_softback_in)
+		s = (u16 *) vc->vc_origin;
+	while (count--) {
+		unsigned short *start;
+		unsigned short *le;
+		unsigned short c;
+		int x = 0;
+		unsigned short attr = 1;
+
+		start = s;
+		le = advance_row(s, 1);
+		/* Temporarily overwrite the character at the cursor position
+		 * with the one we actually want to see on the screen.  */
+		if (count == vc->vc_rows - vc->state.y - 1)
+		{
+			c = scr_readw((u16 *)(s + vc->state.x));
+			scr_writew(c, (u16 *)vc->vc_pos);
+			vc->vc_sw->con_putcs
+				(vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+		}
+		do {
+			c = scr_readw(s);
+			if (attr != (c & 0xff00)) {
+				attr = c & 0xff00;
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start;
+					start = s;
+				}
+			}
+			if (c == scr_readw(d)) {
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start + 1;
+					start = s + 1;
+				} else {
+					x++;
+					start++;
+				}
+			}
+			s++;
+			d++;
+		} while (s < le);
+		if (s > start)
+			vc->vc_sw->con_putcs(vc, start, s - start, line, x);
+		line++;
+		if (d == (u16 *) vc->vc_softback_end)
+			d = (u16 *) vc->vc_softback_buf;
+		if (d == (u16 *) vc->vc_softback_in)
+			d = (u16 *) vc->vc_origin;
+		if (s == (u16 *) vc->vc_softback_end)
+			s = (u16 *) vc->vc_softback_buf;
+		if (s == (u16 *) vc->vc_softback_in)
+			s = (u16 *) vc->vc_origin;
+	}
 }
 
+static inline void con_softback_note(struct vc_data *vc, int t,
+				     int count)
+{
+	unsigned short *p;
+
+	if (vc->vc_num != fg_console)
+		return;
+	p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+	while (count) {
+		scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+		count--;
+		p = advance_row(p, 1);
+		vc->vc_softback_in += vc->vc_size_row;
+		if (vc->vc_softback_in == vc->vc_softback_end)
+			vc->vc_softback_in = vc->vc_softback_buf;
+		if (vc->vc_softback_in == vc->vc_softback_top) {
+			vc->vc_softback_top += vc->vc_size_row;
+			if (vc->vc_softback_top == vc->vc_softback_end)
+				vc->vc_softback_top = vc->vc_softback_buf;
+		}
+	}
+	vc->vc_softback_curr = vc->vc_softback_in;
+}
+
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+	/* struct display *disp = &fb_display[fg_console]; */
+	/* int offset, limit, scrollback_old; */
+
+	if (vc->vc_softback_top) {
+		if (vc->vc_num != fg_console)
+			return;
+		if (vc->vc_mode != KD_TEXT || !lines)
+			return;
+#if 0
+		if (logo_shown >= 0) {
+			struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+			if (conp2->vc_top == logo_lines
+			    && conp2->vc_bottom == conp2->vc_rows)
+				conp2->vc_top = 0;
+			if (logo_shown == vc->vc_num) {
+				unsigned long p, q;
+				int i;
+
+				p = vc->vc_softback_in;
+				q = vc->vc_origin +
+				    logo_lines * vc->vc_size_row;
+				for (i = 0; i < logo_lines; i++) {
+					if (p == vc->vc_softback_top)
+						break;
+					if (p == vc->vc_softback_buf)
+						p = vc->vc_softback_end;
+					p -= vc->vc_size_row;
+					q -= vc->vc_size_row;
+					scr_memcpyw((u16 *) q, (u16 *) p,
+						    vc->vc_size_row);
+				}
+				vc->vc_softback_in = vc->vc_softback_curr = p;
+				update_region(vc, vc->vc_origin,
+					      logo_lines * vc->vc_cols);
+			}
+			logo_shown = FBCON_LOGO_CANSHOW;
+		}
+#endif
+		vc->vc_sw->con_cursor(vc, CM_ERASE /* | CM_SOFTBACK */);
+		con_redraw_softback(vc, /* disp, */ lines);
+		if (!vc->vc_softback_lines)
+			vc->vc_sw->con_cursor(vc, CM_DRAW /* | CM_SOFTBACK */);
+	}
+}
+
+#endif  /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
+
 static void con_scroll(struct vc_data *vc, unsigned int top,
 		       unsigned int bottom, enum con_scroll dir,
 		       unsigned int nr)
@@ -571,6 +824,10 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 		nr = rows - 1;
 	if (bottom > vc->vc_rows || top >= bottom || nr < 1)
 		return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (dir == SM_UP && vc->vc_softback_top)
+		con_softback_note (vc, top, nr);
+#endif
 
 	vc_uniscr_scroll(vc, top, bottom, dir, nr);
 	if (con_is_visible(vc) &&
@@ -588,6 +845,53 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 	scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+	unsigned int xx, yy, offset;
+	u16 *p;
+
+	unsigned long origin =
+		(start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+		? start >= vc->vc_softback_curr
+		  ? vc->vc_softback_curr
+		  : vc->vc_softback_curr
+		    - (vc->vc_softback_end - vc->vc_softback_buf)
+		: vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+	p = (u16 *) start;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
+	for(;;) {
+		u16 attrib = scr_readw(p) & 0xff00;
+		int startx = xx;
+		u16 *q = p;
+		while (xx < vc->vc_cols && count) {
+			if (attrib != (scr_readw(p) & 0xff00)) {
+				if (p > q)
+					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+				startx = xx;
+				q = p;
+				attrib = scr_readw(p) & 0xff00;
+			}
+			p++;
+			xx++;
+			count--;
+		}
+		if (p > q)
+			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+		if (p == (u16 *) vc->vc_softback_end)
+			p = (u16 *)vc->vc_softback_buf;
+		if (p == (u16 *) vc->vc_softback_in)
+			p = (u16 *)vc->vc_origin;
+		if (!count)
+			break;
+		xx = 0;
+		yy++;
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -631,6 +935,7 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 		}
 	}
 }
+#endif
 
 void update_region(struct vc_data *vc, unsigned long start, int count)
 {
@@ -639,7 +944,10 @@ void update_region(struct vc_data *vc, unsigned long start, int count)
 	if (con_should_update(vc)) {
 		hide_cursor(vc);
 		do_update_region(vc, start, count);
-		set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		if (!vc->vc_softback_lines)
+#endif
+			set_cursor(vc);
 	}
 }
 EXPORT_SYMBOL(update_region);
@@ -701,51 +1009,68 @@ static void update_attr(struct vc_data *vc)
 }
 
 /* Note: inverting the screen twice should revert to the original state */
+/* OFFSET is the offset in bytes (not 16-bit characters), COUNT is a byte
+ * count (not a character count).  */
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 {
 	unsigned short *p;
+	int row_offset, bytes_left_in_row;
+	int row_count;
 
 	WARN_CONSOLE_UNLOCKED();
 
 	count /= 2;
-	p = screenpos(vc, offset, viewed);
-	if (vc->vc_sw->con_invert_region) {
-		vc->vc_sw->con_invert_region(vc, p, count);
-	} else {
-		u16 *q = p;
-		int cnt = count;
-		u16 a;
-
-		if (!vc->vc_can_do_color) {
-			while (cnt--) {
-			    a = scr_readw(q);
-			    a ^= 0x0800;
-			    scr_writew(a, q);
-			    q++;
-			}
-		} else if (vc->vc_hi_font_mask == 0x100) {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x11ff) |
-				   ((a & 0xe000) >> 4) |
-				   ((a & 0x0e00) << 4);
-				scr_writew(a, q);
-				q++;
-			}
+	row_offset = offset;
+	bytes_left_in_row = vc->vc_size_row - (row_offset % vc->vc_size_row);
+	row_count = (count < bytes_left_in_row / 2)
+		? count
+		: bytes_left_in_row / 2;
+
+	while (count) {
+		p = screenpos(vc, row_offset, viewed);
+		if (vc->vc_sw->con_invert_region) {
+			vc->vc_sw->con_invert_region(vc, p, row_count);
 		} else {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x88ff) |
-				   ((a & 0x7000) >> 4) |
-				   ((a & 0x0700) << 4);
-				scr_writew(a, q);
-				q++;
+			u16 *q = p;
+			int cnt = row_count;
+			u16 a;
+
+			if (!vc->vc_can_do_color) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a ^= 0x0800;
+					scr_writew(a, q);
+					q++;
+				}
+			} else if (vc->vc_hi_font_mask == 0x100) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x11ff) |
+						((a & 0xe000) >> 4) |
+						((a & 0x0e00) << 4);
+					scr_writew(a, q);
+					q++;
+				}
+			} else {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x88ff) |
+						((a & 0x7000) >> 4) |
+						((a & 0x0700) << 4);
+					scr_writew(a, q);
+					q++;
+				}
 			}
 		}
-	}
 
-	if (con_should_update(vc))
-		do_update_region(vc, (unsigned long) p, count);
+		if (con_should_update(vc))
+			do_update_region(vc, (unsigned long) p, row_count);
+		row_offset += 2 * row_count;
+		count -= row_count;
+		row_count = (count >= vc->vc_cols)
+			? vc->vc_cols
+			: count;
+	}
 	notify_update(vc);
 }
 
@@ -875,8 +1200,17 @@ static void set_origin(struct vc_data *vc)
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!con_is_visible(vc) ||
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	    (
+	     !concon_set_origin (vc) &&
+	     (
+#endif
 	    !vc->vc_sw->con_set_origin ||
-	    !vc->vc_sw->con_set_origin(vc))
+	    !vc->vc_sw->con_set_origin(vc)
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		     ))
+#endif
+					  )
 		vc->vc_origin = (unsigned long)vc->vc_screenbuf;
 	vc->vc_visible_origin = vc->vc_origin;
 	vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
@@ -937,7 +1271,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 
 	if (!vc) {
 		/* strange ... */
-		/* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
 		return;
 	}
 
@@ -952,7 +1285,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		hide_cursor(old_vc);
 		if (!con_is_visible(old_vc)) {
 			save_screen(old_vc);
-			set_origin(old_vc);
 		}
 		if (tty0dev)
 			sysfs_notify(&tty0dev->kobj, NULL, "active");
@@ -965,7 +1297,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		int update;
 		int old_was_color = vc->vc_can_do_color;
 
-		set_origin(vc);
 		update = vc->vc_sw->con_switch(vc);
 		set_palette(vc);
 		/*
@@ -980,9 +1311,19 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		}
 
 		if (update && vc->vc_mode != KD_GRAPHICS)
-			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+			do_update_region(vc,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+					 vc->vc_softback_lines
+					 ? vc->vc_softback_curr
+					 :
+#endif
+					   vc->vc_origin,
+					 vc->vc_screenbuf_size / 2);
 	}
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	if (is_switch) {
 		vt_set_leds_compute_shiftstate();
 		notify_update(vc);
@@ -1061,7 +1402,6 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
@@ -1103,9 +1443,24 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 		global_cursor_default = 1;
 
 	vc_init(vc, 1);
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows) != 0)
+		goto err_free;
 	vcs_make_sysfs(currcons);
 	atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_size = console_soft_scrollback_size;
+	err = -ENOMEM;
+	vc->vc_softback_buf =
+		(unsigned long)vzalloc(console_soft_scrollback_size);
+	if (!vc->vc_softback_buf)
+		goto err_free;
+	vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+		vc->vc_softback_buf;
+	vc->vc_softback_lines = 0;
+	con_update_softback(vc);
+	vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1126,6 +1481,75 @@ static inline int resize_screen(struct vc_data *vc, int width, int height,
 	return err;
 }
 
+static int vc_copy_uniscr_to_new_area (struct vc_data *vc,
+				       unsigned int new_cols,
+				       unsigned int new_rows)
+{
+	unsigned int old_rows = vc->vc_rows, old_cols = vc->vc_cols;
+        uint32_t *old_uniscr_curr = vc->vc_uniscr_curr,
+		*old_uniscr_buf = vc->vc_uniscr_buf;
+	unsigned int old_uniscr_char_size = vc->vc_uniscr_char_size;
+	unsigned int new_lines;
+	unsigned int copy_cols;
+	uint32_t *dest, *src;
+	int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int new_uniscr_rows;
+	unsigned int old_lines;
+	unsigned long tmp;
+
+	if (vc->vc_softback_in >= vc->vc_softback_top)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_lines = tmp / vc->vc_size_row + old_rows;
+
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_uniscr_rows = vc->vc_uniscr_char_size / new_cols + new_rows;
+	new_lines = min(old_lines, new_uniscr_rows);
+	copy_cols = min(old_cols, new_cols);
+
+	dest = vc->vc_uniscr_curr;
+	if (new_lines > old_rows) {
+		dest -= (new_lines - old_rows) * new_cols;
+		while (dest < vc->vc_uniscr_buf) /* Could happen twice.  */
+			dest += vc->vc_uniscr_char_size;
+	}
+	src = old_uniscr_curr;
+	if (new_lines > old_rows) {
+		src -= (new_lines - old_rows) * old_cols;
+		while (src < old_uniscr_buf)
+			src += old_uniscr_char_size;
+	}
+#else
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_lines = min(old_rows, new_rows);
+	copy_cols = min(old_cols, new_cols);
+	dest = vc->vc_uniscr_curr;
+	src = old_uniscr_curr;
+#endif
+	if (old_uniscr_buf) {
+		while (new_lines--) {
+			memcpy(dest, src, copy_cols * sizeof(uint32_t));
+			if (new_cols > old_cols)
+				memset32(dest + old_cols, ' ',
+					 new_cols - old_cols);
+			UNISCR_PLUS(dest, new_cols);
+			src += old_cols;
+			if (src >= old_uniscr_buf + old_uniscr_char_size)
+				src -= old_uniscr_char_size;
+		}
+		kvfree(old_uniscr_buf);
+	}
+	return 0;
+}
+
 /**
  *	vc_do_resize	-	resizing method for the tty
  *	@tty: tty being resized
@@ -1146,12 +1570,19 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 {
 	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
 	unsigned long end;
-	unsigned int old_rows, old_row_size, first_copied_row;
-	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+	unsigned int old_rows, old_size_row, first_copied_row;
+	unsigned int new_cols, new_rows, new_size_row, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	u32 **new_uniscr = NULL;
-
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned short *d;
+	unsigned long old_softback_buf, new_softback_buf, tmp;
+	unsigned long old_softback_end, new_softback_end;
+	unsigned int old_scrolled_rows, new_scrolled_rows;
+	unsigned int count, copied_scrolled_rows;
+	void *temp_new_softback_buf;
+#endif
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!vc)
@@ -1165,8 +1596,8 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 
 	new_cols = (cols ? cols : vc->vc_cols);
 	new_rows = (lines ? lines : vc->vc_rows);
-	new_row_size = new_cols << 1;
-	new_screen_size = new_row_size * new_rows;
+	new_size_row = new_cols << 1;
+	new_screen_size = new_size_row * new_rows;
 
 	if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
 		/*
@@ -1194,61 +1625,91 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	if (!newscreen)
 		return -ENOMEM;
 
-	if (vc->vc_uni_lines) {
-		new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
-		if (!new_uniscr) {
-			kfree(newscreen);
-			return -ENOMEM;
-		}
-	}
-
 	if (vc_is_sel(vc))
 		clear_selection();
 
 	old_rows = vc->vc_rows;
-	old_row_size = vc->vc_size_row;
+	old_size_row = vc->vc_size_row;
 
 	err = resize_screen(vc, new_cols, new_rows, user);
 	if (err) {
 		kfree(newscreen);
-		vc_uniscr_free(new_uniscr);
+		vc_uniscr_free(vc);
+		vc->vc_uniscr_buf = old_uniscr;
 		return err;
 	}
 
-	vc->vc_rows = new_rows;
-	vc->vc_cols = new_cols;
-	vc->vc_size_row = new_row_size;
-	vc->vc_screenbuf_size = new_screen_size;
-
-	rlth = min(old_row_size, new_row_size);
-	rrem = new_row_size - rlth;
+	rlth = min(old_size_row, new_size_row);
+	rrem = new_size_row - rlth;
 	old_origin = vc->vc_origin;
 	new_origin = (long) newscreen;
 	new_scr_end = new_origin + new_screen_size;
 
 	if (vc->state.y > new_rows) {
-		if (old_rows - vc->state.y < new_rows) {
+		if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+			if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+				con_softback_note(vc, vc->vc_top,
+						  old_rows - new_rows - vc->vc_top);
+#endif
+				vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+						 old_rows - new_rows - vc->vc_top);
+			}
 			/*
 			 * Cursor near the bottom, copy contents from the
 			 * bottom of buffer
 			 */
 			first_copied_row = (old_rows - new_rows);
 		} else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+			con_softback_note(vc, vc->vc_top,
+					  vc->state.y - new_rows/2);
+#endif
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 vc->state.y - new_rows/2);
 			/*
 			 * Cursor is in no man's land, copy 1/2 screenful
 			 * from the top and bottom of cursor position
 			 */
 			first_copied_row = (vc->state.y - new_rows/2);
 		}
-		old_origin += first_copied_row * old_row_size;
+		old_origin += first_copied_row * old_size_row;
 	} else
 		first_copied_row = 0;
-	end = old_origin + old_row_size * min(old_rows, new_rows);
+	end = old_origin + old_size_row * min(old_rows, new_rows);
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    vc->vc_uni_lines, rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_cols, new_rows)) != 0)
+		return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	concon_set_origin(vc);
+	old_softback_buf = vc->vc_softback_buf;
+	old_softback_end = vc->vc_softback_end;
+	vc->vc_softback_size = console_soft_scrollback_size;
+	temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+	new_softback_buf = (unsigned long)temp_new_softback_buf;
+	if (!new_softback_buf)
+		return -ENOMEM;
+	new_softback_end = new_softback_buf + console_soft_scrollback_size;
+	d = (unsigned short *)new_softback_buf;
+	while (d != (u16 *)new_softback_end) {
+		scr_writew (0x0020, d);
+		d++;
+	}
+	if (vc->vc_softback_top <= vc->vc_softback_in)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_scrolled_rows = tmp / vc->vc_size_row;
+	new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+	copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
+
+	vc->vc_cols = new_cols;
+	vc->vc_rows = new_rows;
+	vc->vc_size_row = new_size_row;
+	vc->vc_screenbuf_size = new_screen_size;
 
 	update_attr(vc);
 
@@ -1258,17 +1719,60 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		if (rrem)
 			scr_memsetw((void *)(new_origin + rlth),
 				    vc->vc_video_erase_char, rrem);
-		old_origin += old_row_size;
-		new_origin += new_row_size;
+		old_origin += old_size_row;
+		new_origin += new_size_row;
 	}
 	if (new_scr_end > new_origin)
 		scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
 			    new_scr_end - new_origin);
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	new_origin = new_softback_buf;
+	if (copied_scrolled_rows) {
+		old_origin = vc->vc_softback_in
+			- copied_scrolled_rows * old_size_row;
+		if (old_origin < vc->vc_softback_buf)
+			old_origin += vc->vc_softback_end
+				- vc->vc_softback_buf;
+		count = copied_scrolled_rows;
+
+		while (count--) {
+			scr_memcpyw((unsigned short *) new_origin,
+				    (unsigned short *) old_origin, rlth);
+			if (rrem)
+				scr_memsetw((void *)(new_origin + rlth),
+					    vc->vc_video_erase_char, rrem);
+			old_origin += old_size_row;
+			if (old_origin >= old_softback_end)
+				old_origin -= old_softback_end
+					- old_softback_buf;
+			new_origin += new_size_row;
+		}
+	}
+#endif
 	oldscreen = vc->vc_screenbuf;
 	vc->vc_screenbuf = newscreen;
 	vc->vc_screenbuf_size = new_screen_size;
 	set_origin(vc);
 	kfree(oldscreen);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_buf = new_softback_buf;
+	vc->vc_softback_end = new_softback_buf
+		+ new_scrolled_rows * new_size_row;
+	if (copied_scrolled_rows) {
+		if (new_origin >= vc->vc_softback_end)
+			new_origin -= vc->vc_softback_end - vc->vc_softback_buf;
+		vc->vc_softback_in = new_origin;
+	} else
+		vc->vc_softback_in = new_softback_buf;
+	vc->vc_softback_top = new_softback_buf;
+	if (copied_scrolled_rows
+	    && (new_origin == new_softback_buf))
+		vc->vc_softback_top += new_size_row;
+	vc->vc_softback_curr = vc->vc_softback_in;
+	vc->vc_softback_lines = 0; /* Probably redundant. */
+	kvfree((void *)old_softback_buf);
+#endif
 
 	/* do part of a reset_terminal() */
 	vc->vc_top = 0;
@@ -1350,8 +1854,8 @@ struct vc_data *vc_deallocate(unsigned int currcons)
 		visual_deinit(vc);
 		con_free_unimap(vc);
 		put_pid(vc->vt_pid);
-		vc_uniscr_set(vc, NULL);
 		kfree(vc->vc_screenbuf);
+		vc->vc_uniscr_buf = NULL;
 		vc_cons[currcons].d = NULL;
 	}
 	return vc;
@@ -1600,7 +2104,7 @@ struct rgb { u8 r; u8 g; u8 b; };
 
 static void rgb_from_256(int i, struct rgb *c)
 {
-	if (i < 8) {            /* Standard colours. */
+	if (i < 8) {	    /* Standard colours. */
 		c->r = i&1 ? 0xaa : 0x00;
 		c->g = i&2 ? 0xaa : 0x00;
 		c->b = i&4 ? 0xaa : 0x00;
@@ -1612,7 +2116,7 @@ static void rgb_from_256(int i, struct rgb *c)
 		c->r = (i - 16) / 36 * 85 / 2;
 		c->g = (i - 16) / 6 % 6 * 85 / 2;
 		c->b = (i - 16) % 6 * 85 / 2;
-	} else                  /* Grayscale ramp. */
+	} else		  /* Grayscale ramp. */
 		c->r = c->g = c->b = i * 10 - 2312;
 }
 
@@ -2882,6 +3386,12 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
 
 	param.vc = vc;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	/* Undo any soft scrolling - <Alt><Fn> and <Shift><PgUp/Down> do
+	   not pass through this function.  */
+	concon_set_origin (vc);
+#endif
+
 	while (!tty->flow.stopped && count) {
 		int orig = *buf;
 		buf++;
@@ -3047,11 +3557,8 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	if (kmsg_console && vc_cons_allocated(kmsg_console - 1))
 		vc = vc_cons[kmsg_console - 1].d;
 
-	if (!vc_cons_allocated(fg_console)) {
-		/* impossible */
-		/* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
+	if (!vc_cons_allocated(fg_console))
 		goto quit;
-	}
 
 	if (vc->vc_mode != KD_TEXT)
 		goto quit;
@@ -3096,7 +3603,11 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	}
 	if (cnt && con_is_visible(vc))
 		vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
+
 	notify_update(vc);
 
 quit:
@@ -3314,7 +3825,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();
 }
@@ -3335,6 +3850,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;
@@ -3390,7 +3913,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);
@@ -3469,6 +3992,7 @@ static int __init con_init(void)
 		/* Assuming vc->vc_{cols,rows,screenbuf_size} are sane here. */
 		vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
 		vc_init(vc, currcons || !vc->vc_sw->con_save_screen);
+		vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
 	}
 	currcons = fg_console = 0;
 	master_display_fg = vc = vc_cons[currcons].d;
@@ -4080,7 +4604,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;
@@ -4380,7 +4904,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);
@@ -4693,12 +5220,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 30577b1d3de5..96530aeb7434 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -100,6 +100,44 @@ config FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
 
 	  If unsure, select n.
 
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	bool "Enable Scrollback Buffer in System RAM"
+	depends on FB=y && FRAMEBUFFER_CONSOLE
+	default y
+	help
+	  This option creates a scrollback buffer for each framebuffer console.
+          These buffers are allocated dynamically during initialisation.
+
+	  If you want this feature, say 'Y' here and enter in
+          FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+          allocate for each buffer.  If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+	int "Scrollback Buffer Size (in KB)"
+	depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	range 1 1024
+	default "128"
+	help
+	  Enter the amount of System RAM in kilobytes to allocate for the
+          scrollback buffer of each framebuffer console.  Each character
+	  position on the video takes 2 bytes of storage.  128kB will give you
+	  approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       bool "Enable a working GPM for scrolled back scrollback buffer in System RAM"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       default y
+       help
+         This option buffers up Unicode characters corresponding to the glyphs
+         displayed by the scrollback buffer.  This enables the GPM mouse driver
+         (or similar) to copy characters from a scrolled back buffer.
+
+         A buffer is created for each framebuffer console, this buffer being
+         approximately twice as big as the buffer size specified in
+         FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+         If unsure, say 'Y'.
+
 config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index f157a5a1dffc..92985a585add 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 */
@@ -2063,6 +2104,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);
@@ -2072,8 +2114,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;
 	}
 
@@ -3163,6 +3218,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.6.13-TRIPLE.20240123.diff --]
[-- Type: text/plain, Size: 940 bytes --]

diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 8967c3a0d916..f40ebbfb87de 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -217,7 +217,8 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
 			   unless non-space at end of line. */
 			if (obp != bp) {
 				bp = obp;
-				*bp++ = '\r';
+				if ((i + 2) < vc_sel.end) /* Don't add \r to the last line. */
+					*bp++ = '\r';
 			}
 			obp = bp;
 		}
@@ -263,6 +264,11 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 		new_sel_start = rounddown(ps, vc->vc_size_row);
 		new_sel_end = rounddown(pe, vc->vc_size_row) +
 			vc->vc_size_row - 2;
+		while ((new_sel_end > pe)
+		       && (is_space_on_vt (sel_pos (new_sel_end, unicode))))
+			new_sel_end -= 2;
+		if (!((new_sel_end) % vc->vc_size_row))
+			new_sel_end += 2;
 		break;
 	case TIOCL_SELPOINTER:
 		highlight_pointer(pe);

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.6 [was 6.3] onwards.
  2024-01-24 10:00                             ` Alan Mackenzie
@ 2024-01-24 12:20                               ` Alan Mackenzie
  2024-03-11 10:47                                 ` Peter Humphrey
  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
  1 sibling, 1 reply; 31+ messages in thread
From: Alan Mackenzie @ 2024-01-24 12:20 UTC (permalink / raw
  To: gentoo-user

Hello, Gentoo.

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

[ .... ]

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

-- 
Alan Mackenzie (Nuremberg, Germany).


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards.
  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-01-24 14:08                               ` Peter Humphrey
  1 sibling, 0 replies; 31+ messages in thread
From: Peter Humphrey @ 2024-01-24 14:08 UTC (permalink / raw
  To: gentoo-user

On Wednesday, 24 January 2024 10:00:37 GMT Alan Mackenzie wrote:

> I've adapted this patch for kernel 6.6.13.  All of the parent post still
> applies, with the exception of the version numbers.
> 
> The main patch for the new kernel is 6.6.13-GPM.20240123.diff.
> 
> Additionally I've attached a smaller patch which fixes what to me was a
> little glitch in the gpm utility.  This was that on a triple click to
> select a whole line (or sequence of lines), the (last) line got a
> linefeed appended to it.  The effect was that if you wanted to hoik a
> line out of some screen output and execute that from the bash command
> line, you couldn't edit it first.  So with this patch, you no longer get
> that annoying linefeed, and the highlighting is adjusted accordingly.
> This patch is (as far as I'm aware, without testing) independent of the
> main GPM patch.

Ever the star, Alan!

-- 
Regards,
Peter.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.6 [was 6.3] onwards.
  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
  0 siblings, 1 reply; 31+ messages in thread
From: Peter Humphrey @ 2024-03-11 10:47 UTC (permalink / raw
  To: gentoo-user

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

On Wednesday, 24 January 2024 12:20:29 GMT Alan Mackenzie wrote:
> Hello, Gentoo.
> 
> On Wed, Jan 24, 2024 at 10:00:37 +0000, Alan Mackenzie wrote:
> 
> [ .... ]
> 
> Please note the corrected subject line.  This version of the soft
> scrolling patch is for kernel 6.6.13, or thereabouts.

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

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

-- 
Regards,
Peter.

[-- Attachment #2: vt.c.rej --]
[-- Type: text/x-reject, Size: 5293 bytes --]

--- drivers/tty/vt/vt.c
+++ drivers/tty/vt/vt.c
@@ -348,107 +377,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;
 	}
 }
 

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.8.1 onwards.
  2024-03-11 10:47                                 ` Peter Humphrey
@ 2024-04-04  8:05                                   ` Alan Mackenzie
  0 siblings, 0 replies; 31+ messages in thread
From: Alan Mackenzie @ 2024-04-04  8:05 UTC (permalink / raw
  To: gentoo-user; +Cc: Peter Humphrey

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

Hello, Peter.

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

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

> > [ .... ]

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

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

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

Thanks!

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

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

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

..  Have fun!

> -- 
> Regards,
> Peter.



[-- Attachment #2: 6.8.1-GPM.20240402.diff --]
[-- Type: text/plain, Size: 50297 bytes --]

diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c
index 8967c3a0d916..f40ebbfb87de 100644
--- a/drivers/tty/vt/selection.c
+++ b/drivers/tty/vt/selection.c
@@ -217,7 +217,8 @@ static int vc_selection_store_chars(struct vc_data *vc, bool unicode)
 			   unless non-space at end of line. */
 			if (obp != bp) {
 				bp = obp;
-				*bp++ = '\r';
+				if ((i + 2) < vc_sel.end) /* Don't add \r to the last line. */
+					*bp++ = '\r';
 			}
 			obp = bp;
 		}
@@ -263,6 +264,11 @@ static int vc_do_selection(struct vc_data *vc, unsigned short mode, int ps,
 		new_sel_start = rounddown(ps, vc->vc_size_row);
 		new_sel_end = rounddown(pe, vc->vc_size_row) +
 			vc->vc_size_row - 2;
+		while ((new_sel_end > pe)
+		       && (is_space_on_vt (sel_pos (new_sel_end, unicode))))
+			new_sel_end -= 2;
+		if (!((new_sel_end) % vc->vc_size_row))
+			new_sel_end += 2;
 		break;
 	case TIOCL_SELPOINTER:
 		highlight_pointer(pe);
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 38a765eadbe2..dd012e99e797 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -134,6 +134,11 @@ const struct consw *conswitchp;
 #define DEFAULT_BELL_DURATION	(HZ/8)
 #define DEFAULT_CURSOR_BLINK_MS	200
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static unsigned int console_soft_scrollback_size =
+	1024 * CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE;
+#endif
+
 struct vc vc_cons [MAX_NR_CONSOLES];
 EXPORT_SYMBOL(vc_cons);
 
@@ -289,8 +294,31 @@ static inline bool con_should_update(const struct vc_data *vc)
 static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 		bool viewed)
 {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned long softback_pos, scrolled_expanse;
+
+	if (vc->vc_softback_curr == vc->vc_origin) /* Should never happen! */
+		return (unsigned short *)(vc->vc_origin + offset);
+	else {
+		if (vc->vc_softback_in >= vc->vc_softback_curr)
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr;
+		else
+			scrolled_expanse = vc->vc_softback_in - vc->vc_softback_curr
+				+ vc->vc_softback_end - vc->vc_softback_buf;
+		if (offset >= scrolled_expanse)
+			return (unsigned short *)(vc->vc_origin
+						  + (offset - scrolled_expanse));
+		else {
+			softback_pos = vc->vc_softback_curr + offset;
+			if (softback_pos >= vc->vc_softback_end)
+				softback_pos -= vc->vc_softback_end
+					- vc->vc_softback_buf;
+		}
+	}
+	return (unsigned short *)softback_pos;
+#else
 	unsigned short *p;
-	
+
 	if (!viewed)
 		p = (unsigned short *)(vc->vc_origin + offset);
 	else if (!vc->vc_sw->con_screen_pos)
@@ -298,6 +326,7 @@ static inline unsigned short *screenpos(const struct vc_data *vc, int offset,
 	else
 		p = vc->vc_sw->con_screen_pos(vc, offset);
 	return p;
+#endif
 }
 
 /* Called  from the keyboard irq path.. */
@@ -319,107 +348,111 @@ void schedule_console_callback(void)
  * Code to manage unicode-based screen buffers
  */
 
-/*
- * Our screen buffer is preceded by an array of line pointers so that
- * scrolling only implies some pointer shuffling.
- */
+#define vc_uniscr_buf_end(vc) (vc->vc_uniscr_buf + vc->vc_uniscr_char_size)
 
-static u32 **vc_uniscr_alloc(unsigned int cols, unsigned int rows)
+static int vc_uniscr_alloc(struct vc_data *vc, unsigned int cols, unsigned int rows)
 {
-	u32 **uni_lines;
-	void *p;
-	unsigned int memsize, i, col_size = cols * sizeof(**uni_lines);
-
-	/* allocate everything in one go */
-	memsize = col_size * rows;
-	memsize += rows * sizeof(*uni_lines);
-	uni_lines = vzalloc(memsize);
-	if (!uni_lines)
-		return NULL;
-
-	/* initial line pointers */
-	p = uni_lines + rows;
-	for (i = 0; i < rows; i++) {
-		uni_lines[i] = p;
-		p += col_size;
-	}
+	uint32_t *p;
+	unsigned int new_size;	/* In 32-bit characters */
 
-	return uni_lines;
-}
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int num_scrollback_rows;
 
-static void vc_uniscr_free(u32 **uni_lines)
-{
-	vfree(uni_lines);
+	num_scrollback_rows = (console_soft_scrollback_size / 2) / cols;
+	new_size = cols * (num_scrollback_rows + rows + 1);
+#else
+	new_size = cols * (rows + 1); /* 1 row for the circular buffer admin */
+#endif
+	p = (uint32_t *)vzalloc (sizeof (uint32_t) * new_size);
+	if (!p)
+		return -ENOMEM;
+	vc->vc_uniscr_buf = p;
+	vc->vc_uniscr_curr = p;
+	vc->vc_uniscr_char_size = new_size;
+	memset32(p, ' ', new_size); /* Probably redundant. */
+	return 0;
 }
 
-static void vc_uniscr_set(struct vc_data *vc, u32 **new_uni_lines)
+static void vc_uniscr_free(struct vc_data *vc)
 {
-	vc_uniscr_free(vc->vc_uni_lines);
-	vc->vc_uni_lines = new_uni_lines;
+	kvfree(vc->vc_uniscr_buf);
+	vc->vc_uniscr_buf = NULL;
 }
 
 static void vc_uniscr_putc(struct vc_data *vc, u32 uc)
 {
-	if (vc->vc_uni_lines)
-		vc->vc_uni_lines[vc->state.y][vc->state.x] = uc;
+	uint32_t *pos;
+
+	if (vc->vc_uniscr_buf) {
+		pos = vc->vc_uniscr_curr;
+		UNISCR_PLUS(pos, vc->state.y * vc->vc_cols + vc->state.x);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(pos, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		*pos = uc;
+	}
 }
 
 static void vc_uniscr_insert(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memmove(&ln[x + nr], &ln[x], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[x], ' ', nr);
 	}
 }
 
 static void vc_uniscr_delete(struct vc_data *vc, unsigned int nr)
 {
-	if (vc->vc_uni_lines) {
-		u32 *ln = vc->vc_uni_lines[vc->state.y];
-		unsigned int x = vc->state.x, cols = vc->vc_cols;
+	unsigned int x = vc->state.x, y = vc->state.y, cols = vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-		memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(*ln));
+	if (vc->vc_uniscr_buf) {
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memmove(&ln[x], &ln[x + nr], (cols - x - nr) * sizeof(uint32_t));
 		memset32(&ln[cols - nr], ' ', nr);
 	}
 }
 
+/* FIXME!!!  We need to check that NR never goes beyond the current line end.  !!! */
 static void vc_uniscr_clear_line(struct vc_data *vc, unsigned int x,
 				 unsigned int nr)
 {
-	if (vc->vc_uni_lines)
-		memset32(&vc->vc_uni_lines[vc->state.y][x], ' ', nr);
+	if (vc->vc_uniscr_buf) {
+		uint32_t *ln = vc->vc_uniscr_curr;
+
+		UNISCR_PLUS(ln, vc->state.y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		memset32(&ln[x], ' ', nr);
+	}
 }
 
 static void vc_uniscr_clear_lines(struct vc_data *vc, unsigned int y,
 				  unsigned int nr)
 {
-	if (vc->vc_uni_lines)
-		while (nr--)
-			memset32(vc->vc_uni_lines[y++], ' ', vc->vc_cols);
-}
-
-/* juggling array rotation algorithm (complexity O(N), size complexity O(1)) */
-static void juggle_array(u32 **array, unsigned int size, unsigned int nr)
-{
-	unsigned int gcd_idx;
-
-	for (gcd_idx = 0; gcd_idx < gcd(nr, size); gcd_idx++) {
-		u32 *gcd_idx_val = array[gcd_idx];
-		unsigned int dst_idx = gcd_idx;
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		uint32_t *ln = vc->vc_uniscr_curr;
 
-		while (1) {
-			unsigned int src_idx = (dst_idx + nr) % size;
-			if (src_idx == gcd_idx)
-				break;
-
-			array[dst_idx] = array[src_idx];
-			dst_idx = src_idx;
+		UNISCR_PLUS(ln, y * cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_cols * (vc->vc_softback_lines + vc->vc_top));
+#endif
+		while (nr--) {
+			memset32(ln, ' ', cols);
+			UNISCR_PLUS(ln, cols);
 		}
-
-		array[dst_idx] = gcd_idx_val;
 	}
 }
 
@@ -427,49 +460,52 @@ static void vc_uniscr_scroll(struct vc_data *vc, unsigned int top,
 			     unsigned int bottom, enum con_scroll dir,
 			     unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
-	unsigned int size = bottom - top;
-
-	if (!uni_lines)
-		return;
-
-	if (dir == SM_DOWN) {
-		juggle_array(&uni_lines[top], size, size - nr);
-		vc_uniscr_clear_lines(vc, top, nr);
-	} else {
-		juggle_array(&uni_lines[top], size, nr);
-		vc_uniscr_clear_lines(vc, bottom - nr, nr);
-	}
-}
-
-static void vc_uniscr_copy_area(u32 **dst_lines,
-				unsigned int dst_cols,
-				unsigned int dst_rows,
-				u32 **src_lines,
-				unsigned int src_cols,
-				unsigned int src_top_row,
-				unsigned int src_bot_row)
-{
-	unsigned int dst_row = 0;
-
-	if (!dst_lines)
-		return;
-
-	while (src_top_row < src_bot_row) {
-		u32 *src_line = src_lines[src_top_row];
-		u32 *dst_line = dst_lines[dst_row];
-
-		memcpy(dst_line, src_line, src_cols * sizeof(*src_line));
-		if (dst_cols - src_cols)
-			memset32(dst_line + src_cols, ' ', dst_cols - src_cols);
-		src_top_row++;
-		dst_row++;
-	}
-	while (dst_row < dst_rows) {
-		u32 *dst_line = dst_lines[dst_row];
-
-		memset32(dst_line, ' ', dst_cols);
-		dst_row++;
+	if (vc->vc_uniscr_buf) {
+		unsigned int cols = vc->vc_cols;
+		unsigned int sz, /* number of rows being scrolled */
+			d,		  /* number of rows needing blanking */
+			clear;		  /* The number of the topmost row needing blanking. */
+		uint32_t *dest, *src;
+		unsigned int i;
+
+		if (dir == SM_UP /* && top == 0 */&& bottom == vc->vc_rows) {
+			UNISCR_PLUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+			clear = vc->vc_rows - nr;
+		} else if (dir == SM_DOWN && top == 0 && bottom == vc->vc_rows - nr) {
+			UNISCR_MINUS(vc->vc_uniscr_curr, nr * cols);
+			d = nr;
+		clear = top;
+		} else if (dir == SM_UP) {
+			sz = bottom - top;
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, top * cols);
+			dest = src;
+			UNISCR_MINUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				memcpy(dest, src, cols * sizeof(uint32_t));
+				UNISCR_PLUS(src, cols);
+				UNISCR_PLUS(dest, cols);
+			}
+			d = nr;
+			clear = bottom - nr;
+		} else {
+			src = vc->vc_uniscr_curr;
+			UNISCR_PLUS(src, bottom * cols);
+			dest = src;
+			UNISCR_PLUS(dest, nr * cols);
+			i = bottom - top;
+			while (i--) {
+				UNISCR_MINUS(src, cols);
+				UNISCR_MINUS(dest, cols);
+				memcpy(dest, src, cols * sizeof(uint32_t));
+			}
+			d = nr;
+			clear = top;
+		}
+		if (d)
+			vc_uniscr_clear_lines(vc, clear, nr);
 	}
 }
 
@@ -481,7 +517,6 @@ static void vc_uniscr_copy_area(u32 **dst_lines,
  */
 int vc_uniscr_check(struct vc_data *vc)
 {
-	u32 **uni_lines;
 	unsigned short *p;
 	int x, y, mask;
 
@@ -490,11 +525,10 @@ int vc_uniscr_check(struct vc_data *vc)
 	if (!vc->vc_utf)
 		return -ENODATA;
 
-	if (vc->vc_uni_lines)
+	if (vc->vc_uniscr_buf)
 		return 0;
 
-	uni_lines = vc_uniscr_alloc(vc->vc_cols, vc->vc_rows);
-	if (!uni_lines)
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows))
 		return -ENOMEM;
 
 	/*
@@ -506,15 +540,14 @@ int vc_uniscr_check(struct vc_data *vc)
 	p = (unsigned short *)vc->vc_origin;
 	mask = vc->vc_hi_font_mask | 0xff;
 	for (y = 0; y < vc->vc_rows; y++) {
-		u32 *line = uni_lines[y];
+		uint32_t *line = vc->vc_uniscr_curr;
+		UNISCR_PLUS(line, y * vc->vc_cols);
 		for (x = 0; x < vc->vc_cols; x++) {
 			u16 glyph = scr_readw(p++) & mask;
 			line[x] = inverse_translate(vc, glyph, true);
 		}
 	}
 
-	vc->vc_uni_lines = uni_lines;
-
 	return 0;
 }
 
@@ -526,13 +559,24 @@ int vc_uniscr_check(struct vc_data *vc)
 void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 			 unsigned int row, unsigned int col, unsigned int nr)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	uint32_t *pos;	       /* Position in the unicode buffer of col/row */
+#else
 	int offset = row * vc->vc_size_row + col * 2;
-	unsigned long pos;
+	unsigned long pos; /* Position in the main screen buffer of col/row */
+#endif
 
-	if (WARN_ON_ONCE(!uni_lines))
+	if (WARN_ON_ONCE(!vc->vc_uniscr_buf))
 		return;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	pos = vc->vc_uniscr_curr;
+	UNISCR_PLUS(pos, row * vc->vc_cols + col);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	UNISCR_MINUS(pos, vc->vc_softback_lines * vc->vc_cols);
+#endif
+	memcpy(dest, pos, nr * sizeof(uint32_t));
+#else
 	pos = (unsigned long)screenpos(vc, offset, viewed);
 	if (pos >= vc->vc_origin && pos < vc->vc_scr_end) {
 		/*
@@ -542,24 +586,233 @@ void vc_uniscr_copy_line(const struct vc_data *vc, void *dest, bool viewed,
 		 */
 		row = (pos - vc->vc_origin) / vc->vc_size_row;
 		col = ((pos - vc->vc_origin) % vc->vc_size_row) / 2;
-		memcpy(dest, &uni_lines[row][col], nr * sizeof(u32));
+		memcpy(dest,
+		       (void *)(vc->vc_uniscr_curr + row * vc->vc_cols + col),
+		       nr);
 	} else {
 		/*
-		 * Scrollback is active. For now let's simply backtranslate
-		 * the screen glyphs until the unicode screen buffer does
-		 * synchronize with console display drivers for a scrollback
-		 * buffer of its own.
+		 * Scrollback is active.  So hoik the unicode characters out
+		 * of the unicode circular buffer.
 		 */
-		u16 *p = (u16 *)pos;
-		int mask = vc->vc_hi_font_mask | 0xff;
-		u32 *uni_buf = dest;
-		while (nr--) {
-			u16 glyph = scr_readw(p++) & mask;
-			*uni_buf++ = inverse_translate(vc, glyph, true);
+		/* CAN'T HAPPEN!!!  (Hah hah!) */
+	}
+#endif
+}
+
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void con_update_softback(struct vc_data *vc)
+{
+	int l = console_soft_scrollback_size / vc->vc_size_row;
+	if (l > 5)
+	{
+		vc->vc_softback_end = vc->vc_softback_buf + l * vc->vc_size_row;
+		vc->vc_softback_top = vc->vc_softback_buf;
+	}
+	else
+		/* Smaller scrollback makes no sense, and 0 would screw
+		   the operation totally */
+		vc->vc_softback_top = 0;
+}
+
+static int concon_set_origin(struct vc_data *vc)
+{
+	if (vc->vc_softback_lines)
+		concon_scrolldelta(vc, vc->vc_softback_lines);
+	return 0;
+}
+
+#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+
+static void con_redraw_softback(struct vc_data *vc, /* struct display *p, */
+				long delta)
+{
+	int count = vc->vc_rows;
+	unsigned short *d, *s;
+	unsigned long n;
+	int line = 0;
+
+	if (!vc->vc_softback_lines)
+		vc->vc_char_at_pos = scr_readw((u16 *)vc->vc_pos);
+
+	d = (u16 *) vc->vc_softback_curr;
+	if (d == (u16 *) vc->vc_softback_in)
+		d = (u16 *) vc->vc_origin;
+	n = vc->vc_softback_curr + delta * vc->vc_size_row;
+	vc->vc_softback_lines -= delta;
+	if (delta < 0) {
+		if (vc->vc_softback_curr < vc->vc_softback_top
+		    && n < vc->vc_softback_buf) {
+			n += vc->vc_softback_end - vc->vc_softback_buf;
+			if (n < vc->vc_softback_top) {
+				vc->vc_softback_lines -=
+				    (vc->vc_softback_top - n) / vc->vc_size_row;
+				n = vc->vc_softback_top;
+			}
+		} else if (vc->vc_softback_curr >= vc->vc_softback_top
+			   && n < vc->vc_softback_top) {
+			vc->vc_softback_lines -=
+			    (vc->vc_softback_top - n) / vc->vc_size_row;
+			n = vc->vc_softback_top;
+		}
+	} else {
+		if (vc->vc_softback_curr > vc->vc_softback_in
+		    && n >= vc->vc_softback_end) {
+			n += vc->vc_softback_buf - vc->vc_softback_end;
+			if (n > vc->vc_softback_in) {
+				n = vc->vc_softback_in;
+				vc->vc_softback_lines = 0;
+			}
+		} else if (vc->vc_softback_curr <= vc->vc_softback_in
+			   && n > vc->vc_softback_in) {
+			n = vc->vc_softback_in;
+			vc->vc_softback_lines = 0;
 		}
 	}
+	if (n == vc->vc_softback_curr)
+		return;
+	vc->vc_softback_curr = n;
+	/* If we're not scrolled any more, restore the character to the cursor
+	 * position */
+	if (!vc->vc_softback_lines)
+		scr_writew(vc->vc_char_at_pos, (u16 *)vc->vc_pos);
+	s = (u16 *) vc->vc_softback_curr;
+	if (s == (u16 *) vc->vc_softback_in)
+		s = (u16 *) vc->vc_origin;
+	while (count--) {
+		unsigned short *start;
+		unsigned short *le;
+		unsigned short c;
+		int x = 0;
+		unsigned short attr = 1;
+
+		start = s;
+		le = advance_row(s, 1);
+		/* Temporarily overwrite the character at the cursor position
+		 * with the one we actually want to see on the screen.  */
+		if (count == vc->vc_rows - vc->state.y - 1)
+		{
+			c = scr_readw((u16 *)(s + vc->state.x));
+			scr_writew(c, (u16 *)vc->vc_pos);
+			vc->vc_sw->con_putcs
+				(vc, (u16 *)vc->vc_pos, 1, line, vc->state.x);
+		}
+		do {
+			c = scr_readw(s);
+			if (attr != (c & 0xff00)) {
+				attr = c & 0xff00;
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start;
+					start = s;
+				}
+			}
+			if (c == scr_readw(d)) {
+				if (s > start) {
+					vc->vc_sw->con_putcs(
+						vc, start, s - start,
+						line, x);
+					x += s - start + 1;
+					start = s + 1;
+				} else {
+					x++;
+					start++;
+				}
+			}
+			s++;
+			d++;
+		} while (s < le);
+		if (s > start)
+			vc->vc_sw->con_putcs(vc, start, s - start, line, x);
+		line++;
+		if (d == (u16 *) vc->vc_softback_end)
+			d = (u16 *) vc->vc_softback_buf;
+		if (d == (u16 *) vc->vc_softback_in)
+			d = (u16 *) vc->vc_origin;
+		if (s == (u16 *) vc->vc_softback_end)
+			s = (u16 *) vc->vc_softback_buf;
+		if (s == (u16 *) vc->vc_softback_in)
+			s = (u16 *) vc->vc_origin;
+	}
 }
 
+static inline void con_softback_note(struct vc_data *vc, int t,
+				     int count)
+{
+	unsigned short *p;
+
+	if (vc->vc_num != fg_console)
+		return;
+	p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+	while (count) {
+		scr_memcpyw((u16 *) vc->vc_softback_in, p, vc->vc_size_row);
+		count--;
+		p = advance_row(p, 1);
+		vc->vc_softback_in += vc->vc_size_row;
+		if (vc->vc_softback_in == vc->vc_softback_end)
+			vc->vc_softback_in = vc->vc_softback_buf;
+		if (vc->vc_softback_in == vc->vc_softback_top) {
+			vc->vc_softback_top += vc->vc_size_row;
+			if (vc->vc_softback_top == vc->vc_softback_end)
+				vc->vc_softback_top = vc->vc_softback_buf;
+		}
+	}
+	vc->vc_softback_curr = vc->vc_softback_in;
+}
+
+void concon_scrolldelta(struct vc_data *vc, int lines)
+{
+	/* struct display *disp = &fb_display[fg_console]; */
+	/* int offset, limit, scrollback_old; */
+
+	if (vc->vc_softback_top) {
+		if (vc->vc_num != fg_console)
+			return;
+		if (vc->vc_mode != KD_TEXT || !lines)
+			return;
+#if 0
+		if (logo_shown >= 0) {
+			struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+			if (conp2->vc_top == logo_lines
+			    && conp2->vc_bottom == conp2->vc_rows)
+				conp2->vc_top = 0;
+			if (logo_shown == vc->vc_num) {
+				unsigned long p, q;
+				int i;
+
+				p = vc->vc_softback_in;
+				q = vc->vc_origin +
+				    logo_lines * vc->vc_size_row;
+				for (i = 0; i < logo_lines; i++) {
+					if (p == vc->vc_softback_top)
+						break;
+					if (p == vc->vc_softback_buf)
+						p = vc->vc_softback_end;
+					p -= vc->vc_size_row;
+					q -= vc->vc_size_row;
+					scr_memcpyw((u16 *) q, (u16 *) p,
+						    vc->vc_size_row);
+				}
+				vc->vc_softback_in = vc->vc_softback_curr = p;
+				update_region(vc, vc->vc_origin,
+					      logo_lines * vc->vc_cols);
+			}
+			logo_shown = FBCON_LOGO_CANSHOW;
+		}
+#endif
+		vc->vc_sw->con_cursor(vc, CM_ERASE /* | CM_SOFTBACK */);
+		con_redraw_softback(vc, /* disp, */ lines);
+		if (!vc->vc_softback_lines)
+			vc->vc_sw->con_cursor(vc, CM_DRAW /* | CM_SOFTBACK */);
+	}
+}
+
+#endif  /* CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK */
+
 static void con_scroll(struct vc_data *vc, unsigned int top,
 		       unsigned int bottom, enum con_scroll dir,
 		       unsigned int nr)
@@ -571,6 +824,10 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 		nr = rows - 1;
 	if (bottom > vc->vc_rows || top >= bottom || nr < 1)
 		return;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (dir == SM_UP && vc->vc_softback_top)
+		con_softback_note (vc, top, nr);
+#endif
 
 	vc_uniscr_scroll(vc, top, bottom, dir, nr);
 	if (con_is_visible(vc) &&
@@ -588,6 +845,53 @@ static void con_scroll(struct vc_data *vc, unsigned int top,
 	scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr);
 }
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+	unsigned int xx, yy, offset;
+	u16 *p;
+
+	unsigned long origin =
+		(start >= vc->vc_softback_buf && start < vc->vc_softback_end)
+		? start >= vc->vc_softback_curr
+		  ? vc->vc_softback_curr
+		  : vc->vc_softback_curr
+		    - (vc->vc_softback_end - vc->vc_softback_buf)
+		: vc->vc_origin - vc->vc_softback_lines * vc->vc_size_row;
+	p = (u16 *) start;
+	offset = (start - origin) / 2;
+	xx = offset % vc->vc_cols;
+	yy = offset / vc->vc_cols;
+
+	for(;;) {
+		u16 attrib = scr_readw(p) & 0xff00;
+		int startx = xx;
+		u16 *q = p;
+		while (xx < vc->vc_cols && count) {
+			if (attrib != (scr_readw(p) & 0xff00)) {
+				if (p > q)
+					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+				startx = xx;
+				q = p;
+				attrib = scr_readw(p) & 0xff00;
+			}
+			p++;
+			xx++;
+			count--;
+		}
+		if (p > q)
+			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+		if (p == (u16 *) vc->vc_softback_end)
+			p = (u16 *)vc->vc_softback_buf;
+		if (p == (u16 *) vc->vc_softback_in)
+			p = (u16 *)vc->vc_origin;
+		if (!count)
+			break;
+		xx = 0;
+		yy++;
+	}
+}
+#else
 static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 {
 	unsigned int xx, yy, offset;
@@ -631,6 +935,7 @@ static void do_update_region(struct vc_data *vc, unsigned long start, int count)
 		}
 	}
 }
+#endif
 
 void update_region(struct vc_data *vc, unsigned long start, int count)
 {
@@ -639,7 +944,10 @@ void update_region(struct vc_data *vc, unsigned long start, int count)
 	if (con_should_update(vc)) {
 		hide_cursor(vc);
 		do_update_region(vc, start, count);
-		set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		if (!vc->vc_softback_lines)
+#endif
+			set_cursor(vc);
 	}
 }
 EXPORT_SYMBOL(update_region);
@@ -701,51 +1009,68 @@ static void update_attr(struct vc_data *vc)
 }
 
 /* Note: inverting the screen twice should revert to the original state */
+/* OFFSET is the offset in bytes (not 16-bit characters), COUNT is a byte
+ * count (not a character count).  */
 void invert_screen(struct vc_data *vc, int offset, int count, bool viewed)
 {
 	unsigned short *p;
+	int row_offset, bytes_left_in_row;
+	int row_count;
 
 	WARN_CONSOLE_UNLOCKED();
 
 	count /= 2;
-	p = screenpos(vc, offset, viewed);
-	if (vc->vc_sw->con_invert_region) {
-		vc->vc_sw->con_invert_region(vc, p, count);
-	} else {
-		u16 *q = p;
-		int cnt = count;
-		u16 a;
-
-		if (!vc->vc_can_do_color) {
-			while (cnt--) {
-			    a = scr_readw(q);
-			    a ^= 0x0800;
-			    scr_writew(a, q);
-			    q++;
-			}
-		} else if (vc->vc_hi_font_mask == 0x100) {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x11ff) |
-				   ((a & 0xe000) >> 4) |
-				   ((a & 0x0e00) << 4);
-				scr_writew(a, q);
-				q++;
-			}
+	row_offset = offset;
+	bytes_left_in_row = vc->vc_size_row - (row_offset % vc->vc_size_row);
+	row_count = (count < bytes_left_in_row / 2)
+		? count
+		: bytes_left_in_row / 2;
+
+	while (count) {
+		p = screenpos(vc, row_offset, viewed);
+		if (vc->vc_sw->con_invert_region) {
+			vc->vc_sw->con_invert_region(vc, p, row_count);
 		} else {
-			while (cnt--) {
-				a = scr_readw(q);
-				a = (a & 0x88ff) |
-				   ((a & 0x7000) >> 4) |
-				   ((a & 0x0700) << 4);
-				scr_writew(a, q);
-				q++;
+			u16 *q = p;
+			int cnt = row_count;
+			u16 a;
+
+			if (!vc->vc_can_do_color) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a ^= 0x0800;
+					scr_writew(a, q);
+					q++;
+				}
+			} else if (vc->vc_hi_font_mask == 0x100) {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x11ff) |
+						((a & 0xe000) >> 4) |
+						((a & 0x0e00) << 4);
+					scr_writew(a, q);
+					q++;
+				}
+			} else {
+				while (cnt--) {
+					a = scr_readw(q);
+					a = (a & 0x88ff) |
+						((a & 0x7000) >> 4) |
+						((a & 0x0700) << 4);
+					scr_writew(a, q);
+					q++;
+				}
 			}
 		}
-	}
 
-	if (con_should_update(vc))
-		do_update_region(vc, (unsigned long) p, count);
+		if (con_should_update(vc))
+			do_update_region(vc, (unsigned long) p, row_count);
+		row_offset += 2 * row_count;
+		count -= row_count;
+		row_count = (count >= vc->vc_cols)
+			? vc->vc_cols
+			: count;
+	}
 	notify_update(vc);
 }
 
@@ -875,8 +1200,17 @@ static void set_origin(struct vc_data *vc)
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!con_is_visible(vc) ||
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	    (
+	     !concon_set_origin (vc) &&
+	     (
+#endif
 	    !vc->vc_sw->con_set_origin ||
-	    !vc->vc_sw->con_set_origin(vc))
+	    !vc->vc_sw->con_set_origin(vc)
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+		     ))
+#endif
+					  )
 		vc->vc_origin = (unsigned long)vc->vc_screenbuf;
 	vc->vc_visible_origin = vc->vc_origin;
 	vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
@@ -937,7 +1271,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 
 	if (!vc) {
 		/* strange ... */
-		/* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
 		return;
 	}
 
@@ -952,7 +1285,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		hide_cursor(old_vc);
 		if (!con_is_visible(old_vc)) {
 			save_screen(old_vc);
-			set_origin(old_vc);
 		}
 		if (tty0dev)
 			sysfs_notify(&tty0dev->kobj, NULL, "active");
@@ -965,7 +1297,6 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		int update;
 		int old_was_color = vc->vc_can_do_color;
 
-		set_origin(vc);
 		update = vc->vc_sw->con_switch(vc);
 		set_palette(vc);
 		/*
@@ -980,9 +1311,19 @@ void redraw_screen(struct vc_data *vc, int is_switch)
 		}
 
 		if (update && vc->vc_mode != KD_GRAPHICS)
-			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+			do_update_region(vc,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+					 vc->vc_softback_lines
+					 ? vc->vc_softback_curr
+					 :
+#endif
+					   vc->vc_origin,
+					 vc->vc_screenbuf_size / 2);
 	}
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	if (is_switch) {
 		vt_set_leds_compute_shiftstate();
 		notify_update(vc);
@@ -1061,7 +1402,6 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 	int err;
 
 	WARN_CONSOLE_UNLOCKED();
-
 	if (currcons >= MAX_NR_CONSOLES)
 		return -ENXIO;
 
@@ -1103,9 +1443,24 @@ int vc_allocate(unsigned int currcons)	/* return 0 on success */
 		global_cursor_default = 1;
 
 	vc_init(vc, 1);
+	if (vc_uniscr_alloc (vc, vc->vc_cols, vc->vc_rows) != 0)
+		goto err_free;
 	vcs_make_sysfs(currcons);
 	atomic_notifier_call_chain(&vt_notifier_list, VT_ALLOCATE, &param);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_size = console_soft_scrollback_size;
+	err = -ENOMEM;
+	vc->vc_softback_buf =
+		(unsigned long)vzalloc(console_soft_scrollback_size);
+	if (!vc->vc_softback_buf)
+		goto err_free;
+	vc->vc_softback_in = vc->vc_softback_top = vc->vc_softback_curr =
+		vc->vc_softback_buf;
+	vc->vc_softback_lines = 0;
+	con_update_softback(vc);
+	vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+#endif
 	return 0;
 err_free:
 	visual_deinit(vc);
@@ -1126,6 +1481,75 @@ static inline int resize_screen(struct vc_data *vc, int width, int height,
 	return err;
 }
 
+static int vc_copy_uniscr_to_new_area (struct vc_data *vc,
+				       unsigned int new_cols,
+				       unsigned int new_rows)
+{
+	unsigned int old_rows = vc->vc_rows, old_cols = vc->vc_cols;
+        uint32_t *old_uniscr_curr = vc->vc_uniscr_curr,
+		*old_uniscr_buf = vc->vc_uniscr_buf;
+	unsigned int old_uniscr_char_size = vc->vc_uniscr_char_size;
+	unsigned int new_lines;
+	unsigned int copy_cols;
+	uint32_t *dest, *src;
+	int res;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	unsigned int new_uniscr_rows;
+	unsigned int old_lines;
+	unsigned long tmp;
+
+	if (vc->vc_softback_in >= vc->vc_softback_top)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_lines = tmp / vc->vc_size_row + old_rows;
+
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_uniscr_rows = vc->vc_uniscr_char_size / new_cols + new_rows;
+	new_lines = min(old_lines, new_uniscr_rows);
+	copy_cols = min(old_cols, new_cols);
+
+	dest = vc->vc_uniscr_curr;
+	if (new_lines > old_rows) {
+		dest -= (new_lines - old_rows) * new_cols;
+		while (dest < vc->vc_uniscr_buf) /* Could happen twice.  */
+			dest += vc->vc_uniscr_char_size;
+	}
+	src = old_uniscr_curr;
+	if (new_lines > old_rows) {
+		src -= (new_lines - old_rows) * old_cols;
+		while (src < old_uniscr_buf)
+			src += old_uniscr_char_size;
+	}
+#else
+	if ((res = vc_uniscr_alloc(vc, new_cols, new_rows)) != 0)
+		return res;
+
+	new_lines = min(old_rows, new_rows);
+	copy_cols = min(old_cols, new_cols);
+	dest = vc->vc_uniscr_curr;
+	src = old_uniscr_curr;
+#endif
+	if (old_uniscr_buf) {
+		while (new_lines--) {
+			memcpy(dest, src, copy_cols * sizeof(uint32_t));
+			if (new_cols > old_cols)
+				memset32(dest + old_cols, ' ',
+					 new_cols - old_cols);
+			UNISCR_PLUS(dest, new_cols);
+			src += old_cols;
+			if (src >= old_uniscr_buf + old_uniscr_char_size)
+				src -= old_uniscr_char_size;
+		}
+		kvfree(old_uniscr_buf);
+	}
+	return 0;
+}
+
 /**
  *	vc_do_resize	-	resizing method for the tty
  *	@tty: tty being resized
@@ -1146,12 +1570,19 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 {
 	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
 	unsigned long end;
-	unsigned int old_rows, old_row_size, first_copied_row;
-	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+	unsigned int old_rows, old_size_row, first_copied_row;
+	unsigned int new_cols, new_rows, new_size_row, new_screen_size;
 	unsigned int user;
 	unsigned short *oldscreen, *newscreen;
-	u32 **new_uniscr = NULL;
-
+	uint32_t *old_uniscr = vc->vc_uniscr_buf;
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned short *d;
+	unsigned long old_softback_buf, new_softback_buf, tmp;
+	unsigned long old_softback_end, new_softback_end;
+	unsigned int old_scrolled_rows, new_scrolled_rows;
+	unsigned int count, copied_scrolled_rows;
+	void *temp_new_softback_buf;
+#endif
 	WARN_CONSOLE_UNLOCKED();
 
 	if (!vc)
@@ -1165,8 +1596,8 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 
 	new_cols = (cols ? cols : vc->vc_cols);
 	new_rows = (lines ? lines : vc->vc_rows);
-	new_row_size = new_cols << 1;
-	new_screen_size = new_row_size * new_rows;
+	new_size_row = new_cols << 1;
+	new_screen_size = new_size_row * new_rows;
 
 	if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) {
 		/*
@@ -1194,61 +1625,91 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 	if (!newscreen)
 		return -ENOMEM;
 
-	if (vc->vc_uni_lines) {
-		new_uniscr = vc_uniscr_alloc(new_cols, new_rows);
-		if (!new_uniscr) {
-			kfree(newscreen);
-			return -ENOMEM;
-		}
-	}
-
 	if (vc_is_sel(vc))
 		clear_selection();
 
 	old_rows = vc->vc_rows;
-	old_row_size = vc->vc_size_row;
+	old_size_row = vc->vc_size_row;
 
 	err = resize_screen(vc, new_cols, new_rows, user);
 	if (err) {
 		kfree(newscreen);
-		vc_uniscr_free(new_uniscr);
+		vc_uniscr_free(vc);
+		vc->vc_uniscr_buf = old_uniscr;
 		return err;
 	}
 
-	vc->vc_rows = new_rows;
-	vc->vc_cols = new_cols;
-	vc->vc_size_row = new_row_size;
-	vc->vc_screenbuf_size = new_screen_size;
-
-	rlth = min(old_row_size, new_row_size);
-	rrem = new_row_size - rlth;
+	rlth = min(old_size_row, new_size_row);
+	rrem = new_size_row - rlth;
 	old_origin = vc->vc_origin;
 	new_origin = (long) newscreen;
 	new_scr_end = new_origin + new_screen_size;
 
 	if (vc->state.y > new_rows) {
-		if (old_rows - vc->state.y < new_rows) {
+		if (old_rows - new_rows < vc->vc_top + vc->state.y) {
+			if (old_rows - new_rows > vc->vc_top) {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+				con_softback_note(vc, vc->vc_top,
+						  old_rows - new_rows - vc->vc_top);
+#endif
+				vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+						 old_rows - new_rows - vc->vc_top);
+			}
 			/*
 			 * Cursor near the bottom, copy contents from the
 			 * bottom of buffer
 			 */
 			first_copied_row = (old_rows - new_rows);
 		} else {
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+			con_softback_note(vc, vc->vc_top,
+					  vc->state.y - new_rows/2);
+#endif
+			vc_uniscr_scroll(vc, 0, old_rows, SM_UP,
+					 vc->state.y - new_rows/2);
 			/*
 			 * Cursor is in no man's land, copy 1/2 screenful
 			 * from the top and bottom of cursor position
 			 */
 			first_copied_row = (vc->state.y - new_rows/2);
 		}
-		old_origin += first_copied_row * old_row_size;
+		old_origin += first_copied_row * old_size_row;
 	} else
 		first_copied_row = 0;
-	end = old_origin + old_row_size * min(old_rows, new_rows);
+	end = old_origin + old_size_row * min(old_rows, new_rows);
 
-	vc_uniscr_copy_area(new_uniscr, new_cols, new_rows,
-			    vc->vc_uni_lines, rlth/2, first_copied_row,
-			    min(old_rows, new_rows));
-	vc_uniscr_set(vc, new_uniscr);
+	if ((err = vc_copy_uniscr_to_new_area(vc, new_cols, new_rows)) != 0)
+		return err;
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	concon_set_origin(vc);
+	old_softback_buf = vc->vc_softback_buf;
+	old_softback_end = vc->vc_softback_end;
+	vc->vc_softback_size = console_soft_scrollback_size;
+	temp_new_softback_buf = vzalloc(console_soft_scrollback_size);
+	new_softback_buf = (unsigned long)temp_new_softback_buf;
+	if (!new_softback_buf)
+		return -ENOMEM;
+	new_softback_end = new_softback_buf + console_soft_scrollback_size;
+	d = (unsigned short *)new_softback_buf;
+	while (d != (u16 *)new_softback_end) {
+		scr_writew (0x0020, d);
+		d++;
+	}
+	if (vc->vc_softback_top <= vc->vc_softback_in)
+		tmp = vc->vc_softback_in - vc->vc_softback_top;
+	else
+		tmp = vc->vc_softback_in - vc->vc_softback_top
+			+ vc->vc_softback_end - vc->vc_softback_buf;
+	old_scrolled_rows = tmp / vc->vc_size_row;
+	new_scrolled_rows = console_soft_scrollback_size / new_size_row;
+	copied_scrolled_rows = min(old_scrolled_rows, new_scrolled_rows);
+#endif
+
+	vc->vc_cols = new_cols;
+	vc->vc_rows = new_rows;
+	vc->vc_size_row = new_size_row;
+	vc->vc_screenbuf_size = new_screen_size;
 
 	update_attr(vc);
 
@@ -1258,17 +1719,60 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc,
 		if (rrem)
 			scr_memsetw((void *)(new_origin + rlth),
 				    vc->vc_video_erase_char, rrem);
-		old_origin += old_row_size;
-		new_origin += new_row_size;
+		old_origin += old_size_row;
+		new_origin += new_size_row;
 	}
 	if (new_scr_end > new_origin)
 		scr_memsetw((void *)new_origin, vc->vc_video_erase_char,
 			    new_scr_end - new_origin);
+
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	new_origin = new_softback_buf;
+	if (copied_scrolled_rows) {
+		old_origin = vc->vc_softback_in
+			- copied_scrolled_rows * old_size_row;
+		if (old_origin < vc->vc_softback_buf)
+			old_origin += vc->vc_softback_end
+				- vc->vc_softback_buf;
+		count = copied_scrolled_rows;
+
+		while (count--) {
+			scr_memcpyw((unsigned short *) new_origin,
+				    (unsigned short *) old_origin, rlth);
+			if (rrem)
+				scr_memsetw((void *)(new_origin + rlth),
+					    vc->vc_video_erase_char, rrem);
+			old_origin += old_size_row;
+			if (old_origin >= old_softback_end)
+				old_origin -= old_softback_end
+					- old_softback_buf;
+			new_origin += new_size_row;
+		}
+	}
+#endif
 	oldscreen = vc->vc_screenbuf;
 	vc->vc_screenbuf = newscreen;
 	vc->vc_screenbuf_size = new_screen_size;
 	set_origin(vc);
 	kfree(oldscreen);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	vc->vc_softback_buf = new_softback_buf;
+	vc->vc_softback_end = new_softback_buf
+		+ new_scrolled_rows * new_size_row;
+	if (copied_scrolled_rows) {
+		if (new_origin >= vc->vc_softback_end)
+			new_origin -= vc->vc_softback_end - vc->vc_softback_buf;
+		vc->vc_softback_in = new_origin;
+	} else
+		vc->vc_softback_in = new_softback_buf;
+	vc->vc_softback_top = new_softback_buf;
+	if (copied_scrolled_rows
+	    && (new_origin == new_softback_buf))
+		vc->vc_softback_top += new_size_row;
+	vc->vc_softback_curr = vc->vc_softback_in;
+	vc->vc_softback_lines = 0; /* Probably redundant. */
+	kvfree((void *)old_softback_buf);
+#endif
 
 	/* do part of a reset_terminal() */
 	vc->vc_top = 0;
@@ -1350,8 +1854,8 @@ struct vc_data *vc_deallocate(unsigned int currcons)
 		visual_deinit(vc);
 		con_free_unimap(vc);
 		put_pid(vc->vt_pid);
-		vc_uniscr_set(vc, NULL);
 		kfree(vc->vc_screenbuf);
+		vc->vc_uniscr_buf = NULL;
 		vc_cons[currcons].d = NULL;
 	}
 	return vc;
@@ -1600,7 +2104,7 @@ struct rgb { u8 r; u8 g; u8 b; };
 
 static void rgb_from_256(int i, struct rgb *c)
 {
-	if (i < 8) {            /* Standard colours. */
+	if (i < 8) {	    /* Standard colours. */
 		c->r = i&1 ? 0xaa : 0x00;
 		c->g = i&2 ? 0xaa : 0x00;
 		c->b = i&4 ? 0xaa : 0x00;
@@ -1612,7 +2116,7 @@ static void rgb_from_256(int i, struct rgb *c)
 		c->r = (i - 16) / 36 * 85 / 2;
 		c->g = (i - 16) / 6 % 6 * 85 / 2;
 		c->b = (i - 16) % 6 * 85 / 2;
-	} else                  /* Grayscale ramp. */
+	} else		  /* Grayscale ramp. */
 		c->r = c->g = c->b = i * 10 - 2312;
 }
 
@@ -2882,6 +3386,12 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
 
 	param.vc = vc;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	/* Undo any soft scrolling - <Alt><Fn> and <Shift><PgUp/Down> do
+	   not pass through this function.  */
+	concon_set_origin (vc);
+#endif
+
 	while (!tty->flow.stopped && count) {
 		int orig = *buf;
 		buf++;
@@ -3047,11 +3557,8 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	if (kmsg_console && vc_cons_allocated(kmsg_console - 1))
 		vc = vc_cons[kmsg_console - 1].d;
 
-	if (!vc_cons_allocated(fg_console)) {
-		/* impossible */
-		/* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
+	if (!vc_cons_allocated(fg_console))
 		goto quit;
-	}
 
 	if (vc->vc_mode != KD_TEXT)
 		goto quit;
@@ -3096,7 +3603,11 @@ static void vt_console_print(struct console *co, const char *b, unsigned count)
 	}
 	if (cnt && con_is_visible(vc))
 		vc->vc_sw->con_putcs(vc, start, cnt, vc->state.y, start_x);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
+
 	notify_update(vc);
 
 quit:
@@ -3320,7 +3831,11 @@ static void con_flush_chars(struct tty_struct *tty)
 	/* if we race with con_close(), vt may be null */
 	console_lock();
 	vc = tty->driver_data;
-	if (vc)
+	if (vc
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	    && !vc->vc_softback_lines
+#endif
+	      )
 		set_cursor(vc);
 	console_unlock();
 }
@@ -3341,6 +3856,14 @@ static int con_install(struct tty_driver *driver, struct tty_struct *tty)
 
 	vc = vc_cons[currcons].d;
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	con_update_softback(vc);
+	if (!vc->vc_uniscr_buf)
+		ret = vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
+	if (ret)
+		goto unlock;
+#endif
+
 	/* Still being freed */
 	if (vc->port.tty) {
 		ret = -ERESTARTSYS;
@@ -3396,7 +3919,7 @@ static void con_cleanup(struct tty_struct *tty)
 	tty_port_put(&vc->port);
 }
 
-static int default_color           = 7; /* white */
+static int default_color	   = 7; /* white */
 static int default_italic_color    = 2; // green (ASCII)
 static int default_underline_color = 3; // cyan (ASCII)
 module_param_named(color, default_color, int, S_IRUGO | S_IWUSR);
@@ -3475,6 +3998,7 @@ static int __init con_init(void)
 		/* Assuming vc->vc_{cols,rows,screenbuf_size} are sane here. */
 		vc->vc_screenbuf = kzalloc(vc->vc_screenbuf_size, GFP_NOWAIT);
 		vc_init(vc, currcons || !vc->vc_sw->con_save_screen);
+		vc_uniscr_alloc(vc, vc->vc_cols, vc->vc_rows);
 	}
 	currcons = fg_console = 0;
 	master_display_fg = vc = vc_cons[currcons].d;
@@ -4088,7 +4612,7 @@ static int do_register_con_driver(const struct consw *csw, int first, int last)
 			con_driver->desc = desc;
 			con_driver->node = i;
 			con_driver->flag = CON_DRIVER_FLAG_MODULE |
-			                   CON_DRIVER_FLAG_INIT;
+					   CON_DRIVER_FLAG_INIT;
 			con_driver->first = first;
 			con_driver->last = last;
 			retval = 0;
@@ -4385,7 +4909,10 @@ void do_unblank_screen(int leaving_gfx)
 	if (console_blank_hook)
 		console_blank_hook(0);
 	set_palette(vc);
-	set_cursor(vc);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	if (!vc->vc_softback_lines)
+#endif
+		set_cursor(vc);
 	vt_event_post(VT_EVENT_UNBLANK, vc->vc_num, vc->vc_num);
 }
 EXPORT_SYMBOL(do_unblank_screen);
@@ -4698,12 +5225,17 @@ EXPORT_SYMBOL_GPL(screen_glyph);
 
 u32 screen_glyph_unicode(const struct vc_data *vc, int n)
 {
-	u32 **uni_lines = vc->vc_uni_lines;
+	int y = n / vc->vc_cols, x = n % vc->vc_cols;
+	uint32_t *ln = vc->vc_uniscr_curr;
 
-	if (uni_lines)
-		return uni_lines[n / vc->vc_cols][n % vc->vc_cols];
-
-	return inverse_translate(vc, screen_glyph(vc, n * 2), true);
+	if (vc->vc_uniscr_curr) {
+		UNISCR_PLUS(ln, y * vc->vc_cols);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		UNISCR_MINUS(ln, vc->vc_softback_lines * vc->vc_cols);
+#endif
+		return ln[x];
+	}
+	return inverse_translate(vc, screen_glyph(vc, n * 2), 1);
 }
 EXPORT_SYMBOL_GPL(screen_glyph_unicode);
 
diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index bc31db6ef7d2..dc76e4852347 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -101,6 +101,44 @@ config FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
 
 	  If unsure, select n.
 
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	bool "Enable Scrollback Buffer in System RAM"
+	depends on FB=y && FRAMEBUFFER_CONSOLE
+	default y
+	help
+	  This option creates a scrollback buffer for each framebuffer console.
+          These buffers are allocated dynamically during initialisation.
+
+	  If you want this feature, say 'Y' here and enter in
+          FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE the amount of RAM to
+          allocate for each buffer.  If unsure, say 'N'.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE
+	int "Scrollback Buffer Size (in KB)"
+	depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	range 1 1024
+	default "128"
+	help
+	  Enter the amount of System RAM in kilobytes to allocate for the
+          scrollback buffer of each framebuffer console.  Each character
+	  position on the video takes 2 bytes of storage.  128kB will give you
+	  approximately four 240x67 screenfuls of scrollback buffer.
+
+config FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+       bool "Enable a working GPM for scrolled back scrollback buffer in System RAM"
+       depends on FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+       default y
+       help
+         This option buffers up Unicode characters corresponding to the glyphs
+         displayed by the scrollback buffer.  This enables the GPM mouse driver
+         (or similar) to copy characters from a scrolled back buffer.
+
+         A buffer is created for each framebuffer console, this buffer being
+         approximately twice as big as the buffer size specified in
+         FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_SIZE.
+
+         If unsure, say 'Y'.
+
 config FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
        bool "Map the console to the primary display device"
        depends on FRAMEBUFFER_CONSOLE
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 46823c2e2ba1..e95244e01c94 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -611,6 +611,28 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 		    erase,
 		    vc->vc_size_row * logo_lines);
 
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+	{
+		uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+		unsigned int i = vc->vc_rows - logo_lines;
+
+		UNISCR_PLUS(d, vc->vc_rows * vc->vc_cols);
+		UNISCR_PLUS(s, (vc->vc_rows - logo_lines) * vc->vc_cols);
+		while (i--) {
+			UNISCR_MINUS(d, vc->vc_cols);
+			UNISCR_MINUS(s, vc->vc_cols);
+			memcpy(d, s, vc->vc_cols * sizeof (uint32_t));
+		}
+		i = logo_lines;
+		d = vc->vc_uniscr_curr;
+		while (i--) {
+			memset32(d, ' ', vc->vc_cols);
+			UNISCR_PLUS(d, vc->vc_cols);
+		}
+		vc->vc_uniscr_curr = d;
+	}
+#endif
+
 	if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
 		fbcon_clear_margins(vc, 0);
 		update_screen(vc);
@@ -1258,6 +1280,25 @@ static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height,
 		 * bitmap stretched into the margin area.
 		 */
 		fbcon_clear_margins(vc, 0);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK_GPM
+		{
+			uint32_t *s = vc->vc_uniscr_curr, *d = vc->vc_uniscr_curr;
+			unsigned int i = vc->vc_rows - logo_lines;
+
+			UNISCR_PLUS(d, (vc->vc_rows - logo_lines)  * vc->vc_cols);
+			UNISCR_PLUS(s, (vc->vc_rows - 2 * logo_lines) * vc->vc_cols);
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				UNISCR_MINUS(s, vc->vc_cols);
+				memcpy (d, s, vc->vc_cols * sizeof (uint32_t));
+			}
+			i = logo_lines;
+			while (i--) {
+				UNISCR_MINUS(d, vc->vc_cols);
+				memset32(d, ' ', vc->vc_cols);
+			}
+		}
+#endif
 	}
 
 	/* Split blits that cross physical y_wrap boundary */
@@ -2064,6 +2105,7 @@ static int fbcon_switch(struct vc_data *vc)
 	struct fbcon_ops *ops;
 	struct fbcon_display *p = &fb_display[vc->vc_num];
 	struct fb_var_screeninfo var;
+	unsigned short *d, *s;
 	int i, ret, prev_console;
 
 	info = fbcon_info_from_console(vc->vc_num);
@@ -2073,8 +2115,21 @@ static int fbcon_switch(struct vc_data *vc)
 		struct vc_data *conp2 = vc_cons[logo_shown].d;
 
 		if (conp2->vc_top == logo_lines
-		    && conp2->vc_bottom == conp2->vc_rows)
+		    && conp2->vc_bottom == conp2->vc_rows) {
+			/* Scroll the bottom part of the screen up to fill the
+			 * logo lines. */
+			i = conp2->vc_bottom - conp2->vc_top;
+			d = (unsigned short *)conp2->vc_origin;
+			s = (unsigned short *)(conp2->vc_origin + logo_lines * conp2->vc_size_row);
+			while (i--) {
+				scr_memcpyw(d, s, conp2->vc_size_row);
+				d += conp2->vc_cols;
+				s += conp2->vc_cols;
+			}
+			scr_memsetw(d, conp2->vc_video_erase_char,
+				    conp2->vc_size_row * logo_lines);
 			conp2->vc_top = 0;
+		}
 		logo_shown = FBCON_LOGO_CANSHOW;
 	}
 
@@ -3162,6 +3217,9 @@ static const struct consw fb_con = {
 	.con_font_get 		= fbcon_get_font,
 	.con_font_default	= fbcon_set_def_font,
 	.con_set_palette 	= fbcon_set_palette,
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	.con_scrolldelta	= concon_scrolldelta,
+#endif
 	.con_invert_region 	= fbcon_invert_region,
 	.con_screen_pos 	= fbcon_screen_pos,
 	.con_getxy 		= fbcon_getxy,
diff --git a/include/linux/console_struct.h b/include/linux/console_struct.h
index 539f1cd45309..9d87a3fa6ed3 100644
--- a/include/linux/console_struct.h
+++ b/include/linux/console_struct.h
@@ -109,6 +109,17 @@ struct vc_data {
 	unsigned short	*vc_screenbuf;		/* In-memory character/attribute buffer */
 	unsigned int	vc_screenbuf_size;
 	unsigned char	vc_mode;		/* KD_TEXT, ... */
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+	unsigned int    vc_softback_size;	/* Size in bytes of scrollback buffer. */
+	unsigned long   vc_softback_buf;	/* Address of scrollback buffer. */
+	unsigned long   vc_softback_end;	/* (Just past) end of buffer. */
+	unsigned long   vc_softback_in;		/* Head pointer into circular buffer. */
+	unsigned long   vc_softback_top;	/* Tail pointer into circular buffer. */
+	unsigned long   vc_softback_curr;	/* Pos in vc_screenbuf or vc_softback_buf
+						   corresponding to visible screen. */
+	int             vc_softback_lines;	/* Number of lines currently scrolled. */
+	unsigned short  vc_char_at_pos;		/* Char at vc_pos when no soft scroll */
+#endif
 	/* attributes for all characters on screen */
 	unsigned char	vc_attr;		/* Current attributes */
 	unsigned char	vc_def_color;		/* Default colors */
@@ -158,7 +169,9 @@ struct vc_data {
 	struct vc_data **vc_display_fg;		/* [!] Ptr to var holding fg console for this display */
 	struct uni_pagedict *uni_pagedict;
 	struct uni_pagedict **uni_pagedict_loc; /* [!] Location of uni_pagedict variable for this console */
-	u32 **vc_uni_lines;			/* unicode screen content */
+	uint32_t *vc_uniscr_buf;    /* Address of unicode screen content */
+	unsigned int vc_uniscr_char_size; /* Size of *vc-uniscr_buf in 32-bit chars */
+	uint32_t *vc_uniscr_curr;	/* Pos of first char of (unscrolled) screen */
 	/* additional information is in vt_kern.h */
 };
 
@@ -193,4 +206,22 @@ extern void vc_SAK(struct work_struct *work);
 
 bool con_is_visible(const struct vc_data *vc);
 
+/* Macros for wraparound in the uniscr buffer.  POS must be a uint32_t pointer
+ * variable which is expected to be within the confines of vc->vc_uniscr_buf
+ * and vc->vc_uniscr_buf + vc->vc_uniscr_char_size.  OFFSET must be a positive
+ * distance measured in uint32_t's, which when added to or subtracted from POS
+ * won't be too far away from the buffer. */
+#define UNISCR_PLUS(pos,offset)						\
+	do {								\
+		pos += offset;						\
+		if (pos >= vc->vc_uniscr_buf + vc->vc_uniscr_char_size) \
+			pos -= vc->vc_uniscr_char_size;			\
+	} while (0)
+#define UNISCR_MINUS(pos,offset)			\
+	do {						\
+		pos -= offset;				\
+		if (pos < vc->vc_uniscr_buf)		\
+			pos += vc->vc_uniscr_char_size; \
+	} while (0)
+
 #endif /* _LINUX_CONSOLE_STRUCT_H */
diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h
index c1f5aebef170..97ecca06eceb 100644
--- a/include/linux/vt_kern.h
+++ b/include/linux/vt_kern.h
@@ -114,6 +114,9 @@ int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
 /* vt.c */
 void vt_event_post(unsigned int event, unsigned int old, unsigned int new);
 int vt_waitactive(int n);
+#ifdef CONFIG_FRAMEBUFFER_CONSOLE_SOFT_SCROLLBACK
+void concon_scrolldelta(struct vc_data *vc, int lines);
+#endif
 void change_console(struct vc_data *new_vc);
 void reset_vc(struct vc_data *vc);
 int do_unbind_con_driver(const struct consw *csw, int first, int last,

^ permalink raw reply related	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2024-04-04  8:05 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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                             ` [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch for kernel 6.3 onwards Alan Mackenzie
2023-10-04 14:41                               ` 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox