RTL8169

来自osdev
跳到导航 跳到搜索

RTL8169(S)-32/64网络接口芯片组编程指南

RTL8169是Realtek的下一代高性能网卡。 这种特殊的芯片组被设计为以10/100/1000 Mbps的速度运行。

识别

RTL8169系列的基本接口在几个Realtek网卡中是通用的。 这适用于以下来自FreeBSD的re驱动程序的PCI供应商和设备id的非全面列表:

  • 10ec:8161
  • 10ec:8168
  • 10ec:8169
  • 1259:c107
  • 1737:1032
  • 16ec:0116

基本启动

获取MAC地址

首先,我们需要获取NIC的物理MAC地址。 通常,我们应该通过EEPROM访问MAC地址,但在这种情况下,我们将采取快捷方式,从MAC0-6寄存器获取地址。 MAC寄存器从偏移量0x00开始,应按8位段访问。

例如:

 char i;
 for (i = 0; i < 6; i++)
 {
    mac_address[i] = inportb(ioaddr + i); /*ioaddr是可从PCI设备配置空间获得的基址*/
 }

复位芯片

有必要重置RTL8169以将寄存器置于已知的默认状态,并开始为芯片加电。 我们需要将重置命令发送到偏移量0x37处的芯片命令寄存器。 复位位位于偏移量0x10,因此我们需要使用这些偏移量执行‘outportb’,以告知芯片开始复位过程。 一旦我们发送了该命令,芯片将需要一些时间来完成操作,并且因为它正在改变寄存器,所以我们应该停止对寄存器的所有进一步写入/读取,直到它完成。 一旦操作完成,命令寄存器中的复位位将自动清除,这是我们在继续设置芯片之前应该检查的。

例如:

 outportb(ioaddr + 0x37, 0x10); /*set the Reset bit (0x10) to the Command Register (0x37)*/
 while(inportb(ioaddr + 0x37) & 0x10)
   ;/*setting a timeout could be useful if the card is problematic*/

设置Rx描述符

这种新一代芯片引入的一个新特性是,它完全基于描述符。 芯片支持多达3个唯一描述符集,每个描述符集包含1024个描述符;1024个用于接收数据包,1024个用于正常传输,1024个用于高优先级传输数据包。 不需要使用每个集合的所有描述符,甚至不需要使用所有三个集合。 每个描述符的宽度为16字节,包含一个64位缓冲区地址(用于存储传输数据或告知接收过程将数据转储到何处)和命令uint32_t。

RX描述符用来告诉网卡在收到数据包后将数据包放在哪里。 因为这是异步操作,所以在启用数据包接收之前,我们必须设置描述符,以便NIC知道将东西放在哪里。 描述符的一个主要功能是告诉NIC哪些描述符归OS(主机)所有,哪些描述符归NIC所有。 如果描述符归NIC所有,则NIC可以读取它并将其用于它接收的下一个数据包,如果不是,它或者跳过它,或者发送RDU(Rx描述符不可用)中断并暂停所有进一步的Rx操作。 因为可以使用多个描述符,所以当NIC到达描述符的末尾并且需要循环回到描述符的开头时,还使用一个EOR (Rx描述符Ring的结尾) 位。 还有一个更大的问题是,所有这些描述符数组的开头必须在256字节的边界上对齐。

描述符格式

偏移 名称 描述
dword 0 Flags 31 bit=OWN (if set, card own this descriptor) 30 bit=EOR (last descriptor in list)
dword 1 VLAN Unused
dword 2 Pointer to buffer low 32 bits
dword 3 Pointer to buffer high 32 bits

