Index: uspace/drv/nic/rtl8169/defs.h
===================================================================
--- uspace/drv/nic/rtl8169/defs.h	(revision 0764cc8a36c4a2046d7e67f9f6d923869dd12d71)
+++ uspace/drv/nic/rtl8169/defs.h	(revision 59b3095656fce3d8f29777d27bf531042dca1c58)
@@ -42,4 +42,6 @@
 /** Size of RTL8169 registers address space */
 #define RTL8169_IO_SIZE  256
+
+#define	RTL8169_FRAME_MAX_LENGTH	1518
 
 /** Registers of RTL8169 family card offsets from the memory address base */
@@ -254,10 +256,36 @@
 };
 
-struct rtl8169_descr {
+enum rtl8169_tppoll {
+	TPPOLL_HPQ = (1 << 7), /**< Start transmit on high priority queue */
+	TPPOLL_NPQ = (1 << 6), /**< Start transmit on normal queue */
+	/* Bits 5-1 reserved */
+	TPPOLL_FSWINT = (1 << 0), /** < Generate software interrupt */
+};
+
+enum rtl8169_descr_control {
+	CONTROL_OWN = (1 << 31), /**< Descriptor ownership */
+	CONTROL_EOR = (1 << 30), /**< End Of Ring marker */
+	CONTROL_FS = (1 << 29), /**< First Segment marker */
+	CONTROL_LS = (1 << 28), /**< Last Segment marker */
+	CONTROL_LGSEN = (1 << 27), /**< Large send enable */
+	CONTROL_MSS_SHIFT = 16,
+	CONTROL_MSS_MASK = 10,
+	CONTROL_FRAMELEN_MASK = 0xffff
+};
+
+enum rtl8169_descr_txstatus {
+	TXSTATUS_UNDERRUN = (1 << 25),
+	TXSTATUS_TXERRSUM = (1 << 23),
+	TXSTATUS_OWINCOL = (1 << 22),
+	TXSTATUS_LINKFAIL = (1 << 21),
+	TXSTATUS_EXCESSCOL = (1 << 20)
+};
+
+typedef struct rtl8169_descr {
 	uint32_t	control;
 	uint32_t	vlan;
 	uint32_t	buf_low;
 	uint32_t	buf_high;
-};
+} rtl8169_descr_t;
 
 #endif
Index: uspace/drv/nic/rtl8169/driver.c
===================================================================
--- uspace/drv/nic/rtl8169/driver.c	(revision 0764cc8a36c4a2046d7e67f9f6d923869dd12d71)
+++ uspace/drv/nic/rtl8169/driver.c	(revision 59b3095656fce3d8f29777d27bf531042dca1c58)
@@ -209,4 +209,5 @@
 		return EINVAL;
 	};
+
 	if (hw_resources->io_ranges.count != 1) {
 		ddf_msg(LVL_ERROR, "%s device: unexpected io ranges count", ddf_dev_get_name(dev));
@@ -237,5 +238,5 @@
 	rc = dmamem_map_anonymous(TX_RING_SIZE, DMAMEM_4GiB, 
 	    AS_AREA_READ | AS_AREA_WRITE, 0, &rtl8169->tx_ring_phys,
-	    &rtl8169->tx_ring_virt);
+	    (void **)&rtl8169->tx_ring);
 
 	if (rc != EOK)
@@ -245,5 +246,5 @@
 	rc = dmamem_map_anonymous(RX_RING_SIZE, DMAMEM_4GiB, 
 	    AS_AREA_READ | AS_AREA_WRITE, 0, &rtl8169->rx_ring_phys,
-	    &rtl8169->rx_ring_virt);
+	    (void **)&rtl8169->rx_ring);
 
 	if (rc != EOK)
@@ -253,5 +254,5 @@
 	rc = dmamem_map_anonymous(TX_BUFFERS_SIZE, DMAMEM_4GiB, 
 	    AS_AREA_READ | AS_AREA_WRITE, 0, &rtl8169->tx_buff_phys,
-	    &rtl8169->tx_buff_virt);
+	    &rtl8169->tx_buff);
 
 	if (rc != EOK)
