summaryrefslogtreecommitdiff
path: root/src/kernel/arch/amd64/driver/rtl8139.c
blob: 1face380941ab530166d07f88f2163397c11aec9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <kernel/arch/amd64/driver/rtl8139.h>
#include <kernel/arch/amd64/driver/util.h>
#include <kernel/arch/amd64/pci.h>
#include <kernel/arch/amd64/port_io.h>
#include <kernel/mem/virt.h>
#include <kernel/panic.h>
#include <kernel/proc.h>
#include <kernel/vfs/request.h>
#include <stdbool.h>

#define WAIT -1000

static void accept(struct vfs_request *req);
static struct vfs_request *blocked_on = NULL;


enum {
	MAC = 0,
	TXSTATUS0 = 0x10,
	TXSTART0 = 0x20,
	RBSTART = 0x30,
	CMD = 0x37,
	CAPR = 0x38,
	CBR = 0x3A,
	INTRMASK = 0x3C,
	INTRSTATUS = 0x3E,
	TCR = 0x40,
	RCR = 0x44,
	CONFIG1 = 0x52,
};

static uint16_t iobase;

#define buflen_shift 3
#define rxbuf_baselen ((8 * 1024) << buflen_shift)
/* the +16 is apparently required for... something */
static char rxbuf[rxbuf_baselen + 16];
static size_t rxpos;

#define txbuf_len 2048
static char txbuf[4][txbuf_len];

static void rx_irq_enable(bool v) {
	uint16_t mask = 1; /* rx ok */
	port_out16(iobase + INTRMASK, v ? mask : 0);
}

void rtl8139_init(uint32_t bdf) {
	if (iobase) panic_unimplemented(); /* multiple devices */
	iobase = pcicfg_iobase(bdf);

	/* also includes the status, because i have only implemented w32 */
	uint32_t cmd = pcicfg_r32(bdf, PCICFG_CMD);
	cmd |= 1 << 2; /* bus mastering */
	pcicfg_w32(bdf, PCICFG_CMD, cmd);


	port_out8(iobase + CONFIG1, 0); /* power on */

	port_out8(iobase + CMD, 0x10); /* software reset */
	while (port_in8(iobase + CMD) & 0x10);

	assert((long)(void*)rxbuf <= 0xFFFFFFFF);
	port_out32(iobase + RBSTART, (long)(void*)rxbuf);

	port_out32(iobase + TCR, 0);

	uint32_t rcr = 0;
	// rcr |= 1 << 0; /* accept all packets */
	rcr |= 1 << 1; /* accept packets with our mac */
	rcr |= 1 << 2; /* accept multicast */
	rcr |= 1 << 3; /* accept broadcast */
	rcr |= buflen_shift << 11;
	rcr |= 7 << 13; /* no rx threshold, copy whole packets */
	port_out32(iobase + RCR, rcr);

	port_out8(iobase + CMD, 0xC); /* enable RX TX */

	rx_irq_enable(false);

	uint64_t mac = (((uint64_t)port_in32(iobase + MAC + 4) & 0xFFFF) << 32) + port_in32(iobase + MAC);
	kprintf("rtl8139 mac %012x\n", mac);

	vfs_root_register("/eth", accept);
}

void rtl8139_irq(void) {
	uint16_t status = port_in16(iobase + INTRSTATUS);
	if (!(status & 1)) {
		kprintf("bad rtl8139 status 0x%x\n", status);
		panic_unimplemented();
	}
	status &= 1;

	/* bit 0 of cmd - Rx Buffer Empty
	 * not a do while() because sometimes the bit is empty on IRQ. no clue why. */
	while (!(port_in8(iobase + CMD) & 1)) {
		if (!postqueue_pop(&blocked_on, accept)) {
			rx_irq_enable(false);
			break;
		}
	}

	//kprintf("rxpos %x cbr %x\n", rxpos, port_in16(iobase + CBR));
	port_out16(iobase + INTRSTATUS, status);
}

static int try_rx(struct process *proc, void __user *dest, size_t dlen) {
	uint16_t flags, size;
	/* bit 0 - Rx Buffer Empty */
	if (port_in8(iobase + CMD) & 1) return WAIT;

	/* https://github.com/qemu/qemu/blob/04ddcda6a/hw/net/rtl8139.c#L1169 */
	/* https://www.cs.usfca.edu/~cruse/cs326f04/RTL8139D_DataSheet.pdf page 12
	 * bits of interest:
	 *  0 - Receive OK
	 * 14 - Physical Address Matched */
	flags = *(uint16_t*)(rxbuf + rxpos);
	rxpos += 2;
	/* doesn't include the header, includes a 4 byte crc */
	size = *(uint16_t*)(rxbuf + rxpos);
	rxpos += 2;
	if (size == 0) panic_invalid_state();

	// kprintf("packet size 0x%x, flags 0x%x, rxpos %x\n", size, flags, rxpos - 4);
	if (dlen > size) dlen = size;
	if (rxpos + dlen <= rxbuf_baselen) {
		pcpy_to(proc, dest, rxbuf + rxpos, dlen);
	} else {
		size_t chunk = rxbuf_baselen - rxpos;
		pcpy_to(proc, dest, rxbuf + rxpos, chunk);
		pcpy_to(proc, dest + chunk, rxbuf, dlen - chunk);
	}

	rxpos += size;
	rxpos = (rxpos + 3) & ~3;
	while (rxpos >= rxbuf_baselen) rxpos -= rxbuf_baselen;
	/* the device adds the 0x10 back, it's supposed to avoid overflow */
	port_out16(iobase + CAPR, rxpos - 0x10);
	return size;
}

static int try_tx(struct process *proc, const void __user *src, size_t slen) {
	static uint8_t desc = 0;

	if (slen > 0xFFF) return -1;
	if (slen > txbuf_len) return -1;

	uint32_t status = port_in32(iobase + TXSTATUS0 + desc*4);
	if (!(status & (1<<13))) {
		/* can't (?) be caused (and thus, tested) on a vm */
		kprintf("try_tx called with all descriptors full.");
		panic_unimplemented();
	}

	if (pcpy_from(proc, txbuf[desc], src, slen) < slen) {
		return -1;
	}
	assert((long)(void*)txbuf <= 0xFFFFFFFF);
	port_out32(iobase + TXSTART0  + desc*4, (long)(void*)txbuf[desc]);
	port_out32(iobase + TXSTATUS0 + desc*4, slen);

	desc = (desc + 1) & 3;
	return slen;
}

static void accept(struct vfs_request *req) {
	if (!req->caller) {
		vfsreq_finish_short(req, -1);
		return;
	}
	switch (req->type) {
		long ret;
		case VFSOP_OPEN:
			vfsreq_finish_short(req, req->input.len == 0 ? 0 : -1);
			break;
		case VFSOP_READ:
			ret = try_rx(req->caller, req->output.buf, req->output.len);
			if (ret == WAIT) {
				postqueue_join(&blocked_on, req);
				rx_irq_enable(true);
			} else {
				vfsreq_finish_short(req, ret);
			}
			break;
		case VFSOP_WRITE:
			assert(!req->input.kern);
			vfsreq_finish_short(req, try_tx(req->caller, req->input.buf, req->input.len));
			break;
		default:
			vfsreq_finish_short(req, -1);
			break;
	}
}