[ARM Linux 驱动开发] “新”字符设备驱动

文章标题中的“新”,只是对于 [ARM Linux 驱动开发] 第一个字符设备驱动 这篇文章而言的。先前的字符设备驱动,我们需要自己检查还未被分配的设备号,之后传递给相关函数;并且我们还需要手动使用 mknod 命令来创建字符设备文件。本篇文章解决以上两个问题,让驱动更加“智能”。

1. 自动分配设备号

之前我们是通过 register_chrdev 函数来注册字符设备的。但如之前所说,设备号需要我们自己确定,且一个主设备号下的所有此设备号都会使用掉。

现在我们了解一组新的函数,它们可以解决当前的问题:

  • int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
  •             const char *name);

alloc_chrdev_region 函数可用于动态注册一组设备号。因为是动态申请,所以第一个参数 dev 是一个指针,返回动态分配的设备号。第二个参数 baseminor 指明起始的次设备号。第三个参数 count 指明申请的设备号个数,一般为 1 个。最后一个参数 name 指明设备名称。

  • int register_chrdev_region(dev_t from, unsigned count, const char *name);

register_chrdev_regionalloc_chrdev_region 函数类似,都是手动指定设备号的。不同点是 register_chrdev_region 是可以注册一组连续的设备号。第一个参数 from 指明起始设备号,其中必须指明主设备号。countname 参数指明申请设备号个数和设备名称。

  • void unregister_chrdev_region(dev_t from, unsigned count);

无论是 alloc_chrdev_region 还是 register_chrdev_region 注册的设备号,用完都需要使用 register_chrdev_region 函数进行卸载。

字符设备注册

之前的 register_chrdev 函数是设备号分配加注册设备一体的(可以看到有一个 file_operations 参数)。现在我们使用 alloc_chrdev_region 这系列函数“自行”分配设备号了,所以还需要“自行”注册设备。

linux 中使用 cdev 结构体来表示一个字符设备,在其上也有一系列函数进行操作:

  • void cdev_init(struct cdev *cdev, const struct file_operations *fops);

cdev_init 函数对字符设备进行初始化。第一个参数 cdev 指定要初始化的字符设备结构体;第二个参数 fops 我们已经很熟悉了,是文件操作函数集合。

  • int cdev_add(struct cdev *p, dev_t dev, unsigned count);

cdev_add 函数用于把指定的字符设备添加到 linux 系统。第一个参数 p 就是要添加的字符设备;第二个参数 dev 指明起始设备号;第三个参数 count 指明添加的设备数量。

  • void cdev_del(struct cdev *p);

cdev_del 函数用于把指定的字符设备从 linux 系统中移除。参数 p 指明需要移除的字符设备。

驱动程序编写与测试

现在我们开始编写驱动程序,先将文件操作函数留空。重点关注模块加载和卸载函数。

模块加载函数 led_init:如果指定了主设备号,则通过 register_chrdev_region 注册(第 47 至 50 行);否则通过 alloc_chrdev_region 动态分配(第 54 至 56 行)。第 60 至 63 行初始化字符设备,并将字符设备添加到 linux 系统中。

模块卸载函数 led_exit:第 70 行将此字符设备从 linux 系统中移除;第 72 行卸载注册的设备号。

