【Linux基础系列之】设备模型

xiaoxiao2021-02-28  10

  linux设备模型是linux比较基础的知识,虽然有很多资料很多书籍都会去讲设备模型,这里以我自己的方式清晰的描述下linux设备模型;

(一) kobject

  kobject表示linux设备模型的基本结构,Kobject是基本数据类型,每个Kobject都会在”/sys/“文件系统中以目录的形式出现。最初只是作为一个引用计数现在主要有如下几个方面的作用:

使用一个引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放;通过parent指针,可以将所有Kobject以层次结构的形式组合起来。和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间;当系统的硬件被热拔插的时候,在kobject子系统的控制下,将产生的事件通知用户空间;

struct kobject:

63 struct kobject { 64 const char *name; //该Kobject的名称,同时也是sysfs中的目录名称。由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。如果需要修改Kobject的名字,需要调用kobject_rename接口; 65 struct list_head entry; //用于将Kobject加入到Kset中的list_head。 66 struct kobject *parent; //指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构); 67 struct kset *kset; //该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(Kset是一个特殊的Kobject)。 68 struct kobj_type *ktype; //每个Kobject必须有一个ktype,否则Kernel会提示错误,Ktype中的release回调函数负责释放Kobject; 69 struct kernfs_node *sd; //该Kobject在sysfs中的表示; 70 struct kref kref; //一个可用于原子操作的引用计数;当该计数减为0时,自动释放; 71 #ifdef CONFIG_DEBUG_KOBJECT_RELEASE 72 struct delayed_work release; 73 #endif 74 unsigned int state_initialized:1; //Kobject是否已经初始化; 75 unsigned int state_in_sysfs:1; 76 unsigned int state_add_uevent_sent:1; 77 unsigned int state_remove_uevent_sent:1; //记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。 78 unsigned int uevent_suppress:1; //1则表示忽略所有上报的uevent事件; 79 };

如上图表示kobject,kset,ktype的关系,说明如下:

  (1) Kset是一个特殊的Kobject(因此它也会在”/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以是不同属性的)。成员list用于保存该kset下所有的kobject的链表;

  成员uevent_ops:该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的;

  (2) kobject一般内嵌在一些数据结构里面,例如kset、device、device_driver等等,每个都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构;

  其中release可以将包含该种类型kobject的数据结构的内存空间释放掉。sysfs_ops:该种类型的Kobject的sysfs文件系统接口。default_attrs:该种类型的Kobject的atrribute列表,也以文件的形式在sysfs体现,用户层面就可以通过都写来操作这写文件;

(1) attribute

  attibute,就是内核空间和用户空间进行信息交互的一种方法。例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的运行行为,那么就可以将该变量以sysfs attribute的形式开放出来。

attibute分为普通的attribute和bin attribute:

//struct attribute为普通的attribute,使用该attribute生成的sysfs文件,只能用字符串的形式读写 29 struct attribute { 30 const char *name; 31 umode_t mode; 32 #ifdef CONFIG_DEBUG_LOCK_ALLOC 33 bool ignore_lockdep:1; 34 struct lock_class_key *key; 35 struct lock_class_key skey; 36 #endif 37 }; //struct bin_attribute在struct attribute的基础上,增加了read、write等函数,因此它所生成的sysfs文件可以用任何方式读写。 121 struct bin_attribute { 122 struct attribute attr; 123 size_t size; 124 void *private; 125 ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *, 126 char *, loff_t, size_t); 127 ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *, 128 char *, loff_t, size_t); 129 int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr, 130 struct vm_area_struct *vma); 131 };

  在linux内核中,attibute文件的创建是由fs/sysfs/file.c中sysfs_create_file接口完成的;

  以device 添加attr写的一个模版:

void size_t test_show(struct device dev,struct device_attribute *attr, char *buf) { //test属性所记录的值写到buf里面,通常是用snprintf(); //实现对test的读操作; } void size_t test_store(struct device dev,struct device_attribute *attr, const char *buf,size_t count) { //实现对test的属性值写操作; } DEVICE_ATTR(test,S_IWUSR | S_IRUGO,test_show,test_store); device_create_file(device,&dev_attr_test);

  通过上面自己添加设备属性文件之后我们可以在/sys/devices/platform/或者/sys/devices/soc/目录下找到test属性文件,userspace就可以通过对这个文件进行都写操作,来实现于底层交互;

(2) uevent

  Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。有事件需要上报时,会触发Uevent提供的接口,用户空间程序收到这样的事件后,会做相应的处理。 前面知道每个kset含有uevent_ops来实现kobject的事件上报,上报的事件类型有:

53 enum kobject_action { 54 KOBJ_ADD, //添加 55 KOBJ_REMOVE, //移除 56 KOBJ_CHANGE, //状态发生改变 57 KOBJ_MOVE, //更改parent 58 KOBJ_ONLINE, //上线 59 KOBJ_OFFLINE, //掉线 60 KOBJ_MAX 61 }; //uevent_ops 131 struct kset_uevent_ops { 132 int (* const filter)(struct kset *kset, struct kobject *kobj);//当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,从而达到从整体上管理的目的。 133 const char *(* const name)(struct kset *kset, struct kobject *kobj);//该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uvent; 134 int (* const uevent)(struct kset *kset, struct kobject *kobj, 135 struct kobj_uevent_env *env);//当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加; 136 }; 123 struct kobj_uevent_env { 124 char *argv[3]; 125 char *envp[UEVENT_NUM_ENVP];//指针数组,用于保存每个环境变量的地址,最多可支持的环境变量数量为UEVENT_NUM_ENVP 126 int envp_idx; //用于访问环境变量指针数组的index。 127 char buf[UEVENT_BUFFER_SIZE]; //保存环境变量的buffer,最大为UEVENT_BUFFER_SIZE。 128 int buflen; //访问buf的变量 129 };

  kobject_uevent_env来完成真正的上报的动作,以envp为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。

  Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

  以komd为例,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper )处理该event。而该uevent helper的路径保存在uevent_helper数组中;

  usb 热插拔原理:当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。可参考文档:Linux USB 驱动开发(四)—— 热插拔那点事

(3) kobject 操作

kobject操作流程:

1. 定义一个struct kset类型的指针,并在初始化时为它分配空间,添加到内核中; 2. 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有Kobject; 3. 定义一个适合自己的ktype,并实现其中回调函数; 4. 在需要使用到包含Kobject的数据结构时,动态分配该数据结构,并分配Kobject空间,添加到内核中; 5. 每一次引用数据结构时,调用kobject_get接口增加引用计数;引用结束时,调用kobject_put接口,减少引用计数; 6. 当引用计数减少为0时,Kobject模块调用ktype所提供的release接口,释放上层数据结构以及Kobject的内存空间;

  初始化kobject:kobject_init(struct kobject *kobj, struct kobj_type *ktype) –> kobject_init_internal() : 初始化kobj内部的参数,包括引用计数、list、各种标志等;

  添加kobject到内存空间 : 调用kobject_add() -> kobject_add_internal() :

校验kobj以及kobj->name的合法性调用kobject_get增加该kobject的parent的引用计数,如果存在parent的话如果存在kset(即kobj->kset不为空),则调用kobj_kset_join接口加入kset。同时,如果该kobject没有parent,却存在kset,则将它的parent设为kset(kset是一个特殊的kobject),并增加kset的引用计数 通过create_dir接口,调用sysfs的相关接口,在sysfs下创建该kobject对应的目录 kobj_kset_join,负责将kobj加入到对应kset的链表中。

  kobject_create分配空间 : kobject_create() 或者 kobject_create_and_add();

  通过kobject_get和kobject_put可以修改kobject的引用计数,并在计数为0时,调用ktype的release接口,释放占用空间。

(二) bus

  总线bus:在linux设备模型当中,所有的设备都是通过总线连接起来,总线并不一定是真实存在的,也可以是虚拟总线,比如platform bus;这里就以通用的虚拟总线platform bus为例来描述总线,通过struct bus_type定义:

1010 struct bus_type platform_bus_type = { 1011 .name = "platform", 1012 .dev_groups = platform_dev_groups, 1013 .match = platform_match, 1014 .uevent = platform_uevent, 1015 .pm = &platform_dev_pm_ops, 1016 }; 1017 EXPORT_SYMBOL_GPL(platform_bus_type);

  platform bus 的注册:

1019 int __init platform_bus_init(void) 1020 { 1021 int error; 1022 1023 early_platform_cleanup(); 1024 1025 error = device_register(&platform_bus); 1026 if (error) 1027 return error; 1028 error = bus_register(&platform_bus_type); 1029 if (error) 1030 device_unregister(&platform_bus); 1031 return error; 1032 }

  early_platform_cleanup():一些设备需要在early阶段就启动比如一些serial,consoles设备,因为执行到这里的时候,证明系统已经完成了Early阶段的启动,转而进行正常的设备初始化、启动操作,所以这里清除注册在early_platform_device_list链表的设备;

  device_register(): platform bus本身也是一个设备,这里通过这个函数注册设备到device list,这个函数在后面device注册的时候在具体分析;

  bus_register()主要完成如下操作:

873 int bus_register(struct bus_type *bus) 874 { 875 int retval; 876 struct subsys_private *priv; 877 struct lock_class_key *key = &bus->lock_key; 878 879 priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); 880 if (!priv) 881 return -ENOMEM; 882 883 priv->bus = bus; 884 bus->p = priv; 885 886 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); 887 888 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //初始化priv->subsys.kobj,kset本身也是一个特殊的kobject; 889 if (retval) 890 goto out; 891 892 priv->subsys.kobj.kset = bus_kset;//该bus kobject 所属的kset; 893 priv->subsys.kobj.ktype = &bus_ktype; 894 priv->drivers_autoprobe = 1;//选择是否调用driver_attach进行probe; 895 896 retval = kset_register(&priv->subsys); 897 if (retval) 898 goto out; 899 900 retval = bus_create_file(bus, &bus_attr_uevent); //向bus目录下添加一个uevent attribute(如/sys/bus/platform/uevent) 901 if (retval) 902 goto bus_uevent_fail; 903 904 priv->devices_kset = kset_create_and_add("devices", NULL, 905 &priv->subsys.kobj); 906 if (!priv->devices_kset) { 907 retval = -ENOMEM; 908 goto bus_devices_fail; 909 } 911 priv->drivers_kset = kset_create_and_add("drivers", NULL, 912 &priv->subsys.kobj); 913 if (!priv->drivers_kset) { 914 retval = -ENOMEM; 915 goto bus_drivers_fail; 916 } 917 //interfaces 实现的是该bus的一些特殊借口,这里初始化这个接口链表; 918 INIT_LIST_HEAD(&priv->interfaces); 919 __mutex_init(&priv->mutex, "subsys mutex", key); //初始化该bus上的device和drvier list; 920 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); 921 klist_init(&priv->klist_drivers, NULL, NULL); 922 923 retval = add_probe_files(bus); 924 if (retval) 925 goto bus_probe_files_fail; 926 927 retval = bus_add_groups(bus, bus->bus_groups);//添加由bus_attrs指针定义的bus的默认attribute,如果有定义的话; 928 if (retval) 929 goto bus_groups_fail; 930 931 pr_debug("bus: '%s': registered\n", bus->name); 932 return 0; 933 948 } 949 EXPORT_SYMBOL_GPL(bus_register);

(1) 申请subsys_private空间,并赋值给注册的总线,subsys_private主要集合了一些bus模块需要使用的私有数据:设备驱动的kset,klist;

(2) 初始化改bus kobject,通过kset_register()把该bus注册到内核空间,并生成/sys/bus/platform目录;

(3) 通过kset_create_and_add 创建kset,分别生成/sys/bus/platform/device和/sys/bus/platform/driver目录;

(4) 通过add_probe_files()在/sys/bus/platform/下添加drivers_probe和drivers_autoprobe两个attribute,其中drivers_probe允许用户空间程序主动触发指定bus下的device_driver的probe动作,而drivers_autoprobe控制是否在device或device_driver添加到内核时,自动执行probe;

  添加了bus之后,就可以在sys目录下形成如下:

sys/--- bus/--- platform/--- devices/--- drivers/--- drivers_autoprobe drivers_probe uevent

  现在bus上还没有device,就需要通过bus_add_device()添加device:

506 int bus_add_device(struct device *dev) 507 { 508 struct bus_type *bus = bus_get(dev->bus); 509 int error = 0; 510 511 if (bus) { 512 pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev)); 513 error = device_add_attrs(bus, dev); 514 if (error) 515 goto out_put; 516 error = device_add_groups(dev, bus->dev_groups); 517 if (error) 518 goto out_id; 519 error = sysfs_create_link(&bus->p->devices_kset->kobj, 520 &dev->kobj, dev_name(dev)); 521 if (error) 522 goto out_groups; 523 error = sysfs_create_link(&dev->kobj, 524 &dev->bus->p->subsys.kobj, "subsystem"); //在该设备的sysfs目录中创建一个指向该设备所在bus目录的链接,取名为subsystem; 525 if (error) 526 goto out_subsys; 527 klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); 528 } 529 return 0; }

  bus_add_device()主要完成如下操作:

(1) device_add_attrs()添加该bus的dev_attrs属性,如果有定义;device_add_groups()添加该bus的dev_groups属性组:

782 static struct attribute *platform_dev_attrs[] = { 783 &dev_attr_modalias.attr, 784 &dev_attr_driver_override.attr, 785 NULL, 786 }; 787 ATTRIBUTE_GROUPS(platform_dev);

(2) 调用sysfs_create_link接口,将该device在sysfs中的目录,链接到该bus的devices目录下;

/sys # ls bus/platform/devices/led@0 -l lrwxrwxrwx 1 root root 0 2015-01-05 16:43 bus/platform/devices/dev_xx -> ../../../devices/dev_xx

(3) 通过调用klist_add_tail把该设备指针保存在bus->priv->klist_devices中;

现在已经有了bus,有了device,剩下的driver就通过bus_add_driver来添加:

666 int bus_add_driver(struct device_driver *drv) 667 { 668 struct bus_type *bus; 669 struct driver_private *priv; 670 int error = 0; 671 672 bus = bus_get(drv->bus); 673 if (!bus) 674 return -EINVAL; 675 676 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); 677 678 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 679 if (!priv) { 680 error = -ENOMEM; 681 goto out_put_bus; 682 } 683 klist_init(&priv->klist_devices, NULL, NULL); 684 priv->driver = drv; 685 drv->p = priv; 686 priv->kobj.kset = bus->p->drivers_kset; 687 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, 688 "%s", drv->name); 689 if (error) 690 goto out_unregister; 691 692 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //将该driver保存在bus的klist_drivers链表中,并根据drivers_autoprobe的值,选择是否调用driver_attach进行probe 693 if (drv->bus->p->drivers_autoprobe) { 694 error = driver_attach(drv); 695 if (error) 696 goto out_unregister; 697 } 698 module_add_driver(drv->owner, drv); 699 700 error = driver_create_file(drv, &driver_attr_uevent);//创建uevent attribute 701 if (error) { 702 printk(KERN_ERR "%s: uevent attr (%s) failed\n", 703 __func__, drv->name); 704 } 705 error = driver_add_groups(drv, bus->drv_groups); 706 if (error) { 708 printk(KERN_ERR "%s: driver_create_groups(%s) failed\n", 709 __func__, drv->name); 710 } 711 712 if (!drv->suppress_bind_attrs) { //同时根据suppress_bind_attrs标志,决定是否在sysfs的该driver的目录下,创建bind和unbind attribute; 713 error = add_bind_files(drv); 714 if (error) { 715 /* Ditto */ 716 printk(KERN_ERR "%s: add_bind_files(%s) failed\n", 717 __func__, drv->name); 718 } 719 } 720 721 return 0;

