summaryrefslogtreecommitdiff
path: root/src/kernel/arch/amd64/driver/rtl8139.c
blob: 3f6d07f97f0358a0c745a7262815255dd8ca0838 (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
#include <kernel/arch/amd64/driver/rtl8139.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/vfs/request.h>
#include <stdbool.h>

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


enum {
	MAC = 0,
	RBSTART = 0x30,
	CMD = 0x37,
	CAPR = 0x38,
	CBR = 0x3A,
	INTRMASK = 0x3C,
	INTRSTATUS = 0x3E,
	RCR = 0x44, /* receive configure */
	CONFIG1 = 0x52,
};

static uint16_t iobase;

#define buflen_shift 3
#define rxbuf_baselen ((8 * 1024) << buflen_shift)
static char rxbuf[rxbuf_baselen + 16 + 1500];
static size_t rxpos;

static void rx_irq_enable(bool v) {
	uint16_t mask = 1 | 4; /* rx/tx 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);

	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 |= 1 << 7; /* WARP */
	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_mount_root_register("/eth", &backend);
}

void rtl8139_irq(void) {
	uint16_t status = port_in16(iobase + INTRSTATUS);
	// TODO don't assume this is an rx irq

	do {
		if (blocked_on) {
			accept(blocked_on);
			blocked_on = blocked_on->postqueue_next;
		} else {
			rx_irq_enable(false);
			break;
		}
	} while (!(port_in8(iobase + CMD) & 1)); /* bit 0 - Rx Buffer Empty */

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

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

	/* 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);
	virt_cpy_to(pages, dest, rxbuf + rxpos, size);

	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 void accept(struct vfs_request *req) {
	switch (req->type) {
		long ret;
		case VFSOP_OPEN:
			vfsreq_finish_short(req, req->input.len == 0 ? 0 : -1);
			break;
		case VFSOP_READ:
			if (!req->caller) {
				vfsreq_finish_short(req, -1);
				break;
			}
			ret = try_rx(req->caller->pages, req->output.buf, req->output.len);
			if (ret < 0) {
				// TODO this is a pretty common pattern in drivers, try to make it unneeded
				assert(!req->postqueue_next);
				struct vfs_request **slot = &blocked_on;
				while (*slot) slot = &(*slot)->postqueue_next;
				*slot = req;
				rx_irq_enable(true);
			} else {
				vfsreq_finish_short(req, ret);
			}
			break;
		default:
			vfsreq_finish_short(req, -1);
			break;
	}
}