一、背景
本实验开发一个网卡驱动程序,其相关代码分布如下:
- kernel/e1000.c包含E1000的初始化代码以及你需要实现的transmitting与receiving数据包函数;
- kernel/e1000_dev包含寄存器定义和E1000开发者文档定义的flag bits;
- kernel/net.c和kernel/net.h包含简单的IP、UDP、ARP网络协议栈。这些文件中使用mbuf存放数据包;
- 最后,kernel/pci.c实现了xv6启动时在PCI总线搜索E1000网卡。
任务:实现e1000_transmit和e1000_recv,使得网络驱动能够发送和接收数据包。
细节:
- e1000_init函数配置了tx_ring、tx_mbufs和rx_ring、rx_mbufs,采用的是DMA技术传输数据。
- 考虑到突发流量,E1000在e1000_init中初始化了多个buffer用来写packets,采用tx_desc、tx_desc来描述。
- 当协议栈调用e1000_transmit发送packet时,会从一个mbuf中的addr读取packet并发送出去。
- 当E1000接收到packet时,首先将packet写入到RX ring中,然后生成一个中断。
实现效果:
- 在一个窗口运行make server,然后在另外一个窗口运行make qemu并在xv6中运行nettests。第一个test case将发送UDP packet到宿主机。
- 在宿主机发送reply时会发送ARP解析下一站MAC地址,并期望xv6返回一个ARP reply,也就是要先ping 通。
本实验的实现需要了解xv6系统的网络协议及其实现,可以参考:xv6源码分析--networking
二、代码实现
1 发送端
代码语言:c复制int
e1000_transmit(struct mbuf *m)
{
acquire(&e1000_lock);
uint32 bufindex=regs[E1000_TDT];
struct tx_desc *desc=&tx_ring[bufindex];
//该packet已经发送完了
if(!(desc->status&E1000_TXD_STAT_DD)){
release(&e1000_lock);
return -1;
}
//如果该mbuf还未释放,则释放掉
if(tx_mbufs[bufindex]){
mbuffree(tx_mbufs[bufindex]);
tx_mbufs[bufindex]=0;
}
desc->addr=(uint64)m->head;
desc->length=m->len;
//该packet是完整的
desc->cmd=E1000_TXD_CMD_EOP | E1000_TXD_CMD_RS;
tx_mbufs[bufindex]=m;
regs[E1000_TDT]=(regs[E1000_TDT] 1)%TX_RING_SIZE;
release(&e1000_lock);
return 0;
}
2 接收端
代码语言:c复制static void
e1000_recv(void)
{
while(1){
uint32 bufindex=(regs[E1000_RDT] 1)%RX_RING_SIZE;
struct rx_desc * desc=&rx_ring[bufindex];
if(!(desc->status & E1000_RXD_STAT_DD))
return;
rx_mbufs[bufindex]->len=desc->length;
//数据由设备写入到内存中,不经过CPU,直接调用协议栈处理即可
net_rx(rx_mbufs[bufindex]);
rx_mbufs[bufindex]=mbufalloc(0);
desc->addr=(uint64)rx_mbufs[bufindex]->head;
desc->status=0;
regs[E1000_RDT]=bufindex;
}
}