PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和网络等领域。PCIE插槽是可以向下兼容的,比如PCIE 1X接口可以插4X、8X、16X的插槽上。

实现基本的PCIE驱动程序,实现以下模块:初始化设备、设备打开、数据读写和控制、中断处理、设备释放、设备卸载。本程序适合PCIE驱动开发通用调试的基本框架,对于具体PCIE设备,需要配置相关寄存器才可以使用!

源代码

 
  1. #include <linux/module.h>

  2. #include <linux/kernel.h>

  3. #include <linux/fs.h>

  4. #include <linux/signal.h>

  5. #include <linux/init.h>

  6. #include <linux/cdev.h>

  7. #include <linux/delay.h>

  8. #include <linux/poll.h>

  9. #include <linux/device.h>

  10. #include <linux/pci.h>

  11. #include <linux/interrupt.h>

  12. #include <asm/uaccess.h>

  13.  
  14. MODULE_LICENSE("Dual BSD/GPL");

  15. MODULE_DESCRIPTION("pcie device driver");

  16.  
  17. #define DEV_NAME "hello_pcie"

  18. #define DEBUG

  19.  
  20. #ifdef DEBUG

  21. #define DEBUG_ERR(format,args...) \

  22. do{ \

  23. printk("[%s:%d] ",__FUNCTION__,__LINE__); \

  24. printk(format,##args); \

  25. }while(0)

  26. #else

  27. #define DEBUG_PRINT(format,args...)

  28. #endif

  29.  
  30. //1M

  31. #define DMA_BUFFER_SIZE 1*1024*1024

  32. #define FASYNC_MINOR 1

  33. #define FASYNC_MAJOR 244

  34. #define DEVICE_NUMBER 1

  35.  
  36. static struct class * hello_class;

  37. static struct device * hello_class_dev;

  38.  
  39. struct hello_device

  40. {

  41. struct pci_dev* pci_dev;

  42. struct cdev cdev;

  43. dev_t devno;

  44. }my_device;

  45.  
  46. //barn(n=0,1,2或者0,1,2,3,4,5) 空间的物理地址,长度,虚拟地址

  47. unsigned long bar0_phy;

  48. unsigned long bar0_vir;

  49. unsigned long bar0_length;

  50. unsigned long bar1_phy;

  51. unsigned long bar1_vir;

  52. unsigned long bar1_length;

  53.  
  54. //进行DMA转换时,dma的源地址和目的地址

  55. dma_addr_t dma_src_phy;

  56. dma_addr_t dma_src_vir;

  57. dma_addr_t dma_dst_phy;

  58. dma_addr_t dma_dst_vir;

  59.  
  60. //根据设备的id填写,这里假设厂商id和设备id

  61. #define HELLO_VENDOR_ID 0x666

  62. #define HELLO_DEVICE_ID 0x999

  63. static struct pci_device_id hello_ids[] = {

  64. {HELLO_VENDOR_ID,HELLO_DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},

  65. {0,}

  66. };

  67. MODULE_DEVICE_TABLE(pci,hello_ids);

  68.  
  69. static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id);

  70. static void hello_remove(struct pci_dev *pdev);

  71. static irqreturn_t hello_interrupt(int irq, void * dev);

  72.  
  73. //往iATU写数据的函数

  74. void iATU_write_config_dword(struct pci_dev *pdev,int offset,int value)

  75. {

  76.  
  77. }

  78.  
  79. //假设需要将bar0映射到内存

  80. static void iATU_bar0(void)

  81. {

  82. //下面几步,在手册中有example

  83. //iATU_write_config_dword(my_device.pci_dev,iATU Lower Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存

  84. //iATU_write_config_dword(my_device.pci_dev,iATU Upper Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存

  85.  
  86. //iATU_write_config_dword(my_device.pci_dev,iATU Control 1,0x0);//映射的时内存,所以写0x0

  87. //iATU_write_config_dword(my_device.pci_dev,iATU Control 2,xxx);//使能某个region,开始地址转换

  88. }

  89.  
  90.  
  91. //往dma配置寄存器中读写数据的函数,这是难点一:dma寄存器的寻址。

  92. int dma_read_config_dword(struct pci_dev *pdev,int offset)

  93. {

  94. int value =0;

  95. return value;

  96. }

  97.  
  98. void dma_write_config_dword(struct pci_dev *pdev,int offset,int value)

  99. {

  100.  
  101. }

  102.  
  103. void dma_init(void)

  104. {

  105. int pos;

  106. u16 msi_control;

  107. u32 msi_addr_l;

  108. u32 msi_addr_h;

  109. u32 msi_data;

  110.  
  111. //1.dma 通道0 写初始化 。如何访问DMA global register 寄存器组需要根据具体的硬件,可以通过pci_write/read_config_word/dword,

  112. //也可以通过某个bar,比如通过bar0+偏移量访问。

  113. //1.1 DMA write engine enable =0x1,这里请根据自己的芯片填写

  114. //dma_write_config_dword(->pci_dev,DMA write engine enable,0x1);

  115. //1.2 获取msi能力寄存器的地址

  116. pos =pci_find_capability(my_device.pci_dev,PCI_CAP_ID_MSI);

  117. //1.3 读取msi的协议部分,得到pci设备是32位还是64位,不同的架构msi data寄存器地址同

  118. pci_read_config_word(my_device.pci_dev,pos+2,&msi_control);

  119. //1.4 读取msi能力寄存器组中的地址寄存器的值

  120. pci_read_config_dword(my_device.pci_dev,pos+4,&msi_addr_l);

  121. //1.5 设置 DMA write done IMWr Address Low.这里请根据自己的芯片填写

  122. //dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address Low,msi_addr_l);

  123. //1.6 设置 DMA write abort IMWr Address Low.这里请根据自己的芯片填写

  124. //dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address Low,msi_addr_l);

  125.  
  126. if(msi_control&0x80){

  127. //64位的

  128. //1.7 读取msi能力寄存器组中的高32位地址寄存器的值

  129. pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_addr_h);

  130. //1.8 读取msi能力寄存器组中的数据寄存器的值

  131. pci_read_config_dword(my_device.pci_dev,pos+0xc,&msi_data);

  132.  
  133. //1.9 设置 DMA write done IMWr Address High.这里请根据自己的芯片填写

  134. //dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address High,msi_addr_h);

  135. //1.10 设置 DMA write abort IMWr Address High.这里请根据自己的芯片填写

  136. //dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address High,msi_addr_h);

  137.  
  138. } else {

  139. //1.11 读取msi能力寄存器组中的数据寄存器的值

  140. pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_data);

  141. }

  142.  
  143. //1.12 把数据寄存器的值写入到dma的控制寄存器组中的 DMA write channel 0 IMWr data中

  144. //dma_write_config_dword(my_device.pci_dev,DMA write channel 0 IMWr data,msi_data);

  145.  
  146. //1.13 DMA channel 0 control register 1 = 0x4000010

  147. //dma_write_config_dword(my_device.pci_dev,DMA channel 0 control register 1,0x4000010);

  148.  
  149. //2.dma 通道0 读初始化 和上述操作类似,不再叙述。

  150. }

  151.  
  152. static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id)

  153. {

  154. int i;

  155. int result;

  156. //使能pci设备

  157. if (pci_enable_device(pdev)){

  158. result = -EIO;

  159. goto end;

  160. }

  161.  
  162. pci_set_master(pdev);

  163. my_device.pci_dev=pdev;

  164.  
  165. if(unlikely(pci_request_regions(pdev,DEV_NAME))){

  166. DEBUG_ERR("failed:pci_request_regions\n");

  167. result = -EIO;

  168. goto enable_device_err;

  169. }

  170.  
  171. //获得bar0的物理地址和虚拟地址

  172. bar0_phy = pci_resource_start(pdev,0);

  173. if(bar0_phy<0){

  174. DEBUG_ERR("failed:pci_resource_start\n");

  175. result =-EIO;

  176. goto request_regions_err;

  177. }

  178.  
  179. //假设bar0是作为内存,流程是这样的,但是在本程序中不对bar0进行任何操作。

  180. bar0_length = pci_resource_len(pdev,0);

  181. if(bar0_length!=0){

  182. bar0_vir = (unsigned long)ioremap(bar0_phy,bar0_length);

  183. }

  184.  
  185. //申请一块DMA内存,作为源地址,在进行DMA读写的时候会用到。

  186. dma_src_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_src_phy);

  187. if(dma_src_vir != 0){

  188. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  189. SetPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));

  190. }

  191. } else {

  192. goto free_bar0;

  193. }

  194.  
  195. //申请一块DMA内存,作为目的地址,在进行DMA读写的时候会用到。

  196. dma_dst_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_dst_phy);

  197. if(dma_dst_vir!=0){

  198. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  199. SetPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));

  200. }

  201. } else {

  202. goto alloc_dma_src_err;

  203. }

  204. //使能msi,然后才能得到pdev->irq

  205. result = pci_enable_msi(pdev);

  206. if (unlikely(result)){

  207. DEBUG_ERR("failed:pci_enable_msi\n");

  208. goto alloc_dma_dst_err;

  209. }

  210.  
  211. result = request_irq(pdev->irq, hello_interrupt, 0, DEV_NAME, my_device.pci_dev);

  212. if (unlikely(result)){

  213. DEBUG_ERR("failed:request_irq\n");

  214. goto enable_msi_error;

  215. }

  216.  
  217. //DMA 的读写初始化

  218. dma_init();

  219.  
  220. enable_msi_error:

  221. pci_disable_msi(pdev);

  222. alloc_dma_dst_err:

  223. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  224. ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));

  225. }

  226. pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);

  227. alloc_dma_src_err:

  228. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  229. ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));

  230. }

  231. pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);

  232. free_bar0:

  233. iounmap((void *)bar0_vir);

  234. request_regions_err:

  235. pci_release_regions(pdev);

  236.  
  237. enable_device_err:

  238. pci_disable_device(pdev);

  239. end:

  240. return result;

  241. }

  242.  
  243. static void hello_remove(struct pci_dev *pdev)

  244. {

  245. int i;

  246.  
  247. free_irq(pdev->irq,my_device.pci_dev);

  248. pci_disable_msi(pdev);

  249.  
  250. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  251. ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));

  252. }

  253. pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);

  254.  
  255. for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){

  256. ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));

  257. }

  258. pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);

  259.  
  260. iounmap((void *)bar0_vir);

  261. pci_release_regions(pdev);

  262. pci_disable_device(pdev);

  263. }

  264.  
  265. //难点三:中断响应设置

  266. static irqreturn_t hello_interrupt(int irq, void * dev)

  267. {

  268. //1.该中断调用时机:当DMA完成的时候,会往msi_addr中写入msi_data,从而产生中断调用这个函数

  269. //2.根据DMA Channel control 1 register寄存器的状态,判断读写状态,读失败,写失败,读成功,写成功,做出不同的处理。

  270. return 0;

  271. }

  272. static struct pci_driver hello_driver = {

  273. .name = DEV_NAME,

  274. .id_table = hello_ids,

  275. .probe = hello_probe,

  276. .remove = hello_remove,

  277. };

  278.  
  279. static int hello_open(struct inode *inode, struct file *file)

  280. {

  281. printk("driver: hello_open\n");

  282. //填写产品的逻辑

  283. return 0;

  284. }

  285.  
  286. int hello_close(struct inode *inode, struct file *file)

  287. {

  288. printk("driver: hello_close\n");

  289. //填写产品的逻辑

  290. return 0;

  291. }

  292.  
  293. long hello_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)

  294. {

  295. //填写产品的逻辑

  296. //为应用层提供的函数接口,通过解析cmd,在switch中做出不同的处理。

  297. iATU_bar0();//某个合适的地方调用

  298. return 0;

  299.  
  300. }

  301.  
  302. //难点二:启动dma的读写(read和write函数).

  303. static struct file_operations hello_fops = {

  304. .owner = THIS_MODULE,

  305. .open = hello_open,

  306. .release = hello_close,

  307. .unlocked_ioctl = hello_unlocked_ioctl,

  308. };

  309.  
  310. static int hello_drv_init(void)

  311. {

  312. int ret;

  313. ret = pci_register_driver(&hello_driver);

  314. if (ret < 0) {

  315. printk("failed: pci_register_driver\n");

  316. return ret;

  317. }

  318.  
  319. ret=alloc_chrdev_region(&my_device.devno,0,DEVICE_NUMBER,"hello");

  320. if (ret < 0) {

  321. printk("failed: register_chrdev_region\n");

  322. return ret;

  323. }

  324.  
  325. cdev_init(&my_device.cdev, &hello_fops);

  326. ret = cdev_add(&my_device.cdev, my_device.devno, DEVICE_NUMBER);

  327. if (ret < 0) {

  328. printk("faield: cdev_add\n");

  329. return ret;

  330. }

  331.  
  332. hello_class = class_create(THIS_MODULE, "hello_class");

  333. hello_class_dev = device_create(hello_class, NULL, my_device.devno, NULL, "hello_device");

  334.  
  335. return 0;

  336. }

  337.  
  338. static void hello_drv_exit(void)

  339. {

  340. device_destroy(hello_class,my_device.devno);

  341. class_destroy(hello_class);

  342.  
  343. cdev_del(&(my_device.cdev));

  344. unregister_chrdev_region(my_device.devno,DEVICE_NUMBER);

  345. pci_unregister_driver(&hello_driver);

  346. }

  347.  
  348. module_init(hello_drv_init);

  349. module_exit(hello_drv_exit);

运行结果

程序运行后,在linux内核注册PCIE设备,内容如下

下载

PCIE驱动开发(内含Makefile,直接编译即可使用)
http://download.csdn.net/download/u010872301/10116259

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