PCIE开发流程.docx
- 文档编号:10945255
- 上传时间:2023-02-23
- 格式:DOCX
- 页数:23
- 大小:70.78KB
PCIE开发流程.docx
《PCIE开发流程.docx》由会员分享,可在线阅读,更多相关《PCIE开发流程.docx(23页珍藏版)》请在冰豆网上搜索。
PCIE开发流程
PCIE开发流程
前言:
对于USB、PCIE设备这种挂接在总线上的设备而言,USB、PCI只是它们的”工作单位”,它们需要向”工作单位”注册(使用usb_driver,pci_driver),并接收”工作单位”的管理(被调入probe()、调出disconnect/remove()、放假suspend()/shutdown()、继续上班resume()等),但设备本身可能是一个工程师、一个前台或者一个经理,因此做好工程师,前台或者经理是其主题工作,这部分对应于字符设备驱动,tty设备驱动,网络设备驱动等。
第一节整体构成
整个驱动程序的开发应该包括三个大的部分
1.1驱动模块的加载与卸载
xxx_init_module()注册pci_driver设备。
xxx_cleanup_module()注销pci_driver设备。
1.2pci_driver成员函数的初始化
xxx_probe()完成PCI设备初始化,注册字符设备
xxx_remove()完成PCI设备释放,注销字符设备
1.3字符设备file_operations成员函数
用于实现上层应用程序对下层驱动程序调用时的调用函数。
xxx_open()
xxx_release()
xxx_ioctl()
xxx_read()
xxx_write()
第二节PCIE设备实现细节
由于PCIE设备的驱动的开发都是按照一个统一规范的框架进行的。
因此以一个字符设备为例说明这个框架的实现机制。
在所有PCIE驱动开发的过程中
2.1驱动程序的初始化和注销
涉及的函数为module_init(xxx_init_module),并在init中完成的功能为注册PCIE设备,具体函数内容如下所示:
注销涉及的函数为module_exit(xxx_cleanup_module)在exit中完成的功能为注销PCIE设备,具体函数内容如下所示:
2.2PCIE设备的注册
在模块的初始化过程中,首先是注册PCIE设备,使用函数为pci_register_driver(&xxx_pci_driver),输入变量指明了PCIE结构体,如下所示:
#defineXXX_MODULE_NAME"xxx_audio"
staticstructpci_driverxxx_pci_driver={
.name=XXX_MODULE_NAME,
.id_table=xxx_pci_tbl,
.probe=xxx_probe,
.remove=__devexit_p(xxx_remove),
#ifdefCONFIG_PM
.suspend=xxx_pm_suspend,
.resume=xxx_pm_resume,
#endif/*CONFIG_PM*/
};
结构体中name指明PCIE模块的名称,id_table指明了PCIE的设备驱动号也就是为哪个设备进行驱动等。
其中probe函数完成PCI设备的初始化以及其设备本身身份(字符,TTY,网络等)的驱动注册。
也是驱动注册中最重要的函数。
probe函数讲解
1、首先使能pci设备,pci_enable_device(pci_dev),该函数主要作用是调用底层代码初始化PCI配置空间命令寄存器的I/O位和memory位。
2、设置成总线主DMA模式,pci_set_dma_mask(pci_dev,XXX_DMA_MASK)用于实现对dma设备的设置。
3、读取PCI的配置信息,使用的函数是pci_resource_start(pci_dev,1)获取设备的内存基地址和所有BAR的长度,
4、调用ioremap完成配置信息的映射,可以配置PCI定义的寄存器BAR设置的空间用于映射DMA的寄存器。
4、申请I/O资源,request_region(card->ac97base,256,card_names[pci_id->driver_data])
5、注册字符/网络设备涉及到的函数为
cdev_init(xxx_cdev,&xxx_fops);/*注册驱动*/
register_chrdev_region(xxx_dev_no,1,XXX);/*申请设备号*/
cdev_add(xxx_cdev);/*添加字符设备*/
request_irq(card->irq,&xxx_interrupt,SA_SHIRQ,card_names[pci_id->driver_data],card))/*申请中断以及注册中断处理程序*/
remove函数讲解
1、释放I/O资源pci_release_regions(pdev)
2、禁止PCI设备pci_disable_device(pdev)
释放占用的设备号register_chrdev_region(xxx_dev_no,1,XXX);
3、注销字符设备cdev_del(&xxx_dev.cdev)。
2.3设备的file_operations操作
在probe中需要注册字符设备,实现应用程序对PCIE设备的调用,例如打开,读取,控制等。
这些功能都是通过file_operations操作接口实现。
例如用户使用该设备完成读取操作,那么用户的过程为open(),read()。
而用户调用的这些函数对于linux来说这些调用都会变成系统调用,并指向改设备对应的open(),read()函数,对于该设备来说,指向了xxx_open和xxx_read。
staticstructfile_operationsxxx_fops={
.owner=THIS_MODULE,
.llseek=no_llseek,
.read=sgma_read,
.write=xxx_write,
.poll=xxx_poll,
.ioctl=xxx_ioctl,
.mmap=xxx_mmap,
.open=xxx_open,
.release=xxx_release,
};
接下来,需要实现上面的这些操作,然后就能实现用户对设备的调用。
staticinti810_open(structinode*inode,structfile*file)
{
^^^^^^^^
}
2.4其他说明
a)、中断
在PCIE中可以使用request_irq共享中断或者pci_enable_msi消息告知申请中断,不同之处在于前者在扫描PCI的时候自动为设备分配好中断号,这样存在多个设备共享中断号的情况。
MSI中断是在调用初始化函数pci_enable_msi()才分配中断号,可以保证设备的中断号不会与其他设备共用,从而避免了中断共享能够提高整体性能,但是MSI中断的使用需要Linux操作系统特殊的支持,不具有普遍的适用性。
传统的中断,由于资源号是有限的,常常涉及到多个设备共享同一个中断号,在中断的处理过程中要依次调用每个中断处理函数来判断中断是不是目标设备发出,这会消耗系统性能。
第三节示例程序
#include"card.h"
#include
#include
#defineDMA_MASK0xffffffff
#definetest_dri_major249//主设备号
//#defineINT_ASSERT_W0x02//DMAWriteComplete
//#defineINT_ASSERT_R0x10//DMAReadComplete
/*PCI驱动基本框架,为下面的设备进行驱动*/
staticstructpci_device_idcard_ids[]={
{PCI_DEVICE(PCI_VENDOR_ID_XILINX,PCI_DEVICE_ID_EP_PIPE),},
{0,}
};
MODULE_DEVICE_TABLE(pci,card_ids);
/*probe和remove基本函数*/
staticintcard_probe(structpci_dev*pci_dev,conststructpci_device_id*id);
staticvoidcard_remove(structpci_dev*pdev);
/*pci_driver结构体*/
staticstructpci_drivercard_driver={
.name=DEV_NAME,
.id_table=card_ids,
.probe=card_probe,
.remove=card_remove,
};
staticint__initcard_init(void)
{
intresult;
result=pci_register_driver(&card_driver);
returnresult;
}
staticvoid__exitcard_exit(void)
{
pci_unregister_driver(&card_driver);
}
module_init(card_init);
module_exit(card_exit);
/*PCI驱动基本框架*/
/*特定设备私有数据结构*/
structcard_private{
structpci_dev*pci_dev;
void*pci_bar0;
//wait_queue_head_t*dma_write_wait;
//wait_queue_head_t*dma_read_wait;
};
/*特定设备私有数据结构*/
staticstructcard_private*adapter;
//staticDECLARE_WAIT_QUEUE_HEAD(dma_write_wait);
//staticintflag=1;
//将文件操作与分配的设备号相连
staticconststructfile_operationscard_fops=
{
.owner=THIS_MODULE,
//.ioctl=card_ioctl,
.open=card_open,
.release=card_release,
.read=card_read,
.write=card_write,
};
staticintcard_probe(structpci_dev*pdev,conststructpci_device_id*id)
{
unsignedlongphymem;
void__iomem*mem;
u_int8_tcsz;
u32val;
intresult;
/*配置PCI设备*/
if(pci_enable_device(pdev))
return-EIO;
/*XXX32-bitaddressingonly*/
if(pci_set_dma_mask(pdev,0xffffffff)){
printk(KERN_ERR"ath_pci:
32-bitDMAnotavailable\n");
gotobad;
}
//pci_write_config_word(pdev,0x04,0x0007);
/*配置PCI寄存器,首先调用pci_read_config_byte进行读取PCI配置空间,并将值返回给csz
*Cachelinesizeisusedtosizeandalignvarious
*structuresusedtocommunicatewiththehardware.
*/
pci_read_config_byte(pdev,PCI_CACHE_LINE_SIZE,&csz);
if(csz==0){
/*
*Linux2.4.18(atleast)writesthecachelinesize
*registerasa16-bitwideregisterwhichiswrong.
*Wemusthavethissetupproperlyforrxbuffer
*DMAtoworksoforceareasonablevaluehereifit
*comesupzero.
*/
csz=L1_CACHE_BYTES/sizeof(u_int32_t);
pci_write_config_byte(pdev,PCI_CACHE_LINE_SIZE,csz);
}
/*
*Thedefaultsettingoflatencytimeryieldspoorresults,
*setittothevalueusedbyothersystems.Itmaybeworth
*tweakingthissettingmore.
*配置PCI配置空间
*/
pci_write_config_byte(pdev,PCI_LATENCY_TIMER,0xa8);
/*设置成总线主模式*/
pci_set_master(pdev);
/*读取寄存器信息
*DisabletheRETRY_TIMEOUTregister(0x41)tokeep
*PCITxretriesfrominterferingwithC3CPUstate.
*
*Codetakenfromipw2100driver-jg
*/
pci_read_config_dword(pdev,0x40,&val);
if((val&0x0000ff00)!
=0)
pci_write_config_dword(pdev,0x40,val&0xffff00ff);
//获得BAR0空间的基地址,该地址为存储器域的物理地址;
phymem=pci_resource_start(pdev,0);
if(!
request_mem_region(phymem,pci_resource_len(pdev,0),DEV_NAME)){
printk(KERN_ERR"card_driver:
cannotreservePCImemoryregion\n");
gotobad;
}
//将存储器域的物理地址映射为虚拟地址;
mem=ioremap(phymem,pci_resource_len(pdev,0));
if(!
mem){
printk(KERN_ERR"card_driver:
cannotremapPCImemoryregion\n");
gotobad1;
}
adapter=kmalloc(sizeof(structcard_private),GFP_KERNEL);
if(unlikely(!
adapter)){
return-ENOMEM;
}
adapter->pci_dev=pdev;
adapter->pci_bar0=mem;
//注册设备驱动程序
result=register_chrdev(test_dri_major,DEV_NAME,&card_fops);
if(unlikely(result)){
printk(KERN_ERR"card_driver:
nomemoryfordevicestate\n");
gotobad2;
}
/*
//init_waitqueue_head(adapter->dma_write_wait);
//init_waitqueue_head(adapter->dma_read_wait);
result=pci_enable_msi(pdev);
if(unlikely(result)){
//PDEBUG("cannotenablemsi...\n");
gotobad3;
}
result=request_irq(pdev->irq,card_interrupt,0,DEV_NAME,NULL);
if(unlikely(result)){
//PDEBUG("requestinterruptfailed...\n");
gotobad3;
printk(KERN_DEBUG"request_irq(pdev->irq,card_interrupt,0,DEV_NAME,NULL);");
}
*/
return0;
//bad3:
//unregister_chrdev(test_dri_major,DEV_NAME);
bad2:
iounmap(mem);
bad1:
release_mem_region(phymem,pci_resource_len(pdev,0));
bad:
pci_disable_device(pdev);
return(-ENODEV);
}
staticvoidcard_remove(structpci_dev*pdev)
{
//pci_disable_msi(pdev);
//if(pdev->irq)
//free_irq(pdev->irq,pdev);
iounmap(adapter->pci_bar0);
release_mem_region(pci_resource_start(pdev,0),pci_resource_len(pdev,0));
pci_disable_device(pdev);
unregister_chrdev(test_dri_major,DEV_NAME);
}
//打开设备文件系统调用对应的操作
staticintcard_open(structinode*inode,structfile*filp)
{
return0;
}
//关闭设备文件系统调用对应的操作
staticintcard_release(structinode*inode,structfile*filp)
{
return0;
}
//读取设备信息
staticssize_tcard_read(structfile*file,char__user*buf,size_tcount,loff_t*f_pos)
{
void*virt_addr=NULL;
dma_addr_tdma_write_addr;
u32base,w_ddr2,w_addr,w_size,cst_32,w_counter,dma_cst;
u32ddr2;
inti;
i=0;
/*读取寄存器的值,0x10的位置也就是BAR的初始地址*/
pci_read_config_dword(adapter->pci_dev,0x10,&base);
printk(KERN_DEBUG"pci_read_config_dword,base:
%x\n",base);
printk(KERN_DEBUG"adater->pci_bar0%lx\n",(unsignedlong)(adapter->pci_bar0));
/*********Requestvirt_addr(kernel)forRead(DMAwrite)********/
virt_addr=kmalloc(count,GFP_KERNEL|__GFP_DMA);
if(unlikely(!
virt_addr))
{
//PDEBUG("cannotallocrxmemoryyouwant...\n");
return-EIO;
}
printk(KERN_DEBUG"virt_addr(kernel):
%x\n",(u32)virt_addr);
/*********Requestvirt_addr(kernel)forRead(DMAwrite)********/
/**********************dma_write_addr************************/
//将存储器域的虚拟地址virt_addr转化为pci总线域的物理地址dma_write_addr,供card的DMA控制器使用。
dma_write_addr=pci_map_single(adapter->pci_dev,virt_addr,count,PCI_DMA_FROMDEVICE);
if(unlikely(pci_dma_mapping_error(adapter->pci_dev,dma_write_addr)))
{
//PDEBUG("RXDMAMAPPINGFAIL...\n");
gotoerr_kmalloc;
}
printk(KERN_DEBUG"dma_write_addr:
%x\n",dma_write_addr);
/**********************dma_write_addr************************/
/**********************BAR0kongjian***********************/
//START,w_counter读取BAR0空间
w_counter=ioread32((adapter->pci_bar0+WRITE_DMA_COUNTER_OFFSET));
printk(KERN_DEBUG"START,w_counter:
%x",w_counter);
//w_ddr2DMA写的原地址
iowrite32(*f_pos,(adapter->pci_bar0+WRITE_DDR2_SA_OFFSET));
ddr2=ioread32((adapter->pci_bar0+WRITE_DDR2_SA_OFFSET));
printk(KERN_DEBUG"WRITE_DDR2_SA_OFFSET:
%x",ddr2);
//w_addr
//Lower32-bitaddressofsystemmemorybuffer
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- PCIE 开发 流程