Linux设备模型

x33g5p2x  于2022-06-13 转载在 Linux  
字(28.1k)|赞(0)|评价(0)|浏览(621)

本文是对这篇文章的补充:从零开始学Linux设备驱动–Linux设备模型

设备模型基础补充

关于解决
不能查看相应的设备和驱动的的信息,比如Windows中有设备管理器,我们就可以比较方便的查看相关的设备和驱动的信息。

是关于设备和驱动信息的展示。在Linux系统中有一个sysfs伪文件系统,挂载于/sys目录下,该目录罗列了设备、驱动和硬件相关的信息

在fs4412的终端上,可以使用下面的命令来查看

在/sys目录下有很多子目录
例如
block目录下是块设备
bus目录下是系统中的所有总线(如I2C、SPI和 USB等)
class目录下是一些设备类(如 input输入设备类、tty终端设备类)
devices目录下是系统中所有的设备。

再仔细查看/sys/bus/platforml/devices/5000000.ethernet/目录,它是一个挂接在一个叫platform总线下的以太网设备,其目录下的driver是一个软链接,指向了... ../bus/platform/drivers/dm9000
也就是说,该设备是被注册在platform总线下的一个名叫dm9000的驱动程序所驱动

再看对应的驱动目录/sys/bus/platform/drivers/dm9000/,会发现该驱动程序驱动了..../ .. ../devices/5000000.srom-cs1/5000000.ethernet设备,即驱动了devices目录下的以太网设备

/sys/bus/platform/devices/5000000.ethernet又指向.../.devices/5000000.srom-cs1/5000000.ethernet的软链接。
所以也可以说前面的驱动程序驱动了/sys/bus/platform/drivers/dm9000/设备

上面的内容看起来有点乱,但思路是清晰的,即在总线bus目录下有很多具体的总线,而具体的总线目录下有注册的驱动和挂接的设备,注册的驱动程序驱动对应总线目录下的某些具体设备,总线目录下的某些设备被对应总线下的某个驱动程序所驱动。

那么上面这些信息是怎么来的呢。我们知道,伪文件系统在系统运行时才会有内容,也就是说,伪文件系统的目录、文件以及软链接都是动态生成的,这些内容都是反映内核的相关信息,回顾我们之前学习的 proc接口,不难猜想得出这些信息的生成可以在驱动中来实现。

structkobject

接下来我们就来讨论要生成这些信息的一个重要内核数据结构——structkobject

了解MFC 或者Qt的人都知道那些窗口部件都是一层一层继承下来的,而在最上层有一个最基础的类,MFC的根类是CObject,而 Qt的根类则是QObject。

在这里我们将结构看成类,那么kobject就是Linux 设备驱动模型中的根类。作为驱动开发者,我们没有必要了解kobject 的详细信息,就像作为一个Qt应用程序开发者不需要了解QObject的详细信息一样。

在这里,我们只需知道它和/sys目录下的目录和文件的关系

当向内核成功添加一个kobject对象后,底层的代码会自动在/sys目录下生成一个子目录。

另外,kobject可以附加一些属性,并绑定操作这些属性的方法,当向内核成功添加一个kobject对象后,其附加的属性会被底层的代码自动实现为对象对应目录下的文件,用户访问这些文件最终就变成了调用操作属性的方法来访问其属性。最后,通过 sys的API接口可以将两个kobject对象关联起来,形成软链接

struct kset

除了struct kobject,还有一个叫struct kset的类,它是多个kobject对象的集合,也就是多个kobject对象可以通过一个kset集合在一起。kset本身也内嵌了一个kobject,它可以作为集合中的kobject对象的父对象,从而在 kobject之间形成父子关系,这种父子关系在/sys目录下体现为父目录和子目录的关系。而属于同一集合的 kobject对象形成兄弟关系,在/sys目录下体现为同级目录。

kset也可以附加属性,从而在对应的目录下产生文件。

例子

为了能更好地了解这部分内容,而又不过分深入细节,特别编写了一个非常简单的模块,为了突出主线,省略了出错处理

  1. #include <linux/init.h>
  2. #include <linux/kernel.h>
  3. #include <linux/module.h>
  4. #include <linux/slab.h>
  5. #include <linux/kobject.h>
  6. static struct kset *kset;
  7. static struct kobject *kobj1;
  8. static struct kobject *kobj2;
  9. static unsigned int val = 0;
  10. static ssize_t val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
  11. {
  12. return snprintf(buf, PAGE_SIZE, "%d\n", val);
  13. }
  14. static ssize_t val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
  15. {
  16. char *endp;
  17. printk("size = %d\n", count);
  18. val = simple_strtoul(buf, &endp, 10);
  19. return count;
  20. }
  21. static struct kobj_attribute kobj1_val_attr = __ATTR(val, 0666, val_show, val_store);
  22. static struct attribute *kobj1_attrs[] = {
  23. &kobj1_val_attr.attr,
  24. NULL,
  25. };
  26. static struct attribute_group kobj1_attr_group = {
  27. .attrs = kobj1_attrs,
  28. };
  29. static int __init model_init(void)
  30. {
  31. int ret;
  32. kset = kset_create_and_add("kset", NULL, NULL);
  33. kobj1 = kobject_create_and_add("kobj1", &kset->kobj);
  34. kobj2 = kobject_create_and_add("kobj2", &kset->kobj);
  35. ret = sysfs_create_group(kobj1, &kobj1_attr_group);
  36. ret = sysfs_create_link(kobj2, kobj1, "kobj1");
  37. return 0;
  38. }
  39. static void __exit model_exit(void)
  40. {
  41. sysfs_remove_link(kobj2, "kobj1");
  42. sysfs_remove_group(kobj1, &kobj1_attr_group);
  43. kobject_del(kobj2);
  44. kobject_del(kobj1);
  45. kset_unregister(kset);
  46. }
  47. module_init(model_init);
  48. module_exit(model_exit);
  49. MODULE_LICENSE("GPL");
  50. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  51. MODULE_DESCRIPTION("A simple module for device model");

