MIT 6.828 Lab6 网卡驱动简单介绍

911 阅读3分钟

MIT 6.828 Lab6 网卡驱动

Lab 6 的内容是 E1000 网卡相关的网卡驱动部分,这部分的难度在于缺少了之前 Lab 部分预先准备好的函数与 Hint,大部分内容得自己根据 Intel 的手册去完成,此外 PCI 这边也是在做 Lab 之前需要掌握的一个知识点,Lab 6 socket 发送信息的流程如图示1:

image-20210917111436301

Lab 6 的内容图示如下:

image-20210903104333179

1. PCI

PCI 全称为 Peripheral Component Interconnect ,外部组件互连标准,通过 PCI 总线,CPU 可以跟外部的组件进行通信与数据传输。具体内容可以参考:wiki.osdev.org/PCI

1.1 配置空间

PCI 标准为每个设备提供一个配置空间,用来进行设备的初始化和配置

The PCI specification provides for totally software driven initialization and configuration of each device (or target) on the PCI Bus via a separate Configuration Address Space. All PCI devices, except host bus bridges, are required to provide 256 bytes of configuration registers for this purpose.

访问方式,大多数 CPU 并不提供访问 PCI 设备配置空间的访问机制,一般由 Host to PCI 的桥接设备提供。

Systems must provide a mechanism that allows access to the PCI configuration space, as most CPUs do not have any such mechanism. This task is usually performed by the Host to PCI Bridge (Host Bridge).

CPU 可以通过 CONFIG_ADDRESSCONFIG_DATA 这两个寄存器访问配置空间,Lab 6 提供的访问代码如下:

image-20211018131145477

static void
pci_conf1_set_addr(uint32_t bus,
		   uint32_t dev,
		   uint32_t func,
		   uint32_t offset)
{
	assert(bus < 256);
	assert(dev < 32);
	assert(func < 8);
	assert(offset < 256);
	assert((offset & 0x3) == 0);

	uint32_t v = (1 << 31) |		// config-space
		(bus << 16) | (dev << 11) | (func << 8) | (offset);
	outl(pci_conf1_addr_ioport, v);
}

static uint32_t
pci_conf_read(struct pci_func *f, uint32_t off)
{
	pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off);
	return inl(pci_conf1_data_ioport);
}

static void
pci_conf_write(struct pci_func *f, uint32_t off, uint32_t v)
{
	pci_conf1_set_addr(f->bus->busno, f->dev, f->func, off);
	outl(pci_conf1_data_ioport, v);
}

1.2 组织方式

简单来说,PCI 的设备组织方式为树形,根节点为 root_bus 。

image-20211018141849381

PCI 相关的初始化,扫描,启用可以参考:pci_initpci_scan_buspci_func_enable

2. Descriptor

CPU 与 E1000 网卡的数据发送与接收通过 descriptor 来处理,这部分刚开始有些迷惑性,但将 head 与 tail 看做读写指针即可(另外还要考虑环形队列的索引变化),因为 head 的取值不准,所以在发送与接收时,都通过判断 tail 相关的信息与标志位。另外,E1000 这边很多标志位是组合起来用的,编程中需要进行对应的设置与判断,常见的有 E1000_DESC_COMMAND_RSE1000_DESC_STATUS_DD ,一个指示 E1000 在读写相关 descriptor 时记录状态,一个用于记录当前描述符已处于就绪状态。

关于具体的交互细节,可以参考官方提供的 E1000 的手册来,整体流程下来感觉都是在进行状态位的设置,这边可以参考 Lab3 的计算理论内容,将 CPU 及网卡看做上位机与下位机,其余部分主要为两个图灵机的分布式通信,以及状态设置。

image-20210911164347125

3. Server

Lab 6 中网络系统的各组件示意图如下,通过 lwIp 来对外暴露 socket API,底层的读写还是通过 low_level_io 来与网卡进行交互,以 socket_write 为例,涉及到的调用路径如下:

image-20210917111436301

Network server architecture

最后完成后 httpd 的服务器效果图如下:

image-20210913142939664

通过设置 NAT 网络的端口转发机制,可以在 Windows 上访问。

image-20210915113128053