Index: uspace/app/virtusbkbd/stdreq.c
===================================================================
--- uspace/app/virtusbkbd/stdreq.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/app/virtusbkbd/stdreq.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -65,5 +65,5 @@
 		 * report descriptor.
 		 */
-		int rc = dev->send_data(dev, 0,
+		int rc = dev->control_transfer_reply(dev, 0,
 		    report_descriptor, report_descriptor_size);
 		
Index: uspace/app/virtusbkbd/virtusbkbd.c
===================================================================
--- uspace/app/virtusbkbd/virtusbkbd.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/app/virtusbkbd/virtusbkbd.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -67,4 +67,6 @@
 	(printf("%s: %s" fmt "\n", NAME, _QUOTEME(cmd), __VA_ARGS__), cmd(__VA_ARGS__))
 
+kb_status_t status;
+
 static int on_incoming_data(struct usbvirt_device *dev,
     usb_endpoint_t endpoint, void *buffer, size_t size)
@@ -83,4 +85,28 @@
 }
 
+static int on_request_for_data(struct usbvirt_device *dev,
+    usb_endpoint_t endpoint, void *buffer, size_t size, size_t *actual_size)
+{
+	if (size < 2 + KB_MAX_KEYS_AT_ONCE) {
+		return EINVAL;
+	}
+	
+	*actual_size = 2 + KB_MAX_KEYS_AT_ONCE;
+	
+	uint8_t data[2 + KB_MAX_KEYS_AT_ONCE];
+	data[0] = status.modifiers;
+	data[1] = 0;
+	
+	size_t i;
+	for (i = 0; i < KB_MAX_KEYS_AT_ONCE; i++) {
+		data[2 + i] = status.pressed_keys[i];
+	}
+	
+	memcpy(buffer, &data, *actual_size);
+	
+	return EOK;
+}
+
+
 /** Keyboard callbacks.
  * We abuse the fact that static variables are zero-filled.
@@ -89,5 +115,6 @@
 	.standard_request_ops = &standard_request_ops,
 	.on_class_device_request = on_class_request,
-	.on_data = on_incoming_data
+	.on_data = on_incoming_data,
+	.on_data_request = on_request_for_data
 };
 
@@ -151,15 +178,4 @@
 	}
 	printf("\n");
-	
-	uint8_t data[3 + KB_MAX_KEYS_AT_ONCE];
-	data[0] = status->modifiers;
-	data[1] = 0;
-	data[2] = 0;
-	for (i = 0; i < KB_MAX_KEYS_AT_ONCE; i++) {
-		data[3 + i] = status->pressed_keys[i];
-	}
-	
-	int rc = keyboard_dev.send_data(&keyboard_dev, 0, data, sizeof(data));
-	printf("%s:   Sent to VHCD (%s).\n", NAME, str_error(rc));
 	
 	fibril_sleep(1);
@@ -198,4 +214,6 @@
 	}
 	
+	kb_init(&status);
+	
 	
 	int rc = usbvirt_connect(&keyboard_dev, DEV_HCD_NAME);
@@ -206,7 +224,4 @@
 	}
 	
-	kb_status_t status;
-	kb_init(&status);
-	
 	printf("%s: Simulating keyboard events...\n", NAME);
 	kb_process_events(&status, keyboard_events, keyboard_events_count,
Index: uspace/lib/usb/usb.h
===================================================================
--- uspace/lib/usb/usb.h	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usb/usb.h	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -64,4 +64,9 @@
 typedef int usb_endpoint_t;
 
+/** Maximum endpoint number in USB 1.1.
+ */
+#define USB11_ENDPOINT_MAX 16
+
+
 /** USB complete address type. 
  * Pair address + endpoint is identification of transaction recipient.
Index: uspace/lib/usbvirt/Makefile
===================================================================
--- uspace/lib/usbvirt/Makefile	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/Makefile	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -35,7 +35,7 @@
 SOURCES = \
 	ctrlpipe.c \
-	incoming.c \
 	main.c \
-	stdreq.c
+	stdreq.c \
+	transaction.c
 
 include $(USPACE_PREFIX)/Makefile.common
Index: uspace/lib/usbvirt/ctrlpipe.c
===================================================================
--- uspace/lib/usbvirt/ctrlpipe.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/ctrlpipe.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -53,13 +53,12 @@
 
 
-
-int control_pipe(void *buffer, size_t size)
+int control_pipe(usbvirt_control_transfer_t *transfer)
 {
-	if (size < sizeof(usb_device_request_setup_packet_t)) {
+	if (transfer->request_size < sizeof(usb_device_request_setup_packet_t)) {
 		return ENOMEM;
 	}
 	
-	usb_device_request_setup_packet_t *request = (usb_device_request_setup_packet_t *) buffer;
-	uint8_t *remaining_data = ((uint8_t *) request) + sizeof(usb_device_request_setup_packet_t);
+	usb_device_request_setup_packet_t *request = (usb_device_request_setup_packet_t *) transfer->request;
+	uint8_t *remaining_data = transfer->data;
 	
 	int type = request_get_type(request->request_type);
@@ -80,6 +79,4 @@
 			break;
 	}
-	
-	device->send_data(device, 0, NULL, 0);
 	
 	if (dev_new_address != -1) {
Index: uspace/lib/usbvirt/device.h
===================================================================
--- uspace/lib/usbvirt/device.h	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/device.h	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -41,4 +41,5 @@
 
 struct usbvirt_device;
+struct usbvirt_control_transfer;
 
 typedef int (*usbvirt_on_device_request_t)(struct usbvirt_device *dev,
@@ -70,6 +71,18 @@
 	/** Callback for class-specific USB request. */
 	usbvirt_on_device_request_t on_class_device_request;
