平台设备驱动分析

 

通常在Linux中,把SOC系统中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。在Linux中用platform_device结构体来描述一个平台设备

 


平台设备驱动分析:
   使用内核linux-2.6.22
   分析工具:source insight

 

我们来分析s3c2410-lcd是如何进行平台设备驱动注册的。

 drivers/video/s3c2410fb.c
 
 首先我们找到s3c2410fb.c的入口函数:
  module_init(s3c2410fb_init);
  module_exit(s3c2410fb_cleanup);
 

 然后查找 s3c2410fb:
 int __devinit s3c2410fb_init(void)
 {
  return platform_driver_register(&s3c2410fb_driver);   //在入口函数中注册了&s3c2410fb_driver结构体
 }

 static void __exit s3c2410fb_cleanup(void)
 {
  platform_driver_unregister(&s3c2410fb_driver);
 }

 我们就跳到s3c2410fb_driver结构体:
 

 static struct platform_driver s3c2410fb_driver = {
 .probe  = s3c2410fb_probe,             /*这个函数很重要,它的作用就相当于我们普通字符设备驱动中的init函数*/
 .remove  = s3c2410fb_remove,        /*这个函数的作用就相当于我们普通字符设备驱动中的exit函数里所作的工作*/
 .suspend = s3c2410fb_suspend,
 .resume  = s3c2410fb_resume,
 .driver  = {
  .name = "s3c2410-lcd",     //名字很重要,设备驱动和设备只有在相同的名字情况下,才能够进行匹配。
  .owner = THIS_MODULE,
 },
};

 

 接下来分析.probe  = s3c2410fb_probe, 中的s3c2410fb_probe结构体

 

  static int __init s3c2410fb_probe(struct platform_device *pdev)
{
 struct s3c2410fb_info *info;
 struct fb_info    *fbinfo;
 struct s3c2410fb_hw *mregs;
 int ret;
 int irq;
 int i;
 u32 lcdcon1;


      mach_info = pdev->dev.platform_data;  //mach_info函数非常重要,他会和dev.platform_data中的dev进行匹配。
}
对于这个结构体里面的函数我就不一一分析了
 


我们构造的每一个平台设备结构体都会添加到platform.c(总线驱动中):

          drivers/base/platform.c

找到它的入口函数:

int __init platform_bus_init(void)
{
 int error;

 error = device_register(&platform_bus);
 if (error)
  return error;
 error =  bus_register(&platform_bus_type);
 if (error)
  device_unregister(&platform_bus);
 return error;
}

找到platform_bus_type结构体:

struct bus_type platform_bus_type = {
 .name  = "platform",
 .dev_attrs = platform_dev_attrs,
 .match  = platform_match,
 .uevent  = platform_uevent,
 .suspend = platform_suspend,
 .suspend_late = platform_suspend_late,
 .resume_early = platform_resume_early,
 .resume  = platform_resume,
};

那么我们的设备驱动&s3c2410fb_driver又是如何注册到总线驱动platform.c中的呢?
platform_driver_register(&s3c2410fb_driver);

在platform.c中:

int platform_driver_register(struct platform_driver *drv)
{
 drv->driver.bus = &platform_bus_type;  //将设备驱动加入到平台总线的驱动链表上面
 if (drv->probe)
  drv->driver.probe = platform_drv_probe;
 if (drv->remove)
  drv->driver.remove = platform_drv_remove;
 if (drv->shutdown)
  drv->driver.shutdown = platform_drv_shutdown;
 if (drv->suspend)
  drv->driver.suspend = platform_drv_suspend;
 if (drv->resume)
  drv->driver.resume = platform_drv_resume;
 return driver_register(&drv->driver);
}

drivers/base/driver.c

int driver_register(struct device_driver * drv)
{
 if ((drv->bus->probe && drv->probe) ||
     (drv->bus->remove && drv->remove) ||
     (drv->bus->shutdown && drv->shutdown)) {
  printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
 }
 klist_init(&drv->klist_devices, NULL, NULL);    //
 return bus_add_driver(drv);   //将本drv驱动注册登记到drv->bus所在的总线上
}
我们找到bus_add_driver结构体:
drivers/base/bus.c

int bus_add_driver(struct device_driver *drv)
{
 struct bus_type * bus = get_bus(drv->bus);
 int error = 0;
 
 drv->kobj.kset = &bus->drivers;


 if (drv->bus->drivers_autoprobe) {
  error = driver_attach(drv);  //在此去总线的设备链表上去搜索每一个设备,每搜索一个设备都会去调用__driver_attach;
  if (error)
   goto out_unregister;
 }
 klist_add_tail(&drv->knode_bus, &bus->klist_drivers);  //将自己挂载到驱动链表上面去
 module_add_driver(drv->owner, drv);

 error = driver_add_attrs(bus, drv);
 
}
driver_attach起的作用是什么?

drivers/base/dd.c

int driver_attach(struct device_driver * drv)
{
 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);   //查找bus驱动中的每一个drv

}

来看看 __driver_attach函数的作用:

static int __driver_attach(struct device * dev, void * data)
{
 struct device_driver * drv = data;

 

 down(&dev->sem);
 if (!dev->driver)
  driver_probe_device(drv, dev);
 return 0;
}


