Index: kernel/generic/include/console/console.h
===================================================================
--- kernel/generic/include/console/console.h	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/include/console/console.h	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -64,5 +64,5 @@
 extern void kio_update(void *);
 extern void kio_flush(void);
-extern void kio_push_char(const char32_t);
+extern void kio_push_bytes(const char *, size_t);
 extern irq_spinlock_t kio_lock;
 
@@ -78,4 +78,6 @@
 extern void console_unlock(void);
 
+extern void putstr(const char *s, size_t n);
+
 #endif /* KERN_CONSOLE_H_ */
 
Index: rnel/generic/include/putchar.h
===================================================================
--- kernel/generic/include/putchar.h	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ 	(revision )
@@ -1,45 +1,0 @@
-/*
- * Copyright (c) 2001-2004 Jakub Jermar
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup kernel_generic
- * @{
- */
-/** @file
- */
-
-#ifndef KERN_PUTCHAR_H_
-#define KERN_PUTCHAR_H_
-
-#include <uchar.h>
-
-extern void putuchar(char32_t);
-
-#endif
-
-/** @}
- */
Index: kernel/generic/include/stdio.h
===================================================================
--- kernel/generic/include/stdio.h	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/include/stdio.h	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -37,5 +37,4 @@
 
 #include <print.h>
-#include <putchar.h>
 
 #endif
Index: kernel/generic/src/console/console.c
===================================================================
--- kernel/generic/src/console/console.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/src/console/console.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -40,11 +40,12 @@
 #include <errno.h>
 #include <ipc/event.h>
+#include <log.h>
 #include <panic.h>
 #include <preemption.h>
 #include <proc/task.h>
-#include <putchar.h>
 #include <stdatomic.h>
 #include <stdio.h>
 #include <stdlib.h>  /* malloc */
+#include <str.h>
 #include <synch/mutex.h>
 #include <synch/spinlock.h>
@@ -53,8 +54,8 @@
 
 #define KIO_PAGES    8
-#define KIO_LENGTH   (KIO_PAGES * PAGE_SIZE / sizeof(char32_t))
+#define KIO_LENGTH   (KIO_PAGES * PAGE_SIZE)
 
 /** Kernel log cyclic buffer */
-static char32_t kio[KIO_LENGTH];
+static char kio[KIO_LENGTH];
 
 /** Kernel log initialized */
@@ -78,4 +79,7 @@
 /** Kernel log spinlock */
 IRQ_SPINLOCK_INITIALIZE(kio_lock);
+
+static IRQ_SPINLOCK_INITIALIZE(early_mbstate_lock);
+static mbstate_t early_mbstate;
 
 static indev_t stdin_sink;
@@ -245,8 +249,15 @@
 	irq_spinlock_lock(&kio_lock, true);
 
+	static mbstate_t mbstate;
+
 	/* Print characters that weren't printed earlier */
 	while (kio_written != kio_processed) {
-		char32_t tmp = kio[kio_processed % KIO_LENGTH];
-		kio_processed++;
+		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;
 
 		/*
@@ -256,5 +267,5 @@
 		 */
 		irq_spinlock_unlock(&kio_lock, true);
-		stdout->op->write(stdout, tmp);
+		stdout->op->write(stdout, ch);
 		irq_spinlock_lock(&kio_lock, true);
 	}
@@ -263,20 +274,46 @@
 }
 
-/** Put a character into the output buffer.
- *
- * The caller is required to hold kio_lock
- */
-void kio_push_char(const char32_t ch)
-{
-	kio[kio_written % KIO_LENGTH] = ch;
-	kio_written++;
-}
-
-void putuchar(const char32_t ch)
+void kio_push_bytes(const char *s, size_t n)
+{
+	/* Skip the section we know we can't keep. */
+	if (n > KIO_LENGTH) {
+		size_t lost = n - KIO_LENGTH;
+		kio_written += lost;
+		s += lost;
+		n -= lost;
+	}
+
+	size_t offset = kio_written % KIO_LENGTH;
+	if (offset + n > KIO_LENGTH) {
+		size_t first = KIO_LENGTH - offset;
+		size_t last = n - first;
+		memcpy(kio + offset, s, first);
+		memcpy(kio, s + first, last);
+	} else {
+		memcpy(kio + offset, s, n);
+	}
+
+	kio_written += n;
+}
+
+static void early_putstr(const char *s, size_t n)
+{
+	irq_spinlock_lock(&early_mbstate_lock, true);
+
+	size_t offset = 0;
+	char32_t c;
+
+	while ((c = str_decode_r(s, &offset, n, U_SPECIAL, &early_mbstate)))
+		early_putuchar(c);
+
+	irq_spinlock_unlock(&early_mbstate_lock, true);
+}
+
+void putstr(const char *s, size_t n)
 {
 	bool ordy = ((stdout) && (stdout->op->write));
 
 	irq_spinlock_lock(&kio_lock, true);
-	kio_push_char(ch);
+	kio_push_bytes(s, n);
 	irq_spinlock_unlock(&kio_lock, true);
 
@@ -295,9 +332,9 @@
 		 * a no-op on certain hardware configurations.
 		 */
-		early_putuchar(ch);
-	}
-
-	/* Force notification on newline */
-	if (ch == '\n')
+		early_putstr(s, n);
+	}
+
+	/* Force notification when containing a newline */
+	if (memchr(s, '\n', n) != NULL)
 		kio_update(NULL);
 }
