日期:2014-05-16  浏览次数:20649 次

Linux网络驱动源码分析(二)

Linux网络驱动源码分析(二)
2011年04月17日
  作为中科大的学生真觉得耻辱,整个学校弥漫着一种"小校"思想,科大学生都活在自己的小圈子里,回忆着自己高考时的辉煌,幻想着自己的美好未来,甚至北大清华都不放在眼里,等到毕业的时候突然发现找个工作是这么的难,这也罢了,有时本来找的工作都挫的一屁,还自我感觉良好,认为比谁都好,殊不知很多学校都比自己强。哎,知识分子的思想猛于虎,害死人啊!
  
  
  
  
  
  
  
  
  
  上次讲到如何从pci核心驱动一步一步的进入了rtl8139网络驱动,并且调用的第一个函数是驱动的probe函数,即rtl8139_init_one,本文就从这里入手,简单的介绍rtl8139网络驱动的相关原理和源码分析。
  1 rtl8139_init_one
  上文讲到当实现了驱动和设备的匹配后,需要设备和驱动做一些相应的工作,如正常使用前的初始化操作等,rtl8139_init_one就实现了一些初始化操作,原则上probe函数应该尽可能的短,尽量避免执行耗时的操作。rtl8139_init_one仅仅实现了两个结构体struct net_device和struct rtl8139_private的初始化。前一篇文章中也提到了数据结构的抽象层次的问题,在网络子系统中,所有的网络设备都用net_device来表示,但是并不是所有的网络设备都有相同的属性,因此,对应不同的网络设备增加一个private数据结构来描述,这里就是struct rtl8139_private。
  rtl8139_init_one主要函数和功能分析
  (1)dev = rtl8139_init_board (pdev);
  view plain copy to clipboard print ?
  /* 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 ERR_PTR(-ENOMEM);  
  }  
  SET_NETDEV_DEV(dev, &pdev->dev);  
  tp = netdev_priv(dev);  
  tp->pci_dev = pdev;  
  /* enable device (incl. PCI PM wakeup and hotplug setup) */  
  rc = pci_enable_device (pdev);  
  if (rc)  
  goto err_out;  
  pio_start = pci_resource_start (pdev, 0);  
  pio_end = pci_resource_end (pdev, 0);  
  pio_flags = pci_resource_flags (pdev, 0);  
  pio_len = pci_resource_len (pdev, 0);  
  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, DRV_NAME);  
  a). dev = alloc_etherdev (sizeof (*tp)); --> 分配struct rtl8139_private数据结构,并进行预初始化,之所以称之为预初始化是因为只进行了某些固定数据成员的初始化。
  b). 调用pci核心驱动的接口函数:pci_enable_device (),pci_enable_device 也是一个内核开发出来的接口,代码在drivers/pci/pci.c中,笔者跟踪发现这个函数主要就是把PCI配置空间的Command域的0位和1 位置成了1,从而达到了开启设备的目的,因为rtl8139的官方datasheet中,说明了这两位的作用就是开启内存映射和I/O映射,如果不开的话,那我们以上讨论的把控制寄存器空间映射到内存空间的这一功能就被屏蔽了。
  pci_resource_[start|end|flags|len]:在硬件加电初始化时,BIOS固件统一检查了所有的PCI设备,并统一为他们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们的寄存器,这些地址被BIOS写进了各个设备的配置空间,因为这个活动是一个PCI的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控制寄存器空间里。当然只有BIOS可以访问配置空间。当操作系统初始化时,他为每个PCI设备分配了pci_dev结构,并且把BIOS获得的并写到了配置空间中的地址读出来写到了pci_dev中的resource字段中。这样以后我们在读这些地址就不需要在访问配置空间了,直接跟pci_dev要就可以了,我们这里的四个函数就是直接从pci_dev读出了相关数据,代码在include/linux/pci.h中。具体参见PCI配置空间相关的介绍。
  c). rc = pci_request_regions (pdev, DRV_NAME);通知内核该设备对应的IO端口和内存资源已经使用,其他的PCI设备不要再使用这个区域
  d). 获得当前pci设备对应的IO端口和IO内存的基址。
  2. rtl8139_open
  此函数在网络设备端口被打开时调用,例如执行命令ifconfig eth0 up,就会触发这个函数,此函数是真正的rtl8139网络设备的初始化函数。这个函数主要做了三件事。 
  ① 注册这个设备的中断处理函数。
  view plain copy to clipboard print ?
  retval = request_irq (dev->irq, rtl8139_interrupt, IRQF_SHARED, dev->name, dev);  
  当网卡发送数据完成或者接收到数据时,是用中断的形式来告知的,比如有数据从网线传来,中断也通知了我们,那么必须要有一个处理这个中断的函数来完成数据的接收。关于Linux的中断机制不是我们详细讲解的范畴,但是有个非常重要的资源我们必须注意,那就是中断号的分配,和内存地址映射一样,中断号也是BIOS在初始化阶段分配并写入设备的配置空间的,然后Linux在建立 pci_dev时从配置空间读出这个中断号然后写入pci_dev的irq成员中,所以我们注册中断程序需要中断号就是直接从pci_dev里取就可以了。 
  retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev); 
  if (retval) { 
  return retval; 
  } 
  我们注册的中断处理函数是rtl8139_interrupt,也就是说当网卡发生中断(如数据到达)时,中断控制器8259A把中断号发给CPU,CPU 根据这个中断号找到处理程序,这里就是rtl8139_interrupt,然后执行。rtl8139_interrupt