代码第42行使用kset_create_and_add创建并向内核添加了一个名叫kset的kset对象。

  1. kset = kset_create_and_add("kset", NULL, NULL);

代码第43行和第44行用kobject_create_and_add 分别创建并向内核添加了两个名叫kobj1
和kobj2的kobject对象。

  1. kobj1 = kobject_create_and_add("kobj1", &kset->kobj);
  2. kobj2 = kobject_create_and_add("kobj2", &kset->kobj);

代码第46行为kobj1添加了一组属性kobj1_attr_group

  1. ret = sysfs_create_group(kobj1, &kobj1_attr_group);

这组属性中只有一个属性kobj1_val_ attr, 属性的名字叫val, 所绑定的读和写的方法分别是val_show 和val_store, 对应的文件访问权限是0666。

代码第47行使用sysfs_create_ link在kobj2下创建了一个kobj1的软链接,名叫kobj1。

  1. ret = sysfs_create_link(kobj2, kobj1, "kobj1");

代码第54行至第58行是初始化操作的反操作,用于删除软链接、属性和对象。

  1. static void __exit model_exit(void)
  2. {
  3. sysfs_remove_link(kobj2, "kobj1");
  4. sysfs_remove_group(kobj1, &kobj1_attr_group);
  5. kobject_del(kobj2);
  6. kobject_del(kobj1);
  7. kset_unregister(kset);
  8. }

属性val的读方法将val的值以格式%d打印在buf中,那么读取相应的属性文件,则会得到val的十进制字符串

属性val的写方法是将用户写入文件的内容,即buf中的字符串通过simple_strtoul将字符串转换成十进制的数值再赋值给val

测试:

在创建kset对象时,由于没有指定其父对象,所以kset位于/sys目录下,在创建kobj1
和kobj2时,指定其父对象为kset中内嵌的kobject,所以kobj1 和kobj2位于kset目录之
下。

kobj1 附加了一个属性叫val,所以在kobj1目录下有一个val的文件,对该文件可以
进行读写,其实就是对属性val进行读写。
在kobj2下创建了一个软链接kobj1,所以在kobj2目录下有kobj1的软链接。

对象的关系如图8.1所示。其中,虚线表示kobj1、kobj2属于集合kset,kobj1和kobj2实线指向kset内嵌的kobject表示它们的父对象是kset内嵌的kobject.

总线设备和驱动补充

