Index: uspace/drv/bus/usb/xhci/bus.c
===================================================================
--- uspace/drv/bus/usb/xhci/bus.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/bus.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -187,4 +187,6 @@
 	ret_dev->device = dev;
 
+	usb_log_debug("Device(%d) registered to XHCI bus.", dev->address);
+
 	hash_table_insert(&bus->devices, &ret_dev->link);
 	*hashed_dev = ret_dev;
@@ -200,4 +202,6 @@
 static int hashed_device_remove(xhci_bus_t *bus, hashed_device_t *hashed_dev)
 {
+	usb_log_debug("Device(%d) released from XHCI bus.", hashed_dev->device->address);
+
 	hash_table_remove(&bus->devices, &hashed_dev->device->address);
 	xhci_device_fini(hashed_dev->device);
@@ -225,4 +229,6 @@
 	}
 
+	usb_log_debug("Endpoint(%d:%d) registered to XHCI bus.", ep->target.address, ep->target.endpoint);
+
 	return xhci_device_add_endpoint(hashed_dev->device, xhci_endpoint_get(ep));
 }
@@ -232,4 +238,6 @@
 	xhci_bus_t *bus = bus_to_xhci_bus(bus_base);
 	assert(bus);
+
+	usb_log_debug("Endpoint(%d:%d) released from XHCI bus.", ep->target.address, ep->target.endpoint);
 
 	hashed_device_t *hashed_dev;
Index: uspace/drv/bus/usb/xhci/endpoint.c
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/endpoint.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -35,4 +35,5 @@
 
 #include <usb/host/endpoint.h>
+#include <usb/descriptor.h>
 
 #include <errno.h>
@@ -53,6 +54,4 @@
 	xhci_ep->device = NULL;
 
-	usb_log_debug("Endpoint %d:%d initialized.", ep->target.address, ep->target.endpoint);
-
 	return EOK;
 }
@@ -63,8 +62,4 @@
 
 	/* FIXME: Tear down TR's? */
-
-	endpoint_t *ep = &xhci_ep->base;
-
-	usb_log_debug("Endpoint %d:%d destroyed.", ep->target.address, ep->target.endpoint);
 }
 
@@ -76,5 +71,4 @@
 	dev->slot_id = 0;
 
-	usb_log_debug("Device %d initialized.", dev->address);
 	return EOK;
 }
@@ -83,5 +77,107 @@
 {
 	// TODO: Check that all endpoints are dead.
-	usb_log_debug("Device %d destroyed.", dev->address);
+	assert(dev);
+}
+
+static inline uint8_t xhci_endpoint_ctx_offset(xhci_endpoint_t *ep)
+{
+	/* 0 is slot ctx, 1 is EP0, then it's EP1 out, in, EP2 out, in, etc. */
+
+	uint8_t off = 2 + 2 * (ep->base.target.endpoint - 1);
+	if (ep->base.direction == USB_DIRECTION_IN)
+		++off;
+
+	return off;
+}
+
+static int xhci_endpoint_type(xhci_endpoint_t *ep)
+{
+	const bool in = ep->base.direction == USB_DIRECTION_IN;
+
+	switch (ep->base.transfer_type) {
+	case USB_TRANSFER_CONTROL:
+		return EP_TYPE_CONTROL;
+
+	case USB_TRANSFER_ISOCHRONOUS:
+		return in ? EP_TYPE_ISOCH_IN
+			  : EP_TYPE_ISOCH_OUT;
+
+	case USB_TRANSFER_BULK:
+		return in ? EP_TYPE_BULK_IN
+			  : EP_TYPE_BULK_OUT;
+
+	case USB_TRANSFER_INTERRUPT:
+		return in ? EP_TYPE_INTERRUPT_IN
+			  : EP_TYPE_INTERRUPT_OUT;
+	}
+
+	return EP_TYPE_INVALID;
+}
+
+static void setup_control_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx,
+	xhci_trb_ring_t *ring)
+{
+	// EP0 is configured elsewhere.
+	assert(ep->base.target.endpoint > 0);
+
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+	XHCI_EP_TR_DPTR_SET(*ctx, ring->dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+}
+
+static void setup_bulk_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx,
+	xhci_trb_ring_t *ring, usb_superspeed_endpoint_companion_descriptor_t *ss_desc)
+{
+
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ep->device->usb3 ? ss_desc->max_burst : 0);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+
+	// FIXME: Get maxStreams and other things from ss_desc
+	const uint8_t maxStreams = 0;
+	if (maxStreams > 0) {
+		// TODO: allocate and clear primary stream array
+		// TODO: XHCI_EP_MAX_P_STREAMS_SET(ctx, psa_size);
+		// TODO: XHCI_EP_TR_DPTR_SET(ctx, psa_start_phys_addr);
+		// TODO: set HID
+		// TODO: set LSA
+	} else {
+		XHCI_EP_MAX_P_STREAMS_SET(*ctx, 0);
+		/* FIXME physical pointer? */
+		XHCI_EP_TR_DPTR_SET(*ctx, ring->dequeue);
+		XHCI_EP_DCS_SET(*ctx, 1);
+	}
+}
+
+static void setup_isoch_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx,
+	xhci_trb_ring_t *ring, usb_superspeed_endpoint_companion_descriptor_t *ss_desc)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size & 0x07FF);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ss_desc->max_burst);
+	// FIXME: get Mult field from SS companion descriptor somehow
+	XHCI_EP_MULT_SET(*ctx, 0);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 0);
+	/* FIXME physical pointer? */
+	XHCI_EP_TR_DPTR_SET(*ctx, ring->dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+	// TODO: max ESIT payload
+}
+
+static void setup_interrupt_ep_ctx(xhci_endpoint_t *ep, xhci_ep_ctx_t *ctx,
+	xhci_trb_ring_t *ring, usb_superspeed_endpoint_companion_descriptor_t *ss_desc)
+{
+	XHCI_EP_TYPE_SET(*ctx, xhci_endpoint_type(ep));
+	XHCI_EP_MAX_PACKET_SIZE_SET(*ctx, ep->base.max_packet_size & 0x07FF);
+	XHCI_EP_MAX_BURST_SIZE_SET(*ctx, ss_desc->max_burst);
+	XHCI_EP_MULT_SET(*ctx, 0);
+	XHCI_EP_ERROR_COUNT_SET(*ctx, 3);
+	/* FIXME physical pointer? */
+	XHCI_EP_TR_DPTR_SET(*ctx, ring->dequeue);
+	XHCI_EP_DCS_SET(*ctx, 1);
+	// TODO: max ESIT payload
 }
 
