Index: uspace/drv/nic/virtio-net/virtio-net.c
===================================================================
--- uspace/drv/nic/virtio-net/virtio-net.c	(revision 3d135e92dfa5eada208677984fcd67d69711f95b)
+++ uspace/drv/nic/virtio-net/virtio-net.c	(revision b8ef198b865bb1e67719113decaabfc4685fe25c)
@@ -69,9 +69,4 @@
 };
 
-static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
-{
-	ddf_msg(LVL_NOTE, "Got interrupt");
-}
-
 static errno_t virtio_net_setup_bufs(unsigned int buffers, size_t size,
     bool write, void *buf[], uintptr_t buf_p[])
@@ -125,4 +120,53 @@
 		*head = virtio_virtq_desc_get_next(vdev, num, descno);
 	return descno;
+}
+
+static void virtio_net_free_buf(virtio_dev_t *vdev, uint16_t num,
+    uint16_t *head, uint16_t descno)
+{
+	virtio_virtq_desc_set(vdev, num, descno, 0, 0, VIRTQ_DESC_F_NEXT,
+	    *head);
+	*head = descno;
+}
+
+static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
+{
+	nic_t *nic = ddf_dev_data_get(dev);
+	virtio_net_t *virtio_net = nic_get_specific(nic);
+	virtio_dev_t *vdev = &virtio_net->virtio_dev;
+
+	uint16_t descno;
+	uint32_t len;
+	while (virtio_virtq_consume_used(vdev, RX_QUEUE_1, &descno, &len)) {
+		virtio_net_hdr_t *hdr =
+		    (virtio_net_hdr_t *) virtio_net->rx_buf[descno];
+		if (len <= sizeof(*hdr)) {
+			ddf_msg(LVL_WARN,
+			    "RX data length too short, packet dropped");
+			virtio_virtq_produce_available(vdev, RX_QUEUE_1,
+			    descno);
+			continue;
+		}
+
+		nic_frame_t *frame = nic_alloc_frame(nic, len - sizeof(*hdr));
+		if (frame) {
+			memcpy(frame->data, &hdr[1], len - sizeof(*hdr));
+			nic_received_frame(nic, frame);
+		} else {
+			ddf_msg(LVL_WARN,
+			    "Cannot allocate RX frame, packet dropped");
+		}
+
+		virtio_virtq_produce_available(vdev, RX_QUEUE_1, descno);
+	}
+
+	while (virtio_virtq_consume_used(vdev, TX_QUEUE_1, &descno, &len)) {
+		virtio_net_free_buf(vdev, TX_QUEUE_1, &virtio_net->tx_free_head,
+		    descno);
+	}
+	while (virtio_virtq_consume_used(vdev, CT_QUEUE_1, &descno, &len)) {
+		virtio_net_free_buf(vdev, CT_QUEUE_1, &virtio_net->ct_free_head,
+		    descno);
+	}
 }
 
@@ -365,5 +409,5 @@
 	hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
 
-	/* Copy packet data into the buffer just past the header */ 
+	/* Copy packet data into the buffer just past the header */
 	memcpy(&hdr[1], data, size);
 
Index: uspace/lib/virtio/virtio-pci.h
===================================================================
--- uspace/lib/virtio/virtio-pci.h	(revision 3d135e92dfa5eada208677984fcd67d69711f95b)
+++ uspace/lib/virtio/virtio-pci.h	(revision b8ef198b865bb1e67719113decaabfc4685fe25c)
@@ -143,4 +143,5 @@
 	/** Virtual address of the used ring */
 	virtq_used_t *used;
+	uint16_t used_last_idx;
 
 	/** Address of the queue's notification register */
@@ -182,4 +183,6 @@
 
 extern void virtio_virtq_produce_available(virtio_dev_t *, uint16_t, uint16_t);
+extern bool virtio_virtq_consume_used(virtio_dev_t *, uint16_t, uint16_t *,
+    uint32_t *);
 
 extern errno_t virtio_virtq_setup(virtio_dev_t *, uint16_t, uint16_t);
Index: uspace/lib/virtio/virtio.c
===================================================================
--- uspace/lib/virtio/virtio.c	(revision 3d135e92dfa5eada208677984fcd67d69711f95b)
+++ uspace/lib/virtio/virtio.c	(revision b8ef198b865bb1e67719113decaabfc4685fe25c)
@@ -71,4 +71,21 @@
 }
 
+bool virtio_virtq_consume_used(virtio_dev_t *vdev, uint16_t num,
+    uint16_t *descno, uint32_t *len)
+{
+	virtq_t *q = &vdev->queues[num];
+
+	uint16_t last_idx = q->used_last_idx % q->queue_size;
+	if (last_idx == (pio_read_le16(&q->used->idx) % q->queue_size))
+		return false;
+
+	*descno = (uint16_t) pio_read_le32(&q->used->ring[last_idx].id);
+	*len = pio_read_le32(&q->used->ring[last_idx].len);
+
+	q->used_last_idx++;
+
+	return true;
+}
+
 errno_t virtio_virtq_setup(virtio_dev_t *vdev, uint16_t num, uint16_t size)
 {
@@ -120,4 +137,5 @@
 	q->avail = q->virt + avail_offset;
 	q->used = q->virt + used_offset;
+	q->used_last_idx = 0;
 
 	memset(q->virt, 0, q->size);
