針對相應設備定義描述該PCI設備的數據結構:
struct device_private { /*注冊字符驅動和發現PCI設備的時候使用*/ struct pci_dev *my_pdev;// struct cdev my_cdev;// dev_t my_dev; atomic_t created; /* 用於獲取PCI設備配置空間的基本信息 */ unsigned long mmio_addr; unsigned long regs_len; int irq;//中斷號 /*用於保存分配給PCI設備的內存空間的信息*/ dma_addr_t rx_dma_addrp; dma_addr_t tx_dma_addrp; /*基本的同步手段*/ spinlock_t lock_send; spinlock_t lock_rev; /*保存內存空間轉換后的地址信息*/ void __iomem *ioaddr; unsigned long virts_addr; int open_flag // 設備打開標記 ..... };
初始化設備模塊:
static struct pci_driver my_pci_driver = { name: DRV_NAME, // 驅動的名字,一般是一個宏定義 id_table: my_pci_tbl, //包含了相關物理PCI設備的基本信息,vendorID,deviceID等 probe: pci_probe, //用於發現PCI設備 remove: __devexit_p(pci_remove), //PCI設備的移除 };
// my_pci_tbl 其實是一個 struct pci_device 結構,該結構可以有很多項,每一項代表一個設備
// 該結構可以包含很多項,每一項表明使用該結構的驅動支持的設備
// 注意:需要以一個空的項結尾,也就是:{0,}
static struct pci_device_id my_pci_tbl[] __initdata = { { vendor_id, device_id, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { 0,} }; static int __init init_module(void) { int result; printk(KERN_INFO "my_pci_driver built on %s, %s\n",__DATE__,__TIME__); result = pci_register_driver(&my_pci_driver ); //注冊設備驅動 if(result) return result; return 0; }
卸載設備模塊:
static void __devexit my_pci_remove(struct pci_dev *pci_dev) { struct device_private *private; private= (struct device_private*)pci_get_drvdata(pci_dev); printk("FCswitch->irq = %d\n",private->irq); // register_w32 是封裝的宏,便於直接操作 // #define register_w32 (reg, val32) iowrite32 ((val32), device_private->ioaddr + (reg)) // 這里的作用是關中斷,硬件復位 register_w32(IntrMask,0x00000001); register_w32(Reg_reset,0x00000001); // 移除動態創建的設備號和設備 device_destroy(device_class, device->my_dev); class_destroy(device_class); cdev_del(&private->my_cdev); unregister_chrdev_region(priv->my_dev,1); //清理用於映射到用戶空間的內存頁面 for(private->virts_addr = (unsigned long)private->rx_buf_virts;private->virts_addr < (unsigned long)private->rx_buf_virts + BUF_SIZE;private->virts_addr += PAGE_SIZE) { ClearPageReserved(virt_to_page(FCswitch->virts_addr)); } ... // 釋放分配的內存空間 pci_free_consistent(private->my_pdev, BUF_SIZE, private->rx_buf_virts, private->rx_dma_addrp); ... free_irq(private->irq, private); iounmap(private->ioaddr); pci_release_regions(pci_dev); kfree(private); pci_set_drvdata(pci_dev,NULL); pci_disable_device(pci_dev); }
// 總之模塊卸載函數的職責就是釋放一切分配過的資源,根據自己代碼的需要進行具體的操作
PCI設備的探測(probe):
static int __devinit pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id) { unsigned long mmio_start; unsigned long mmio_end; unsigned long mmio_flags; unsigned long mmio_len; void __iomem *ioaddr1=NULL; struct device_private *private; int result; printk("probe function is running\n"); /* 啟動PCI設備 */ if(pci_enable_device(pci_dev)) { printk(KERN_ERR "%s:cannot enable device\n",pci_name(pci_dev)); return -ENODEV; } printk( "enable device\n"); /* 在內核空間中動態申請內存 */ if((private= kmalloc(sizeof(struct device_private), GFP_KERNEL)) == NULL) { printk(KERN_ERR "pci_demo: out of memory\n"); return -ENOMEM; } memset(private, 0, sizeof(*private)); private->my_pdev = pci_dev; mmio_start = pci_resource_start(pci_dev, 0); mmio_end = pci_resource_end(pci_dev, 0); mmio_flags = pci_resource_flags(pci_dev, 0); mmio_len = pci_resource_len(pci_dev, 0); printk("mmio_start is 0x%0x\n",(unsigned int)mmio_start); printk("mmio_len is 0x%0x\n",(unsigned int)mmio_len); if(!(mmio_flags & IORESOURCE_MEM)) { printk(KERN_ERR "cannot find proper PCI device base address, aborting.\n"); result = -ENODEV; goto err_out; } /* 對PCI區進行標記 ,標記該區域已經分配出去*/ result = pci_request_regions(pci_dev, DEVICE_NAME); if(result) goto err_out; /* 設置成總線主DMA模式 */ pci_set_master(pci_dev); /*ioremap 重映射一個物理地址范圍到處理器的虛擬地址空間, 使它對內核可用.*/ ioaddr1 = ioremap(mmio_start, mmio_len); if(ioaddr1 == NULL) { printk(KERN_ERR "%s:cannot remap mmio, aborting\n",pci_name(pci_dev)); result = -EIO; goto err_out; } printk("ioaddr1 = 0x%0x\n",(unsigned int)ioaddr1); private->ioaddr = ioaddr1; private->mmio_addr = mmio_start; private->regs_len = mmio_len; private->irq = pci_dev->irq; printk("irq is %d\n",pci_dev->irq); /* 初始化自旋鎖 */ spin_lock_init(&private->lock_send); spin_lock_init(&private->lock_rev); if(my_register_chrdev(private)) //注:這里的注冊字符設備,類似於前面的文章中介紹過的動態創建設備號和動態生成設備結點 { printk("chrdev register fail\n"); goto err_out; } //下面這兩個函數根據具體的硬件來處理,主要就是內存分配、對硬件進行初始化設置等 device_init_buf(xx_device);//這個函數主要進行內存分配,內存映射,獲取中斷 device_hw_start(xx_device);//這個函數主要是往寄存器中寫一些值,復位硬件,開中斷,打開DMA等 //把設備指針地址放入PCI設備中的設備指針中,便於后面調用pci_get_drvdata pci_set_drvdata(pci_dev, FCswitch); return 0; err_out: printk("error process\n"); resource_cleanup_dev(FCswitch); //如果出現任何問題,釋放已經分配了的資源 return result; }
// probe函數的作用就是啟動pci設備,讀取配置空間信息,進行相應的初始化
中斷處理:
//中斷處理,主要就是讀取中斷寄存器,然后調用中斷處理函數來處理中斷的下半部分,一般通過tasklet或者workqueue來實現
注意:由於使用request_irq 獲得的中斷是共享中斷,因此在中斷處理函數的上半部需要區分是不是該設備發出的中斷,這就需要讀取中斷狀態寄存器的值來判斷,如果不是該設備發起的中斷則 返回 IRQ_NONE
static irqreturn_t device_interrupt(int irq, void *dev_id) { ...
if( READ(IntMask) == 0x00000001)
{
return IRQ_NONE;
} WRITE(IntMask,0x00000001);
tasklet_schedule(&my_tasklet); // 需要先申明tasklet 並關聯處理函數 ... return IRQ_HANDLED; } // 聲明tasklet static void my_tasklet_process(unsigned long unused); DECLARE_TASKLET(my_tasklet, my_tasklet_process, (unsigned long)&private);//第三個參數為傳遞給my_tasklet_process 函數的參數
設備驅動的接口:
static struct file_operations device_fops = { owner: THIS_MODULE, open: device_open, //打開設備 ioctl: device_ioctl, //設備控制操作 mmap: device_mmap,//內存重映射操作 release: device_release,// 釋放設備 };
打開設備:
open 方法提供給驅動來做任何的初始化來准備后續的操作. open 方法的原型是:
int (*open)(struct inode *inode, struct file *filp);
inode 參數有我們需要的信息,以它的 i_cdev 成員的形式, 里面包含我們之前建立的cdev 結構. 唯一的問題是通常我們不想要 cdev 結構本身, 我們需要的是包含 cdev 結構的 device_private 結構.
static int device_open(struct inode *inode, struct file *filp) { struct device_private *private; private= container_of(inode->i_cdev, struct device_private, my_cdev); filp->private_data = private; private->open_flag++; try_module_get(THIS_MODULE); ... return 0; }
釋放設備:
release 方法的角色是 open 的反面,設備方法應當進行下面的任務:
• 釋放 open 分配在 filp->private_data 中的任何東西
• 在最后的 close 關閉設備
static int FCswitch_release(struct inode *inode,struct file *filp) { struct device_private *private= filp->private_data; private->open_flag--; module_put(THIS_MODULE); printk("pci device close success\n"); return 0; }
設備控制操作:
PCI設備驅動程序可以通過device_fops 結構中的函數device_ioctl( ),向應用程序提供對硬件進行控制的接口。例如,通過它可以從I/O寄存器里讀取一個數據,並傳送到用戶空間里。
static int device_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) { int retval = 0; struct device_private *FCswitch = filp->private_data; switch (cmd) { case DMA_EN://DMA使能 device_w32(Dma_wr_en, arg); break; ... default: retval = -EINVAL; } return retval; }
內存映射:
static int device_mmap(struct file *filp, struct vm_area_struct *vma) { int ret; struct device_private *private = filp->private_data; vma->vm_page_prot = PAGE_SHARED;//訪問權限 vma->vm_pgoff = virt_to_phys(FCswitch->rx_buf_virts) >> PAGE_SHIFT;//偏移(頁幀號) ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, (unsigned long)(vma->vm_end-vma->vm_start), vma->vm_page_prot); if(ret!=0) return -EAGAIN; return 0; }
對 remap_pfn_range()函數的說明:
remap_pfn_range()函數的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
該函數的功能是創建頁表。其中參數vma是內核根據用戶的請求自己填寫的,而參數addr表示內存映射開始處的虛擬地址,因此,該函數為addr~addr+size之間的虛擬地址構造頁表。
另外,pfn(Page Fram Number)是虛擬地址應該映射到的物理地址的頁面號,實際上就是物理地址右移PAGE_SHIFT位。如果PAGE_SHIFT為4kb,則 PAGE_SHIFT為12,因為PAGE_SHIFT等於1<<PAGE_SHIFT。最后一個參數prot是新頁所要求的保護屬性。
在驅動程序中,一般能使用remap_pfn_range()映射內存中的保留頁(如X86系統中的640KB~1MB區域)和設備I/O內存。因此,如 果想把kmalloc()申請的內存映射到用戶空間,則可以通過SetPageReserved把相應的內存設置為保留后就可以。