一个最简单的LINUX-PCIE设备驱动
+ -

PCIE-LINUX DMA驱动编写与测试

2024-09-14 1 0

一、前言

代码参考:https://gitee.com/daalw/PCIe_Driver_Demo
通过查看docs/specs/edu.txt可以知道 EDU 设备是支持DMA的

https://github.com/qemu/qemu/blob/v2.7.0/docs/specs/edu.txt

  1. EDU device
  2. ==========
  3. Copyright (c) 2014-2015 Jiri Slaby
  4. This document is licensed under the GPLv2 (or later).
  5. This is an educational device for writing (kernel) drivers. Its original
  6. intention was to support the Linux kernel lectures taught at the Masaryk
  7. University. Students are given this virtual device and are expected to write a
  8. driver with I/Os, IRQs, DMAs and such.
  9. The devices behaves very similar to the PCI bridge present in the COMBO6 cards
  10. developed under the Liberouter wings. Both PCI device ID and PCI space is
  11. inherited from that device.
  12. Command line switches:
  13. -device edu[,dma_mask=mask]
  14. dma_mask makes the virtual device work with DMA addresses with the given
  15. mask. For educational purposes, the device supports only 28 bits (256 MiB)
  16. by default. Students shall set dma_mask for the device in the OS driver
  17. properly.
  18. PCI specs
  19. ---------
  20. PCI ID: 1234:11e8
  21. PCI Region 0:
  22. I/O memory, 1 MB in size. Users are supposed to communicate with the card
  23. through this memory.
  24. MMIO area spec
  25. --------------
  26. Only size == 4 accesses are allowed for addresses < 0x80. size == 4 or
  27. size == 8 for the rest.
  28. 0x00 (RO) : identification (0xRRrr00edu)
  29. RR -- major version
  30. rr -- minor version
  31. 0x04 (RW) : card liveness check
  32. It is a simple value inversion (~ C operator).
  33. 0x08 (RW) : factorial computation
  34. The stored value is taken and factorial of it is put back here.
  35. This happens only after factorial bit in the status register (0x20
  36. below) is cleared.
  37. 0x20 (RW) : status register, bitwise OR
  38. 0x01 -- computing factorial (RO)
  39. 0x80 -- raise interrupt 0x01 after finishing factorial computation
  40. 0x24 (RO) : interrupt status register
  41. It contains values which raised the interrupt (see interrupt raise
  42. register below).
  43. 0x60 (WO) : interrupt raise register
  44. Raise an interrupt. The value will be put to the interrupt status
  45. register (using bitwise OR).
  46. 0x64 (WO) : interrupt acknowledge register
  47. Clear an interrupt. The value will be cleared from the interrupt
  48. status register. This needs to be done from the ISR to stop
  49. generating interrupts.
  50. 0x80 (RW) : DMA source address
  51. Where to perform the DMA from.
  52. 0x88 (RW) : DMA destination address
  53. Where to perform the DMA to.
  54. 0x90 (RW) : DMA transfer count
  55. The size of the area to perform the DMA on.
  56. 0x98 (RW) : DMA command register, bitwise OR
  57. 0x01 -- start transfer
  58. 0x02 -- direction (0: from RAM to EDU, 1: from EDU to RAM)
  59. 0x04 -- raise interrupt 0x100 after finishing the DMA
  60. IRQ controller
  61. --------------
  62. An IRQ is generated when written to the interrupt raise register. The value
  63. appears in interrupt status register when the interrupt is raised and has to
  64. be written to the interrupt acknowledge register to lower it.
  65. DMA controller
  66. --------------
  67. One has to specify, source, destination, size, and start the transfer. One
  68. 4096 bytes long buffer at offset 0x40000 is available in the EDU device. I.e.
  69. one can perform DMA to/from this space when programmed properly.
  70. Example of transferring a 100 byte block to and from the buffer using a given
  71. PCI address 'addr':
  72. addr -> DMA source address
  73. 0x40000 -> DMA destination address
  74. 100 -> DMA transfer count
  75. 1 -> DMA command register
  76. while (DMA command register & 1)
  77. ;
  78. 0x40000 -> DMA source address
  79. addr+100 -> DMA destination address
  80. 100 -> DMA transfer count
  81. 3 -> DMA command register
  82. while (DMA command register & 1)
  83. ;

