summaryrefslogtreecommitdiff
path: root/src/kernel/arch/amd64/driver/rtl8139.c
diff options
context:
space:
mode:
authordzwdz2022-08-17 12:49:34 +0200
committerdzwdz2022-08-17 12:49:34 +0200
commit2341a0705164e94d0874572505b60680fdbe631f (patch)
treeaea0bcd5c2339076b10d9e416ea1af4d1fb48400 /src/kernel/arch/amd64/driver/rtl8139.c
parent63fd7ce362c7f1d59365045f19cf1ca87ffe2db9 (diff)
amd64: rtl8139 driver with basic rx support
Diffstat (limited to 'src/kernel/arch/amd64/driver/rtl8139.c')
-rw-r--r--src/kernel/arch/amd64/driver/rtl8139.c150
1 files changed, 150 insertions, 0 deletions
diff --git a/src/kernel/arch/amd64/driver/rtl8139.c b/src/kernel/arch/amd64/driver/rtl8139.c
new file mode 100644
index 0000000..d9e5af0
--- /dev/null
+++ b/src/kernel/arch/amd64/driver/rtl8139.c
@@ -0,0 +1,150 @@
+#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, pktsize;
+ /* 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();
+ pktsize = size - 4;
+
+ // kprintf("packet size 0x%x, flags 0x%x, rxpos %x\n", size, flags, rxpos - 4);
+ virt_cpy_to(pages, dest, rxbuf + rxpos, pktsize);
+
+ 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 pktsize;
+}
+
+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;
+ }
+}