从串口驱动的移植看linux2.6内核中的驱动模型 platform device & platform driver【转】...-程序员宅基地

技术标签: 数据结构与算法  

转自:http://blog.csdn.net/bonnshore/article/details/7979705

写在前面的话:

博主新开了个人站点:你也可以在这里看到这篇文章,点击打开链接

本文是博主学习linux驱动移植整整两周后通过查阅资料并结合自己的一些观察所做的一些记录,旨在作为日后温习材料,由于博主尚无太多经验文中内可能会出现一些谬误,希望看到的热心朋友能拍砖指正。

在我前面的日中已经提到了我所做的SC16C550的串口移植,本来是没有什么技术难度,但对于新人来讲了解内核代码的结构和移植的原理是首要的,于是在工作中就做了一些记录。

//gap//

Linux-2.6 16c550 串口驱动移植一文中的static struct platform_device sc16550_device结构体在配置好以后,使用了linux内核模型的platform总线机制中设备注册接口函数:platform_device_register(&sc16550_device);将 sc16550_device 设备挂载到了platform bus上。上文已经提到驱动所使用的正是 8250来进行驱动,所以在8250.c驱动init时,调用的platform_driver_register(&serial8250_isa_driver);函数正是加载该驱动到platform bus上,

 

下面是关于sc16550_device的重要的结构体的配置具体情况:

static struct plat_serial8250_port sc16550_data[] = {
.mapbase  = sc16550_UART_BASE, //flags使用IOREMAP,8250驱动会自动映射mapbase
.irq = sc16550_UART_IRQ,
.uartclk  = sc16550_UART_BAUD * 16, 
.iotype  = UPIO_MEM, 
.flags  = UPF_BOOT_AUTOCONF | 
                          UPF_SKIP_TEST | UPF_IOREMAP,
        .regshift       = 0,
};
static struct platform_device sc16550_device = {
.name = "serial8250",
.id = PLAT8250_DEV_PLATFORM,
.dev = { .platform_data = sc16550_data, },
};

 

而serial8250_isa_driver结构体的定义为:

static struct platform_driver serial8250_isa_driver = {
.probe = serial8250_probe,
.remove = __devexit_p(serial8250_remove),
.suspend = serial8250_suspend,
.resume = serial8250_resume,
.driver = {
.name = "serial8250",
.owner = THIS_MODULE,
},
};

那么在sc16550_device设备和serial8250_isa_driver驱动具体是怎样加载到总线中,而两者之间又是如何匹配相认的呢?从而团结一心一致对外的呢?

今天我们就一起深究一下吧……

当设备挂接到总线上时,与总线上的所有驱动进行匹配(用bus_type.match进行匹配),如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备,挂接到总线上如果匹配失败,则只是将该设备挂接到总线上。

驱动挂接到总线上时,与总线上的所有设备进行匹配(用bus_type.match进行匹配), 如果匹配成功,则调用bus_type.probe或者driver.probe初始化该设备;挂接到总线上如果匹配失败,则只是将该驱动挂接到总线上。

那么现在我们无法判断到底我们的sc16550_device和serial8250_isa_driver是哪一个先挂载到总线上的。不过这也并不影响我们理解剖析整个linux设备模型。

因为实际上platform_bus_type总线先被kenrel注册,有必要对platform_bus_type 的定义作一番注释,其 定义如下: 
struct bus_type platform_bus_type = { 
       .name         = "platform",       // bus 的名字,将会生成/sys/bus/platform  目录 

       /* 该属性文件将产生在所有 platform_bus_type 类型的设备目录下,文件名为"modalias” */ 
       .dev_attrs    = platform_dev_attrs,   
       .match        = platform_match,   // 用于drive 与device 匹配的例程 
       .uevent       = platform_uevent,  //  用于输出环境变量,与属性文件“uevent”相关 
       .pm           = PLATFORM_PM_OPS_PTR, //  电源管理方面 
}; 

代码中, 通过bus_register(&platform_bus_type)将platform_bus_type 注册到总线模块。

我们继续,系统初始化过程中调用platform_add_devices或者platform_device_register,将平台设备(platform devices)注册到平台总线中(platform_bus_type),平台驱动(platform driver)与平台设备(platform device)的关联是在platform device或者driver_register中实现,一般这个函数在驱动的初始化过程调用。通过这三步,就将平台总线,设备,驱动关联起来。

下面我们就具体的来看看这整个过程吧。

首先,我们先来看看platform_device_register……