三、驱动编写

编写驱动代码如下所示:

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/pci.h>
  4. #include <linux/init.h>
  5. #include <linux/module.h>
  6. #include <linux/errno.h>
  7. #include <linux/cdev.h>
  8. #include <linux/device.h>
  9. #include <linux/dma-mapping.h>
  10. #define HELLO_PCI_DEVICE_ID 0x11e8
  11. #define HELLO_PCI_VENDOR_ID 0x1234
  12. #define HELLO_PCI_REVISION_ID 0x10
  13. #define ONCHIP_MEM_BASE 0x40000
  14. static struct pci_device_id ids[] = {
  15. { PCI_DEVICE(HELLO_PCI_VENDOR_ID, HELLO_PCI_DEVICE_ID), },
  16. { 0 , }
  17. };
  18. static struct hello_pci_info_t {
  19. dev_t dev_id;
  20. struct cdev char_dev;
  21. struct class *class;
  22. struct device *device;
  23. struct pci_dev *pdev;
  24. void __iomem *address_bar0;
  25. atomic_t dma_running;
  26. spinlock_t lock;
  27. wait_queue_head_t r_wait;
  28. } hello_pci_info;
  29. MODULE_DEVICE_TABLE(pci, ids);
  30. static irqreturn_t hello_pci_irq_handler(int irq, void *dev_info)
  31. {
  32. struct hello_pci_info_t *_pci_info = dev_info;
  33. uint32_t irq_status;
  34. // get irq_stutas
  35. irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
  36. printk("hello_pcie: get irq status: 0x%0x\n", irq_status);
  37. // clean irq
  38. *((uint32_t *)(_pci_info->address_bar0 + 0x64)) = irq_status;
  39. // get irq_stutas
  40. irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
  41. if(irq_status == 0x00){
  42. printk("hello_pcie: receive irq and clean success. \n");
  43. }else{
  44. printk("hello_pcie: receive irq but clean failed !!! \n");
  45. return IRQ_NONE;
  46. }
  47. atomic_set(&(_pci_info->dma_running), 0);
  48. wake_up_interruptible(&(_pci_info->r_wait));
  49. return IRQ_HANDLED;
  50. }
  51. /*
  52. * @description : 打开设备
  53. * @param - inode : 传递给驱动的inode
  54. * @param - file : 设备文件,file结构体有个叫做private_data的成员变量
  55. * 一般在open的时候将private_data指向设备结构体。
  56. * @return : 0 成功;其他 失败
  57. */
  58. static int hello_pcie_open(struct inode *inode, struct file *file)
  59. {
  60. printk("hello_pcie: open dev file.\n");
  61. init_waitqueue_head(&hello_pci_info.r_wait);
  62. return 0;
  63. }
  64. /*
  65. * @description : 关闭/释放设备
  66. * @param - file : 要关闭的设备文件(文件描述符)
  67. * @return : 0 成功;其他 失败
  68. */
  69. static int hello_pcie_close(struct inode *inode, struct file *file)
  70. {
  71. printk("hello_pcie: close dev file.\n");
  72. return 0;
  73. }
  74. //dma transefer from RC to EP
  75. int dma_write_block(dma_addr_t dma_handle_addr, int count, struct hello_pci_info_t *_pci_info)
  76. {
  77. spin_lock(&_pci_info->lock);
  78. //源地址低32位
  79. iowrite32((uint32_t)(dma_handle_addr), _pci_info->address_bar0 + 0x80);
  80. //源地址高32位
  81. iowrite32((uint32_t)(dma_handle_addr>>32), _pci_info->address_bar0 + 0x84);
  82. //目的地址低32位
  83. iowrite32(ONCHIP_MEM_BASE, _pci_info->address_bar0 + 0x88);
  84. //目的地址高32位
  85. iowrite32(0x0, _pci_info->address_bar0 + 0x8c);
  86. //传输长度
  87. iowrite32(count, _pci_info->address_bar0 + 0x90);
  88. //启动DMA一次
  89. iowrite32((0x01) | (0x00<<1) | (0x01<<2), _pci_info->address_bar0 + 0x98);
  90. spin_unlock(&_pci_info->lock);
  91. return 0;
  92. }
  93. //dma transefer from EP to RC
  94. int dma_read_block(dma_addr_t dma_handle_addr, int count, struct hello_pci_info_t *_pci_info)
  95. {
  96. spin_lock(&_pci_info->lock);
  97. // 源地址低32位
  98. iowrite32(ONCHIP_MEM_BASE, _pci_info->address_bar0 + 0x80);
  99. // 源地址高32位
  100. iowrite32(0, _pci_info->address_bar0 + 0x84);
  101. // 目的地址低32位
  102. iowrite32((uint32_t)(dma_handle_addr), _pci_info->address_bar0 + 0x88);
  103. // 目的地址高32位
  104. iowrite32((uint32_t)(dma_handle_addr>>32), _pci_info->address_bar0 + 0x8c);
  105. // 传输长度
  106. iowrite32(count, _pci_info->address_bar0 + 0x90);
  107. // 启动DMA一次
  108. iowrite32((0x01) | (0x01<<1) | (0x01<<2), _pci_info->address_bar0 + 0x98);
  109. spin_unlock(&_pci_info->lock);
  110. return 0;
  111. }
  112. /*
  113. * @description : 向设备写数据
  114. * @param - pfile : 设备文件,表示打开的文件描述符
  115. * @param - buf : 要写给设备写入的数据
  116. * @param - cnt : 要写入的数据长度
  117. * @param - offt : 相对于文件首地址的偏移
  118. * @return : 写入的字节数,如果为负值,表示写入失败
  119. */
  120. static ssize_t hello_pcie_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offt)
  121. {
  122. int ret;
  123. unsigned char * databuf;
  124. dma_addr_t dma_handle_addr;
  125. if(cnt > 4096){
  126. printk("hello_pcie: dma does not support transfers larger than 4096.\n");
  127. return -ENOMEM;
  128. }
  129. databuf = dma_alloc_coherent(&hello_pci_info.pdev->dev, 4096, &dma_handle_addr, GFP_KERNEL);
  130. if (!databuf) {
  131. printk("hello_pcie: Failed to allocate DMA buffer\n");
  132. return -ENOMEM;
  133. }
  134. else {
  135. printk("hello_pcie: get dma_handle_addr success: 0x%0llx\n", dma_handle_addr);
  136. }
  137. ret = copy_from_user(databuf, buf, cnt);
  138. if(ret < 0) {
  139. printk("hello_pcie: write failed!\n");
  140. return -EFAULT;
  141. }
  142. dma_write_block(dma_handle_addr, cnt, &hello_pci_info);
  143. atomic_set(&hello_pci_info.dma_running, 1);
  144. ret = wait_event_interruptible(hello_pci_info.r_wait, 0 == atomic_read(&hello_pci_info.dma_running));
  145. dma_free_coherent(&hello_pci_info.pdev->dev, 4096, databuf, dma_handle_addr);
  146. return ret;
  147. }
  148. /*
  149. * @description : 从设备读取数据
  150. * @param – filp : 要打开的设备文件(文件描述符)
  151. * @param – buf : 返回给用户空间的数据缓冲区
  152. * @param – cnt : 要读取的数据长度
  153. * @param – offt : 相对于文件首地址的偏移
  154. * @return : 读取的字节数,如果为负值,表示读取失败
  155. */
  156. static ssize_t hello_pcie_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
  157. {
  158. int ret;
  159. unsigned char * databuf;
  160. dma_addr_t dma_handle_addr;
  161. if(cnt > 4096){
  162. printk("hello_pcie: dma does not support transfers larger than 4096.\n");
  163. return -ENOMEM;
  164. }
  165. databuf = dma_alloc_coherent(&hello_pci_info.pdev->dev, 4096, &dma_handle_addr, GFP_KERNEL);
  166. if (!databuf) {
  167. printk("hello_pcie: Failed to allocate DMA buffer\n");
  168. return -ENOMEM;
  169. }
  170. else {
  171. printk("hello_pcie: get dma_handle_addr success: 0x%0llx\n", dma_handle_addr);
  172. }
  173. dma_read_block(dma_handle_addr, cnt, &hello_pci_info);
  174. atomic_set(&hello_pci_info.dma_running, 1);
  175. /* 加入等待队列,当有DMA传输完成时,才会被唤醒 */
  176. ret = wait_event_interruptible(hello_pci_info.r_wait, 0 == atomic_read(&hello_pci_info.dma_running));
  177. if(ret)
  178. return ret;
  179. memset(buf, 0, cnt);
  180. ret = copy_to_user(buf, databuf, cnt);
  181. dma_free_coherent(&hello_pci_info.pdev->dev, 4096, databuf, dma_handle_addr);
  182. return ret;
  183. }
  184. /* device file operations function */
  185. static struct file_operations hello_pcie_fops = {
  186. .owner = THIS_MODULE,
  187. .open = hello_pcie_open,
  188. .release = hello_pcie_close,
  189. .read = hello_pcie_read,
  190. .write = hello_pcie_write,
  191. };
  192. static int hello_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
  193. {
  194. int bar = 0;
  195. int ret;
  196. resource_size_t len;
  197. ret = pci_enable_device(pdev);
  198. if(ret) {
  199. return ret;
  200. }
  201. pci_set_master(pdev);
  202. if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(28)))
  203. {
  204. pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(28));
  205. dev_info(&pdev->dev, "using a 28-bit dma mask\n");
  206. }
  207. else
  208. {
  209. dev_info(&pdev->dev, "unable to use 28-bit dma mask\n");
  210. return -1;
  211. }
  212. len = pci_resource_len(pdev, bar);
  213. hello_pci_info.address_bar0 = pci_iomap(pdev, bar, len);
  214. hello_pci_info.pdev = pdev;
  215. // register interrupt
  216. ret = request_irq(pdev->irq, hello_pci_irq_handler, IRQF_SHARED, "hello_pci", &hello_pci_info);
  217. if(ret) {
  218. printk("request IRQ failed.\n");
  219. return ret;
  220. }
  221. // enable irq for finishing factorial computation
  222. *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x80;
  223. return 0;
  224. }
  225. static void hello_pcie_remove(struct pci_dev *pdev)
  226. {
  227. // disable irq for finishing factorial computation
  228. *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x01;
  229. free_irq(pdev->irq, &hello_pci_info);
  230. pci_iounmap(pdev, hello_pci_info.address_bar0);
  231. pci_disable_device(pdev);
  232. }
  233. static struct pci_driver hello_pci_driver = {
  234. .name = "hello_pcie",
  235. .id_table = ids,
  236. .probe = hello_pcie_probe,
  237. .remove = hello_pcie_remove,
  238. };
  239. static int __init hello_pci_init(void)
  240. {
  241. int ret = pci_register_driver(&hello_pci_driver);
  242. if(hello_pci_info.pdev == NULL){
  243. printk("hello_pci: probe pcie device failed!\n");
  244. return ret;
  245. }
  246. /* 1、Request device number */
  247. ret = alloc_chrdev_region(&hello_pci_info.dev_id, 0, 1, "hello_pcie");
  248. /* 2、Initial char_dev */
  249. hello_pci_info.char_dev.owner = THIS_MODULE;
  250. cdev_init(&hello_pci_info.char_dev, &hello_pcie_fops);
  251. /* 3、add char_dev */
  252. cdev_add(&hello_pci_info.char_dev, hello_pci_info.dev_id, 1);
  253. /* 4、create class */
  254. hello_pci_info.class = class_create(THIS_MODULE, "hello_pcie");
  255. if (IS_ERR(hello_pci_info.class)) {
  256. return PTR_ERR(hello_pci_info.class);
  257. }
  258. /* 5、create device */
  259. hello_pci_info.device = device_create(hello_pci_info.class, NULL, hello_pci_info.dev_id, NULL, "hello_pcie");
  260. if (IS_ERR(hello_pci_info.device)) {
  261. return PTR_ERR(hello_pci_info.device);
  262. }
  263. return ret;
  264. }
  265. static void __exit hello_pci_exit(void)
  266. {
  267. if(hello_pci_info.pdev != NULL) {
  268. cdev_del(&hello_pci_info.char_dev); /* del cdev */
  269. unregister_chrdev_region(hello_pci_info.dev_id, 1); /* unregister device number */
  270. device_destroy(hello_pci_info.class, hello_pci_info.dev_id);
  271. class_destroy(hello_pci_info.class);
  272. }
  273. pci_unregister_driver(&hello_pci_driver);
  274. }
  275. module_init(hello_pci_init);
  276. module_exit(hello_pci_exit);
  277. MODULE_LICENSE("GPL");
  278. MODULE_INFO(intree, "Y");

