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把相应的内存设置为保留后就可以。

  • 相关阅读:
    Android自定义之仿360Root大师水纹效果
    Android之TextView的Span样式源码剖析
    Android之TextView的样式类Span的使用详解
    随着ScrollView的滑动,渐渐的执行动画View
    仿微信主界面导航栏图标字体颜色的变化
    android自定义之 5.0 风格progressBar
    Android性能优化之内存篇
    Android性能优化之运算篇
    How to install Zabbix5.0 LTS version with Yum on the CentOS 7.8 system?
    How to install Zabbix4.0 LTS version with Yum on the Oracle Linux 7.3 system?
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2571408.html
Copyright © 2011-2022 走看看