Index: kernel/arch/ia64/src/drivers/ski.c
===================================================================
--- kernel/arch/ia64/src/drivers/ski.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/arch/ia64/src/drivers/ski.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -61,5 +61,7 @@
 static outdev_operations_t skidev_ops = {
 	.write = ski_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/arch/mips32/src/mach/malta/malta.c
===================================================================
--- kernel/arch/mips32/src/mach/malta/malta.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/arch/mips32/src/mach/malta/malta.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -92,5 +92,7 @@
 static outdev_operations_t yamon_outdev_ops = {
 	.write = yamon_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/arch/sparc64/src/drivers/niagara.c
===================================================================
--- kernel/arch/sparc64/src/drivers/niagara.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/arch/sparc64/src/drivers/niagara.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -63,5 +63,7 @@
 static outdev_operations_t niagara_ops = {
 	.write = niagara_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/dsrln/dsrlnout.c
===================================================================
--- kernel/genarch/src/drivers/dsrln/dsrlnout.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/dsrln/dsrlnout.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -63,5 +63,7 @@
 static outdev_operations_t dsrlndev_ops = {
 	.write = dsrlnout_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/ega/ega.c
===================================================================
--- kernel/genarch/src/drivers/ega/ega.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/ega/ega.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -76,5 +76,7 @@
 static outdev_operations_t egadev_ops = {
 	.write = ega_putchar,
-	.redraw = ega_redraw
+	.redraw = ega_redraw,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/grlib/uart.c
===================================================================
--- kernel/genarch/src/drivers/grlib/uart.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/grlib/uart.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -100,5 +100,7 @@
 static outdev_operations_t grlib_uart_ops = {
 	.write = grlib_uart_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/omap/uart.c
===================================================================
--- kernel/genarch/src/drivers/omap/uart.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/omap/uart.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -61,6 +61,8 @@
 
 static outdev_operations_t omap_uart_ops = {
+	.write = omap_uart_putchar,
 	.redraw = NULL,
-	.write = omap_uart_putchar,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/pl011/pl011.c
===================================================================
--- kernel/genarch/src/drivers/pl011/pl011.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/pl011/pl011.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -72,4 +72,6 @@
 	.write = pl011_uart_putchar,
 	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/drivers/s3c24xx/uart.c
===================================================================
--- kernel/genarch/src/drivers/s3c24xx/uart.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/drivers/s3c24xx/uart.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -95,5 +95,7 @@
 static outdev_operations_t s3c24xx_uart_ops = {
 	.write = s3c24xx_uart_putchar,
-	.redraw = NULL
+	.redraw = NULL,
+	.scroll_up = NULL,
+	.scroll_down = NULL
 };
 
Index: kernel/genarch/src/fb/fb.c
===================================================================
--- kernel/genarch/src/fb/fb.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/fb/fb.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -56,4 +56,6 @@
 #define INV_COLOR    0xaaaaaa
 
+#define FB_PAGES  8
+
 #define RED(x, bits)    (((x) >> (8 + 8 + 8 - (bits))) & ((1 << (bits)) - 1))
 #define GREEN(x, bits)  (((x) >> (8 + 8 - (bits))) & ((1 << (bits)) - 1))
@@ -70,5 +72,6 @@
 
 #define BB_POS(instance, col, row) \
-	((row) * (instance)->cols + (col))
+	((((instance)->start_row + (row)) % (instance)->rows) * \
+	    (instance)->cols + (col))
 
 #define GLYPH_POS(instance, glyph, y) \
@@ -92,4 +95,5 @@
 	unsigned int yres;
 	
+	/** Number of rows that fit on framebuffer */
 	unsigned int rowtrim;
 	
@@ -101,17 +105,29 @@
 	unsigned int bgscanbytes;
 	
+	/** Number of columns in the backbuffer */
 	unsigned int cols;
+	/** Number of rows in the backbuffer */
 	unsigned int rows;
 	
+	/** Starting row in the cyclic backbuffer */
+	unsigned int start_row;
+	
+	/** Top-most visible row (relative to start_row) */
+	unsigned int offset_row;
+	
+	/** Current backbuffer position */
 	unsigned int position;
 } fb_instance_t;
 
-static void fb_putchar(outdev_t *dev, wchar_t ch);
-static void fb_redraw_internal(fb_instance_t *instance);
-static void fb_redraw(outdev_t *dev);
+static void fb_putchar(outdev_t *, wchar_t);
+static void fb_redraw(outdev_t *);
+static void fb_scroll_up(outdev_t *);
+static void fb_scroll_down(outdev_t *);
 
 static outdev_operations_t fbdev_ops = {
 	.write = fb_putchar,
-	.redraw = fb_redraw
+	.redraw = fb_redraw,
+	.scroll_up = fb_scroll_up,
+	.scroll_down = fb_scroll_down
 };
 
@@ -216,33 +232,39 @@
     unsigned int col, unsigned int row, bool overlay)
 {
-	unsigned int x = COL2X(col);
-	unsigned int y = ROW2Y(row);
-	unsigned int yd;
-	
 	if (!overlay)
 		instance->backbuf[BB_POS(instance, col, row)] = glyph;
 	
+	/* Do not output if the framebuffer is used by user space */
+	if ((instance->parea.mapped) && (!console_override))
+		return;
+	
+	/* Check whether the glyph should be visible */
+	if (row < instance->offset_row)
+		return;
+	
+	unsigned int rel_row = row - instance->offset_row;
+	if (rel_row >= instance->rowtrim)
+		return;
+	
+	unsigned int x = COL2X(col);
+	unsigned int y = ROW2Y(rel_row);
+	
+	for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++)
+		memcpy(&instance->addr[FB_POS(instance, x, y + yd)],
+		    &instance->glyphs[GLYPH_POS(instance, glyph, yd)],
+		    instance->glyphscanline);
+}
+
+/** Scroll screen down by one row
+ *
+ */
+static void screen_scroll(fb_instance_t *instance)
+{
 	if ((!instance->parea.mapped) || (console_override)) {
-		for (yd = 0; yd < FONT_SCANLINES; yd++)
-			memcpy(&instance->addr[FB_POS(instance, x, y + yd)],
-			    &instance->glyphs[GLYPH_POS(instance, glyph, yd)],
-			    instance->glyphscanline);
-	}
-}
-
-/** Scroll screen down by one row
- *
- *
- */
-static void screen_scroll(fb_instance_t *instance)
-{
-	if ((!instance->parea.mapped) || (console_override)) {
-		unsigned int row;
-		
-		for (row = 0; row < instance->rows; row++) {
-			unsigned int y = ROW2Y(row);
-			unsigned int yd;
+		for (unsigned int rel_row = 0; rel_row < instance->rowtrim; rel_row++) {
+			unsigned int y = ROW2Y(rel_row);
+			unsigned int row = rel_row + instance->offset_row;
 			
-			for (yd = 0; yd < FONT_SCANLINES; yd++) {
+			for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++) {
 				unsigned int x;
 				unsigned int col;
@@ -269,6 +291,13 @@
 	}
 	
-	memmove(instance->backbuf, &instance->backbuf[BB_POS(instance, 0, 1)],
-	    instance->cols * (instance->rows - 1) * sizeof(uint16_t));
+	/*
+	 * Implement backbuffer scrolling by wrapping around
+	 * the cyclic buffer.
+	 */
+	
+	instance->start_row++;
+	if (instance->start_row == instance->rows)
+		instance->start_row = 0;
+	
 	memsetw(&instance->backbuf[BB_POS(instance, 0, instance->rows - 1)],
 	    instance->cols, 0);
@@ -334,65 +363,11 @@
 }
 
-/** Print character to screen
- *
- * Emulate basic terminal commands.
- *
- */
-static void fb_putchar(outdev_t *dev, wchar_t ch)
-{
-	fb_instance_t *instance = (fb_instance_t *) dev->data;
-	spinlock_lock(&instance->lock);
-	
-	switch (ch) {
-	case '\n':
-		cursor_remove(instance);
-		instance->position += instance->cols;
-		instance->position -= instance->position % instance->cols;
-		break;
-	case '\r':
-		cursor_remove(instance);
-		instance->position -= instance->position % instance->cols;
-		break;
-	case '\b':
-		cursor_remove(instance);
-		if (instance->position % instance->cols)
-			instance->position--;
-		break;
-	case '\t':
-		cursor_remove(instance);
-		do {
-			glyph_draw(instance, fb_font_glyph(' '),
-			    instance->position % instance->cols,
-			    instance->position / instance->cols, false);
-			instance->position++;
-		} while (((instance->position % instance->cols) % 8 != 0) &&
-		    (instance->position < instance->cols * instance->rows));
-		break;
-	default:
-		glyph_draw(instance, fb_font_glyph(ch),
-		    instance->position % instance->cols,
-		    instance->position / instance->cols, false);
-		instance->position++;
-	}
-	
-	if (instance->position >= instance->cols * instance->rows) {
-		instance->position -= instance->cols;
-		screen_scroll(instance);
-	}
-	
-	cursor_put(instance);
-	
-	spinlock_unlock(&instance->lock);
-}
-
 static void fb_redraw_internal(fb_instance_t *instance)
 {
-	unsigned int row;
-	
-	for (row = 0; row < instance->rowtrim; row++) {
-		unsigned int y = ROW2Y(row);
-		unsigned int yd;
+	for (unsigned int rel_row = 0; rel_row < instance->rowtrim; rel_row++) {
+		unsigned int y = ROW2Y(rel_row);
+		unsigned int row = rel_row + instance->offset_row;
 		
-		for (yd = 0; yd < FONT_SCANLINES; yd++) {
+		for (unsigned int yd = 0; yd < FONT_SCANLINES; yd++) {
 			unsigned int x;
 			unsigned int col;
@@ -428,4 +403,95 @@
 }
 
+/** Print character to screen
+ *
+ * Emulate basic terminal commands.
+ *
+ */
+static void fb_putchar(outdev_t *dev, wchar_t ch)
+{
+	fb_instance_t *instance = (fb_instance_t *) dev->data;
+	spinlock_lock(&instance->lock);
+	
+	switch (ch) {
+	case '\n':
+		cursor_remove(instance);
+		instance->position += instance->cols;
+		instance->position -= instance->position % instance->cols;
+		break;
+	case '\r':
+		cursor_remove(instance);
+		instance->position -= instance->position % instance->cols;
+		break;
+	case '\b':
+		cursor_remove(instance);
+		if (instance->position % instance->cols)
+			instance->position--;
+		break;
+	case '\t':
+		cursor_remove(instance);
+		do {
+			glyph_draw(instance, fb_font_glyph(' '),
+			    instance->position % instance->cols,
+			    instance->position / instance->cols, false);
+			instance->position++;
+		} while (((instance->position % instance->cols) % 8 != 0) &&
+		    (instance->position < instance->cols * instance->rows));
+		break;
+	default:
+		glyph_draw(instance, fb_font_glyph(ch),
+		    instance->position % instance->cols,
+		    instance->position / instance->cols, false);
+		instance->position++;
+	}
+	
+	if (instance->position >= instance->cols * instance->rows) {
+		instance->position -= instance->cols;
+		screen_scroll(instance);
+	}
+	
+	cursor_put(instance);
+	
+	spinlock_unlock(&instance->lock);
+}
+
+/** Scroll the framebuffer up
+ *
+ */
+static void fb_scroll_up(outdev_t *dev)
+{
+	fb_instance_t *instance = (fb_instance_t *) dev->data;
+	spinlock_lock(&instance->lock);
+	
+	if (instance->offset_row >= instance->rowtrim / 2)
+		instance->offset_row -= instance->rowtrim / 2;
+	else
+		instance->offset_row = 0;
+	
+	fb_redraw_internal(instance);
+	cursor_put(instance);
+	
+	spinlock_unlock(&instance->lock);
+}
+
+/** Scroll the framebuffer down
+ *
+ */
+static void fb_scroll_down(outdev_t *dev)
+{
+	fb_instance_t *instance = (fb_instance_t *) dev->data;
+	spinlock_lock(&instance->lock);
+	
+	if (instance->offset_row + instance->rowtrim / 2 <=
+	    instance->rows - instance->rowtrim)
+		instance->offset_row += instance->rowtrim / 2;
+	else
+		instance->offset_row = instance->rows - instance->rowtrim;
+	
+	fb_redraw_internal(instance);
+	cursor_put(instance);
+	
+	spinlock_unlock(&instance->lock);
+}
+
 /** Refresh the screen
  *
@@ -523,10 +589,13 @@
 	instance->yres = props->y;
 	instance->scanline = props->scan;
-	instance->position = 0;
+	
+	instance->rowtrim = Y2ROW(instance->yres);
 	
 	instance->cols = X2COL(instance->xres);
-	instance->rows = Y2ROW(instance->yres);
-	
-	instance->rowtrim = instance->rows;
+	instance->rows = FB_PAGES * instance->rowtrim;
+	
+	instance->start_row = instance->rows - instance->rowtrim;
+	instance->offset_row = instance->start_row;
+	instance->position = instance->start_row * instance->cols;
 	
 	instance->glyphscanline = FONT_WIDTH * instance->pixelbytes;
Index: kernel/genarch/src/kbrd/kbrd.c
===================================================================
--- kernel/genarch/src/kbrd/kbrd.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/kbrd/kbrd.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -56,4 +56,5 @@
 #include <arch.h>
 #include <macros.h>
+#include <str.h>
 
 #define IGNORE_CODE  0x7f
@@ -65,5 +66,6 @@
 
 static indev_operations_t kbrd_raw_ops = {
-	.poll = NULL
+	.poll = NULL,
+	.signal = NULL
 };
 
@@ -104,4 +106,5 @@
 	bool shift;
 	bool capslock;
+	wchar_t ch;
 	
 	spinlock_lock(&instance->keylock);
@@ -127,7 +130,19 @@
 		
 		if (shift)
-			indev_push_character(instance->sink, sc_secondary_map[sc]);
+			ch = sc_secondary_map[sc];
 		else
-			indev_push_character(instance->sink, sc_primary_map[sc]);
+			ch = sc_primary_map[sc];
+		
+		switch (ch) {
+		case U_PAGE_UP:
+			indev_signal(instance->sink, INDEV_SIGNAL_SCROLL_UP);
+			break;
+		case U_PAGE_DOWN:
+			indev_signal(instance->sink, INDEV_SIGNAL_SCROLL_DOWN);
+			break;
+		default:
+			indev_push_character(instance->sink, ch);
+		}
+		
 		break;
 	}
Index: kernel/genarch/src/srln/srln.c
===================================================================
--- kernel/genarch/src/srln/srln.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/genarch/src/srln/srln.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -43,5 +43,6 @@
 
 static indev_operations_t srln_raw_ops = {
-	.poll = NULL
+	.poll = NULL,
+	.signal = NULL
 };
 
Index: kernel/generic/include/console/chardev.h
===================================================================
--- kernel/generic/include/console/chardev.h	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/generic/include/console/chardev.h	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -43,10 +43,19 @@
 #define INDEV_BUFLEN  512
 
+/** Input character device out-of-band signal type. */
+typedef enum {
+	INDEV_SIGNAL_SCROLL_UP = 0,
+	INDEV_SIGNAL_SCROLL_DOWN
+} indev_signal_t;
+
 struct indev;
 
-/* Input character device operations interface. */
+/** Input character device operations interface. */
 typedef struct {
 	/** Read character directly from device, assume interrupts disabled. */
 	wchar_t (* poll)(struct indev *);
+	
+	/** Signal out-of-band condition. */
+	void (* signal)(struct indev *, indev_signal_t);
 } indev_operations_t;
 
@@ -67,8 +76,7 @@
 } indev_t;
 
-
 struct outdev;
 
-/* Output character device operations interface. */
+/** Output character device operations interface. */
 typedef struct {
 	/** Write character to output. */
@@ -77,4 +85,10 @@
 	/** Redraw any previously cached characters. */
 	void (* redraw)(struct outdev *);
+	
+	/** Scroll up in the device cache. */
+	void (* scroll_up)(struct outdev *);
+	
+	/** Scroll down in the device cache. */
+	void (* scroll_down)(struct outdev *);
 } outdev_operations_t;
 
@@ -99,4 +113,5 @@
 extern void indev_push_character(indev_t *, wchar_t);
 extern wchar_t indev_pop_character(indev_t *);
+extern void indev_signal(indev_t *, indev_signal_t);
 
 extern void outdev_initialize(const char *, outdev_t *,
Index: kernel/generic/src/console/chardev.c
===================================================================
--- kernel/generic/src/console/chardev.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/generic/src/console/chardev.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -94,5 +94,6 @@
 {
 	if (atomic_get(&haltstate)) {
-		/* If we are here, we are hopefully on the processor that
+		/*
+		 * If we are here, we are hopefully on the processor that
 		 * issued the 'halt' command, so proceed to read the character
 		 * directly from input
@@ -115,9 +116,23 @@
 	waitq_sleep(&indev->wq);
 	irq_spinlock_lock(&indev->lock, true);
-	wchar_t ch = indev->buffer[(indev->index - indev->counter) % INDEV_BUFLEN];
+	wchar_t ch = indev->buffer[(indev->index - indev->counter) %
+	    INDEV_BUFLEN];
 	indev->counter--;
 	irq_spinlock_unlock(&indev->lock, true);
 	
 	return ch;
+}
+
+/** Signal out-of-band condition
+ *
+ * @param indev  Input character device.
+ * @param signal Out-of-band condition to signal.
+ *
+ */
+void indev_signal(indev_t *indev, indev_signal_t signal)
+{
+	if ((indev != NULL) && (indev->op != NULL) &&
+	    (indev->op->signal != NULL))
+		indev->op->signal(indev, signal);
 }
 
Index: kernel/generic/src/console/console.c
===================================================================
--- kernel/generic/src/console/console.c	(revision e1fc679a4476fa9e58fe39d81cd6aac27992912a)
+++ kernel/generic/src/console/console.c	(revision 7ddc2c7a97bf75277145753f192b8a5f0365792d)
@@ -84,14 +84,21 @@
 static outdev_t stdout_source;
 
+static void stdin_signal(indev_t *, indev_signal_t);
+
 static indev_operations_t stdin_ops = {
-	.poll = NULL
+	.poll = NULL,
+	.signal = stdin_signal
 };
 
 static void stdout_write(outdev_t *, wchar_t);
 static void stdout_redraw(outdev_t *);
+static void stdout_scroll_up(outdev_t *);
+static void stdout_scroll_down(outdev_t *);
 
 static outdev_operations_t stdout_ops = {
 	.write = stdout_write,
-	.redraw = stdout_redraw
+	.redraw = stdout_redraw,
+	.scroll_up = stdout_scroll_up,
+	.scroll_down = stdout_scroll_down
 };
 
@@ -113,4 +120,18 @@
 }
 
+static void stdin_signal(indev_t *indev, indev_signal_t signal)
+{
+	switch (signal) {
+	case INDEV_SIGNAL_SCROLL_UP:
+		if (stdout != NULL)
+			stdout_scroll_up(stdout);
+		break;
+	case INDEV_SIGNAL_SCROLL_DOWN:
+		if (stdout != NULL)
+			stdout_scroll_down(stdout);
+		break;
+	}
+}
+
 void stdout_wire(outdev_t *outdev)
 {
@@ -136,4 +157,20 @@
 		if ((sink) && (sink->op->redraw))
 			sink->op->redraw(sink);
+	}
+}
+
+static void stdout_scroll_up(outdev_t *dev)
+{
+	list_foreach(dev->list, link, outdev_t, sink) {
+		if ((sink) && (sink->op->scroll_up))
+			sink->op->scroll_up(sink);
+	}
+}
+
+static void stdout_scroll_down(outdev_t *dev)
+{
+	list_foreach(dev->list, link, outdev_t, sink) {
+		if ((sink) && (sink->op->scroll_down))
+			sink->op->scroll_down(sink);
 	}
 }
@@ -229,4 +266,5 @@
 			}
 		}
+		
 		if (chr_encode(ch, buf, &offset, buflen - 1) == EOK) {
 			putchar(ch);
@@ -264,5 +302,5 @@
 
 /** Flush characters that are stored in the output buffer
- * 
+ *
  */
 void kio_flush(void)
@@ -294,5 +332,5 @@
 
 /** Put a character into the output buffer.
- * 
+ *
  * The caller is required to hold kio_lock
  */
