VirtIO-net driver cannot send packets
Posted: Tue Oct 27, 2015 3:09 pm
I am trying to write a VirtIO-net driver (testing under VirtualBox). I found different versions of the specification and they were a bit unclear about the constants (especially in bit fields), sometimes indicating their values at the bit number, and sometimes as the value of the mask, without indicating which one it is. The specification inside the Linux kernel seems to always use bit numbers though. Just in case, let me drop my defines here:
To the best of my knowledge, the #defines, the structs and the vionet_qdsize() and vionet_qinit() functions work correctly (they match with the examples in the specification). I initialize the device as follows:
The "vionet_thread()" currently does nothing other than wait for an interrupt and print "VIONET INTERRUPT!!!" when it arrive (spoiler alert: it never does).
I transmit the frame as follows:
After vionet_send() is called, no interrupt arrives, and nothing goes into the "used" ring of the TX queue (I previously put a loop at the end of vionet_send() to keep checking the used index (as uint16_t *volatile). The index was always 0).
Where is the problem? Am I not initializing correctly?
NOTE: The MAC address is correctly identified.
Code: Select all
#define VIONET_DESC_F_NEXT 1
#define VIONET_DESC_F_WRITE 2
#define VIONET_REG_QADDR 0x08
#define VIONET_REG_QSIZE 0x0C
#define VIONET_REG_QSEL 0x0E
#define VIONET_REG_DEVST 0x12
#define VIONET_REG_QNOTF 0x10
#define VIONET_ALIGN(x) (((x) + 4095) & ~4095)
#define VIONET_DEVST_ACK (1 << 1)
#define VIONET_DEVST_DRV (1 << 2)
#define VIONET_DEVST_OK (1 << 3)
#define VIONET_DEVST_FAIL (1 << 8)
typedef struct
{
uint64_t phaddr;
uint32_t len;
uint16_t flags;
uint16_t next;
} VionetBufferDesc;
typedef struct
{
uint32_t index;
uint32_t len;
} VionetUsedElem;
typedef struct
{
uint16_t flags;
uint16_t index;
uint16_t ring[];
/* after the ring: uint16_t intIndex */
} VionetAvail;
typedef struct
{
uint16_t flags;
uint16_t index;
VionetUsedElem ring[];
/* after the ring: uint16_t intIndex */
} VionetUsed;
typedef struct
{
uint16_t count;
VionetBufferDesc* bufDesc;
VionetAvail* avail;
VionetUsed* used;
uint16_t* availIntIndex;
uint16_t* usedIntIndex;
} VionetQueue;
typedef struct
{
uint8_t flags;
uint8_t seg;
uint16_t headlen;
uint16_t seglen;
uint16_t cstart;
uint16_t coff;
uint16_t bufcount;
} VionetPacketHeader;
static void vionet_qinit(VionetQueue *q, uint16_t count, void *p)
{
uint64_t addr = (uint64_t) p;
q->count = count;
q->bufDesc = (VionetBufferDesc*) addr;
q->avail = (VionetAvail*) (addr + sizeof(VionetBufferDesc) * count);
q->used = (VionetUsed*) VIONET_ALIGN(((uint64_t) &q->avail->ring[count]));
};
static inline size_t vionet_qdsize(uint16_t qsz)
{
return VIONET_ALIGN(sizeof(VionetBufferDesc)*qsz + sizeof(uint16_t)*(2 + qsz)) + VIONET_ALIGN(sizeof(VionetUsedElem)*qsz);
}
Code: Select all
MODULE_INIT(const char *opt)
{
kprintf("vionet: enumerating virtio-net devices\n");
pciEnumDevices(THIS_MODULE, vionet_enumerator, NULL);
kprintf("vionet: creating network interfaces\n");
VionetInterface *nif;
for (nif=interfaces; nif!=NULL; nif=nif->next)
{
NetIfConfig ifconfig;
memset(&ifconfig, 0, sizeof(NetIfConfig));
ifconfig.ethernet.type = IF_ETHERNET;
ifconfig.ethernet.send = vionet_send;
int i;
for (i=0; i<6; i++)
{
ifconfig.ethernet.mac.addr[i] = inb(nif->iobase + 0x14 + i);
};
pciSetBusMastering(nif->pcidev, 1);
outw(nif->iobase + VIONET_REG_DEVST, 0);
outw(nif->iobase + VIONET_REG_DEVST, VIONET_DEVST_ACK);
outw(nif->iobase + VIONET_REG_DEVST, VIONET_DEVST_ACK | VIONET_DEVST_DRV);
// find out the size of RX and TX queues.
outw(nif->iobase + VIONET_REG_QSEL, 0);
uint16_t rxqsize = inw(nif->iobase + VIONET_REG_QSIZE);
outw(nif->iobase + VIONET_REG_QSEL, 1);
uint16_t txqsize = inw(nif->iobase + VIONET_REG_QSIZE);
if ((rxqsize < 4) || (txqsize < 4))
{
panic("vionet: at least 4 RX and 4 TX buffers must be available!");
};
size_t rxqdsize = vionet_qdsize(rxqsize);
size_t txqdsize = vionet_qdsize(txqsize);
// allocate the queues as DMA buffers
int errnum;
if ((errnum = dmaCreateBuffer(&nif->dmaRX, rxqdsize, DMA_32BIT)) != 0)
{
panic("vionet: failed to allocate RX queue! (%d)", errnum);
};
if ((errnum = dmaCreateBuffer(&nif->dmaTX, txqdsize, DMA_32BIT)) != 0)
{
panic("vionet: failed to allocate TX queue! (%d)", errnum);
};
// zero out the queue descriptors
memset(dmaGetPtr(&nif->dmaRX), 0, rxqdsize);
memset(dmaGetPtr(&nif->dmaTX), 0, txqdsize);
// load the queue handles
vionet_qinit(&nif->rxq, rxqsize, dmaGetPtr(&nif->dmaRX));
vionet_qinit(&nif->txq, txqsize, dmaGetPtr(&nif->dmaTX));
// allocate the RX/TX space. It starts with 8 packet headers
// (4 for RX, then 4 for TX), followed by 1518-byte frame
// buffers (4 for RX and another 4 for TX). The packet headers
// shall be zeroed out and left like that.
if ((errnum = dmaCreateBuffer(&nif->dmaIO, 8*sizeof(VionetPacketHeader) + 8*1518, 0)) != 0)
{
panic("vionet: failed to allocate RX/TX space! (%d)", errnum);
};
// zero out the RX/TX space then post the 4 RX buffers to the queue.
memset(dmaGetPtr(&nif->dmaIO), 0, 8*sizeof(VionetPacketHeader) + 8*1518);
uint64_t spaceBase = dmaGetPhys(&nif->dmaIO);
for (i=0; i<4; i++)
{
// packet header buffer
nif->rxq.bufDesc[2*i].phaddr = spaceBase + i * sizeof(VionetPacketHeader);
nif->rxq.bufDesc[2*i].len = sizeof(VionetPacketHeader);
nif->rxq.bufDesc[2*i].flags = VIONET_DESC_F_NEXT;
nif->rxq.bufDesc[2*i].next = 2*i+1;
// frame buffer
nif->rxq.bufDesc[2*i+1].phaddr = spaceBase + 8*sizeof(VionetPacketHeader) + i*1518;
nif->rxq.bufDesc[2*i+1].len = 1518;
nif->rxq.bufDesc[2*i+1].flags = VIONET_DESC_F_WRITE;
nif->rxq.bufDesc[2*i+1].next = 0;
// post as available
nif->rxq.avail->ring[i] = 2*i;
};
// update the RX ring index
nif->rxq.avail->index = 4;
// make sure all memory is flushed!
__sync_synchronize();
// program in the queue addresses
uint32_t rxphys = (uint32_t) (dmaGetPhys(&nif->dmaRX) >> 12);
uint32_t txphys = (uint32_t) (dmaGetPhys(&nif->dmaTX) >> 12);
outw(nif->iobase + VIONET_REG_QSEL, 0);
outd(nif->iobase + VIONET_REG_QADDR, rxphys);
outw(nif->iobase + VIONET_REG_QSEL, 1);
outd(nif->iobase + VIONET_REG_QADDR, txphys);
nif->netif = CreateNetworkInterface(nif, &ifconfig);
if (nif->netif == NULL)
{
kprintf("vionet: CreateNetworkInterface() failed\n");
}
else
{
kprintf("vionet: created interface: %s\n", nif->netif->name);
};
nif->running = 1;
spinlockRelease(&nif->lock);
nif->txBitmap = 0;
// tell the host that we are ready to drive the device, and that we support MAC.
outw(nif->iobase + 0x04, (1 << 5));
outw(nif->iobase + VIONET_REG_DEVST, VIONET_DEVST_ACK | VIONET_DEVST_DRV | VIONET_DEVST_OK);
KernelThreadParams pars;
memset(&pars, 0, sizeof(KernelThreadParams));
pars.name = "VirtIO-net Interrupt Handler";
pars.stackSize = DEFAULT_STACK_SIZE;
nif->thread = CreateKernelThread(vionet_thread, &pars, nif);
};
if (interfaces == NULL)
{
return MODINIT_CANCEL;
};
return MODINIT_OK;
};
I transmit the frame as follows:
Code: Select all
static void vionet_send(NetIf *netif, const void *frame, size_t framelen)
{
VionetInterface *nif = (VionetInterface*) netif->drvdata;
spinlockAcquire(&nif->lock);
int txbuf;
if ((txbuf = vionet_findAvailTXBuffer(nif)) == 4)
{
// there are no available buffers, so
spinlockRelease(&nif->lock);
};
nif->txBitmap |= (1 << txbuf);
uint64_t spaceBase = dmaGetPhys(&nif->dmaIO);
// load the frame into the buffer
uint64_t fbufAddr = (uint64_t) dmaGetPtr(&nif->dmaIO) + 8*sizeof(VionetPacketHeader) + (txbuf+4)*1518;
memset((void*)fbufAddr, 0, 1518); // never send uninitialized data!
memcpy((void*)fbufAddr, frame, framelen);
// fill in the descriptors
nif->txq.bufDesc[2*txbuf].phaddr = spaceBase + (txbuf+4)*sizeof(VionetPacketHeader);
nif->txq.bufDesc[2*txbuf].len = sizeof(VionetPacketHeader);
nif->txq.bufDesc[2*txbuf].flags = VIONET_DESC_F_NEXT;
nif->txq.bufDesc[2*txbuf].next = 2*txbuf+1;
// frame buffer
nif->txq.bufDesc[2*txbuf+1].phaddr = spaceBase + 8*sizeof(VionetPacketHeader) + (txbuf+4)*1518;
nif->txq.bufDesc[2*txbuf+1].len = framelen;
nif->txq.bufDesc[2*txbuf+1].flags = 0;
nif->txq.bufDesc[2*txbuf+1].next = 0;
// flush memory
__sync_synchronize();
// post the buffer to the available ring
nif->txq.avail->ring[nif->txq.avail->index] = 2*txbuf;
__sync_synchronize();
nif->txq.avail->index++;
__sync_synchronize();
// notify the device that we have updated the TX queue.
outw(nif->iobase + VIONET_REG_QNOTF, 1);
spinlockRelease(&nif->lock);
};
Where is the problem? Am I not initializing correctly?
NOTE: The MAC address is correctly identified.