前言
现在来聊点原理性的东西——linux设备管理模型
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
行文目录
- 前言
- 1. LDM数据结构
- 1.1 总线、设备、驱动关系
- 1.1.1 类
- 1.1.2 设备
- 1.1.3 驱动
- 2. kobject sysfs
- 2.1 kobjects
- 2.2 ktype
- 2.3 kset
- 参考资料
1. LDM数据结构
linux
设备模型LDM
的上层依赖于总线、设备驱动程序、设备和类。
1.1 总线、设备、驱动关系
在LINUX驱动的世界里,所有的设备和驱动都是挂在总线上的,也就是总线来管理设备和驱动的,总线知道挂在它上边的所有驱动和设备的情况,由总线完成驱动和设备的匹配和探测。
总线上挂着驱动和设备,一个驱动可以管理多个设备,一个设备保存一个对应驱动的信息,一般在初始化的时候,总线先初始化,然后设备先注册,最后驱动去找设备,完成他们之间的衔接。
系统已经给我们准备好了我们所学要的总线。对于我们来说,就是去学好怎么在系统中添加设备以及相关的驱动就行了。
1.1.1 类
相关结构体:struct class
和 struct class_device
类发明来就是来管理设备的,是对设备的高级抽象,本质也是一个结构体,但是按照类的思想来组织成员的。运用class,可以让用户空间的程序根据自己要处理的事情来调用设备,而不是根据设备被接入到系统的方式或设备的工作原来调用。
一个struct class
结构体类型变量对应一个类,内核提供了class_create()
函数,可以用它来创建一个类,这个类存放于 sysfs 下面, 一旦创建了类,再调用 device_create() 函数在 /dev 目录下创建相应的设备节点。
1.1.2 设备
驱动中常写的struct device
是硬件设备在内核驱动框架中的抽象
使用device_register
函数向内核驱动框架注册一个设备,也可使用device_create
来创建,device_create
函数是对device_register
的封装。
通常device
不会单独使用,而是被包含在一个具体的设备结构体中
使用案例如下所示:
struct gpioled_dev {dev_t devid;struct cdev cdev;struct class *class; // 定义一个classstruct device *device; // 定义一个deviceint major;int minor;struct device_node *nd;int led_gpio;
};static int __init led_init(void)
{......newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); // 先创建classif (IS_ERR(newchrled.class)) {return PTR_ERR(newchrled.class);}newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); // 再用class创建deviceif (IS_ERR(newchrled.device)) {return PTR_ERR(newchrled.device);}......
}static void __exit led_exit(void)
{......device_destroy(newchrled.class, newchrled.devid); // 先释放deviceclass_destroy(newchrled.class); // 再释放class
}
1.1.3 驱动
struct device_driver
是驱动程序在内核驱动框架中的抽象
关键元素1:name
,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
关键元素2:probe
,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理
使用方式:
/** @description: 驱动程序的探测函数,检测设备是否被该驱动所管理,当驱动与设备匹配后此函数会执行* @param-dev : platform设备* @return : 0,成功;其他负值,失败*/
static int led_probe(struct platform_device *dev)
{int i = 0;int ressize[5];u32 val = 0;struct resource *ledsource[5];printk("led driver and device has matched!\r\n");for (i = 0; i < 5; i++) {ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);if (!ledsource[i]) {dev_err(&dev->dev, "No MEM resource for always on\n");return -ENXIO;}ressize[i] = resource_size(ledsource[i]);}IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);SW_MUX_GPIO1_IO3 = ioremap(ledsource[1]->start, ressize[1]);SW_PAD_GPIO1_IO3 = ioremap(ledsource[2]->start, ressize[2]);GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);val |= (3 << 26);writel(val, IMX6U_CCM_CCGR1);writel(5, SW_MUX_GPIO1_IO3);writel(0x10b0, SW_PAD_GPIO1_IO3);val = readl(GPIO1_GDIR);val &= ~(1 << 3);val |= (1 << 3);writel(val, GPIO1_GDIR);val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);if (leddev.major) {leddev.devid = MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else {alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);leddev.major = MAJOR(leddev.devid);leddev.minor = MINOR(leddev.devid);}leddev.cdev.owner = THIS_MODULE;cdev_init(&leddev.cdev, &led_fops);cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)){return PTR_ERR(leddev.class);}leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}return 0;
}/** @description: remove函数,移除platform驱动的时候此函数会执行* @param-dev : platfrom 设备* @return : 0,成功;其他负值,失败*/
static int led_remove(struct platform_device *dev)
{iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO3);iounmap(SW_PAD_GPIO1_IO3);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);cdev_del(&leddev.cdev);unregister_chrdev_region(leddev.devid, LEDDEV_CNT);device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led2",},.probe = led_probe,.remove = led_remove
};
2. kobject sysfs
2.1 kobjects
设备模型的核心部分是kobjects
,类似于java中object对象类,提供了诸如计数、名称、父指针等字段,可以创建对象的层次结构。其定义如下:
struct kobject {char * k_name; // 指向kobject名称,如果名称长度小于KOBJ_NAME_LEN,则存入name数组中,如果超过,则动态分配一个缓冲区存放,KOBJ_NAME_LEN是20个字节char name[KOBJ_NAME_LEN];struct kref kref; // 实现kobject的引用计数struct list_head entry;struct kobject * parent; // 父对象,在内核中构造一个对象层次结构,并可以将多个对象间的关系表现出来struct kset * kset;struct kobj_type * ktype;struct dentry * dentry; // 指向dentry结构体,在sysfs中该结构体就表示这个kobject
};
kobject通常是嵌入到其他结构体中的,其单独意义其实并不大。当kobject被嵌入到其他结构体中时,该结构体便拥有了kobject提供的标准功能。
2.2 ktype
ktype是为了描述一族kobject所具有的普遍特性。因此,不在需要每个kobject都分别定义自己的特性,而是将这些特性在ktype结构体中一次定义,然后所有“同类”的kobject都能共享一样的特性。
struct kobj_type {void (*release)(struct kobject *);struct sysfs_ops * sysfs_ops;struct attribute ** default_attrs;
};
release指针指向在kobject引用计数减为零时要被调用的析构函数。该函数负责释放所有的kobject使用的内存和其他相关清理工作。
sysfs_ops变量指向sysfs_ops结构体。该结构体表述了sysfs文件读写是的特性。
default_attrs指向一个attribute结构体数组。这些结构体定义了该kobject相关的默认属性。属性描述了给定对象的特征,如果该kobject被导出到sysfs中,那么这些属性都将相应地作为文件而导出。数组中的最后一项必须为NULL。
2.3 kset
kset是kobject对象的集合体。把它看成是一个容器,可将所有相关的kobject对象,比如“全部的块设备”置于一个位置。kset把kobject集中到一个集合中。
struct kset {struct subsystem * subsys;struct kobj_type * ktype;struct list_head list;struct kobject kobj;struct kset_hotplug_ops * hotplug_ops;
};
其中ktype
指向kset
集合中kobject
对象的类型。
list
连接该集合中所有的kobject
对象。
kobj
指向的kobject
对象代表了该集合的基类。
hotplug_ops
指向一个用于处理集合中kobject
对象的热插拔操作的结构体。
subsys
指针指向该结构体相关的struct subsystem
结构体。
参考资料
[1] linux设备模型
[2] Linux设备模型
[3] Linux设备驱动模型
[4] device_create()、device_register()、deivce_add()区别
[5] Linux内核设计与实现—kobject sysfs