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

PCIE驱动设备文件的创建与操作

2024-09-14 1 0

一、前言

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

二、创建设备文件

PCIe设备属于字符设备,我们按如下步骤创建一个字符设备:

  1. /* 1、Request device number */
  2. ret = alloc_chrdev_region(&hello_pci_info.dev_id, 0, 1, "hello_pcie");
  3. /* 2、Initial char_dev */
  4. hello_pci_info.cdev.owner = THIS_MODULE;
  5. cdev_init(&hello_pci_info.char_dev, &hello_pci_fops);
  6. /* 3、add char_dev */
  7. cdev_add(&hello_pci_info.char_dev, hello_pci_info.dev_id, 1);
  8. /* 4、create class */
  9. hello_pci_info.class = class_create(THIS_MODULE, "hello_pcie");
  10. if (IS_ERR(hello_pci_info.class)) {
  11. return PTR_ERR(hello_pci_info.class);
  12. }
  13. /* 5、create device */
  14. hello_pci_info.device = device_create(hello_pci_info.class, NULL, hello_pci_info.dev_id, NULL, "hello_pcie");
  15. if (IS_ERR(newchrled.device)) {
  16. return PTR_ERR(newchrled.device);
  17. }

其中需要定义一个设备文件操作函数结构体,可以暂时定义为如下所示:

  1. /* device file operations function */
  2. static struct file_operations hello_pcie_fops = {
  3. .owner = THIS_MODULE,
  4. };

将上述创建一个字符设备的操作加在hello_pci_init函数里,同时hello_pci_exit添加对应的卸载操作:

  1. static void __exit hello_pci_exit(void)
  2. {
  3. if(hello_pci_info.dev != NULL) {
  4. cdev_del(&hello_pci_info.char_dev); /* del cdev */
  5. unregister_chrdev_region(hello_pci_info.dev_id, 1); /* unregister device number */
  6. device_destroy(hello_pci_info.class, hello_pci_info.dev_id);
  7. class_destroy(hello_pci_info.class);
  8. }
  9. pci_unregister_driver(&hello_pci_driver);
  10. }

然后编译加载驱动,便可以看到在/dev下有我们创建的hello_pcie设备了:
142656820376

三、添加文件操作函数

