MIT 6.S081 Lab 11 -- NetWork -- 上
- 引言
- Network
- 背景
- 你的工作(hard)
- 提示
- Lab 解析
- E1000 网卡重点内容官方手册摘录
- 3.2 Packet Reception
- 3.3 Packet Transmission
- 小结
引言
本文为 MIT 6.S081 2020 操作系统 实验十一解析。
MIT 6.S081课程前置基础参考: 基于RISC-V搭建操作系统系列
Network
在本实验室中,您将为网络接口卡(NIC)编写一个xv6设备驱动程序。
获取xv6实验的源代码并切换到net
分支:
$ git fetch
$ git checkout net
$ make clean
背景
Tip:
- 在编写代码之前,您可能会发现阅读xv6手册中的《第5章:中断和设备驱动》很有帮助。
您将使用名为E1000的网络设备来处理网络通信。对于xv6(以及您编写的驱动程序),E1000看起来像是连接到真正以太网局域网(LAN)的真正硬件。事实上,用于与您的驱动程序对话的E1000是qemu提供的模拟,连接到的LAN也由qemu模拟。在这个模拟LAN上,xv6(“来宾”)的IP地址为10.0.2.15。Qemu还安排运行Qemu的计算机出现在IP地址为10.0.2.2的LAN上。当xv6使用E1000将数据包发送到10.0.2.2时,qemu会将数据包发送到运行qemu的(真实)计算机上的相应应用程序(“主机”)。
您将使用QEMU的“用户模式网络栈(user-mode network stack
)”。QEMU的文档中有更多关于用户模式栈的内容。我们已经更新了Makefile以启用QEMU的用户模式网络栈和E1000网卡。
Makefile将QEMU配置为将所有传入和传出数据包记录到实验目录中的packets.pcap文件中。查看这些记录可能有助于确认xv6正在发送和接收您期望的数据包。要显示记录的数据包,请执行以下操作:
tcpdump -XXnr packets.pcap
我们已将一些文件添加到本实验的xv6存储库中。kernel/e1000.c文件包含E1000的初始化代码以及用于发送和接收数据包的空函数,您将填写这些函数。kernel/e1000_dev.h包含E1000定义的寄存器和标志位的定义,并在《英特尔E1000软件开发人员手册》中进行了描述。kernel/net.c和kernel/net.h包含一个实现IP、UDP和ARP协议的简单网络栈。这些文件还包含用于保存数据包的灵活数据结构(称为mbuf
)的代码。最后,kernel/pci.c包含在xv6引导时在PCI总线上搜索E1000卡的代码。
你的工作(hard)
YOUR JOB:
- 您的工作是在kernel/e1000.c中完成
e1000_transmit()
和e1000_recv()
,以便驱动程序可以发送和接收数据包。当make grade
表示您的解决方案通过了所有测试时,您就完成了。
TIPS:
- 在编写代码时,您会发现自己参考了《E1000软件开发人员手册》。以下部分可能特别有用:
- Section 2是必不可少的,它概述了整个设备。
- Section 3.2概述了数据包接收。
- Section 3.3与Section 3.4一起概述了数据包传输。
- Section 13概述了E1000使用的寄存器。
- Section 14可能会帮助您理解我们提供的init代码。
浏览《E1000软件开发人员手册》。本手册涵盖了几个密切相关的以太网控制器。QEMU模拟82540EM。现在浏览第2章,了解该设备。要编写驱动程序,您需要熟悉第3章和第14章以及第4.1节(虽然不包括4.1的子节)。你还需要参考第13章。其他章节主要介绍你的驱动程序不必与之交互的E1000组件。一开始不要担心细节;只需了解文档的结构,就可以在以后找到内容。E1000具有许多高级功能,其中大部分您可以忽略。完成这个实验只需要一小部分基本功能。
我们在e1000.c中提供的e1000_init()
函数将E1000配置为读取要从RAM传输的数据包,并将接收到的数据包写入RAM。这种技术称为DMA,用于直接内存访问,指的是E1000硬件直接向RAM写入和读取数据包。
由于数据包突发到达的速度可能快于驱动程序处理数据包的速度,因此e1000_init()
为E1000提供了多个缓冲区,E1000可以将数据包写入其中。E1000要求这些缓冲区由RAM中的“描述符”数组描述;每个描述符在RAM中都包含一个地址,E1000可以在其中写入接收到的数据包。struct rx_desc
描述描述符格式。描述符数组称为接收环或接收队列。它是一个圆环,在这个意义上,当网卡或驱动程序到达队列的末尾时,它会绕回到数组的开头。e1000_init()
使用mbufalloc()
为要进行DMA的E1000分配mbuf
数据包缓冲区。此外还有一个传输环,驱动程序将需要E1000发送的数据包放入其中。e1000_init()
将两个环的大小配置为RX_RING_SIZE
和TX_RING_SIZE
。
当net.c中的网络栈需要发送数据包时,它会调用e1000_transmit()
,并使用一个保存要发送的数据包的mbuf
作为参数。传输代码必须在TX(传输)环的描述符中放置指向数据包数据的指针。struct tx_desc
描述了描述符的格式。您需要确保每个mbuf
最终被释放,但只能在E1000完成数据包传输之后(E1000在描述符中设置E1000_TXD_STAT_DD
位以指示此情况)。
当E1000从以太网接收到每个包时,它首先将包DMA到下一个RX(接收)环描述符指向的mbuf
,然后产生一个中断。e1000_recv()
代码必须扫描RX环,并通过调用net_rx()
将每个新数据包的mbuf
发送到网络栈(在net.c中)。然后,您需要分配一个新的mbuf
并将其放入描述符中,以便当E1000再次到达RX环中的该点时,它会找到一个新的缓冲区,以便DMA新数据包。
除了在RAM中读取和写入描述符环外,您的驱动程序还需要通过其内存映射控制寄存器与E1000交互,以检测接收到数据包何时可用,并通知E1000驱动程序已经用要发送的数据包填充了一些TX描述符。全局变量regs
包含指向E1000第一个控制寄存器的指针;您的驱动程序可以通过将regs
索引为数组来获取其他寄存器。您需要特别使用索引E1000_RDT
和E1000_TDT
。
要测试驱动程序,请在一个窗口中运行make server
,在另一个窗口中运行make qemu
,然后在xv6中运行nettests
。nettests
中的第一个测试尝试将UDP数据包发送到主机操作系统,地址是make server
运行的程序。如果您还没有完成实验,E1000驱动程序实际上不会发送数据包,也不会发生什么事情。
完成实验后,E1000驱动程序将发送数据包,qemu将其发送到主机,make server
将看到它并发送响应数据包,然后E1000驱动程序和nettests
将看到响应数据包。但是,在主机发送应答之前,它会向xv6发送一个“ARP”请求包,以找出其48位以太网地址,并期望xv6以ARP应答进行响应。一旦您完成了对E1000驱动程序的工作,kernel/net.c就会处理这个问题。如果一切顺利,nettests
将打印testing ping: OK
,make server
将打印a message from xv6!
。
tcpdump -XXnr packets.pcap
应该生成这样的输出:
reading from file packets.pcap, link-type EN10MB (Ethernet)
15:27:40.861988 IP 10.0.2.15.2000 > 10.0.2.2.25603: UDP, length 19
0x0000: ffff ffff ffff 5254 0012 3456 0800 4500 ......RT..4V..E.
0x0010: 002f 0000 0000 6411 3eae 0a00 020f 0a00 ./....d.>.......
0x0020: 0202 07d0 6403 001b 0000 6120 6d65 7373 ....d.....a.mess
0x0030: 6167 6520 6672 6f6d 2078 7636 21 age.from.xv6!
15:27:40.862370 ARP, Request who-has 10.0.2.15 tell 10.0.2.2, length 28
0x0000: ffff ffff ffff 5255 0a00 0202 0806 0001 ......RU........
0x0010: 0800 0604 0001 5255 0a00 0202 0a00 0202 ......RU........
0x0020: 0000 0000 0000 0a00 020f ..........
15:27:40.862844 ARP, Reply 10.0.2.15 is-at 52:54:00:12:34:56, length 28
0x0000: ffff ffff ffff 5254 0012 3456 0806 0001 ......RT..4V....
0x0010: 0800 0604 0002 5254 0012 3456 0a00 020f ......RT..4V....
0x0020: 5255 0a00 0202 0a00 0202 RU........
15:27:40.863036 IP 10.0.2.2.25603 > 10.0.2.15.2000: UDP, length 17
0x0000: 5254 0012 3456 5255 0a00 0202 0800 4500 RT..4VRU......E.
0x0010: 002d 0000 0000 4011 62b0 0a00 0202 0a00 .-....@.b.......
0x0020: 020f 6403 07d0 0019 3406 7468 6973 2069 ..d.....4.this.i
0x0030: 7320 7468 6520 686f 7374 21 s.the.host!
您的输出看起来会有些不同,但它应该包含字符串“ARP, Request”,“ARP, Reply”,“UDP”,“a.message.from.xv6”和“this.is.the.host”。
nettests
执行一些其他测试,最终通过(真实的)互联网将DNS请求发送到谷歌的一个名称服务器。您应该确保您的代码通过所有这些测试,然后您应该看到以下输出:
$ nettests
nettests running on port 25603
testing ping: OK
testing single-process pings: OK
testing multi-process pings: OK
testing DNS
DNS arecord for pdos.csail.mit.edu. is 128.52.129.126
DNS OK
all tests passed.
您应该确保make grade
同意您的解决方案通过。
提示
首先,将打印语句添加到e1000_transmit()
和e1000_recv()
,然后运行make server
和(在xv6中)nettests
。您应该从打印语句中看到,nettests
生成对e1000_transmit
的调用。
实现e1000_transmit
的一些提示:
- 首先,通过读取
E1000_TDT
控制寄存器,向E1000询问等待下一个数据包的TX环索引。 - 然后检查环是否溢出。如果
E1000_TXD_STAT_DD
未在E1000_TDT
索引的描述符中设置,则E1000尚未完成先前相应的传输请求,因此返回错误。 - 否则,使用
mbuffree()
释放从该描述符传输的最后一个mbuf
(如果有)。 - 然后填写描述符。
m->head
指向内存中数据包的内容,m->len
是数据包的长度。设置必要的cmd标志(请参阅E1000手册的第3.3节),并保存指向mbuf
的指针,以便稍后释放。 - 最后,通过将一加到
E1000_TDT再对TX_RING_SIZE
取模来更新环位置。 - 如果
e1000_transmit()
成功地将mbuf
添加到环中,则返回0。如果失败(例如,没有可用的描述符来传输mbuf
),则返回-1,以便调用方知道应该释放mbuf
。
实现e1000_recv
的一些提示:
- 首先通过提取
E1000_RDT
控制寄存器并加一对RX_RING_SIZE
取模,向E1000询问下一个等待接收数据包(如果有)所在的环索引。 - 然后通过检查描述符
status
部分中的E1000_RXD_STAT_DD
位来检查新数据包是否可用。如果不可用,请停止。 - 否则,将
mbuf
的m->len
更新为描述符中报告的长度。使用net_rx()
将mbuf
传送到网络栈。 - 然后使用
mbufalloc()
分配一个新的mbuf
,以替换刚刚给net_rx()
的mbuf
。将其数据指针(m->head
)编程到描述符中。将描述符的状态位清除为零。 - 最后,将
E1000_RDT
寄存器更新为最后处理的环描述符的索引。 e1000_init()
使用mbufs初始化RX环,您需要通过浏览代码来了解它是如何做到这一点的。- 在某刻,曾经到达的数据包总数将超过环大小(16);确保你的代码可以处理这个问题。
您将需要锁来应对xv6可能从多个进程使用E1000,或者在中断到达时在内核线程中使用E1000的可能性。
Lab 解析
E1000 网卡重点内容官方手册摘录
- 本部分是笔者个人拙劣翻译,个人对硬件方面不是特别了解,所以翻译的质量可能不高,大家可以配合手册对应章节凑合着看。
- 前半部分翻译较为完整,后面省去一些不太重要的点,以及本lab中不太相关的内容。
3.2 Packet Reception
总的来说,数据包接收过程由识别线路上存在的合法数据包,进行地址过滤,将数据包缓存到网卡内部的receive FIFO队列中,将数据包传输到主存对应的receive buffer中,最后更新receive descriptor的状态信息这几步组成。
3.2.1 Packet Address Filtering:
- 硬件根据以下过滤模式在主机内存中存储传入数据包:
- 如果网卡内部的receive FIFO队列没有足够空间了,那么会丢弃这些数据包,并使用相关的statistics registers寄存器来保存硬件丢包数量
网卡本身还支持下面的一些过滤模式:
- 精确单播/组播(Exact Unicast/Multicast):目标地址必须与16个预存的地址之一完全匹配。这些地址可以是单播(unicast)或组播(multicast)地址。
- 混杂单播(Promiscuous Unicast):接收所有的单播数据包,无论目标地址是什么。
- 组播(Multicast):入站数据包的目标地址的高位用于索引一个4096位的位向量(bit vector),该位向量指示是否接受数据包。如果位向量中对应索引的位为1,则接受该数据包;否则,拒绝该数据包。软件提供四个选择用于确定索引的位是目标地址内部存储表示的哪几位,即[47:36]、[46:35]、[45:34]或[43:32]。
- 混杂组播(Promiscuous Multicast):接收所有的组播数据包,无论目标地址是什么。
- VLAN:接收所有VLAN1数据包,且这些数据包是针对本站点的,并在VLAN过滤表中设置了适当的位。
- 如果数据包的目的MAC地址与当前主机的MAC地址不匹配,通常情况下后续的过滤步骤将继续进行。虽然数据包不是直接发送给当前主机的,但仍然可以根据配置的过滤模式对该数据包进行进一步的过滤和决策。
- 例如,在精确单播/组播模式下,会检查数据包的目的地址是否与预存的地址列表中的任何一个地址完全匹配。如果匹配成功,数据包将被接受并存储在主机内存中。如果匹配失败,即使目的MAC地址不是当前主机的地址,后续的过滤模式仍然会被应用。
正常来说,只有符合要求的数据包才会被网卡接收: 没有CRC错误,符号错误,序列错误,长度错误,对齐错误,载波扩展错误以及接收错误。然而,如果在设备控制寄存器(RCTL)的store-bad-packet位(RCTL.SBP)被设置,那么通过过滤函数的错误数据包也会被存储在主机内存中。数据包的错误会通过接收描述符(RDESC)中的错误位(RDESC.ERRORS)进行指示。
通过设置混杂使能位(RCTL.UPE/MPE)和store-bad-packet位(RCTL.SBP),可以接收所有的数据包,无论它们是否出现错误。这样设置可以导致主机接收并存储所有传入的数据包,包括有错误的数据包。
需要注意的是,这种设置的目的可能是进行特定的调试、错误分析或监测任务。在正常情况下,通常只有良好的数据包被接收和处理,而错误的数据包会被丢弃。只有在特定的需求或测试场景下,才会将设备设置为接收和存储所有数据包,包括错误的数据包。这种设置可能会增加主机处理和存储的负担,并且需要谨慎使用以避免不必要的资源消耗。
当启用管理功能并启用了RCMCP时,如果满足一定条件,ARP请求数据包可以通过SMBus(系统管理总线)直接发送或由ASF(远程管理控制器)控制器内部进行处理,而不会传递到主机内存中。需要注意的是,这个特性对于82544GC/EI或82541ER型号的网卡不适用。
3.2.2 Receive Data Storage:
网卡通过descriptors指向的主存buffer地址,来将接收到的packet传输到对应地址处;硬件支持七种buffer size大小:
- 256 B
- 512 B
- 1024 B
- 2048 B
- 4096 B
- 8192 B
- 16384 B
通过设置Receive Control register (RCTL.BSIZE & RCTL.BSEX)的对应位完成硬件buffer size的选择。
以太网控制器对主存中packet buffer的地址没有严格的对齐限制,主要考虑到receive buffer可能是在网络的上层进行的分配,这些higher layers可能并不了解特定的以太网控制器地址对齐要求。
在e1000硬件中,接收缓冲区的对齐方式(alignment)是完全不受限制的,即可以在任意字节对齐。然而,强烈建议在可能的情况下,软件将接收缓冲区分配在至少缓存行边界上。
缓存行是计算机体系结构中的内存管理单位,它是从主存中读取的最小数据块大小。对接收缓冲区进行缓存行对齐,可以提高性能和效率。当接收缓冲区在缓存行边界上对齐时,处理器可以更有效地访问这些缓冲区,因为它可以按照缓存行的大小进行高速缓存操作,而无需跨越多个缓存行。
通过在至少缓存行边界上进行接收缓冲区的分配,可以最大程度地利用硬件的高速缓存机制,从而提高数据的读取效率和处理性能。这对于处理高速数据流或实时应用程序特别重要。
3.2.3 Receive Descriptor Format:
一个接收描述符(receive descriptor)是一个数据结构,其中包含接收数据缓冲区的地址以及硬件存储数据包信息的字段。在表3-1中,阴影区域表示硬件在接收数据包时会修改的字段。
文档下面给出的note描述个人理解是:
- 通过借助硬件来完成TCP校验和计算,可以提高数据包处理的效率和性能。硬件通常具有专用的电路和算法来执行校验和计算,可以在硬件层面上更快速地完成计算,并且减轻了主机处理器的负担。
- 对于标准的802.3数据包(非VLAN),硬件会默认计算整个数据包从目的地址(DA)的第一个字节到CRC的最后一个字节的校验和。因此,对于这些数据包,可以直接使用硬件计算得到的校验和值进行验证。
- 然而,为了使用硬件计算得到的校验和值正确验证TCP校验和,软件需要调整校验和值,以排除不属于真正TCP校验和的字节。这是因为TCP校验和仅涵盖了TCP头部和数据部分,而不包括以太网和IP头部。
- 因此,通过硬件协助进行TCP校验和计算,可以加速数据包处理,并且通过适当的调整,软件可以使用硬件计算得到的校验和值来准确验证TCP校验和。这样可以在更低的计算成本下实现数据包的完整性验证。
3.2.3.1 Receive Descriptor Status Field:
- status字段用于指示描述符是否已被使用以及所引用的缓冲区是否为数据包的最后一个缓冲区。
- 对于multi-descriptor packets,数据包的状态信息是在数据包的最后一个描述符中提供的(EOP位设置)。
- 如果某个描述符的EOP位没有被设置,那么只有地址(Address)、长度(Length)和完成(DD)位是有效的。
-
PIF (bit 7) :
- 当数据包经过不精确过滤器时,硬件会根据过滤条件判断数据包的类型。如果数据包通过了多播向量(Multicast Vector)过滤,那么PIF字段会被设置为1。这意味着该数据包是一个多播数据包,需要进一步检查其内容以确定是否接受。
- 通过设置PIF字段,硬件可以在传递数据包时提供额外的信息给软件,以帮助软件更快速地决定数据包的接受与否。如果PIF字段被设置为1,软件会意识到该数据包是一个多播数据包,因此会进一步检查数据包的内容来确定是否接受。如果PIF字段被清除为0,软件则知道该数据包是发给本站点的,可以直接接受而无需进一步检查。
-
IPCS (bit 6):
- 在接收数据包时,硬件可以执行IP校验和计算。IP校验和是用于验证IP数据包完整性的一种校验值。
- 在特定的情况下,硬件会根据配置进行IP校验和的计算。当"Ignore Checksum Indication"(IXSM)位被取消设置(0b)时,IPCS位用于指示硬件是否对接收到的数据包执行了IP校验和计算。
- 当IPCS位被设置为0b时,表示硬件没有执行IP校验和计算。
- 当IPCS位被设置为1b时,表示硬件执行了IP校验和计算。
- 关于IP校验和的通过与失败信息可以在描述符中的接收错误(RDESC.ERRORS)中的错误位(IPE)中得到指示。
- 需要注意的是,对于IPv6数据包,IPCS位不会被设置。在读取IPCS位时,它会显示为0b。
- 通过这些位的设置,软件可以了解硬件是否执行了IP校验和计算以及校验和的通过与失败情况。这有助于软件进行进一步的处理和决策,以确保接收到的IP数据包的完整性和准确性。
-
TCPCS (bit 5):
- 在接收数据包时,硬件可以执行TCP/UDP校验和计算。TCP/UDP校验和用于验证TCP或UDP数据包的完整性。
- 当"Ignore Checksum Indication"(IXSM)位被取消设置(0b)时,TCPCS位用于指示硬件是否对接收到的数据包执行了TCP/UDP校验和计算。
- 当TCPCS位被设置为0b时,表示硬件没有执行TCP/UDP校验和计算。
- 当TCPCS位被设置为1b时,表示硬件执行了TCP/UDP校验和计算。
- 关于TCP/UDP校验和的通过与失败信息可以在描述符中的接收错误(RDESC.ERRORS)中的错误位(TCPE)中得到指示。
- 需要注意的是,对于IPv6数据包,如果TCP/UDP数据包被识别,那么TCPCS位可能被设置。在读取TCPCS位时,它会显示为0b。
- 通过这些位的设置,软件可以了解硬件是否执行了TCP/UDP校验和计算以及校验和的通过与失败情况。这有助于软件进行进一步的处理和决策,以确保接收到的TCP/UDP数据包的完整性和准确性。
-
RSV (bit 4) : 保留位(Reserved)
-
VP (bit 3) :
- 当数据包的类型与VET(VLAN EtherType)匹配时,该位被设置为1。它指示着入站数据包是否为VLAN(802.1q)类型的数据包。
-
IXSM (bit 2):
- 当"Ignore Checksum Indication"(IXSM)位被设置为1b时,校验和指示结果(IPCS、TCPCS位)应被忽略。
- 在这种情况下,硬件执行的IP或TCP/UDP校验和(如果有的话)的指示结果(IPCS、TCPCS位)应被忽略。也就是说,无论IPCS和TCPCS位的值是什么,都不应该依赖它们来判断硬件是否执行了相应的校验和计算。
- 而当IXSM位被设置为0b时,IPCS和TCPCS位会指示硬件是否对接收到的数据包执行了IP或TCP/UDP校验和计算。
- 当IPCS位被设置为0b时,表示硬件没有执行IP校验和计算。
- 当IPCS位被设置为1b时,表示硬件执行了IP校验和计算。
- 当TCPCS位被设置为0b时,表示硬件没有执行TCP/UDP校验和计算。
- 当TCPCS位被设置为1b时,表示硬件执行了TCP/UDP校验和计算。
- 关于校验和的通过与失败信息可以在状态位中得到指示,具体如下所述:
- 对于IP校验和,校验和的通过与失败信息可以在状态位的IPE(IP Error)中得到指示。
- 对于TCP/UDP校验和,校验和的通过与失败信息可以在状态位的TCPE(TCP/UDP Error)中得到指示。
- 通过这些位的设置,可以了解硬件是否执行了IP或TCP/UDP校验和计算以及校验和的通过与失败情况。软件可以根据这些信息进行进一步的处理和决策,以确保接收到的数据包的完整性和准确性。
-
EOP (bit 1):
- “End of Packet”(EOP)指示了该描述符是否为入站数据包的最后一个描述符。
- 在接收数据包时,通常一个数据包会被拆分为多个描述符进行传输。每个描述符对应数据包的一部分。EOP位用于指示当前描述符是否为数据包的最后一个描述符。
- 当EOP位被设置为1时,表示该描述符是数据包的最后一个描述符。这意味着所有与该数据包相关的描述符都已传输完毕。
- 通过检查EOP位,软件可以确定数据包的边界,并知道何时一个完整的数据包已经接收完毕。
- 需要注意的是,EOP位只在多描述符数据包中才有意义。对于单描述符数据包,由于只有一个描述符,它必然是数据包的最后一个描述符,因此EOP位的设置是显而易见的。
- 通过EOP位,软件可以识别出数据包的结束,从而进行相应的处理和后续操作。
-
DD (bit 0):
- “Descriptor Done”(DD)指示硬件是否已经完成了对描述符的处理。
- 当DD位被设置为1时,表示硬件已经完成了对该描述符的处理。这意味着硬件已经使用了该描述符,并且对应的数据包已经完成传输或处理。
- 当EOP位和DD位同时被设置时,表示接收的数据包已经完整地存储在主存储器中。也就是说,整个数据包的所有描述符都已经被硬件处理完毕,并且数据包的内容已经完整地存储在主存储器中。
- 通过检查DD位的设置,软件可以确定硬件是否已经完成了对描述符和数据包的处理。这可以帮助软件确定何时可以安全地访问数据包的内容或进行后续的处理和操作。
- 需要注意的是,DD位的设置通常与其他标志位(如EOP位)一起使用,以提供更准确的描述符状态和数据包的完整性信息。
- 通过DD位,软件可以知道硬件是否已经完成了对描述符和数据包的处理,从而进行相应的操作和处理。
- 当硬件执行了TCP/UDP校验和计算时,硬件会设置TCPCS位来指示这一行为。通过读取TCPCS位,软件可以知道硬件已经完成了校验和计算,并且校验和的结果已经被存储或传递给软件。
- 因此,在软件层面上,如果检测到TCPCS位被设置,软件就可以知道硬件已经完成了校验和计算,因此无需再进行重复的校验和计算操作。
3.2.3.2 Receive Descriptor Errors Field:
大多数错误信息只在设置了"Store Bad Packets"位(RCTL.SBP)且接收到错误数据包时才会出现。
错误位只在描述符状态字段(RDESC.STATUS)中的EOP和DD位被设置时才有效。
当设置了"Store Bad Packets"位(RCTL.SBP)且接收到了错误的数据包时,硬件会将相应的错误信息写入到描述符的状态字段中。
需要注意的是,只有当描述符的EOP和DD位同时被设置时,错误位才是有效的。这意味着当接收到完整的数据包并且硬件已经完成对描述符的处理时,才能读取错误位来获取错误信息。
通过读取描述符的错误位,软件可以了解到数据包接收过程中出现的错误情况,例如CRC错误、符号错误、序列错误等。
总结起来,当设置了"Store Bad Packets"位且接收到了错误的数据包时,描述符的状态字段中的错误位才是有效的。通过读取这些错误位,软件可以获取数据包接收过程中出现的具体错误信息。这有助于软件进行错误处理和调试。
- RXE (bit 7)
- 当数据包的接收过程中发生数据错误时,硬件会设置"RX Data Error"位。在TBIa模式下,接收到/V/码会被认为是数据错误。在GMII或MII模式下,当数据接收过程中I_RX_ER被置位时,也表示发生了数据错误。
- 需要注意的是,"RX Data Error"位只在描述符的EOP和DD位被设置时才是有效的。这意味着只有当接收到完整的数据包并且硬件已经完成对描述符的处理时,才能读取该位来获取数据错误信息。并且,该位在描述符中不会被设置,除非设置了RCTL.SBP(存储错误数据包)控制位。
- 通过检查"RX Data Error"位,软件可以确定在数据包接收过程中是否发生了数据错误。这有助于软件进行错误处理和调试,例如处理接收到的损坏数据包或跟踪数据传输中的问题。
- IPE (bit 6)
- 当设置了"IP Checksum Error"(IP校验和错误)位时,表示在接收到的数据包中检测到了IP校验和错误。该位只在通过RDESC.STATUS字段中的IPCS位指示进行接收数据包的IP校验和计算时才是有效的。
- 如果接收IP校验和卸载被禁用(RXCSUM.IPOFL),则IPE位被设置为0b,表示没有发生IP校验和错误。这不会影响数据包过滤机制。
- TCPE (bit 5)
- 当设置了"TCP/UDP Checksum Error"(TCP/UDP校验和错误)位时,表示在接收到的数据包中检测到了TCP/UDP校验和错误。该位只在通过RDESC.STATUS字段中的TCPCS位指示进行接收数据包的TCP/UDP校验和计算时才是有效的。
- 如果接收TCP/UDP校验和卸载被禁用(RXCSUM.TUOFL),则TCPE位被设置为0b,表示没有发生TCP/UDP校验和错误。这不会影响数据包过滤机制。
- CXE RSV (bit 4)
- "Carrier Extension Error"位用于指示接收到的数据包中传递了载波延长错误的信号。载波延长错误通常与以太网的1000 Mb/s半双工模式相关。当PHY检测到载波延长错误时,它会通过设置相应的信号来指示错误的发生
- RSV (Bit 3) : 保留位
- SEQ (bit 2)
- 在标准的以太网帧格式中,每个数据帧都包含起始帧符(SOF)和结束帧符(EOF),它们用来标识帧的开始和结束位置,并且遵循了一定的格式规则。如果在接收到的以太网帧中,起始帧符、结束帧符、填充或闲置状态等字段的顺序或格式不符合标准要求,那么就会设置“Sequence Error”错误标志,表示该数据帧存在分隔符序列错误。
- 在Ethernet中,一个有效的分隔符序列应该包括以下内容:
- 闲置状态(idle)
- 帧起始符(start-of-frame,SOF)
- 数据
- 填充(pad,可选)
- 帧结束符(end-of-frame,EOF)
- 填充(fill,可选)
- 闲置状态(idle)
- 如果分隔符序列不按照上述顺序或格式进行,则可能会发生“Sequence Error”错误。这通常是由于网络通信中的传输错误或硬件问题造成的。为了解决这个问题,需要对网络设备和通信链路进行详细的故障排除,并确保网络设备和软件都符合标准规范,并且不存在配置错误或硬件故障等问题。
- SE (bit 1) :在以太网协议中,符号错误(Symbol Error)通常意味着某些位被破坏或丢失,从而导致数据包无法正确解析。
- CE (bit 0): CRC错误和对齐错误都可以通过CE位(Control and Status Register Error bit)进行指示。软件可以通过监视相应的统计寄存器来区分这些错误 。
3.2.3.3 Receive Descriptor Special Field:
硬件在接收描述符中为802.1q数据包存储额外的信息。如果数据包类型是802.1q,则由硬件决定,当一个报文类型字段匹配VLAN1以太网寄存器(VET),而RCTL.VME = 1b时,则特殊字段记录VLAN信息,并从数据包存储中剥离四个字节的VLAN信息。以太网控制器将802.1q标记的标签控制信息(TCI)存储在特殊字段中。否则,特殊字段包含0000h。
3.2.4 Receive Descriptor Fetching:
描述符获取策略旨在支持PCI总线上的大型数据传输突发。这是通过使用64个芯片上的接收描述符和优化的获取算法实现的。获取算法尝试通过每个突发获取一个缓存行(或更多)描述符,以最大限度地利用PCI带宽。以下段落简要描述了描述符获取算法及其提供的软件控制:
描述符获取算法:
- 硬件维护两个指针:描述符读指针(RDH)和描述符写指针(RDT)。RDH指向下一个可以读取的描述符,RDT指向下一个可以写入的描述符。
- 软件将RDT设置为最后一个可用描述符的索引。
- 告诉硬件开始处理描述符,此时硬件将自动从RDH处读取描述符,并根据所需的数据传输长度(由RDLEN寄存器指定)在内存中分配空间。
- 在传输完成之前,硬件会更新RDH指针,该指针将指向下一个可用的、尚未被读取的描述符。当RDH等于RDT时,说明没有可用的描述符,即缓冲区已满,此时应停止数据传输。
软件控制:
- 软件需要维护一个指向描述符数组的基地址指针,以及对RDT寄存器的访问权限。这样就可以在需要时更新RDT指针,以便告诉硬件有多少空闲描述符可用。
- 软件还可以在必要时使用另一个特殊的Descriptor Done(DD)位,来告知硬件何时可以重用描述符。
通过优化的描述符获取算法,网络硬件能够在PCI总线上高效地传输大量数据,而且软件控制也相对简单。
当芯片上的缓冲区为空时,只要有任何描述符可用(软件将尾指针写入),就会进行获取。当芯片上的缓冲区几乎为空(RXDCTL.PTHRESH)时,会在主机内存中有足够的有效描述符(RXDCTL.HTHRESH),并且没有更高优先级的其他PCI活动(如描述符获取和回写或数据包传输)时,预取操作会被执行。
当主机内存中的描述符数大于可用的芯片上描述符存储空间时,芯片可能选择执行不是缓存行大小的获取。硬件在这种情况下执行非对齐的获取,如果这样做可以使下一个描述符获取对齐到缓存行边界上。这种机制可以在获取落后于软件的情况下提供最高效率。
Note: The Ethernet controller never fetches descriptors beyond the descriptor TAIL pointer.
3.2.5 Receive Descriptor Write-Back:
处理器的缓存行大小通常大于接收描述符的大小(16字节)。因此,为每个接收到的数据包写回描述符信息会导致昂贵的部分缓存行更新。为了尽量减少这种情况的发生,采用了两种机制:
- 接收描述符打包
- 空描述符填充
下面的章节将解释这些机制。
3.2.5.1 Receive Descriptor Packing:
为了最大化内存效率,接收描述符会被“紧凑”并尽可能地写成缓存行。它们会累积起来,并在以下三种情况之一进行写出:
- 使用了RXDCTL.WTHRESH指定的描述符数量时(即已达到未写入的已使用描述符的最大阈值),它们将被写回,而不管缓存行对齐方式如何。因此建议WTHRESH是缓存行大小的倍数。
- 接收计时器到期(RADV或RDTR)时,所有已使用的描述符都会被写回,然后才会触发中断。
- 软件可以通过设置计时器寄存器的高位来显式刷新累积的描述符。
第二种情况包括一个定时器(RDTR或RADV),它会导致在触发中断之前写回所有已使用的描述符。第三种情况允许软件显式地刷新累积的描述符。通常情况下,在负载下打包是常见情况。
3.2.5.2 Null Descriptor Padding:
硬件不会在具有空数据地址的描述符中存储任何数据。软件可以利用这个特性来尽早触发接收描述符打包中的第一种情况。硬件会将状态字节中DD位设置为1,其他位不变,并回写空描述符。
3.2.6 Receive Descriptor Queue Structure:
图3-2显示了接收描述符环的结构。硬件维护一个循环的描述符环,并在推进头指针之前回写使用过的描述符。当处理了“size”个描述符时,头部和尾部指针会回到基地址。
软件通过将尾指针写为最后一个有效描述符的下一个条目的索引来添加接收描述符。当数据包到达时,它们被存储在内存中,并由硬件递增头指针。当头指针等于尾指针时,环为空。硬件停止在系统内存中存储数据包,直到软件推进尾指针,使更多的接收缓冲区可用。
接收描述符的头部和尾部指针引用内存的16字节块。图中阴影框表示已经存储了传入数据包但尚未被软件识别的描述符。软件可以通过读取内存中的描述符而不是进行I/O读取来确定接收缓冲区是否有效。任何具有非零状态字节的描述符都已由硬件处理,并准备好由软件处理。
注意:头指针指向下一个写回的描述符。在描述符回写操作完成后,该指针将增加已写回的描述符数量。硬件拥有[HEAD和TAIL]之间的所有描述符。不在此范围内的任何描述符都由软件拥有。
接收描述符环由以下寄存器描述:
• 接收描述符基地址寄存器(RDBAL和RDBAH)
- 这些寄存器指示描述符环缓冲区的起始位置。这个64位地址按16字节边界对齐,并存储在连续的两个32位寄存器中。RDBAL包含低32位;RDBAH包含高32位。硬件忽略RDBAL中的低4位。
• 接收描述符长度寄存器(RDLEN)
- 该寄存器确定分配给循环缓冲区的字节数。该值必须是128的倍数(最大缓存行大小)。由于每个描述符长度为16字节,因此接收描述符的总数始终是8的倍数。
• 接收描述符头指针寄存器(RDH)
- 该寄存器保存的值是基地址相对偏移量,表示正在进行的描述符。循环缓冲区中最多可以有64K个描述符。硬件维护一个影子复制,其中包括那些已完成但尚未存储在内存中的描述符。
• 接收描述符尾指针寄存器(RDT)
- 该寄存器保存的值是基地址相对偏移量,标识硬件可以处理的最后一个描述符之后的位置。请注意,尾指针仍应指向描述符环中的某个区域(在RDBA和RDBA + RDLEN之间的某个位置)。这是因为尾指针指向软件写入第一个新描述符的位置。
如果软件静态分配缓冲区,并使用内存读取来检查已完成的描述符,它只需将描述符中的状态字节清零即可使其准备好由硬件重复使用。这不是硬件要求(移动硬件尾指针是必须的),但对于执行内存扫描是必要的。
3.2.7 Receive Interrupts:
以太网控制器可以生成四个与接收相关的中断:
- 接收计时器中断(ICR.RXT0)
- 小封包检测中断 (ICR.SRPD)
- 接收描述符最小阈值中断 (ICR.RXDMT0)
- 接收FIFO溢出中断 (ICR.RX0)
3.2.7.1 Receive Timer Interrupt:
接收计时器中断用于通知大多数数据包接收事件(在本节稍后描述的某些情况下也使用小封包检测中断)。为了最大程度减少每个工作所完成的中断次数,以太网控制器提供了两个定时器来控制中断生成频率。
3.2.7.1.1 Receive Interrupt Delay Timer / Packet Timer (RDTR)
数据包定时器(Packet Timer)在短时间内接收到许多数据包时最小化生成的中断数量。一旦接收到一个数据包并转移到主机存储器(特别是,在最后一个数据包字节被写入存储器后),数据包定时器就会启动,然后在每次接收到新数据包并传输到主机存储器时重新初始化(以RDTR定义的值为准)。当数据包定时器计时结束(例如,在RDTR定义的时间内没有新数据包被接收和传输到主机存储器时),将生成接收计时器中断。
将数据包定时器设置为0b会禁用数据包定时器和绝对定时器(下文介绍),并导致每当新数据包被存储在存储器中时都会生成接收计时器中断。
使用设置RDTR高位(FPD)来强制消耗描述符的显式回写(可能是部分缓存行数量的描述符)可以引起Packet Timer立即超时并生成接收计时器中断。
当绝对定时器超时或小封包检测中断引起接收计时器中断时,将重新初始化(但不启动)数据包定时器。
有关更多关于数据包定时器的详细信息,请参见第13.4.30节。
3.2.7.1.2 Receive Interrupt Absolute Delay Timer (RADV)
绝对定时器确保在第一个数据包接收后的某个预定义时间间隔内生成接收中断。绝对定时器一旦接收到一个数据包并传输到主机存储器(特别是,在最后一个数据包字节被写入存储器后)就会开始计时,但不会在每次接收到新数据包时重新初始化/重新启动。当绝对定时器超时(在RADV中定义的时间内没有接收中断被生成)时将生成接收计时器中断。
将RADV设置为0b或RDTR设置为0b将禁用绝对定时器。要仅禁用数据包定时器,应将RDTR设置为RADV + 1b。
当因数据包定时器超时或小封包检测中断引起接收计时器中断时,绝对定时器会被重新初始化(但不会启动)。
下面的图示说明了如何同时使用数据包定时器和绝对定时器:
Packet Timer可以限制在短时间内接收到大量数据包时所产生的中断数量,而Absolute Timer则定期生成接收中断,即使没有接收到新数据包也是如此。使用这两个定时器,以太网控制器可以在不影响网络数据传输性能的情况下,及时向处理器通知有新的数据包需要处理。
3.2.7.2 Small Receive Packet Detect
当启用小封包检测(RSRPD设置为非零值)并且传输到主机存储器的数据包大小≤ RSRPD.SIZE时,将触发小封包检测中断(ICR.SRPD)。在比较大小时,头部和CRC也会被计算在内(如果未启用CRC剥离则包括CRC)。如果已经剥离,则不会包括CRC和VLAN头。当小封包检测中断发生时,也会注意到接收计时器中断原因(ICR.RXT0)。
对于82541xx和82547GI / EI,接收小数据包不会清除绝对定时器或数据包延迟定时器,因此一个数据包可能会产生两个中断,一个由于小数据包接收,另一个由于定时器超时。
小封包检测触发中断是为了通知主机处理器有新的小数据包需要及时处理,以确保网络数据传输的实时性和可靠性。在一些应用场景下,如视频流、语音通话等,可能需要快速地传输大量小封包的数据,如果不及时处理这些小数据包就会造成网络延迟或数据丢失等问题。因此,网卡通过小封包检测触发中断来提示主机处理器及时处理小数据包,以保证网络数据传输的效率和稳定性。
定时器触发中断适用于在一定时间间隔内(通过绝对定时器或数据包定时器的设置)没有接收到新的数据包时,通知主机处理器停止等待并开始处理已接收的数据包。它可以帮助处理器及时处理已经接收的数据包,避免因等待而导致的性能损失和消息延迟。
与小封包检测触发中断相比,定时器触发中断不是根据数据包的大小来判断是否需要中断处理器的。它是基于设定的时间间隔来判断是否需要中断,而不受数据包大小的限制。二者都是为了提高网络数据传输的效率和稳定性,但应用场景不同:小封包检测适用于需要快速传输大量小数据包的场景,而定时器触发中断适用于需要控制处理器等待时间并确保已接收到数据包被及时处理的场景。
3.2.7.3 Receive Descriptor Minimum Threshold (ICR.RXDMT)
最小可用描述符数量阈值通过在空闲描述符数量等于RCTL.RDMTS(接收描述符环的大小的一部分)中定义的最小值时生成中断,帮助避免描述符不足的情况发生。这有助于确保接收描述符环中始终存在足够的可用描述符来存储接收到的网络数据包,并防止描述符不足的问题。
3.2.7.4 Receiver FIFO Overrun
FIFO溢出指当硬件试图将字节写入一个已满的FIFO时,由于FIFO没有足够的空间,数据无法被正确写入。FIFO通常是用来存储和缓存数据的一种硬件结构,如果数据流的速度过快或者处理器没有及时处理数据,就会导致FIFO中数据积压,并可能触发FIFO溢出。
FIFO溢出通常可以通过增加FIFO的大小、优化缓冲区的管理和处理方式,或调整数据传输的速率来解决。避免FIFO溢出对于保证数据传输的稳定性和可靠性非常重要,因为溢出的数据可能会丢失或损坏,从而影响系统的正常运行。
3.2.8 82544GC/EI Receive Interrupts
当网卡接收到新的数据包时,可以通过以下方式进行指示:
- 绝对定时器(RDTR)
- 当网卡接收到第一个数据包后,会启动一个计时器(也称为绝对定时器),该计时器会在一段预先设定好的时间后触发。具体来说,在最后一个数据包字节被写入内存之后,经过了预定的一段时间后,计时器将会触发,并将所有已积累的描述符刷新到内存中。如果软件希望立即得到通知,可以将计时器的值设置为0b,这样每当有新的数据包被存储到内存中时都会通知软件。
- 此外,如果通过高位为1写入绝对定时器,则会强制刷新任何部分缓存行。硬件会将所有已使用的描述符写入内存,并更新全局可见的头指针值。这有助于避免缓存不一致的问题,并确保处理器能够及时看到所有已接收到的数据包。
此外,硬件还提供以下中断:
- 接收描述符最小阈值(ICR.RXDMT) 最小描述符门限帮助避免由于描述符不足而引起的问题,当空闲描述符的数量等于最小描述符门限时,会生成一个中断信号。它是以接收描述符环大小的一部分的形式来衡量的。
- 接收FIFO溢出(ICR.RXO) 当硬件试图向一个已满的FIFO写入字节时发生FIFO溢出。这可能表示软件未更新尾指针以提供足够的描述符/缓冲区,或者PCI总线排空接收FIFO的速度太慢。超出FIFO容量的传入数据包将被丢弃,并不会影响未来数据包的接收。
3.2.9 Receive Packet Checksum Offloading
以太网控制器支持三种接收校验和计算的卸载:数据包校验和、IP报头校验和和TCP/UDP校验和:
- 数据包校验和是指对整个数据包的所有字节进行校验和计算;
- IP报头校验和是指对IP报头的所有字段进行校验和计算;
- 而TCP/UDP校验和则是针对传输层的TCP或UDP协议头和负载数据进行校验和计算。
需要注意的是,IPv6数据包没有IP校验和字段,因此不需要进行IP校验和计算。
Packet checksum是接收数据包中从RXCSUM.PCSS指定的字节开始,进行去除后计算得到的校验和。
- 例如,对于作为802.3ac VLAN数据包封装的以太网II帧,并且RXCSUM.PCSS设置为十进制14,那么Packet Checksum将包括整个封装的帧,但不包括14字节的以太网头(DA、SA、Type/Length)和4字节的q-tag。
- 如果设置了RCTL.SECRC位,Packet checksum将不包括以太网CRC。
在比较Packet Checksum和数据包中存储的TCP校验和之前,软件必须进行所需的抵消计算(以排除不应包括的字节并包括伪头)。
对于支持的数据包/帧类型,可以将整个校验和计算卸载到以太网控制器中完成:
- 如果将RXCSUM.IPOFLD设置为1b,则控制器会计算IP校验和,并通过接收描述符中的ERROR字段中的IP Checksum Error位(RDESC.IPE)通知软件是否校验失败。
- 类似地,如果将RXCSUM.TUOFLD设置为1b,则以太网控制器会计算TCP或UDP校验和,并通过TCP/UDP Checksum Error位(RDESC.TCPE)通知软件是否校验失败。
- 如果RXCSUM.IPOFLD和RXCSUM.TUOFLD都没有设置,则所有数据包的Checksum Error位(IPE和TCPE)均为0b。
支持的帧类型包括:
- 以太网II帧
- 以太网SNAP帧
3.3 Packet Transmission
针对常规的非TCP分段数据包,传输过程涉及以下步骤:
- 协议栈从应用程序接收要传输的数据块。
- 协议栈基于介质的MTU大小和所需的数据包头计算传输该数据块所需的数据包数量。
- 对于数据块中的每个数据包:
- 协议栈准备以太网、IP和TCP/UDP头。
- 协议栈通过与设备驱动程序之间预定义的interface进行通信,并命令驱动程序发送单个数据包。
- 驱动程序获取帧,然后向网络适配器发送命令,指示其需要发送或接收数据包,并提供相关的数据包信息。适配器在执行这些命令时,会从主机内存中读取数据包(或将其写入主机内存),并使用适当的物理层协议将它们发送到网络或接收来自网络的数据包。
- 硬件通过DMA传输从主机内存读取数据包。
- 当硬件完成帧的DMA传输(由中断指示)时,驱动程序将数据包的所有权返回给网络操作系统(NOS)。
输出数据包由指针-长度对组成的描述符链组成(称为基于描述符的传输)。软件通过组装指针-长度对列表来形成传输数据包,将此信息存储在传输描述符中,然后更新芯片上的传输尾指针到该描述符。传输描述符和缓冲区存储在主机内存中。硬件通常只有在从主机内存完全获取了所有包数据并将其存入芯片上的传输FIFO之后才会传输该数据包。这允许进行TCP或UDP校验和计算,并避免出现PCI欠速问题。这种传输方式被称为“基于描述符的传输”,因为数据包的传输是由描述符链驱动的,而不是直接使用数据包本身。使用描述符链可以降低CPU的负载,并提高系统性能
3.3.1 Transmit Data Storage
数据存储在由描述符指向的缓冲区中。数据的对齐在任意字节边界上,每个描述符的最大大小仅受允许的最大数据包大小(16288字节)限制。一个数据包通常由两个或多个描述符组成,一个或多个用于头部,一个用于实际数据。一些软件实现会将头文件和数据包数据复制到一个缓冲区中,并仅使用一个描述符来传输每个数据包。
3.3.2 Transmit Descriptors
以太网控制器提供了三种类型的传输描述符格式。原始描述符称为“传统”描述符格式。另外两种描述符类型合称为扩展描述符。其中一种类似于传统描述符,它指向数据块。这种描述符类型称为TCP/IP数据描述符,并且是传统描述符的替代品,因为它提供了新的卸载功能。另一种描述符类型基本上不同,因为它不指向数据包数据。它只包含控制信息,这些信息加载到控制器的寄存器中,并影响未来数据包的处理。以下各节介绍了三个描述符格式。
通过将TDESC.DEXT位设置为1b,可以访问扩展描述符类型。如果设置了此位,则会检查TDESC.DTYP字段,以控制描述符的其余位的解释。表3-7显示了所有扩展描述符的通用布局。标记为NR的字段不保留任何特定功能,并根据每个描述符类型进行定义。请注意,DEXT和DTYP字段不连续,以适应传统模式操作。对于传统模式操作,第29位设置为0b,并在第3.3.3节中定义描述符。
3.3.3 Legacy Transmit Descriptor Format
要选择传统模式操作,应将位29(TDESC.DEXT)设置为0b。在这种情况下,描述符格式如表3-8所示。地址和长度必须由软件提供。命令字节中的位是可选的,校验和偏移(CSO)和校验和起始(CSS)字段也是可选的。
- Buffer Address:当前描述符指向的数据包在主存中的地址。具有空地址的描述符不传输数据。如果它们的命令字节中设置了RS位(TDESC.CMD),则在硬件处理它们时写入状态字(TDESC.STATUS)中的DD字段。
- Length :
- “长度”是指每个数据包段的长度。一个数据包可能由多个段组成,每个段有自己的长度。单个传统描述符允许的最大长度为16288字节。虽然允许一个缓冲区长度至少为1字节,但在添加填充和CRC之前,数据包的总长度必须至少为48个字节。
- 每个描述符默认指向的缓冲区可以高达16288字节,而各个描述符指向的缓冲区长度之和也可以达到最大允许的传输数据包长度。如果一个描述符的长度为零,则表示该描述符不传输任何数据。如果该描述符的命令字节中设置了RS位(TDESC.CMD),则在硬件处理它们时将写入状态字(TDESC.STATUS)中的DD字段。
- CSO :
- 在TDESC.CMD中设置Insert Checksum位(IC), 会启用硬件自动计算checksum的功能 --> 插入TCP校验和 , Checksum Offset字段指示checksum在数据包起始位置的哪个位置 , 硬件在未设置TDESC.CMD中的EOP时将忽略CSO 。
- CSO以字节为单位提供,并且必须在传输描述符中提供给以太网控制器的数据范围内(即CSO < length - 1)
其他字段含义如下所示:
3.3.3.1 Transmit Descriptor Command Field Format
TDESC.CMD字段各个位的含义:
- IDE(第7位):中断延迟使能位。
- 假设以太网控制器需要发送多个数据包,每个数据包都对应一个传输描述符,传输描述符的命令字段设置了IDE和RS位。当以太网控制器写回第一个数据包的传输描述符时,它会加载一个倒计时寄存器并从中断延迟寄存器读取相应的值。在倒计时器倒计时期间,以太网控制器可以继续处理其他的数据包,并且会继续加载传输中断计数器。只有当倒计时器倒计时到0时,以太网控制器才会触发一个传输中断来通知主机CPU。
- 通过启用IDE位,以太网控制器可以将传输中断的生成时间推迟到合适的时机,从而提高系统性能和效率。
- VLE(第6位):VLAN数据包使能位。
- DEXT(第5位):扩展位,在传统模式下为0b。
- RS(第3位):报告状态位。
- IC(第2位):插入校验和位。
- IFCS(第1位):插入FCS位。
- EOP(第0位):数据包结尾位。
3.3.3.2 Transmit Descriptor Status Field Format
STATUS字段存储适用的传输描述符状态,并具有表3-11中显示的字段。
传输描述符状态字段仅在命令字段中设置了RS(对于82544GC/EI还有RPS)时才存在。
-
TU RSV (bit 3)
- 传输欠速事件指的是某个数据包在传输过程中,由于发送缓冲区中数据不足而无法继续发送的情况。这通常会发生在启用“早期传输”(Early Transmits)功能时,并且所要发送的数据包大小超出了发送缓冲区可用空间的情况下。
- 如果发生了传输欠速事件,以太网控制器会将Transmit Underrun(传输欠速)标志位置为1,以表明发生了此类事件。但是,这并不意味着数据包已经失败,因为以太网控制器仍然会尝试重新传输该数据包。如果重新传输成功,则Transmit Underrun标志位会被清除。需要注意的是,这种情况应该少见,因为早期传输功能可以帮助避免传输欠速事件的发生。
- 对于除82544GC/EI以外的所有以太网控制器,Transmit Underrun标志位都是保留位,应该设置为0。
-
LC (bit 2)
- Late Collision(迟碰撞)是指在半双工模式下发生的迟碰撞事件。在全双工模式下,该标志位没有任何意义。需要注意的是,碰撞窗口大小取决于网络速度:在10/100 Mb/s上为64字节,在1000 Mb/s上为512字节。当发送数据包时,如果探测到了迟碰撞事件,则会设置Late Collision标志位。
-
EC (bit 1)
- Excess Collisions(过多碰撞)表示数据包发生了超过TCTL.CT字段定义的最大过多碰撞次数,因此未能被成功传输。该标志位在全双工模式下没有任何意义。
-
DD (bit 0)
- Descriptor Done(描述符完成)表示该描述符已经被处理完毕并写回。在处理完成的时候,RS位会被设置;而对于82544GC/EI控制器,在网络线上传输的数据包发送完成后,RPS位才会被设置。
- 当RS位被设置时,它表示数据包已经被成功发送或已经被处理完成。对于82544GC/EI控制器,需要等待数据包真正地发送到网络线上,才能保证数据包的传输完成,因此只有在RPS位被设置时,才能确认数据包已经成功传输。
3.4 Transmit Descriptor Ring Structure
当数据包在以太网控制器中进行传输时,使用了一个称为"transmit descriptor ring"的结构来管理传输队列。该结构如图3-4所示:
图3-4中的阴影框表示已经由硬件传输但尚未被软件回收的描述符。回收的过程涉及释放与描述符相关联的缓冲区。
以下是主要内容:
- "transmit descriptor ring"由一个循环缓冲区内存区域组成,用于存储描述符。
- 传输队列由一对硬件寄存器维护。
- 要向队列中添加新的描述符,软件需要将它们写入循环缓冲区内存区域,并更新环的尾指针。
- 尾指针指向硬件所拥有的最后一个描述符的下一个位置(但仍然在描述符环内部)。它不会超出描述符环。
- 当头指针和尾指针相等时,表示队列为空。传输会一直进行到头指针等于尾指针所指向的描述符位置,此时队列为空。
- 在软件的头指针超过描述符之前,不应对已传递给硬件的描述符进行操作。也就是说,软件在描述符被硬件处理之前,不应该对其进行修改或释放。
简而言之,"transmit descriptor ring"结构通过循环缓冲区管理传输队列,在软件向环中添加新的描述符时,更新尾指针。头指针则表示硬件已经处理的描述符位置。软件在头指针超过描述符之前,应避免对已传递给硬件的描述符进行操作。
传输描述符由下列寄存器进行描述:
- 传输描述符基地址寄存器(TDBAL和TDBAH)
- 这些寄存器指示描述符环缓冲区的起始位置。这个64位地址在16字节边界对齐,并且存储在连续的两个32位寄存器中。TDBAL包含低32位,TDBAH包含高32位。硬件忽略TDBAL中的低4位。
- 传输描述符长度寄存器(TDLEN)
- 这个寄存器确定为循环缓冲区分配的字节数。该值必须是128字节对齐的。
- 传输描述符头寄存器(TDH)
- 这个寄存器保存的值是相对于基地址的偏移量,表示正在进行处理的描述符。循环缓冲区中最多可以有64K个描述符。读取此寄存器会返回已加载到输出FIFO中的描述符的“head”值。
- 传输描述符尾寄存器(TDT)
- 这个寄存器保存的值是相对于基地址的偏移量,表示硬件可以处理的最后一个描述符的位置。这是软件写入第一个新描述符的位置。
基地址寄存器指示循环描述符队列的起始位置,而长度寄存器则指示描述符环的最大大小。长度寄存器的最低7位硬连线为0b。描述符缓冲区内的字节地址计算如下:
地址 = 基地址 + (指针 * 16),其中指针是硬件头或尾寄存器中的值。
头寄存器和尾寄存器的选择大小允许最多64K个描述符,或者大约16K个数据包用于传输队列,假设每个数据包平均有四个描述符。
一旦激活,硬件会获取由硬件头寄存器指示的描述符。硬件尾寄存器指向最后一个有效描述符的下一个位置。
软件可以通过在传输描述符的命令字段中设置RS位(或仅适用于82544GC/EI的RPS位)来确定是否已发送数据包。通过检查内存中的传输描述符DD位,可以消除潜在的竞争条件。所有描述符数据在递增头寄存器之前都被写入IO总线,但在进行IO写缓冲的系统中,对头寄存器的读取可能会“跳过”数据写入。传输描述符的更新使用相同的IO写路径,并遵循所有数据写入。因此,它们不受竞争条件的影响。还有其他潜在条件也禁止了软件读取头指针。
一般情况下,硬件在传输之前会预取数据包数据。硬件通常在将数据存储到传输FIFO之后更新头指针的值。
检查已完成的数据包的过程包括以下操作之一:
- 在内存中扫描描述符状态的回写。
- 触发中断。当传输队列为空时,可以生成中断条件(ICR.TXQE)。中断也可以通过其他方式触发。
3.4.1 Transmit Descriptor Fetching
传输描述符的处理策略与接收描述符的处理策略基本相同,只是使用了不同的阈值。对于传输描述符,芯片上的描述符缓冲区空间为64个描述符。
当芯片上的缓冲区为空时,只要有任何描述符可用(软件写入尾指针),就会立即进行获取操作。当芯片上的缓冲区接近为空(TXDCTL.PTHRESH),只要在主机内存中有足够的有效描述符(TXDCTL.HTHRESH),并且没有更高优先级的其他DMA活动(描述符获取和回写或数据传输),就会执行预取操作。
描述符的预取策略是积极的,以最大化性能。如果描述符驻留在外部缓存中,系统必须在更改尾指针之前确保缓存一致性。
当主机内存中的描述符数量大于可用的芯片上描述符存储时,芯片可以选择执行非缓存行大小的获取操作。硬件会执行这种非对齐的获取操作,前提是这样做会导致下一个描述符获取操作在缓存行边界对齐。这样可以使得描述符获取机制在落后于软件时效率最高。
总之,传输描述符的处理策略与接收描述符类似,但使用了不同的阈值。在芯片上的缓冲区为空时立即获取描述符,接近为空时进行预取操作。预取策略积极,以最大化性能。在主机内存中存在更多描述符时,可以执行非对齐的获取操作,以提高效率。
3.4.2 Transmit Descriptor Write-back
传输描述符的写回策略有几种情况。首先,通常会将阈值设置为默认值,以便立即写回传输描述符并保持向后兼容性,因为延迟并批量写回的好处非常有限。
传输描述符的写回条件有三种情况:
- 如果设置了RS1标志的描述符准备写回,并且TXDCTL.WTHRESH设置为0b,那么将立即进行写回。
- 当传输中断延迟计时器(TIDV)到期时,会强制及时写回描述符。在计时器初始化后的第一个数据包开始计时,计时器到期后会清空已积累的描述符并触发一个中断事件(TXDW)。
- 如果已经积累了TXDCTL.WTHRESH个待写回的描述符,并且描述符批量写回功能已启用,那么会进行写回操作。
简而言之,传输描述符的写回根据不同的条件进行。大多数情况下会立即写回,以确保及时处理。只有在特定的条件下,如启用了描述符批量写回功能,才会进行延迟和批量写回。这样的策略可以提高性能并保持向后兼容性。
3.4.3 Transmit Interrupts
硬件提供了三个传输中断,这些中断是通过以下条件触发的:
- 传输队列为空(TXQE)- 所有描述符都已处理完毕。头指针等于尾指针。
- 描述符完成(传输描述符写回,TXDW)- 当硬件写回一个设置了RS1标志的描述符时触发。这通常在流接口用尽描述符并希望在取得进展时被中断的情况下使用。
- 传输延迟中断(TXDW)- 与中断延迟使能(IDE)一起使用,根据TIDV寄存器中的特定时间延迟TXDW指示。当传输中断倒计时寄存器到期时,设置此中断。倒计时寄存器加载TIDV寄存器的IDV字段的值,当写回设置了RS1位和IDE位的传输描述符时。当传输延迟中断发生时,设置TXDW中断标志位(与传输描述符写回中断发生时相同)。此中断可以像TXDW中断一样被屏蔽。这个中断经常被执行动态传输链接的软件使用,通过逐个将数据包添加到传输链中。
- 链路状态变化(LSC)- 当链路状态发生变化时设置。在使用内部PHY时,链路状态的变化由PHY通过其LINK指示的变化来确定和指示。在使用外部TBI设备(仅适用于82544GC/EI)时,设备可能使用其LOS(失去同步)指示来指示链路状态的变化。在此TBI模式下,如果启用了硬件自动协商(Auto-Negotiation),MAC还可以检测和信号链路状态的变化,如果收到了配置基本页寄存器(0b),或者如果LRST或ANE位被软件更改。
- 传输描述符环低阈值命中(TXD_LOW)- 当可用的传输描述符总数(通过Tx描述符环头指针和尾指针之间的差异来衡量)达到TXDCTL.LWTHRESH字段中指定的低阈值时设置。该中断不适用于82544GC/EI。
这些中断用于在特定的情况下通知软件发生的事件,以便软件能够相应地处理。例如,传输队列为空时,表示所有数据已经发送完毕;描述符完成中断用于在描述符用尽时通知软件;传输延迟中断用于延迟指示传输的进度;链路状态变化中断用于指示链路连接状态的变化;传输描述符环低阈值命中中断用于在传输描述符可用数量达到低阈值时发出警告。这些中断允许软件根据需要及时做出响应,并进行适当的处理。
传输延迟中断使用与传输写回中断(TXDW)相同的中断位进行指示。传输延迟中断仅在时间上进行了延迟,如上述所讨论的。这意味着传输延迟中断使用了相同的中断标志位,但在时间上进行了延迟。
3.4.3.1 Delayed Transmit Interrupts
这种机制允许软件在一定时间内延迟传输中断的触发,直到不再向传输链中添加描述符,而不是在以太网控制器的头指针追赶上尾指针时触发。这种情况通常发生在以太网控制器处理数据包的速度略快于软件的情况下,这在千兆操作中是可能的情况。
软件驱动程序通常不知道何时会被要求发送另一个帧。出于性能原因,最好在发送了一批数据包之后仅生成一个传输中断。
小结
由于手册涉及内容较多,所以本节到此为止,下节我们来解析lab具体代码实现