+	
+	int (*on_control_transfer)(struct usbvirt_device *dev,
+	    usb_endpoint_t endpoint, struct usbvirt_control_transfer *transfer);
+	
 	/** Callback for all other incoming data. */
 	int (*on_data)(struct usbvirt_device *dev,
+	    usb_endpoint_t endpoint, void *buffer, size_t size);
+	
+	/** Callback for host request for data. */
+	int (*on_data_request)(struct usbvirt_device *dev,
+	    usb_endpoint_t endpoint, void *buffer, size_t size, size_t *actual_size);
+	
+	/** Decides direction of control transfer. */
+	usb_direction_t (*decide_control_transfer_direction)(
 	    usb_endpoint_t endpoint, void *buffer, size_t size);
 } usbvirt_device_ops_t;
@@ -117,4 +130,14 @@
 } usbvirt_device_state_t;
 
+/** Information about on-going control transfer.
+ */
+typedef struct usbvirt_control_transfer {
+	usb_direction_t direction;
+	void *request;
+	size_t request_size;
+	void *data;
+	size_t data_size;
+} usbvirt_control_transfer_t;
+
 /** Virtual USB device. */
 typedef struct usbvirt_device {
@@ -122,10 +145,8 @@
 	usbvirt_device_ops_t *ops;
 	
-	/** Send data to HC.
-	 * @warning Do not change after initializing with
-	 * usbvirt_device_init().
-	 * This function is here merely to make the interface more OOP.
+	
+	/** Reply onto control transfer.
 	 */
-	int (*send_data)(struct usbvirt_device *dev,
+	int (*control_transfer_reply)(struct usbvirt_device *dev,
 	    usb_endpoint_t endpoint, void *buffer, size_t size);
 	
@@ -153,12 +174,12 @@
 	int device_id_;
 	
-	/** Main routine called when data is received from HC.
-	 * @warning Do not change after initializing with
-	 * usbvirt_device_init().
-	 * This function is here merely to make the interface more OOP.
-	 */
-	int (*receive_data)(struct usbvirt_device *dev,
+	int (*transaction_out)(struct usbvirt_device *dev,
 	    usb_endpoint_t endpoint, void *buffer, size_t size);
+	int (*transaction_setup)(struct usbvirt_device *dev,
+	    usb_endpoint_t endpoint, void *buffer, size_t size);
+	int (*transaction_in)(struct usbvirt_device *dev,
+	    usb_endpoint_t endpoint, void *buffer, size_t size, size_t *data_size);
 	
+	usbvirt_control_transfer_t current_control_transfers[USB11_ENDPOINT_MAX];
 } usbvirt_device_t;
 
Index: uspace/lib/usbvirt/hub.h
===================================================================
--- uspace/lib/usbvirt/hub.h	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/hub.h	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -41,5 +41,8 @@
 typedef enum {
 	IPC_M_USBVIRT_DATA_TO_DEVICE = IPC_FIRST_USER_METHOD,
-	IPC_M_USBVIRT_DATA_FROM_DEVICE
+	IPC_M_USBVIRT_DATA_FROM_DEVICE,
+	IPC_M_USBVIRT_TRANSACTION_SETUP,
+	IPC_M_USBVIRT_TRANSACTION_OUT,
+	IPC_M_USBVIRT_TRANSACTION_IN,
 } usbvirt_device_method_t;
 
Index: pace/lib/usbvirt/incoming.c
===================================================================
--- uspace/lib/usbvirt/incoming.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ 	(revision )
@@ -1,62 +1,0 @@
-/*
- * Copyright (c) 2010 Vojtech Horky
- * 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 libusbvirt usb
- * @{
- */
-/** @file
- * @brief General handling of data transfer.
- */
-#include <errno.h>
-
-#include "device.h"
-#include "private.h"
-
-int handle_incoming_data(struct usbvirt_device *dev,
-    usb_endpoint_t endpoint, void *buffer, size_t size)
-{
-	/*
-	 * Endpoint zero is device control pipe.
-	 */
-	if (endpoint == 0) {
-		return control_pipe(buffer, size);
-	}
-	
-	/*
-	 * Any other endpoint will be handled by general handler.
-	 */
-	if (device->ops->on_data) {
-		return device->ops->on_data(device, endpoint, buffer, size);
-	} else {
-		return ENOTSUP;
-	}
-}
-
-/**
- * @}
- */
Index: uspace/lib/usbvirt/main.c
===================================================================
--- uspace/lib/usbvirt/main.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/main.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -38,4 +38,5 @@
 #include <errno.h>
 #include <stdlib.h>
+#include <mem.h>
 
 #include "hub.h"
@@ -47,10 +48,9 @@
 usbvirt_device_t *device = NULL;
 
-
-static void handle_data_to_device(ipc_callid_t iid, ipc_call_t icall)
+static void handle_setup_transaction(ipc_callid_t iid, ipc_call_t icall)
 {
 	usb_address_t address = IPC_GET_ARG1(icall);
 	usb_endpoint_t endpoint = IPC_GET_ARG2(icall);
-	size_t expected_len = IPC_GET_ARG5(icall);
+	size_t expected_len = IPC_GET_ARG3(icall);
 	
 	if (address != device->address) {
@@ -59,11 +59,55 @@
 	}
 	
+	if ((endpoint < 0) || (endpoint >= USB11_ENDPOINT_MAX)) {
+		ipc_answer_0(iid, EINVAL);
+		return;
+	}
+	
+	if (expected_len == 0) {
+		ipc_answer_0(iid, EINVAL);
+		return;
+	}
+	
 	size_t len = 0;
 	void * buffer = NULL;
+	int rc = async_data_write_accept(&buffer, false,
+	    1, USB_MAX_PAYLOAD_SIZE, 0, &len);
+		
+	if (rc != EOK) {
+		ipc_answer_0(iid, rc);
+		return;
+	}
+	
+	rc = device->transaction_setup(device, endpoint, buffer, len);
+	
+	ipc_answer_0(iid, rc);
+}
+
+
+static void handle_out_transaction(ipc_callid_t iid, ipc_call_t icall)
+{
+	usb_address_t address = IPC_GET_ARG1(icall);
+	usb_endpoint_t endpoint = IPC_GET_ARG2(icall);
+	size_t expected_len = IPC_GET_ARG3(icall);
+	
+	if (address != device->address) {
+		ipc_answer_0(iid, EADDRNOTAVAIL);
+		return;
+	}
+	
+	if ((endpoint < 0) || (endpoint >= USB11_ENDPOINT_MAX)) {
+		ipc_answer_0(iid, EINVAL);
+		return;
+	}
+	
+	int rc = EOK;
+	
+	size_t len = 0;
+	void *buffer = NULL;
+	
 	if (expected_len > 0) {
-		int rc = async_data_write_accept(&buffer, false,
-		    1, USB_MAX_PAYLOAD_SIZE,
-		    0, &len);
-		
+		rc = async_data_write_accept(&buffer, false,
+		    1, USB_MAX_PAYLOAD_SIZE, 0, &len);
+			
 		if (rc != EOK) {
 			ipc_answer_0(iid, rc);
@@ -72,5 +116,5 @@
 	}
 	
-	device->receive_data(device, endpoint, buffer, len);
+	rc = device->transaction_out(device, endpoint, buffer, len);
 	
 	if (buffer != NULL) {
@@ -78,6 +122,47 @@
 	}
 	
-	ipc_answer_0(iid, EOK);
-}
+	ipc_answer_0(iid, rc);
+}
+
+
+
+static void handle_in_transaction(ipc_callid_t iid, ipc_call_t icall)
+{
+	usb_address_t address = IPC_GET_ARG1(icall);
+	usb_endpoint_t endpoint = IPC_GET_ARG2(icall);
+	size_t expected_len = IPC_GET_ARG3(icall);
+	
+	if (address != device->address) {
+		ipc_answer_0(iid, EADDRNOTAVAIL);
+		return;
+	}
+	
+	if ((endpoint < 0) || (endpoint >= USB11_ENDPOINT_MAX)) {
+		ipc_answer_0(iid, EINVAL);
+		return;
+	}
+	
+	int rc = EOK;
+	
+	void *buffer = expected_len > 0 ? malloc(expected_len) : NULL;
+	size_t len;
+	
+	rc = device->transaction_in(device, endpoint, buffer, expected_len, &len);
+	/*
+	 * If the request was processed, we will send data back.
+	 */
+	if (rc == EOK) {
+		size_t receive_len;
+		if (!async_data_read_receive(&iid, &receive_len)) {
+			ipc_answer_0(iid, EINVAL);
+			return;
+		}
+		async_data_read_finalize(iid, buffer, receive_len);
+	}
+	
+	ipc_answer_0(iid, rc);
+}
+
+
 
 static void callback_connection(ipc_callid_t iid, ipc_call_t *icall)
@@ -95,6 +180,14 @@
 				return;
 			
-			case IPC_M_USBVIRT_DATA_TO_DEVICE:
-				handle_data_to_device(callid, call);
+			case IPC_M_USBVIRT_TRANSACTION_SETUP:
+				handle_setup_transaction(callid, call);
+				break;
+			
+			case IPC_M_USBVIRT_TRANSACTION_OUT:
+				handle_out_transaction(callid, call);
+				break;
+				
+			case IPC_M_USBVIRT_TRANSACTION_IN:
+				handle_in_transaction(callid, call);
 				break;
 			
@@ -106,51 +199,38 @@
 }
 
+static int control_transfer_reply(struct usbvirt_device *device,
+	    usb_endpoint_t endpoint, void *buffer, size_t size)
+{
+	usbvirt_control_transfer_t *transfer = &device->current_control_transfers[endpoint];
+	if (transfer->data != NULL) {
+		free(transfer->data);
+	}
+	transfer->data = malloc(size);
+	memcpy(transfer->data, buffer, size);
+	transfer->data_size = size;
+	
+	return EOK;
+}
+
 static void device_init(usbvirt_device_t *dev)
 {
-	dev->send_data = usbvirt_data_to_host;
-	dev->receive_data = handle_incoming_data;
+	dev->transaction_out = transaction_out;
+	dev->transaction_setup = transaction_setup;
+	dev->transaction_in = transaction_in;
+	
+	dev->control_transfer_reply = control_transfer_reply;
+	
 	dev->state = USBVIRT_STATE_DEFAULT;
 	dev->address = 0;
-}
-
-int usbvirt_data_to_host(struct usbvirt_device *dev,
-    usb_endpoint_t endpoint, void *buffer, size_t size)
-{
-	int phone = dev->vhcd_phone_;
-	
-	if (phone < 0) {
-		return EINVAL;
-	}
-	if ((buffer == NULL) && (size != 0)) {
-		return EINVAL;
-	}
-
-	ipc_call_t answer_data;
-	ipcarg_t answer_rc;
-	aid_t req;
-	int rc;
-	
-	req = async_send_3(phone,
-	    IPC_M_USBVIRT_DATA_FROM_DEVICE,
-	    dev->address,
-	    endpoint,
-	    size,
-	    &answer_data);
-	
-	if (size > 0) {
-		rc = async_data_write_start(phone, buffer, size);
-		if (rc != EOK) {
-			async_wait_for(req, NULL);
-			return rc;
-		}
-	}
-	
-	async_wait_for(req, &answer_rc);
-	rc = (int)answer_rc;
-	if (rc != EOK) {
-		return rc;
-	}
-	
-	return EOK;
+	
+	size_t i;
+	for (i = 0; i < USB11_ENDPOINT_MAX; i++) {
+		usbvirt_control_transfer_t *transfer = &dev->current_control_transfers[i];
+		transfer->direction = 0;
+		transfer->request = NULL;
+		transfer->request_size = 0;
+		transfer->data = NULL;
+		transfer->data_size = 0;
+	}
 }
 
Index: uspace/lib/usbvirt/private.h
===================================================================
--- uspace/lib/usbvirt/private.h	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/private.h	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -54,5 +54,5 @@
     usb_endpoint_t endpoint, void *buffer, size_t size);
 
