#include #include #include #include #include #include #include #include #include #include #define WAIT -1000 static void accept(VfsReq *req); static void rtl8139_irq(void); static VfsReq *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 uint8_t mac[6]; 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); irq_fn[IRQ_RTL8139] = rtl8139_irq; /* 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); for (int i = 0; i < 6; i++) { mac[i] = port_in8(iobase + Mac + i); } vfs_root_register("/dev/eth/", accept); } static 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(Proc *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); (void)flags; // TODO check rtl8139 rx flags 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(Proc *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; } enum { HandleRoot, HandleNet, HandleMac, }; static void accept(VfsReq *req) { long ret; long id = (long __force)req->id; if (!req->caller) { vfsreq_finish_short(req, -1); return; } switch (req->type) { case VFSOP_OPEN: if (reqpathcmp(req, "")) ret = HandleRoot; else if (reqpathcmp(req, "net")) ret = HandleNet; else if (reqpathcmp(req, "mac")) ret = HandleMac; else ret = -ENOENT; vfsreq_finish_short(req, ret); break; case VFSOP_READ: if (id == HandleRoot) { const char data[] = "mac\0net"; ret = req_readcopy(req, data, sizeof data); vfsreq_finish_short(req, ret); } else if (id == HandleNet) { 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); } } else if (id == HandleMac) { ret = req_readcopy(req, mac, sizeof mac); vfsreq_finish_short(req, ret); } else panic_invalid_state(); break; case VFSOP_WRITE: if (id == HandleNet) { assert(!req->input.kern); vfsreq_finish_short(req, try_tx(req->caller, req->input.buf, req->input.len)); } else { vfsreq_finish_short(req, -ENOSYS); } break; default: vfsreq_finish_short(req, -1); break; } }