Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ boot/Makefile.common	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -109,4 +109,5 @@
 	$(USPACE_PATH)/srv/fs/ext4fs/ext4fs \
 	$(USPACE_PATH)/srv/hid/remcons/remcons \
+	$(USPACE_PATH)/srv/hid/isdv4_tablet/isdv4_tablet \
 	$(USPACE_PATH)/srv/net/ethip/ethip \
 	$(USPACE_PATH)/srv/net/inetsrv/inetsrv \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/Makefile	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -103,4 +103,5 @@
 	srv/hid/console \
 	srv/hid/s3c24xx_ts \
+	srv/hid/isdv4_tablet \
 	srv/hid/fb \
 	srv/hid/input \
Index: uspace/app/edit/edit.c
===================================================================
--- uspace/app/edit/edit.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/app/edit/edit.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -126,6 +126,7 @@
 static int file_save_range(char const *fname, spt_t const *spos,
     spt_t const *epos);
-static char *filename_prompt(char const *prompt, char const *init_value);
 static char *range_get_str(spt_t const *spos, spt_t const *epos);
+
+static char *prompt(char const *prompt, char const *init_value);
 
 static void pane_text_display(void);
@@ -142,4 +143,6 @@
 static void caret_move_word_left(void);
 static void caret_move_word_right(void);
+static void caret_move_to_line(int row);
+static void caret_go_to_line_ask(void);
 
 static bool selection_active(void);
@@ -404,4 +407,7 @@
 		caret_move_word_left();
 		break;
+	case KC_L:
+		caret_go_to_line_ask();
+		break;
 	default:
 		break;
@@ -423,5 +429,7 @@
 }
 
-static void key_handle_movement(unsigned int key, bool select)
+/** Move caret while preserving or resetting selection. */
+static void caret_movement(int drow, int dcolumn, enum dir_spec align_dir,
+    bool select)
 {
 	spt_t pt;
@@ -435,32 +443,5 @@
 	had_sel = !spt_equal(&caret_pt, &pt);
 
-	switch (key) {
-	case KC_LEFT:
-		caret_move(0, -1, dir_before);
-		break;
-	case KC_RIGHT:
-		caret_move(0, 0, dir_after);
-		break;
-	case KC_UP:
-		caret_move(-1, 0, dir_before);
-		break;
-	case KC_DOWN:
-		caret_move(+1, 0, dir_before);
-		break;
-	case KC_HOME:
-		caret_move(0, -ED_INFTY, dir_before);
-		break;
-	case KC_END:
-		caret_move(0, +ED_INFTY, dir_before);
-		break;
-	case KC_PAGE_UP:
-		caret_move(-pane.rows, 0, dir_before);
-		break;
-	case KC_PAGE_DOWN:
-		caret_move(+pane.rows, 0, dir_before);
-		break;
-	default:
-		break;
-	}
+	caret_move(drow, dcolumn, align_dir);
 
 	if (select == false) {
@@ -487,4 +468,36 @@
 }
 
+static void key_handle_movement(unsigned int key, bool select)
+{
+	switch (key) {
+	case KC_LEFT:
+		caret_movement(0, -1, dir_before, select);
+		break;
+	case KC_RIGHT:
+		caret_movement(0, 0, dir_after, select);
+		break;
+	case KC_UP:
+		caret_movement(-1, 0, dir_before, select);
+		break;
+	case KC_DOWN:
+		caret_movement(+1, 0, dir_before, select);
+		break;
+	case KC_HOME:
+		caret_movement(0, -ED_INFTY, dir_before, select);
+		break;
+	case KC_END:
+		caret_movement(0, +ED_INFTY, dir_before, select);
+		break;
+	case KC_PAGE_UP:
+		caret_movement(-pane.rows, 0, dir_before, select);
+		break;
+	case KC_PAGE_DOWN:
+		caret_movement(+pane.rows, 0, dir_before, select);
+		break;
+	default:
+		break;
+	}
+}
+
 /** Save the document. */
 static int file_save(char const *fname)
@@ -520,5 +533,5 @@
 	char *fname;
 	
-	fname = filename_prompt("Save As", old_fname);
+	fname = prompt("Save As", old_fname);
 	if (fname == NULL) {
 		status_display("Save cancelled.");
@@ -535,6 +548,6 @@
 }
 
-/** Ask for a file name. */
-static char *filename_prompt(char const *prompt, char const *init_value)
+/** Ask for a string. */
+static char *prompt(char const *prompt, char const *init_value)
 {
 	kbd_event_t ev;
@@ -842,6 +855,6 @@
 		/* Fill until the end of display area. */
 
-		if (str_length(row_buf) < (unsigned) scr_columns)
-			fill = scr_columns - str_length(row_buf);
+		if ((unsigned)s_column - 1 < scr_columns)
+			fill = scr_columns - (s_column - 1);
 		else
 			fill = 0;
@@ -1077,4 +1090,39 @@
 	pane.rflags |= REDRAW_TEXT;
 }
+
+/** Change the caret position to a beginning of a given line
+ */
+static void caret_move_to_line(int row)
+{
+	spt_t pt;
+	coord_t coord;
+
+	tag_get_pt(&pane.caret_pos, &pt);
+	spt_get_coord(&pt, &coord);
+
+	caret_movement(row - coord.row, 0, dir_before, false);
+}
+
+/** Ask for line and go to it. */
+static void caret_go_to_line_ask(void)
+{
+	char *sline;
+	
+	sline = prompt("Go to line", "");
+	if (sline == NULL) {
+		status_display("Go to line cancelled.");
+		return;
+	}
+	
+	char *endptr;
+	int line = strtol(sline, &endptr, 10);
+	if (*endptr != '\0') {
+		status_display("Invalid number entered.");
+		return;
+	}
+	
+	caret_move_to_line(line);
+}
+
 
 /** Check for non-empty selection. */
Index: uspace/app/sportdmp/sportdmp.c
===================================================================
--- uspace/app/sportdmp/sportdmp.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/app/sportdmp/sportdmp.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -140,10 +140,7 @@
 		ssize_t i;
 		for (i = 0; i < read; i++) {
-			if ((buf[i] >= 32) && (buf[i] < 128))
-				putchar((wchar_t) buf[i]);
-			else
-				putchar('.');
-			fflush(stdout);
+			printf("%02hhx ", buf[i]);
 		}
+		fflush(stdout);
 	}
 	
Index: uspace/drv/bus/usb/usbmast/main.c
===================================================================
--- uspace/drv/bus/usb/usbmast/main.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/drv/bus/usb/usbmast/main.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -82,5 +82,5 @@
     void *arg);
 
-static int usbmast_bd_open(bd_srv_t *);
+static int usbmast_bd_open(bd_srvs_t *, bd_srv_t *);
 static int usbmast_bd_close(bd_srv_t *);
 static int usbmast_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -100,5 +100,5 @@
 static usbmast_fun_t *bd_srv_usbmast(bd_srv_t *bd)
 {
-	return (usbmast_fun_t *)bd->arg;
+	return (usbmast_fun_t *)bd->srvs->sarg;
 }
 
@@ -240,7 +240,7 @@
 	mfun->lun = lun;
 
-	bd_srv_init(&mfun->bd);
-	mfun->bd.ops = &usbmast_bd_ops;
-	mfun->bd.arg = mfun;
+	bd_srvs_init(&mfun->bds);
+	mfun->bds.ops = &usbmast_bd_ops;
+	mfun->bds.sarg = mfun;
 
 	/* Set up a connection handler. */
@@ -311,9 +311,9 @@
 
 	mfun = (usbmast_fun_t *) ((ddf_fun_t *)arg)->driver_data;
-	bd_conn(iid, icall, &mfun->bd);
+	bd_conn(iid, icall, &mfun->bds);
 }
 
 /** Open device. */
-static int usbmast_bd_open(bd_srv_t *bd)
+static int usbmast_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/drv/bus/usb/usbmast/usbmast.h
===================================================================
--- uspace/drv/bus/usb/usbmast/usbmast.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/drv/bus/usb/usbmast/usbmast.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -69,6 +69,6 @@
 	/** Block size in bytes */
 	size_t block_size;
-	/** Block device server structure */
-	bd_srv_t bd;
+	/** Block device service structure */
+	bd_srvs_t bds;
 } usbmast_fun_t;
 
Index: uspace/drv/char/ns8250/cyclic_buffer.h
===================================================================
--- uspace/drv/char/ns8250/cyclic_buffer.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/drv/char/ns8250/cyclic_buffer.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -36,5 +36,5 @@
 #define CYCLIC_BUFFER_H_
 
