网页:https://pdos.csail.mit.edu/6.S081/2023/labs/net.html
E1000 网卡手册:https://pdos.csail.mit.edu/6.S081/2023/readings/8254x_GBe_SDM.pdf
任务:分析清楚 e1000 网卡的初始化过程 (完成)
PCI 全称是 Peripheral Component Interconnect,外围部件互联,相关可以看这里
【(done) 什么是 PCI? Peripheral Component Interconnect】
根据 Lecture 上得到的知识:网卡接收到包时会产生一个中断,可知 xv6 应该注册了和网络相关的中断,我们先来读 main.c
main.c 源码显示调用了 pci_init 和 sockinit 初始化中断和套接字
如下是 pci_init 源码
void
pci_init()
{
// vm.c 中的 kvmmake 已经提前映射了 0x30000000L 和 0x40000000L
// e1000 的寄存器映射在 0x40000000L
// PCIe 的配置空间映射在 0x30000000L
// we'll place the e1000 registers at this address.
// vm.c maps this range.
uint64 e1000_regs = 0x40000000L;
// qemu -machine virt puts PCIe config space here.
// vm.c maps this range.
uint32 *ecam = (uint32 *) 0x30000000L;
// 扫描 PCIe 配置空间,查找设备
// look at each possible PCI device on bus 0.
for(int dev = 0; dev < 32; dev++){
int bus = 0;
int func = 0;
int offset = 0;
uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset);
volatile uint32 *base = ecam + off;
uint32 id = base[0];
// 如果找到了 e1000
// 100e:8086 is an e1000
if(id == 0x100e8086){
// 设置 e1000 的属性 (这里要看 e1000 的手册)
// command and status register.
// bit 0 : I/O access enable
// bit 1 : memory access enable
// bit 2 : enable mastering
base[1] = 7;
__sync_synchronize();
// 对 e1000 的配置空间做一些操作,具体是啥咱不关心
for(int i = 0; i < 6; i++){
uint32 old = base[4+i];
// writing all 1's to the BAR causes it to be
// replaced with its size.
base[4+i] = 0xffffffff;
__sync_synchronize();
base[4+i] = old;
}
// NOTE: 这个很有趣!把 e1000 的寄存器映射在 0x40000000
// tell the e1000 to reveal its registers at
// physical address 0x40000000.
base[4+0] = e1000_regs;
// 进一步初始化
e1000_init((uint32*)e1000_regs);
}
}
}
上面源码比较有意思的部分就是 base[4+0] = e1000_regs;
,把 e1000 寄存器映射在 0x40000000L
这里可以看看 E1000 网卡手册:
可知 base[4+0] 的内存位置是 “Base Address 0a”,看下面的解释
xv6 采用的是 PCI mode — 32-bit BARs,所以刚刚写入 base[4+0] 实际上是在设置 e1000 网卡寄存器的映射内存地址(基地址)。
再来看 e1000_init,源码如下:
// called by pci_init().
// xregs is the memory address at which the
// e1000's registers are mapped.
void
e1000_init(uint32 *xregs)
{
int i;
initlock(&e1000_lock, "e1000");
regs = xregs;
// Reset the device
regs[E1000_IMS] = 0; // disable interrupts
regs[E1000_CTL] |= E1000_CTL_RST;
regs[E1000_IMS] = 0; // redisable interrupts
__sync_synchronize();
// [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;
if(sizeof(tx_ring) % 128 != 0)
panic("e1000");
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;
if(sizeof(rx_ring) % 128 != 0)
panic("e1000");
regs[E1000_RDH] = 0;
regs[E1000_RDT] = RX_RING_SIZE - 1;
regs[E1000_RDLEN] = sizeof(rx_ring);
// filter by qemu's MAC address, 52:54:00:12:34:56
regs[E1000_RA] = 0x12005452;
regs[E1000_RA+1] = 0x5634 | (1<<31);
// multicast table
for (int i = 0; i < 4096/32; i++)
regs[E1000_MTA + i] = 0;
// transmitter control bits.
regs[E1000_TCTL] = E1000_TCTL_EN | // enable
E1000_TCTL_PSP | // pad short packets
(0x10 << E1000_TCTL_CT_SHIFT) | // collision stuff
(0x40 << E1000_TCTL_COLD_SHIFT);
regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap
// receiver control bits.
regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver
E1000_RCTL_BAM | // enable broadcast
E1000_RCTL_SZ_2048 | // 2048-byte rx buffers
E1000_RCTL_SECRC; // strip CRC
// ask e1000 for receive interrupts.
regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer)
regs[E1000_RADV] = 0; // interrupt after every packet (no timer)
regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back
}
这里应该是读写了一堆 e1000 的寄存器,这里为了节约时间就不细看了。
需要注意的是,e1000.c 有个函数 e1000_intr
void
e1000_intr(void)
{
// tell the e1000 we've seen this interrupt;
// without this the e1000 won't raise any
// further interrupts.
regs[E1000_ICR] = 0xffffffff;
e1000_recv();
}
这个函数调用了 e1000_recv。可以很自然的认为 xv6 接受网络包的流程是:
网卡接受包 ----> 网卡产生中断 ----> 网卡进入 e1000_intr ----> 进入 e1000_recv 处理接收到的网络包