Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ boot/Makefile.common	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -123,4 +123,5 @@
 	$(USPACE_PATH)/srv/net/udp/udp \
 	$(USPACE_PATH)/srv/taskmon/taskmon \
+	$(USPACE_PATH)/srv/test/chardev-test/chardev-test \
 	$(USPACE_PATH)/srv/volsrv/volsrv
 
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ uspace/Makefile	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -136,4 +136,5 @@
 	srv/hw/char/s3c24xx_uart \
 	srv/hid/rfb \
+	srv/test/chardev-test \
 	drv/audio/hdaudio \
 	drv/audio/sb16 \
Index: uspace/app/tester/Makefile
===================================================================
--- uspace/app/tester/Makefile	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ uspace/app/tester/Makefile	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -69,5 +69,6 @@
 	mm/pager1.c \
 	hw/misc/virtchar1.c \
-	hw/serial/serial1.c
+	hw/serial/serial1.c \
+	chardev/chardev1.c
 
 include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/tester/chardev/chardev1.c
===================================================================
--- uspace/app/tester/chardev/chardev1.c	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
+++ uspace/app/tester/chardev/chardev1.c	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * 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.
+ */
+
+#include <errno.h>
+#include <ipc/services.h>
+#include <io/chardev.h>
+#include <loc.h>
+#include "../tester.h"
+
+#define SMALL_BUFFER_SIZE 64
+#define LARGE_BUFFER_SIZE (DATA_XFER_LIMIT * 4)
+
+static char small_buffer[SMALL_BUFFER_SIZE];
+static char large_buffer[LARGE_BUFFER_SIZE];
+
+/** Test device that always performs small transfers. */
+static const char *test_chardev1_smallx(void)
+{
+	chardev_t *chardev;
+	service_id_t sid;
+	async_sess_t *sess;
+	size_t nbytes;
+	int rc;
+
+	TPRINTF("Test small transfer character device operations\n");
+
+	rc = loc_service_get_id(SERVICE_NAME_CHARDEV_TEST_SMALLX, &sid, 0);
+	if (rc != EOK) {
+		return "Failed resolving test device "
+		    SERVICE_NAME_CHARDEV_TEST_SMALLX;
+	}
+
+	sess = loc_service_connect(sid, INTERFACE_DDF, 0);
+	if (sess == NULL)
+		return "Failed connecting test device";
+
+	rc = chardev_open(sess, &chardev);
+	if (rc != EOK) {
+		async_hangup(sess);
+		return "Failed opening test device";
+	}
+
+	rc = chardev_write(chardev, small_buffer, SMALL_BUFFER_SIZE, &nbytes);
+	if (rc != EOK) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed sending data";
+	}
+
+	TPRINTF("Sent %zu bytes\n", nbytes);
+
+	rc = chardev_read(chardev, small_buffer, SMALL_BUFFER_SIZE, &nbytes);
+	if (rc != EOK) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed receiving data";
+	}
+
+	TPRINTF("Received %zu bytes\n", nbytes);
+
+	chardev_close(chardev);
+	async_hangup(sess);
+
+	TPRINTF("Done\n");
+	return NULL;
+}
+
+/** Test device that always performs large transfers. */
+static const char *test_chardev1_largex(void)
+{
+	chardev_t *chardev;
+	service_id_t sid;
+	async_sess_t *sess;
+	size_t nbytes;
+	int rc;
+
+	TPRINTF("Test large transfer character device operations\n");
+
+	rc = loc_service_get_id(SERVICE_NAME_CHARDEV_TEST_LARGEX, &sid, 0);
+	if (rc != EOK) {
+		return "Failed resolving test device "
+		    SERVICE_NAME_CHARDEV_TEST_LARGEX;
+	}
+
+	sess = loc_service_connect(sid, INTERFACE_DDF, 0);
+	if (sess == NULL)
+		return "Failed connecting test device";
+
+	rc = chardev_open(sess, &chardev);
+	if (rc != EOK) {
+		async_hangup(sess);
+		return "Failed opening test device";
+	}
+
+	rc = chardev_write(chardev, large_buffer, LARGE_BUFFER_SIZE, &nbytes);
+	if (rc != EOK) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed sending data";
+	}
+
+	TPRINTF("Sent %zu bytes\n", nbytes);
+
+	rc = chardev_read(chardev, large_buffer, LARGE_BUFFER_SIZE, &nbytes);
+	if (rc != EOK) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed receiving data";
+	}
+
+	TPRINTF("Received %zu bytes\n", nbytes);
+
+	chardev_close(chardev);
+	async_hangup(sess);
+
+	TPRINTF("Done\n");
+	return NULL;
+}
+
+/** Test device where all transfers return partial success. */
+static const char *test_chardev1_partialx(void)
+{
+	chardev_t *chardev;
+	service_id_t sid;
+	async_sess_t *sess;
+	size_t nbytes;
+	int rc;
+
+	TPRINTF("Test partially-successful character device operations\n");
+
+	rc = loc_service_get_id(SERVICE_NAME_CHARDEV_TEST_PARTIALX, &sid, 0);
+	if (rc != EOK) {
+		return "Failed resolving test device "
+		    SERVICE_NAME_CHARDEV_TEST_SMALLX;
+	}
+
+	sess = loc_service_connect(sid, INTERFACE_DDF, 0);
+	if (sess == NULL)
+		return "Failed connecting test device";
+
+	rc = chardev_open(sess, &chardev);
+	if (rc != EOK) {
+		async_hangup(sess);
+		return "Failed opening test device";
+	}
+
+	rc = chardev_write(chardev, small_buffer, SMALL_BUFFER_SIZE, &nbytes);
+	if (rc != EIO || nbytes != 1) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed sending data";
+	}
+
+	TPRINTF("Sent %zu bytes and got rc = %d (expected)\n", nbytes, rc);
+
+	rc = chardev_read(chardev, small_buffer, SMALL_BUFFER_SIZE, &nbytes);
+	if (rc != EIO || nbytes != 1) {
+		chardev_close(chardev);
+		async_hangup(sess);
+		return "Failed receiving data";
+	}
+
+	TPRINTF("Received %zu bytes and got rc = %d (expected)\n", nbytes, rc);
+
+	chardev_close(chardev);
+	async_hangup(sess);
+
+	TPRINTF("Done\n");
+	return NULL;
+}
+
+const char *test_chardev1(void)
+{
+	const char *s;
+
+	s = test_chardev1_smallx();
+	if (s != NULL)
+		return s;
+
+	s = test_chardev1_largex();
+	if (s != NULL)
+		return s;
+
+	return test_chardev1_partialx();;
+}
Index: uspace/app/tester/chardev/chardev1.def
===================================================================
--- uspace/app/tester/chardev/chardev1.def	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
+++ uspace/app/tester/chardev/chardev1.def	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -0,0 +1,6 @@
+{
+	"chardev1",
+	"Character device basic functionality",
+	&test_chardev1,
+	false
+},
Index: uspace/app/tester/tester.c
===================================================================
--- uspace/app/tester/tester.c	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ uspace/app/tester/tester.c	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -77,4 +77,5 @@
 #include "hw/serial/serial1.def"
 #include "hw/misc/virtchar1.def"