-#define BUF_LEN 256
+#define BUF_LEN 4096
 
 typedef struct cyclic_buffer {
Index: uspace/drv/char/ns8250/ns8250.c
===================================================================
--- uspace/drv/char/ns8250/ns8250.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/drv/char/ns8250/ns8250.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -82,4 +82,6 @@
 /** Interrupt ID Register definition. */
 #define	NS8250_IID_ACTIVE	(1 << 0)
+#define NS8250_IID_CAUSE_MASK 0x0e
+#define NS8250_IID_CAUSE_RXSTATUS 0x06
 
 /** FIFO Control Register definition. */
@@ -179,4 +181,6 @@
 	/** The fibril mutex for synchronizing the access to the device. */
 	fibril_mutex_t mutex;
+	/** Indicates that some data has become available */
+	fibril_condvar_t input_buffer_available;
 	/** True if device is removed. */
 	bool removed;
@@ -238,7 +242,11 @@
 {
 	ns8250_t *ns = NS8250(fun);
-	int ret = EOK;
+	int ret = 0;
+	
+	if (count == 0) return 0;
 	
 	fibril_mutex_lock(&ns->mutex);
+	while (buf_is_empty(&ns->input_buffer))
+		fibril_condvar_wait(&ns->input_buffer_available, &ns->mutex);
 	while (!buf_is_empty(&ns->input_buffer) && (size_t)ret < count) {
 		buf[ret] = (char)buf_pop_front(&ns->input_buffer);
@@ -460,5 +468,5 @@
 {
 	/* Interrupt when data received. */
-	pio_write_8(&regs->ier, NS8250_IER_RXREADY);
+	pio_write_8(&regs->ier, NS8250_IER_RXREADY | NS8250_IER_RXSTATUS);
 	pio_write_8(&regs->mcr, NS8250_MCR_DTR | NS8250_MCR_RTS 
 	    | NS8250_MCR_OUT2);
@@ -499,4 +507,7 @@
 	async_exchange_end(exch);
 
+	/* Read LSR to clear possible previous LSR interrupt */
+	pio_read_8(&ns->regs->lsr);
+
 	/* Enable interrupt on the serial port. */
 	ns8250_port_interrupts_enable(ns->regs);
@@ -695,8 +706,11 @@
 	/* 8 bits, no parity, two stop bits. */
 	ns8250_port_set_com_props(ns->regs, SERIAL_NO_PARITY, 8, 2);
-	/* Enable FIFO, clear them, with 14-byte threshold. */
+	/*
+	 * Enable FIFO, clear them, with 4-byte threshold for greater
+	 * reliability.
+	 */
 	pio_write_8(&ns->regs->iid, NS8250_FCR_FIFOENABLE
-	    | NS8250_FCR_RXFIFORESET | NS8250_FCR_TXFIFORESET 
-	    | NS8250_FCR_RXTRIGGERLOW | NS8250_FCR_RXTRIGGERHI);
+	    | NS8250_FCR_RXFIFORESET | NS8250_FCR_TXFIFORESET
+	    | NS8250_FCR_RXTRIGGERLOW);
 	/*
 	 * RTS/DSR set (Request to Send and Data Terminal Ready lines enabled),
@@ -731,7 +745,6 @@
 	bool cont = true;
 	
+	fibril_mutex_lock(&ns->mutex);
 	while (cont) {
-		fibril_mutex_lock(&ns->mutex);
-		
 		cont = ns8250_received(regs);
 		if (cont) {
@@ -739,24 +752,28 @@
 			
 			if (ns->client_connected) {
+				bool buf_was_empty = buf_is_empty(&ns->input_buffer);
 				if (!buf_push_back(&ns->input_buffer, val)) {
 					ddf_msg(LVL_WARN, "Buffer overflow on "
 					    "%s.", ns->dev->name);
+					break;
 				} else {
 					ddf_msg(LVL_DEBUG2, "Character %c saved "
 					    "to the buffer of %s.",
 					    val, ns->dev->name);
+					if (buf_was_empty)
+						fibril_condvar_broadcast(&ns->input_buffer_available);
 				}
 			}
 		}
-		
-		fibril_mutex_unlock(&ns->mutex);
-		fibril_yield();
-	}
+	}
+	fibril_mutex_unlock(&ns->mutex);
+	fibril_yield();
 }
 
 /** The interrupt handler.
  *
- * The serial port is initialized to interrupt when some data come, so the
- * interrupt is handled by reading the incomming data.
+ * The serial port is initialized to interrupt when some data come or line
+ * status register changes, so the interrupt is handled by reading the incoming
+ * data and reading the line status register.
  *
  * @param dev		The serial port device.
@@ -765,5 +782,15 @@
     ipc_call_t *icall)
 {
-	ns8250_read_from_device(NS8250_FROM_DEV(dev));
+	ns8250_t *ns = NS8250_FROM_DEV(dev);
+
+	uint8_t iir = pio_read_8(&ns->regs->iid);
+	if ((iir & NS8250_IID_CAUSE_MASK) == NS8250_IID_CAUSE_RXSTATUS) {
+		uint8_t lsr = pio_read_8(&ns->regs->lsr);
+		if (lsr & NS8250_LSR_OE) {
+			ddf_msg(LVL_WARN, "Overrun error on %s", ns->dev->name);
+		}
+	}
+	
+	ns8250_read_from_device(ns);
 }
 
@@ -811,4 +838,5 @@
 	
 	fibril_mutex_initialize(&ns->mutex);
+	fibril_condvar_initialize(&ns->input_buffer_available);
 	ns->dev = dev;
 	
@@ -1053,5 +1081,5 @@
 static void ns8250_init(void)
 {
-	ddf_log_init(NAME, LVL_ERROR);
+	ddf_log_init(NAME, LVL_WARN);
 	
 	ns8250_dev_ops.open = &ns8250_open;
Index: uspace/lib/c/generic/bd_srv.c
===================================================================
--- uspace/lib/c/generic/bd_srv.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/lib/c/generic/bd_srv.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -67,5 +67,5 @@
 	}
 
-	if (srv->ops->read_blocks == NULL) {
+	if (srv->srvs->ops->read_blocks == NULL) {
 		async_answer_0(rcallid, ENOTSUP);
 		async_answer_0(callid, ENOTSUP);
@@ -73,5 +73,5 @@
 	}
 
-	rc = srv->ops->read_blocks(srv, ba, cnt, buf, size);
+	rc = srv->srvs->ops->read_blocks(srv, ba, cnt, buf, size);
 	if (rc != EOK) {
 		async_answer_0(rcallid, ENOMEM);
@@ -109,5 +109,5 @@
 	}
 
-	if (srv->ops->read_toc == NULL) {
+	if (srv->srvs->ops->read_toc == NULL) {
 		async_answer_0(rcallid, ENOTSUP);
 		async_answer_0(callid, ENOTSUP);
@@ -115,5 +115,5 @@
 	}
 
-	rc = srv->ops->read_toc(srv, session, buf, size);
+	rc = srv->srvs->ops->read_toc(srv, session, buf, size);
 	if (rc != EOK) {
 		async_answer_0(rcallid, ENOMEM);
@@ -146,10 +146,10 @@
 	}
 
-	if (srv->ops->write_blocks == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	rc = srv->ops->write_blocks(srv, ba, cnt, data, size);
+	if (srv->srvs->ops->write_blocks == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->write_blocks(srv, ba, cnt, data, size);
 	free(data);
 	async_answer_0(callid, rc);
@@ -162,10 +162,10 @@
 	size_t block_size;
 
-	if (srv->ops->get_block_size == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	rc = srv->ops->get_block_size(srv, &block_size);
+	if (srv->srvs->ops->get_block_size == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_block_size(srv, &block_size);
 	async_answer_1(callid, rc, block_size);
 }
@@ -177,39 +177,42 @@
 	aoff64_t num_blocks;
 
-	if (srv->ops->get_num_blocks == NULL) {
-		async_answer_0(callid, ENOTSUP);
-		return;
-	}
-
-	rc = srv->ops->get_num_blocks(srv, &num_blocks);
+	if (srv->srvs->ops->get_num_blocks == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->get_num_blocks(srv, &num_blocks);
 	async_answer_2(callid, rc, LOWER32(num_blocks), UPPER32(num_blocks));
 }
 
-void bd_srv_init(bd_srv_t *srv)
-{
-	fibril_mutex_initialize(&srv->lock);
-	srv->connected = false;
-	srv->ops = NULL;
-	srv->arg = NULL;
-	srv->client_sess = NULL;
-}
-
-int bd_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
-{
-	bd_srv_t *srv = (bd_srv_t *)arg;
-	int rc;
-
-	fibril_mutex_lock(&srv->lock);
-	if (srv->connected) {
-		fibril_mutex_unlock(&srv->lock);
-		async_answer_0(iid, EBUSY);
-		return EBUSY;
-	}
-
-	srv->connected = true;
-	fibril_mutex_unlock(&srv->lock);
+static bd_srv_t *bd_srv_create(bd_srvs_t *srvs)
+{
+	bd_srv_t *srv;
+
+	srv = calloc(1, sizeof(srv));
+	if (srv == NULL)
+		return NULL;
+
+	srv->srvs = srvs;
+	return srv;
+}
+
+void bd_srvs_init(bd_srvs_t *srvs)
+{
+	srvs->ops = NULL;
+	srvs->sarg = NULL;
+}
+
+int bd_conn(ipc_callid_t iid, ipc_call_t *icall, bd_srvs_t *srvs)
+{
+	bd_srv_t *srv;
+	int rc;
 
 	/* Accept the connection */
 	async_answer_0(iid, EOK);
+
+	srv = bd_srv_create(srvs);
+	if (srv == NULL)
+		return ENOMEM;
 
 	async_sess_t *sess = async_callback_receive(EXCHANGE_SERIALIZE);
@@ -219,5 +222,5 @@
 	srv->client_sess = sess;
 
-	rc = srv->ops->open(srv);
+	rc = srvs->ops->open(srvs, srv);
 	if (rc != EOK)
 		return rc;
@@ -230,7 +233,4 @@
 		if (!method) {
 			/* The other side has hung up */
-			fibril_mutex_lock(&srv->lock);
-			srv->connected = false;
-			fibril_mutex_unlock(&srv->lock);
 			async_answer_0(callid, EOK);
 			break;
@@ -258,5 +258,8 @@
 	}
 
-	return srv->ops->close(srv);
+	rc = srvs->ops->close(srv);
+	free(srv);
+
+	return rc;
 }
 
Index: uspace/lib/c/include/bd_srv.h
===================================================================
--- uspace/lib/c/include/bd_srv.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/lib/c/include/bd_srv.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -36,4 +36,5 @@
 #define LIBC_BD_SRV_H_
 
+#include <adt/list.h>
 #include <async.h>
 #include <fibril_synch.h>
@@ -43,14 +44,19 @@
 typedef struct bd_ops bd_ops_t;
 
+/** Service setup (per sevice) */
 typedef struct {
-	fibril_mutex_t lock;
-	bool connected;
 	bd_ops_t *ops;
-	void *arg;
+	void *sarg;
+} bd_srvs_t;
+
+/** Server structure (per client session) */
+typedef struct {
+	bd_srvs_t *srvs;
 	async_sess_t *client_sess;
+	void *carg;
 } bd_srv_t;
 
 typedef struct bd_ops {
-	int (*open)(bd_srv_t *);
+	int (*open)(bd_srvs_t *, bd_srv_t *);
 	int (*close)(bd_srv_t *);
 	int (*read_blocks)(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -61,7 +67,7 @@
 } bd_ops_t;
 
-extern void bd_srv_init(bd_srv_t *);
+extern void bd_srvs_init(bd_srvs_t *);
 
-extern int bd_conn(ipc_callid_t, ipc_call_t *, void *);
+extern int bd_conn(ipc_callid_t, ipc_call_t *, bd_srvs_t *);
 
 #endif
Index: uspace/lib/c/include/ipc/input.h
===================================================================
--- uspace/lib/c/include/ipc/input.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/lib/c/include/ipc/input.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -46,4 +46,5 @@
 	INPUT_EVENT_KEY = IPC_FIRST_USER_METHOD,
 	INPUT_EVENT_MOVE,
+	INPUT_EVENT_ABS_MOVE,
 	INPUT_EVENT_BUTTON
 } input_notif_t;
Index: uspace/lib/c/include/ipc/mouseev.h
===================================================================
--- uspace/lib/c/include/ipc/mouseev.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/lib/c/include/ipc/mouseev.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -47,4 +47,5 @@
 typedef enum {
 	MOUSEEV_MOVE_EVENT = IPC_FIRST_USER_METHOD,
+	MOUSEEV_ABS_MOVE_EVENT,
 	MOUSEEV_BUTTON_EVENT
 } mouseev_notif_t;
Index: uspace/srv/bd/ata_bd/ata_bd.c
===================================================================
--- uspace/srv/bd/ata_bd/ata_bd.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/ata_bd/ata_bd.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -104,5 +104,5 @@
 static void ata_bd_connection(ipc_callid_t iid, ipc_call_t *icall, void *);
 
-static int ata_bd_open(bd_srv_t *);
+static int ata_bd_open(bd_srvs_t *, bd_srv_t *);
 static int ata_bd_close(bd_srv_t *);
 static int ata_bd_read_blocks(bd_srv_t *, uint64_t ba, size_t cnt, void *buf,
@@ -114,17 +114,17 @@
 static int ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
 
-static int ata_rcmd_read(int disk_id, uint64_t ba, size_t cnt,
+static int ata_rcmd_read(disk_t *disk, uint64_t ba, size_t cnt,
     void *buf);
-static int ata_rcmd_write(int disk_id, uint64_t ba, size_t cnt,
+static int ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
     const void *buf);
 static int disk_init(disk_t *d, int disk_id);
-static int drive_identify(int drive_id, void *buf);
-static int identify_pkt_dev(int dev_idx, void *buf);
-static int ata_cmd_packet(int dev_idx, const void *cpkt, size_t cpkt_size,
+static int drive_identify(disk_t *disk, void *buf);
+static int identify_pkt_dev(disk_t *disk, void *buf);
+static int ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
     void *obuf, size_t obuf_size);
-static int ata_pcmd_inquiry(int dev_idx, void *obuf, size_t obuf_size);
-static int ata_pcmd_read_12(int dev_idx, uint64_t ba, size_t cnt,
+static int ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size);
+static int ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
     void *obuf, size_t obuf_size);
-static int ata_pcmd_read_toc(int dev_idx, uint8_t ses,
+static int ata_pcmd_read_toc(disk_t *disk, uint8_t ses,
     void *obuf, size_t obuf_size);
 static void disk_print_summary(disk_t *d);
@@ -146,5 +146,10 @@
 static disk_t *bd_srv_disk(bd_srv_t *bd)
 {
-	return (disk_t *)bd->arg;
+	return (disk_t *)bd->srvs->sarg;
+}
+
+static int disk_dev_idx(disk_t *disk)
+{
+	return (disk->disk_id & 1);
 }
 
@@ -296,5 +301,6 @@
 {
 	service_id_t dsid;
-	int disk_id, i;
+	int i;
+	disk_t *disk;
 
 	/* Get the device service ID. */
@@ -302,15 +308,15 @@
 
 	/* Determine which disk device is the client connecting to. */
-	disk_id = -1;
+	disk = NULL;
 	for (i = 0; i < MAX_DISKS; i++)
 		if (ata_disk[i].service_id == dsid)
-			disk_id = i;
-
-	if (disk_id < 0 || ata_disk[disk_id].present == false) {
+			disk = &ata_disk[i];
+
+	if (disk == NULL || disk->present == false) {
 		async_answer_0(iid, EINVAL);
 		return;
 	}
 
-	bd_conn(iid, icall, &ata_disk[disk_id].bd);
+	bd_conn(iid, icall, &disk->bds);
 }
 
@@ -336,10 +342,10 @@
 	fibril_mutex_initialize(&d->lock);
 
-	bd_srv_init(&d->bd);
-	d->bd.ops = &ata_bd_ops;
-	d->bd.arg = d;
+	bd_srvs_init(&d->bds);
+	d->bds.ops = &ata_bd_ops;
+	d->bds.sarg = d;
 
 	/* Try identify command. */
-	rc = drive_identify(disk_id, &idata);
+	rc = drive_identify(d, &idata);
 	if (rc == EOK) {
 		/* Success. It's a register (non-packet) device. */
@@ -361,5 +367,5 @@
 
 		if (bc == PDEV_SIGNATURE_BC) {
-			rc = identify_pkt_dev(disk_id, &idata);
+			rc = identify_pkt_dev(d, &idata);
 			if (rc == EOK) {
 				/* We have a packet device. */
@@ -445,5 +451,5 @@
 	if (d->dev_type == ata_pkt_dev) {
 		/* Send inquiry. */
-		rc = ata_pcmd_inquiry(0, &inq_data, sizeof(inq_data));
+		rc = ata_pcmd_inquiry(d, &inq_data, sizeof(inq_data));
 		if (rc != EOK) {
 			printf("Device inquiry failed.\n");
@@ -467,5 +473,5 @@
 }
 
-static int ata_bd_open(bd_srv_t *bd)
+static int ata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
@@ -488,9 +494,10 @@
 
 	while (cnt > 0) {
-		if (disk->dev_type == ata_reg_dev)
-			rc = ata_rcmd_read(disk->disk_id, ba, 1, buf);
-		else
-			rc = ata_pcmd_read_12(disk->disk_id, ba, 1, buf,
+		if (disk->dev_type == ata_reg_dev) {
+			rc = ata_rcmd_read(disk, ba, 1, buf);
+		} else {
+			rc = ata_pcmd_read_12(disk, ba, 1, buf,
 			    disk->block_size);
+		}
 
 		if (rc != EOK)
@@ -510,5 +517,5 @@
 	disk_t *disk = bd_srv_disk(bd);
 
-	return ata_pcmd_read_toc(disk->disk_id, session, buf, size);
+	return ata_pcmd_read_toc(disk, session, buf, size);
 }
 
@@ -527,5 +534,5 @@
 
 	while (cnt > 0) {
-		rc = ata_rcmd_write(disk->disk_id, ba, 1, buf);
+		rc = ata_rcmd_write(disk, ba, 1, buf);
 		if (rc != EOK)
 			return rc;
@@ -562,5 +569,5 @@
  * whether an ATA device is present and if so, to determine its parameters.
  *
- * @param disk_id	Device ID, 0 or 1.
+ * @param disk		Disk
  * @param buf		Pointer to a 512-byte buffer.
  *
@@ -568,5 +575,5 @@
  *			not present). EIO if device responds with error.
  */
-static int drive_identify(int disk_id, void *buf)
+static int drive_identify(disk_t *disk, void *buf)
 {
 	uint16_t data;
@@ -575,5 +582,5 @@
 	size_t i;
 
-	drv_head = ((disk_id != 0) ? DHR_DRV : 0);
+	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
 
 	if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
@@ -621,8 +628,8 @@
  * whether an ATAPI device is present and if so, to determine its parameters.
  *
- * @param dev_idx	Device index, 0 or 1.
+ * @param disk		Disk
  * @param buf		Pointer to a 512-byte buffer.
  */
-static int identify_pkt_dev(int dev_idx, void *buf)
+static int identify_pkt_dev(disk_t *disk, void *buf)
 {
 	uint16_t data;
@@ -631,5 +638,5 @@
 	size_t i;
 
-	drv_head = ((dev_idx != 0) ? DHR_DRV : 0);
+	drv_head = ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
 
 	if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK)
@@ -666,5 +673,5 @@
  * Only data-in commands are supported (e.g. inquiry, read).
  *
- * @param dev_idx	Device index (0 or 1)
+ * @param disk		Disk
  * @param obuf		Buffer for storing data read from device
  * @param obuf_size	Size of obuf in bytes
@@ -672,5 +679,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_cmd_packet(int dev_idx, const void *cpkt, size_t cpkt_size,
+static int ata_cmd_packet(disk_t *disk, const void *cpkt, size_t cpkt_size,
     void *obuf, size_t obuf_size)
 {
@@ -678,17 +685,15 @@
 	uint8_t status;
 	uint8_t drv_head;
-	disk_t *d;
 	size_t data_size;
 	uint16_t val;
 
-	d = &ata_disk[dev_idx];
-	fibril_mutex_lock(&d->lock);
+	fibril_mutex_lock(&disk->lock);
 
 	/* New value for Drive/Head register */
 	drv_head =
-	    ((dev_idx != 0) ? DHR_DRV : 0);
+	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0);
 
 	if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -697,5 +702,5 @@
 
 	if (wait_status(0, ~(SR_BSY|SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -708,5 +713,5 @@
 
 	if (wait_status(SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -717,10 +722,10 @@
 
 	if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
 
 	if ((status & SR_DRQ) == 0) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -733,5 +738,5 @@
 	if (data_size > obuf_size) {
 		/* Output buffer is too small to store data. */
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -744,9 +749,9 @@
 
 	if (status & SR_ERR) {
-		fibril_mutex_unlock(&d->lock);
-		return EIO;
-	}
-
-	fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
+		return EIO;
+	}
+
+	fibril_mutex_unlock(&disk->lock);
 
 	return EOK;
@@ -755,5 +760,5 @@
 /** Issue ATAPI Inquiry.
  *
- * @param dev_idx	Device index (0 or 1)
+ * @param disk		Disk
  * @param obuf		Buffer for storing inquiry data read from device
  * @param obuf_size	Size of obuf in bytes
@@ -761,5 +766,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_pcmd_inquiry(int dev_idx, void *obuf, size_t obuf_size)
+static int ata_pcmd_inquiry(disk_t *disk, void *obuf, size_t obuf_size)
 {
 	ata_pcmd_inquiry_t cp;
@@ -771,5 +776,5 @@
 	cp.alloc_len = min(obuf_size, 0xff); /* Allocation length */
 
-	rc = ata_cmd_packet(0, &cp, sizeof(cp), obuf, obuf_size);
+	rc = ata_cmd_packet(disk, &cp, sizeof(cp), obuf, obuf_size);
 	if (rc != EOK)
 		return rc;
@@ -783,5 +788,5 @@
  * function will fail.
  *
- * @param dev_idx	Device index (0 or 1)
+ * @param disk		Disk
  * @param ba		Starting block address
  * @param cnt		Number of blocks to read
@@ -791,5 +796,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_pcmd_read_12(int dev_idx, uint64_t ba, size_t cnt,
+static int ata_pcmd_read_12(disk_t *disk, uint64_t ba, size_t cnt,
     void *obuf, size_t obuf_size)
 {
@@ -806,5 +811,5 @@
 	cp.nblocks = host2uint32_t_be(cnt);
 
-	rc = ata_cmd_packet(0, &cp, sizeof(cp), obuf, obuf_size);
+	rc = ata_cmd_packet(disk, &cp, sizeof(cp), obuf, obuf_size);
 	if (rc != EOK)
 		return rc;
@@ -823,5 +828,5 @@
  * function will fail.
  *
- * @param dev_idx	Device index (0 or 1)
+ * @param disk		Disk
  * @param session	Starting session
  * @param obuf		Buffer for storing inquiry data read from device
@@ -830,5 +835,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_pcmd_read_toc(int dev_idx, uint8_t session, void *obuf,
+static int ata_pcmd_read_toc(disk_t *disk, uint8_t session, void *obuf,
     size_t obuf_size)
 {
@@ -854,5 +859,5 @@
 /** Read a physical from the device.
  *
- * @param disk_id	Device index (0 or 1)
+ * @param disk		Disk
  * @param ba		Address the first block.
  * @param cnt		Number of blocks to transfer.
@@ -861,5 +866,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_rcmd_read(int disk_id, uint64_t ba, size_t blk_cnt,
+static int ata_rcmd_read(disk_t *disk, uint64_t ba, size_t blk_cnt,
     void *buf)
 {
@@ -868,28 +873,25 @@
 	uint8_t status;
 	uint8_t drv_head;
-	disk_t *d;
 	block_coord_t bc;
 
-	d = &ata_disk[disk_id];
-	
 	/* Silence warning. */
 	memset(&bc, 0, sizeof(bc));
 
 	/* Compute block coordinates. */
-	if (coord_calc(d, ba, &bc) != EOK)
+	if (coord_calc(disk, ba, &bc) != EOK)
 		return EINVAL;
 
 	/* New value for Drive/Head register */
 	drv_head =
-	    ((disk_id != 0) ? DHR_DRV : 0) |
-	    ((d->amode != am_chs) ? DHR_LBA : 0) |
+	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
+	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
 	    (bc.h & 0x0f);
 
-	fibril_mutex_lock(&d->lock);
+	fibril_mutex_lock(&disk->lock);
 
 	/* Program a Read Sectors operation. */
 
 	if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -898,5 +900,5 @@
 
 	if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -905,9 +907,9 @@
 	coord_sc_program(&bc, 1);
 
-	pio_write_8(&cmd->command, d->amode == am_lba48 ?
+	pio_write_8(&cmd->command, disk->amode == am_lba48 ?
 	    CMD_READ_SECTORS_EXT : CMD_READ_SECTORS);
 
 	if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -916,5 +918,5 @@
 		/* Read data from the device buffer. */
 
-		for (i = 0; i < ata_disk[disk_id].block_size / 2; i++) {
+		for (i = 0; i < disk->block_size / 2; i++) {
 			data = pio_read_16(&cmd->data_port);
 			((uint16_t *) buf)[i] = data;
@@ -925,5 +927,5 @@
 		return EIO;
 
-	fibril_mutex_unlock(&d->lock);
+	fibril_mutex_unlock(&disk->lock);
 	return EOK;
 }
@@ -931,5 +933,5 @@
 /** Write a physical block to the device.
  *
- * @param disk_id	Device index (0 or 1)
+ * @param disk		Disk
  * @param ba		Address of the first block.
  * @param cnt		Number of blocks to transfer.
@@ -938,5 +940,5 @@
  * @return EOK on success, EIO on error.
  */
-static int ata_rcmd_write(int disk_id, uint64_t ba, size_t cnt,
+static int ata_rcmd_write(disk_t *disk, uint64_t ba, size_t cnt,
     const void *buf)
 {
@@ -944,28 +946,25 @@
 	uint8_t status;
 	uint8_t drv_head;
-	disk_t *d;
 	block_coord_t bc;
 
-	d = &ata_disk[disk_id];
-	
 	/* Silence warning. */
 	memset(&bc, 0, sizeof(bc));
 
 	/* Compute block coordinates. */
-	if (coord_calc(d, ba, &bc) != EOK)
+	if (coord_calc(disk, ba, &bc) != EOK)
 		return EINVAL;
 
 	/* New value for Drive/Head register */
 	drv_head =
-	    ((disk_id != 0) ? DHR_DRV : 0) |
-	    ((d->amode != am_chs) ? DHR_LBA : 0) |
+	    ((disk_dev_idx(disk) != 0) ? DHR_DRV : 0) |
+	    ((disk->amode != am_chs) ? DHR_LBA : 0) |
 	    (bc.h & 0x0f);
 
-	fibril_mutex_lock(&d->lock);
+	fibril_mutex_lock(&disk->lock);
 
 	/* Program a Write Sectors operation. */
 
 	if (wait_status(0, ~SR_BSY, NULL, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -974,5 +973,5 @@
 
 	if (wait_status(SR_DRDY, ~SR_BSY, NULL, TIMEOUT_DRDY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -981,9 +980,9 @@
 	coord_sc_program(&bc, 1);
 
-	pio_write_8(&cmd->command, d->amode == am_lba48 ?
+	pio_write_8(&cmd->command, disk->amode == am_lba48 ?
 	    CMD_WRITE_SECTORS_EXT : CMD_WRITE_SECTORS);
 
 	if (wait_status(0, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
-		fibril_mutex_unlock(&d->lock);
+		fibril_mutex_unlock(&disk->lock);
 		return EIO;
 	}
@@ -992,10 +991,10 @@
 		/* Write data to the device buffer. */
 
-		for (i = 0; i < d->block_size / 2; i++) {
+		for (i = 0; i < disk->block_size / 2; i++) {
 			pio_write_16(&cmd->data_port, ((uint16_t *) buf)[i]);
 		}
 	}
 
-	fibril_mutex_unlock(&d->lock);
+	fibril_mutex_unlock(&disk->lock);
 
 	if (status & SR_ERR)
Index: uspace/srv/bd/ata_bd/ata_bd.h
===================================================================
--- uspace/srv/bd/ata_bd/ata_bd.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/ata_bd/ata_bd.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -119,5 +119,5 @@
 	service_id_t service_id;
 	int disk_id;
-	bd_srv_t bd;
+	bd_srvs_t bds;
 } disk_t;
 
Index: uspace/srv/bd/file_bd/file_bd.c
===================================================================
--- uspace/srv/bd/file_bd/file_bd.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/file_bd/file_bd.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -62,5 +62,5 @@
 
 static service_id_t service_id;
-static bd_srv_t bd_srv;
+static bd_srvs_t bd_srvs;
 static fibril_mutex_t dev_lock;
 
@@ -69,5 +69,5 @@
 static void file_bd_connection(ipc_callid_t iid, ipc_call_t *icall, void *);
 
-static int file_bd_open(bd_srv_t *);
+static int file_bd_open(bd_srvs_t *, bd_srv_t *);
 static int file_bd_close(bd_srv_t *);
 static int file_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -154,6 +154,6 @@
 static int file_bd_init(const char *fname)
 {
-	bd_srv_init(&bd_srv);
-	bd_srv.ops = &file_bd_ops;
+	bd_srvs_init(&bd_srvs);
+	bd_srvs.ops = &file_bd_ops;
 	
 	async_set_client_connection(file_bd_connection);
@@ -188,9 +188,9 @@
 static void file_bd_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
 {
-	bd_conn(iid, icall, &bd_srv);
+	bd_conn(iid, icall, &bd_srvs);
 }
 
 /** Open device. */
-static int file_bd_open(bd_srv_t *bd)
+static int file_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/srv/bd/gxe_bd/gxe_bd.c
===================================================================
--- uspace/srv/bd/gxe_bd/gxe_bd.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/gxe_bd/gxe_bd.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -88,6 +88,6 @@
 /** GXE block device soft state */
 typedef struct {
-	/** Block device server structure */
-	bd_srv_t bd;
+	/** Block device service structure */
+	bd_srvs_t bds;
 	int disk_id;
 } gxe_bd_t;
@@ -109,5 +109,5 @@
 static int gxe_bd_write_block(int disk_id, uint64_t ba, const void *buf);
 
-static int gxe_bd_open(bd_srv_t *);
+static int gxe_bd_open(bd_srvs_t *, bd_srv_t *);
 static int gxe_bd_close(bd_srv_t *);
 static int gxe_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -127,5 +127,5 @@
 static gxe_bd_t *bd_srv_gxe(bd_srv_t *bd)
 {
-	return (gxe_bd_t *)bd->arg;
+	return (gxe_bd_t *)bd->srvs->sarg;
 }
 
@@ -166,7 +166,7 @@
 		char name[16];
 		
-		bd_srv_init(&gxe_bd[i].bd);
-		gxe_bd[i].bd.ops = &gxe_bd_ops;
-		gxe_bd[i].bd.arg = (void *)&gxe_bd[i];
+		bd_srvs_init(&gxe_bd[i].bds);
+		gxe_bd[i].bds.ops = &gxe_bd_ops;
+		gxe_bd[i].bds.sarg = (void *)&gxe_bd[i];
 		
 		snprintf(name, 16, "%s/disk%u", NAMESPACE, i);
@@ -203,9 +203,9 @@
 	}
 
-	bd_conn(iid, icall, &gxe_bd[disk_id].bd);
+	bd_conn(iid, icall, &gxe_bd[disk_id].bds);
 }
 
 /** Open device. */
-static int gxe_bd_open(bd_srv_t *bd)
+static int gxe_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/srv/bd/part/guid_part/guid_part.c
===================================================================
--- uspace/srv/bd/part/guid_part/guid_part.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/part/guid_part/guid_part.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -83,6 +83,6 @@
 	/** Service representing the partition (outbound device) */
 	service_id_t dsid;
-	/** Block device server structure */
-	bd_srv_t bd;
+	/** Block device service structure */
+	bd_srvs_t bds;
 	/** Points to next partition structure. */
 	struct part *next;
@@ -104,5 +104,5 @@
 static int gpt_bsa_translate(part_t *p, aoff64_t ba, size_t cnt, aoff64_t *gba);
 
-static int gpt_bd_open(bd_srv_t *);
+static int gpt_bd_open(bd_srvs_t *, bd_srv_t *);
 static int gpt_bd_close(bd_srv_t *);
 static int gpt_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -122,5 +122,5 @@
 static part_t *bd_srv_part(bd_srv_t *bd)
 {
-	return (part_t *)bd->arg;
+	return (part_t *)bd->srvs->sarg;
 }
 
@@ -325,7 +325,7 @@
 	}
 
-	bd_srv_init(&part->bd);
-	part->bd.ops = &gpt_bd_ops;
-	part->bd.arg = part;
+	bd_srvs_init(&part->bds);
+	part->bds.ops = &gpt_bd_ops;
+	part->bds.sarg = part;
 
 	part->dsid = 0;
@@ -357,9 +357,9 @@
 	assert(part->present == true);
 
-	bd_conn(iid, icall, &part->bd);
+	bd_conn(iid, icall, &part->bds);
 }
 
 /** Open device. */
-static int gpt_bd_open(bd_srv_t *bd)
+static int gpt_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/srv/bd/part/mbr_part/mbr_part.c
===================================================================
--- uspace/srv/bd/part/mbr_part/mbr_part.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/part/mbr_part/mbr_part.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -100,6 +100,6 @@
 	/** Device representing the partition (outbound device) */
 	service_id_t dsid;
-	/** Block device server structure */
-	bd_srv_t bd;
+	/** Block device service sturcture */
+	bd_srvs_t bds;
 	/** Points to next partition structure. */
 	struct part *next;
@@ -154,5 +154,5 @@
 static int mbr_bsa_translate(part_t *p, uint64_t ba, size_t cnt, uint64_t *gba);
 
-static int mbr_bd_open(bd_srv_t *);
+static int mbr_bd_open(bd_srvs_t *, bd_srv_t *);
 static int mbr_bd_close(bd_srv_t *);
 static int mbr_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -172,5 +172,5 @@
 static part_t *bd_srv_part(bd_srv_t *bd)
 {
-	return (part_t *)bd->arg;
+	return (part_t *)bd->srvs->sarg;
 }
 
@@ -402,7 +402,7 @@
 	part->present = (pte->ptype != PT_UNUSED) ? true : false;
 
-	bd_srv_init(&part->bd);
-	part->bd.ops = &mbr_bd_ops;
-	part->bd.arg = part;
+	bd_srvs_init(&part->bds);
+	part->bds.ops = &mbr_bd_ops;
+	part->bds.sarg = part;
 
 	part->dsid = 0;
@@ -433,9 +433,9 @@
 
 	assert(part->present == true);
-	bd_conn(iid, icall, &part->bd);
+	bd_conn(iid, icall, &part->bds);
 }
 
 /** Open device. */
-static int mbr_bd_open(bd_srv_t *bd)
+static int mbr_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/srv/bd/rd/rd.c
===================================================================
--- uspace/srv/bd/rd/rd.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/rd/rd.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -68,5 +68,5 @@
 static const size_t block_size = 512;
 
-static int rd_open(bd_srv_t *);
+static int rd_open(bd_srvs_t *, bd_srv_t *);
 static int rd_close(bd_srv_t *);
 static int rd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -93,13 +93,13 @@
 };
 
-static bd_srv_t bd_srv;
+static bd_srvs_t bd_srvs;
 
 static void rd_client_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
 {
-	bd_conn(iid, icall, &bd_srv);
+	bd_conn(iid, icall, &bd_srvs);
 }
 
 /** Open device. */
-static int rd_open(bd_srv_t *bd)
+static int rd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
@@ -175,6 +175,6 @@
 	    (void *) addr_phys, size);
 	
-	bd_srv_init(&bd_srv);
-	bd_srv.ops = &rd_bd_ops;
+	bd_srvs_init(&bd_srvs);
+	bd_srvs.ops = &rd_bd_ops;
 	
 	async_set_client_connection(rd_client_conn);
Index: uspace/srv/bd/sata_bd/sata_bd.c
===================================================================
--- uspace/srv/bd/sata_bd/sata_bd.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/sata_bd/sata_bd.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -57,5 +57,5 @@
 static int disk_count;
 
-static int sata_bd_open(bd_srv_t *);
+static int sata_bd_open(bd_srvs_t *, bd_srv_t *);
 static int sata_bd_close(bd_srv_t *);
 static int sata_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
@@ -75,5 +75,5 @@
 static sata_bd_dev_t *bd_srv_sata(bd_srv_t *bd)
 {
-	return (sata_bd_dev_t *)bd->arg;
+	return (sata_bd_dev_t *)bd->srvs->sarg;
 }
 
@@ -104,7 +104,7 @@
 		ahci_get_num_blocks(disk[disk_count].sess, &disk[disk_count].blocks);
 		
-		bd_srv_init(&disk[disk_count].bd);
-		disk[disk_count].bd.ops = &sata_bd_ops;
-		disk[disk_count].bd.arg = &disk[disk_count];
+		bd_srvs_init(&disk[disk_count].bds);
+		disk[disk_count].bds.ops = &sata_bd_ops;
+		disk[disk_count].bds.sarg = &disk[disk_count];
 		
 		printf("Device %s - %s , blocks: %lu, block_size: %lu\n", 
@@ -183,9 +183,9 @@
 	}
 
-	bd_conn(iid, icall, &disk[disk_id].bd);
+	bd_conn(iid, icall, &disk[disk_id].bds);
 }
 
 /** Open device. */
-static int sata_bd_open(bd_srv_t *bd)
+static int sata_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
 {
 	return EOK;
Index: uspace/srv/bd/sata_bd/sata_bd.h
===================================================================
--- uspace/srv/bd/sata_bd/sata_bd.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/bd/sata_bd/sata_bd.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -58,5 +58,5 @@
 	size_t block_size;
 	/** Block device server structure */
-	bd_srv_t bd;
+	bd_srvs_t bds;
 } sata_bd_dev_t;
 
Index: uspace/srv/hid/console/console.c
===================================================================
--- uspace/srv/hid/console/console.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/hid/console/console.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -383,4 +383,15 @@
 	
 	fb_pointer_update(fb_sess, mouse.x, mouse.y, true);
+}
+
+static void cons_mouse_abs_move(sysarg_t x, sysarg_t y,
+    sysarg_t max_x, sysarg_t max_y)
+{
+	if (max_x && max_y) {
+		mouse.x = limit(x * xres / max_x, 0, xres);
+		mouse.y = limit(y * yres / max_y, 0, yres);
+		
+		fb_pointer_update(fb_sess, mouse.x, mouse.y, true);
+	}
 }
 
@@ -503,4 +514,9 @@
 			async_answer_0(callid, EOK);
 			break;
+		case INPUT_EVENT_ABS_MOVE:
+			cons_mouse_abs_move(IPC_GET_ARG1(call), IPC_GET_ARG2(call),
+			    IPC_GET_ARG3(call), IPC_GET_ARG4(call));
+			async_answer_0(callid, EOK);
+			break;
 		case INPUT_EVENT_BUTTON:
 			/* Got pointer button press/release event */
Index: uspace/srv/hid/input/generic/input.c
===================================================================
--- uspace/srv/hid/input/generic/input.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/hid/input/generic/input.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -189,4 +189,15 @@
 	}
 	async_exchange_end(exch);
+}
+
+/** Mouse pointer has moved in absolute mode. */
+void mouse_push_event_abs_move(mouse_dev_t *mdev, unsigned int x, unsigned int y,
+    unsigned int max_x, unsigned int max_y)
+{
+	if (max_x && max_y) {
+		async_exch_t *exch = async_exchange_begin(client_sess);
+		async_msg_4(exch, INPUT_EVENT_ABS_MOVE, x, y, max_x, max_y);
+		async_exchange_end(exch);
+	}
 }
 
Index: uspace/srv/hid/input/include/mouse.h
===================================================================
--- uspace/srv/hid/input/include/mouse.h	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/hid/input/include/mouse.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -63,4 +63,6 @@
 extern void mouse_push_data(mouse_dev_t *, sysarg_t);
 extern void mouse_push_event_move(mouse_dev_t *, int, int, int);
+extern void mouse_push_event_abs_move(mouse_dev_t *, unsigned int, unsigned int,
+    unsigned int, unsigned int);
 extern void mouse_push_event_button(mouse_dev_t *, int, int);
 
Index: uspace/srv/hid/input/proto/mousedev.c
===================================================================
--- uspace/srv/hid/input/proto/mousedev.c	(revision b52dd1de1cebdd2d4803f43c0f81f89a172198c2)
+++ uspace/srv/hid/input/proto/mousedev.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -96,4 +96,10 @@
 			retval = EOK;
 			break;
+		case MOUSEEV_ABS_MOVE_EVENT:
+			mouse_push_event_abs_move(mousedev->mouse_dev,
+				IPC_GET_ARG1(call), IPC_GET_ARG2(call),
+				IPC_GET_ARG3(call), IPC_GET_ARG4(call));
+			retval = EOK;
+			break;
 		case MOUSEEV_BUTTON_EVENT:
 			mouse_push_event_button(mousedev->mouse_dev,
Index: uspace/srv/hid/isdv4_tablet/Makefile
===================================================================
--- uspace/srv/hid/isdv4_tablet/Makefile	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
+++ uspace/srv/hid/isdv4_tablet/Makefile	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2012 Martin Sucha
+# 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 = ../../..
+#LIBS = 
+#EXTRA_CFLAGS = 
+BINARY = isdv4_tablet
+
+SOURCES = \
+	main.c \
+	isdv4.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/hid/isdv4_tablet/isdv4.c
===================================================================
--- uspace/srv/hid/isdv4_tablet/isdv4.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
+++ uspace/srv/hid/isdv4_tablet/isdv4.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * 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 <device/char_dev.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <mem.h>
+
+#include "isdv4.h"
+
+#define BUF_SIZE 64
+
+#define START_OF_PACKET 128
+#define CONTROL_PACKET 64
+#define TOUCH_EVENT 16
+#define FINGER1 1
+#define FINGER2 2
+#define TIP 1
+#define BUTTON1 2
+#define BUTTON2 4
+#define PROXIMITY 32
+
+#define CMD_START '1'
+#define CMD_STOP '0'
+#define CMD_QUERY_STYLUS '*'
+#define CMD_QUERY_TOUCH '%'
+
+/* packet_consumer_fn(uint8_t *packet, size_t size, isdv4_state_t *state,
+   void *data)
+   return true if reading of packets should continue */
+typedef bool (*packet_consumer_fn)(uint8_t *, size_t, isdv4_state_t *);
+
+static void isdv4_event_init(isdv4_event_t *event)
+{
+	memset(event, 0, sizeof(isdv4_event_t));
+}
+
+/**
+ * Parse event packet and emit events
+ * @return true if reading of packets should continue
+ */
+static bool parse_event(uint8_t *packet, size_t size, isdv4_state_t *state)
+{
+	if (size < 1)
+		return false;
+
+	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
+	if (control_packet)
+		return true;
+
+	/* This is an event initiated by the device */
+	isdv4_event_t event;
+	isdv4_event_init(&event);
+
+	if (packet[0] & TOUCH_EVENT) {
+		if (size != 5)
+			return true;
+
+		/* This is a touch event */
+		bool finger1 = (packet[0] & FINGER1) > 0;
+		event.x = ((packet[1] & 127) << 7) | (packet[2] & 127);
+		event.y = ((packet[3] & 127) << 7) | (packet[4] & 127);
+		event.source = TOUCH;
+
+		if (!state->stylus_in_proximity) {
+			if (!finger1 && state->finger1_pressed) {
+				state->finger1_pressed = false;
+
+				event.type = RELEASE;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			else if (finger1 && !state->finger1_pressed) {
+				state->finger1_pressed = true;
+
+				event.type = PRESS;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			else {
+				event.type = MOVE;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+		}
+	}
+	else {
+		if (size != 9)
+			return true;
+
+		/* This is a stylus event */
+		bool tip = packet[0] & TIP;
+		bool button1 = packet[0] & BUTTON1;
+		bool button2 = packet[0] & BUTTON2;
+		bool proximity = packet[0] & PROXIMITY;
+		event.x = ((packet[1] & 127) << 7) | (packet[2] & 124) | ((packet[6] >> 5) & 3);
+		event.y = ((packet[3] & 127) << 7) | (packet[4] & 124) | ((packet[6] >> 3) & 3);
+		event.pressure = (packet[5] & 127) | ((packet[6] & 7) << 7);
+
+		if (proximity && !state->stylus_in_proximity) {
+			/* Stylus came into proximity */
+			state->stylus_in_proximity = true;
+			state->stylus_is_eraser = !tip && button2;
+			event.source = (state->stylus_is_eraser ? STYLUS_ERASER : STYLUS_TIP);
+			event.type = PROXIMITY_IN;
+			state->emit_event_fn(&event);
+		}
+		else if (!proximity && state->stylus_in_proximity) {
+			/* Stylus came out of proximity */
+			state->stylus_in_proximity = false;
+			event.source = (state->stylus_is_eraser ? STYLUS_ERASER : STYLUS_TIP);
+			event.type = PROXIMITY_OUT;
+			state->emit_event_fn(&event);
+		}
+		else {
+			/* Proximity state didn't change, but we need to check if it is still eraser */
+			if (state->stylus_is_eraser && !button2) {
+				event.type = PROXIMITY_OUT;
+				event.source = STYLUS_ERASER;
+				state->emit_event_fn(&event);
+				event.type = PROXIMITY_IN;
+				event.source = STYLUS_TIP;
+				state->emit_event_fn(&event);
+				state->stylus_is_eraser = false;
+			}
+			else if (!state->stylus_is_eraser && !tip && button2) {
+				event.type = PROXIMITY_OUT;
+				event.source = STYLUS_TIP;
+				state->emit_event_fn(&event);
+				event.type = PROXIMITY_IN;
+				event.source = STYLUS_ERASER;
+				state->emit_event_fn(&event);
+				state->stylus_is_eraser = true;
+			}
+		}
+
+		if (!state->stylus_is_eraser) {
+			if (tip && !state->tip_pressed) {
+				state->tip_pressed = true;
+				event.type = PRESS;
+				event.source = STYLUS_TIP;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			else if (!tip && state->tip_pressed) {
+				state->tip_pressed = false;
+				event.type = RELEASE;
+				event.source = STYLUS_TIP;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			if (button1 && !state->button1_pressed) {
+				state->button1_pressed = true;
+				event.type = PRESS;
+				event.source = STYLUS_TIP;
+				event.button = 2;
+				state->emit_event_fn(&event);
+			}
+			else if (!button1 && state->button1_pressed) {
+				state->button1_pressed = false;
+				event.type = RELEASE;
+				event.source = STYLUS_TIP;
+				event.button = 2;
+				state->emit_event_fn(&event);
+			}
+			if (button2 && !state->button2_pressed) {
+				state->button2_pressed = true;
+				event.type = PRESS;
+				event.source = STYLUS_TIP;
+				event.button = 3;
+				state->emit_event_fn(&event);
+			}
+			else if (!button2 && state->button2_pressed) {
+				state->button2_pressed = false;
+				event.type = RELEASE;
+				event.source = STYLUS_TIP;
+				event.button = 3;
+				state->emit_event_fn(&event);
+			}
+			event.type = MOVE;
+			event.source = STYLUS_TIP;
+			event.button = 0;
+			state->emit_event_fn(&event);
+		}
+		else {
+			if (tip && !state->tip_pressed) {
+				state->tip_pressed = true;
+				event.type = PRESS;
+				event.source = STYLUS_ERASER;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			else if (!tip && state->tip_pressed) {
+				state->tip_pressed = false;
+				event.type = RELEASE;
+				event.source = STYLUS_ERASER;
+				event.button = 1;
+				state->emit_event_fn(&event);
+			}
+			event.type = MOVE;
+			event.source = STYLUS_ERASER;
+			event.button = 0;
+			state->emit_event_fn(&event);
+		}
+	}
+
+	return true;
+}
+
+static bool parse_response_stylus(uint8_t *packet, size_t size,
+    isdv4_state_t *state)
+{
+	if (size < 1)
+		return false;
+
+	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
+	if (!control_packet)
+		return true;
+
+	if (size != 11)
+		return false;
+
+	state->stylus_max_x = ((packet[1] & 127) << 7) | (packet[2] & 124) |
+	    ((packet[6] >> 5) & 3);
+	state->stylus_max_y = ((packet[3] & 127) << 7) | (packet[4] & 124) |
+	    ((packet[6] >> 3) & 3);
+	state->stylus_max_pressure = (packet[5] & 63) | ((packet[6] & 7) << 7);
+	state->stylus_max_xtilt = packet[8] & 127;
+	state->stylus_max_ytilt = packet[7] & 127;
+	state->stylus_tilt_supported = (state->stylus_max_xtilt &&
+	    state->stylus_max_ytilt);
+
+	return false;
+}
+
+static bool parse_response_touch(uint8_t *packet, size_t size,
+    isdv4_state_t *state)
+{
+	if (size < 1)
+		return false;
+
+	bool control_packet = ((packet[0] & CONTROL_PACKET) > 0);
+	if (!control_packet)
+		return true;
+
+	if (size != 11)
+		return false;
+
+	state->touch_type = (packet[0] & 63);
+
+	unsigned int touch_resolution = packet[1] & 127;
+	state->touch_max_x = ((packet[2] >> 5) & 3) | ((packet[3] & 127) << 7) |
+	    (packet[4] & 124);
+	state->touch_max_y = ((packet[2] >> 3) & 3) | ((packet[5] & 127) << 7) |
+	    (packet[6] & 124);
+	
+	if (touch_resolution == 0)
+		touch_resolution = 10;
+
+	if (state->touch_max_x == 0 || state->touch_max_y == 0) {
+		state->touch_max_x = (1 << touch_resolution);
+		state->touch_max_y = (1 << touch_resolution);
+	}
+
+	return false;
+}
+
+static int read_packets(isdv4_state_t *state, packet_consumer_fn consumer)
+{
+	bool reading = true;
+	while (reading) {
+		ssize_t read = char_dev_read(state->sess, state->buf + state->buf_end,
+		    state->buf_size - state->buf_end);
+		if (read < 0)
+			return EIO;
+		state->buf_end += read;
+
+		size_t i = 0;
+
+		/* Skip data until a start of packet is found */
+		while (i < state->buf_end && (state->buf[i] & START_OF_PACKET) == 0) i++;
+
+		size_t start = i;
+		size_t end = i;
+		size_t processed_end = i;
+
+		/* Process packets one by one */
+		while (reading && i < state->buf_end) {
+			/* Determine the packet length */
+			size_t packet_remaining;
+			if (state->buf[i] & CONTROL_PACKET) {
+				packet_remaining = 11;
+			}
+			else if (state->buf[i] & TOUCH_EVENT) {
+				packet_remaining = 5;
+			}
+			else {
+				packet_remaining = 9;
+			}
+
+			/* Find the end of the packet */
+			i++; /* We need to skip the first byte with START_OF_PACKET set */
+			packet_remaining--; 
+			while (packet_remaining > 0 && i < state->buf_end &&
+			    (state->buf[i] & START_OF_PACKET) == 0) {
+				i++;
+				packet_remaining--;
+			}
+			end = i;
+
+			/* If we have whole packet, process it */
+			if (end > start && packet_remaining == 0) {
+				reading = consumer(state->buf + start, end - start, state);
+				start = end;
+				processed_end = end;
+			}
+		}
+
+		if (processed_end == 0 && state->buf_end == state->buf_size) {
+			/* Packet too large, throw it away */
+			state->buf_end = 0;
+		}
+
+		/* Shift the buffer contents to the left */
+		size_t unprocessed_len = state->buf_end - processed_end;
+		memcpy(state->buf, state->buf + processed_end, unprocessed_len);
+		state->buf_end = unprocessed_len;
+	}
+	return EOK;
+}
+static bool write_command(async_sess_t *sess, uint8_t command)
+{
+	return char_dev_write(sess, &command, 1) == 1;
+}
+
+int isdv4_init(isdv4_state_t *state, async_sess_t *sess,
+    isdv4_event_fn event_fn)
+{
+	memset(state, 0, sizeof(isdv4_state_t));
+	state->sess = sess;
+	state->buf = malloc(BUF_SIZE);
+	if (state->buf == NULL)
+		return ENOMEM;
+	state->buf_size = BUF_SIZE;
+	state->emit_event_fn = event_fn;
+	return EOK;
+}
+
+int isdv4_init_tablet(isdv4_state_t *state)
+{
+	if (!write_command(state->sess, CMD_STOP))
+		return EIO;
+
+	usleep(250000); /* 250 ms */
+
+	// FIXME: Read all possible garbage before sending commands
+	if (!write_command(state->sess, CMD_QUERY_STYLUS))
+		return EIO;
+
+	int rc = read_packets(state, parse_response_stylus);
+	if (rc != EOK)
+		return rc;
+
+	if (!write_command(state->sess, CMD_QUERY_TOUCH))
+		return EIO;
+
+	rc = read_packets(state, parse_response_touch);
+	if (rc != EOK)
+		return rc;
+
+	if (!write_command(state->sess, CMD_START))
+		return EIO;
+
+	return EOK;
+}
+
+int isdv4_read_events(isdv4_state_t *state)
+{
+	return read_packets(state, parse_event);
+}
+
+void isdv4_fini(isdv4_state_t *state)
+{
+	free(state->buf);
+}
Index: uspace/srv/hid/isdv4_tablet/isdv4.h
===================================================================
--- uspace/srv/hid/isdv4_tablet/isdv4.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
+++ uspace/srv/hid/isdv4_tablet/isdv4.h	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * 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.
+ */
+
+#ifndef __ISDV4_H__
+#define __ISDV4_H__
+
+typedef struct isdv4_event isdv4_event_t;
+
+typedef void (*isdv4_event_fn)(const isdv4_event_t *);
+
+typedef struct {
+	/* Stylus information */
+	unsigned int stylus_max_x;
+	unsigned int stylus_max_y;
+	unsigned int stylus_max_pressure;
+	unsigned int stylus_max_xtilt;
+	unsigned int stylus_max_ytilt;
+	bool stylus_tilt_supported;
+
+	/* Touch information */
+	unsigned int touch_type;
+	unsigned int touch_max_x;
+	unsigned int touch_max_y;
+
+	/* Event state */
+	bool stylus_in_proximity;
+	bool stylus_is_eraser;
+	bool tip_pressed; /* Reported as stylus button 1 */
+	bool button1_pressed; /* Reported as stylus button 2 */
+	bool button2_pressed; /* Reported as stylus button 3 */
+	bool finger1_pressed; /* Reported as touch button 1 */
+
+	/* Session to the serial device */
+	async_sess_t *sess;
+
+	/* Receive buffer state */
+	uint8_t *buf;
+	size_t buf_size;
+	size_t buf_end;
+
+	/* Callbacks */
+	isdv4_event_fn emit_event_fn;
+} isdv4_state_t;
+
+typedef enum {
+	UNKNOWN, PRESS, RELEASE, PROXIMITY_IN, PROXIMITY_OUT, MOVE
+} isdv4_event_type_t;
+
+typedef enum {
+	STYLUS_TIP, STYLUS_ERASER, TOUCH
+} isdv4_source_type_t;
+
+typedef struct isdv4_event {
+	isdv4_event_type_t type;
+	isdv4_source_type_t source;
+	unsigned int x;
+	unsigned int y;
+	unsigned int pressure;
+	unsigned int button;
+} isdv4_event_t;
+
+extern int isdv4_init(isdv4_state_t *, async_sess_t *, isdv4_event_fn);
+extern int isdv4_init_tablet(isdv4_state_t *);
+extern int isdv4_read_events(isdv4_state_t *state);
+extern void isdv4_fini(isdv4_state_t *);
+
+#endif
Index: uspace/srv/hid/isdv4_tablet/main.c
===================================================================
--- uspace/srv/hid/isdv4_tablet/main.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
+++ uspace/srv/hid/isdv4_tablet/main.c	(revision 5882487f18f36ec6b1081462e7a23b41498904f5)
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2012 Martin Sucha
+ * 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 <device/char_dev.h>
+#include <errno.h>
+#include <ipc/serial_ctl.h>
+#include <loc.h>
+#include <stdio.h>
+#include <fibril_synch.h>
+#include <abi/ipc/methods.h>
+#include <ipc/mouseev.h>
+#include <inttypes.h>
+
+#include "isdv4.h"
+
+#define NAME "isdv4_tablet"
+
+static async_sess_t *client_sess = NULL;
+static fibril_mutex_t client_mutex;
+static isdv4_state_t state;
+
+static void syntax_print(void)
+{
+	fprintf(stderr, "Usage: %s [--baud=<baud>] [--print-events] [device_service]\n", NAME);
+}
+
+static int read_fibril(void *unused)
+{
+	int rc = isdv4_read_events(&state);
+	if (rc != EOK) {
+		fprintf(stderr, "Failed reading events");
+		return rc;
+	}
+
+	isdv4_fini(&state);
+	return EOK;
+}
+
+static void mouse_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	async_answer_0(iid, EOK);
+	
+	async_sess_t *sess =
+	    async_callback_receive(EXCHANGE_SERIALIZE);
+	fibril_mutex_lock(&client_mutex);
+		if (client_sess == NULL) {
+			client_sess = sess;
+		}
+	fibril_mutex_unlock(&client_mutex);
+
+	while (true) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		
+		if (!IPC_GET_IMETHOD(call))
+			break;
+		
+		async_answer_0(callid, ENOTSUP);
+	}
+}
+
+static void emit_event(const isdv4_event_t *event)
+{
+	fibril_mutex_lock(&client_mutex);
+	async_sess_t *sess = client_sess;
+	fibril_mutex_unlock(&client_mutex);
+	
+	if (!sess) return;
+	
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (exch) {
+		unsigned int max_x = state.stylus_max_x;
+		unsigned int max_y = state.stylus_max_y;
+		if (event->source == TOUCH) {
+			max_x = state.touch_max_x;
+			max_y = state.touch_max_y;
+		}
+		async_msg_4(exch, MOUSEEV_ABS_MOVE_EVENT, event->x, event->y,
+				    max_x, max_y);
+		if (event->type == PRESS || event->type == RELEASE) {
+			async_msg_2(exch, MOUSEEV_BUTTON_EVENT, event->button,
+				    event->type == PRESS);
+		}
+	}
+	async_exchange_end(exch);
+}
+
+static void print_and_emit_event(const isdv4_event_t *event)
+{
+	const char *type = NULL;
+	switch (event->type) {
+		case PRESS:
+			type = "PRESS";
+			break;
+		case RELEASE:
+			type = "RELEASE";
+			break;
+		case PROXIMITY_IN:
+			type = "PROXIMITY IN";
+			break;
+		case PROXIMITY_OUT:
+			type = "PROXIMITY OUT";
+			break;
+		case MOVE:
+			type = "MOVE";
+			break;
+		default:
+			type = "UNKNOWN";
+			break;
+	}
+
+	const char *source = NULL;
+	switch (event->source) {
+		case STYLUS_TIP:
+			source = "stylus tip";
+			break;
+		case STYLUS_ERASER:
+			source = "stylus eraser";
+			break;
+		case TOUCH:
+			source = "touch";
+			break;
+	}
+
+	printf("%s %s %u %u %u %u\n", type, source, event->x, event->y,
+	    event->pressure, event->button);
+	
+	emit_event(event);
+}
+
+static const char *touch_type(unsigned int data_id)
+{
+	switch (data_id) {
+		case 0:
+			return "resistive+stylus";
+		case 1:
+			return "capacitive+stylus";
+		case 2:
+			return "resistive";
+		case 3:
+		case 4:
+			return "capacitive";
+		case 5:
+			return "penabled";
+	}
+	return "unknown";
+}
+
+int main(int argc, char **argv)
+{
+	sysarg_t baud = 38400;
+	service_id_t svc_id;
+	char *serial_port_name = NULL;
+
+	int arg = 1;
+	int rc;
+
+	isdv4_event_fn event_fn = emit_event;
+
+	if (argc > arg && str_test_prefix(argv[arg], "--baud=")) {
+		size_t arg_offset = str_lsize(argv[arg], 7);
+		char* arg_str = argv[arg] + arg_offset;
+		if (str_length(arg_str) == 0) {
+			fprintf(stderr, "--baud requires an argument\n");
+			syntax_print();
+			return 1;
+		}
+		char *endptr;
+		baud = strtol(arg_str, &endptr, 10);
+		if (*endptr != '\0') {
+			fprintf(stderr, "Invalid value for baud\n");
+			syntax_print();
+			return 1;
+		}
+		arg++;
+	}
+
+	if (argc > arg && str_cmp(argv[arg], "--print-events") == 0) {
+		event_fn = print_and_emit_event;
+		arg++;
+	}
+
+	if (argc > arg) {
+		serial_port_name = argv[arg];
+		rc = loc_service_get_id(serial_port_name, &svc_id, 0);
+		if (rc != EOK) {
+			fprintf(stderr, "Cannot find device service %s\n",
+			    argv[arg]);
+			return 1;
+		}
+		arg++;
+	}
+	else {
+		category_id_t serial_cat_id;
+
+		rc = loc_category_get_id("serial", &serial_cat_id, 0);
+		if (rc != EOK) {
+			fprintf(stderr, "Failed getting id of category "
+			    "'serial'\n");
+			return 1;
+		}
+
+		service_id_t *svc_ids;
+		size_t svc_count;
+
+		rc = loc_category_get_svcs(serial_cat_id, &svc_ids, &svc_count);		if (rc != EOK) {
+			fprintf(stderr, "Failed getting list of services\n");
+			return 1;
+		}
+
+		if (svc_count == 0) {
+			fprintf(stderr, "No service in category 'serial'\n");
+			free(svc_ids);
+			return 1;
+		}
+
+		svc_id = svc_ids[0];
+
+		rc = loc_service_get_name(svc_id, &serial_port_name);
+		if (rc != EOK) {
+			fprintf(stderr, "Failed getting name of serial service\n");
+			return 1;
+		}
+
+		free(svc_ids);
+	}
+
+	if (argc > arg) {
+		fprintf(stderr, "Too many arguments\n");
+		syntax_print();
+		return 1;
+	}
+
+	fibril_mutex_initialize(&client_mutex);
+
+	printf(NAME ": Using serial port %s\n", serial_port_name);
+
+	async_sess_t *sess = loc_service_connect(EXCHANGE_SERIALIZE, svc_id,
+	    IPC_FLAG_BLOCKING);
+	if (!sess) {
+		fprintf(stderr, "Failed connecting to service\n");
+	}
+
+	async_exch_t *exch = async_exchange_begin(sess);
+	rc = async_req_4_0(exch, SERIAL_SET_COM_PROPS, baud,
+	    SERIAL_NO_PARITY, 8, 1);
+	async_exchange_end(exch);
+
+	if (rc != EOK) {
+		fprintf(stderr, "Failed setting serial properties\n");
+		return 2;
+	}
+
+	rc = isdv4_init(&state, sess, event_fn);
+	if (rc != EOK) {
+		fprintf(stderr, "Failed initializing isdv4 state");
+		return 2;
+	}
+
+	rc = isdv4_init_tablet(&state);
+	if (rc != EOK) {
+		fprintf(stderr, "Failed initializing tablet");
+		return 2;
+	}
+
+	printf("Tablet information:\n");
+	printf(" Stylus: %ux%u pressure: %u tilt: ", state.stylus_max_x,
+	    state.stylus_max_y, state.stylus_max_pressure);
+	if (state.stylus_tilt_supported) {
+		printf("%ux%u\n", state.stylus_max_xtilt, state.stylus_max_ytilt);
+	}
+	else {
+		printf("not supported\n");
+	}
+	printf(" Touch: %ux%u type: %s\n", state.touch_max_x, state.touch_max_y,
+		touch_type(state.touch_type));
+	
+	fid_t fibril = fibril_create(read_fibril, NULL);
+	/* From this on, state is to be used only by read_fibril */
+	fibril_add_ready(fibril);
+
+	async_set_client_connection(mouse_connection);
+	rc = loc_server_register(NAME);
+	if (rc != EOK) {
+		printf("%s: Unable to register driver.\n", NAME);
+		return rc;
+	}
+
+	service_id_t service_id;
+	char *service_name;
+	rc = asprintf(&service_name, "mouse/isdv4-%" PRIun, svc_id);
+	if (rc < 0) {
+		printf(NAME ": Unable to create service name\n");
+		return rc;
+	}
+
+	rc = loc_service_register(service_name, &service_id);
+	if (rc != EOK) {
+		printf(NAME ": Unable to register service %s.\n", service_name);
+		return rc;
+	}
+
+	category_id_t mouse_category;
+	rc = loc_category_get_id("mouse", &mouse_category, IPC_FLAG_BLOCKING);
+	if (rc != EOK) {
+		printf(NAME ": Unable to get mouse category id.\n");
+	}
+	else {
+		rc = loc_service_add_to_cat(service_id, mouse_category);
+		if (rc != EOK) {
+			printf(NAME ": Unable to add device to mouse category.\n");
+		}
+	}
+
+	printf("%s: Accepting connections\n", NAME);
+	task_retval(0);
+	async_manager();
+
+	/* Not reached */
+	return 0;
+}