@@ -336,12 +373,10 @@
 		size_t first = KIO_LENGTH - offset;
 		size_t last = actual_read - first;
-		size_t first_bytes = first * sizeof(kio[0]);
-		size_t last_bytes = last * sizeof(kio[0]);
-
-		rc = copy_to_uspace(buf, &kio[offset], first_bytes);
+
+		rc = copy_to_uspace(buf, &kio[offset], first);
 		if (rc == EOK)
-			rc = copy_to_uspace(buf + first_bytes, &kio[0], last_bytes);
+			rc = copy_to_uspace(buf + first, &kio[0], last);
 	} else {
-		rc = copy_to_uspace(buf, &kio[offset], actual_read * sizeof(kio[0]));
+		rc = copy_to_uspace(buf, &kio[offset], actual_read);
 	}
 
Index: kernel/generic/src/console/kconsole.c
===================================================================
--- kernel/generic/src/console/kconsole.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/src/console/kconsole.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -39,24 +39,23 @@
  */
 
+#include <adt/list.h>
+#include <arch.h>
 #include <assert.h>
-#include <console/kconsole.h>
-#include <console/console.h>
 #include <console/chardev.h>
 #include <console/cmd.h>
+#include <console/console.h>
+#include <console/kconsole.h>
 #include <console/prompt.h>
+#include <debug.h>
+#include <errno.h>
+#include <halt.h>
+#include <macros.h>
+#include <panic.h>
 #include <stdio.h>
-#include <panic.h>
+#include <stdlib.h>
+#include <str.h>
+#include <symtab.h>
+#include <sysinfo/sysinfo.h>
 #include <typedefs.h>
-#include <adt/list.h>
-#include <arch.h>
-#include <macros.h>
-#include <debug.h>
-#include <halt.h>
-#include <str.h>
-#include <sysinfo/sysinfo.h>
-#include <symtab.h>
-#include <errno.h>
-#include <putchar.h>
-#include <stdlib.h>
 
 /** Simple kernel console.
@@ -158,9 +157,10 @@
 
 /** Print count times a character */