+#include "chardev/chardev1.def"
 	{NULL, NULL, NULL, false}
 };
Index: uspace/app/tester/tester.h
===================================================================
--- uspace/app/tester/tester.h	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ uspace/app/tester/tester.h	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -111,4 +111,5 @@
 extern const char *test_devman1(void);
 extern const char *test_devman2(void);
+extern const char *test_chardev1(void);
 
 extern test_t tests[];
Index: uspace/lib/c/include/ipc/services.h
===================================================================
--- uspace/lib/c/include/ipc/services.h	(revision b2f542a7efa3ca69bfbe7544962e906503d25b89)
+++ uspace/lib/c/include/ipc/services.h	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -49,4 +49,7 @@
 } service_t;
 
+#define SERVICE_NAME_CHARDEV_TEST_SMALLX "chardev-test/smallx"
+#define SERVICE_NAME_CHARDEV_TEST_LARGEX "chardev-test/largex"
+#define SERVICE_NAME_CHARDEV_TEST_PARTIALX "chardev-test/partialx"
 #define SERVICE_NAME_CLIPBOARD "clipboard"
 #define SERVICE_NAME_CORECFG  "corecfg"
Index: uspace/srv/test/chardev-test/Makefile
===================================================================
--- uspace/srv/test/chardev-test/Makefile	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
+++ uspace/srv/test/chardev-test/Makefile	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2017 Jiri Svoboda
+# 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.
+#
+
+USPACE_PREFIX = ../../..
+BINARY = chardev-test
+
+SOURCES = \
+	main.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/test/chardev-test/main.c
===================================================================
--- uspace/srv/test/chardev-test/main.c	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
+++ uspace/srv/test/chardev-test/main.c	(revision 57914494f0017d0618e3eaa16f2ae9c2fd1baefd)
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2017 Jiri Svoboda
+ * 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.
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <io/chardev_srv.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <mem.h>
+#include <stdio.h>
+#include <task.h>
+
+#define NAME  "chardev-test"
+
+static int smallx_open(chardev_srvs_t *, chardev_srv_t *);
+static int smallx_close(chardev_srv_t *);
+static int smallx_write(chardev_srv_t *, const void *, size_t, size_t *);
+static int smallx_read(chardev_srv_t *, void *, size_t, size_t *);
+
+static int largex_open(chardev_srvs_t *, chardev_srv_t *);
+static int largex_close(chardev_srv_t *);
+static int largex_write(chardev_srv_t *, const void *, size_t, size_t *);
+static int largex_read(chardev_srv_t *, void *, size_t, size_t *);
+
+static int partialx_open(chardev_srvs_t *, chardev_srv_t *);
+static int partialx_close(chardev_srv_t *);
+static int partialx_write(chardev_srv_t *, const void *, size_t, size_t *);
+static int partialx_read(chardev_srv_t *, void *, size_t, size_t *);
+
+static service_id_t smallx_svc_id;
+static chardev_srvs_t smallx_srvs;
+
+static service_id_t largex_svc_id;
+static chardev_srvs_t largex_srvs;
+
+static service_id_t partialx_svc_id;
+static chardev_srvs_t partialx_srvs;
+
+static chardev_ops_t chardev_test_smallx_ops = {
+	.open = smallx_open,
+	.close = smallx_close,
+	.write = smallx_write,
+	.read = smallx_read
+};
+
+static chardev_ops_t chardev_test_largex_ops = {
+	.open = largex_open,
+	.close = largex_close,
+	.write = largex_write,
+	.read = largex_read
+};
+
+static chardev_ops_t chardev_test_partialx_ops = {
+	.open = partialx_open,
+	.close = partialx_close,
+	.write = partialx_write,
+	.read = partialx_read
+};
+
+static void chardev_test_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	chardev_srvs_t *svc;
+	sysarg_t svcid;
+
+	svcid = IPC_GET_ARG2(*icall);
+	if (svcid == smallx_svc_id) {
+		svc = &smallx_srvs;
+	} else if (svcid == largex_svc_id) {
+		svc = &largex_srvs;
+	} else if (svcid == partialx_svc_id) {
+		svc = &partialx_srvs;
+	} else {
+		async_answer_0(iid, ENOENT);
+		return;
+	}
+
+	chardev_conn(iid, icall, svc);
+}
+
+int main(int argc, char *argv[])
+{
+	int rc;
+
+	printf("%s: Character device test service\n", NAME);
+	async_set_fallback_port_handler(chardev_test_connection, NULL);
+
+	rc = loc_server_register(NAME);
+	if (rc != EOK) {
+		printf("%s: Failed registering server. (%d)\n", NAME, rc);
+		return rc;
+	}
+
+	chardev_srvs_init(&smallx_srvs);
+	smallx_srvs.ops = &chardev_test_smallx_ops;
+	smallx_srvs.sarg = NULL;
+
+	chardev_srvs_init(&largex_srvs);
+	largex_srvs.ops = &chardev_test_largex_ops;
+	largex_srvs.sarg = NULL;
+
+	chardev_srvs_init(&partialx_srvs);
+	partialx_srvs.ops = &chardev_test_partialx_ops;
+	partialx_srvs.sarg = NULL;
+
+	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_SMALLX, &smallx_svc_id);
+	if (rc != EOK) {
+		printf("%s: Failed registering service. (%d)\n", NAME, rc);
+		return rc;
+	}
+
+	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_LARGEX, &largex_svc_id);
+	if (rc != EOK) {
+		printf("%s: Failed registering service. (%d)\n", NAME, rc);
+		return rc;
+	}
+
+	rc = loc_service_register(SERVICE_NAME_CHARDEV_TEST_PARTIALX, &partialx_svc_id);
+	if (rc != EOK) {
+		printf("%s: Failed registering service. (%d)\n", NAME, rc);
+		return rc;
+	}
+
+
+	printf("%s: Accepting connections\n", NAME);
+	task_retval(0);
+	async_manager();
+
+	/* Not reached */
+	return 0;
+}
+
+static int smallx_open(chardev_srvs_t *srvs, chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int smallx_close(chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int smallx_write(chardev_srv_t *srv, const void *data, size_t size,
+    size_t *nwritten)
+{
+	if (size < 1) {
+		*nwritten = 0;
+		return EOK;
+	}
+
+	*nwritten = 1;
+	return EOK;
+}
+
+static int smallx_read(chardev_srv_t *srv, void *buf, size_t size,
+    size_t *nread)
+{
+	if (size < 1) {
+		*nread = 0;
+		return EOK;
+	}
+
+	memset(buf, 0, 1);
+	*nread = 1;
+	return EOK;
+}
+
+static int largex_open(chardev_srvs_t *srvs, chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int largex_close(chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int largex_write(chardev_srv_t *srv, const void *data, size_t size,
+    size_t *nwritten)
+{
+	if (size < 1) {
+		*nwritten = 0;
+		return EOK;
+	}
+
+	*nwritten = size;
+	return EOK;
+}
+
+static int largex_read(chardev_srv_t *srv, void *buf, size_t size,
+    size_t *nread)
+{
+	if (size < 1) {
+		*nread = 0;
+		return EOK;
+	}
+
+	memset(buf, 0, size);
+	*nread = size;
+	return EOK;
+}
+
+static int partialx_open(chardev_srvs_t *srvs, chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int partialx_close(chardev_srv_t *srv)
+{
+	return EOK;
+}
+
+static int partialx_write(chardev_srv_t *srv, const void *data, size_t size,
+    size_t *nwritten)
+{
+	if (size < 1) {
+		*nwritten = 0;
+		return EOK;
+	}
+
+	*nwritten = 1;
+	return EIO;
+}
+
+static int partialx_read(chardev_srv_t *srv, void *buf, size_t size,
+    size_t *nread)
+{
+	if (size < 1) {
+		*nread = 0;
+		return EOK;
+	}
+
+	memset(buf, 0, 1);
+	*nread = 1;
+	return EIO;
+}