-int control_pipe(void *buffer, size_t size);
+int control_pipe(usbvirt_control_transfer_t *transfer);
 
 int handle_std_request(usb_device_request_setup_packet_t *request, uint8_t *data);
@@ -60,4 +60,11 @@
 extern usb_address_t dev_new_address;
 
+int transaction_setup(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size);
+int transaction_out(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size);
+int transaction_in(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size, size_t *data_size);
+
 #endif
 /**
Index: uspace/lib/usbvirt/stdreq.c
===================================================================
--- uspace/lib/usbvirt/stdreq.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/lib/usbvirt/stdreq.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -57,5 +57,5 @@
 	if ((type == USB_DESCTYPE_DEVICE) && (index == 0)) {
 		if (device->descriptors && device->descriptors->device) {
-			return device->send_data(device, 0,
+			return device->control_transfer_reply(device, 0,
 			    device->descriptors->device,
 			    device->descriptors->device->length);
@@ -95,6 +95,6 @@
 		}
 		
-		int rc = device->send_data(device, 0, all_data,
-		    config->descriptor->total_length);
+		int rc = device->control_transfer_reply(device, 0,
+		    all_data, config->descriptor->total_length);
 		
 		free(all_data);
Index: uspace/lib/usbvirt/transaction.c
===================================================================
--- uspace/lib/usbvirt/transaction.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
+++ uspace/lib/usbvirt/transaction.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2010 Vojtech Horky
+ * 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 libusbvirt usb
+ * @{
+ */
+/** @file
+ * @brief Transaction processing.
+ */
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <mem.h>
+
+#include "private.h"
+
+static usb_direction_t setup_transaction_direction(usb_endpoint_t, void *, size_t);
+static void process_control_transfer(usb_endpoint_t, usbvirt_control_transfer_t *);
+
+int transaction_setup(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size)
+{
+	usbvirt_control_transfer_t *transfer = &device->current_control_transfers[endpoint];
+	
+	if (transfer->request != NULL) {
+		free(transfer->request);
+	}
+	if (transfer->data != NULL) {
+		free(transfer->data);
+	}
+	
+	transfer->direction = setup_transaction_direction(endpoint,
+	    buffer, size);
+	transfer->request = buffer;
+	transfer->request_size = size;
+	transfer->data = NULL;
+	transfer->data_size = 0;
+	
+	return EOK;
+}
+
+int transaction_out(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size)
+{
+	/*
+	 * First check whether it is a transaction over control pipe.
+	 */
+	usbvirt_control_transfer_t *transfer = &device->current_control_transfers[endpoint];
+	if (transfer->request != NULL) {
+		if (transfer->direction == USB_DIRECTION_OUT) {
+			/*
+			 * For out transactions, append the data to the buffer.
+			 */
+			uint8_t *new_buffer = (uint8_t *) malloc(transfer->data_size + size);
+			if (transfer->data) {
+				memcpy(new_buffer, transfer->data, transfer->data_size);
+			}
+			memcpy(new_buffer + transfer->data_size, buffer, size);
+			
+			if (transfer->data) {
+				free(transfer->data);
+			}
+			transfer->data = new_buffer;
+			transfer->data_size += size;
+		} else {
+			/*
+			 * For in transactions, this means end of the
+			 * transaction.
+			 */
+			free(transfer->request);
+			if (transfer->data) {
+				free(transfer->data);
+			}
+			transfer->request = NULL;
+			transfer->request_size = 0;
+			transfer->data = NULL;
+			transfer->data_size = 0;
+		}
+		
+		return EOK;
+	}
+	
+	/*
+	 * Otherwise, announce that some data has come.
+	 */
+	if (device->ops && device->ops->on_data) {
+		return device->ops->on_data(device, endpoint, buffer, size);
+	} else {
+		return ENOTSUP;
+	}
+}
+
+int transaction_in(usbvirt_device_t *device, usb_endpoint_t endpoint,
+    void *buffer, size_t size, size_t *data_size)
+{
+	/*
+	 * First check whether it is a transaction over control pipe.
+	 */
+	usbvirt_control_transfer_t *transfer = &device->current_control_transfers[endpoint];
+	if (transfer->request != NULL) {
+		if (transfer->direction == USB_DIRECTION_OUT) {
+			/*
+			 * This means end of input data.
+			 */
+			process_control_transfer(endpoint, transfer);
+		} else {
+			/*
+			 * For in transactions, this means sending next part
+			 * of the buffer.
+			 */
+			// TODO: implement
+		}
+		
+		return EOK;
+	}
+	
+	if (size == 0) {
+		return EINVAL;
+	}
+	
+	int rc = 1;
+	
+	if (device->ops && device->ops->on_data_request) {
+		rc = device->ops->on_data_request(device, endpoint, buffer, size, data_size);
+	}
+	
+	return rc;
+}
+
+
+static usb_direction_t setup_transaction_direction(usb_endpoint_t endpoint,
+    void *data, size_t size)
+{
+	int direction = -1;
+	if (device->ops && device->ops->decide_control_transfer_direction) {
+		direction = device->ops->decide_control_transfer_direction(endpoint,
+		    data, size);
+	}
+	
+	/*
+	 * If the user-supplied routine have not handled the direction
+	 * (or simply was not provided) we will guess, hoping that it 
+	 * uses same format as standard request on control pipe zero.
+	 */
+	if (direction < 0) {
+		if (size > 0) {
+			uint8_t *ptr = (uint8_t *) data;
+			if ((ptr[0] & 128) == 128) {
+				direction = USB_DIRECTION_IN;
+			} else {
+				direction = USB_DIRECTION_OUT;
+			}
+		} else {
+			/* This shall not happen anyway. */
+			direction = USB_DIRECTION_OUT;
+		}
+	}
+	
+	return (usb_direction_t) direction;
+}
+
+static void process_control_transfer(usb_endpoint_t endpoint,
+    usbvirt_control_transfer_t *transfer)
+{
+	int rc = EFORWARD;
+	
+	if (device->ops && device->ops->on_control_transfer) {
+		rc = device->ops->on_control_transfer(device, endpoint, transfer);
+	}
+	
+	if (rc == EFORWARD) {
+		if (endpoint == 0) {
+			rc = control_pipe(transfer);
+		}
+	}
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/hw/bus/usb/hcd/virtual/conndev.c
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/conndev.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/conndev.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -42,42 +42,4 @@
 #include "hub.h"
 
-/** Handle data from device to host.
- */
-static void handle_data_from_device(ipc_callid_t iid, ipc_call_t icall,
-    virtdev_connection_t *dev)
-{
-	usb_target_t target = {
-		.address = IPC_GET_ARG1(icall),
-		.endpoint = IPC_GET_ARG2(icall)
-	};
-	size_t len = IPC_GET_ARG3(icall);
-	
-	if (!hub_can_device_signal(dev)) {
-		ipc_answer_0(iid, EREFUSED);
-		return;
-	}
-	
-	dprintf("data from device %d [%d.%d]", dev->id,
-	    target.address, target.endpoint);
-	
-	int rc;
-	
-	void * buffer = NULL;
-	if (len > 0) {
-		rc = async_data_write_accept(&buffer, false,
-		    1, USB_MAX_PAYLOAD_SIZE,
-		    0, &len);
-		
-		if (rc != EOK) {
-			ipc_answer_0(iid, rc);
-			return;
-		}
-	}
-	
-	rc = hc_fillin_transaction_from_device(target, buffer, len);
-	
-	ipc_answer_0(iid, rc);
-}
-
 /** Connection handler for communcation with virtual device.
  *
@@ -110,8 +72,4 @@
 				break;
 			
-			case IPC_M_USBVIRT_DATA_FROM_DEVICE:
-				handle_data_from_device(callid, call, dev);
-				break;
-			
 			default:
 				ipc_answer_0(callid, EINVAL);
Index: uspace/srv/hw/bus/usb/hcd/virtual/devices.c
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/devices.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/devices.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -126,17 +126,35 @@
 		aid_t req;
 		int rc = EOK;
-		
-		req = async_send_4(dev->phone,
-		    IPC_M_USBVIRT_DATA_TO_DEVICE,
+		int method = IPC_M_USBVIRT_TRANSACTION_SETUP;
+		
+		switch (transaction->type) {
+			case USBVIRT_TRANSACTION_SETUP:
+				method = IPC_M_USBVIRT_TRANSACTION_SETUP;
+				break;
+			case USBVIRT_TRANSACTION_IN:
+				method = IPC_M_USBVIRT_TRANSACTION_IN;
+				break;
+			case USBVIRT_TRANSACTION_OUT:
+				method = IPC_M_USBVIRT_TRANSACTION_OUT;
+				break;
+		}
+		
+		req = async_send_3(dev->phone,
+		    method,
 		    transaction->target.address,
 		    transaction->target.endpoint,
-		    transaction->type,
 		    transaction->len,
 		    &answer_data);
 		
 		if (transaction->len > 0) {
-			rc = async_data_write_start(dev->phone,
-			    transaction->buffer, transaction->len);
-		}
+			if (transaction->type == USBVIRT_TRANSACTION_IN) {
+				rc = async_data_read_start(dev->phone,
+				    transaction->buffer, transaction->len);
+			} else {
+				rc = async_data_write_start(dev->phone,
+				    transaction->buffer, transaction->len);
+			}
+		}
+		
 		if (rc != EOK) {
 			async_wait_for(req, NULL);
@@ -152,6 +170,28 @@
 	 */
 	if (virthub_dev.address == transaction->target.address) {
-		virthub_dev.receive_data(&virthub_dev, transaction->target.endpoint,
-		    transaction->buffer, transaction->len);
+		size_t tmp;
+		switch (transaction->type) {
+			case USBVIRT_TRANSACTION_SETUP:
+				virthub_dev.transaction_setup(&virthub_dev,
+				    transaction->target.endpoint,
+				    transaction->buffer, transaction->len);
+				break;
+				
+			case USBVIRT_TRANSACTION_IN:
+				virthub_dev.transaction_in(&virthub_dev,
+				    transaction->target.endpoint,
+				    transaction->buffer, transaction->len,
+				    &tmp);
+				if (tmp < transaction->len) {
+					transaction->len = tmp;
+				}
+				break;
+				
+			case USBVIRT_TRANSACTION_OUT:
+				virthub_dev.transaction_out(&virthub_dev,
+				    transaction->target.endpoint,
+				    transaction->buffer, transaction->len);
+				break;
+		}
 	}
 	
Index: uspace/srv/hw/bus/usb/hcd/virtual/hc.c
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/hc.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/hc.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -48,4 +48,5 @@
 #include "hc.h"
 #include "devices.h"
+#include "hub.h"
 
 #define USLEEP_BASE (500 * 1000)
@@ -65,11 +66,10 @@
 	} while (0)
 