代码示例

 struct Descriptor {
     uint32_t command;  /* command/status uint32_t */
     uint32_t vlan;     /* currently unused */
     uint32_t low_buf;  /* low 32-bits of physical buffer address */
     uint32_t high_buf; /* high 32-bits of physical buffer address */
 };
  
 struct Descriptor *Rx_Descriptors = (struct Descriptor *)0x100000; /* 1MB Base Address of Rx Descriptors */
  
 void setup_rx_descriptors()
 {
     /* rx_buffer_len is the size (in bytes) that is reserved for incoming packets */
     unsigned int OWN = 0x80000000, EOR = 0x40000000; /* Bit offsets */
     int i;
     for(i = 0; i < num_of_rx_descriptors; i++) /* num_of_rx_descriptors can be up to 1024 */
     {
         if(i == (num_of_rx_descriptors - 1)) /* Last descriptor? if so, set the EOR bit */
           Rx_Descriptors[i].command = (OWN | EOR | (rx_buffer_len & 0x3FFF));
         else
           Rx_Descriptors[i].command = (OWN | (rx_buffer_len & 0x3FFF));
         /* VLAN adjustments are not part of this guide at the moment - leave as zeros for normal operation */
         Rx_Descriptors[i].low_buf = (unsigned int)&packet_buffer_address; /* This is where the packet data will go */
         /* If you are programming for a 64-bit OS, put the high memory location in the 'high_buf' descriptor area */
     }
 }

配置RxConfig和TxConfig

(取消)锁定寄存器

为了使用某些关键寄存器,我们必须 “解锁” 它们以更改其状态,然后在完成操作后 “锁定” 它们,以防止在操作过程中意外更改这些寄存器。 9346CR寄存器位于偏移量0x50处,用作这些关键寄存器的钥匙(key)。 为了“解锁”它们,我们必须将卡设置为“配置寄存器写启用”模式。 这是通过设置两个EEM (操作模式) 位来完成的。 完成后,就可以完成网卡的设置。 配置完成且操作系统开始正常运行后,应将EEM位设置为低电平,以将卡设置为“正常”模式并锁定配置寄存器。

TxConfig

传输配置寄存器(Transmit Configuration register)位于偏移量0x40处,包含DMA阈值和环回(loopback)状态等配置数据。 我们只需要关注两个位,即MXDMA和IFG位。 MXDMA位用于设置发送突发的DMA传输阈值。 我通常将其设置为无限突发数据(一个完整数据包)。 如果您通过主机操作系统进行了适当的处理,则可以将其设置为更低。 IFG (帧间间隙 Inter-Frame Gap) 是每个分组操作之间经过的时间量。 这可以设置为标准(96/960/9600ns),或者您可以将时间(出于我不知道的原因)增加8ns。

例如:

 outportl(ioaddr + 0x40, 0x03000700); /* IFG: normal, MXDMA: unlimited */

RxConfig

接收配置寄存器(Receive Configuration register)的偏移量为0x44,包含DMA阈值、FIFO阈值和分组接收规则等事物的配置数据。 RXFTH位用于设置Rx Fifo阈值。 此阈值确定缓冲区中需要多少数据才能开始通知主机操作系统已准备好数据。 我通常将其设置为无限数据,因为这意味着一旦完整的数据包进入卡上的FIFO缓冲区,NIC就会告诉操作系统数据已准备就绪。 MXDMA基于对Tx MXDMA的相同解释。 我将MXDMA设置为无限制。 AB/AM/APM/AAP位用于设置数据包过滤器。 我通常将其设置为混杂模式,并将所有之前的位设置为允许卡接收和处理物理到达NIC的任何数据包。

例如:

 outportl(ioaddr + 0x44, 0x0000E70F) /* RXFTH: unlimited, MXDMA: unlimited, AAP: set (promisc. mode set) */

最大数据包大小

最大传输数据包大小

MTPS寄存器位于偏移量0xEC,用于设置可以从RTL8169发送的最大数据包大小。 我们在这里可以提供的最大值是0x3B。 该值足以覆盖“巨型(jumbo)”数据包,并且仍然允许稳定的FIFO操作(无欠载)。 如果需要,可以将此值设置得更低,但对于正常使用,最大值是可以接受的。

eg:

 outportb(ioaddr + 0xEC, 0x3B); /* Maximum tx packet size */

接收数据包最大大小

RMS寄存器位于偏移量0xDA处,用于设置可接收到NIC的最大数据包大小。 如果设置为零,网卡将不接受数据包(必须在正常操作之前设置!)。 可以设置的最大大小是0x3FFF (16k-1),但是如果接收到大于8k的数据包,则设置错误位,并且必须启动更长的过程,以确定该数据包实际上是否良好。 我将这个值保持为0x1FFF(8k-1),因为很少收到如此大的数据包,而且它还确保错误数据包实际上是错误数据包,而不是误报。

