MIT 6.S081 Network Driver

通过阅读E1000网卡驱动的文档理解网络驱动在接收和发送数据包时的作用。以下记录。

QEMU emulates the 82540EM,是一个网卡驱动,给的manual是网卡的说明

E1000模拟了一个以太网卡 xv6作为作为主机,使用驱动向网卡发数据包,我们要写一个e1000的驱动

实验任务就是把包含了一个以太网帧的mbuf,包进一个e1000的发送描述符tx_desc中(e1000_transmit)

Receive descriptor ring的HEAD、TAIL指针存储在硬件寄存器中 每个接收描述符包含了一个地址,E1000可以吧接收到的package写入此地址中

         receive_pkg
Ethernet  ---> E1000 => xv6
Ethernet  <--- E1000 <= xv6
         transmit_pkg

e1000_transmit 需要将mbuf的地址放入发送描述符中。要确保mbuf被释放,直到 the E1000 sets the E1000_TXD_STAT_DD bit in the descriptor 由于尾指针TDT可能指向的是一个已经发送了的描述符,需要在增加新的描述符的时候进行释放. return -1表示transmit失败,需要让caller释放mbuf。

int
e1000_transmit(struct mbuf *m)
{
    //
    // Your code here.
    //
    // the mbuf contains an ethernet frame; program it into
    // the TX descriptor ring so that the e1000 sends it. Stash
    // a pointer so that it can be freed after sending.
    //
    acquire(&e1000_lock);

    uint32 tdt = regs[E1000_TDT];
    if (tdt < 0 || tdt >= TX_RING_SIZE) {
        printf("E1000_TDT index overflowing\n");
        release(&e1000_lock);
        return -1;
    }
    if (tx_ring[tdt].status & E1000_TXD_STAT_DD) {
        if (tx_mbufs[tdt] != 0) {
            mbuffree(tx_mbufs[tdt]);  // free the last mbuf
        }
        tx_ring[tdt].addr = (uint64) m->head;
        tx_ring[tdt].length = m->len;
        tx_ring[tdt].cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS; // end of packet , report status(for DD bit)
        tx_mbufs[tdt] = m;   // stash away a pointer for later freeing
        regs[E1000_TDT] = (regs[E1000_TDT] + 1) % TX_RING_SIZE;  // update the tail pointer

        release(&e1000_lock);
        return 0;
    } else {
        printf("The TXD_STAT_DD is not set, the last descriptor hasn't finished transmitting\n");
        release(&e1000_lock);
        return -1;
    }
}

e1000_recv 扫描接收描述符环上的每个描述符,使用net_rx发送给xv6。同时分配一个新的空接收描述符,指定下次DMA传送的起点。 must scan the RX ring and deliver each new packet's mbuf to the network stack 当e1000从以太网中接收到一个packet的时候,it first DMAs the packet to the mbuf pointed to by the next RX (receive) ring descriptor , and then generates an interrupt. 在初始化时,head指向rx_bufs的头,tail指向rx_bufs的尾部,此时没有从hardware中接收到任何packet,当head=tail时,表示接收buffer已满, hardware不再write to the head,直到software移动tail指针,读取接收的数据包。

static void
e1000_recv(void)
{
    //
    // Your code here.
    //
    // Check for packets that have arrived from the e1000
    // Create and deliver an mbuf for each packet (using net_rx()).
    //
//    acquire(&e1000_lock);

//    uint32 rdh = regs[E1000_RDH];
    uint32 rdt = regs[E1000_RDT];
    uint32 next_waiting_pkg_idx = (rdt + 1) % RX_RING_SIZE;
    while (1) {
        if (rx_ring[next_waiting_pkg_idx].status & E1000_RXD_STAT_DD) {
            rx_mbufs[next_waiting_pkg_idx]->len = rx_ring[next_waiting_pkg_idx].length;
            net_rx(rx_mbufs[next_waiting_pkg_idx]);

            rx_mbufs[next_waiting_pkg_idx] = mbufalloc(0);
            rx_ring[next_waiting_pkg_idx].status = 0;
            rx_ring[next_waiting_pkg_idx].addr = (uint64)rx_mbufs[next_waiting_pkg_idx]->head; // Program its data pointer (m->head) into the descriptor

            regs[E1000_RDT] = (rdt + 1) % RX_RING_SIZE;
            rdt = regs[E1000_RDT];
            next_waiting_pkg_idx = (rdt + 1) % RX_RING_SIZE;
        } else {
            printf("the new packet is not available\n");
            break;
        }
    }
//    release(&e1000_lock); // todo: why not required
}