-static link_t transaction_to_device_list;
-static link_t transaction_from_device_list;
+static link_t transaction_list;
 
-#define TRANSACTION_FORMAT "T[%d:%d (%d)]"
+#define TRANSACTION_FORMAT "T[%d:%d (%d) %d]"
 #define TRANSACTION_PRINTF(t) \
 	(t).target.address, (t).target.endpoint, \
-	(int)(t).len
+	(int)(t).len, (int)(t).type
 
 #define transaction_get_instance(lnk) \
@@ -100,6 +100,5 @@
 void hc_manager(void)
 {
-	list_initialize(&transaction_to_device_list);
-	list_initialize(&transaction_from_device_list);
+	list_initialize(&transaction_list);
 	
 	static unsigned int seed = 4573;
@@ -110,12 +109,17 @@
 		async_usleep(USLEEP_BASE + (pseudo_random(&seed) % USLEEP_VAR));
 		
-		if (list_empty(&transaction_to_device_list)) {
+		if (list_empty(&transaction_list)) {
 			continue;
 		}
 		
-		link_t *first_transaction_link = transaction_to_device_list.next;
+		dprintf("virtual hub has address %d:*.", virthub_dev.address);
+		
+		link_t *first_transaction_link = transaction_list.next;
 		transaction_t *transaction
 		    = transaction_get_instance(first_transaction_link);
 		list_remove(first_transaction_link);
+		
+		dprintf("processing transaction " TRANSACTION_FORMAT "",
+		    TRANSACTION_PRINTF(*transaction));
 		
 		usb_transaction_outcome_t outcome;
@@ -157,5 +161,5 @@
 	    setup ? USBVIRT_TRANSACTION_SETUP : USBVIRT_TRANSACTION_OUT, target,
 	    buffer, len, callback, arg);
-	list_append(&transaction->link, &transaction_to_device_list);
+	list_append(&transaction->link, &transaction_list);
 }
 