程序中将所有需要用到的全局变量放在了自定义的结构体 LedDev 中。相比于定义多个全局变量,定义一个全局的结构体变量,会显得更加紧凑一点。

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5.  
  6. #define LED_CNT 1
  7. #define LED_NAME "led"
  8.  
  9. struct LedDev
  10. {
  11.     /* 设备号相关 */
  12.     dev_t devId;
  13.     int major;
  14.     int minor;
  15.     /* cdev */
  16.     struct cdev cdev;
  17. };
  18.  
  19. struct LedDev ledDev;
  20.  
  21. int led_open(struct inode* inode, struct file* filp)
  22. {
  23.     return 0;
  24. }
  25.  
  26. int led_release(struct inode *inode, struct file *filp)
  27. {
  28.     return 0;
  29. }
  30.  
  31. ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  32. {   
  33.     return 0;
  34. }
  35.  
  36. const struct file_operations led_fops =
  37. {
  38.     .owner = THIS_MODULE,
  39.     .open = led_open,
  40.     .release = led_release,
  41.     .write = led_write,
  42. };
  43.  
  44. static int __init led_init(void)
  45. {
  46.     /* 注册设备号 */
  47.     if (ledDev.major)
  48.     {
  49.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  50.         register_chrdev_region(ledDev.devId, LED_CNTLED_NAME);
  51.     }
  52.     else
  53.     {
  54.         alloc_chrdev_region(&ledDev.devId, 0LED_CNTLED_NAME);
  55.         ledDev.major = MAJOR(ledDev.devId);
  56.         ledDev.minor = MINOR(ledDev.devId);
  57.     }
  58.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  59.  
  60.     /* 设置 cdev */
  61.     ledDev.cdev.owner = THIS_MODULE;
  62.     cdev_init(&ledDev.cdev, &led_fops);
  63.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  64.  
  65.     return 0;
  66. }
  67.  
  68. static void __exit led_exit(void)
  69. {
  70.     cdev_del(&ledDev.cdev);
  71.  
  72.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  73. }
  74.  
  75. module_init(led_init);
  76. module_exit(led_exit);
  77. MODULE_LICENSE("GPL");
  78. MODULE_AUTHOR("hanhan blog");

程序写好后,我们把驱动文件放入开发板上进行测试。因为没有指定主设备号,所以程序是动态分配的设备号,从图 1 中可以看到,当前给 led 驱动设备分配了 (249,0) 这个设备号。

图1 动态分配的设备号

2. 自动创建设备文件

上一节我们已经解决了手动分配设备号的问题,现在接着解决需要通过 mknod 命令手动创建字符设备文件的问题。

在 linux 下通过 udev 来实现设备文件的创建和删除。使用 busybox 构建根文件系统的时候,busybox 会创建一个 udev 的简化版——mdev,来实现设备文件节点的自动创建与删除。这边我们先不细究原理,还是直接看需要使用到的函数:

  • struct class *class_create(struct module *owner, const char *name);

class_create 函数(宏)用于创建一个 class 结构体。第一个参数 owner 指定其所属的模块,一般为 THIS_MODULE;第二个参数 name 指定其名称。

  • void class_destroy(struct class *cls);

class_destroy 函数用于销毁一个 class

  • struct device *device_create(struct class *class, struct device *parent,
  •                  dev_t devt, void *drvdata, const char *fmt, ...);

device_create 函数用于在类下自动创建设备节点。参数 class 指明设备文件要在哪个类下创建;参数 parent 指明父设备,一般为 NULL;参数 devt 指明设备号;参数 drvdata 是设备需要使用到的数据,一般为 NULL;参数 fmt 指定设备名称,可用可变参数指定。

  • void device_destroy(struct class *class, dev_t devt);

device_destroy 函数用于删除创建的设备。参数 class 指明所属的类;参数 devt 指明要删除的设备号。

驱动程序编写与测试

在了解了自动创建设备文件的函数之后,我们在之前写的驱动代码上增加功能。在模块的加载和卸载函数上进行了添加。

模块加载函数:第 69 行创建 class 结构体;第 70 至 73 行对返回的地址进行判断,如果错误则进行返回。第 75 行创建设备节点;同样第 76 至 79 行对返回的地址进行判断。