int driver_attach(struct device_driver * drv)
{
 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

分析driver_probe_device结构体:

int driver_probe_device(struct device_driver * drv, struct device * dev)
{

 if (drv->bus->match && !drv->bus->match(dev, drv))     //platform_match
  goto done;

 ret = really_probe(dev, drv);    //名字相同,代表匹配成功,成功则调用次函数

done:
 return ret;
}


再来看看really_probe:


static int really_probe(struct device *dev, struct device_driver *drv)
{

 dev->driver = drv;
 if (driver_sysfs_add(dev)) {

 }

 if (dev->bus->probe) {
  ret = dev->bus->probe(dev);
  if (ret)

 } else if (drv->probe) {
  ret = drv->probe(dev);   //platform_drv_probe
  if (ret)

 }
}

 

drivers/base/platform.c

static int platform_match(struct device * dev, struct device_driver * drv)
{
 struct platform_device *pdev = container_of(dev, struct platform_device, dev);

 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}


static int platform_drv_probe(struct device *_dev)
{
 struct platform_driver *drv = to_platform_driver(_dev->driver);
 struct platform_device *dev = to_platform_device(_dev);

 return drv->probe(dev);             //s3c2410fb_probe
}


platform_device注册过程:arch/arm/mach-s3c2410/mach-smdk2410.c

static void __init smdk2410_init(void)
{
 platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));   //添加dev
 smdk_machine_init();
}

 


static struct platform_device *smdk2410_devices[] __initdata = {                 //各种设备
 &s3c_device_usb,
 &s3c_device_lcd,
 &s3c_device_wdt,
 &s3c_device_i2c,
 &s3c_device_iis,
};

drivers/base/platform.c


int platform_device_register(struct platform_device * pdev)
{
 device_initialize(&pdev->dev);
 return platform_device_add(pdev);
}

 

int platform_device_add(struct platform_device *pdev)
{


 if (!pdev->dev.parent)
  pdev->dev.parent = &platform_bus;

 pdev->dev.bus = &platform_bus_type;

 else
  strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

 for (i = 0; i < pdev->num_resources; i++) {

 }

 ret = device_add(&pdev->dev);


}

drivers/base/core.c

int device_add(struct device *dev)
{

 dev = get_device(dev);

 if (dev->bus)

 bus_attach_device(dev);  //开始匹配平台驱动,开始去找控制驱动(lcd控制器驱动)
 if (parent)
  klist_add_tail(&dev->knode_parent, &parent->klist_children);

 if (dev->class) {
  down(&dev->class->sem);

  list_add_tail(&dev->node, &dev->class->devices);

  list_for_each_entry(class_intf, &dev->class->interfaces, node)
   if (class_intf->add_dev)
    class_intf->add_dev(dev, class_intf);
  up(&dev->class->sem);
 }


void bus_attach_device(struct device * dev)
{
 struct bus_type *bus = dev->bus;
 int ret = 0;

 if (bus) {
  dev->is_registered = 1;
  if (bus->drivers_autoprobe)
   ret = device_attach(dev);
  if (ret >= 0)
   klist_add_tail(&dev->knode_bus, &bus->klist_devices);

 }
}
查看device_attach函数

int device_attach(struct device * dev)
{
 int ret = 0;

 down(&dev->sem);
 if (dev->driver) {
  ret = device_bind_driver(dev);
  if (ret == 0)
   ret = 1;
  else {
   dev->driver = NULL;
   ret = 0;
  }
 } else {
  ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
 }
 up(&dev->sem);
 return ret;
}

再看看__device_attach函数:

drivers/base/dd.c

static int __device_attach(struct device_driver * drv, void * data)
{
 struct device * dev = data;
 return driver_probe_device(drv, dev);   //也会调用总线的match函数来实现匹配过程,根据名字进行匹配
}


int driver_probe_device(struct device_driver * drv, struct device * dev)
{
 
 if (drv->bus->match && !drv->bus->match(dev, drv))
  goto done;

 ret = really_probe(dev, drv);

}

 

static int really_probe(struct device *dev, struct device_driver *drv)
{

 dev->driver = drv;

 if (dev->bus->probe) {
  ret = dev->bus->probe(dev);
  if (ret)
   goto probe_failed;
 } else if (drv->probe) {
  ret = drv->probe(dev);                                //platform_drv_probe最终会调用到我们的s3c2410fb_probe  if (ret)
 }


}

就分析到这了,后面有一些因为时间匆忙以后再做详细分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
设备树(device tree)机制是Linux内核从linux-3.x版本开始引进的一种机制,目的是解决内核源码的arch/arm目录下代码混乱的问题:随着ARM生态的快速发展,在内核源码的arch/arm目录下,存放着几十种arm芯片和几百个开发板相关的源文件,很多开发板和处理器的中断、寄存器等相关硬件资源都在这个目录下以.c或.h的文件格式定义。而对于内核来说,与这些硬件耦合,会导致内核代码混乱不堪,每个开发板上运行的内核镜像都必须单独编译配置,无法通用。什么时候Linux内核能像Windows镜像那样,无论你的电脑什么配置,一个Windows安装包,都可以直接下载安装运行呢?设备树机制,实现了Linux内核和硬件平台的解耦:每个硬件平台的硬件资源使用一个设备树文件(xxx.dts)来描述,而不是在arch/arm下以.c 或 .h 文件来定义。Linux内核是一个通用的内核,在启动过程中,在通过解析设备树中的硬件资源来初始化某个具体的平台。 引入设备树后,很多和内核驱动开发的工作也发生了变化:以往驱动工程师关注的头文件宏定义、寄存器定义,现在这些基本上不用关注,关注的重点则转向了如何根据硬件平台去配置和修改设备树文件。很多驱动的编程接口也发生了变化,开始慢慢使用device tree提供的编程接口去开发驱动。本期课程主要面向嵌入式开发人员,分享Linux下驱动开发所需要的设备树知识和必备技能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Keep Coding...

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值