例如:

 outportw(ioaddr + 0xDA, 0x1FFF); /* Maximum rx packet size */

完全重置示例

这是一个简单的例子,说明了如何在没有自动协商(稍后解释)或(G)MII交互的情况下重置RTL8169。

例如:

 struct Descriptor
 {
     uint32_t command;  /* command/status uint32_t */
     uint32_t vlan;     /* currently unused */
     uint32_t low_buf;  /* low 32-bits of physical buffer address */
     uint32_t high_buf; /* high 32-bits of physical buffer address */
 };

 /** 
  * Note that this assumes 16*1024=16KB (4 pages) of physical memory at 1MB and 2MB is identity mapped to 
  * the same linear address range
  */
 struct Descriptor *Rx_Descriptors = (struct Descriptor *)0x100000; /* 1MB Base Address of Rx Descriptors */
 struct Descriptor *Tx_Descriptors = (struct Descriptor *)0x200000; /* 2MB Base Address of Tx Descriptors */
    
 unsigned long num_of_rx_descriptors = 1024, num_of_tx_descriptors = 1024;
  
 void setup_rx_descriptors()
 {
     /* rx_buffer_len is the size (in bytes) that is reserved for incoming packets */
     unsigned int OWN = 0x80000000, EOR = 0x40000000; /* bit offsets */
     int i;
     for(i = 0; i < num_of_rx_descriptors; i++) /* num_of_rx_descriptors can be up to 1024 */
     {
         if(i == (num_of_rx_descriptors - 1)) /* Last descriptor? if so, set the EOR bit */
           Rx_Descriptors[i].command = (OWN | EOR | (rx_buffer_len & 0x3FFF));
         else
           Rx_Descriptors[i].command = (OWN | (rx_buffer_len & 0x3FFF));

         /** packet_buffer_address is the *physical* address for the buffer */
         Rx_Descriptors[i].low_buf = (unsigned int)&packet_buffer_address;
         Rx_Descriptors[i].high_buf = 0;
         /* If you are programming for a 64-bit OS, put the high memory location in the 'high_buf' descriptor area */
     }
 }
   
 void reset()
 {
     unsigned int i;
     unsigned char mac_address[6];
     
     outportb(ioaddr + 0x37, 0x10); /* Send the Reset bit to the Command register */
     while(inportb(ioaddr + 0x37) & 0x10){} /* Wait for the chip to finish resetting */
     
     for (i = 0; i < 6; i++)
       mac_address[i] = inportb(ioaddr + i);
    
     setup_rx_descriptors();
     outportb(ioaddr + 0x50, 0xC0); /* Unlock config registers */
     outportl(ioaddr + 0x44, 0x0000E70F); /* RxConfig = RXFTH: unlimited, MXDMA: unlimited, AAP: set (promisc. mode set) */
     outportb(ioaddr + 0x37, 0x04); /* Enable Tx in the Command register, required before setting TxConfig */
     outportl(ioaddr + 0x40, 0x03000700); /* TxConfig = IFG: normal, MXDMA: unlimited */
     outportw(ioaddr + 0xDA, 0x1FFF); /* Max rx packet size */
     outportb(ioaddr + 0xEC, 0x3B); /* max tx packet size */
     
     /* offset 0x20 == Transmit Descriptor Start Address Register 
        offset 0xE4 == Receive Descriptor Start Address Register 
  
        Again, these are *physical* addresses. This code assumes physical==linear, this is
        typically not the case in real world kernels
     */
     outportl(ioaddr + 0x20, (unsigned long)&Tx_Descriptors[0]; /* Tell the NIC where the first Tx descriptor is. NOTE: If writing a 64-bit address, split it into two 32-bit writes.*/
     outportl(ioaddr + 0xE4, (unsigned long)&Rx_Descriptors[0]; /* Tell the NIC where the first Rx descriptor is. NOTE: If writing a 64-bit address, split it into two 32-bit writes.*/
     
     outportb(ioaddr + 0x37, 0x0C); /* Enable Rx/Tx in the Command register */
     outportb(ioaddr + 0x50, 0x00); /* Lock config registers */
 }

外部链接