分类: LINUX
2016-08-03 10:27:31
rtl8139_init_one 用来初始化一个 8139 芯片。PCI 驱动程序最大的好处是 PCI BUS
提供了组态空间 (configuration space) 来存放驱动程序所需的 IO 位址及中断号码等资料,
我们不必再像 ISA 驱动程序一样需要指定这些资源。
rtl8139_init_one,它主要做了七件事:
① 建立 net_device 结构,让它在内核中代表这个网络设备。但是读者可能会问,pci_dev
也是代表着这个设备,那么两者有什么区别呢,正如我们上 面讨论的,网卡设备
既要遵循PCI 规范,也要担负起其作为网卡设备的职责,于是就分了两块,pci_dev
用来负责网卡的PCI 规范,而这里要说的 net_device 则是负责网卡的网络设备这个
职责。
② 开启这个设备(其实是开启了设备的寄存器映射到内存的功能), \
pci_enable_device 也是一个内核开发出来的接口,代码在drivers/pci/pci.c 中,笔者
跟踪发现这个函数主要就是把PCI 配 置空间的Command 域的0 位和1 位置成了1,
从而达到了开启设备的目的,因为rtl8139 的官方datasheet 中,说明了这两位的作
用就是开启 内存映射和I/O 映射,如果不开的话,那我们以上讨论的把控制寄存器
空间映射到内存空间的这一功能就被屏蔽了,这对我们是非常不利的,除此之外,
pci_enable_device 还做了些中断开启工作。
③ 获得各项资源
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);
也许疑问我们的寄存器被映射到内存中的什么地方是什么时候有谁决定的呢。是这
样的,在硬件加电初始化时,BIOS 固件同一检查了所有的PCI 设备,并 统一为他
们分配了一个和其他互不冲突的地址,让他们的驱动程序可以向这些地址映射他们
的寄存器,这些地址被BIOS 写进了各个设备的配置空间,因为这个活 动是一个
PCI 的标准的活动,所以自然写到各个设备的配置空间里而不是他们风格各异的控
制寄存器空间里。当然只有BIOS 可以访问配置空间。当操作系统初 始化时,他为
每个PCI 设备分配了pci_dev 结构,并且把BIOS 获得的并写到了配置空间中的地址
读出来写到了pci_dev 中的resource 字 段中。这样以后我们在读这些地址就不需
要在访问配置空间了,直接跟pci_dev 要就可以了,我们这里的四个函数就是直接
从pci_dev 读出了相关数 据,代码在include/linux/pci.h 中。定义如下:
pci_resource_start
pci_resource_end
pci_resource_flags
pci_resource_len
每个PCI 设备有0‐5 一共6 个地址空间,我们通常只使用前两个,这里我们把参数
1 传给了bar 就是使用内存映射的地址空间。
④ 把得到的地址进行映射
ioaddr = ioremap (mmio_start, mmio_len);
if (ioaddr == NULL) {
printk ("cannot remap MMIO, aborting\n");
rc = ‐EIO;
goto err_out_free_res;
} ioremap 是内核提供的用来映射外设寄存器到主存的函数,我们要映射的地址已经
从pci_dev 中读了出来(上一步),这样就水到渠成的成功映射了而 不会和其他地
址有冲突。映射完了有什么效果呢,我举个例子,比如某个网卡有100 个寄存器,
他们都是连在一块的,位置是固定的,加入每个寄存器占4 个字 节,那么一共400
个字节的空间被映射到内存成功后,ioaddr 就是这段地址的开头(注意ioaddr 是虚
拟地址,而mmio_start 是物理地址, 它是BIOS 得到的,肯定是物理地址,而保护
模式下CPU 不认物理地址,只认虚拟地址),ioaddr+0 就是第一个寄存器的地址,
ioaddr+4 就是 第二个寄存器地址(每个寄存器占4 个字节),以此类推,我们就能
够在内存中访问到所有的寄存器进而操控他们了。
⑤ 重启网卡设备
重启网卡设备是初始化网卡设备的一个重要部分,它的原理就是向寄存器中写入命
令就可以了(注意这里写寄存器,而不是配置空间,因为跟PCI 没有什么关系),代
码如下:
writeb ((readb(ioaddr+ChipCmd) & ChipCmdClear) | CmdReset,ioaddr+ChipCmd);
是 我们看到第二参数ioaddr+ChipCmd,ChipCmd 是一个位移,使地址刚好对应的
就是ChipCmd 哪个寄存器,读者可以查阅官方 datasheet 得到这个位移量,我们在
程序中定义的这个值为:ChipCmd = 0x37;与datasheet 是吻合的。我们把这个命令
寄存器中相应位(RESET)置1 就可以完成操作。
⑥ 获得 MAC 地址,并把它存储到net_device 中。
for(i = 0; i < 6; i++) { /* Hardware Address */
dev‐>dev_addr[i] = readb(ioaddr+i);
dev‐>broadcast[i] = 0xff;
}
我们可以看到读的地址是ioaddr+0 到ioaddr+5,读者查看官方datasheet 会发现
寄存器地址空间的开头6 个字节正好存的是这个网卡设备的MAC 地址,MAC 地址
是网络中标识网卡的物理地址,这个地址在今后的收发数据包时会用的上。
⑦ 向 net_device 中登记一些主要的函数
dev‐>open = rtl8139_open;
dev‐>hard_start_xmit = rtl8139_start_xmit;
dev‐>stop = rtl8139_close;
由于dev(net_device)代表着设备,把这些函数注册完后,rtl8139_open 就是用于
打开这个设备, rtl8139_start_xmit 就是当应用程序要通过这个设备往外面发数据
时被调用,具体的其实这个函数是在网络协议层中调用的,这就涉及到 Linux 网络
协议栈的内容,不再我们讨论之列,我们只是负责实现它。rtl8139_close 用来关掉
这个设备。
|
|
|
|