图1 PCI子系统的体系结构
由于使用了更高的时钟频率,因此PCI总线能够获得比ISA总线更好的整体性能。PCI总线的时钟频率一般在25MHz到33MHz范围内,有些甚至能够达到66MHz或者133MHz,而在64位系统中则最高能达到266MHz。尽管目前PCI设备大多采用32位数据总线,但PCI规范中已经给出了64位的扩展实现,从而使PCI总线能够更好地实现平台无关性,现在PCI总线已经能够用于IA-32、Alpha、PowerPC、SPARC64和IA-64等体系结构中。 PCI总线具有三个非常显著的优点,使得它能够完成最终取代ISA总线这一历史使命:
- 在计算机和外设间传输数据时具有更好的性能;
- 能够尽量独立于具体的平台;
- 可以很方便地实现即插即用。
图2 PCI系统示意图
在此我只对PCI总线系统体系结构作了概括性介绍,如果读者想进一步了解,David A Rusling在The Linux Kernel(http://tldp.org/LDP/tlk/dd/pci.html)中对Linux的PCI子系统有比较详细的介绍。 二、Linux驱动程序框架 Linux将所有外部设备看成是一类特殊文件,称之为“设备文件”,如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是Linux内核与外部设备之间的接口。设备驱动程序向应用程序屏蔽了硬件在实现上的细节,使得应用程序可以像操作普通文件一样来操作外部设备。 1. 字符设备和块设备 Linux抽象了对硬件的处理,所有的硬件设备都可以像普通文件一样来看待:它们可以使用和操作文件相同的、标准的系统调用接口来完成打开、关闭、读写和I/O控制操作,而驱动程序的主要任务也就是要实现这些系统调用函数。Linux系统中的所有硬件设备都使用一个特殊的设备文件来表示,例如,系统中的第一个IDE硬盘使用/dev/hda表示。每个设备文件对应有两个设备号:一个是主设备号,标识该设备的种类,也标识了该设备所使用的驱动程序;另一个是次设备号,标识使用同一设备驱动程序的不同硬件设备。设备文件的主设备号必须与设备驱动程序在登录该设备时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。 在Linux操作系统下有两类主要的设备文件:一类是字符设备,另一类则是块设备。字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。一般说来,PCI卡通常都属于字符设备。 所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:
[root@gary root]# mknod /dev/lp0 c 6 0 |
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);}; |
- 驱动程序的注册与注销
- 设备的打开与释放
- 检查设备相关错误,如设备尚未准备好等。
- 如果是第一次打开,则初始化硬件设备。
- 识别次设备号,如果有必要则更新读写操作的当前位置指针f_ops。
- 分配和填写要放在file->private_data里的数据结构。
- 使用计数增1。
- 使用计数减1。
- 释放在file->private_data中分配的内存。
- 如果使用计算为0,则关闭设备。
- 设备的控制操作
- 设备的中断和轮询处理
- pci_driver
struct pci_driver { struct list_head node; char *name; 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); int (*save_state) (struct pci_dev *dev, u32 state); int (*suspend)(struct pci_dev *dev, u32 state); int (*resume) (struct pci_dev *dev); int (*enable_wake) (struct pci_dev *dev, u32 state, int enable);}; |
- pci_dev
struct pci_dev { struct list_head global_list; struct list_head bus_list; struct pci_bus *bus; struct pci_bus *subordinate; void *sysdata; struct proc_dir_entry *procent; unsigned int devfn; unsigned short vendor; unsigned short device; unsigned short subsystem_vendor; unsigned short subsystem_device; unsigned int class; u8 hdr_type; u8 rom_base_reg; struct pci_driver *driver; void *driver_data; u64 dma_mask; u32 current_state; unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE]; unsigned int irq; struct resource resource[DEVICE_COUNT_RESOURCE]; struct resource dma_resource[DEVICE_COUNT_DMA]; struct resource irq_resource[DEVICE_COUNT_IRQ]; char name[80]; char slot_name[8]; int active; int ro; unsigned short regs; int (*prepare)(struct pci_dev *dev); int (*activate)(struct pci_dev *dev); int (*deactivate)(struct pci_dev *dev);}; |
/* 指明该驱动程序适用于哪一些PCI设备 */static struct pci_device_id demo_pci_tbl [] __initdata = { {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO}, {0,}};/* 对特定PCI设备进行描述的数据结构 */struct demo_card { unsigned int magic; /* 使用链表保存所有同类的PCI设备 */ struct demo_card *next; /* ... */}/* 中断处理模块 */static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs){ /* ... */}/* 设备文件操作接口 */static struct file_operations demo_fops = { owner: THIS_MODULE, /* demo_fops所属的设备模块 */ read: demo_read, /* 读设备操作*/ write: demo_write, /* 写设备操作*/ ioctl: demo_ioctl, /* 控制设备操作*/ mmap: demo_mmap, /* 内存重映射操作*/ open: demo_open, /* 打开设备操作*/ release: demo_release /* 释放设备操作*/ /* ... */};/* 设备模块信息 */static struct pci_driver demo_pci_driver = { name: demo_MODULE_NAME, /* 设备模块名称 */ id_table: demo_pci_tbl, /* 能够驱动的设备列表 */ probe: demo_probe, /* 查找并初始化设备 */ remove: demo_remove /* 卸载设备模块 */ /* ... */};static int __init demo_init_module (void){ /* ... */}static void __exit demo_cleanup_module (void){ pci_unregister_driver(&demo_pci_driver);}/* 加载驱动程序模块入口 */module_init(demo_init_module);/* 卸载驱动程序模块入口 */module_exit(demo_cleanup_module); |
- 检查PCI总线是否被Linux内核支持;
- 检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。
- 读出配置头中的信息提供给驱动程序使用。
static int __init demo_init_module (void){ /* 检查系统是否支持PCI总线 */ if (!pci_present()) return -ENODEV; /* 注册硬件驱动程序 */ if (!pci_register_driver(&demo_pci_driver)) { pci_unregister_driver(&demo_pci_driver); return -ENODEV; } /* ... */ return 0;} |
static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id){ struct demo_card *card; /* 启动PCI设备 */ if (pci_enable_device(pci_dev)) return -EIO; /* 设备DMA标识 */ if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) { return -ENODEV; } /* 在内核空间中动态申请内存 */ if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) { printk(KERN_ERR "pci_demo: out of memory\n"); return -ENOMEM; } memset(card, 0, sizeof(*card)); /* 读取PCI配置信息 */ card->iobase = pci_resource_start (pci_dev, 1); card->pci_dev = pci_dev; card->pci_id = pci_id->device; card->irq = pci_dev->irq; card->next = devs; card->magic = DEMO_CARD_MAGIC; /* 设置成总线主DMA模式 */ pci_set_master(pci_dev); /* 申请I/O资源 */ request_region(card->iobase, 64, card_names[pci_id->driver_data]); return 0;} |
static int demo_open(struct inode *inode, struct file *file){ /* 申请中断,注册中断处理程序 */ request_irq(card->irq, &demo_interrupt, SA_SHIRQ, card_names[pci_id->driver_data], card)) { /* 检查读写模式 */ if(file->f_mode & FMODE_READ) { /* ... */ } if(file->f_mode & FMODE_WRITE) { /* ... */ } /* 申请对设备的控制权 */ down(&card->open_sem); while(card->open_mode & file->f_mode) { if (file->f_flags & O_NONBLOCK) { /* NONBLOCK模式,返回-EBUSY */ up(&card->open_sem); return -EBUSY; } else { /* 等待调度,获得控制权 */ card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE); up(&card->open_sem); /* 设备打开计数增1 */ MOD_INC_USE_COUNT; /* ... */ } }} |
static int demo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){ /* ... */ switch(cmd) { case DEMO_RDATA: /* 从I/O端口读取4字节的数据 */ val = inl(card->iobae + 0x10); /* 将读取的数据传输到用户空间 */ return 0; } /* ... */} |
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs){ struct demo_card *card = (struct demo_card *)dev_id; u32 status; spin_lock(&card->lock); /* 识别中断 */ status = inl(card->iobase + GLOB_STA); if(!(status & INT_MASK)) { spin_unlock(&card->lock); return; /* not for us */ } /* 告诉设备已经收到中断 */ outl(status & INT_MASK, card->iobase + GLOB_STA); spin_unlock(&card->lock); /* 其它进一步的处理,如更新DMA缓冲区指针等 */} |
static int demo_release(struct inode *inode, struct file *file){ /* ... */ /* 释放对设备的控制权 */ card->open_mode &= (FMODE_READ | FMODE_WRITE); /* 唤醒其它等待获取控制权的进程 */ wake_up(&card->open_wait); up(&card->open_sem); /* 释放中断 */ free_irq(card->irq, card); /* 设备打开计数增1 */ MOD_DEC_USE_COUNT; /* ... */ } |
static void __exit demo_cleanup_module (void){ pci_unregister_driver(&demo_pci_driver);} |
- David A Rusling在 The Linux Kernel中对Linux的PCI子系统进行了比较详细的介绍。
- Linux PCI-HOWTO是了解Linux下PCI设备的最好读物。
- 毛德操,胡希明,Linux内核源代码情景分析,杭州:浙江大学出版社,2001
- Alessandro Rubini,,Linux Device Drivers(2nd Edition) USA:O’Reilly,2001
- Tomshanley,DonAderson,PCI系统结构(第四版),北京:电子工业出版社,2000