https://pdos.csail.mit.edu/6.828/2023/labs/net.html
为E1000实现驱动,补全kernel/e1000.c中的两个空函数。
为了达成目的,需要看E1000的文档,对E1000有足够的了解。
驱动程序分为top、bottom两部分:
本实验中,top部分的调用链示例:

网卡约定的数据格式。当收到一个数据包时,网卡填充的信息。

与之对应的代码是:


代码中与之对应的数据结构是tx_desc。
手册中约定了控制寄存器的地址,例如环形队列的地址…:

与之对应的宏定义在kernel/e1000_dev.h中
举例:
以接收环形队列为例:
为硬件所有,当网卡收到包时,会检查环形队列 head 位置的描述符。然后把数据写入 head 描述符的缓冲区。

接收功能的初始化代码,初始化了环形队列rx_ring;对应的mbuf;位于指定内存中的控制寄存器regs:

所以,接收函数就是要去实现处理这个环形队列中已有的待处理包。
此时,生产者是硬件网卡(维护head指针),消费者是需要实现的接收函数(维护tail指针)。
生产者消费者之间的通讯方式是:网卡收到包时产生中断,中断处理程序去调用接收函数。
作为生产者将入参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_tx);
// 在环形队列中获取发送描述符位置
uint idx = regs[E1000_TDT] % TX_RING_SIZE;
struct tx_desc *desc = &tx_ring[idx];
if (desc->status & E1000_TXD_STAT_DD) {
// 发送描述符对应的mbuf
if (tx_mbufs[idx]) {
mbuffree(tx_mbufs[idx]);
}
desc->addr = (uint64)m->head;
desc->length = m->len;
desc->cmd = E1000_TXD_CMD_RS | E1000_TXD_CMD_EOP;
tx_mbufs[idx] = m;
regs[E1000_TDT] = (idx + 1) % TX_RING_SIZE;
} else {
goto fail;
}
release(&e1000_lock_tx);
return 0;
fail:
release(&e1000_lock_tx);
printf("e1000 tx error.\n");
return -1;
}
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_rx);
while (1) {
uint idx = (regs[E1000_RDT] + 1) % RX_RING_SIZE;
struct rx_desc *desc = &rx_ring[idx];
if (desc->status & E1000_RXD_STAT_DD) {
// 获取描述符对应的mbuf
rx_mbufs[idx]->len = desc->length;
// 将包交给处理函数,解析包头协议&路由给对应的协议栈
net_rx(rx_mbufs[idx]);
// 收尾
rx_mbufs[idx] = mbufalloc(0);
desc->addr = (uint64)rx_mbufs[idx]->head;
desc->status = 0;
regs[E1000_RDT] = idx;
} else {
goto end;
}
}
end:
release(&e1000_lock_rx);
return;
}
make grade进行测试。desc->addr存储的是是mbuf->head。