-_NO_TRACE static void print_cc(char32_t ch, size_t count)
-{
-	size_t i;
-	for (i = 0; i < count; i++)
-		putuchar(ch);
+_NO_TRACE static void print_cc(char ch, size_t count)
+{
+	// FIXME: only lock once
+
+	for (size_t i = 0; i < count; i++)
+		putstr(&ch, 1);
 }
 
@@ -347,5 +347,5 @@
 		if (ch == '\n') {
 			/* Enter */
-			putuchar(ch);
+			putstr("\n", 1);
 			break;
 		}
@@ -358,5 +358,5 @@
 			if (wstr_remove(current, position - 1)) {
 				position--;
-				putuchar('\b');
+				putstr("\b", 1);
 				printf("%ls ", current + position);
 				print_cc('\b', wstr_length(current) - position + 1);
@@ -368,8 +368,13 @@
 			/* Tab completion */
 
-			/* Move to the end of the word */
-			for (; (current[position] != 0) && (!isspace(current[position]));
-			    position++)
-				putuchar(current[position]);
+			size_t i = position;
+			while (current[i] && !isspace(current[i]))
+				i++;
+
+			char32_t stash = current[i];
+			current[i] = 0;
+			printf("%ls", &current[position]);
+			current[i] = stash;
+			position = i;
 
 			/*
@@ -430,11 +435,7 @@
 			 */
 			size_t off = 0;
-			size_t i = 0;
-			while ((ch = str_decode(tmp, &off, STR_NO_LIMIT)) != 0) {
+			for (size_t i = 0; (ch = str_decode(tmp, &off, STR_NO_LIMIT)); i++)
 				if (!wstr_linsert(current, ch, position + i, MAX_CMDLINE))
 					break;
-
-				i++;
-			}
 
 			if (found > 1) {
@@ -466,5 +467,5 @@
 			/* Left */
 			if (position > 0) {
-				putuchar('\b');
+				putstr("\b", 1);
 				position--;
 			}
@@ -475,5 +476,5 @@
 			/* Right */
 			if (position < wstr_length(current)) {
-				putuchar(current[position]);
+				printf("%lc", current[position]);
 				position++;
 			}
Index: kernel/generic/src/log/log.c
===================================================================
--- kernel/generic/src/log/log.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/src/log/log.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -46,5 +46,4 @@
 #include <print.h>
 #include <printf_core.h>
-#include <putchar.h>
 #include <stdarg.h>
 #include <stdlib.h>
@@ -179,5 +178,5 @@
 	log_used += log_current_len;
 
-	kio_push_char('\n');
+	kio_push_bytes("\n", 1);
 	irq_spinlock_unlock(&kio_lock, true);
 	irq_spinlock_unlock(&log_lock, true);
@@ -203,11 +202,6 @@
 static int log_printf_str_write(const char *str, size_t size, void *data)
 {
-	size_t offset = 0;
-
-	while (offset < size)
-		kio_push_char(str_decode(str, &offset, size));
-
+	kio_push_bytes(str, size);
 	log_append((const uint8_t *)str, size);
-
 	return EOK;
 }
Index: kernel/generic/src/printf/vprintf.c
===================================================================
--- kernel/generic/src/printf/vprintf.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ kernel/generic/src/printf/vprintf.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -37,5 +37,4 @@
 #include <print.h>
 #include <printf_core.h>
-#include <putchar.h>
 #include <str.h>
 #include <synch/spinlock.h>
@@ -44,9 +43,5 @@
 static errno_t vprintf_str_write(const char *str, size_t size, void *data)
 {
-	size_t offset = 0;
-
-	while (offset < size)
-		putuchar(str_decode(str, &offset, size));
-
+	putstr(str, size);
 	return EOK;
 }
@@ -54,19 +49,7 @@
 int puts(const char *str)
 {
-	size_t offset = 0;
-	size_t chars = 0;
-	char32_t uc;
-
-	console_lock();
-
-	while ((uc = str_decode(str, &offset, STR_NO_LIMIT)) != 0) {
-		putuchar(uc);
-		chars++;
-	}
-
-	putuchar('\n');
-
-	console_unlock();
-	return chars;
+	size_t n = str_size(str);
+	putstr(str, n);
+	return n;
 }
 
Index: uspace/app/kio/kio.c
===================================================================
--- uspace/app/kio/kio.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ uspace/app/kio/kio.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -59,6 +59,6 @@
 typedef struct {
 	link_t link;
-	size_t length;
-	char32_t *data;
+	size_t bytes;
+	char *data;
 } item_t;
 
@@ -68,8 +68,8 @@
 static FIBRIL_MUTEX_INITIALIZE(mtx);
 
-#define READ_BUFFER_SIZE (PAGE_SIZE / sizeof(char32_t))
+#define READ_BUFFER_SIZE PAGE_SIZE
 
 static size_t current_at;
-static char32_t read_buffer[READ_BUFFER_SIZE];
+static char read_buffer[READ_BUFFER_SIZE];
 
 /** Klog producer
@@ -82,22 +82,20 @@
  *
  */
-static void producer(size_t length, char32_t *data)
-{
-	item_t *item = (item_t *) malloc(sizeof(item_t));
+static void producer(size_t bytes, char *data)
+{
+	item_t *item = malloc(sizeof(item_t));
 	if (item == NULL)
 		return;
 
-	size_t sz = sizeof(char32_t) * length;
-	char32_t *buf = (char32_t *) malloc(sz);
-	if (buf == NULL) {
+	item->bytes = bytes;
+	item->data = malloc(bytes);
+	if (!item->data) {
 		free(item);
 		return;
 	}
 
-	memcpy(buf, data, sz);
+	memcpy(item->data, data, bytes);
 
 	link_initialize(&item->link);
-	item->length = length;
-	item->data = buf;
 	prodcons_produce(&pc, &item->link);
 }
@@ -125,11 +123,8 @@
 		item_t *item = list_get_instance(link, item_t, link);
 
-		for (size_t i = 0; i < item->length; i++)
-			putuchar(item->data[i]);
-
-		if (log != NULL) {
-			for (size_t i = 0; i < item->length; i++)
-				fputuc(item->data[i], log);
-
+		fwrite(item->data, 1, item->bytes, stdout);
+
+		if (log) {
+			fwrite(item->data, 1, item->bytes, log);
 			fflush(log);
 			vfs_sync(fileno(log));
Index: uspace/lib/c/generic/io/kio.c
===================================================================
--- uspace/lib/c/generic/io/kio.c	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ uspace/lib/c/generic/io/kio.c	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -113,5 +113,5 @@
 }
 
-size_t kio_read(char32_t *buf, size_t n, size_t at)
+size_t kio_read(char *buf, size_t n, size_t at)
 {
 	return __SYSCALL3(SYS_KIO_READ, (sysarg_t) buf, n, at);
Index: uspace/lib/c/include/io/kio.h
===================================================================
--- uspace/lib/c/include/io/kio.h	(revision d5b37b625566a35b56a2768799f8082882139e67)
+++ uspace/lib/c/include/io/kio.h	(revision 690ad204fd2c77aaab4749d22a821fb25b360ae4)
@@ -52,5 +52,5 @@
 extern int kio_vprintf(const char *, va_list);
 
-extern size_t kio_read(char32_t *buf, size_t n, size_t at);
+extern size_t kio_read(char *buf, size_t n, size_t at);
 
 /*
