Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision a0be5d09918a5ace86ecb87efdf8a4042077764d)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision 42bc933def8a0fa3c265ead66cec74d1ccb6798f)
@@ -464,12 +464,10 @@
 	switch (batch->ep->transfer_type) {
 	case USB_TRANSFER_CONTROL:
-		xhci_schedule_control_transfer(hc, batch);
-		break;
+		return xhci_schedule_control_transfer(hc, batch);
 	case USB_TRANSFER_ISOCHRONOUS:
 		/* TODO: Implement me. */
 		break;
 	case USB_TRANSFER_BULK:
-		/* TODO: Implement me. */
-		break;
+		return xhci_schedule_bulk_transfer(hc, batch);
 	case USB_TRANSFER_INTERRUPT:
 		/* TODO: Implement me. */
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision a0be5d09918a5ace86ecb87efdf8a4042077764d)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision 42bc933def8a0fa3c265ead66cec74d1ccb6798f)
@@ -221,4 +221,36 @@
 }
 
+int xhci_schedule_bulk_transfer(xhci_hc_t* hc, usb_transfer_batch_t* batch) {
+	if (batch->setup_size) {
+		usb_log_warning("Setup packet present for a bulk transfer.");
+	}
+
+	uint8_t slot_id = batch->ep->hc_data.slot_id;
+	xhci_trb_ring_t* ring = hc->dcbaa_virt[slot_id].trs[batch->ep->endpoint];
+
+	xhci_transfer_t *transfer = xhci_transfer_alloc(batch);
+
+	xhci_trb_t trb;
+	memset(&trb, 0, sizeof(xhci_trb_t));
+	trb.parameter = (uintptr_t) addr_to_phys(batch->buffer);
+
+	// data size (sent for OUT, or buffer size)
+ 	TRB_CTRL_SET_XFER_LEN(trb, batch->buffer_size);
+ 	// FIXME: TD size 4.11.2.4
+ 	TRB_CTRL_SET_TD_SIZE(trb, 1);
+
+	// we want an interrupt after this td is done
+	TRB_CTRL_SET_IOC(trb, 1);
+
+	TRB_CTRL_SET_TRB_TYPE(trb, XHCI_TRB_TYPE_NORMAL);
+
+ 	xhci_trb_ring_enqueue(ring, &trb, &transfer->interrupt_trb_phys);
+	list_append(&transfer->link, &hc->transfers);
+
+ 	/* For control transfers, the target is always 1. */
+ 	hc_ring_doorbell(hc, slot_id, 1);
+	return EOK;
+}
+
 int xhci_handle_transfer_event(xhci_hc_t* hc, xhci_trb_t* trb)
 {
Index: uspace/drv/bus/usb/xhci/transfers.h
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.h	(revision a0be5d09918a5ace86ecb87efdf8a4042077764d)
+++ uspace/drv/bus/usb/xhci/transfers.h	(revision 42bc933def8a0fa3c265ead66cec74d1ccb6798f)
@@ -37,22 +37,4 @@
 #include "trb_ring.h"
 
-/*
-
-typedef struct xhci_command {
-	link_t link;
-
-	xhci_trb_t trb;
-	uintptr_t trb_phys;
-
-	uint32_t slot_id;
-	uint32_t status;
-
-	bool completed;
-
-	fibril_mutex_t completed_mtx;
-	fibril_condvar_t completed_cv;
-} xhci_cmd_t;
-*/
-
 typedef struct {
 	link_t link;
@@ -69,3 +51,4 @@
 void xhci_transfer_fini(xhci_transfer_t*);
 int xhci_schedule_control_transfer(xhci_hc_t*, usb_transfer_batch_t*);
+int xhci_schedule_bulk_transfer(xhci_hc_t*, usb_transfer_batch_t*);
 int xhci_handle_transfer_event(xhci_hc_t*, xhci_trb_t*);
