From 2341a0705164e94d0874572505b60680fdbe631f Mon Sep 17 00:00:00 2001 From: dzwdz Date: Wed, 17 Aug 2022 12:49:34 +0200 Subject: amd64: rtl8139 driver with basic rx support --- src/kernel/arch/amd64/driver/rtl8139.c | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/kernel/arch/amd64/driver/rtl8139.c (limited to 'src/kernel/arch/amd64/driver/rtl8139.c') 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 +#include +#include +#include +#include +#include +#include + +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; + } +} -- cgit v1.2.3