我们知道在系统boot up的时候,系统初始化会调用platform_device_register(),而其又先后调用了 device_initialize()和platform_device_add()。下面解析device_initialize()和platform_device_add()两个例程,它们分别定义在drivers/ base/core.c 和drivers/base/platform.c 中。

device_initialize(  的代码如下: 
void device_initialize(struct device *dev) 

       dev->kobj.kset = devices_kset;    // 设置其指向的kset 容器 


       kobject_init(&dev->kobj, &device_ktype); // 初始化 kobj,将 device_ktype 传递给它 


       klist_init(&dev->klist_children, klist_children_get, 


                 klist_children_put);    // 初试化klist 


       INIT_LIST_HEAD(&dev->dma_pools); 


       init_MUTEX(&dev->sem); 


       spin_lock_init(&dev->devres_lock); 


       INIT_LIST_HEAD(&dev->devres_head); 


       device_init_wakeup(dev, 0); 


       device_pm_init(dev);       // 初试化电源管理 


       set_dev_node(dev, -1); 
}

代码中:
     1. devices_kset 是所有dev 的kset,也就是所有dev 都被链接在该kset 下,其在初试化例程 devices_init()中通过调kset_create_and_add("devices", &device_uevent_ops, NULL)来创建。由于参数parent=NULL ,所以生成/sys/devices  目录。这里说明下kobj,kset 结构体中包含有一个 kobj,一个kobj 生成一个目录,在这里就是”devices " 目录,通过调用kobject_add_internal()例程 生成。所以从dev->kobj.kset = devices_kset 可以看出,该dev.kobj 添加到了devices_kset 容器 中,所的kobj 都归属于一个特定的kset 。关于kset,kobj,ktype,kref 的关系可以参考书LDD3 
的第十四章,在第370 页有一张说明kobj 和kset 关系的图(英文版)。 
     2. kobject_init(&dev->kobj, &device_ktype)用于初始化 dev->kobj 中变量的参数,如ktype、 kref、entry 和state*等。初试化例程devices_init()还会调用kobject_create_and_add()例程生成/sys/ dev、/sys/dev/block 和/sys/dev/char  目录。 
     3. 其他初始化。 

下面分析platform_device_add:

int platform_device_add(struct platform_device *pdev)

 

  {

  int i, ret = 0;

  if (!pdev)

  return -EINVAL;

  if (!pdev->dev.parent)

  pdev->dev.parent = &platform_bus;

  //可以看出,platform设备的父设备一般都是platform_bus,所以注册后的platform设备都出现在/sys/devices/platform_bus下

  pdev->dev.bus = &platform_bus_type;

  //挂到platform总线上

  if (pdev->id != -1)

  dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);

  else

  dev_set_name(&pdev->dev, "%s", pdev->name);

  //设置设备名字,这个名字与/sys/devices/platform_bus下的名字对应

  for (i = 0; i < pdev->num_resources; i++) { //下面操作设备所占用的系统资源

  struct resource *p, *r = &pdev->resource[i];

  if (r->name == NULL)

  r->name = dev_name(&pdev->dev);

  p = r->parent;

  if (!p) {

  if (resource_type(r) == IORESOURCE_MEM)

  p = &iomem_resource;

  else if (resource_type(r) == IORESOURCE_IO)

  p = &ioport_resource;

  }

  if (p && insert_resource(p, r)) {

  printk(KERN_ERR

  "%s: failed to claim resource %d\n",

  dev_name(&pdev->dev), i);

  ret = -EBUSY;

  goto failed;

  }

  }

  //上面主要是遍历设备所占用的资源,找到对应的父资源,如果没有定义,那么根据资源的类型,分别赋予iomem_resource和ioport_resource,然后调用insert_resource插入资源。

  //这样系统的资源就形成了一个树形的数据结构,便于系统的管理

  pr_debug("Registering platform device '%s'. Parent at %s\n",

  dev_name(&pdev->dev), dev_name(pdev->dev.parent));

  ret = device_add(&pdev->dev);

  //注册到设备模型中

  if (ret == 0)

  return ret;

  failed:

  while (--i >= 0) {

  struct resource *r = &pdev->resource[i];

  unsigned long type = resource_type(r);

  if (type == IORESOURCE_MEM || type == IORESOURCE_IO)

  release_resource(r);

  }

  return ret;

  }

以上就完成了device的到总线上的注册。接下来我就来看driver到总线上的挂载过程

 

