zoukankan      html  css  js  c++  java
  • Linux PCI 设备驱动基本框架(二)

    针对相应设备定义描述该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把相应的内存设置为保留后就可以。

  • 相关阅读:
    黄金眼游戏
    四则运算
    关于构建之法的一些问题
    WorkConter
    python 列表构造时的引用问题
    网站前端求职的经历记录
    弱类型语言、强类型语言?
    关于JAVA的Random类的冷知识(转自菜鸟V)
    节后大礼包!XSql 源码开放,插件源码开放,Sofire v1.6 源码开放(已上传)
    【转】ASP.NET 尖括号 百分号 井号 等号 的用法
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2571408.html
Copyright © 2011-2022 走看看