1.关于PMON
2.关于RTL8169
RTL8169是Realtek公司生产的一款千兆以太网卡,其特征有[2]:
l 集成的10/100/1000M传输器
l 自适应(auto-negotiation)能力
l 支持PCI 2.3版本协议,可以工作在32位,33/66MHz下
l 支持全双工流控(IEEE 802.3x)
l 与IEEE 802.3, IEEE 802.3u, IEEE 802.3ab完全兼容
l 支持收/发缓冲(FIFO)
l 支持PCI消息触发中断(PCI Message Singaled Interrupt)
等。
3. RTL8169工作原理及代码分析
RTL8169支持一种新的基于描述符(descriptor)的收发缓冲管理策略,该策略可以有效减少对CPU资源的要求。RTL8169最多可以分别支持1024个连续的发送/接收描述符队列,即意味着可以有三个描述符队列,分别为:高优先级发送描述符队列,普通优先级发送描述符队列以及接收描述符队列。在我们的驱动程序中,考虑到实际情况,未实现高优先级发送描述符队列。每个描述符占用四个连续的双字(double work)大小内存。分别对发送和接收描述符中的主要内容介绍如下:
l 发送描述符:
u TX_BUFFER_ADDRESS_HIGH:32-bit大小。存放该描述符所指向的发送缓冲的32位高地址。
u TX_BUFFER_ADDRESS_LOW:32-bit大小。存放该描述符所指向的发送缓冲的32位低地址。
u OWN位:1-bit大小。当该位为1时,表示该描述符所指向的发送缓冲中有数据,网卡可以进行发送。当该位为0时,表示上层协议可以调用驱动程序向相应缓冲中写入数据;写完数据后驱动程序会将该位置成1。当网卡将相应缓冲中数据发送完毕后,会自动将该位清0。初始状态时该位为0。
u EOR位:1-bit大小。当该位为1时,表示该描述符是发送描述符队列中的最后一个。每个描述符队列中有且只有一个描述符中的EOR位被置1。
u FS位:1-bit大小。RTL8169支持对包的分片。FS位指示该描述符所对应缓冲区中的数据是否为某个包的第一个分片。是为1,否为0。
u LS位:1-bit大小。LS位指示该描述符所对应缓冲区中的数据是否为某个包的最后一个分片。是为1,否为0。
u Frame_Length:16-bit大小。描述该描述符所对应的发送缓冲中的数据帧的大小。
l 接收描述符:
u RX_BUFFER_ADDRESS_HIGH:32-bit大小。存放该描述符所指向的接收缓冲的32位高地址。
u RX_BUFFER_ADDRESS_LOW:32-bit大小。存放该描述符所指向的接收缓冲的32位低地址。
u OWN位:1-bit位大小。当该位为1时,表示该描述符所指向的接收缓冲归网卡所有,即网卡尚未向缓冲中填充数据。当该位为0时,表示该描述符所指向的接收缓冲中已经存有数据,可以由驱动程序传递到上层协议栈。初始时OWN位由驱动程序初始化为1;当RTL8169向缓冲中填充数据之后会自动将其置为0。
u EOR位:同发送描述符中EOR位。
u Buffer_Size:14-bit大小。指示该描述符对应的接收缓冲区的大小。该值应小于8KB。
u FS位:同发送描述符中FS位。
u LS位:同发送描述符中LS位。
u Frame_Length:14-bit大小。当OWN为0且LS为1时,该域表示收到的包的大小(包括CRC校验位)。
当对RTL8169进行初始化时,会在内存中分别建立普通优先级发送描述符队列和接收描述队列,并为每个描述符分配一段缓冲;描述符中的各标识位的初始化工作也将进行。在RTL8169的初始化过程中还有一项重要工作,即将每个描述符队列的起始(物理)地址写入到RTL8169的相应寄存器中。
当上层协议栈有数据要发送时,会调用驱动程序中的发送函数。该函数会在发送描述符队列中找到OWN位为0,即指向的缓冲区空闲的发送描述符,将数据填入其所指示的发送缓冲。然后会将OWN位置1,以及对描述符中的其他标识位进行适当设置。RTL8169会轮询发送描述符队列,把OWN位为1的描述符所指向的缓冲中的数据从内存中取走,并将OWN位置为0和对描述符中的其他相关标识位进行适当设置。
当RTL8169从网络上接收到数据后,会从接收描述符队列找出OWN位为1的描述符,随后将数据填入该描述符对指向的接收缓冲,并将OWN位置为0和对描述符中其他相关标识位进行适当设置。在完成上述一系列动作之后,RTL8169会触发一个中断,随后即由驱动程序中的接收函数将数据从接收缓冲中取出并上传到上层协议栈。接收函数还会对相关接收描述符中的标识位进行适当设置。我们在PMON中的RTL8169驱动程序中并未实现NAPI机制。
4.RTL8169驱动程序源代码简析
当PMON程序启动时,会对PCI设备进行轮询。RTL8169驱动的入口点为函数:static void r8169_attach(struct device * parent, struct device * self, void *aux){…};
该函数主要完成以下任务:
l 调用static int RTL8169_init_board(struct rtl8169_private *tp, struct pci_attach_args *pa) 函数。这个函数的主要工作是为RTL8169网卡分配内存空间等。
l 设置描述RTL8169的结构(struct rtl8169_private)中的各个函数指针,如工作模式的设定函数、状态检测的函数等。
l 设置MAC地址寄存器。当RTL8169附有一个EEPROM时,默认的是从EEPROM中读取MAC地址。此时写MAC地址,则不论是否已从EEPROM中读取过MAC地址,MAC地址都将以此时所设置的值为准。
l 调用static void rtl8169_hw_phy_config(struct rtl8169_private *tp) 函数,对物理寄存器(PHY Registers)进行设置。
l 调用void if_attach(ifp) 函数和void ether_ifattach(ifp) 函数将RTL8169插入到设备列表中。
l 调用static int rtl8169_open(struct rtl8169_private *tp) 函数,打开RTL8169。该函数主要任务是:
u 在内存中开辟接收/发送描述符队列并调用static int rtl8169_init_ring(struct rtl8169_private *tp) 函数对描述符队列进行初始化。
u 调用static void rtl8169_hw_start(struct rtl8169_private *tp) 函数,主要是对RTL8169中的寄存器进行设置。
l 调用pci_intr_map(pc, pa->pa_intrtag, pa->pa_intrpin, pa->pa_intrline, &ih) 和pci_intr_establish(pc, ih, IPL_NET, rtl8169_interrupt, tp, self->dv_xname) 进行中断映射及将RTL8169中断加入到中断表中。
下面分析发送和接收函数。
发送函数:static void rtl8169_start_xmit(struct ifnet *ifp):
l 该函数会道德判断是否还有空间的发送缓冲。否则返回,是则继续。
l 调用static int rtl8169_xmit_frags(struct rtl8169_private *tp, struct ifnet *ifp) 函数,该函数会将上层协议栈传来的数据包从队列中取出,拷贝到发送缓冲中,并释放原来的数据包所占用的内存空间。完成数据的复制后,该函数还会对描述符中的OWN等标志位进行设置。
l 完成上述工作后,即写RTL8169寄存器,通知其有包等发送。
接收函数:static int rtl8169_rx_interrupt(struct rtl8169_private *tp)。当有数据
被网卡写入到接收缓冲时,会触发一个中断,并调用此函数。其主要工作如下:
l 检测描述符中的OWN位,以判断是该描述符所指向的缓冲中是否有数据。否则返回,是则继续。
l 判断包是否分片。在我们的实现中,无论收发,均不支持包的分片。为保证正确性,我们所分配的收发缓冲区大小均大于1536个字节,即以太网上所能传输的最大的包的大小。在此处,是则出错返回,否则继续。
l 进行校验和检查。
l 调用static struct mbuf * getmbuf(struct rtl8169_private *tp) 函数,为接收缓冲的数据在内存中另外分配一块空间,随后从接收缓冲中将数据复制到此空间中。
l 调用void ether_input(ifp, eh, m) 函数,将数据传入到上层协议栈。
至此,PMON中的RTL8169驱动程序的大概柜架已经分析完毕了。