@@ -168,70 +172,5 @@
 	transaction_t *transaction = transaction_create(USBVIRT_TRANSACTION_IN,
 	    target, buffer, len, callback, arg);
-	list_append(&transaction->link, &transaction_from_device_list);
-}
-
-/** Fill data to existing transaction from device.
- */
-int hc_fillin_transaction_from_device(usb_target_t target,
-    void * buffer, size_t len)
-{
-	dprintf("finding transaction to fill data in (%d:%d)...",
-	    target.address, target.endpoint);
-	int rc;
-	
-	/*
-	 * Find correct transaction envelope in the list.
-	 */
-	if (list_empty(&transaction_from_device_list)) {
-		rc = ENOENT;
-		goto leave;
-	}
-	
-	transaction_t *transaction = NULL;
-	link_t *pos = transaction_from_device_list.next;
-	
-	while (pos != &transaction_from_device_list) {
-		transaction_t *t = transaction_get_instance(pos);
-		if (usb_target_same(t->target, target)) {
-			transaction = t;
-			break;
-		}
-		pos = pos->next;
-	}
-	if (transaction == NULL) {
-		rc = ENOENT;
-		goto leave;
-	}
-	
-	/*
-	 * Remove the transaction from the list as it will be processed now.
-	 */
-	list_remove(&transaction->link);
-	
-	if (transaction->len < len) {
-		process_transaction_with_outcome(transaction, USB_OUTCOME_BABBLE);
-		rc = ENOMEM;
-		goto leave;
-	}
-	
-	/*
-	 * Copy the data and finish processing the transaction.
-	 */
-	transaction->len = len;
-	memcpy(transaction->buffer, buffer, len);
-	
-	process_transaction_with_outcome(transaction, USB_OUTCOME_OK);
-	
-	dprintf("  ...transaction " TRANSACTION_FORMAT " sent back",
-	    TRANSACTION_PRINTF(*transaction));
-	
-	
-	free(transaction);
-	rc = EOK;
-	
-leave:
-	dprintf("  ...fill-in transaction: %s", str_error(rc));
-	
-	return rc;
+	list_append(&transaction->link, &transaction_list);
 }
 
