Featured image of post MIT 6.S081 lab7 Networking

MIT 6.S081 lab7 Networking

使用 E1000 网卡写一个驱动程序

# Lab07 Networking

说是一个驱动程序,实际上需要完成的只有两个函数,把源码看明白之后直接跟着 Hint 写就行。(记得去看 Background

那本厚厚的文档可以不用读,只用看几个部分就行。

实验难度为 hard

# Your Job

发送数据的流程

CPUIP数据包打包放入内存中,通知DMA Engine进行DMA传输,数据放入FIFO data buffer中。MAC将IP数据包拆分为最小64KB,最大1518KB的数据帧,每一帧包含了目标MAC地址、自己的MAC地址和数据包中的协议类型以及CRC校验码。目标MAC地址通过ARP协议获取。PHY接受MAC传送的数据,将并行数据转化为串行数据后进行编码,在转变为模拟信号将数据进行传输。

首先,我们给出 struct tx_descstruct desc 的结构,在文档的 3.3 可以找到:

tx_desc

tx_decs

# e1000_transmit

根据 Hint ,我们知道首先我们从 reg E1000_TDT 中读取索引,然后对索引进行检查。然后,我们从 TX ring 中取出此索引指向的 tx_desc ,我们会检查这个 tx_desc 的状态是否被设置了 E1000_TXD_STAT_DD ,也就是是否被硬件设置了完成标志(在文档的 3.3.3.2 中有描述),如果没有被设置,那么我们直接返回错误,否则我们使用 mbuffree() 释放已经完成转运的 mbuf (也就是上面索引指向的那个 mbuf )。随后,我们会对传入的 mbuf m 进行一系列设定:将其 tx_desc 中的 Buffer Address 指向 m->head ,将 tx_desc 中的 Length 指向 m->len 并且我们还需要设定 tx_desc 中的 cmd(含义可以去文档的 3.3.3.1 中查看),但是宏就给了两个,所以……

最后,我们需要更新 reg E1000_TDT 的值

这样,代码描述为:

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 idx = regs[E1000_TDT];
  if (idx >= TX_RING_SIZE) {
    release(&e1000_lock);
    return -1;
  }
  struct tx_desc* t = &tx_ring[idx];
  if ((t->status & E1000_TXD_STAT_DD) == 0) {
    release(&e1000_lock);
    printf("hasn't finished previous transmission request\n");
    return -1;
  }
  if (tx_mbufs[idx])
    mbuffree(tx_mbufs[idx]);
  t->addr = (uint64)m->head;
  t->length = (uint16)m->len;
  t->cmd = (uint8)(E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS);
  tx_mbufs[idx] = m;
  regs[E1000_TDT] = (idx + 1) % TX_RING_SIZE;
  release(&e1000_lock);
  return 0;
}

注意,我们这里使用了锁,因为这里涉及到了临界区的竞争问题:tx_ringtx_mbufs 这两个临界区,所以我们必须要使用锁来保证次序。

(如果一开始想不到的话,可以去 kernel/uart.c 中的 uartputc 看看)

# e1000_recv

这个我就不解释 Hint 了描述了,直接上代码:

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()).
  //

  while (1) {
    uint32 idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
    struct rx_desc* r = &rx_ring[idx];

    if ((r->status & E1000_RXD_STAT_DD) == 0)
      return;

    struct mbuf* m = rx_mbufs[idx];
    mbufput(m, r->length);
    net_rx(m);
    rx_mbufs[idx] = mbufalloc(0);

    if (!rx_mbufs[idx])
      panic("e1000_recv");

    rx_ring[idx].addr = (uint64)rx_mbufs[idx]->head;
    rx_ring[idx].status = 0;
    regs[E1000_RDT] = idx;
  }
}

注意到这里,我们并没有使用锁,这是因为因为e1000_recv()是在e1000_intr()中被调用的,也就是说这实际上是一个 interrupt handler,只有一个进程在跑这个handler,因此不存在共享的数据结构。

(如果这个也不能想到的话,也可以查看 kernel/uart.c 中的 uartgetc 实现)

# 实验结果

Final Grade

使用 Hugo 构建