如下所示,添加文件的open,close,write,read函数:

  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. #define HELLO_PCI_DEVICE_ID 0x11e8
  10. #define HELLO_PCI_VENDOR_ID 0x1234
  11. #define HELLO_PCI_REVISION_ID 0x10
  12. static struct pci_device_id ids[] = {
  13. { PCI_DEVICE(HELLO_PCI_VENDOR_ID, HELLO_PCI_DEVICE_ID), },
  14. { 0 , }
  15. };
  16. static struct hello_pci_info_t {
  17. dev_t dev_id;
  18. struct cdev char_dev;
  19. struct class *class;
  20. struct device *device;
  21. struct pci_dev *dev;
  22. void __iomem *address_bar0;
  23. atomic_t compute_running;
  24. wait_queue_head_t r_wait;
  25. } hello_pci_info;
  26. MODULE_DEVICE_TABLE(pci, ids);
  27. static irqreturn_t hello_pci_irq_handler(int irq, void *dev_info)
  28. {
  29. struct hello_pci_info_t *_pci_info = dev_info;
  30. uint32_t irq_status;
  31. // get irq_stutas
  32. irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
  33. printk("hello_pcie: get irq status: 0x%0x\n", irq_status);
  34. // clean irq
  35. *((uint32_t *)(_pci_info->address_bar0 + 0x64)) = irq_status;
  36. // get irq_stutas
  37. irq_status = *((uint32_t *)(_pci_info->address_bar0 + 0x24));
  38. if(irq_status == 0x00){
  39. printk("hello_pcie: receive irq and clean success. \n");
  40. }else{
  41. printk("hello_pcie: receive irq but clean failed !!! \n");
  42. return IRQ_NONE;
  43. }
  44. atomic_set(&(_pci_info->compute_running), 0);
  45. wake_up_interruptible(&(_pci_info->r_wait));
  46. return IRQ_HANDLED;
  47. }
  48. /*
  49. * @description : 打开设备
  50. * @param - inode : 传递给驱动的inode
  51. * @param - file : 设备文件,file结构体有个叫做private_data的成员变量
  52. * 一般在open的时候将private_data指向设备结构体。
  53. * @return : 0 成功;其他 失败
  54. */
  55. static int hello_pcie_open(struct inode *inode, struct file *file)
  56. {
  57. printk("hello_pcie: open dev file.\n");
  58. init_waitqueue_head(&hello_pci_info.r_wait);
  59. return 0;
  60. }
  61. /*
  62. * @description : 关闭/释放设备
  63. * @param - file : 要关闭的设备文件(文件描述符)
  64. * @return : 0 成功;其他 失败
  65. */
  66. static int hello_pcie_close(struct inode *inode, struct file *file)
  67. {
  68. printk("hello_pcie: close dev file.\n");
  69. return 0;
  70. }
  71. /*
  72. * @description : 向设备写数据
  73. * @param - filp : 设备文件,表示打开的文件描述符
  74. * @param - buf : 要写给设备写入的数据
  75. * @param - cnt : 要写入的数据长度
  76. * @param - offt : 相对于文件首地址的偏移
  77. * @return : 写入的字节数,如果为负值,表示写入失败
  78. */
  79. static ssize_t hello_pcie_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
  80. {
  81. int retvalue;
  82. unsigned char databuf[4] = {0, 0, 0, 0};
  83. uint32_t compute_value;
  84. retvalue = copy_from_user(databuf, buf, cnt);
  85. if(retvalue < 0) {
  86. printk("hello_pcie: write failed!\n");
  87. return -EFAULT;
  88. }
  89. atomic_set(&hello_pci_info.compute_running, 1);
  90. compute_value = ((databuf[0]) | (databuf[1]<<8) | (databuf[2]<<16) | (databuf[3]<<24));
  91. *((uint32_t *)(hello_pci_info.address_bar0 + 0x08)) = compute_value;
  92. return 0;
  93. }
  94. /*
  95. * @description : 从设备读取数据
  96. * @param – filp : 要打开的设备文件(文件描述符)
  97. * @param – buf : 返回给用户空间的数据缓冲区
  98. * @param – cnt : 要读取的数据长度
  99. * @param – offt : 相对于文件首地址的偏移
  100. * @return : 读取的字节数,如果为负值,表示读取失败
  101. */
  102. static ssize_t hello_pcie_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
  103. {
  104. int ret;
  105. uint32_t compute_result = 0;
  106. /* 加入等待队列,当有按键按下或松开动作发生时,才会被唤醒 */
  107. ret = wait_event_interruptible(hello_pci_info.r_wait, 0 == atomic_read(&hello_pci_info.compute_running));
  108. if(ret)
  109. return ret;
  110. compute_result = *((uint32_t *)(hello_pci_info.address_bar0 + 0x08));
  111. printk("hello_pcie: get compute_result: %0d\n", compute_result);
  112. /* 将按键状态信息发送给应用程序 */
  113. ret = copy_to_user(buf, &compute_result, sizeof(int));
  114. return ret;
  115. }
  116. /* device file operations function */
  117. static struct file_operations hello_pcie_fops = {
  118. .owner = THIS_MODULE,
  119. .open = hello_pcie_open,
  120. .release = hello_pcie_close,
  121. .read = hello_pcie_read,
  122. .write = hello_pcie_write,
  123. };
  124. static int hello_pcie_probe(struct pci_dev *dev, const struct pci_device_id *id)
  125. {
  126. int bar = 0;
  127. int ret;
  128. resource_size_t len;
  129. ret = pci_enable_device(dev);
  130. if(ret) {
  131. return ret;
  132. }
  133. len = pci_resource_len(dev, bar);
  134. hello_pci_info.address_bar0 = pci_iomap(dev, bar, len);
  135. hello_pci_info.dev = dev;
  136. // register interrupt
  137. ret = request_irq(dev->irq, hello_pci_irq_handler, IRQF_SHARED, "hello_pci", &hello_pci_info);
  138. if(ret) {
  139. printk("request IRQ failed.\n");
  140. return ret;
  141. }
  142. // enable irq for finishing factorial computation
  143. *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x80;
  144. return 0;
  145. }
  146. static void hello_pcie_remove(struct pci_dev *dev)
  147. {
  148. // disable irq for finishing factorial computation
  149. *((uint32_t *)(hello_pci_info.address_bar0 + 0x20)) = 0x01;
  150. free_irq(dev->irq, &hello_pci_info);
  151. pci_iounmap(dev, hello_pci_info.address_bar0);
  152. pci_disable_device(dev);
  153. }
  154. static struct pci_driver hello_pci_driver = {
  155. .name = "hello_pcie",
  156. .id_table = ids,
  157. .probe = hello_pcie_probe,
  158. .remove = hello_pcie_remove,
  159. };
  160. static int __init hello_pci_init(void)
  161. {
  162. int ret = pci_register_driver(&hello_pci_driver);
  163. if(hello_pci_info.dev == NULL){
  164. printk("hello_pci: probe pcie device failed!\n");
  165. return ret;
  166. }
  167. /* 1、Request device number */
  168. ret = alloc_chrdev_region(&hello_pci_info.dev_id, 0, 1, "hello_pcie");
  169. /* 2、Initial char_dev */
  170. hello_pci_info.char_dev.owner = THIS_MODULE;
  171. cdev_init(&hello_pci_info.char_dev, &hello_pcie_fops);
  172. /* 3、add char_dev */
  173. cdev_add(&hello_pci_info.char_dev, hello_pci_info.dev_id, 1);
  174. /* 4、create class */
  175. hello_pci_info.class = class_create(THIS_MODULE, "hello_pcie");
  176. if (IS_ERR(hello_pci_info.class)) {
  177. return PTR_ERR(hello_pci_info.class);
  178. }
  179. /* 5、create device */
  180. hello_pci_info.device = device_create(hello_pci_info.class, NULL, hello_pci_info.dev_id, NULL, "hello_pcie");
  181. if (IS_ERR(hello_pci_info.device)) {
  182. return PTR_ERR(hello_pci_info.device);
  183. }
  184. return ret;
  185. }
  186. static void __exit hello_pci_exit(void)
  187. {
  188. if(hello_pci_info.dev != NULL) {
  189. cdev_del(&hello_pci_info.char_dev); /* del cdev */
  190. unregister_chrdev_region(hello_pci_info.dev_id, 1); /* unregister device number */
  191. device_destroy(hello_pci_info.class, hello_pci_info.dev_id);
  192. class_destroy(hello_pci_info.class);
  193. }
  194. pci_unregister_driver(&hello_pci_driver);
  195. }
  196. module_init(hello_pci_init);
  197. module_exit(hello_pci_exit);
  198. MODULE_LICENSE("GPL");
  199. MODULE_INFO(intree, "Y");

