欢迎转载!
一.理论知识
1. PCI总线的特点:
(1)速度快,时钟频率提高到33M,而且还为进一步把时钟频率提高到66MHZ、总线带宽提高到64位留下了余地。(2)对于地址的分配和设置,系统软件自动设置,每块外设通过某种途径告诉系统该外设有几个存储区间和I/O地址区间,每个区间的大小以及本地地址。系统软件知道了总共有多少外设以及各种的存储空间后就会统一为外设分配物理地址。(3)对于总线的竞争,PCI总线上配备了一个仲裁器,遇到冲突,仲裁器会选择其中之一暂时成为当前的主设备,而其他只能等待。同时考虑到这样的效率问题,PCI总线为写提纲了缓冲,故写比读会快。(4)对于总线扩充问题,PCI总线引入HOST-PCI桥(北桥),PCI-PCI桥,PCI-ISA桥(南桥)。CPU与内存通过系统总线连接,北桥连接内存控制器与主PCI总线,南桥连接主PCI总线和ISA总线,PCI-PCI桥连接主PCI总和次层PCI总线。
2. PCI设备概述
每个PCI设备有许多地址配置的寄存器,初始化时要通过这些寄存器来配置该设备的总线地址,一旦完成配置以后,CPU就可以访问该设备的各项资源了。PCI标准规定每个设备的配置寄存器组最多可以有256个连续的字节空间,开头64个字节叫头部,分为0型(PCI设备)和1型(PCI桥)头部,头部开头16个字节是设备的类型、型号和厂商等。这些头部寄存器除了地址配置的作用,还能使CPU能够探测到相应设备的存在,这样就不需要用户告诉系统都有哪些设备了,而是改由CPU通过一个号称枚举的过程自动扫描探测所有挂接在PCI总线上的设备。
设备的配置寄存器组采用相同的地址,由所在总线的PCI桥在访问时附加上其他条件区分,对于I386处理器,有两个32位寄存器,0XCF8为地址寄存器,0XCFC为数据寄存器。地址寄存器写入的内容包括总线号,设备号,功能号。逻辑地址(XX:YY.Z),XX表示PCI总线号,最多256个总线。YY表示PCI设备号,最多32个设备。Z表示PCI设备功能号,最多8个功能。
3. 查询PCI总线和设备的命令
查看PCI总线和PCI设备组成的树状图 lspci –t
查看配置区的情况 lspci –x,注意PCI寄存器是小端格式
4. PCI总线架构
所有的根总线都链接在pci_root_buses链表中。Pci_bus ->device链表链接着该总线下的所有设备。而pci_bus->children链表链接着它的下层总线,对于pci_dev来说,pci_dev->bus指向它所属的pci_bus. Pci_dev->bus_list链接在它所属bus的device链表上。此外,所有pci设备都链接在pci_device链表中。
二.PCI驱动
1. PCI寻址空间
PCI设备包括杀个寻址空间:配置空间,I/O端口空间,内存空间。
1.1 PCI配置空间:
内核为驱动提供的函数:
pci_read_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value)
pci_write_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value)
配置空间的偏移定义在include/linux/pci_regs.h
1.2 PCI的I/O和内存空间:
从配置区相应寄存器得到I/O区域的基址:
pci_resource_start(struct pci_dev *dev, int bar) Bar值的范围为0-5。
从配置区相应寄存器得到I/O区域的内存区域长度:
pci_resource_length(struct pci_dev *dev, int bar) Bar值的范围为0-5。
从配置区相应寄存器得到I/O区域的内存的相关标志:
pci_resource_flags(struct pci_dev *dev, int bar) Bar值的范围为0-5。
申请I/O端口:
request_mem_region(io_base, length, name)
读写:
inb() inw() inl() outb() outw() outl()
2. PCI总线支持的设备
PCI驱动程序向PCI子系统注册其支持的厂家ID,设备ID和设备类编码。使用这个数据库,插入的卡通过配置空间被识别后,PCI子系统把插入的卡和对应的驱动程序绑定。
PCI设备列表
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};
注意:如果可以处理任何情况,可将相应的寄存器设置为PCI_ANY_ID。
3. PCI驱动其他API
获取驱动私有数据:pci_get_drvdata();
使能PCI设备:pci_enable_device()
总线主DMA模式设置:pci_set_master()
4. *******
三.PCI驱动模型
一个通过PCI总线与系统连接的设备的驱动主要包括两部分:第一PCI驱动,第二,设备本身的驱动,包括字符设备,网络设备,tty设备,音频设备等。PCI驱动的核心是pci_driver,在探测函数中完成资源的申请,并注册相应的字符设备,网络设备,tty设备,音频设备等。
static struct pci_device_id buttons_pci_tbl[] __initdata={
{PCI_ANY_ID,PCI_ANY_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
{0,}
}; //PCI设备支持项
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
//中断处理程序
}
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
}
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
}
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int pci_key__probe (struct pci_dev *pdev, const struct pci_device_id *ent)
{
int ret;
pci_enable_device(pdev); //使能PCI设备
pci_set_master(pdev);
ret = misc_register(&misc); //注册杂项设备
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static int pci_key__remove (struct pci_dev *pdev, const struct pci_device_id *ent)
{
pci_disable_device(pdev);
misc_deregister(&misc);
return 0;
}
static struct pci_driver pci_key_driver = {
.name = "pci_key",
.id_table =buttons_pci_tbl,
.probe = pci_key__probe,
.remove = pci_key__remove,
};
static int __init dev_init(void)
{
return pci_register_driver(&pci_key_driver);
}
static void __exit dev_exit(void)
{
pci_unregister_driver(&pci_key_driver);
}
module_init(dev_init);
module_exit(dev_exit);
四.PCI设备的枚举过程
基于Mini2440开发板,在此,我只分析linux2.6.32.2内核的PCI设备枚举过程
pci的代码分为两个部份.一个部份是与平台相关的部份.存放在linux2.6.32.2\arch\x86\pci\ ,另一个部份是平台无关的代码,存放在linux2.6.32.2\driver\pci\下面.
我们从与平台相关的部份.存放在linux2.6.32.2\arch\x86\pci\ Makefile看起,
obj-y := i386.o init.o
obj-$(CONFIG_PCI_BIOS) += pcbios.o
obj-$(CONFIG_PCI_MMCONFIG) += mmconfig_$(BITS).o direct.o mmconfig-shared.o
obj-$(CONFIG_PCI_DIRECT) += direct.o
obj-$(CONFIG_PCI_OLPC) += olpc.o
obj-y += fixup.o
obj-$(CONFIG_ACPI) += acpi.o
obj-y += legacy.o irq.o
obj-$(CONFIG_X86_VISWS) += visws.o
obj-$(CONFIG_X86_NUMAQ) += numaq_32.o
obj-y += common.o early.o
obj-y += amd_bus.o
由这个Malefile我们可以知道init.c是一定被编译的,那么我们就看看这个init.c,在这个文件里面只有pci_arch_init函数,那么我们就看看。
static __init int pci_arch_init(void)
{
#ifdef CONFIG_PCI_DIRECT //直接进行PCI设备的探测和枚举
int type = 0;
type = pci_direct_probe();
#endif
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init();
#ifdef CONFIG_PCI_OLPC
if (!pci_olpc_init())
return 0; /* skip additional checks if it's an XO */
#endif
#ifdef CONFIG_PCI_BIOS //通过BIOS进行PCI设备的探测和枚举
pci_pcbios_init();
#endif
#ifdef CONFIG_PCI_DIRECT //直接进行PCI设备的探测和枚举
pci_direct_init(type);
#endif
if (!raw_pci_ops && !raw_pci_ext_ops)
printk(KERN_ERR