基本知识见最上面链接文章
为了更好的理解设备模型的影响,以一个简单的例子来说明

  1. /* vbus.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/device.h>
  6. static int vbus_match(struct device *dev, struct device_driver *drv)
  7. {
  8. return 1;
  9. }
  10. static struct bus_type vbus = {
  11. .name = "vbus",
  12. .match = vbus_match,
  13. };
  14. EXPORT_SYMBOL(vbus);
  15. static int __init vbus_init(void)
  16. {
  17. return bus_register(&vbus);
  18. }
  19. static void __exit vbus_exit(void)
  20. {
  21. bus_unregister(&vbus);
  22. }
  23. module_init(vbus_init);
  24. module_exit(vbus_exit);
  25. MODULE_LICENSE("GPL");
  26. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  27. MODULE_DESCRIPTION("A virtual bus");

在vbus.c文件中,代码第12行至第15行

  1. static struct bus_type vbus = {
  2. .name = "vbus",
  3. .match = vbus_match,
  4. };

定义了一个代表总线的vbus对象,该总线的名字是vbus,用于匹配驱动和设备的函数是vbus_ match

代码第21行向内核注册了该总线。

  1. static int __init vbus_init(void)
  2. {
  3. return bus_register(&vbus);
  4. }

代码第26行是总线的注销。

  1. static void __exit vbus_exit(void)
  2. {
  3. bus_unregister(&vbus);
  4. }

为了简单起见,vbus_match仅仅返回1,表示传入的设备和驱动匹配成功,而更一般的情况是考察它们的ID号是否匹配

  1. /* vdrv.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/device.h>
  6. extern struct bus_type vbus;
  7. static struct device_driver vdrv = {
  8. .name = "vdrv",
  9. .bus = &vbus,
  10. };
  11. static int __init vdrv_init(void)
  12. {
  13. return driver_register(&vdrv);
  14. }
  15. static void __exit vdrv_exit(void)
  16. {
  17. driver_unregister(&vdrv);
  18. }
  19. module_init(vdrv_init);
  20. module_exit(vdrv_exit);
  21. MODULE_LICENSE("GPL");
  22. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  23. MODULE_DESCRIPTION("A virtual device driver");

在vdrv.c文件中,代码第9行至第12行定义了一个代表驱动的vdrv对象,该驱动的名字是vdrv,所属的总线是vbus

  1. static struct device_driver vdrv = {
  2. .name = "vdrv",
  3. .bus = &vbus,
  4. };

这样注册这个驱动时,就会将之注册在vbus总线之下。

代码第16行和第21行分别是驱动的注册和注销操作。

  1. static int __init vdrv_init(void)
  2. {
  3. return driver_register(&vdrv);
  4. }
  5. static void __exit vdrv_exit(void)
  6. {
  7. driver_unregister(&vdrv);
  8. }

模块中使用了vbus模块导出的符号vbus。

  1. /* vdev.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/device.h>
  6. extern struct bus_type vbus;
  7. static void vdev_release(struct device *dev)
  8. {
  9. }
  10. static struct device vdev = {
  11. .init_name = "vdev",
  12. .bus = &vbus,
  13. .release = vdev_release,
  14. };
  15. static int __init vdev_init(void)
  16. {
  17. return device_register(&vdev);
  18. }
  19. static void __exit vdev_exit(void)
  20. {
  21. device_unregister(&vdev);
  22. }
  23. module_init(vdev_init);
  24. module_exit(vdev_exit);
  25. MODULE_LICENSE("GPL");
  26. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  27. MODULE_DESCRIPTION("A virtual device");

在vdev.c文件中,代码第13行至第17行定义了一个代表设备的vdev对象,该设备的名字是vdev,是完全用代码虚拟出来的一个设备。所属的总线是vbus,这样注册这个设备时,就会将之挂接到vbus总线之下。

  1. static struct device vdev = {
  2. .init_name = "vdev",
  3. .bus = &vbus,
  4. .release = vdev_release,
  5. };

还有一个用于释放的函数vdev_release, 为了简单起见,这个函数什么都没做。

代码第21行和第26行分别是设备的注册和注销。模块中使用了vbus模块导出的符号vbus。

  1. static int __init vdev_init(void)
  2. {
  3. return device_register(&vdev);
  4. }
  5. static void __exit vdev_exit(void)
  6. {
  7. device_unregister(&vdev);
  8. }

下面是测试和编译的命令结果:

在加载了vbus模块后,/sys/bus 目录下自动生成了vbus目录,并且在vbus目录下生
成了devices和drivers两个目录,分别来记录挂接在vbus总线上的设备 和注册在vbus总线上的驱动

当加载了vdrv模块后,/sys/bus/vbus/drivers 目录下自动生成了vdrv 目录,此时还没有设备与之绑定。

当加载了vdev模块后,/sys/bus/vbus/devices 目录下自动生成了vdev目录,并…bus/vbus/drivers/vdrv的驱动绑定成功,在/sys/devices 目录下也自动生成了vdev目录

其实/sys/bus/vbus/devices/vdev是指向/sys/bus/vbus/devices/vdev的软链接

最后/sys/bus/vbus/drivers/vdrv/中的 vdev 也指定了其绑定的设备…/…/…/…/devices/vdev。

这和我们在前面看到的DM9000网卡非常类似,只是DM9000网卡设备是挂接在platform总线下的,而驱动也是注册在platform总线下的。

虽然使用struct bus_type、 struct device和struct device_driver 能够实现Linux设备模型,但是它们的抽象层次还是太高,不能具体地刻画某一种特定的总线。

所以一种具体的总线会在它们的基础上派生出来,形成更具体的子类,这些子类对象能够更好地描述相应的对象。比如,针对USB总线就派生出了struct usb_bus_type、 struct usb_device 和struct usb_driver, 分别代表具体的USB总线、USB设备和USB驱动。

通常情况下,总线已经在内核中实现好,我们只需要写对应总线的驱动即可,有时候还会编写相应的设备注册代码。

平台设备补充

平台设备及其资源常存在于BSP(Board Support Package, 板级支持包)文件中,该文件通常包含和目标板相关的一些代码。

例如对于QT2410目标板,其对应的BSP文件为arch/arm/mach-s3c24xx/mach-qt2410.c
以CS8900网卡的平台设备摘录如下:

  1. static struct resource qt2410_cs89x0_resources[]={
  2. [0] = DEFINE_RES_MEM(0x19000000,17),
  3. [1] = DEFINE_RES_IRQ(IRQ_EINT9),
  4. };
  5. static struct platform_device qt2410_cs89x0 = {
  6. .name ="cirrus-cs89x0",
  7. .num resources =ARRAY SIZE(qt2410_cs89x0_resources),
  8. .resource =qt2410_cs89x0_resources,
  9. };

CS8900平台设备有两个资源,分别是IORESOURCE_MEM和IORESOURCE_IRQ两种类型的,并用宏DEFINE_RES_MEM和DEFINE_RES_IRQ来定义。

对于DEFINE_RES_MEM宏,里面的两个参数分别是内存的起始地址和大小;

对于DEFINE_RES_IRQ宏,里面的参数则是中断号

读者可以自行查看这两个宏的定义,最终是对start、end 和flags成员进行了赋值

最终定义的平台设备是qt2410_cs89x0, ARRAY_SIZE 是用于获取数组元素个数的宏。

平台总线注册和注销平台设备的API接口补充

主要是举个利用它的例子:

例如:在CS8900网卡驱动中就有如下的代码来获取资源及其大小。

  1. mem_res = platform get_resource(pdev, IORESOURCE_MEM, 0);
  2. dev->irq = platform get_irq(pdev,0);
  3. lp->size = resource_size(mem_res);
  4. virt_addr = ioremap(mem_res->start, lp->size);

先获取了IORESOURCE_ MEM资源,序号为0。
再获取了IORESOURCE_IRQ资源,序号也为0。
所以,当资源类型不同后,序号重新开始编号

lp->size = resource_size(mem_res);获取了内存资源的大小。

最后使用ioremap将内存资源进行映射,得到映射后的虚拟地址。

平台驱动补充

因为在驱动中,经常在模块初始化函数中注册一个平台驱动,在清除函数中注销一个平台驱动

所以内核定义了一个宏来简化这些代码(module_platform_driver)宏的定义如下:

  1. #define module_platform_driver(_platform_driver)\
  2. module_driver(__platform driver, platform driver_register,\
  3. platform_driver_unregister)
  4. #define module_driver(driver,_register,__unregister, ...)\
  5. static int _init__driver##_init_(void)\
  6. (\
  7. return register(&(__driver),##__VA_ARGS__);\
  8. }\
  9. module_init(__driver##_init);\
  10. static void__exit__driver##_exit(void)\
  11. (\
  12. __unregister(&(driver),## __VA_ARGS__); \
  13. module exit(__driver##_exit);

平台驱动简单实例

在前面的基础之上,我们来编写一个简单的平台驱动,再编写一个模块来注册二个设备,代码如下:

  1. /* pltdev.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/platform_device.h>
  6. static void pdev_release(struct device *dev)
  7. {
  8. }
  9. struct platform_device pdev0 = {
  10. .name = "pdev",
  11. .id = 0,
  12. .num_resources = 0,
  13. .resource = NULL,
  14. .dev = {
  15. .release = pdev_release,
  16. },
  17. };
  18. struct platform_device pdev1 = {
  19. .name = "pdev",
  20. .id = 1,
  21. .num_resources = 0,
  22. .resource = NULL,
  23. .dev = {
  24. .release = pdev_release,
  25. },
  26. };
  27. static int __init pltdev_init(void)
  28. {
  29. platform_device_register(&pdev0);
  30. platform_device_register(&pdev1);
  31. return 0;
  32. }
  33. static void __exit pltdev_exit(void)
  34. {
  35. platform_device_unregister(&pdev1);
  36. platform_device_unregister(&pdev0);
  37. }
  38. module_init(pltdev_init);
  39. module_exit(pltdev_exit);
  40. MODULE_LICENSE("GPL");
  41. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  42. MODULE_DESCRIPTION("register a platfom device");

解析:
在pltdev.c文件中,代码第7行至第29行分别定义了两个平台设备,id 为0和1,以示区别,名字都为pdev,没有使用任何资源。

  1. static void pdev_release(struct device *dev)
  2. {
  3. }
  4. struct platform_device pdev0 = {
  5. .name = "pdev",
  6. .id = 0,
  7. .num_resources = 0,
  8. .resource = NULL,
  9. .dev = {
  10. .release = pdev_release,
  11. },
  12. };
  13. struct platform_device pdev1 = {
  14. .name = "pdev",
  15. .id = 1,
  16. .num_resources = 0,
  17. .resource = NULL,
  18. .dev = {
  19. .release = pdev_release,
  20. },
  21. };

在(后面的init和exit)模块的初始化函数和清除函数中分别注册和注销了这两个平台设备。

  1. /* pltdrv.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/platform_device.h>
  6. static int pdrv_suspend(struct device *dev)
  7. {
  8. printk("pdev: suspend\n");
  9. return 0;
  10. }
  11. static int pdrv_resume(struct device *dev)
  12. {
  13. printk("pdev: resume\n");
  14. return 0;
  15. }
  16. static const struct dev_pm_ops pdrv_pm_ops = {
  17. .suspend = pdrv_suspend,
  18. .resume = pdrv_resume,
  19. };
  20. static int pdrv_probe(struct platform_device *pdev)
  21. {
  22. return 0;
  23. }
  24. static int pdrv_remove(struct platform_device *pdev)
  25. {
  26. return 0;
  27. }
  28. struct platform_driver pdrv = {
  29. .driver = {
  30. .name = "pdev",
  31. .owner = THIS_MODULE,
  32. .pm = &pdrv_pm_ops,
  33. },
  34. .probe = pdrv_probe,
  35. .remove = pdrv_remove,
  36. };
  37. module_platform_driver(pdrv);
  38. MODULE_LICENSE("GPL");
  39. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  40. MODULE_DESCRIPTION("A simple platform driver");
  41. MODULE_ALIAS("platform:pdev");

解析:
在pltdrv.c文件中,代码第34行至第42行定义了一个平台驱动,名字也为pdev,这样才能和平台设备匹配。

  1. struct platform_driver pdrv = {
  2. .driver = {
  3. .name = "pdev",
  4. .owner = THIS_MODULE,
  5. .pm = &pdrv_pm_ops,
  6. },
  7. .probe = pdrv_probe,
  8. .remove = pdrv_remove,
  9. };

.pm电源管理函数的集合,实现了挂起和恢复两个电源管理操作。因为是虚拟设备,所以并没有做任何电源管理相关的操作。
为了简单,probe和remove函数也只是返回成功而已。

代码第44行module_platform_driver(pdrv);使用module_ platform _driver这个宏来简化模块初始化函数和卸载函数的编写。
这个宏的定义见上面

编译和测试命令如下:

从上面的测试看出,平台驱动驱动了二个设备pdev.0 和 pdev.1,这是设备名字加id构成的名字

电源管理

在平台驱动里面实现了挂起和恢复两个电源管理函数,从而可以管理设备的电源状
态。

/sys/devices/platform/pdev.0/power/control/sys/devices/platform/pdev.1/power/control两个文件可以用来管理两个设备的电源控制方式,如果文件的内容为auto,那么设备的电源会根据系统的状态自动进行管理为on则表示打开

我们首先确定电源控制方式为自动,可以使用下面的命令进行确认。

  1. cat /sys/devices/platform/pdev.0/power/control
  2. auto
  3. cat /sys/devices/platform/pdev.1/power/control
  4. auto

接下来将Ubuntu系统挂起

系统挂起后,再恢复系统,使用dmesg可以看到,驱动中的suspend和resume(就是挂起和唤醒)函数先后都被调用了二次。

udev和驱动的自动加载

在上面的例子中,我们可以通过加载模块来向系统添加两个设备,也可以通过移除模块来删除这两个设备。
对于这样的操作,我们想使设备被添加到系统后,其驱动能够自动被加载,这对于实际的可支持热插拔的硬件来说更有必要。
比如,我们插入一个USB无线网卡,那么对应的驱动就应该自动加载,而不是由用户来手动加载。

要做到这一点,就必须利用到一个工具一udev,在嵌入式系统中通常使用mdev,其功能比udev要弱很多,但也可以移植udev到嵌入式系统上

使用了Linux设备模型后,任何设备的添加、删除或状态修改都会导致内核向用户空间发送相应的事件,这个事件叫uevent,和kobject密切关联。这样用户空间就可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建和删除设备节点、修改权限、创建软链接、修改网络设备的名字等。

目前实现这个功能的工具就是udev (或mdev),这是一个用户空间的应用程序,捕获来自内核空间发来的事件,然后根据其规则文件进行操作

udev 的规则文件为/etc/udev/rules.d目录下后缀为.rules的文件。
udev规则文件用#来注释,除此之外的就是一条一条的规则。
每条规则至少包含一个键值对,键分为匹配和赋值两种类型。
如果内核发来的事件匹配了规则中的所有匹配键的值,那么这条规则就可以得到应用,并且赋值键被赋予指定的值。
一条规则包含了一个或多个键值对,这些键值对用逗号隔开每个键由操作符规定一个操作,合法的操作符如下:

  • ==和!=:判等,用于匹配键。
  • =、+=和:=:赋值,用于赋值键,=和:=的区别是前者允许用新值来覆盖原来的值,后者则不允许。+=则是追加赋值。

常见的键如下。

  • ACTION:事件动作的名字,如 add表示添加。
  • DEVPATH:事件设备的路径。
  • KERNEL:事件设备的名字。
  • NAME:节点或网络接口的名字。
  • SUBSYSTEM:事件设备子系统。
  • DRIVER:事件设备驱动的名字。
  • ENV {(key}:设备的属性。
  • OWNER、 GROUP、MODE:设备节点的权限。
  • RUN:添加一个和设备相关的命令到一个命令列表中。
  • IMPORT{type}:导入一组设备属性的变量,依赖于类型 type。

上面的键有的是匹配键,有的是赋值键,还有的既是匹配键又是赋值键。其他详细详见udev的man手册(帮助手册)

值还可以使用?、*和[]来进行通配,这和正则表达式中的含义是一样的。接下来来看一个例子:

  1. ACTION=="add", SUBSYSTEM=="scsi_device", RUN+="/sbin/modprobe sg"

它表示当向SCSI子系统添加任意设备后都要添加一个命令“/sbin/modprobe sg”到命令列表中,这个命令就是为相应的设备加载sg驱动模块。

在Ubuntu中自动加载驱动的规则如下,请将这条规则添加到/etc/udev/rules.d/40-modprobe.rules文件中,如果没有这个文件请新建一个。

  1. ENV{MODALIAS}=="?*",RUN+="/sbin/modprobe Senv{MODALIAS}"

它表示根据模块的别名信息,用modprobe命令加载对应的内核模块
为此,我们要给平台驱动一个别名,如 pltdrv.c文件中代码的MODULE_ALIAS("platform:pdev");

pdev要和驱动中用于匹配平台设备的名字保持一致。

  1. MODULE_ALIAS("platform:pdev");

添加了这一条规则后,加载pltdev模块就可以自动加载平台 pltdrv驱动

使用平台设备的LED驱动

前面我们说过,之前的驱动最大的问题就是没有把设备和驱动分离开,这使得驱动的通用性很差。
只要硬件有任何改动(比如换一个管脚,增加或删除 LED 灯),都会导致驱动代码的修改。

有了Linux设备模型以及平台总线后,我们可以把设备的信息用平台设备来实现,这就大大提高了驱动的通用性。

接下来的任务就是把前面的LED驱动改造成基于平台总线的设备和驱动

首先是平台设备,代码如下:

  1. /* fsdev.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/platform_device.h>
  6. static void fsdev_release(struct device *dev)
  7. {
  8. }
  9. static struct resource led2_resources[] = {
  10. [0] = DEFINE_RES_MEM(0x11000C40, 4),
  11. };
  12. static struct resource led3_resources[] = {
  13. [0] = DEFINE_RES_MEM(0x11000C20, 4),
  14. };
  15. static struct resource led4_resources[] = {
  16. [0] = DEFINE_RES_MEM(0x114001E0, 4),
  17. };
  18. static struct resource led5_resources[] = {
  19. [0] = DEFINE_RES_MEM(0x114001E0, 4),
  20. };
  21. unsigned int led2pin = 7;
  22. unsigned int led3pin = 0;
  23. unsigned int led4pin = 4;
  24. unsigned int led5pin = 5;
  25. struct platform_device fsled2 = {
  26. .name = "fsled",
  27. .id = 2,
  28. .num_resources = ARRAY_SIZE(led2_resources),
  29. .resource = led2_resources,
  30. .dev = {
  31. .release = fsdev_release,
  32. .platform_data = &led2pin,
  33. },
  34. };
  35. struct platform_device fsled3 = {
  36. .name = "fsled",
  37. .id = 3,
  38. .num_resources = ARRAY_SIZE(led3_resources),
  39. .resource = led3_resources,
  40. .dev = {
  41. .release = fsdev_release,
  42. .platform_data = &led3pin,
  43. },
  44. };
  45. struct platform_device fsled4 = {
  46. .name = "fsled",
  47. .id = 4,
  48. .num_resources = ARRAY_SIZE(led4_resources),
  49. .resource = led4_resources,
  50. .dev = {
  51. .release = fsdev_release,
  52. .platform_data = &led4pin,
  53. },
  54. };
  55. struct platform_device fsled5 = {
  56. .name = "fsled",
  57. .id = 5,
  58. .num_resources = ARRAY_SIZE(led5_resources),
  59. .resource = led5_resources,
  60. .dev = {
  61. .release = fsdev_release,
  62. .platform_data = &led5pin,
  63. },
  64. };
  65. static struct platform_device *fsled_devices[] = {
  66. &fsled2,
  67. &fsled3,
  68. &fsled4,
  69. &fsled5,
  70. };
  71. static int __init fsdev_init(void)
  72. {
  73. return platform_add_devices(fsled_devices, ARRAY_SIZE(fsled_devices));
  74. }
  75. static void __exit fsdev_exit(void)
  76. {
  77. platform_device_unregister(&fsled5);
  78. platform_device_unregister(&fsled4);
  79. platform_device_unregister(&fsled3);
  80. platform_device_unregister(&fsled2);
  81. }
  82. module_init(fsdev_init);
  83. module_exit(fsdev_exit);
  84. MODULE_LICENSE("GPL");
  85. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  86. MODULE_DESCRIPTION("register LED devices");

由上可知,我们分别定义了4个平台设备,每一个平台设备代表一个LED灯,之所以要这样做,是因为可以任意增加或删除一个LED灯。

4个平台设备都有一个IORESOURCE_MEM资源,用来描述2个寄存器所占用的内存空间;名字都为fsled,用来和平台驱动匹配;id分别为2、3、4、5,用来区别不同的设备。

还给每个平台设备的platform_data成员赋了值,platform_data的类型是void *,用来向驱动传递更多的信息,在这里传递的是每个LED灯使用的管脚号因为只有I/O内存是不能够控制一个具体的管脚的

这些平台设备放在fsled_devices数组中,在模块初始化函数中使用platform_add_devices一次注册到平台总线。

在模块的清除函数中,则使用 platform_device_unregister来注销。

再看看看平台驱动:

  1. /* fsled.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/fs.h>
  6. #include <linux/cdev.h>
  7. #include <linux/slab.h>
  8. #include <linux/ioctl.h>
  9. #include <linux/uaccess.h>
  10. #include <linux/io.h>
  11. #include <linux/ioport.h>
  12. #include <linux/platform_device.h>
  13. #include "fsled.h"
  14. #define FSLED_MAJOR 256
  15. #define FSLED_DEV_NAME "fsled"
  16. struct fsled_dev {
  17. unsigned int __iomem *con;
  18. unsigned int __iomem *dat;
  19. unsigned int pin;
  20. atomic_t available;
  21. struct cdev cdev;
  22. };
  23. static int fsled_open(struct inode *inode, struct file *filp)
  24. {
  25. struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);
  26. filp->private_data = fsled;
  27. if (atomic_dec_and_test(&fsled->available))
  28. return 0;
  29. else {
  30. atomic_inc(&fsled->available);
  31. return -EBUSY;
  32. }
  33. }
  34. static int fsled_release(struct inode *inode, struct file *filp)
  35. {
  36. struct fsled_dev *fsled = filp->private_data;
  37. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  38. atomic_inc(&fsled->available);
  39. return 0;
  40. }
  41. static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  42. {
  43. struct fsled_dev *fsled = filp->private_data;
  44. if (_IOC_TYPE(cmd) != FSLED_MAGIC)
  45. return -ENOTTY;
  46. switch (cmd) {
  47. case FSLED_ON:
  48. writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
  49. break;
  50. case FSLED_OFF:
  51. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  52. break;
  53. default:
  54. return -ENOTTY;
  55. }
  56. return 0;
  57. }
  58. static struct file_operations fsled_ops = {
  59. .owner = THIS_MODULE,
  60. .open = fsled_open,
  61. .release = fsled_release,
  62. .unlocked_ioctl = fsled_ioctl,
  63. };
  64. static int fsled_probe(struct platform_device *pdev)
  65. {
  66. int ret;
  67. dev_t dev;
  68. struct fsled_dev *fsled;
  69. struct resource *res;
  70. unsigned int pin = *(unsigned int*)pdev->dev.platform_data;
  71. dev = MKDEV(FSLED_MAJOR, pdev->id);
  72. ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
  73. if (ret)
  74. goto reg_err;
  75. fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
  76. if (!fsled) {
  77. ret = -ENOMEM;
  78. goto mem_err;
  79. }
  80. cdev_init(&fsled->cdev, &fsled_ops);
  81. fsled->cdev.owner = THIS_MODULE;
  82. ret = cdev_add(&fsled->cdev, dev, 1);
  83. if (ret)
  84. goto add_err;
  85. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  86. if (!res) {
  87. ret = -ENOENT;
  88. goto res_err;
  89. }
  90. fsled->con = ioremap(res->start, resource_size(res));
  91. if (!fsled->con) {
  92. ret = -EBUSY;
  93. goto map_err;
  94. }
  95. fsled->dat = fsled->con + 1;
  96. fsled->pin = pin;
  97. atomic_set(&fsled->available, 1);
  98. writel((readl(fsled->con) & ~(0xF << 4 * fsled->pin)) | (0x1 << 4 * fsled->pin), fsled->con);
  99. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  100. platform_set_drvdata(pdev, fsled);
  101. return 0;
  102. map_err:
  103. res_err:
  104. cdev_del(&fsled->cdev);
  105. add_err:
  106. kfree(fsled);
  107. mem_err:
  108. unregister_chrdev_region(dev, 1);
  109. reg_err:
  110. return ret;
  111. }
  112. static int fsled_remove(struct platform_device *pdev)
  113. {
  114. dev_t dev;
  115. struct fsled_dev *fsled = platform_get_drvdata(pdev);
  116. dev = MKDEV(FSLED_MAJOR, pdev->id);
  117. iounmap(fsled->con);
  118. cdev_del(&fsled->cdev);
  119. kfree(fsled);
  120. unregister_chrdev_region(dev, 1);
  121. return 0;
  122. }
  123. struct platform_driver fsled_drv = {
  124. .driver = {
  125. .name = "fsled",
  126. .owner = THIS_MODULE,
  127. },
  128. .probe = fsled_probe,
  129. .remove = fsled_remove,
  130. };
  131. module_platform_driver(fsled_drv);
  132. MODULE_LICENSE("GPL");
  133. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  134. MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

代码分析:
代码第152行至第159行定义了一个平台驱动fsled_drv,名字叫 fsled,和平台设备匹配。

  1. struct platform_driver fsled_drv = {
  2. .driver = {
  3. .name = "fsled",
  4. .owner = THIS_MODULE,
  5. },
  6. .probe = fsled_probe,
  7. .remove = fsled_remove,
  8. };

代码第161行是平台驱动注册和注销的简化宏。

  1. module_platform_driver(fsled_drv);

fsled_probe函数中,代码第86行首先通过platform_data获取了管脚号。

  1. unsigned int pin = *(unsigned int*)pdev->dev.platform_data;

代码第88行以平台设备中的id为次设备号。

  1. dev = MKDEV(FSLED_MAJOR, pdev->id);

代码第93行动态分配了struct fsled_dev结构对象。

  1. fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);

代码第105行使用platform_get_resource获取了I/O内存的资源,这样要操作GPIO管脚的两个信息就都获得了,一个是管脚号,一个是I/O内存地址

  1. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

代码第122行使用platform_set_drvdata将动态分配得到的fsled 保存到了平台设备中,便于之后的代码能从平台设备中获取 struct fsled_dev结构对象的地址,是经常会使用到的一种技巧,也是一个驱动支持多个设备的关键。

  1. platform_set_drvdata(pdev, fsled);

函数fsled_remove中使用了platform_get_drvdata得到了对应的struct fsled_dev结构对象的地址,其他操作则是函数fsled_probe的反操作。

  1. static int fsled_remove(struct platform_device *pdev)
  2. {
  3. dev_t dev;
  4. struct fsled_dev *fsled = platform_get_drvdata(pdev);
  5. dev = MKDEV(FSLED_MAJOR, pdev->id);
  6. iounmap(fsled->con);
  7. cdev_del(&fsled->cdev);
  8. kfree(fsled);
  9. unregister_chrdev_region(dev, 1);
  10. return 0;
  11. }

函数fsled_open也使用了container_of宏得到了对应的struct fsled_dev结构对象的地址。并保存在 filn->private _data中,这也是我前面读到的一个驱动支持多个设备的技巧。

  1. static int fsled_open(struct inode *inode, struct file *filp)
  2. {
  3. struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);
  4. filp->private_data = fsled;
  5. if (atomic_dec_and_test(&fsled->available))
  6. return 0;
  7. else {
  8. atomic_inc(&fsled->available);
  9. return -EBUSY;
  10. }
  11. }

函数fsled_ioctl相比于以前则要简单一些,因为只控制一个对应的LED灯。

测试的应用代码则是分别打开了4个LED设备文件,然后再分别控制,代码比较简单,这里就不再赘述。
测试方法和前面基本一致,只是要创建4个设备文件,用到4个不同的次设备号2、3、4、5。

自动创建设备节点

前面谈到,内核中设备的添加、删除或修改都会向应用层发送热插拔事件,应用程序可以捕获这些事件来自动完成某些操作,如自动加载驱动、自动创建设备节点等。

接下来以mdev为例,来说明如何自动创建设备节点。
mdev创建设备节点有两种方法,一种是运行mdev-s命令,一种是实时捕获热插拔事件

mdev -s命令通常在根文件系统挂载完成后运行一次,它将递归扫描/sys/block目录/sys/class目录下的文件,根据文件的内容来调用make_device自动创建设备文件,这在busybox中的mdev源码中展现得非常清楚:

  1. int mdev_main(int argc UNUSED_PARAM, char **argv)
  2. {
  3. .....
  4. if(argv[1] && strcmp(argv[1],"-s")==0){
  5. /*
  6. * Scan: mdev -s
  7. * /
  8. .....
  9. recursive_action("/sys/block",ACTION_RECURSE | ACTION_FOLLOWLINKS | ACTION_QUIET,fileAction, dirAction, temp,0);
  10. )
  11. recursive_action("/sys/olass",ACTION_RECURSE| ACTION_FOLLOWLINKS,fileAction, dirAction, temp, 0);

另外一种情况则是当内核发生了热插拔事件后,mdev会自动被调用,这体现在根文件系统中的/etc/init.d/rcS初始化脚本文件中。

  1. echo/sbin/mdev > /proc/sys/kernel/hotplug

内核有一种在发生热插拔事件后调用应用程序的方式,那就是执行/proc/sys/kernel/hotplug 文件中的程序,因为这种方式比较简单,所以常用在嵌入式系统之中。而之前说的udev使用的则是netlink机制。发生热插拔事件时,调用mdev程序会将热插拔信息放在环境变量和参数当中mdev程序利用这些信息就可以自动创建设备节点,在mdev的源码中也有清晰的体现:

  1. int mdev main(int arge UNUSED_PARAM,char **argv)
  2. {
  3. .....
  4. env_devname= getenv("DEVNAME");/*can be NULL*/
  5. G.subsystem = getenv("SUBSYSTEM");
  6. action=getenv("ACTION");
  7. env_devpath=getenv("DEVPATH");
  8. ......
  9. op= index_in_strings (keywords,action);
  10. .....
  11. snprintf(temp, PATH_MAX, "/sys%s", env_devpath);
  12. if (op==OP_remove){
  13. .....
  14. if (!fw)
  15. make_device(env_devname, temp, op);
  16. }
  17. else{
  18. make_device(env_devname, temp, op);
  19. if(ENABLE_FEATURE_MDEV_LOAD_FIRMWARE){
  20. if (op == OP_add && fw)
  21. load_firmware(fw, temp);
  22. }
  23. }