四、编写用户程序

编写用户测试程序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. int main(int argc, char *argv[])
  10. {
  11. int fd, retvalue;
  12. char *filename = "/dev/hello_pcie";
  13. uint32_t data_val = 6;
  14. int read_val;
  15. /* 打开驱动设备文件 */
  16. fd = open(filename, O_RDWR);
  17. if(fd < 0){
  18. printf("file %s open failed!\n", filename);
  19. return -1;
  20. }
  21. /* 向/dev/hello_pcie文件写入数据 */
  22. retvalue = write(fd, &data_val, sizeof(int));
  23. if(retvalue < 0){
  24. printf("Open %s Failed!\n", filename);
  25. close(fd);
  26. return -1;
  27. }
  28. read(fd, &read_val, sizeof(int));
  29. printf("factorial computation result : %0d \n", read_val);
  30. retvalue = close(fd); /* 关闭文件 */
  31. if(retvalue < 0){
  32. printf("file %s close failed!\r\n", filename);
  33. return -1;
  34. }
  35. return 0;
  36. }

五、运行测试

编译加载驱动,
142815241795
使用如下命令编译测试程序:

  1. gcc testapp.c

然后运行测试程序,我们可以看到计算得到的阶乘结果为720,即6*5*4*3*2*1=720,符合预期结果
142904468635

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

0 篇笔记 写笔记

LINUX-PCIE驱动hello_pcie源代码
一、前言教程参考:02 实战部分 PCIE设备测试https://www.bilibili.com/video/BV1zx4y1Y7D2教程参考:03 PCIe设备驱动源码解析https://www.bilibili.com/video/BV1KT421m7Gk二、驱动编写新建hello_pcie......
PCIE驱动设备文件的创建与操作
一、前言在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。二、创建设备文件PCIe设备属于字符设备,我们按如下步骤创建一个字符设备: ......
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......
关注公众号
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

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

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