设备树和Platform架构--4--platform bus概述及其初始化

1 概述

1.1 platform(平台)总线出现背景

    Platform总线是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与cpu进行数据交互的,但是在嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。

    从Linux2.6内核起,便引入这套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 struct platform_device 表示;驱动用 struct platform_driver 进行注册。

    linux_platform_driver 机制和传统的device_driver机制(即:通过 driver_register 函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时,通过 platform device提供的标准接口进行申请并使用。

    platform 是一个虚拟的地址总线,相比 PCI、USB,它主要用于描述SOC上的片上资源platform 所描述的资源有一个共同点:在CPU 的总线上直接取址。平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源。

1.2 platform相关的代码

platform总线相关代码:driver\base\platform.c 文件
相关结构体定义: include\linux\platform_device.h 文件

1.3 Linux platform总线、platform设备、platform驱动之间的关系及框架

(1)总线用于将设备和驱动绑定;在系统注册设备时,寻找与之匹配的驱动;在系统注册驱动时,寻找与之匹配的设备,匹配由总线完成。
(2)对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体
(3)platform bus是一条虚拟总线,platform_device为相应的设备,platform_driver为相应的驱动。
(4)与传统的bus/device/driver机制相比,platform由内核统一进行管理,提高了代码的可移植性和安全性。所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。
(5)Linux总线、设备、驱动的模型框架如下图所示

 

1.4 总线设备驱动框架原理

在总线设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。
(1)当系统向内核注册每一个驱动程序时,都要通过调用platform_driver_register函数将驱动程序注册到总线(bus),并将其放入所属总线的drv链表中,注册驱动的时候会调用所属总线的match函数寻找该总线上与之匹配的每一个设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定;
(2)当系统向内核注册每一个设备时,可以通过调用platform_device_register函数,也可以通过解析DTB由内核完成platform device的创建,并将设备platform device注册到总线platform bus,并将其放入所属总线的dev链表中,注册设备的时候同样也会调用所属总线的match函数寻找该总线上与之匹配的每一个驱动程序,如果找到与之匹配的驱动程序时会调用相应的probe函数将相应的设备和驱动进行绑定;而这一匹配的过程是由总线自动完成的。

 

2 Platform bus初始化

2.1 关键结构体

  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 bus_type_private *p;   //  总线的私有数据  p->subsys.kobj 表示该总线在驱动模型中对应的对象
 };

 struct bus_type_private {
      struct kset subsys;                //  这个是bus主要的kset
      struct kset *drivers_kset;         //  这个kset指针用来指向该总线的 drivers目录的
      struct kset *devices_kset;         //  这个kse指针用来指向该总线的devices目录的
      struct klist klist_devices;        //  用来挂接该总线下的设备的一个链表头
      struct klist klist_drivers;        //   用来挂接该总线下的设备驱动的一个链表头
      struct blocking_notifier_head bus_notifier;
      unsigned int drivers_autoprobe:1;  //   是否需要在设备驱动注册时候子自动匹配设备
      struct bus_type *bus;              //  指向本bus结构体
 };

2.2 Platform bus初始化流程

platform bus的初始化由函数platfrom_bus_init()函数来完成。由于platform bus本身也是一种设备,因此调用了原始的系统接口device_register()和bus_register()分别完成platform_bus设备和platform_bus_type本身的初始化;

start_kernel()      //   init/main.c
        >>>rest_init();
                >>>pid = kernel_thread(kernel_init, NULL, CLONE_FS); 
                >>>kernel_init(void *unused)
                        >>>kernel_init_freeable();
                                >>>do_basic_setup(); 
                                        >>> driver_init(); //   kernel/drivers/base/init.c
                                                >>>devices_init();   #device初始化
                                                >>>buses_init();        #bus初始化
                                                >>>classes_init();      #class初始化
                                                >>>platform_bus_init();   #platform bus初始化   kernel/drivers/base/platform.c
                                                        >>>early_platform_cleanup();
                                                        >>>error = device_register(&platform_bus);
                                                        >>>error =  bus_register(&platform_bus_type);
                                                        >>>of_platform_register_reconfig_notifier();
                                                
                                        >>>do_initcalls();//   init/main.c
                                                >>>for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
                                                            do_initcall_level(level);
                                                                 >>> for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
                                                                                 do_one_initcall(*fn);//此处调用相关模块的初始化
其中,
struct device platform_bus = {                                                                        
  .init_name  = "platform",
};