上面的代码的总体思路是根据ACTION键的值来决定op是增加还是移除操作,最终调用make_device来自动创建或删除设备节点

了解了应用层自动创建设备节点的方式后,接下来就需要讨论在驱动中如何实现了。

既然自动设备节点的创建要依靠热插拔事件和 sysfs文件系统,那这和我们之前讨论的kobject就是分不开的,mdev扫描/sys/class目录暗示我们要创建类,并且在类下面应该有具体的设备。

为此,内核提供了相应的API:

  1. class_create (owner, name)
  2. void class_destroy(struet class *cls);
  3. struct device *device_create(struct class *class,struct device *parent, dev_tdevt, void *drvdata, const char *fmt, ...);
  4. void device_destroy(struct class *class, dev_t devt):
  5. /*
  6. @ class_create:创建类,owner是所属的模块对象指针,name是类的名字,返回struct class对象指针
  7. -返回值通过IS_ERR宏来判断是否失败,通过PTR_ERR宏来获得错误码。
  8. @ class_destroy:销毁cls类。
  9. @ device_create:在类class下创建设备,parent是父设备,没有则为NULL。
  10. -devt是设备的主次设备号,drvdata是驱动数据,没有则为NULL。
  11. -fmt是格式化字符串,使用方法类似于printk。
  12. @ device_destroy:销毁class类下面主次设备号为devt的设备。返回值的检查方式同class_create。
  13. */