@@ -92,8 +188,87 @@
 	assert(!ep->device);
 
+	int err;
+	xhci_input_ctx_t *ictx = NULL;
+	xhci_trb_ring_t *ep_ring = NULL;
+	if (ep->base.target.endpoint > 0) {
+		// FIXME: Retrieve this from somewhere, if applicable.
+		usb_superspeed_endpoint_companion_descriptor_t ss_desc;
+		memset(&ss_desc, 0, sizeof(ss_desc));
+
+		// Prepare input context.
+		ictx = malloc(sizeof(xhci_input_ctx_t));
+		if (!ictx) {
+			return ENOMEM;
+		}
+
+		memset(ictx, 0, sizeof(xhci_input_ctx_t));
+
+		// Quoting sec. 4.6.6: A1, D0, D1 are down, A0 is up.
+		XHCI_INPUT_CTRL_CTX_ADD_CLEAR(ictx->ctrl_ctx, 1);
+		XHCI_INPUT_CTRL_CTX_DROP_CLEAR(ictx->ctrl_ctx, 0);
+		XHCI_INPUT_CTRL_CTX_DROP_CLEAR(ictx->ctrl_ctx, 1);
+		XHCI_INPUT_CTRL_CTX_ADD_SET(ictx->ctrl_ctx, 0);
+
+		const uint8_t ep_offset = xhci_endpoint_ctx_offset(ep);
+		XHCI_INPUT_CTRL_CTX_ADD_SET(ictx->ctrl_ctx, ep_offset);
+
+		ep_ring = malloc(sizeof(xhci_trb_ring_t));
+		if (!ep_ring) {
+			err = ENOMEM;
+			goto err_ictx;
+		}
+
+		// FIXME: This ring need not be allocated all the time.
+		err = xhci_trb_ring_init(ep_ring);
+		if (err)
+			goto err_ring;
+
+		switch (ep->base.transfer_type) {
+		case USB_TRANSFER_CONTROL:
+			setup_control_ep_ctx(ep, &ictx->endpoint_ctx[ep_offset], ep_ring);
+			break;
+
+		case USB_TRANSFER_BULK:
+			setup_bulk_ep_ctx(ep, &ictx->endpoint_ctx[ep_offset], ep_ring, &ss_desc);
+			break;
+
+		case USB_TRANSFER_ISOCHRONOUS:
+			setup_isoch_ep_ctx(ep, &ictx->endpoint_ctx[ep_offset], ep_ring, &ss_desc);
+			break;
+
+		case USB_TRANSFER_INTERRUPT:
+			setup_interrupt_ep_ctx(ep, &ictx->endpoint_ctx[ep_offset], ep_ring, &ss_desc);
+			break;
+
+		}
+
+		dev->hc->dcbaa_virt[dev->slot_id].trs[ep->base.target.endpoint] = ep_ring;
+
+		// Issue configure endpoint command (sec 4.3.5).
+		xhci_cmd_t cmd;
+		xhci_cmd_init(&cmd);
+
+		cmd.slot_id = dev->slot_id;
+		xhci_send_configure_endpoint_command(dev->hc, &cmd, ictx);
+		if ((err = xhci_cmd_wait(&cmd, 100000)) != EOK)
+			goto err_cmd;
+
+		xhci_cmd_fini(&cmd);
+	}
+
 	ep->device = dev;
 	dev->endpoints[ep->base.target.endpoint] = ep;
 	++dev->active_endpoint_count;
 	return EOK;