模块卸载函数:第 89 行卸载创建的设备设备节点;第 90 行卸载创建的类。注意先卸载设备节点,后卸载类。

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5.  
  6. #define LED_CNT 1
  7. #define LED_NAME "led"
  8.  
  9. struct LedDev
  10. {
  11.     /* 设备号相关 */
  12.     dev_t devId;
  13.     int major;
  14.     int minor;
  15.     /* cdev */
  16.     struct cdev cdev;
  17.     /* mdev */
  18.     struct class* class;
  19.     struct device* device;
  20. };
  21.  
  22. struct LedDev ledDev;
  23.  
  24. int led_open(struct inode* inode, struct file* filp)
  25. {
  26.     return 0;
  27. }
  28.  
  29. int led_release(struct inode *inode, struct file *filp)
  30. {
  31.     return 0;
  32. }
  33.  
  34. ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  35. {   
  36.     return 0;
  37. }
  38.  
  39. const struct file_operations led_fops =
  40. {
  41.     .owner = THIS_MODULE,
  42.     .open = led_open,
  43.     .release = led_release,
  44.     .write = led_write,
  45. };
  46.  
  47. static int /*__init*/ led_init(void)
  48. {
  49.     /* 注册设备号 */
  50.     if (ledDev.major)
  51.     {
  52.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  53.         register_chrdev_region(ledDev.devId, LED_CNTLED_NAME);
  54.     }
  55.     else
  56.     {
  57.         alloc_chrdev_region(&ledDev.devId, 0LED_CNTLED_NAME);
  58.         ledDev.major = MAJOR(ledDev.devId);
  59.         ledDev.minor = MINOR(ledDev.devId);
  60.     }
  61.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  62.  
  63.     /* 设置 cdev */
  64.     ledDev.cdev.owner = THIS_MODULE;
  65.     cdev_init(&ledDev.cdev, &led_fops);
  66.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  67.  
  68.     /* 设置 mdev */
  69.     ledDev.class = class_create(THIS_MODULELED_NAME);
  70.     if (IS_ERR(ledDev.class))
  71.     {
  72.         return PTR_ERR(ledDev.class);
  73.     }
  74.     
  75.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULLLED_NAME);
  76.     if (IS_ERR(ledDev.device))
  77.     {
  78.         return PTR_ERR(ledDev.device);
  79.     }
  80.  
  81.     return 0;
  82. }
  83.  
  84. static void __exit led_exit(void)
  85. {
  86.     cdev_del(&ledDev.cdev);
  87.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  88.  
  89.     device_destroy(ledDev.class, ledDev.devId);
  90.     class_destroy(ledDev.class);
  91. }
  92.  
  93. module_init(led_init);
  94. module_exit(led_exit);
  95. MODULE_LICENSE("GPL");
  96. MODULE_AUTHOR("hanhan blog");

对编译的驱动程序进行测试,加载驱动之后可以发现自动创建了 /dev/led 字符设备文件。

图2 自动创建设备文件

3. 补齐点灯功能