Index: uspace/srv/hw/bus/usb/hcd/virtual/hub.c
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/hub.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/hub.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -144,18 +144,4 @@
 hub_device_t hub_dev;
 
-static int send_data(struct usbvirt_device *dev,
-	    usb_endpoint_t endpoint, void *buffer, size_t size)
-{
-	usb_target_t target = { dev->address, endpoint };
-	void *my_buffer = NULL;
-	if (size > 0) {
-		my_buffer = malloc(size);
-		memcpy(my_buffer, buffer, size);
-	}
-	hc_fillin_transaction_from_device(target, my_buffer, size);
-	
-	return EOK;
-}
-
 void hub_init(void)
 {
@@ -170,5 +156,5 @@
 	
 	usbvirt_connect_local(&virthub_dev);
-	virthub_dev.send_data = send_data;
+	//virthub_dev.send_data = send_data;
 	
 	printf("%s: virtual hub (%d ports) created.\n", NAME, HUB_PORT_COUNT);
@@ -235,24 +221,4 @@
 }
 
-void hub_check_port_changes(void)
-{
-	/* FIXME - what if HUB_PORT_COUNT is greater than 8. */
-	uint8_t change_map = 0;
-	
-	size_t i;
-	for (i = 0; i < HUB_PORT_COUNT; i++) {
-		hub_port_t *port = &hub_dev.ports[i];
-		
-		if (port->status_change != 0) {
-			change_map |= (1 << (i + 1));
-		}
-	}
-	
-	/* FIXME - do not send when it has not changed since previous run. */
-	if (change_map != 0) {
-		virthub_dev.send_data(&virthub_dev, HUB_STATUS_CHANGE_PIPE,
-		    &change_map, 1);
-	}
-}
 
 /**
Index: uspace/srv/hw/bus/usb/hcd/virtual/hub.h
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/hub.h	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/hub.h	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -51,5 +51,4 @@
 void hub_remove_device(virtdev_connection_t *);
 bool hub_can_device_signal(virtdev_connection_t *);
-void hub_check_port_changes(void);
 
 #endif
Index: uspace/srv/hw/bus/usb/hcd/virtual/hubops.c
===================================================================
--- uspace/srv/hw/bus/usb/hcd/virtual/hubops.c	(revision b8a3cda7a0bb2e2ea3eeb8a5d2368d8d10f4963b)
+++ uspace/srv/hw/bus/usb/hcd/virtual/hubops.c	(revision 7a7bfeb30fdd348b34bb92383e98a63c4fff1346)
@@ -59,4 +59,8 @@
 static int on_class_request(struct usbvirt_device *dev,
     usb_device_request_setup_packet_t *request, uint8_t *data);
+static int on_data_request(struct usbvirt_device *dev,
+    usb_endpoint_t endpoint,
+    void *buffer, size_t size, size_t *actual_size);
+
 
 static usbvirt_standard_device_request_ops_t standard_request_ops = {
@@ -78,5 +82,6 @@
 	.standard_request_ops = &standard_request_ops,
 	.on_class_device_request = on_class_request,
-	.on_data = NULL
+	.on_data = NULL,
+	.on_data_request = on_data_request
 };
 
@@ -85,5 +90,5 @@
 {
 	if (request->value_high == USB_DESCTYPE_HUB) {
-		int rc = dev->send_data(dev, 0,
+		int rc = dev->control_transfer_reply(dev, 0,
 		    &hub_descriptor, hub_descriptor.length);
 		
@@ -196,5 +201,6 @@
 	uint32_t hub_status = 0;
 	
-	return virthub_dev.send_data(&virthub_dev, 0, &hub_status, 4);
+	return virthub_dev.control_transfer_reply(&virthub_dev, 0,
+	    &hub_status, 4);
 }
 
@@ -230,5 +236,5 @@
 	status |= (port->status_change << 16);
 	
-	return virthub_dev.send_data(&virthub_dev, 0, &status, 4);
+	return virthub_dev.control_transfer_reply(&virthub_dev, 0, &status, 4);
 }
 
@@ -334,5 +340,4 @@
 {
 	port->status_change &= (~change);
-	hub_check_port_changes();
 }
 
@@ -341,4 +346,29 @@
 	port->status_change |= change;
 }
+
+static int on_data_request(struct usbvirt_device *dev,
+    usb_endpoint_t endpoint,
+    void *buffer, size_t size, size_t *actual_size)
+{
+	uint8_t change_map = 0;
+	
+	size_t i;
+	for (i = 0; i < HUB_PORT_COUNT; i++) {
+		hub_port_t *port = &hub_dev.ports[i];
+		
+		if (port->status_change != 0) {
+			change_map |= (1 << (i + 1));
+		}
+	}
+	
+	uint8_t *b = (uint8_t *) buffer;
+	if (size > 0) {
+		*b = change_map;
+		*actual_size = 1;
+	}
+	
+	return EOK;
+}
+
 
 /**