这个函数主要做的操作有:

(1) 该driver的struct driver_private指针(priv)分配空间,并初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等变量;将driver的kset(priv->kobj.kset)设置为bus的drivers kset(bus->p->drivers_kset),这就意味着所有driver的kobject都位于bus->p->drivers_kset之下(寄/sys/bus/xxx/drivers目录下);

(2) 因为每个驱动可以兼容多个设备,所以这里有klist_devices来表示兼容的设备列表;

(3)该driver保存在bus的klist_drivers链表中,并根据drivers_autoprobe的值,选择是否调用driver_attach进行probe ; driver_attach() –>bus_for_each_dev(..(*fn)()) –>while遍历device列表执行__driver_attach() : driver_match_device()–>调用paform bus的 mach函数 platform_match 判断名字是否匹配: 当device和driver匹配完之后就开始 driver_probe_device–> really_probe() –> drv->probe即调用了驱动的probe 函数;

(4) 创建attribute文件,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制;

添加了device和驱动之后,这样在bus目录下:

sys/--- bus/--- platform/--- devices/--- xx_dev --> ../../../devices/soc/xx_dev drivers/--- xx_drv/--- bind uevent unbind drivers_autoprobe drivers_probe uevent

(三) device and driver

  在linux系统中通过device来表示一个设备的属性,这个设备具有什么功能,通过driver来实现其作用,并能够到给上一层操作使用;前面的bus章节已经讲到将device和driver添加到bus上面;

