/* * Copyright (c) 2009 Herbert Poetzl. All rights reserved. * * VirtioNet.h * * HISTORY * * August 12th, 2009 Created * */ #include "VirtioNet.h" #define super IOEthernetController OSDefineMetaClassAndStructors( VirtioNet, super ) #define RX_QUEUE 0 #define TX_QUEUE 1 #define CT_QUEUE 2 #define VIRTIO_PCI_CONFIG VIRTIO_PCI_CONFIG_NOMSI #define MRG_RXBUF #ifdef MRG_RXBUF #define VNH_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf) #else #define VNH_SIZE sizeof(struct virtio_net_hdr) #endif void VirtioNet::dumpPacket(mbuf_t m) { struct IOPhysicalSegment vector; UInt count; count = txMbufCurs->getPhysicalSegments(m, &vector, 1); if (!count) return; UInt index = vring[1].used->idx % vring[1].num; IOLog("[VirtioNet] used[%x,%d] = 0x%x,0x%x\n", vring[1].used->flags, vring[1].used->idx, vring[1].used->ring[index].id, vring[1].used->ring[index].len); IOLog("[VirtioNet] dumpPacket 0x%x [%d]\n", vector.location, vector.length); /* fill in descriptor */ vring[1].desc[0].addr = vector.location; vring[1].desc[0].len = vector.length; vring[1].desc[0].flags = 0; vring[1].desc[0].next = NULL; /* add to available */ /* UInt */index = vring[1].avail->idx % vring[1].num; vring[1].avail->ring[index] = 0; vring[1].avail->idx++; /* notify device about queue 1 */ WriteMMIO16(VIRTIO_PCI_QUEUE_NOTIFY, 1); } bool VirtioNet::setupVQueue(uint16_t idx) { // select queue (device) WriteMMIO16(VIRTIO_PCI_QUEUE_SEL, idx); // get queue size (entries) uint16_t qSize = ReadMMIO16(VIRTIO_PCI_QUEUE_NUM); if (!qSize) return false; IOLog("[VirtioNet] found queue #%d size 0x%x\n", idx, qSize); // calculate vring size and allocate memory size_t vSize = vring_size(qSize, VIRTIO_PCI_VRING_ALIGN); void *vAddr = IOMallocContiguous(vSize, VIRTIO_PCI_VRING_ALIGN, &virtq[idx]); IOLog("[VirtioNet] vring 0x%x/0x%x\n", vAddr, virtq[idx]); // allocate vaddr store for descriptors vdesc[idx] = (struct vdesc *)IOMalloc(qSize * sizeof(struct vdesc *)); bzero(vdesc[idx], qSize * sizeof(struct vdesc *)); // init vring structure vring_init(&vring[idx], qSize, vAddr, VIRTIO_PCI_VRING_ALIGN); bzero(vAddr, vSize); // set queue physical memory address for driver WriteMMIO32(VIRTIO_PCI_QUEUE_PFN, virtq[idx] >> VIRTIO_PCI_QUEUE_ADDR_SHIFT); return true; } bool VirtioNet::releaseVQueue(uint16_t idx) { uint16_t descIdx; uint16_t descNum = vring[idx].num; if (!descNum) return false; for (descIdx = 1; descIdx < descNum; descIdx++) { // skip over unused descriptors if (!vring[idx].desc[descIdx].addr) continue; releaseBuffer(idx, descIdx); } // free up vring memory IOFreeContiguous((void *)vring[idx].desc, vring_size(descNum, VIRTIO_PCI_VRING_ALIGN)); // free vaddr store for descriptors IOFree(vdesc[idx], descNum * sizeof(void *)); return true; } void VirtioNet::releaseBuffer(uint16_t idx, uint16_t descIdx) { IOFreeContiguous(vdesc[idx][descIdx].addr, vdesc[idx][descIdx].size); vdesc[idx][descIdx].addr = NULL; // remove physical address vring[idx].desc[descIdx].addr = 0; } void *VirtioNet::getBuffer(uint16_t idx, uint16_t *descIdx) { *descIdx = vring[idx].desc[0].next; // unused buffers left? if (!descIdx) return NULL; // unchain buffer vring[idx].desc[0].next = vring[idx].desc[*descIdx].next; vring[idx].desc[*descIdx].next = 0; // return virtual address return vdesc[idx][*descIdx].addr; } void VirtioNet::putBuffer(uint16_t idx, uint16_t descIdx) { // chain up buffer vring[idx].desc[descIdx].next = vring[idx].desc[0].next; vring[idx].desc[0].next = descIdx; } bool VirtioNet::addBuffers(uint16_t idx, size_t size, int count) { uint16_t descIdx; uint16_t descNum = vring[idx].num; for (descIdx = 1; descIdx < descNum; descIdx++) { // skip over used descriptors if (vring[idx].desc[descIdx].addr) continue; IOPhysicalAddress phyAddr; vdesc[idx][descIdx].addr = IOMallocContiguous(size, VIRTIO_PCI_VRING_ALIGN, &phyAddr); vdesc[idx][descIdx].size = size; if (!vdesc[idx][descIdx].addr) return false; vring[idx].desc[descIdx].addr = phyAddr; vring[idx].desc[descIdx].len = size; vring[idx].desc[descIdx].flags = 0; // add to unused putBuffer(idx, descIdx); if (!count--) break; } return true; } bool VirtioNet::makeBufferAvail(uint16_t idx, uint16_t descIdx) { uint16_t descNum = vring[idx].num; /* add to available */ uint16_t availIdx = vring[idx].avail->idx % descNum; vring[idx].avail->ring[availIdx] = descIdx; vring[idx].avail->idx++; return true; } // -------------------------------------------------- // IONetworkController methods. // -------------------------------------------------- bool VirtioNet::init(OSDictionary *properties) { bool res = super::init(properties); IOLog("[VirtioNet] init\n"); if (!res) return res; pciDev = NULL; mmioBase = NULL; netIf = NULL; irqSrc = NULL; vring[RX_QUEUE].num = 0; vring[TX_QUEUE].num = 0; vring[CT_QUEUE].num = 0; return res; } #define RELEASE(r) do { if (r) (r)->release(); } while(0) void VirtioNet::free() { IOLog("[VirtioNet] free\n"); // RELEASE(workLoop); RELEASE(irqSrc); RELEASE(netIf); RELEASE(mmioBase); RELEASE(pciDev); releaseVQueue(RX_QUEUE); releaseVQueue(TX_QUEUE); releaseVQueue(CT_QUEUE); super::free(); } bool VirtioNet::start( IOService * provider ) { bool res = super::start(provider); IOLog("[VirtioNet] start\n"); if (!res) return res; // Cache our provider to an instance variable. pciDev = OSDynamicCast(IOPCIDevice, provider); if (!pciDev) { IOLog("[VirtioNet] failed to cast provider\n"); return false; } // retain and open device pciDev->retain(); pciDev->open(this); // register hardware interrupt pciDev->registerInterrupt(0, this, OSMemberFunctionCast(IOInterruptAction, this, &VirtioNet::handleInterrupt), 0); // enable interrupt pciDev->enableInterrupt(0); // enable various PCI aspects pciDev->setIOEnable(true); pciDev->setBusMasterEnable(true); pciDev->setMemoryEnable(true); // read vendor and device id uint16_t vendorId = pciDev->configRead16(kIOPCIConfigVendorID); uint16_t deviceId = pciDev->configRead16(kIOPCIConfigDeviceID); IOLog("[VirtioNet] vendor:device %04x:%04x\n", vendorId, deviceId); uint8_t irqLine = pciDev->configRead8(kIOPCIConfigInterruptLine); uint8_t irqPin = pciDev->configRead8(kIOPCIConfigInterruptPin); pioBase = pciDev->configRead16(kIOPCIConfigBaseAddress0) & 0xFFFC; IOLog("[VirtioNet] pio base 0x%04x, irq 0x%x:0x%x\n", pioBase, irqLine, irqPin); mmioBase = pciDev->mapDeviceMemoryWithRegister(kIOPCIConfigBaseAddress0); IOLog("[VirtioNet] mmio virt 0x%x, phys 0x%x\n", mmioBase->getVirtualAddress(), mmioBase->getPhysicalAddress()); /* hard reset virtio device */ WriteMMIO8(VIRTIO_PCI_STATUS, 0); /* archnowledge device and start driver config */ WriteMMIO8(VIRTIO_PCI_STATUS, VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER); uint32_t hostFeat = ReadMMIO32(VIRTIO_PCI_HOST_FEATURES); IOLog("[VirtioNet] host feature 0x%x\n", hostFeat); setupVQueue(RX_QUEUE); addBuffers(RX_QUEUE, 4096, 5); setupVQueue(TX_QUEUE); addBuffers(TX_QUEUE, 4096, 5); // setupVQueue(CT_QUEUE); /* needs to be done before attachInterface */ for (int i=0; iclose(this); return true; } void VirtioNet::stop(IOService * provider) { IOLog("[VirtioNet] stop\n"); /* hard disable virtio device. needed? */ WriteMMIO8(VIRTIO_PCI_STATUS, VIRTIO_CONFIG_S_FAILED); super::stop(provider); } IOReturn VirtioNet::enable(IONetworkInterface *netif) { IOLog("[VirtioNet] enable\n"); pciDev->open(this); vring[RX_QUEUE].avail->flags = 0; uint16_t bufIdx; while (getBuffer(RX_QUEUE, &bufIdx)) { uint16_t hdrIdx; if (!getBuffer(RX_QUEUE, &hdrIdx)) { putBuffer(RX_QUEUE, bufIdx); break; } vring[RX_QUEUE].desc[hdrIdx].flags = VRING_DESC_F_WRITE; vring[RX_QUEUE].desc[hdrIdx].len = VNH_SIZE; vring[RX_QUEUE].desc[hdrIdx].next = bufIdx; vring[RX_QUEUE].desc[bufIdx].flags = VRING_DESC_F_WRITE; makeBufferAvail(RX_QUEUE, hdrIdx); makeBufferAvail(RX_QUEUE, bufIdx); } /* notify device about RX_QUEUE */ WriteMMIO16(VIRTIO_PCI_QUEUE_NOTIFY, RX_QUEUE); return kIOReturnSuccess; // return kIOReturnIOError; } IOReturn VirtioNet::disable(IONetworkInterface *netif) { IOLog("[VirtioNet] disable\n"); pciDev->close(this); return kIOReturnSuccess; } UInt32 VirtioNet::outputPacket(mbuf_t m, void *param) { IOLog("[VirtioNet] outputPacket\n"); char fakeArp[] = { 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x52, 0x54, 0x00, 0x12, 0x34, 0x56, 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0x02 }; uint16_t bufIdx, hdrIdx; UInt32 res = kIOReturnOutputStall; void *buf = getBuffer(TX_QUEUE, &bufIdx); if (!buf) goto out; struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)getBuffer(TX_QUEUE, &hdrIdx); if (!hdr) { putBuffer(TX_QUEUE, bufIdx); goto out; } hdr->flags = 0 /* VIRTIO_NET_HDR_F_NEEDS_CSUM */; hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; hdr->gso_size = 0; hdr->hdr_len = 0; // hdr->gso_size = sizeof(fakeArp); // hdr->hdr_len = sizeof(struct virtio_net_hdr); hdr->csum_start = 0; hdr->csum_offset = 0; #ifdef MRG_RXBUF ((struct virtio_net_hdr_mrg_rxbuf *)hdr)->num_buffers = 2; #endif bcopy(fakeArp, buf, sizeof(fakeArp)); /* FIXME: hack */ vring[TX_QUEUE].desc[hdrIdx].len = VNH_SIZE; vring[TX_QUEUE].desc[bufIdx].len = sizeof(fakeArp); // chain buffers vring[TX_QUEUE].desc[hdrIdx].next = bufIdx; makeBufferAvail(TX_QUEUE, hdrIdx); makeBufferAvail(TX_QUEUE, bufIdx); /* notify device about TX_QUEUE */ WriteMMIO16(VIRTIO_PCI_QUEUE_NOTIFY, TX_QUEUE); // we should be fine res = kIOReturnOutputSuccess; out: super::freePacket(m); return res; // return kIOReturnOutputDropped; // return kIOReturnOutputStall; } const OSString * VirtioNet::newVendorString() const { return OSString::withCString("Virtual"); } const OSString * VirtioNet::newModelString() const { return OSString::withCString("Net"); } // -------------------------------------------------- // IONetworkController methods.(Debugger) // -------------------------------------------------- void VirtioNet::sendPacket(void *pkt, UInt32 pkt_len) { IOLog("[VirtioNet] sendPacket [%d]\n", pkt_len); } void VirtioNet::receivePacket(void * pkt, UInt32 *pkt_len, UInt32 timeout) { IOLog("[VirtioNet] receivePacket [%d,%d]\n", *pkt_len, timeout); } // -------------------------------------------------- // IOEthernetController methods. // -------------------------------------------------- IOReturn VirtioNet::getHardwareAddress(IOEthernetAddress * addrs) { IOLog("[VirtioNet] getHardwareAddress\n"); bcopy(&myAddress, addrs, sizeof(*addrs)); return kIOReturnSuccess; } // -------------------------------------------------- // VirtioNet driver specific methods. // -------------------------------------------------- void VirtioNet::handleInterrupt(OSObject* target, void * refCon, IOService *pciDev, int source) { IOLog("[VirtioNet] interrupt %d\n", source); }