[prev in list] [next in list] [prev in thread] [next in thread] 

List:       gentoo-user
Subject:    Re: [gentoo-user] Soft scrolling on framebuffer consoles - with GPM handling - version of the patch 
From:       Alan Mackenzie <acm () muc ! de>
Date:       2024-04-04 8:05:29
Message-ID: Zg5fSSnksZ1W2_NV () ACM
[Download RAW message or body]

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.



["6.8.1-GPM.20240402.diff" (text/plain)]

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,


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic