diff --git a/kernel-src/include/kernel/eth.h b/kernel-src/include/kernel/eth.h new file mode 100644 index 00000000..ed33dad7 --- /dev/null +++ b/kernel-src/include/kernel/eth.h @@ -0,0 +1,15 @@ +#ifndef _ETH_H +#define _ETH_H + +#include + +typedef struct { + uint8_t destination[6]; + uint8_t source[6]; + uint16_t type; +} __attribute__((packed)) ethframe_t; + +#define ETH_PROTO_IP 0x0800 +#define ETH_PROTO_ARP 0x0806 + +#endif diff --git a/kernel-src/include/kernel/net.h b/kernel-src/include/kernel/net.h new file mode 100644 index 00000000..7491009c --- /dev/null +++ b/kernel-src/include/kernel/net.h @@ -0,0 +1,67 @@ +#ifndef _NET_H +#define _NET_H + +#include +#include + +#include + +typedef struct { + void *address; + uintmax_t curroffset; + size_t size; +} netdesc_t; + +typedef struct { + uint8_t address[6]; +} __attribute__((packed)) mac_t; + +typedef struct netdev_t { + mac_t mac; + size_t mtu; + int (*allocdesc)(size_t requestedsize, netdesc_t *desc); + int (*sendpacket)(struct netdev_t *_internal, netdesc_t desc, mac_t targetmac, int proto); +} netdev_t; + +typedef struct { + uint16_t hwtype; + uint16_t protocoltype; + uint8_t hwlen; + uint8_t ptlen; + uint16_t opcode; + uint8_t srchw[6]; + uint8_t srcpt[4]; + uint8_t dsthw[6]; + uint8_t dstpt[4]; +} __attribute__((packed)) arpframe_t; + +typedef struct { + uint8_t version_length; + uint8_t servicetype_ecn; + uint16_t packetlen; + uint16_t id; + uint16_t flags_fragoffset; + uint8_t timetolive; + uint8_t protocol; + uint16_t framechecksum; + uint32_t srcaddr; + uint32_t dstaddr; +} __attribute__((packed)) ipv4frame_t; + +typedef struct { + uint16_t srcport; + uint16_t dstport; + uint16_t length; + uint16_t checksum; +} __attribute__((packed)) udpframe_t; + +#define NET_BROADCAST_MAC (mac_t){.address = {0xff,0xff,0xff,0xff,0xff,0xff}} + +#define IPV4_PROTO_UDP 0x11 + +int udp_allocdesc(netdev_t *netdev, size_t requestedsize, netdesc_t *desc); +int udp_sendpacket(netdev_t *netdev, netdesc_t desc, uint32_t ip, uint16_t srcport, uint16_t dstport); +int ipv4_allocdesc(netdev_t *netdev, size_t requestedsize, netdesc_t *desc); +int ipv4_sendpacket(netdev_t *netdev, netdesc_t desc, uint32_t ip, int proto); + +#endif diff --git a/kernel-src/io/net/vionet.c b/kernel-src/io/net/vionet.c new file mode 100644 index 00000000..9b02a184 --- /dev/null +++ b/kernel-src/io/net/vionet.c @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAC_FEATURE (1 << 5) +#define WANTED_FEATURES (VIO_FEATURE_VERSION_1 | MAC_FEATURE) + +#define QUEUE_MAX_SIZE 256 +#define BUFFER_SIZE 1526 + +typedef struct { + netdev_t netdev; + viodevice_t *viodevice; + vioqueue_t rxqueue; + vioqueue_t txqueue; + event_t *txwait[QUEUE_MAX_SIZE]; + semaphore_t txsem; + spinlock_t txlock; + dpc_t txdpc; + int id; +} vionetdev_t; + +typedef struct { + uint8_t mac[6]; +} __attribute__((packed)) vionetconfig_t; + +typedef struct { + uint8_t flags; + uint8_t gsotype; + uint16_t headerlen; + uint16_t gsosize; + uint16_t checksumstart; + uint16_t checksumcount; + uint16_t buffercount; +} __attribute__((packed)) vioframe_t; + +static hashtable_t irqtable; // isr id -> netdev + +static void rx_irq(isr_t *isr, context_t *context) { + __assert(!"rxirq"); +} + +static void tx_dpc(context_t *context, dpcarg_t arg) { + vionetdev_t *netdev = arg; + volatile viobuffer_t *buffers = VIO_QUEUE_BUFFERS(&netdev->txqueue); + spinlock_acquire(&netdev->txlock); + while (netdev->txqueue.lastusedindex != VIO_QUEUE_DEV_IDX(&netdev->txqueue)) { + int idx = netdev->txqueue.lastusedindex++ % netdev->txqueue.size; + int buffidx = VIO_QUEUE_DEV_RING(&netdev->txqueue)[idx].index; + __assert(netdev->txwait[buffidx / 2]); + event_signal(netdev->txwait[buffidx / 2]); + netdev->txwait[buffidx / 2] = NULL; + buffers[buffidx].address = 0; + semaphore_signal(&netdev->txsem); + } + spinlock_release(&netdev->txlock); +} + +static void tx_irq(isr_t *isr, context_t *context) { + void *v; + __assert(hashtable_get(&irqtable, &v, &isr->id, sizeof(isr->id)) == 0); + vionetdev_t *netdev = v; + dpc_enqueue(&netdev->txdpc, tx_dpc, netdev); +} + +#define PREFIX_SIZE (sizeof(ethframe_t) + sizeof(vioframe_t)) + +// requested size doesn't account for ethernet header or the virtio header +static int vionet_allocdesc(size_t requestedsize, netdesc_t *desc) { + size_t truesize = PREFIX_SIZE + requestedsize; + void *phys = pmm_allocpage(PMM_SECTION_DEFAULT); + if (phys == NULL) + return ENOMEM; + + desc->address = MAKE_HHDM(phys); + desc->size = truesize; + desc->curroffset = PREFIX_SIZE; + return 0; +} + +static int vionet_sendpacket(netdev_t *internal, netdesc_t desc, mac_t targetmac, int proto) { + vionetdev_t *netdev = (vionetdev_t *)internal; + vioframe_t vioframe = { + .flags = 0, + .gsotype = 0, + .headerlen = PREFIX_SIZE, + .gsosize = 0, + .checksumstart = 0, + .checksumcount = 0, + .buffercount = 0 + }; + + ethframe_t ethframe = { + .type = cpu_to_be_w(proto) + }; + + memcpy(ðframe.source, &netdev->netdev.mac, sizeof(mac_t)); + memcpy(ðframe.destination, &targetmac, sizeof(mac_t)); + + memcpy(desc.address, &vioframe, sizeof(vioframe_t)); + memcpy((void *)((uintptr_t)desc.address + sizeof(vioframe_t)), ðframe, sizeof(ethframe_t)); + + event_t event; + EVENT_INIT(&event); + + bool intstatus = interrupt_set(false); + semaphore_wait(&netdev->txsem, false); + + spinlock_acquire(&netdev->txlock); + int idx = 0; + volatile viobuffer_t *buffers = VIO_QUEUE_BUFFERS(&netdev->txqueue); + while (buffers[idx].address && idx < netdev->txqueue.size) + ++idx; + + __assert(idx < netdev->txqueue.size); + + buffers[idx].address = (uint64_t)FROM_HHDM(desc.address); + buffers[idx].length = desc.size; + buffers[idx].flags = 0; + + size_t driveridx = VIO_QUEUE_DRV_IDX(&netdev->txqueue)++; + VIO_QUEUE_DRV_RING(&netdev->txqueue)[driveridx % netdev->txqueue.size] = idx; + + netdev->txwait[idx] = &event; + + *netdev->txqueue.notify = 0; + spinlock_release(&netdev->txlock); + + event_wait(&event, false); + + interrupt_set(intstatus); + return 0; +} + +int vionet_newdevice(viodevice_t *viodevice) { + static int id = 0; + volatile vionetconfig_t *vionetconfig = viodevice->devconfig; + + if (viodevice->e->msix.exists == false) { + printf("vionet: device doesn't support msi-x\n"); + return 1; + } + + size_t intcount = pci_initmsix(viodevice->e); + if (intcount < 2) { + printf("vionet: not enough msi-x vectors\n"); + return 1; + } + + uint64_t features = virtio_negotiatefeatures(viodevice, WANTED_FEATURES); + __assert(features == WANTED_FEATURES); + + vionetdev_t *netdev = alloc(sizeof(vionetdev_t)); + __assert(netdev); + netdev->viodevice = viodevice; + netdev->id = id++; + netdev->netdev.mtu = 1500; + netdev->netdev.sendpacket = vionet_sendpacket; + netdev->netdev.allocdesc = vionet_allocdesc; + + // initialize queues + size_t rxsize = min(QUEUE_MAX_SIZE, virtio_queuesize(viodevice, 0)); + size_t txsize = min(QUEUE_MAX_SIZE, virtio_queuesize(viodevice, 1)); + virtio_createqueue(viodevice, &netdev->rxqueue, 0, rxsize, 0); + virtio_createqueue(viodevice, &netdev->txqueue, 1, txsize, 1); + + SEMAPHORE_INIT(&netdev->txsem, txsize); + SPINLOCK_INIT(netdev->txlock); + + isr_t *rxisr = interrupt_allocate(rx_irq, ARCH_EOI, IPL_NET); + __assert(rxisr); + pci_msixadd(viodevice->e, 0, INTERRUPT_IDTOVECTOR(rxisr->id), 0, 0); + __assert(hashtable_set(&irqtable, netdev, &rxisr->id, sizeof(rxisr->id), true) == 0); + + isr_t *txisr = interrupt_allocate(tx_irq, ARCH_EOI, IPL_NET); + __assert(txisr); + pci_msixadd(viodevice->e, 1, INTERRUPT_IDTOVECTOR(txisr->id), 0, 0); + __assert(hashtable_set(&irqtable, netdev, &txisr->id, sizeof(txisr->id), true) == 0); + pci_msixsetmask(viodevice->e, 0); + + virtio_enablequeue(viodevice, 0); + virtio_enablequeue(viodevice, 1); + virtio_enabledevice(viodevice); + + // fill receive queues + // XXX is the memory saving of allocating hundreds of KiBs of contiguous physical memory really worth it? + // the system literally might not have enough (contiguous) physical memory for this, especially considering + // this device is initialised deeper into the boot process. + void *receivebuffer = pmm_alloc(ROUND_UP((rxsize * BUFFER_SIZE), PAGE_SIZE) / PAGE_SIZE, PMM_SECTION_DEFAULT); + __assert(receivebuffer); + + for (int i = 0; i < rxsize; ++i) { + VIO_QUEUE_BUFFERS(&netdev->rxqueue)[i].address = (uintptr_t)receivebuffer + i * BUFFER_SIZE; + VIO_QUEUE_BUFFERS(&netdev->rxqueue)[i].length = BUFFER_SIZE; + VIO_QUEUE_BUFFERS(&netdev->rxqueue)[i].flags = VIO_QUEUE_BUFFER_DEVICE; + VIO_QUEUE_DRV_RING(&netdev->rxqueue)[i] = i; + VIO_QUEUE_DRV_IDX(&netdev->rxqueue)++; + *netdev->rxqueue.notify = 0; + } + + for (int i = 0; i < 6; ++i) + netdev->netdev.mac.address[i] = vionetconfig->mac[i]; + + printf("vionet%d: mac: %02x:%02x:%02x:%02x:%02x:%02x\n", netdev->id, netdev->netdev.mac.address[0], netdev->netdev.mac.address[1], netdev->netdev.mac.address[2], netdev->netdev.mac.address[3], netdev->netdev.mac.address[4], netdev->netdev.mac.address[5]); + + return 0; +} + +void vionet_init() { + __assert(hashtable_init(&irqtable, 20) == 0); +} + +netdev_t *vionet_temporary_getnetdev() { + HASHTABLE_FOREACH(&irqtable) { + return entry->value; + } +}