Linux驱动—实现一个驱动支持多个设备

x33g5p2x  于2022-05-11 转载在 Linux  
字(6.4k)|赞(0)|评价(0)|浏览(607)

前面内容:
前面内容:
1 Linux驱动—内核模块基本使用

2 Linux驱动—内核模块参数,依赖(进一步讨论)

3 字符设备驱动

4 虚拟串口设备驱动

每个设备都写一个驱动太麻烦了,所以要Linux驱动—实现一个驱动支持多个设备。

对于多设备引入的变化:
我们首先要向 内核注册多个设备号
其次就是在添加cdev对象时指明改cdev对象管理了多个设备;
或者添加多个cdev对象,每个cdev对象管理一个设备

接下来最麻烦的部分在于读写操作,因为设备是多个,那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。 在读写操作时,怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言,就是要确定对哪个FIFO进行操作) ?

观察读和写函数,没有发现能够区别设备的形参。
再观察open接口,我们会发现有一个inode 形参,通过前面的内容我们知道,inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。

因此,我们可以在open接口函数中取出这些信息(inode里面包含了对应设备的设备号以及所对应的cdev对象的地址),并存放在file结构对象的某个成员中,再在读写的接口函数中获取该file结构的成员,从而可以区分出对哪个设备进行操作。

下面首先展示用一个cdev实现对多个设备的支持

  1. #include <linux/init.h>
  2. #include <linux/kernel.h>
  3. #include <linux/module.h>
  4. #include <linux/fs.h>
  5. #include <linux/cdev.h>
  6. #include <linux/kfifo.h>
  7. #define VSER_MAJOR 256
  8. #define VSER_MINOR 0
  9. #define VSER_DEV_CNT 2
  10. #define VSER_DEV_NAME "vser"
  11. static struct cdev vsdev;
  12. static DEFINE_KFIFO(vsfifo0, char, 32);
  13. static DEFINE_KFIFO(vsfifo1, char, 32);
  14. static int vser_open(struct inode *inode, struct file *filp)
  15. {
  16. switch (MINOR(inode->i_rdev)) {
  17. default:
  18. case 0:
  19. filp->private_data = &vsfifo0;
  20. break;
  21. case 1:
  22. filp->private_data = &vsfifo1;
  23. break;
  24. }
  25. return 0;
  26. }
  27. static int vser_release(struct inode *inode, struct file *filp)
  28. {
  29. return 0;
  30. }
  31. static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
  32. {
  33. unsigned int copied = 0;
  34. struct kfifo *vsfifo = filp->private_data;
  35. kfifo_to_user(vsfifo, buf, count, &copied);
  36. return copied;
  37. }
  38. static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
  39. {
  40. unsigned int copied = 0;
  41. struct kfifo *vsfifo = filp->private_data;
  42. kfifo_from_user(vsfifo, buf, count, &copied);
  43. return copied;
  44. }
  45. static struct file_operations vser_ops = {
  46. .owner = THIS_MODULE,
  47. .open = vser_open,
  48. .release = vser_release,
  49. .read = vser_read,
  50. .write = vser_write,
  51. };
  52. static int __init vser_init(void)
  53. {
  54. int ret;
  55. dev_t dev;
  56. dev = MKDEV(VSER_MAJOR, VSER_MINOR);
  57. ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
  58. if (ret)
  59. goto reg_err;
  60. cdev_init(&vsdev, &vser_ops);
  61. vsdev.owner = THIS_MODULE;
  62. ret = cdev_add(&vsdev, dev, VSER_DEV_CNT);
  63. if (ret)
  64. goto add_err;
  65. return 0;
  66. add_err:
  67. unregister_chrdev_region(dev, VSER_DEV_CNT);
  68. reg_err:
  69. return ret;
  70. }
  71. static void __exit vser_exit(void)
  72. {
  73. dev_t dev;
  74. dev = MKDEV(VSER_MAJOR, VSER_MINOR);
  75. cdev_del(&vsdev);
  76. unregister_chrdev_region(dev, VSER_DEV_CNT);
  77. }
  78. module_init(vser_init);
  79. module_exit(vser_exit);
  80. MODULE_LICENSE("GPL");
  81. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  82. MODULE_DESCRIPTION("A simple character device driver");
  83. MODULE_ALIAS("virtual-serial");

来分析下代码:

这里把CNT改成2 说明支持2个设备

  1. static struct cdev vsdev;
  2. static DEFINE_KFIFO(vsfifo0, char, 32);
  3. static DEFINE_KFIFO(vsfifo1, char, 32);

定义了二个FIFO,vsfifo0和1 (这里用的是动态分配fifo要更好,但是后面会涉及内存分配的知识,所以先用静态的)

在open接口函数中根据次设备号的值来确定保存哪个FIFO结构的(vsfifo0还是1)地址到file结构中的private_data 的值,即FIFO结构的地址

  1. static int vser_open(struct inode *inode, struct file *filp)
  2. {
  3. switch (MINOR(inode->i_rdev)) {
  4. default:
  5. case 0:
  6. filp->private_data = &vsfifo0;
  7. break;
  8. case 1:
  9. filp->private_data = &vsfifo1;
  10. break;
  11. }
  12. return 0;
  13. }

次设备号 0 选择存 vsfifo0
次设备号 1 选择存 vsfifo1