该过程是一个非常复杂繁琐的过程,期间牵扯到了层层函数的调用,下面就给出了具体的过程:

platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev()对每个挂在虚拟的platform bus的设备作__driver_attach()->driver_probe_device()->drv->bus->match()==platform_match()->比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用platform_drv_probe()->driver->probe(),如果probe成功则绑定该设备到该驱动.

这整个过程中有两个地方我们需要注意,相信大家心里已经很有数了,就是match()和probe(),一个负责匹配一个负责对成功绑定的设备进行port的赋值。

先来看match的过程吧:

前面也大致提到了所谓的match就是驱动成功注册到总线中后逐个与总线上已挂载的设配进行匹配,具体的实现就在driver_attach()里面了bus_for_each_dev()函数负责将驱动与设备们逐个匹配,这个函数中有一个参数函数最终调到了__driver_attach来实现具体的匹配过程,其中指针指向的match成员就是调用了paltform_match(), 当然这是要有根据的,大家不要忘记了platform_bus_type总线被kenrel注册的时候的那platform_bus_type结构体,里面的成员有一项为 .match=platform_match,对,paltform_match()函数就是定义在drivers/base/platform.c中。有兴趣的朋友可以check一下源码,很简单只有3行,值得一提的是有一个container_of的宏定义函数,在内核代码中此函数用的很多,可以着重了解一下,此函数可以返回传至函数内部参数所在的结构体的地址;初次之外就是一个简单的strcmp函数用来对比驱动与设备中所存的name是否一致。匹配成功后以后继续往下执行就会执行到probe。

为什么要执行probe呢,这是因为驱动好不容易找到了对的设备,就要把我们对该设备进行的一些初始化信息加入到驱动的标准处理过程中,当然这之后的行为就和我们的驱动模型没有多大的关系了。当然,继续关注我的朋友在以后应该会看到后续部分。那系统怎么知道probe函数到底调用的是哪一个驱动里的probe呢?就在下面……

probe具体调用的过程,大家先看下面的这个结构体:

static struct platform_driver serial8250_isa_driver = {
.probe  = serial8250_probe,
.remove  = __devexit_p(serial8250_remove),
.suspend  = serial8250_suspend,
.resume  = serial8250_resume,
.driver  = {
.name = "serial8250",
.owner  = THIS_MODULE,
},
};

.probe= serial8250_probe这一句就是重点了,从这一句我们可以很明显的看出我们在将驱动注册挂载到总线时的调用的platform_driver_register()所传的参数就是serial8250_isa_driver 结构体,所以当系统执行到driver->probe()时就会调用serial8250_probe()了。

到此为止,串口设备驱动加载过程中的系统驱动模型方面platform总线的行为就已经结束了,接下来的行为就是具体驱动的事情了。在后面我还会在后面的博客中写一些用户空间对驱动进行的读写等操作时,这些操作是如何层层调用,如何落实到驱动的底层操作。

【作者】 张昺华
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_34117211/article/details/90120094

智能推荐

[译]使用MVI打造响应式APP(四):独立性UI组件-程序员宅基地

文章浏览阅读130次。原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS作者:Hannes Dorfmann译者:却把清梅嗅这篇博客中,我们将针对如何 如何构建独立组件 进行探讨,我将阐述为什么在我看来 父子关系会导致坏味道的代码,以及为何这种关系是没有意义的。有这样一个问题时不时涌现在我的脑海中—— MVI...

tensorflow经过卷积及池化层后特征图的大小计算_池化层后特征图尺寸-程序员宅基地

文章浏览阅读662次。https://blog.csdn.net/qq_32466233/article/details/81075288_池化层后特征图尺寸

使用vue-echarts异步数据加载,不能重新渲染页面问题。_vue echart初始化渲染过后无法重新渲染-程序员宅基地

文章浏览阅读3.3k次。一、问题说明我是用的是官方示例中的这个饼状图。结果在应用到项目中后发现利用axios请求到的数据无法渲染到页面中去。并且其中value值已经改变。二、解决办法用$set改变value的值,并且重新绘制一遍表格。$set是全局 Vue.set 的别名。$set用法:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为..._vue echart初始化渲染过后无法重新渲染

nrf51822 蓝牙协议栈 例程入门 点灯_nrf51822例程-程序员宅基地

