针对相应设备定义描述该PCI设备的数据结构:

structdevice_private

{/*注册字符驱动和发现PCI设备的时候使用*/

struct pci_dev *my_pdev;// struct cdev my_cdev;//dev_t my_dev;

atomic_t created;/*用于获取PCI设备配置空间的基本信息*/unsignedlongmmio_addr;

unsignedlongregs_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;

unsignedlongvirts_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)

{intresult;

printk(KERN_INFO"my_pci_driver built on %s, %s\n",__DATE__,__TIME__);

result= pci_register_driver(&my_pci_driver ); //注册设备驱动

if(result)returnresult;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)

{

unsignedlongmmio_start;

unsignedlongmmio_end;

unsignedlongmmio_flags;

unsignedlongmmio_len;void __iomem *ioaddr1=NULL;struct device_private *private;intresult;

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;gotoerr_out;

}/*对PCI区进行标记 ,标记该区域已经分配出去*/result=pci_request_regions(pci_dev, DEVICE_NAME);if(result)gotoerr_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;gotoerr_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");gotoerr_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);//如果出现任何问题,释放已经分配了的资源

returnresult;

}

// 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 并关联处理函数

...returnIRQ_HANDLED;

}//声明tasklet

static void my_tasklet_process(unsigned longunused);

DECLARE_TASKLET(my_tasklet, my_tasklet_process, (unsignedlong)&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, structdevice_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 longarg)

{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;

}returnretval;

}

内存映射:

static int device_mmap(struct file *filp, struct vm_area_struct *vma)

{intret;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<

在驱动程序中,一般能使用remap_pfn_range()映射内存中的保留页(如X86系统中的640KB~1MB区域)和设备I/O内存。因此,如 果想把kmalloc()申请的内存映射到用户空间,则可以通过SetPageReserved把相应的内存设置为保留后就可以。

linux驱动 pcie 框架_Linux PCI 设备驱动基本框架(二)相关推荐

  1. Linux PCI 设备驱动基本框架(一)

    Linux将所有外部设备看成是一类特殊文件,称之为"设备文件",如果说系统调用是Linux内核和应用程序之间的接口,那么设备驱动程序则可以看成是 Linux内核与外部设备之间的接口 ...

  2. 浅谈Linux PCI设备驱动(一)

    要弄清楚Linux PCI设备驱动,首先要明白,所谓的Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分.不知道读者理不理解这句话,本人觉得这句话很重要,对于PCI.US ...

  3. 浅谈Linux PCI设备驱动(二)

    我们在浅谈Linux PCI设备驱动(一)中(以下简称浅谈(一) )介绍了PCI的配置寄存器组,而Linux PCI初始化就是使用了这些寄存器来进行的.后面我们会举个例子来说明Linux PCI设备驱 ...

  4. linux PCI设备驱动

    1.PCI 简介 1.1 PCI 引脚 为处理数据.寻址.接口控制.仲裁以及系统功能,PC接口作为目标设备的设备至少有47条引脚.作为总线主设备的设备至少有49条引脚.必要的引脚在左边,任选的引脚在右 ...

  5. Pci设备驱动:设备枚举

    有了设备模型基础及usb设备驱动的基础知识,来了解PCI设备驱动,就相对简单了,因为PCI设备驱动仍然套用了设备驱动模型的方式,用到的仍然是设备模型的相应函数,只是把相应的pci设备挂载到PCI总线的 ...

  6. Linux I2C核心、总线与设备驱动

    Linux I2C核心.总线与设备驱动 I2C总线仅仅使用SCL. SDA这两根信号线就实现了设备之间的数据交互,极大地简化了对硬件资源和PCB板布线空间的占用.因此, I2C总线非常广泛地应用在EE ...

  7. PCI设备驱动(一)

    首先要明确两个概念:Linux内核 PCI设备驱动和设备本身驱动两部分.工作中所谓的编写设备驱动,其实就是编写设备本身驱动.因为Linux 内核的PCI驱动是内核自带的. 当然,并不是说内核帮咱们写好 ...

  8. Linux-USB驱动笔记(六)--设备驱动框架

    Linux-USB驱动笔记(六)--设备驱动框架 1.前言 2.USB设备驱动 3.重要结构体 3.1.usb_driver -- USB设备驱动 3.2.usb_device_id -- 支持的US ...

  9. PCI设备驱动与虚拟网卡驱动源码分析

    虚拟网卡驱动例程 #include<linux/module.h> #include<linux/sched.h> #include<linux/kernel.h> ...

最新文章

  1. pandas读取大文件(chunksize)并通过sqlalchemy写入MySQL数据库
  2. FreeRTOS 临界段和开关中断
  3. Thinkphp怎样修改模板标签定界符
  4. matlab--积分integration
  5. B/S结构的一机多屏的技术论证及可行性讨论
  6. 图分区技术基本概念【1】
  7. String类为什么是final
  8. 设置iis网页服务器cpu占比,为什么iis的一个线程池占了100%cpu
  9. CPP中的this指针
  10. html如何取消盒子间的间隔,使用flexbox时,多行子元素之间的间距如何控制?
  11. 复制网站部分内容_这些网站帮你十分钟搞定年终报告/合同/公文/策划写作!
  12. Libnet核心数据结构
  13. Android-界面-隐藏/显示
  14. linux终端文件保存,Linux 终端中命令输出保存到文件中的方法
  15. 电影推荐系统 python简书_电影推荐系统:TMDB 5000 Movie Dataset(一)
  16. 电池电量显示模块、美容仪、剃须刀、血氧仪、红外体温计、脱毛器、(耳)额温枪、电子秤等段码屏LCD液晶显示驱动IC-VK1024B 6*4段显示,VK1056 14*4段显示,少脚位1621,具省电模式
  17. 一文详解HFSS波端口和集总端口
  18. 儿童python编程教程-儿童编程python入门
  19. 利用python通过两点构成的空间直线和平面计算交点
  20. 前端获取北京时间_js获取北京时间

热门文章

  1. 一位大神写的js爬虫
  2. Python_模块介绍
  3. 终于等到你:CYQ.Data V5系列 (ORM数据层)最新版本开源了
  4. 深耕大数据市场,所问数据打造深度学习数据分析与预测引擎
  5. 与应用程序松耦合的报表开发组织
  6. Alt + sysrq + REISUB doesn't reboot my laptop
  7. volcanol_Linux_问题汇总系列_1_系统引导过程中到check filesystem时就无法继续引导问题解决方法。...
  8. FTPVSFTPD安装和参数说明
  9. H极大值—lhMorpHMax
  10. Silverlight+WCF 实战-网络象棋最终篇之解决重复的消息提示(八)