接下来演示如何将每一个cdev对象对应到一个设备来实现一个驱动对多个设备的支持

  1. #include <linux/init.h>
  2. #include <linux/kernel.h>
  3. #include <linux/module.h>
  4. #include <linux/fs.h>
  5. #include <linux/cdev.h>
  6. #include <linux/kfifo.h>
  7. #define VSER_MAJOR 256
  8. #define VSER_MINOR 0
  9. #define VSER_DEV_CNT 2
  10. #define VSER_DEV_NAME "vser"
  11. static DEFINE_KFIFO(vsfifo0, char, 32);
  12. static DEFINE_KFIFO(vsfifo1, char, 32);
  13. struct vser_dev {
  14. struct kfifo *fifo;
  15. struct cdev cdev;
  16. };
  17. static struct vser_dev vsdev[2];
  18. static int vser_open(struct inode *inode, struct file *filp)
  19. {
  20. filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
  21. return 0;
  22. }
  23. static int vser_release(struct inode *inode, struct file *filp)
  24. {
  25. return 0;
  26. }
  27. static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
  28. {
  29. unsigned int copied = 0;
  30. struct vser_dev *dev = filp->private_data;
  31. kfifo_to_user(dev->fifo, buf, count, &copied);
  32. return copied;
  33. }
  34. static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
  35. {
  36. unsigned int copied = 0;
  37. struct vser_dev *dev = filp->private_data;
  38. kfifo_from_user(dev->fifo, buf, count, &copied);
  39. return copied;
  40. }
  41. static struct file_operations vser_ops = {
  42. .owner = THIS_MODULE,
  43. .open = vser_open,
  44. .release = vser_release,
  45. .read = vser_read,
  46. .write = vser_write,
  47. };
  48. static int __init vser_init(void)
  49. {
  50. int i;
  51. int ret;
  52. dev_t dev;
  53. dev = MKDEV(VSER_MAJOR, VSER_MINOR);
  54. ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
  55. if (ret)
  56. goto reg_err;
  57. for (i = 0; i < VSER_DEV_CNT; i++) {
  58. cdev_init(&vsdev[i].cdev, &vser_ops);
  59. vsdev[i].cdev.owner = THIS_MODULE;
  60. vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
  61. ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
  62. if (ret)
  63. goto add_err;
  64. }
  65. return 0;
  66. add_err:
  67. for (--i; i > 0; --i)
  68. cdev_del(&vsdev[i].cdev);
  69. unregister_chrdev_region(dev, VSER_DEV_CNT);
  70. reg_err:
  71. return ret;
  72. }
  73. static void __exit vser_exit(void)
  74. {
  75. int i;
  76. dev_t dev;
  77. dev = MKDEV(VSER_MAJOR, VSER_MINOR);
  78. for (i = 0; i < VSER_DEV_CNT; i++)
  79. cdev_del(&vsdev[i].cdev);
  80. unregister_chrdev_region(dev, VSER_DEV_CNT);
  81. }
  82. module_init(vser_init);
  83. module_exit(vser_exit);
  84. MODULE_LICENSE("GPL");
  85. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  86. MODULE_DESCRIPTION("A simple character device driver");
  87. MODULE_ALIAS("virtual-serial");

代码:

  1. struct vser_dev {
  2. struct kfifo *fifo;
  3. struct cdev cdev;
  4. };

这里定义了一个结构类型 vser_dev,代表一种具体的设备类

通常和设备有关的内容都可以跟cdev一起定义到一个结构中这样更容易

cdev 是所有字符设备的一个抽象,是一个基类,而一个具体类型的设备应该是由该基类派生出来的一个子类,子类包含了特定设备所特有的属性,比如vser_ dev 中的fifo,这样子类就更能刻画好一类具体的设备。

代码

  1. static struct vser_dev vsdev[2];

创建了两个vser_dev 类型的对象,和C++不同的是,创建这两个对象仅仅是为其分配了内存,并没有调用构造函数来初始化这两个对象,但在代码的第74行到第77行完成了这个操作。

  1. for (i = 0; i < VSER_DEV_CNT; i++) {
  2. cdev_init(&vsdev[i].cdev, &vser_ops);
  3. vsdev[i].cdev.owner = THIS_MODULE;
  4. vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;

你看,初始化init cdev

查看内核源码,会发现这种面向对象的思想处处可见,只能说因为语言的特性,并没有把这种形式体现得很明显而已。

代码的第74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作,并且初始化了fifo 成员的指向。

  1. for (i = 0; i < VSER_DEV_CNT; i++) {
  2. cdev_init(&vsdev[i].cdev, &vser_ops);
  3. vsdev[i].cdev.owner = THIS_MODULE;
  4. vsdev[i].fifo = i == 0 ? (struct kfifo *) &vsfifo0 : (struct kfifo*)&vsfifo1;
  5. ret = cdev_add(&vsdev[i].cdev, dev + i, 1);
  6. if (ret)
  7. goto add_err;
  8. }

如果i为0是第一个,指向vsfifo0这个结构的地址
如果i为1是第二个,指向vsfifo1这个结构的地址

这里需要说明的是,用DEFINE_ KFIFO定义的FIFO,每定义一个FIFO就会新定义一种数据类型,所以严格来说vsfifo0和vsfifo1是两种不同类型的对象,但好在这里能和struct kfifo类型兼容。

代码第26行

  1. filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);

用到了一个container_ of宏,这是在Linux内核中设计得非常巧妙的一个宏,在整个Linux内核源码中几乎随处可见。

它的作用就是**根据结构成员的地址来反向得到结构的起始地址**

在代码中,inode->i_cdev 给出了struct vser_dev 结构类型中cdev成员的地址(见图3.2),通过container_ of宏就得到了包含该cdev的结构地址

使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。

完成

相关文章

最新文章

更多