文章浏览阅读6.6k次。1,参考文档: 青云蓝牙光盘V4.1\6.青云系列教程青风出品\3:BLE蓝牙应用篇\2.BLE实验第二节:蓝牙LED任务读写使用说明.pdf青云蓝牙光盘V4.1\6.青云系列教程青风出品\4:蓝牙原理详解手把手教你用蓝牙:蓝牙LED任务读写原理任务详解.pdf2,进入目录: 青云蓝牙光盘V4.1\5.青云测试代码\ 第三部分:BLE蓝牙实验\BLE实验3:按键蓝牙通知\B..._nrf51822例程

TortoiseGit小乌龟工具上传解析-程序员宅基地

文章浏览阅读5.2k次。使用tortoiseGit工具一直出错,由于没有设置自动获取putty key这一项,从网上找了一个教程,现分享如下,以做参考:前半部分参考网上的例子:http://www.showerlee.com/archives/1300,但会出现“git did not exit cleanly (exit code 128)”错误1.在D盘新建一个目录,例如"D:\Git",并进入目录右键目录空白处选择...

bindgetuserinfo="onGotUserInfo" and @getuserinfo="onGotUserInfo_component "pages/index/index" does not have a meth-程序员宅基地

文章浏览阅读1.9k次。发现使用uni-app获取UserInfo,结果使用微信官网栗子发现只弹出提示,没获取到值如下<button open-type="getUserInfo" lang="zh_CN" bindgetuserinfo="onGotUserInfo">获取用户信息</button>onGotUserInfo: function (e) { console.log(e) ..._component "pages/index/index" does not have a method "getuserinfo" to handle

随便推点

python translate函数_Python:内置函数makestrans()、translate()-程序员宅基地

文章浏览阅读287次。一、makestrans()格式: str.maketrans(intab,outtab);功能:用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。注:两个字符串的长度必须相同,为一一对应的关系。注:Python3.6中已经没有string.maketrans()了,取而代之的是内建函数:bytearray...._python maketrance

Set集合详解-程序员宅基地

文章浏览阅读5.7k次,点赞9次,收藏14次。set集合的简介,它的特点和遍历方式。介绍了HashSet重复元素存储底层原理,LinkedHashSet,TreeSet排序方法,SortedSet获取集合值的方法_set集合

详解智慧城市排水管理系统整体方案_污水处理智慧管理系统案列-程序员宅基地

文章浏览阅读3.6k次,点赞3次,收藏29次。随着城市规模的不断扩大和现代化程度的日益提高,城市排水管网越来越复杂,一些城市相继发生大雨内涝、管线泄漏爆炸、路面塌陷等事件,严重影响了人民群众生命财产安全和城市运行秩序。因此,摸清排水管网设施资产家底、建立排水管网地理信息系统,用现代化的技术手段对排水系统进行科学管理显得迫在眉睫。以时空信息为基础,充分利用感知监测网、物联网、云计算、移动互联网、工业控制和水力模型等新一代信息技术,全方位感..._污水处理智慧管理系统案列

详解NTFS文件系统_ntfs文件系统中,磁盘上的所有数据包括源文件都是以什么的形式存储-程序员宅基地

文章浏览阅读5.7k次,点赞4次,收藏13次。上篇在详解FAT32文件系统中介绍了FAT32文件系统存储数据的原理,这篇就来介绍下NTFS文件系统。NTFS、用过Windows系统的人都知道,它是一个很强大的文件系统,支持的功能很多,存储的原理也很复杂。目前绝大多数Windows用户都是使用NTFS文件系统,它主要以安全性和稳定性而闻名,下面是它的一些主要特点。安全性高:NTFS支持基于文件或目录的ACL,并且支持加密文件系统(E_ntfs文件系统中,磁盘上的所有数据包括源文件都是以什么的形式存储

【深度学习】【目标检测】损失函数smooth L1 Loss_smooth_l1_loss公式-程序员宅基地

文章浏览阅读1.2k次。pytorch之常用Loss函数总结参考文档L1、L2、smooth L1 Losssoftmax_cross_entropy_with_logitssparse_softmax_cross_entropy_with_logitsbinary_cross_entropysigmoid_cross_entropy参考文档参考文档参考文档L1、L2、smooth L1 Losssoftmax..._smooth_l1_loss公式

PAT 乙级 1017 (方法 + 代码)_pat 1017乙-程序员宅基地

文章浏览阅读462次。1017 A除以B (20 分)本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数。你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立。输入格式:输入在一行中依次给出 A 和 B,中间以 1 空格分隔。输出格式:在一行中依次输出 Q 和 R,中间以 1 空格分隔。输入样例:123456789050987654321 7输出样例:17636..._pat 1017乙