注意其中的 pci_set_dma_mask和pci_set_consistent_dma_mask就是为了适应28bit的DMA地址做的适配。

四、编写用户程序

编写用户测试程序testapp.c如下:

  1. #include "stdio.h"
  2. #include "stdint.h"
  3. #include "unistd.h"
  4. #include "sys/types.h"
  5. #include "sys/stat.h"
  6. #include "fcntl.h"
  7. #include "stdlib.h"
  8. #include "string.h"
  9. #define BUFFER_LENGTH 128
  10. int main(int argc, char *argv[])
  11. {
  12. int fd, retvalue;
  13. char *filename = "/dev/hello_pcie";
  14. unsigned char *write_buf = malloc(BUFFER_LENGTH);
  15. unsigned char *read_buf = malloc(BUFFER_LENGTH);
  16. /* 打开驱动设备文件 */
  17. fd = open(filename, O_RDWR);
  18. if(fd < 0){
  19. printf("file %s open failed!\n", filename);
  20. return -1;
  21. }
  22. for(int i = 0;i < BUFFER_LENGTH;i++)
  23. {
  24. write_buf[i] = i;
  25. }
  26. /* 向/dev/hello_pcie文件写入数据 */
  27. retvalue = write(fd, write_buf, BUFFER_LENGTH);
  28. if(retvalue < 0){
  29. printf("Write %s Failed!\n", filename);
  30. close(fd);
  31. return -1;
  32. }
  33. printf("write success, data: 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x\n", write_buf[0], write_buf[1], write_buf[2], write_buf[3], write_buf[4], write_buf[5], write_buf[6], write_buf[7]);
  34. retvalue = read(fd, read_buf, BUFFER_LENGTH);
  35. if(retvalue < 0){
  36. printf("Read %s Failed!\n", filename);
  37. close(fd);
  38. return -1;
  39. }
  40. printf("read success, data: 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x, 0x%0x\n", read_buf[0], read_buf[1], read_buf[2], read_buf[3], read_buf[4], read_buf[5], read_buf[6], read_buf[7]);
  41. retvalue = close(fd); /* 关闭文件 */
  42. if(retvalue < 0){
  43. printf("file %s close failed!\r\n", filename);
  44. return -1;
  45. }
  46. return 0;
  47. }

五、运行测试

编译加载驱动,
143410221966

使用如下命令编译测试程序:

  1. gcc testapp.c

然后运行测试程序,可以看到符合预期结果
143428681464

原文转自:https://blog.csdn.net/qq_38113006/article/details/140450481

0 篇笔记 写笔记

PCIE-LINUX DMA驱动编写与测试
一、前言代码参考:https://gitee.com/daalw/PCIe_Driver_Demo通过查看docs/specs/edu.txt可以知道 EDU 设备是支持DMA的https://github.com/qemu/qemu/blob/v2.7.0/docs/specs/edu.txt......
关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!