最近看了一下realtek公司的8139网卡驱动,现在想整理成文。由于没有进行平台下的调试打印跟踪,有理解错误的地方请直接指正,防止误导别人,罪过罪过!
8139网卡已很古老,但价格低,所以很多公司还在用这款网卡芯片,至少我目前的公司在用,呵呵。 由于没有看8139的datasheet,所以本文不分析如何配置寄存器使此芯片正确的工作起来,而是分析在此芯片工作起来后的工作。还是从代码开始吧:
I)初始化
由于8139网卡是典型的PCI设备,所以要遵循PCI设备来写驱动,还好linux下有现成的PCI设备驱动框架, 照此框架来舔砖即可,高楼大厦就是这么建成的,大楼设计师的工资很高,农民工的工资很低。。。
-
static int __init rtl8139_init_module (void)
-
{
-
/* when we're a module, we always print a version message,
-
* even if no 8139 board is found.
-
*/
-
#ifdef MODULE
-
printk (KERN_INFO RTL8139_DRIVER_NAME "\n");
-
#endif
-
-
return pci_register_driver(&rtl8139_pci_driver);
-
}
只要是写PCI设备的驱动就这么干: 搞一个static int __init name_init_module(void)函数,然后直接pci_register_driver(&name_pci_driver); 你这么干pci总线就能找到你,不然不搭理你。。。
接下来定义一个pci设备的数据结构,然后给此数据结构中的函数指针挂上钩子函数,先弄个空函数加一行打印信息,这样就可以编译跑起来了。通过后再往里面舔砖
-
static struct pci_driver rtl8139_pci_driver = {
-
.name = DRV_NAME,
-
.id_table = rtl8139_pci_tbl,
-
.probe = rtl8139_init_one,
-
.remove = __devexit_p(rtl8139_remove_one),
-
#ifdef CONFIG_PM
-
.suspend = rtl8139_suspend,
-
.resume = rtl8139_resume,
-
#endif /* CONFIG_PM */
-
};
所有的工作都是在rtl8139_init_one函数中完成的:
struct net_device *dev = NULL; //这个是必须的,只要是网卡就得有这个数据结构,里面的字段只要是网卡都得有的属性,public,OK
struct rtl8139_private *tp; //从private就看到这个东西只属于rtl8139的,private , OK
int i, addr_len, option;
void __iomem *ioaddr; //这个是设备上板载空间经过内存映射得到的虚拟地址,通过这个地址就可以读写设备,经映射后速度快
static int board_idx = -1; //这个是计数用的,有多少个设备用到了此驱动
u8 pci_rev;
-
pci_read_config_byte(pdev, PCI_REVISION_ID, &pci_rev);
-
-
if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&
-
pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pci_rev >= 0x20) {
-
dev_info(&pdev->dev,
-
"This (id %04x:%04x rev %02x) is an enhanced 8139C+ chip\n",
-
pdev->vendor, pdev->device, pci_rev);
-
dev_info(&pdev->dev,
-
"Use the \"8139cp\" driver for improved performance and stability.\n");
-
}
这段代码是从网卡中读取一些状态信息,然后打印出来(废话), 但pci_read_config_byte函数的实现可以看看,学学写代码的技巧,另外还设计到一个知识点就是,PCI配置空间的概念。
static int __devinit rtl8139_init_board (struct pci_dev *pdev, struct net_device **dev_out) 这个函数比较重要,为struct net_device 和 struct rtl8139_private 两个重要的数据结构开辟内存,并对里面的一些字段进行了初始化。另外还做的一项重要工作就是对设备板载空间进行了内存映射。
-
/* dev and priv zeroed in alloc_etherdev */
-
dev = alloc_etherdev (sizeof (*tp));
-
if (dev == NULL) {
-
dev_err(&pdev->dev, "Unable to alloc new net device\n");
-
return -ENOMEM;
-
}
-
SET_MODULE_OWNER(dev);
-
SET_NETDEV_DEV(dev, &pdev->dev);
-
-
tp = netdev_priv(dev);
-
tp->pci_dev = pdev;
这段代码就是完成了为struct net_device 和 struct rtl8139_private 两个重要的数据结构开辟内存的工作,写pci网卡驱动这么干就行,内核给我们提供了alloc_etherdev函数,不用白不用。
/* enable device (incl. PCI PM wakeup and hotplug setup) */
rc = pci_enable_device (pdev);
if (rc)
goto err_out;
enable设备,直接用就可以
-
rc = pci_request_regions (pdev, DRV_NAME);
-
if (rc)
-
goto err_out;
-
-
-
/* enable PCI bus-mastering */
-
pci_set_master (pdev);
-
-
-
/* ioremap MMIO region */
-
ioaddr = pci_iomap(pdev, 1, 0);
-
if (ioaddr == NULL) {
-
dev_err(&pdev->dev, "cannot remap MMIO, aborting\n");
-
rc = -EIO;
-
goto err_out;
-
}
-
dev->base_addr = (long) ioaddr;
-
tp->mmio_addr = ioaddr;
-
tp->regs_len = mmio_len;
上面的代码片段进行了内存映射,pci_request_regions函数Reserved PCI I/O and memory resources, 通知内核该设备对应的IO端口和内存资源已经使用,其他的PCI设备不要再使用这个区域,具体这个函数怎么做的不再探究。再调用pci_iomap函数就完成了内存映射,得到了虚拟地址ioaddr,以后就可以通过这个虚拟地址读写rtl8139网卡里的寄存器地址等资源,对网卡进行快速配置,使其能正常工作。
-
addr_len = read_eeprom (ioaddr, 0, 8) == 0x8129 ? 8 : 6;
-
for (i = 0; i < 3; i++)
-
((u16 *) (dev->dev_addr))[i] =le16_to_cpu (read_eeprom (ioaddr, i + 7, addr_len));
-
memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
这段代码就是利用刚得到的映射后的虚拟地址ioaddr从网卡板载memory空间中读出MAC地址.
接下来是open函数,这个函数是我们启动网卡设备的时候调用的,即:ifconfig eth0 up。
-
struct rtl8139_private *tp = netdev_priv(dev);
-
int retval;
-
void __iomem *ioaddr = tp->mmio_addr;
netdev_priv这个函数写成了内联函数,通过struct net_device得到struct rtl8139_private结构(通过public得到private),因为这个函数常用且能做成内联函数,所以写成了内联函数。
retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);
if (retval)
return retval;
上面三行代码完成了中断函数的注册,分配好中断号后,再写一个中断回调函数,调用这个中断注册函数就完成了中断的注册,以后再单独分析中断回调函数
-
tp->tx_bufs = pci_alloc_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
-
&tp->tx_bufs_dma);
-
tp->rx_ring = pci_alloc_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
-
&tp->rx_ring_dma);
-
if (tp->tx_bufs == NULL || tp->rx_ring == NULL) {
-
free_irq(dev->irq, dev);
-
-
if (tp->tx_bufs)
-
pci_free_consistent(tp->pci_dev, TX_BUF_TOT_LEN,
-
tp->tx_bufs, tp->tx_bufs_dma);
-
if (tp->rx_ring)
-
pci_free_consistent(tp->pci_dev, RX_BUF_TOT_LEN,
-
tp->rx_ring, tp->rx_ring_dma);
-
-
return -ENOMEM;
-
-
}
这段代码完成了开辟发送和接收缓冲区内存的操作,调用pci_alloc_consistent后得到了发送缓冲区tp->tx_bufs的首地址,这个地址是虚拟地址,进程能使用的地址,当要发送数据时,CPU使用这个地址把数据写到发送缓冲区中。另外得到了tp->tx_bufs_dma这个物理地址或者说是DMA地址,这个是网卡能使用的地址,当发送数据时,把数据送到发送缓冲区后就通过这个DMA地址进行DMA操作把数据发送出去。根据DMA_Mapping.txt文档这个函数pci_alloc_consistent建立的是一致性内存映射,也就是说CUP和rtl9139网卡同时对这个地址是可见的,所见即所得。
-
/* Initialize the Rx and Tx rings, along with various 'dev' bits. */
-
static void rtl8139_init_ring (struct net_device *dev)
-
{
-
struct rtl8139_private *tp = netdev_priv(dev);
-
int i;
-
-
tp->cur_rx = 0;
-
tp->cur_tx = 0;
-
tp->dirty_tx = 0;
-
-
for (i = 0; i < NUM_TX_DESC; i++)
-
tp->tx_buf[i] = &tp->tx_bufs[i * TX_BUF_SIZE];
-
}
完成了对缓冲区的初始化工作,显而易见在接下来的发送和接收过程中要对接收和发送缓冲区进行操作。
static void rtl8139_hw_start (struct net_device *dev) 函数是对网卡芯片进行了配置,其中
/* init Rx ring buffer DMA address */
RTL_W32_F (RxBuf, tp->rx_ring_dma);
/* init Tx buffer DMA addresses */
for (i = 0; i < NUM_TX_DESC; i++)
RTL_W32_F (TxAddr0 + (i * 4), tp->tx_bufs_dma + (tp->tx_buf[i] - tp->tx_bufs));
完成了把刚才所提到的内存映射后得到的DMA地址写入到网卡芯片相关的发送和接收寄存器中,当网卡接收到数据时,通过DMA操作,就把这些数据DMA到这个内存中,从而快速的完成了数据接收工作,同时发送一个接收中断,在中断回调函数中通过tp
->rx_ring读取这段内存的虚拟地址把数据送到上层协议栈中。
i = register_netdev (dev);
if (i) goto err_out;
注册刚才初始化的网卡设备
以上就是基本一个PCI网卡设备主要的初始化流程,主要的工作就是
a)对板载IO和memory空间进行内存映射,使之后能快速的对网卡芯片进行配置
b)配置芯片寄存器,使网卡芯片能正常工作(这部分本文没有进行分析,各个网卡datasheet不同,但大同小异)
c)分配接收和发送缓冲区,并进行内存映射(rtl8139进行的是一致性内存映射),把得到的物理地址写入接收和发送寄存器中。
阅读(5468) | 评论(0) | 转发(1) |