@@ -261,8 +262,10 @@
 	rc = dmamem_map_anonymous(RX_BUFFERS_SIZE, DMAMEM_4GiB, 
 	    AS_AREA_READ | AS_AREA_WRITE, 0, &rtl8169->rx_buff_phys,
-	    &rtl8169->rx_buff_virt);
+	    &rtl8169->rx_buff);
 
 	if (rc != EOK)
 		return rc;
+
+	return EOK;
 }
 
@@ -521,4 +524,14 @@
 	rtl8169_reset(rtl8169);
 
+	/* Allocate buffers */
+	rtl8169_allocate_buffers(rtl8169);
+
+	/* Write address of descriptor as start of TX ring */
+	pio_write_32(rtl8169->regs + TNPDS, rtl8169->tx_ring_phys & 0xffffffff);
+	pio_write_32(rtl8169->regs + TNPDS + 4, (rtl8169->tx_ring_phys >> 32) & 0xffffffff);
+	rtl8169->tx_head = 0;
+	rtl8169->tx_tail = 0;
+	rtl8169->tx_ring[15].control = CONTROL_EOR;
+
 	/* Enable TX and RX */
 	uint8_t cr = pio_read_8(rtl8169->regs + CR);
@@ -594,5 +607,29 @@
 }
 
-
+static void rtl8169_transmit_done(ddf_dev_t *dev)
+{
+	unsigned int tail, head;
+	nic_t *nic_data = nic_get_from_ddf_dev(dev);
+	rtl8169_t *rtl8169 = nic_get_specific(nic_data);
+	rtl8169_descr_t *descr;
+
+	ddf_msg(LVL_NOTE, "rtl8169_transmit_done()");
+
+	fibril_mutex_lock(&rtl8169->tx_lock);
+
+	head = rtl8169->tx_head;
+	tail = rtl8169->tx_tail;
+
+	while (tail != head) {
+		descr = &rtl8169->tx_ring[tail];
+		tail = (tail + 1) % TX_BUFFERS_COUNT;
+
+		ddf_msg(LVL_NOTE, "TX status for descr %d: 0x%08x", tail, descr->control);
+	}
+
+	rtl8169->tx_tail = tail;
+
+	fibril_mutex_unlock(&rtl8169->tx_lock);
+}
 
 static void rtl8169_irq_handler(ddf_dev_t *dev, ipc_callid_t iid,
@@ -601,6 +638,4 @@
 	assert(dev);
 	assert(icall);
-
-	ddf_msg(LVL_NOTE, "rtl8169_irq_handler()");
 
 	uint16_t isr = (uint16_t) IPC_GET_ARG2(*icall);
@@ -608,8 +643,31 @@
 	rtl8169_t *rtl8169 = nic_get_specific(nic_data);
 
+	//ddf_msg(LVL_NOTE, "rtl8169_irq_handler(): isr=0x%04x", isr);
+
 	/* Packet underrun or link change */
 	if (isr & INT_PUN)
-		rtl8169_link_change(dev);	
-
+		rtl8169_link_change(dev);
+
+	/* Frame(s) successfully transmitted */
+	if (isr & INT_TOK)
+		rtl8169_transmit_done(dev);
+
+	/* Transmit error */
+	if (isr & INT_TER)
+		rtl8169_transmit_done(dev);
+
+	if (isr & INT_SERR)
+		ddf_msg(LVL_ERROR, "System error interrupt");
+
+	if (isr & INT_TDU)
+		ddf_msg(LVL_ERROR, "Transmit descriptor unavailable");
+
+	if (isr & INT_RER)
+		ddf_msg(LVL_NOTE, "RX error interrupt");
+
+	if (isr & INT_ROK)
+		ddf_msg(LVL_NOTE, "RX OK interrupt");
+
+	pio_write_16(rtl8169->regs + ISR, 0xffff);
 	pio_write_16(rtl8169->regs + IMR, 0xffff);
 }