+
+err_cmd:
+err_ring:
+	if (ep_ring) {
+		xhci_trb_ring_fini(ep_ring);
+		free(ep_ring);
+	}
+err_ictx:
+	free(ictx);
+	return err;
 }
 
@@ -103,4 +278,6 @@
 	assert(dev->endpoints[ep->base.target.endpoint]);
 	assert(dev == ep->device);
+
+	// TODO: Issue configure endpoint command to drop this endpoint.
 
 	ep->device = NULL;
Index: uspace/drv/bus/usb/xhci/endpoint.h
===================================================================
--- uspace/drv/bus/usb/xhci/endpoint.h	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/endpoint.h	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -49,6 +49,4 @@
 typedef struct xhci_bus xhci_bus_t;
 
-#define XHCI_DEVICE_MAX_ENDPOINTS 32
-
 enum {
 	EP_TYPE_INVALID = 0,
@@ -81,8 +79,14 @@
 
 	/** All endpoints of the device. Inactive ones are NULL */
-	xhci_endpoint_t *endpoints[XHCI_DEVICE_MAX_ENDPOINTS];
+	xhci_endpoint_t *endpoints[XHCI_EP_COUNT];
 
 	/** Number of non-NULL endpoints. Reference count of sorts. */
 	uint8_t active_endpoint_count;
+
+	/** Need HC to schedule commands from bus callbacks. TODO: Move this elsewhere. */
+	xhci_hc_t *hc;
+
+	/** Flag indicating whether the device is USB3 (it's USB2 otherwise). */
+	bool usb3;
 } xhci_device_t;
 
Index: uspace/drv/bus/usb/xhci/hc.c
===================================================================
--- uspace/drv/bus/usb/xhci/hc.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/hc.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -201,8 +201,8 @@
 	}
 
-	if ((err = xhci_trb_ring_init(&hc->command_ring, hc)))
+	if ((err = xhci_trb_ring_init(&hc->command_ring)))
 		goto err_dcbaa_virt;
 
-	if ((err = xhci_event_ring_init(&hc->event_ring, hc)))
+	if ((err = xhci_event_ring_init(&hc->event_ring)))
 		goto err_cmd_ring;
 
@@ -462,5 +462,5 @@
 	assert(batch);
 
-	usb_log_debug2("EP(%d:%d) started %s transfer of size %lu.",
+	usb_log_debug2("Endpoint(%d:%d) started %s transfer of size %lu.",
 		batch->ep->target.address, batch->ep->target.endpoint,
 		usb_str_transfer_type(batch->ep->transfer_type),
@@ -482,7 +482,5 @@
 		return xhci_schedule_bulk_transfer(hc, batch);
 	case USB_TRANSFER_INTERRUPT:
-		/* TODO: Implement me. */
-		usb_log_error("Interrupt transfers are not yet implemented!");
-		return ENOTSUP;
+		return xhci_schedule_interrupt_transfer(hc, batch);
 	}
 
Index: uspace/drv/bus/usb/xhci/rh.c
===================================================================
--- uspace/drv/bus/usb/xhci/rh.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/rh.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -120,5 +120,5 @@
 	}
 
-	err = xhci_trb_ring_init(ep_ring, hc);
+	err = xhci_trb_ring_init(ep_ring);
 	if (err)
 		goto err_ring;
@@ -181,4 +181,6 @@
 	xhci_dev->device = dev;
 	xhci_dev->slot_id = slot_id;
+	xhci_dev->usb3 = speed->major == 3;
+	xhci_dev->hc = hc;
 
 	// TODO: Save anything else?