上图描述device ,driver ,class ,bus的关系,说明如下:

  (1) 这几个结构都有一个privte结构,bus的privte结构主要包含了devie和driver的kset,对应生成sys/bus/platform/devices和sys/bus/platform/drivers目录,并且通过device/driver list把所有device和driver串联起来;device_private的klist_children保存了子设备链表;device_driver的klist_devices保存了该driver所支持的deivce列表;

  (2) 内核要保证在driver运行前,设备所依赖的总线能够正确初始化;在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备; device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

  (3) device的成员dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用;

  (4) device的成员devres_head保存了设备资源的链表;系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,通常在driver probe的时候完成这写资源的分配;

16 struct devres_node { 17 struct list_head entry; 18 dr_release_t release; 19 #ifdef CONFIG_DEBUG_DEVRES 20 const char *name; 21 size_t size; 22 #endif 23 }; 25 struct devres { 26 struct devres_node node; 27 /* -- 3 pointers */ 28 unsigned long long data[]; /* guarantee ull alignment */ 29 };

下面来描述设备驱动注册的过程:

22 struct platform_device { 23 const char *name; //设备的名称,和struct device结构中的init_name; 24 int id; //用于标识该设备的ID; 25 bool id_auto; //指示在注册设备时,是否自动赋予ID值; 26 struct device dev; //真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现 27 u32 num_resources; 28 struct resource *resource; 29 30 const struct platform_device_id *id_entry; 31 char *driver_override; /* Driver name to force a match */ 32 33 /* MFD cell pointer */ 34 struct mfd_cell *mfd_cell; 35 36 /* arch specific additions */ 37 struct pdev_archdata archdata; //保存一些architecture相关的数据 38 }; 174 struct platform_driver { 175 int (*probe)(struct platform_device *); 176 int (*remove)(struct platform_device *); 177 void (*shutdown)(struct platform_device *); 178 int (*suspend)(struct platform_device *, pm_message_t state); 179 int (*resume)(struct platform_device *); 180 struct device_driver driver; 181 const struct platform_device_id *id_table; //match table; 182 bool prevent_deferred_probe; 183 };

设备注册:platform_device_register():

428 int platform_device_register(struct platform_device *pdev) 429 { 430 int ret; 434 436 device_initialize(&pdev->dev); 437 arch_setup_pdev_archdata(pdev); 438 ret = platform_device_add(pdev); 440 bootprof_pdev_register(ts, pdev); 441 return ret; 442 }

device_initialize():初始化该device的kobject,kset指向devices_kset,初始化devres_head列表,numa_node设置为-1;

arch_setup_pdev_archdata()设置arch 相关data;

重点实现platform_device_add() :(a)指定bus,当然这里是&platform_bus_type; 根据device instance id来设置kobject name,这个id通常设为0;(b) 循环resource列表,分为IO类型和MEM类型,插入到全局ioport_resource,iomem_resource列表中;(c)device_add() 来实现真正的添加:初始化device_private, 添加kobject,创建device file,最后通过bus_add_device添加到bus上;

驱动注册:platform_driver_register() -> __platform_driver_register() :

578 int __platform_driver_register(struct platform_driver *drv,struct module *owner) 580 { 581 drv->driver.owner = owner; 582 drv->driver.bus = &platform_bus_type; 583 if (drv->probe) 584 drv->driver.probe = platform_drv_probe; 585 if (drv->remove) 586 drv->driver.remove = platform_drv_remove; 587 if (drv->shutdown) 588 drv->driver.shutdown = platform_drv_shutdown; 589 590 return driver_register(&drv->driver); 591 } 指定driver的回调函数,如果没有,就用platform 默认的;driver_register():driver_find()先查找驱动是否已经注册,bus_add_driver() 添加到bus上,前面已经有分析;kobject_uevent(&drv->p->kobj, KOBJ_ADD),最后调用uevent_ops->uevent()来通知userspace driver添加成功;

下面一个platform设备驱动模版:

const struct file_operations cdev_test_fops = { .owner = THIS_MODULE, .open = cdev_Open, .release = cdev_Release, .unlocked_ioctl = cdev_Ioctl }; struct platform_driver test_driver { .probe = test_probe, .remove = test_remove, .driver = { .name = "test", .owner = THIS_MODULE, } } struct platform_device test_device { .name = "test", .id = 0, .dev = {} } static dev_t test_dev_num; static struct cdev * test_cdev; static int __init test_init(void) { struct device * testdevice = NULL; struct class * test_class = NULL; alloc_chrdev_region(&test_dev_num,0,1,"test"); test_cdev = cdev_alloc(); cdev_init(test_cdev, &cdev_test_fops); test_cdev->owner = THIS_MODULE; test_class = class_create(THIS_MODULE, "test"); device_create(test_class, NULL, test_dev_num, NULL, "test"); platform_driver_register(&test_driver); platform_device_register(&test_device); return 0; }

上面的模版,我们就可以对/dev/test设备文件直接操作,可以open,releas,也可以通过添加cmd,通过ioctl来实现更多IO操作;

转载请注明原文地址: https://www.6miu.com/read-750125.html

最新回复(0)