@@ -617,5 +675,60 @@
 static void rtl8169_send_frame(nic_t *nic_data, void *data, size_t size)
 {
-
+	rtl8169_descr_t *descr, *prev;
+	unsigned int head, tail;
+	void *buff;
+	uintptr_t buff_phys;
+	rtl8169_t *rtl8169 = nic_get_specific(nic_data);
+
+	if (size > RTL8169_FRAME_MAX_LENGTH) {
+		ddf_msg(LVL_ERROR, "Send frame: frame too long, %zu bytes",
+		    size);
+		nic_report_send_error(nic_data, NIC_SEC_OTHER, 1);
+	}
+
+	fibril_mutex_lock(&rtl8169->tx_lock);
+
+	ddf_msg(LVL_NOTE, "send_frame()");
+	ddf_msg(LVL_NOTE, "tx ring virtual at %p", rtl8169->tx_ring);
+	ddf_msg(LVL_NOTE, "tx ring physical at 0x%08lx", rtl8169->tx_ring_phys);
+	ddf_msg(LVL_NOTE, "tx_head=%d tx_tail=%d", rtl8169->tx_head, rtl8169->tx_tail);
+
+	head = rtl8169->tx_head;
+	tail = rtl8169->tx_tail;
+
+	if ((tail + 1) % TX_BUFFERS_COUNT == head) {
+		/* Queue is full */
+		nic_set_tx_busy(nic_data, 1);
+	}
+
+	/* Calculate address of next free buffer and descriptor */
+	buff = rtl8169->tx_buff + (BUFFER_SIZE * head);
+	buff_phys = rtl8169->tx_buff_phys + (BUFFER_SIZE * head);
+
+	/* Copy frame */
+	memcpy(buff, data, size);
+
+	/* Setup descriptor */
+	descr = &rtl8169->tx_ring[head];
+	prev = &rtl8169->tx_ring[(head - 1) % TX_BUFFERS_COUNT];
+
+	ddf_msg(LVL_NOTE, "current_descr=%p, prev_descr=%p", descr, prev);
+
+	descr->control = CONTROL_OWN | CONTROL_FS | CONTROL_LS;
+	descr->control |= size & 0xffff;
+	descr->vlan = 0;
+	descr->buf_low = buff_phys & 0xffffffff;
+	descr->buf_high = (buff_phys >> 32) & 0xffffffff;
+	rtl8169->tx_head = (head + 1) % TX_BUFFERS_COUNT;
+
+	/* Lift EOR flag from previous descriptor */
+	prev->control &= ~CONTROL_EOR;
+
+	write_barrier();
+
+	/* Notify NIC of pending packets */
+	pio_write_8(rtl8169->regs + TPPOLL, TPPOLL_NPQ);
+
+	fibril_mutex_unlock(&rtl8169->tx_lock);
 }
 
Index: uspace/drv/nic/rtl8169/driver.h
===================================================================
--- uspace/drv/nic/rtl8169/driver.h	(revision 0764cc8a36c4a2046d7e67f9f6d923869dd12d71)
+++ uspace/drv/nic/rtl8169/driver.h	(revision 59b3095656fce3d8f29777d27bf531042dca1c58)
@@ -40,7 +40,10 @@
 #define	TX_BUFFERS_COUNT	16
 #define	RX_BUFFERS_COUNT	16
+#define	BUFFER_SIZE		2048
 
 #define	TX_RING_SIZE		(sizeof(rtl8169_descr_t) * TX_BUFFERS_COUNT)
 #define	RX_RING_SIZE		(sizeof(rtl8169_descr_t) * RX_BUFFERS_COUNT)
+#define	TX_BUFFERS_SIZE		(BUFFER_SIZE * TX_BUFFERS_COUNT)
+#define	RX_BUFFERS_SIZE		(BUFFER_SIZE * RX_BUFFERS_COUNT)
 
 /** RTL8139 device data */
@@ -56,11 +59,15 @@
 	/** TX ring */
 	uintptr_t tx_ring_phys;
-	void *tx_ring_virt;
+	rtl8169_descr_t *tx_ring;
+	unsigned int tx_head;
+	unsigned int tx_tail;
 	/** RX ring */
 	uintptr_t rx_ring_phys;
-	void *rx_ring_virt;
+	rtl8169_descr_t *rx_ring;
+	unsigned int rx_head;
+	unsigned int rx_tail;
 	/** TX buffers */
 	uintptr_t tx_buff_phys;
-	void *tx_buff_virt;
+	void *tx_buff;
 	/** RX buffers */
 	uintptr_t rx_buff_phys;
@@ -76,8 +83,4 @@
 	size_t tx_used;
 
-	/** Buffer for receiving frames */
-	uintptr_t rx_buff_phys;
-	void *rx_buff_virt;
-
 	/** Lock for receiver */
 	fibril_mutex_t rx_lock;