Index: uspace/drv/bus/usb/xhci/transfers.c
===================================================================
--- uspace/drv/bus/usb/xhci/transfers.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/transfers.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -266,5 +266,6 @@
 }
 
-int xhci_schedule_bulk_transfer(xhci_hc_t* hc, usb_transfer_batch_t* batch) {
+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.");
@@ -276,5 +277,8 @@
 
 	xhci_transfer_t *transfer = xhci_transfer_alloc(batch);
-	memcpy(transfer->hc_buffer, batch->buffer, batch->buffer_size);
+	if (!transfer->direction) {
+		// Sending stuff from host to device, we need to copy the actual data.
+		memcpy(transfer->hc_buffer, batch->buffer, batch->buffer_size);
+	}
 
 	xhci_trb_t trb;
@@ -300,4 +304,47 @@
 }
 
+int xhci_schedule_interrupt_transfer(xhci_hc_t* hc, usb_transfer_batch_t* batch)
+{
+	/* FIXME: Removing the next 2 rows causes QEMU to crash lol */
+	usb_log_warning("Interrupt transfers not yet implemented!");
+	return ENOTSUP;
+
+	if (batch->setup_size) {
+		usb_log_warning("Setup packet present for a interrupt transfer.");
+	}
+
+	xhci_endpoint_t *xhci_ep = xhci_endpoint_get(batch->ep);
+	uint8_t slot_id = xhci_ep->device->slot_id;
+	xhci_trb_ring_t* ring = hc->dcbaa_virt[slot_id].trs[batch->ep->target.endpoint];
+
+	xhci_transfer_t *transfer = xhci_transfer_alloc(batch);
+	if (!transfer->direction) {
+		// Sending stuff from host to device, we need to copy the actual data.
+		memcpy(transfer->hc_buffer, batch->buffer, batch->buffer_size);
+	}
+
+	xhci_trb_t trb;
+	memset(&trb, 0, sizeof(xhci_trb_t));
+	trb.parameter = addr_to_phys(transfer->hc_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);
+
+	const uint8_t target = 2 * batch->ep->target.endpoint
+		+ (batch->ep->direction == USB_DIRECTION_IN ? 1 : 0);
+	usb_log_debug("Ringing doorbell for slot_id = %d, target = %d", slot_id, target);
+	return hc_ring_doorbell(hc, slot_id, target);
+}
+
 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 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/transfers.h	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -54,3 +54,4 @@
 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_schedule_interrupt_transfer(xhci_hc_t*, usb_transfer_batch_t*);
 int xhci_handle_transfer_event(xhci_hc_t*, xhci_trb_t*);
Index: uspace/drv/bus/usb/xhci/trb_ring.c
===================================================================
--- uspace/drv/bus/usb/xhci/trb_ring.c	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/trb_ring.c	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -92,5 +92,5 @@
  * Event when it fails, the structure needs to be finalized.
  */
-int xhci_trb_ring_init(xhci_trb_ring_t *ring, xhci_hc_t *hc)
+int xhci_trb_ring_init(xhci_trb_ring_t *ring)
 {
 	struct trb_segment *segment;
@@ -237,5 +237,5 @@
  * Even when it fails, the structure needs to be finalized.
  */
-int xhci_event_ring_init(xhci_event_ring_t *ring, xhci_hc_t *hc)
+int xhci_event_ring_init(xhci_event_ring_t *ring)
 {
 	struct trb_segment *segment;
Index: uspace/drv/bus/usb/xhci/trb_ring.h
===================================================================
--- uspace/drv/bus/usb/xhci/trb_ring.h	(revision 816f5f48c86652a41469235b97f890a3275083a0)
+++ uspace/drv/bus/usb/xhci/trb_ring.h	(revision 9b2f69e92c3d7f9c914f9671cf6478585b138d55)
@@ -73,5 +73,5 @@
 } xhci_trb_ring_t;
 
-int xhci_trb_ring_init(xhci_trb_ring_t *, xhci_hc_t *);
+int xhci_trb_ring_init(xhci_trb_ring_t *);
 int xhci_trb_ring_fini(xhci_trb_ring_t *);
 int xhci_trb_ring_enqueue(xhci_trb_ring_t *, xhci_trb_t *, uintptr_t *);
@@ -110,5 +110,5 @@
 } xhci_event_ring_t;
 
-int xhci_event_ring_init(xhci_event_ring_t *, xhci_hc_t *);
+int xhci_event_ring_init(xhci_event_ring_t *);
 int xhci_event_ring_fini(xhci_event_ring_t *);
 int xhci_event_ring_dequeue(xhci_event_ring_t *, xhci_trb_t *);
