RTL8139的linux源代码分析
前言 RTL8139 可能是目前最受欢迎的网络卡,它的价格便宜,功能上也还能接受。虽然在效能 上有时会略不及Intel 的 eepro100,但因为价格实在太便宜了,所以芯片上的一点小问题 通常也接忽略不计。 废话少话,马上来说明 8139too 这个驱动程序。8139 虽然价格不高,但该有的功能一点 也不缺。它内建了符合 MII 规格的 tranceiver,可以自动判断连接的网络是那一种型态
·
前言
RTL8139 可能是目前最受欢迎的网络卡,它的价格便宜,功能上也还能接受。虽然在效能
上有时会略不及Intel 的 eepro100,但因为价格实在太便宜了,所以芯片上的一点小问题
通常也接忽略不计。
废话少话,马上来说明 8139too 这个驱动程序。8139 虽然价格不高,但该有的功能一点
也不缺。它内建了符合 MII 规格的 tranceiver,可以自动判断连接的网络是那一种型态
。它也可以使用 DMA 直接使用位于主记忆体的缓区来存网络上接收的封包,同样的,待传
送的封包也可利用 DMA 传送到网络卡上。所以虽然在 8139 芯片上只有 2K 的接收缓冲区
和 2K 的传送缓冲区,其效能仍十分不错。
除了 realtek 本身外,有不少的厂商也使用相同的内核生产了和 8139 相容的网络芯片,
包括了
SMC 1211
MPX 5030
DELTA 8139
ADDTRON 8139
DFE 538
可能还有更多。
驱动程序初始化
就像其它的驱动程序一样,驱动程序在使用 insmod 载入时,第一个初呼叫的函数是 ini
t_module,在使用 rmmod 移除时,cleanuo_module 会被呼叫。在 init_module 中,我们
注册了一个 PCI 驱动程序
static struct pci_driver rtl8139_pci_driver = {
name: MODNAME,
id_table: rtl8139_pci_tbl,
probe: rtl8139_init_one,
remove: rtl8139_remove_one,
suspend: rtl8139_suspend,
resume: rtl8139_resume,
};
static int __init rtl8139_init_module (void)
{
return pci_module_init (&rtl8139_pci_driver);
}
这个结构和上次介绍的 sis900 其实差别不大。rtl8139_init_one 用来初始化一个 8139
芯片。PCI 驱动程序最大的好处是 PCI BUS 提供了组态空间 (configuration space) 来
存放驱动程序所需的 IO 位址及中断号码等资料,我们不必再像 ISA 驱动程序一样需要指
定这些资源。
rtl8139_init_one 会呼叫 rtl8139_init_board 来初始化芯片,基本上 8139 这个芯片算
是一个很容易使用的芯片,基本的 PCI 初始化后就可以直接使用了。所以 rtl8139_init
_one 和 rtl8139_init_board 其实多半是在做一些错误检查的动作,并由 PCI 表格中所
得稍后会用的到的资源。
我从 rtl8139_init_board 中取出一些比较重要的片断加以说明,其它的部份请自行参考
源代码。
......
// 由 PCI 子系统中读出所需的资源
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
......
......
// 将这些资源保留下来
rc = pci_request_regions (pdev, "8139too");
pci_set_master (pdev);
......
// 将 IO 位址对映到记忆体
ioaddr = ioremap (mmio_start, mmio_len);
dev->base_addr = (long) ioaddr;
tp->mmio_addr = ioaddr;
......
// 重设芯片
RTL_W8 (ChipCmd, (RTL_R8 (ChipCmd) & ChipCmdClear) | CmdReset);
/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--) {
barrier();
udelay (10);
if ((RTL_R8 (ChipCmd) & CmdReset) == 0)
break;
}
// 判断芯片确的版本
......
在 rtl8139_init_one 中最重要的是下面这一段
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->set_multicast_list = rtl8139_set_rx_mode;
dev->do_ioctl = mii_ioctl;
dev->tx_timeout = rtl8139_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
dev->irq = pdev->irq;
基本上和上次介绍的函数基本上相同,在此不再重复。上面比较特别的可能只有 ioremap
这个函数,它的用途是将 mmio_start 开始 mmio_len 长度的 IO 映射到记忆体中,之后
我们就可以直接使用函数的传回值来做 IO 的动作了。
一般而言,mmio_start 的值是一个位于 CPU 定址空间中的实体位址,在一般的架构下,
硬件的设计者会保留一块记忆体位置给记忆体映射装置 (memory-mapped device) 使用。
这些装置允许 CPU 用记忆体调用的方式取用其上的暂存器,在有些不支援 IO 调用的架构
中,这些唯一取得装置暂存器的方法。
举个例说,如果你要调用第 100 号暂存器,你可以使用
unsigned int *ap = (unsigned int *) mmio_start + 0x100;
printf("register 0x100 = %x/n", *ap);
接下来我们一一解释这些函数。
开始装置-- rtl8139_open
这个函数会在你使用 ifconfig 时初呼叫,在这个函数中,你必须做下列的事
注册中断函数 rtl8139_interrupt
分配并初始化 8139 所需的接收与传送缓冲区。
产生一个 kernel thread 负责查看网络连线的状态
比较特别的是第三个动作,
rtl8139_start_xmit
这个函数会在传送一个封包时初呼叫,
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
{
entry = tp->cur_tx % NUM_TX_DESC;
8139 支援四个传送缓冲区,你必须挑出下一个要用的缓冲区。接下来,你必须把缓冲区记
忆体的实体表址 (physical address) 设定到 8139 的暂存器中。
tp->tx_info[entry].skb = skb;
if ((long) skb->data & 3) { /* Must use alignment buffer. */
/* tp->tx_info[entry].mapping = 0; */
memcpy (tp->tx_buf[entry], skb->data, skb->len);
RTL_W32 (TxAddr0 + (entry * 4),
tp->tx_bufs_dma + (tp->tx_buf[entry] - tp->tx_bufs));
} else {
tp->tx_info[entry].mapping =
pci_map_single (tp->pci_dev, skb->data, skb->len,
PCI_DMA_TODEVICE);
RTL_W32 (TxAddr0 + (entry * 4), tp->tx_info[entry].mapping);
}
上面的程序码小心的处理的 alignment 的问题, 8139 要求缓冲区的表址必须对齐 32 位
元。也就是说位址必须能被 4 除尽。如果不行的话,我们必须另外安排一个表址对齐 32
位元的缓冲区,把资料拷贝到那里去,然后将这个新缓冲区的实体表址存放到暂存器中去
。
RTL_W32 (TxStatus0 + (entry * sizeof (u32)),
tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));
这一段程序用来设定封包的长度,一个正确的 ethernet 封包必须至少有 64 位元组长。
不幸的,8139 不管这件事,你设定多长它就送多少。上面这一行程序就在确定封包的长度
至少有 ETH_ZLEN。
dev->trans_start = jiffies;
spin_lock_irq (&tp->lock);
tp->cur_tx++;
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);
spin_unlock_irq (&tp->lock);
return 0;
}
这以前解释过到,当缓冲区用完时必须通知上层不要再送封包下来了。
rtl8139_set_rx_mode
这个函数用来设定接收的模式,8139 提供了 64 组 MAC 位址 filter。只有符合这些 fi
lter 的位址芯片才会用中断通知 CPU 前来处理,一般状态下,我们只接收和 8139 本身
MAC 相符的封包。只有在像 tcpdump 之类的程序中才会想要接收其它的封包。
rtl8139_interrupt
在中断函数中,我们必须将状态码读入,然后根据状态码的指示做不同的事。我们要处理
的状况有
发生错误,可能是接收缓冲区满了,传送发生错误,bus 发生错误,接收发生错误。根据
不同的状况,必须做不同的处理。如果传送错误,则再送一次。如果接收错误,那可能只
好等待上层协定发现并重送封包。如果是 PVCI BUS 错误,则可能要重置 BUS。
接收到封包,呼叫 rtl8139_rx_interrupt
传送完一个封包,呼叫 rtl8139_tx_interrupt
当接收到一个封包时,我们必须通知上层协定前检处理
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
skb->dev = dev;
skb_reserve (skb, 2); /* 16 byte align the IP fields. */
eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
skb_put (skb, pkt_size);
skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
dev->last_rx = jiffies;
tp->stats.rx_bytes += pkt_size;
tp->stats.rx_packets++;
} else {
这段程序是非常典型的接收封包程序,先使用 dev_alloc_skb 分配一段足够大小的缓冲区
。skb_put会调整缓冲区的大小,关鉴在使用 netif_rx 通知上层协定有新的封包传入。在
稍后会由 BH_NET 这个 bottom half 处理这个封包。
当一个封包传送完成后,我们必须将缓冲区释放。这件工作在 rtl8139_tx_interrupt 中
被完成,此时我们必须呼叫上层协定表示可以传送新的封包了。这件事由下列在 rtl8139
_tx_interrupt 最后面的程序完成
if (tp->dirty_tx != dirty_tx) {
tp->dirty_tx = dirty_tx;
if (netif_queue_stopped (dev))
netif_wake_queue (dev);
}
我们小心的避免呼叫太多次 netif_wake_queue,只有在装置己经因为缓冲区满了且有新的
封包要传送时才去呼叫 netif_wake_queue。
结语
其实我还有很多细节还没有解释,如 MII 的处理,错误的处理和 eeprom 的处理,这些就
留给大家自行研究了。如果有人有兴趣将这些细节补上,我很乐意将它们加入文章之中。
RTL8139 可能是目前最受欢迎的网络卡,它的价格便宜,功能上也还能接受。虽然在效能
上有时会略不及Intel 的 eepro100,但因为价格实在太便宜了,所以芯片上的一点小问题
通常也接忽略不计。
废话少话,马上来说明 8139too 这个驱动程序。8139 虽然价格不高,但该有的功能一点
也不缺。它内建了符合 MII 规格的 tranceiver,可以自动判断连接的网络是那一种型态
。它也可以使用 DMA 直接使用位于主记忆体的缓区来存网络上接收的封包,同样的,待传
送的封包也可利用 DMA 传送到网络卡上。所以虽然在 8139 芯片上只有 2K 的接收缓冲区
和 2K 的传送缓冲区,其效能仍十分不错。
除了 realtek 本身外,有不少的厂商也使用相同的内核生产了和 8139 相容的网络芯片,
包括了
SMC 1211
MPX 5030
DELTA 8139
ADDTRON 8139
DFE 538
可能还有更多。
驱动程序初始化
就像其它的驱动程序一样,驱动程序在使用 insmod 载入时,第一个初呼叫的函数是 ini
t_module,在使用 rmmod 移除时,cleanuo_module 会被呼叫。在 init_module 中,我们
注册了一个 PCI 驱动程序
static struct pci_driver rtl8139_pci_driver = {
name: MODNAME,
id_table: rtl8139_pci_tbl,
probe: rtl8139_init_one,
remove: rtl8139_remove_one,
suspend: rtl8139_suspend,
resume: rtl8139_resume,
};
static int __init rtl8139_init_module (void)
{
return pci_module_init (&rtl8139_pci_driver);
}
这个结构和上次介绍的 sis900 其实差别不大。rtl8139_init_one 用来初始化一个 8139
芯片。PCI 驱动程序最大的好处是 PCI BUS 提供了组态空间 (configuration space) 来
存放驱动程序所需的 IO 位址及中断号码等资料,我们不必再像 ISA 驱动程序一样需要指
定这些资源。
rtl8139_init_one 会呼叫 rtl8139_init_board 来初始化芯片,基本上 8139 这个芯片算
是一个很容易使用的芯片,基本的 PCI 初始化后就可以直接使用了。所以 rtl8139_init
_one 和 rtl8139_init_board 其实多半是在做一些错误检查的动作,并由 PCI 表格中所
得稍后会用的到的资源。
我从 rtl8139_init_board 中取出一些比较重要的片断加以说明,其它的部份请自行参考
源代码。
......
// 由 PCI 子系统中读出所需的资源
mmio_start = pci_resource_start (pdev, 1);
mmio_end = pci_resource_end (pdev, 1);
mmio_flags = pci_resource_flags (pdev, 1);
mmio_len = pci_resource_len (pdev, 1);
......
......
// 将这些资源保留下来
rc = pci_request_regions (pdev, "8139too");
pci_set_master (pdev);
......
// 将 IO 位址对映到记忆体
ioaddr = ioremap (mmio_start, mmio_len);
dev->base_addr = (long) ioaddr;
tp->mmio_addr = ioaddr;
......
// 重设芯片
RTL_W8 (ChipCmd, (RTL_R8 (ChipCmd) & ChipCmdClear) | CmdReset);
/* Check that the chip has finished the reset. */
for (i = 1000; i > 0; i--) {
barrier();
udelay (10);
if ((RTL_R8 (ChipCmd) & CmdReset) == 0)
break;
}
// 判断芯片确的版本
......
在 rtl8139_init_one 中最重要的是下面这一段
dev->open = rtl8139_open;
dev->hard_start_xmit = rtl8139_start_xmit;
dev->stop = rtl8139_close;
dev->get_stats = rtl8139_get_stats;
dev->set_multicast_list = rtl8139_set_rx_mode;
dev->do_ioctl = mii_ioctl;
dev->tx_timeout = rtl8139_tx_timeout;
dev->watchdog_timeo = TX_TIMEOUT;
dev->irq = pdev->irq;
基本上和上次介绍的函数基本上相同,在此不再重复。上面比较特别的可能只有 ioremap
这个函数,它的用途是将 mmio_start 开始 mmio_len 长度的 IO 映射到记忆体中,之后
我们就可以直接使用函数的传回值来做 IO 的动作了。
一般而言,mmio_start 的值是一个位于 CPU 定址空间中的实体位址,在一般的架构下,
硬件的设计者会保留一块记忆体位置给记忆体映射装置 (memory-mapped device) 使用。
这些装置允许 CPU 用记忆体调用的方式取用其上的暂存器,在有些不支援 IO 调用的架构
中,这些唯一取得装置暂存器的方法。
举个例说,如果你要调用第 100 号暂存器,你可以使用
unsigned int *ap = (unsigned int *) mmio_start + 0x100;
printf("register 0x100 = %x/n", *ap);
接下来我们一一解释这些函数。
开始装置-- rtl8139_open
这个函数会在你使用 ifconfig 时初呼叫,在这个函数中,你必须做下列的事
注册中断函数 rtl8139_interrupt
分配并初始化 8139 所需的接收与传送缓冲区。
产生一个 kernel thread 负责查看网络连线的状态
比较特别的是第三个动作,
rtl8139_start_xmit
这个函数会在传送一个封包时初呼叫,
static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
{
entry = tp->cur_tx % NUM_TX_DESC;
8139 支援四个传送缓冲区,你必须挑出下一个要用的缓冲区。接下来,你必须把缓冲区记
忆体的实体表址 (physical address) 设定到 8139 的暂存器中。
tp->tx_info[entry].skb = skb;
if ((long) skb->data & 3) { /* Must use alignment buffer. */
/* tp->tx_info[entry].mapping = 0; */
memcpy (tp->tx_buf[entry], skb->data, skb->len);
RTL_W32 (TxAddr0 + (entry * 4),
tp->tx_bufs_dma + (tp->tx_buf[entry] - tp->tx_bufs));
} else {
tp->tx_info[entry].mapping =
pci_map_single (tp->pci_dev, skb->data, skb->len,
PCI_DMA_TODEVICE);
RTL_W32 (TxAddr0 + (entry * 4), tp->tx_info[entry].mapping);
}
上面的程序码小心的处理的 alignment 的问题, 8139 要求缓冲区的表址必须对齐 32 位
元。也就是说位址必须能被 4 除尽。如果不行的话,我们必须另外安排一个表址对齐 32
位元的缓冲区,把资料拷贝到那里去,然后将这个新缓冲区的实体表址存放到暂存器中去
。
RTL_W32 (TxStatus0 + (entry * sizeof (u32)),
tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));
这一段程序用来设定封包的长度,一个正确的 ethernet 封包必须至少有 64 位元组长。
不幸的,8139 不管这件事,你设定多长它就送多少。上面这一行程序就在确定封包的长度
至少有 ETH_ZLEN。
dev->trans_start = jiffies;
spin_lock_irq (&tp->lock);
tp->cur_tx++;
if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
netif_stop_queue (dev);
spin_unlock_irq (&tp->lock);
return 0;
}
这以前解释过到,当缓冲区用完时必须通知上层不要再送封包下来了。
rtl8139_set_rx_mode
这个函数用来设定接收的模式,8139 提供了 64 组 MAC 位址 filter。只有符合这些 fi
lter 的位址芯片才会用中断通知 CPU 前来处理,一般状态下,我们只接收和 8139 本身
MAC 相符的封包。只有在像 tcpdump 之类的程序中才会想要接收其它的封包。
rtl8139_interrupt
在中断函数中,我们必须将状态码读入,然后根据状态码的指示做不同的事。我们要处理
的状况有
发生错误,可能是接收缓冲区满了,传送发生错误,bus 发生错误,接收发生错误。根据
不同的状况,必须做不同的处理。如果传送错误,则再送一次。如果接收错误,那可能只
好等待上层协定发现并重送封包。如果是 PVCI BUS 错误,则可能要重置 BUS。
接收到封包,呼叫 rtl8139_rx_interrupt
传送完一个封包,呼叫 rtl8139_tx_interrupt
当接收到一个封包时,我们必须通知上层协定前检处理
skb = dev_alloc_skb (pkt_size + 2);
if (skb) {
skb->dev = dev;
skb_reserve (skb, 2); /* 16 byte align the IP fields. */
eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
skb_put (skb, pkt_size);
skb->protocol = eth_type_trans (skb, dev);
netif_rx (skb);
dev->last_rx = jiffies;
tp->stats.rx_bytes += pkt_size;
tp->stats.rx_packets++;
} else {
这段程序是非常典型的接收封包程序,先使用 dev_alloc_skb 分配一段足够大小的缓冲区
。skb_put会调整缓冲区的大小,关鉴在使用 netif_rx 通知上层协定有新的封包传入。在
稍后会由 BH_NET 这个 bottom half 处理这个封包。
当一个封包传送完成后,我们必须将缓冲区释放。这件工作在 rtl8139_tx_interrupt 中
被完成,此时我们必须呼叫上层协定表示可以传送新的封包了。这件事由下列在 rtl8139
_tx_interrupt 最后面的程序完成
if (tp->dirty_tx != dirty_tx) {
tp->dirty_tx = dirty_tx;
if (netif_queue_stopped (dev))
netif_wake_queue (dev);
}
我们小心的避免呼叫太多次 netif_wake_queue,只有在装置己经因为缓冲区满了且有新的
封包要传送时才去呼叫 netif_wake_queue。
结语
其实我还有很多细节还没有解释,如 MII 的处理,错误的处理和 eeprom 的处理,这些就
留给大家自行研究了。如果有人有兴趣将这些细节补上,我很乐意将它们加入文章之中。
更多推荐
已为社区贡献3条内容
所有评论(0)