前两节已经把本章需要介绍的内容讲完了。最后需要把这个“新”的 led 驱动补齐一下,补上它最初的点灯功能。这部分内容和 [ARM Linux 驱动开发] LED 驱动开发(ioremap) 完全一样,这里便不再赘述了。

  1. #include <linux/module.h>
  2. #include <linux/cdev.h>
  3. #include <linux/device.h>
  4. #include <linux/fs.h>
  5. #include <asm/io.h>
  6. #include <asm/uaccess.h>
  7.  
  8. #define LED_CNT 1
  9. #define LED_NAME "led"
  10.  
  11. struct LedDev
  12. {
  13.     /* 设备号相关 */
  14.     dev_t devId;
  15.     int major;
  16.     int minor;
  17.     /* cdev */
  18.     struct cdev cdev;
  19.     /* mdev */
  20.     struct class* class;
  21.     struct device* device;
  22. };
  23.  
  24. struct LedDev ledDev;
  25.  
  26. /* 寄存器物理地址 */
  27. #define CCM_CCGR1_BASE (0x020c406c)
  28. #define SW_MUX_GPIO1_IO03_BASE (0x020e0068)
  29. #define SW_PAD_GPIO1_IO03_BASE (0x020e02f4)
  30. #define GPIO1_GDIR_BASE (0x0209c004)
  31. #define GPIO1_DR_BASE (0x0209c000)
  32.  
  33. /* 映射后的虚拟地址 */
  34. void __iomem* CCM_CCGR1;
  35. void __iomem* SW_MUX_GPIO1_IO03;
  36. void __iomem* SW_PAD_GPIO1_IO03;
  37. void __iomem* GPIO1_GDIR;
  38. void __iomem* GPIO1_DR;
  39.  
  40. int led_open(struct inode* inode, struct file* filp)
  41. {
  42.     return 0;
  43. }
  44.  
  45. int led_release(struct inode *inode, struct file *filp)
  46. {
  47.     return 0;
  48. }
  49.  
  50. void turn_on_led(void)
  51. {
  52.     int val = readl(GPIO1_DR);
  53.     val &= ~(1 << 3);
  54.     writel(val, GPIO1_DR);
  55. }
  56.  
  57. void turn_off_led(void)
  58. {
  59.     int val = readl(GPIO1_DR);
  60.     val |= (1 << 3);
  61.     writel(val, GPIO1_DR);  
  62. }
  63.  
  64. ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  65. {   
  66.     int res;
  67.     unsigned char writeBuf[1], status;
  68.  
  69.     res = copy_from_user(writeBuf, buf, sizeof(writeBuf));
  70.     if (res < 0)
  71.     {
  72.         printk("copy_from_user() failed\n");
  73.         return -EFAULT;
  74.     }
  75.  
  76.     status = writeBuf[0];
  77.     if (status == 1)
  78.         turn_on_led();
  79.     else
  80.         turn_off_led();
  81.     
  82.     return 0;
  83. }
  84.  
  85. const struct file_operations led_fops =
  86. {
  87.     .owner = THIS_MODULE,
  88.     .open = led_open,
  89.     .release = led_release,
  90.     .write = led_write,
  91. };
  92.  
  93. static int /*__init*/ led_init(void)
  94. {
  95.     int val;
  96.  
  97.     /* 寄存器映射 */
  98.     CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
  99.     SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
  100.     SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
  101.     GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
  102.     GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
  103.  
  104.     /* 使能 GPIO1 时钟*/
  105.     val = readl(CCM_CCGR1);
  106.     val |= (3 << 26);
  107.     writel(val, CCM_CCGR1);
  108.  
  109.     /* 复用为 GPIO */
  110.     writel(5, SW_MUX_GPIO1_IO03);
  111.     /* IO 属性 */
  112.     writel(0x10b0, SW_PAD_GPIO1_IO03);
  113.  
  114.     /* GPIO 设置为输出 */
  115.     val = readl(GPIO1_GDIR);
  116.     val |= (1 << 3);
  117.     writel(val, GPIO1_GDIR);
  118.     /* 默认关闭 */
  119.     val = readl(GPIO1_DR);
  120.     val |= (1 << 3);
  121.     writel(val, GPIO1_DR);
  122.  
  123.     /* 注册设备号 */
  124.     if (ledDev.major)
  125.     {
  126.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  127.         register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
  128.     }
  129.     else
  130.     {
  131.         alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
  132.         ledDev.major = MAJOR(ledDev.devId);
  133.         ledDev.minor = MINOR(ledDev.devId);
  134.     }
  135.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  136.  
  137.     /* 设置 cdev */
  138.     ledDev.cdev.owner = THIS_MODULE;
  139.     cdev_init(&ledDev.cdev, &led_fops);
  140.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  141.  
  142.     /* 设置 mdev */
  143.     ledDev.class = class_create(THIS_MODULE, LED_NAME);
  144.     if (IS_ERR(ledDev.class))
  145.     {
  146.         return PTR_ERR(ledDev.class);
  147.     }
  148.     
  149.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
  150.     if (IS_ERR(ledDev.device))
  151.     {
  152.         return PTR_ERR(ledDev.device);
  153.     }
  154.  
  155.     return 0;
  156. }
  157.  
  158. static void __exit led_exit(void)
  159. {
  160.     cdev_del(&ledDev.cdev);
  161.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  162.  
  163.     device_destroy(ledDev.class, ledDev.devId);
  164.     class_destroy(ledDev.class);
  165. }
  166.  
  167. module_init(led_init);
  168. module_exit(led_exit);
  169. MODULE_LICENSE("GPL");
  170. MODULE_AUTHOR("hanhan blog");