主要代码(例子)

添加了自动创建设备的驱动的主要代码如下:

  1. /* fsled.c */
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/module.h>
  5. #include <linux/fs.h>
  6. #include <linux/cdev.h>
  7. #include <linux/slab.h>
  8. #include <linux/ioctl.h>
  9. #include <linux/uaccess.h>
  10. #include <linux/io.h>
  11. #include <linux/ioport.h>
  12. #include <linux/platform_device.h>
  13. #include "fsled.h"
  14. #define FSLED_MAJOR 256
  15. #define FSLED_DEV_NAME "fsled"
  16. struct fsled_dev {
  17. unsigned int __iomem *con;
  18. unsigned int __iomem *dat;
  19. unsigned int pin;
  20. atomic_t available;
  21. struct cdev cdev;
  22. struct device *dev;
  23. };
  24. struct class *fsled_cls;
  25. static int fsled_open(struct inode *inode, struct file *filp)
  26. {
  27. struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);
  28. filp->private_data = fsled;
  29. if (atomic_dec_and_test(&fsled->available))
  30. return 0;
  31. else {
  32. atomic_inc(&fsled->available);
  33. return -EBUSY;
  34. }
  35. }
  36. static int fsled_release(struct inode *inode, struct file *filp)
  37. {
  38. struct fsled_dev *fsled = filp->private_data;
  39. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  40. atomic_inc(&fsled->available);
  41. return 0;
  42. }
  43. static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  44. {
  45. struct fsled_dev *fsled = filp->private_data;
  46. if (_IOC_TYPE(cmd) != FSLED_MAGIC)
  47. return -ENOTTY;
  48. switch (cmd) {
  49. case FSLED_ON:
  50. writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
  51. break;
  52. case FSLED_OFF:
  53. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  54. break;
  55. default:
  56. return -ENOTTY;
  57. }
  58. return 0;
  59. }
  60. static struct file_operations fsled_ops = {
  61. .owner = THIS_MODULE,
  62. .open = fsled_open,
  63. .release = fsled_release,
  64. .unlocked_ioctl = fsled_ioctl,
  65. };
  66. static int fsled_probe(struct platform_device *pdev)
  67. {
  68. int ret;
  69. dev_t dev;
  70. struct fsled_dev *fsled;
  71. struct resource *res;
  72. unsigned int pin = *(unsigned int*)pdev->dev.platform_data;
  73. dev = MKDEV(FSLED_MAJOR, pdev->id);
  74. ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
  75. if (ret)
  76. goto reg_err;
  77. fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
  78. if (!fsled) {
  79. ret = -ENOMEM;
  80. goto mem_err;
  81. }
  82. cdev_init(&fsled->cdev, &fsled_ops);
  83. fsled->cdev.owner = THIS_MODULE;
  84. ret = cdev_add(&fsled->cdev, dev, 1);
  85. if (ret)
  86. goto add_err;
  87. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  88. if (!res) {
  89. ret = -ENOENT;
  90. goto res_err;
  91. }
  92. fsled->con = ioremap(res->start, resource_size(res));
  93. if (!fsled->con) {
  94. ret = -EBUSY;
  95. goto map_err;
  96. }
  97. fsled->dat = fsled->con + 1;
  98. fsled->pin = pin;
  99. atomic_set(&fsled->available, 1);
  100. writel((readl(fsled->con) & ~(0xF << 4 * fsled->pin)) | (0x1 << 4 * fsled->pin), fsled->con);
  101. writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
  102. platform_set_drvdata(pdev, fsled);
  103. fsled->dev = device_create(fsled_cls, NULL, dev, NULL, "led%d", pdev->id);
  104. if (IS_ERR(fsled->dev)) {
  105. ret = PTR_ERR(fsled->dev);
  106. goto dev_err;
  107. }
  108. return 0;
  109. dev_err:
  110. iounmap(fsled->con);
  111. map_err:
  112. res_err:
  113. cdev_del(&fsled->cdev);
  114. add_err:
  115. kfree(fsled);
  116. mem_err:
  117. unregister_chrdev_region(dev, 1);
  118. reg_err:
  119. return ret;
  120. }
  121. static int fsled_remove(struct platform_device *pdev)
  122. {
  123. dev_t dev;
  124. struct fsled_dev *fsled = platform_get_drvdata(pdev);
  125. dev = MKDEV(FSLED_MAJOR, pdev->id);
  126. device_destroy(fsled_cls, dev);
  127. iounmap(fsled->con);
  128. cdev_del(&fsled->cdev);
  129. kfree(fsled);
  130. unregister_chrdev_region(dev, 1);
  131. return 0;
  132. }
  133. struct platform_driver fsled_drv = {
  134. .driver = {
  135. .name = "fsled",
  136. .owner = THIS_MODULE,
  137. },
  138. .probe = fsled_probe,
  139. .remove = fsled_remove,
  140. };
  141. static int __init fsled_init(void)
  142. {
  143. int ret;
  144. fsled_cls = class_create(THIS_MODULE, "fsled");
  145. if (IS_ERR(fsled_cls))
  146. return PTR_ERR(fsled_cls);
  147. ret = platform_driver_register(&fsled_drv);
  148. if (ret)
  149. class_destroy(fsled_cls);
  150. return ret;
  151. }
  152. static void __exit fsled_exit(void)
  153. {
  154. platform_driver_unregister(&fsled_drv);
  155. class_destroy(fsled_cls);
  156. }
  157. module_init(fsled_init);
  158. module_exit(fsled_exit);
  159. MODULE_LICENSE("GPL");
  160. MODULE_AUTHOR("Kevin Jiang <jiangxg@farsight.com.cn>");
  161. MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");

代码第177行使用class_create创建了名叫fsled的类。

  1. fsled_cls = class_create(THIS_MODULE, "fsled");

代码第127行使用device_createfsled类下面创建了led%d的设备,%d 用平台设备的id来替代

在创建过程中,内核会发送热插拔事件给mdev,mdev利用这些信息就可以创建设备节点,因为设备的名字和设备号都传递给了device_create,而内核又会利用这些参数生成热插拔信息

  1. static int fsled_probe(struct platform_device *pdev)
  2. {
  3. fsled->dev = device_create(fsled_cls, NULL, dev, NULL, "led%d", pdev->id);
  4. if (IS_ERR(fsled->dev)) {
  5. ret = PTR_ERR(fsled->dev);
  6. goto dev_err;
  7. }
  8. return 0;

使用上面的驱动且驱动加载成功后,设备节点就自动被创建了,不需要再手动创建,整个测试过程和前面的例子类似,这里就不再重复了。

相关文章