Lab: networking

Your Job (hard)

This is definitely a hard and time-consuming lab if you read through the device manual for programming. However, the given hints are useful enough to finish this task. Instead of repeating the hints, I will write about the code structures in the e1000 driver.

Descriptor & Buffer

The e1000 device uses direct memory access (DMA) technique to access the transmitted/received data in RAM. In order to specify the memory address of the data, driver provides the information in descriptors.

Descriptor Ring

To maximize throughput, e1000 uses a circular array of descriptors (or buffers) to support consecutive operations. Device can access its base address, head/tail pointers and length from memory-mapped registers.

Untitled

	// [E1000 14.5] Transmit initialization
  memset(tx_ring, 0, sizeof(tx_ring));
  for (i = 0; i < TX_RING_SIZE; i++) {
    tx_ring[i].status = E1000_TXD_STAT_DD;
    tx_mbufs[i] = 0;
  }
  regs[E1000_TDBAL] = (uint64) tx_ring;
  regs[E1000_TDLEN] = sizeof(tx_ring);
  regs[E1000_TDH] = regs[E1000_TDT] = 0;
  
  // [E1000 14.4] Receive initialization
  memset(rx_ring, 0, sizeof(rx_ring));
  for (i = 0; i < RX_RING_SIZE; i++) {
    rx_mbufs[i] = mbufalloc(0);
    if (!rx_mbufs[i])
      panic("e1000");
    rx_ring[i].addr = (uint64) rx_mbufs[i]->head;
  }
  regs[E1000_RDBAL] = (uint64) rx_ring;
  regs[E1000_RDH] = 0;
  regs[E1000_RDT] = RX_RING_SIZE - 1;
  regs[E1000_RDLEN] = sizeof(rx_ring);

It is a producer-consumer model:

Buffer Pointers

For each descriptor, there is a corresponding buffer pointer mbuf *. It is the bridge between net code stack and hardware.

Note: The buffer pointers are static, but the actual mbuf data is dynamic!

static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16)));
static struct mbuf *tx_mbufs[TX_RING_SIZE];

static struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16)));
static struct mbuf *rx_mbufs[RX_RING_SIZE];

Transmit a packet: