Linux驱动(七)设备模型介绍以及platform设备驱动

姓名:谢焕彬 学号:19020100303
前面讲过了字符驱动,我们把过程再来回顾一下,我们是如何来完成一个驱动的。

1、设备号相关问题,手动或自动创建设备号。

2、设备对象相关问题,完成驱动操作方法集合,并向内核注册该设备对象。

3、生成设备节点

这其中有一个最大的问题:设备和驱动高度耦合,设备修改后,驱动也需要修改,牵一发而动全身。这为后续的驱动开发造成了很不好的影响。我们应该做到的是,高内聚低耦合。设备修改后只修改相应的设备文件,驱动需要修改就只修改驱动部分。那驱动和设备如何联系起来的呢?这就又需要要给文件来配合,总线。这就是Linux2.6以后的设备驱动模型:总线、设备、驱动。

  那三者是如何分工的呢?

  设备:描述一个设备的详细信息。例如一个串口,那应该体现该串口设备的相关信息,例如串口的名称,设备的地址,设备的中断号等等。

  驱动:描述如何使用设备的方法,这个好说,其实还是一堆方法嘛。为用户提供操作设备的方法。

  总线:总得有一个东西把设备和驱动联系起来把,那就是总线。总线中有一个匹配函数,系统每注册一个设备,就去找一下系统中与之匹配的驱动。同样,注册驱动的时候,也要去系统中寻找一个与之匹配的设备。这样,设备和驱动就联系到一起了。

  系统中有很多总线,I2C总线,SPI总线,USB总线。但有很多设备没有总线实体,例如LED灯,这算什么总线?Linux添加了一种虚拟总线,叫做平台总线---platform。

我们再来了解下一个问题,linux设备模型的对象化。c语言是面向过程的语言,很多高级语言c++,java等都是面向对象的过程。c语言同样可以实现对象化的过程。Linux的设备模型就向我们很好的展示了这个过程。我们就以platform设备模型为例来看一看。

   在c语言如何实现一个类呢?用的就是结构体。与类相比,结构体不能定义方法,但我们可以用函数指针来作为方法。文件操作集合struct file_operations就是一个典型的例子。继承如何实现呢?很简单,讲父类(父结构体)包含就ok了,父类(父结构体)中的变量或者方法就能用了。

   linux设备驱动模型中的根类是kobject,这是linux中所有驱动模型的基类,我们刚才讲的总线、设备、驱动都是继承

struct device { //设备的基类
struct device *parent;

struct device_private   *p;

struct kobject kobj;         //++++++++++++++++++++++继承自kobject
const char      *init_name; /* initial name of the device */
const struct device_type *type;

struct mutex        mutex;  /* mutex to synchronize calls to
                 * its driver.
                 */

struct bus_type *bus;       /* type of bus device is on */
struct device_driver *driver;   /* which driver has allocated this
                   device */
void        *platform_data; /* Platform specific data, device
                   core doesn't touch it */
struct dev_pm_info  power;
struct dev_power_domain *pwr_domain;

...........}
struct device_driver {//设备驱动基类结构体
const char *name;
struct bus_type *bus;

struct module       *owner;
const char      *mod_name;  /* used for built-in modules */

bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */

const struct of_device_id   *of_match_table;

int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;//这个里面包含object

};
struct driver_private {
struct kobject kobj; //父类
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
总线基类结构体
struct bus_type {
const char *name;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct subsys_private *p;//这个里面继续++++++++++++++=

};
struct subsys_private {
struct kset subsys; //kset是object基类的集合
struct kset *devices_kset;

struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;

struct list_head class_interfaces;
struct kset glue_dirs;
struct mutex class_mutex;
struct class *class;

};
int (*match)(struct device *dev, struct device_driver *drv);总线中的match方法就是用来匹配驱动和设备的。
struct bus_type、struct device 、struct device_driver 分别是总线、设备、驱动的基类结构体。具体的总线、设备、驱动都继承自这里。
继续,来看看我们的平台设备platform相关的类。

1、platform总线

platform总线是bus_type的一个对象实例。

struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};

借此我们来看一下,platform是如何匹配的驱动和设备的呢?

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
    return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
    return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);

}
匹配方法有三种:1、基于设备树风格的匹配。2、基于id_table表的匹配 3、基于设备name和驱动name的匹配。设备树的方式我们先不讲,我们介绍一下后后两种方式。请继续向下看。
2、platform设备

struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;

const struct platform_device_id *id_entry;

/* MFD cell pointer */
struct mfd_cell *mfd_cell;

/* arch specific additions */
struct pdev_archdata    archdata;

};
name:设备名称,可以用来与驱动名称进行匹配,match函数中的name匹配方法就是比对这里的name
id: 设备id,现在我们一般直接设置成-1,不用它了。

dev: 设备父类

num_resources:资源数量

resource:资源结构体。这是设备最重要的东西。

id_entry:也是用来匹配,match函数中id_table匹配的方法。

resource是设备中最重要的东西。驱动要操作设备,首先需要知道什么呢?当然是设备地址了,如果有中断还需要知道中断号,这就是resource的作用。

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start:资源起始(起始地址或中断号起始值)
end:资源结束(结束地址或中断号结束值)

name:名称

flags:资源标志定义,表征是什么类型的资源,最常用的有以下几个:

      IORESOURCE_MEM:内存资源,也包括IO内存,

     IORESOURCE_IRQ:中断资源

      IORESOURCE_DMA :DMA通道资源

3、platform驱动

struct platform_driver {
int (*probe)(struct platform_device );
int (
remove)(struct platform_device );
void (
shutdown)(struct platform_device );
int (
suspend)(struct platform_device , pm_message_t state);
int (
resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
platform驱动中包含多个方法、一个设备基类、一个id_table表
probe:这是匹配上之后要执行的函数。

remove:所驱动的平台设备被移除时或平台驱动注销时所用

driver:基类,driver中的name成员可用来匹配设备,match匹配函数的第三种方法

id_table:可以驱动的平台设备ID列表。match匹配函数的第二种方法。主要设置name即可。
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data
attribute((aligned(sizeof(kernel_ulong_t))));
};
好,相关的类介绍完毕,现在来看看相关的API函数。总线相关的工作一般情况下内核已经给我们做好了,我们只需要做设备和驱动相关的工作。
1、设备注册与注销

int platform_add_devices(struct platform_device **devs, int num)
int platform_device_register(struct platform_device *pdev)
第一个函数可以注册多个平台设备,第二个函数只注册一个设备,而实际上platform_add_devices内部时多次调用platform_device_register函数的。
devs是platform_device结构体二级指针

num是要注册的个数

pdev是platform_device结构体指针

void platform_device_unregister(struct platform_device *pdev)
设备注销函数。
2、驱动的注册与注销

int platform_driver_register(struct platform_driver *drv)
驱动的注册

void platform_driver_unregister(struct platform_driver *drv)
驱动的注销
3、设备资源获取

我们刚才说到,设备中类中有一个成员变量是resource,是代表设备的硬件信息。驱动要操作设备就必须获取硬件信息,linux为我们提供了这样的API函数。

struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
功能:获取设备资源
参数:dev:设备对象

     type:要获取的资源类型,和resource中的flags对应。

               IORESOURCE_MEM:内存资源,也包括IO内存,

                 IORESOURCE_IRQ:中断资源

                 IORESOURCE_DMA :DMA通道资源

  num:第几个资源

返回值:成功返回获取的资源结构体地址。

有人会不会有这样的疑问,驱动不是分为字符设备、块设备、网络设备吗?platform设备又是什么?这里不要把两种搞混淆了,驱动仍是只有字符设备、块设备和网络设备三类。平台设备只是为了符合linux的驱动模型而产生的,除了平台设备模型,还有I2C设备模型、SPI设备模型。都是为了符合驱动模型而创建的。但i2c设备和spi设备仍然是字符设备。

看个例程把,先来简单熟悉以下过程

设备,资源中我们假定一个寄存器地址

include <linux/init.h>

include <linux/module.h>

include <linux/kernel.h>

include <linux/platform_device.h>

include <linux/ioport.h>

MODULE_LICENSE("GPL");
void demo_release(struct device *dev)
{
printk("%s,%d\n", func, LINE);
}
struct resource res = {
.start = 0x10004000,
.end = 0x10004007,
.flags = IORESOURCE_MEM,
};

struct platform_device virtual_dev = {
.name = "vir_dev", //名称和驱动中匹配。
.id = -1,
.dev = {
.release = demo_release,
},
.num_resources = 1,
.resource = &res,
};

static int __init demo_device_init(void)
{
platform_device_register(&virtual_dev);
printk(KERN_INFO"%s,%d\n",func,LINE);
return 0;
}

static void __exit demo_device_exit(void)
{
platform_device_unregister(&virtual_dev);
printk(KERN_INFO"%s,%d\n",func,LINE);
}

module_init(demo_device_init);
module_exit(demo_device_exit);
驱动中,获得资源,并打印开始和结束地址

include <linux/init.h>

include <linux/module.h>

include <linux/kernel.h>

include <linux/platform_device.h>

include <linux/errno.h>

include <linux/ioport.h>

MODULE_LICENSE("GPL");
struct resource *res0;
static int virtual_probe(struct platform_device *pdev)
{
printk(KERN_INFO"%s,%d\n",func,LINE);
res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
printk(KERN_INFO"startaddr:%x,endaddr:%x\n",(unsigned int)res0->start,(unsigned int)res0->end);
return 0;
}

static int virtual_remove(struct platform_device *pdev)
{
printk(KERN_INFO"%s,%d\n",func,LINE);
return 0;
}
struct platform_driver virtual_driver = {
.probe = virtual_probe,
.remove = virtual_remove,
.driver = {
.name = "vir_dev", //名称和设备中保持一致
.owner = THIS_MODULE,
}
};
module_platform_driver(virtual_driver);
最后一行是模块入口函数和出口函数的简写方式,这一行就可以代替之前的一堆,这是因为,之前初始化的工作我们将都会放到probe函数中,所以xxx_init()函数除了作为入口函数以及注册驱动外也就没什么作用了,所以才有此简写方式。
加载两个模块执行结果

注册设备号,注册字符设备等工作我们放到probe函数中去操作。其余部分和我们之前介绍的字符设备驱动都一样了。基本的框架就是这样,下一节我们来看看如何在实际的开发板中利用platform设备点亮LED灯
————————————————
版权声明:本文为CSDN博主「念念有余」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012142460/article/details/79125461

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容