static const struct dev_pm_ops platform_dev_pm_ops = {
  .runtime_suspend = pm_generic_runtime_suspend,
  .runtime_resume = pm_generic_runtime_resume,
  USE_PLATFORM_PM_SLEEP_OPS
};

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

其中,

early_platform_cleanup()               //  进行一些早期的平台清理   

device_register(&platform_bus)    //注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象  /sys/devices/platform/) 

bus_register(&platform_bus_type)                              //  将Platform bus总线注册进系统

下面给出这两个函数的实现

//向系统注册一个设备
int device_register(struct device *dev)
    >>>device_initialize(dev)//初始化设备结构体成员
    >>>device_add(dev);//通过调用kobject_add()将设备对象添加到系统中,加入到设备链表

//初始化设备结构体成员
void device_initialize(struct device *dev)
{
  dev->kobj.kset = devices_kset;
  kobject_init(&dev->kobj, &device_ktype);
  INIT_LIST_HEAD(&dev->dma_pools);
  mutex_init(&dev->mutex);
  lockdep_set_novalidate_class(&dev->mutex);
  spin_lock_init(&dev->devres_lock);
  INIT_LIST_HEAD(&dev->devres_head);
  device_pm_init(dev);
  set_dev_node(dev, -1);
#ifdef CONFIG_GENERIC_MSI_IRQ
  INIT_LIST_HEAD(&dev->msi_list);
#endif
  INIT_LIST_HEAD(&dev->links.consumers);
  INIT_LIST_HEAD(&dev->links.suppliers);
  dev->links.status = DL_DEV_NO_DRIVER;
}
int device_add(struct device *dev)
        >>>dev = get_device(dev);
        >>>error = device_private_init(dev);
        >>>dev_set_name(dev, "%s", dev->init_name);
        >>>parent = get_device(dev->parent);
        >>>kobj = get_device_parent(dev, parent);
        >>>set_dev_node(dev, dev_to_node(parent));
        >>>error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
        >>>.....

 

 int bus_register(struct bus_type *bus)
 {
     int retval;
     struct bus_type_private *priv; // 定义一个bus_type_private 结构体指针

     priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); // 申请分配内存
     if (!priv)
         return -ENOMEM;

     priv->bus = bus; // 使用 priv->bus 指向我们传进来的bus
     bus->p = priv; // 通过 bus->p 指向priv 这里其实就是将bus与priv建立关系,这个跟之前的evice、class的设计是一样的

     BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

     retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); // 给我们的bus在设备驱模型中的对象设置名字 bus->p->subsys.kobj
     if (retval)
     goto out;

     // 这里就是对bus的私有数据进行一些填充
     priv->subsys.kobj.kset = bus_kset; // 设置bus对象的父对象 也就是 /sys/bus 这目录 作为他的上层目录 所有的具体的总线类型对象都是在这个目录下
     priv->subsys.kobj.ktype = &bus_ktype; // 设置bus对象的 对象类型为 bus_ktype
     priv->drivers_autoprobe = 1; // 配置为在注册设备或者是注册设备驱动时自动进行配置 这个就决定为什么我们在注册设备或者是设备驱动能够进行自动匹配

     retval = kset_register(&priv->subsys); // 注册kset结构体(内部会调用kobject_add_internal函数,也就是将bus对象添加到 /sys/bus/目录下, /sys/bus/xxx_busType 对应具体的总线)
     if (retval)
         goto out;

     retval = bus_create_file(bus, &bus_attr_uevent); // 在该bus下建立属性文件 (对应的就是 bus下的 uevent属性)
     if (retval)
         goto bus_uevent_fail;

     priv->devices_kset = kset_create_and_add("devices", NULL, // 在具体总线的目录下创建 kset 容器对象 /sys/bus/xxx_busType/devices
     &priv->subsys.kobj); // 通过priv->devices_kset指针去指向 这个目录对应的对象
     if (!priv->devices_kset) {
         retval = -ENOMEM;
         goto bus_devices_fail;
     }

     priv->drivers_kset = kset_create_and_add("drivers", NULL, // /sys/bus/xxx_busType    /drivers
     &priv->subsys.kobj); // 通过 priv->drivers_kset 指针去指向 这个目录对应的对象
     if (!priv->drivers_kset) {
         retval = -ENOMEM;
         goto bus_drivers_fail;
     }

     klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); // 初始化链表 klist
     klist_init(&priv->klist_drivers, NULL, NULL); // 初始化链表 klist

     retval = add_probe_files(bus); // 添加探针文件 其实内部做的还是添加属性文件 /sys/bus/xxx_busType/drivers_probe /sys/bus/xxx_busType/drivers_autoprobe
     if (retval)
         goto bus_probe_files_fail;

         retval = bus_add_attrs(bus); // 根据 bus->bus_attrs 中的属性设置来添加属性文件
     if (retval)
         goto bus_attrs_fail;

         pr_debug("bus: '%s': registered\n", bus->name);
         return 0;

     bus_attrs_fail:
         remove_probe_files(bus);
     bus_probe_files_fail:
         kset_unregister(bus->p->drivers_kset);
     bus_drivers_fail:
         kset_unregister(bus->p->devices_kset);
     bus_devices_fail:
         bus_remove_file(bus, &bus_attr_uevent);
     bus_uevent_fail:
         kset_unregister(&bus->p->subsys);
         kfree(bus->p);
     out:
         bus->p = NULL;
     return retval;
 }

 

 

 

  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用platform bus driver的方式编写Winbond SPI Flash驱动程序的示例代码,以及相应的设备树配置。 首先,创建一个名为“winbond-spi-flash”或类似的设备节点,并将其添加到设备树中。以下是一个简单的设备树配置示例: ```dts &spi0 { winbond_spi_flash: winbond-spi-flash@0 { compatible = "winbond,w25q64"; reg = <0>; spi-max-frequency = <100000000>; }; }; ``` 在驱动程序中,您需要实现probe和remove函数,用于初始化和清理设备资源。以下是一个简单的驱动程序示例: ```c #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/spi/spi.h> #define WINBOND_CMD_READ_ID 0x9f #define WINBOND_CMD_READ_DATA 0x03 static struct spi_device *winbond_spi_device; static int winbond_spi_probe(struct platform_device *pdev) { int ret; struct device *dev = &pdev->dev; u8 id[3]; printk(KERN_INFO "winbond_spi: probe\n"); // get the SPI device pointer winbond_spi_device = dev_get_drvdata(dev); // read the Winbond Flash ID struct spi_transfer transfer = { .tx_buf = &WINBOND_CMD_READ_ID, .rx_buf = id, .len = 3, }; struct spi_message message; spi_message_init(&message); spi_message_add_tail(&transfer, &message); ret = spi_sync(winbond_spi_device, &message); if (ret < 0) { printk(KERN_ERR "winbond_spi: failed to read Winbond Flash ID\n"); return ret; } printk(KERN_INFO "winbond_spi: Winbond Flash ID: %02x %02x %02x\n", id[0], id[1], id[2]); return 0; } static int winbond_spi_remove(struct platform_device *pdev) { printk(KERN_INFO "winbond_spi: remove\n"); return 0; } static struct platform_driver winbond_spi_driver = { .driver = { .name = "winbond-spi-flash", }, .probe = winbond_spi_probe, .remove = winbond_spi_remove, }; static int winbond_spi_probe(struct spi_device *spi) { return 0; } static int winbond_spi_remove(struct spi_device *spi) { return 0; } static struct spi_driver winbond_spi_driver = { .driver = { .name = "winbond-spi-flash", }, .probe = winbond_spi_probe, .remove = winbond_spi_remove, }; static int __init winbond_spi_init(void) { int ret; struct spi_master *master; struct platform_device *pdev; printk(KERN_INFO "winbond_spi: init\n"); // find the SPI master controller master = spi_busnum_to_master(0); if (!master) { printk(KERN_ERR "winbond_spi: failed to find SPI master controller\n"); return -ENODEV; } // register the SPI driver ret = spi_register_driver(&winbond_spi_driver); if (ret < 0) { printk(KERN_ERR "winbond_spi: failed to register SPI driver\n"); return ret; } // create the platform device pdev = platform_device_alloc("winbond-spi-flash", 0); if (!pdev) { printk(KERN_ERR "winbond_spi: failed to allocate platform device\n"); return -ENOMEM; } platform_device_add(pdev); return 0; } static void __exit winbond_spi_exit(void) { printk(KERN_INFO "winbond_spi: exit\n"); // remove the platform device platform_device_unregister(&winbond_spi_device->dev); // unregister the SPI driver spi_unregister_driver(&winbond_spi_driver); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Winbond SPI Flash Driver"); module_init(winbond_spi_init); module_exit(winbond_spi_exit); ``` 在该驱动程序中,probe函数用于初始化SPI总线并读取Winbond Flash的ID。在remove函数中,您可以清理和释放设备资源。 需要注意的是,驱动程序中的设备树配置和平台驱动程序名称需要匹配。在本例中,设备树中的节点名称为“winbond-spi-flash”,而平台驱动程序的名称为“winbond-spi-flash”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值