上 编写Linux网络设备驱动( 三 )


注:这个地址属于PCI总线域的地址,而不是CPU域的地址 。
使用配置的例子,如设备驱动的初始接口函数读取基地址寄存器(Base),确定设备接口的基地址,下面的设备初始化时你可以看到具体例子 。
6.初始化
现在我们回到驱动程序代码的开发上来 。刚才我们已经讨论了设备驱动模块初始化中的设备检测和启用的任务,还有网络设备的表征结构,接下来我们先看看逻辑设备的初始化任务 。
6.1
首先,作为一支特殊的网络设备,除了有标准的表征,8139有其特殊数据,这是由C结构 表征,由->priv指向 。的定义如下:
struct rtl8139_private{struct pci_dev *pci_dev;/* PCI device */void *mmio_addr;/* memory mapped I/O addr */unsigned long regs_len; /* length of I/O or MMI/O region */};
Table 4:

上  编写Linux网络设备驱动

文章插图
6.2
现在我们扩展 函数,添加逻辑设备的初始化的任务 。先看代码:
int init_module(void){struct pci_dev *pdev;unsigned long mmio_start, mmio_end, mmio_len, mmio_flags;void *ioaddr;struct rtl8139_private *tp;int i;pdev = probe_for_realtek8139( );if(!pdev)return 0;if(rtl8139_init(pdev, &rtl8139_dev)) {LOG_MSG("Could not initialize device\n");return 0;}tp = rtl8139_dev->priv; /* rtl8139 private information */
首先9函数检测和启用设备后返回一个PCI设备——pdev,然后用pdev初始化,转而初始化网络设备 。
我们下一个目标是得到(初始化)设备的基地址——的域 。这是设备寄存器的内存映射的起始地址 。本设备驱动程序只使用内存映射IO 。
/* get PCI memory mapped I/O space base address from BAR1 */mmio_start = pci_resource_start(pdev, 1);mmio_end = pci_resource_end(pdev, 1);mmio_len = pci_resource_len(pdev, 1);mmio_flags = pci_resource_flags(pdev, 1);/* make sure above region is MMI/O */if(!(mmio_flags & I/ORESOURCE_MEM)) {LOG_MSG("region not MMI/O region\n");goto cleanup1;}/* get PCI memory space */if(pci_request_regions(pdev, DRIVER)) {LOG_MSG("Could not get PCI region\n");goto cleanup1;}pci_set_master(pdev);
为了取得基地址,我们利用了内核PCI总线子系统提供的API:, , ,。注意这些API函数的第二个参数——BAR号1 。PCI规定PCI设备最多可以申请6个PCI总线地址区,这些空间区的基地址分别保存在6个BAR里 。在手册定义里,RTL只申请了两个区,第一个BAR(编号为0)是I/OAR,第二个 BAR(编号为1)是MEMAR 。由于本设备驱动程序只使用内存映射IO,故BAR选用1 。
现在,在使用这些地址之前,我们还有两件事要做 。
/* ioremap MMI/O region */ioaddr = ioremap(mmio_start, mmio_len);if(!ioaddr) {LOG_MSG("Could not ioremap\n");goto cleanup2;}rtl8139_dev->base_addr = (long)ioaddr;tp->mmio_addr = ioaddr;tp->regs_len = mmio_len;
这两个事就是,第一,为设备驱动保留这些地址(调用函数),以免被误用;第二,将这些物理地址重映射(remap);这在前面“内存映射的I/O”小节已经提到,驱动代码不能用直接使用物理地址 。重映射后的地址填入 的域后,我们可以读定设备的寄存器了 。
剩下的代码比较直观和易理解了 。
/* UPDATE NET_DEVICE */for(i = 0; i < 6; i++) {/* Hardware Address */rtl8139_dev->dev_addr[i] = readb(rtl8139_dev->base_addr+i);rtl8139_dev->broadcast[i] = 0xff;}rtl8139_dev->hard_header_len = 14;memcpy(rtl8139_dev->name, DRIVER, sizeof(DRIVER)); /* Device Name */rtl8139_dev->irq = pdev->irq;/* Interrupt Number */rtl8139_dev->open = rtl8139_open;rtl8139_dev->stop = rtl8139_stop;rtl8139_dev->hard_start_xmit = rtl8139_start_xmit;rtl8139_dev->get_stats = rtl8139_get_stats;/* register the device */if(register_netdev(rtl8139_dev)) {LOG_MSG("Could not register netdevice\n");goto cleanup0;}return 0;}
我们用了一个for循环来读取设备的硬件地址和广播地址(注意这回是直接用内核[设备操作API]的readb,而不是 PCI总线系统API),设备的硬件地址位于基地址的最前面 。另外值得注意的是几个网络设备的接口函数指针,如open, 等,它们指向还没有实现的函数 。为了编译驱动模块并进行测试,到此暂时为这些接口函数写一些Dummy测试代码 。