一、PCI总线概述
总线是一种传输信号的信道;总线是连接一个或多个导体的电气连线。总线由电气接口和编程接
口组成,我们重点关注编程接口。
PCI是Peripheral Component Interconnect(外围设备互联)的简称,是在桌面及更大型的计算
机上普遍使用的外设总线。PCI总线具有三个非常重要的显著特点:
1、在计算机和外设间传输数据时具有更好的性能
2、能够尽量独立于具体的平台
3、可以方便地实现即插即用
体系结构-1
从结构上看,PCI总线是一种不依附于某个具体处理器的局部总线,它是在CPU和原来的系统总线之间
插入的一级总线,具体由一个桥接电路实现这一层的管理,并实现上下之间的接口以协调数据传送。
体系结构-2
系统的各个部分通过PCI总线和PCI-PCI桥连接在一起。CPU和RAM通过PCI桥连接到PCI总线0(即PCI总
线),而具有PCI接口的显卡直接连接到主PCI总线上。PCI-PCI桥式一个特殊的PCI设备,它负责将
PCI总线0和PCI总线1连接在一起。图中连接到PCI1号总线上的是SCSI卡和以太网卡。为了兼容就的
ISA总线标准,PCI总线还可以通过PCI-ISA桥来连接ISA总线,从而支持以前的ISA设备,图中ISA上连
接着一个多功能I/O控制器,用于控制键盘、鼠标和软驱等。
1>PCI设备寻址
每个PCI设备由一个总线号、一个设备号、和一个功能号确定。PCI规范允许一个系统最多拥有
256条总线,没条总线最多带32个设备,但每个设备可以是最多9个功能的多功能板(如一个音频设备
带一个CD-ROM驱动器)。
/proc/iomem描述了系统中所有的设备I/O在内存地址空间上的映射。我们来看地址从1G开始的第一个
设备在/proc/iomem中是如何描述的:
40000000-400003ff : 0000 : 00 : 1f.1
这是一个PCI设备,40000000-400003ff是它所映射的内存空间地址,占据了内存地址空间1024bytes
的位置,而0000 : 00 : 1f.1则是这个PCI外设的地址,它以冒号和逗号分隔为4个部分,第一个16位
表示域,第二个8位表示一个总线号,第三个5位表示一个设备号,最后3位,表示功能号。
因为PCI规范允许单个系统拥有最多256条总线,所以总线编号是8位。每个总线上可支持32个设备,
所以设备号是5位,而每个设备上最多可有8种功能,所以功能号是3位。由此可以得出上述的PCI设备
的地址是0号总线上的31号设备上的1号功能。使用lspci命令可以查看系统中的PCI设备。
2>配置寄存器
每个PCI设备都有一组固定格式的寄存器,即配置寄存器,配置寄存器由Linux内核中的PCI初始
化代码与驱动程序共同使用。内核在启动时负责对配置寄存器进行初始化,包括设置中断号以及I/O
基址等。
4>配置空间
00H-01H Vendor ID 制造商标识
02H-03H Device ID 是被标识
04H-05H Command 命令寄存器
06H-07H Status状态寄存器
08H Revision ID 版本识别号寄存器
09H-0bH Class Code 分类代码寄存器
0cH Cache Line Size CACHE 行长度寄存器
0dH Latency Timer主设备延迟时间寄存器
0eH Header Type 头标类型寄存器
0fH Bulit-in-teset- Register 自测试寄存器
10H-13H Base Address Register 0 基地址寄存器0
14H-17H Base Address Register 1 基地址寄存器1
18H-1bH Base Address Register 2 基地址寄存器2
1cH-1eH Base Address Register 3 基地址寄存器3
20H-23H Base Address Register 4 基地址寄存器4
24H-27H Base Address Register 5 基地址寄存器5
28H-2bH Cardbus CIS Pointer 设备总线CIS指针寄存器
2cH-2dH Subsystem Vendor ID 子设备制造商标识
2eH-2fH Subsystem Device ID 子设备标识
30H-33H Expasion ROM Base Address 扩展ROM基地址
34H-3b-保留
3cH Interrupt Line 中断线寄存器
3dH Interrupt Pin 中断引脚寄存器
3eH MIn_Gnt最小授权寄存器
3fH Max_Lat 最大延迟寄存器
厂商标识(Vendor id)
用来标识PCI设备生产厂家的数值。Inter的厂商标识为0x8086,全球厂商标识由PCI Special Interest Group 来分配。
设备标识(Device id)
用来标识设备的数值。Digital 21141快速以太网设备的标识为0x0009
基地址寄存器(Base Address Registers)记录次设备使用的I/O与内存空间的位置
中断连线(Interrupt Line)记录次设备使用的中断号
中断引脚(Interrupt Pin)记录次PCI设备使用的引脚(A,B,C,D)
二、PCI驱动程序设计
在Linux内核中,PCI驱动使用struct pci_driver结构来描述
struct pci_driver
{
..........
const struct pci_device_id* id_table;
int(*probe)(struct pci_dev* dev, const struct pci_device_id* id);
void(*remove)(struct pci_dev* dev);
/*Device remove (NULL if not a hot-plug capable driver)*/
.........
}
1>注册驱动
注册PCI驱动,使用如下函数:
pci_register_driver(struct pci_driver* drv)
2>使能设备
在PCI驱动使用PCI设备的任何资源(I/O区或者中断)之前,驱动必须调用如下函数来使能设备:int pci_enable_device(struct pci_dev* dev)
3>获取基地址
一个PCI设备最多可以实现6个地址区域,大多数PCI设备在这些区域实现I/O寄存器。Linux提供
了一组函数来获取这些区间的基地址:
pci_resource_start(struct pci_dev* dev,int bar)
返回指定区域起始地址,这个区域通过参数bar指定,范围从0-5,表示6个PCI区域中的一个。
pci_resource_end(struct pci_dev* dev , int bar)返回指定区域的末地址。
三、PCI网卡驱动程序分析
1>概述
该分析报告针对GNIC-II的千兆以太网卡,源程序文件:drivers/net/hamachi.c,由于该分析报告旨在对介绍PCI驱动程序结构,所以程序中关于硬件操作的具体部分不作介绍。
-
/*2、初始化*/
-
static int __init hamachi_init(void)
-
{
-
if(pci_register_drivers(&hamachi_driver)>0)
-
return 0;
-
-
pci_unregister_driver(&hamachi_driver);
-
return -ENODEV;
-
}
-
/*在模块初始化时采用pci_register_driver注册pci驱动程序*/
-
-
static struct pci_driver_hamachi_driver = {
-
name: DRV_NAME,
-
id_table: hamachi_pci_tbl,
-
probe: hamachi_init_one,
-
remove: __devexit_p(hamachi_remove_one),
-
};
-
-
static struct pci_device_id hamachi_pci_tbl[] __initdata = {
-
{0x1318,0x0911,PCI_ANY_ID,PCI_ANYID,},
-
{0x1319,0x0901,PCI_ANY_ID,PCI_ANYID,},
-
}
-
-
/*该表记录的是该驱动能耐够支持的PCI设备,分别是厂商号,设备号,子厂商号,子设备号,其中子厂商号,子设备号为PCI_ANY_ID,表示支持各种子类型。该表到底有什么实际用处需要通过分析函数pci_register_driver得出结论*/
-
-
/*probe:hamachi_init_one(该函数的调用时机可通过PCI_register_driver得出)*/
-
-
static int __init hamachi_init_one(struct pci_dev* pdev,const struct pci_device_id* ent)
-
{
-
/*使能PCI设备*/
-
if(pci_enable_device(pdev))
-
{
-
ret = -EIO;
-
goto err_out;
-
}
-
-
/*获取基地址*/
-
ioaddr = pci_resource_stare(pdev,0);
-
-
-
/*申请将要使用的地址空间*/
-
-
i = pci_resource_regions(pdev,DRV_NAME);
-
if(i)
-
return i;
-
-
/*获取中断号*/
-
irq = pdev->irq;
-
-
/*ioremap 是完成物理地址映射为虚拟地址*/
-
ioaddr = (long)ioremap(ioaddr,0x400);
-
-
if(!ioaddr)
-
goto err_out_release;
-
-
-
dev = alloc_etherdev(sizeof(struct hamachi_private));
-
-
if(!dev)
-
goto err_out_iounmap;
-
-
-
SET_MODILE_OWNER(dev);
-
-
/*硬件相关操作,省略*/
-
-
/*The Hamachi_specific entries in the device structe.*/
-
dev->open = &hamachi_open;
-
dev->hard_start_xmit = &hamachi_start_xmit;
-
dev->stop = &hamachi_close;
-
dev->get_stat = &hamachi_get_stats;
-
dev->set_multicast_list = &set_rx_mode;
-
dev->do_ioctl = &netdev_ioctl;
-
dev->watchdog_timeo = TX_TLMEOUT;
-
-
if(mtu)
-
dev->mtu = mtu;
-
-
i = register_netdev(dev);
-
if(i){
-
ret = i;
-
goto err_out_unmap_rx;
-
}
-
}