Index: kernel/genarch/src/drivers/ega/ega.c
===================================================================
--- kernel/genarch/src/drivers/ega/ega.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/genarch/src/drivers/ega/ega.c	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -69,11 +69,12 @@
 	uint8_t *backbuf;
 	ioport8_t *base;
+	mbstate_t mbstate;
 } ega_instance_t;
 
-static void ega_putuchar(outdev_t *, char32_t);
+static void ega_write(outdev_t *, const char *, size_t);
 static void ega_redraw(outdev_t *);
 
 static outdev_operations_t egadev_ops = {
-	.write = ega_putuchar,
+	.write = ega_write,
 	.redraw = ega_redraw,
 	.scroll_up = NULL,
@@ -538,10 +539,6 @@
 }
 
-static void ega_putuchar(outdev_t *dev, char32_t ch)
-{
-	ega_instance_t *instance = (ega_instance_t *) dev->data;
-
-	irq_spinlock_lock(&instance->lock, true);
-
+static void _putuchar(ega_instance_t *instance, char32_t ch)
+{
 	switch (ch) {
 	case '\n':
@@ -564,4 +561,17 @@
 	ega_check_cursor(instance);
 	ega_move_cursor(instance);
+}
+
+static void ega_write(outdev_t *dev, const char *s, size_t n)
+{
+	ega_instance_t *instance = (ega_instance_t *) dev->data;
+
+	irq_spinlock_lock(&instance->lock, true);
+
+	size_t offset = 0;
+	char32_t ch;
+
+	while ((ch = str_decode_r(s, &offset, n, U_SPECIAL, &instance->mbstate)))
+		_putuchar(instance, ch);
 
 	irq_spinlock_unlock(&instance->lock, true);
Index: kernel/genarch/src/drivers/ns16550/ns16550.c
===================================================================
--- kernel/genarch/src/drivers/ns16550/ns16550.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/genarch/src/drivers/ns16550/ns16550.c	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -112,21 +112,24 @@
 }
 
-static void ns16550_putuchar(outdev_t *dev, char32_t ch)
+static void ns16550_write(outdev_t *dev, const char *s, size_t n)
 {
 	ns16550_instance_t *instance = (ns16550_instance_t *) dev->data;
 
-	if ((!instance->parea.mapped) || (console_override)) {
-		if (ch == '\n')
+	if (instance->parea.mapped && !console_override)
+		return;
+
+	const char *top = s + n;
+	assert(top >= s);
+
+	for (; s < top; s++) {
+		if (*s == '\n')
 			ns16550_sendb(instance, '\r');
 
-		if (ascii_check(ch))
-			ns16550_sendb(instance, (uint8_t) ch);
-		else
-			ns16550_sendb(instance, U_SPECIAL);
+		ns16550_sendb(instance, (uint8_t) *s);
 	}
 }
 
 static outdev_operations_t ns16550_ops = {
-	.write = ns16550_putuchar,
+	.write = ns16550_write,
 	.redraw = NULL
 };
Index: kernel/genarch/src/drivers/pl011/pl011.c
===================================================================
--- kernel/genarch/src/drivers/pl011/pl011.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/genarch/src/drivers/pl011/pl011.c	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -56,5 +56,5 @@
 }
 
-static void pl011_uart_putuchar(outdev_t *dev, char32_t ch)
+static void pl011_uart_write(outdev_t *dev, const char *s, size_t n)
 {
 	pl011_uart_t *uart = dev->data;
@@ -64,15 +64,17 @@
 		return;
 
-	if (!ascii_check(ch))
-		pl011_uart_sendb(uart, U_SPECIAL);
-	else {
-		if (ch == '\n')
-			pl011_uart_sendb(uart, (uint8_t) '\r');
-		pl011_uart_sendb(uart, (uint8_t) ch);
+	const char *top = s + n;
+	assert(top >= s);
+
+	for (; s < top; s++) {
+		if (*s == '\n')
+			pl011_uart_sendb(uart, '\r');
+
+		pl011_uart_sendb(uart, (uint8_t) *s);
 	}
 }
 
 static outdev_operations_t pl011_uart_ops = {
-	.write = pl011_uart_putuchar,
+	.write = pl011_uart_write,
 	.redraw = NULL,
 	.scroll_up = NULL,
Index: kernel/genarch/src/fb/fb.c
===================================================================
--- kernel/genarch/src/fb/fb.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/genarch/src/fb/fb.c	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -121,7 +121,10 @@
 	/** Current backbuffer position */
 	unsigned int position;
+
+	/** Partial character between writes */
+	mbstate_t mbstate;
 } fb_instance_t;
 
-static void fb_putuchar(outdev_t *, char32_t);
+static void fb_write(outdev_t *, const char *, size_t);
 static void fb_redraw(outdev_t *);
 static void fb_scroll_up(outdev_t *);
@@ -129,5 +132,5 @@
 
 static outdev_operations_t fbdev_ops = {
-	.write = fb_putuchar,
+	.write = fb_write,
 	.redraw = fb_redraw,
 	.scroll_up = fb_scroll_up,
@@ -418,26 +421,19 @@
  *
  */
-static void fb_putuchar(outdev_t *dev, char32_t ch)
-{
-	fb_instance_t *instance = (fb_instance_t *) dev->data;
-	spinlock_lock(&instance->lock);
-
+static void _putuchar(fb_instance_t *instance, char32_t ch)
+{
 	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(' '),
@@ -459,7 +455,20 @@
 		screen_scroll(instance);
 	}
+}
+
+static void fb_write(outdev_t *dev, const char *s, size_t n)
+{
+	fb_instance_t *instance = (fb_instance_t *) dev->data;
+
+	spinlock_lock(&instance->lock);
+	cursor_remove(instance);
+
+	size_t offset = 0;
+	char32_t ch;
+
+	while ((ch = str_decode_r(s, &offset, n, U_SPECIAL, &instance->mbstate)))
+		_putuchar(instance, ch);
 
 	cursor_put(instance);
-
 	spinlock_unlock(&instance->lock);
 }
Index: kernel/generic/include/console/chardev.h
===================================================================
--- kernel/generic/include/console/chardev.h	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/generic/include/console/chardev.h	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -81,6 +81,6 @@
 /** Output character device operations interface. */
 typedef struct {
-	/** Write character to output. */
-	void (*write)(struct outdev *, char32_t);
+	/** Write string to output. */
+	void (*write)(struct outdev *, const char *, size_t);
 
 	/** Redraw any previously cached characters. */
Index: kernel/generic/src/console/console.c
===================================================================
--- kernel/generic/src/console/console.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
+++ kernel/generic/src/console/console.c	(revision 39e1b9a104f1ac094dcf8801fd04c9afbbaac36f)
@@ -80,4 +80,6 @@
 IRQ_SPINLOCK_INITIALIZE(kio_lock);
 
+static IRQ_SPINLOCK_INITIALIZE(flush_lock);
+
 static IRQ_SPINLOCK_INITIALIZE(early_mbstate_lock);
 static mbstate_t early_mbstate;
@@ -93,5 +95,5 @@
 };
 
-static void stdout_write(outdev_t *, char32_t);
+static void stdout_write(outdev_t *, const char *, size_t);
 static void stdout_redraw(outdev_t *);
 static void stdout_scroll_up(outdev_t *);
@@ -146,9 +148,9 @@
 }
 
-static void stdout_write(outdev_t *dev, char32_t ch)
+static void stdout_write(outdev_t *dev, const char *s, size_t n)
 {
 	list_foreach(dev->list, link, outdev_t, sink) {
 		if ((sink) && (sink->op->write))
-			sink->op->write(sink, ch);
+			sink->op->write(sink, s, n);
 	}
 }
@@ -249,5 +251,12 @@
 	irq_spinlock_lock(&kio_lock, true);
 
-	static mbstate_t mbstate;
+	if (!irq_spinlock_trylock(&flush_lock)) {
+		/* Someone is currently flushing. */
+		irq_spinlock_unlock(&kio_lock, true);
+		return;
+	}
+
+	/* A small-ish local buffer so that we can write to output in chunks. */
+	char buffer[256];
 
 	/* Print characters that weren't printed earlier */
@@ -255,20 +264,20 @@
 		size_t offset = kio_processed % KIO_LENGTH;
 		size_t len = min(kio_written - kio_processed, KIO_LENGTH - offset);
-		size_t bytes = 0;
-
-		char32_t ch = str_decode_r(&kio[offset], &bytes, len, U'�', &mbstate);
-		assert(bytes <= 4);
-		kio_processed += bytes;
+		len = min(len, sizeof(buffer));
+
+		/* Take out a chunk of the big buffer. */
+		memcpy(buffer, &kio[offset], len);
+		kio_processed += len;
 
 		/*
-		 * We need to give up the spinlock for
-		 * the physical operation of writing out
-		 * the character.
+		 * We need to give up the spinlock for the physical operation of writing
+		 * out the buffer.
 		 */
 		irq_spinlock_unlock(&kio_lock, true);
-		stdout->op->write(stdout, ch);
+		stdout->op->write(stdout, buffer, len);
 		irq_spinlock_lock(&kio_lock, true);
 	}
 
+	irq_spinlock_unlock(&flush_lock, false);
 	irq_spinlock_unlock(&kio_lock, true);
 }
