Linux 内核学习(3)---- platform driver模型

platform driver模型简介

在Linux 的设备驱动模型中,关心总线,设备和驱动这三个实体,总线将设备和驱动绑定,在系统中每注册一个设备的时候,会寻找与之对应的驱动;相反的,在系统中每注册一个驱动的时候,会找到相类似的设备,而匹配由总线完成
一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI,USB,I2C和SPI等设备而言,这自然不是问题,但是在嵌入式系统中,Soc系统中集成的独立外设控制器,挂接在Soc 内存空间的外设等确不依赖于此类总线,基于这个背景,Linux 发明了一种模拟的总线,称为Platform总线,相应的设备称为Platform device,驱动则为Platform Driver;但是所谓的Platform_device 不是和字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附件手段

platform driver基本原理

platform driver 的基本架构如下图所示:


platform_driver.png

platform_driver 可将cdev有关的一系列操作(前提是字符设备的驱动开发)放到platform_driver的probe函数中去实现,这样就把cdev挂到platform bus上去了
主要原理:

注册platform device

系统初始化时,调用platform_add_devices函数,把所有放置在板级platform_device数组中的platform_device注册到系统中去,此函数循环调用platform_device_register函数,来注册每个platform_device platform_device_register中会调用platform_device_add函数platform_device全部注册到系统之后,便可以通过platform的操作接口,来获取platform_device中的resource资源. 比如地址、中断号等,以进行request_memregion、ioremap(将resource分配的物理地址映射到kernel的虚拟空间来)和request_irq操作platform的操作接口包括platform_get_irq、platform_get_irq_byname、platform_get_resource、platform_get_resource_byname

加载platform driver

在驱动模块insmod到系统时,驱动代码可以通过上面函数来获取对应platform_device的resource资源.比如在module_init中,会调用plarform_driver_register,这个会引用到platform_driver中的probe函数. probe函数可以通过get_resource来获取寄存器物理基地址,然后ioremap到kernel的虚拟空间来,这样驱动就可以正式操纵和修改设备的寄存器
进行cdev的初始化及cdev_add的操作
驱动注册的时候,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()-& gt;比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用platform_drv_probe()->driver->probe(),如果probe成功则绑定该设备到该驱动

platform driver数据结构

platform_device 结构

代码路径:linux-5.4.6/include/linux/platform_device.h

struct platform_device {
    const char  *name;
    int     id;
    bool        id_auto;
    struct device   dev;
    u64     dma_mask;
    u32     num_resources;
    struct resource *resource;

    const struct platform_device_id *id_entry;
    char *driver_override; /* Driver name to force a match */

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

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

  • name:设备的名称
  • dev:真正有用的设备,通过contain_of,能找到整个platform_device
  • num_resources, resource: 系统使用的资源。Linux系统资源包括IO,寄存器,DMA,Bus,Memory等。
platform_driver 结构
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;
    bool prevent_deferred_probe;
};

  • probe:将driver绑定到device上调用该函数
  • remove:系统卸载设备的时候,将driver和device解绑
  • shutdown:关机时使设备静默
  • suspend:使设备进入睡眠模式
  • resume: 唤醒设备
platform_bus_type结构
struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .dma_configure  = platform_dma_configure,
    .pm     = &platform_dev_pm_ops,
};

kernel在进入C语言阶段,会进入start_kernel函数(init/main.c),进行一些内存管理,调度。最终调用到driver_init函数中,执行的函数platform_bus_init就是platform总线的注册函数
platform_bus_init 最关键是下面的 platform_match函数:

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 ACPI style match */
    if (acpi_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);
}

platform driver实例

platform_driver 注册流程:

  1. 定义module_init 函数
  2. 定义platform_driver 结构体
  3. 注册platform_driver到platform bus 中
static struct platform_driver s3c_tvscaler_driver = {
       .probe          = s3c_tvscaler_probe,
       .remove         = s3c_tvscaler_remove,
       .suspend        = s3c_tvscaler_suspend,
       .resume         = s3c_tvscaler_resume,
       .driver      = {
        .owner  = THIS_MODULE,
        .name   = "s3c-tvscaler",
    },
};

static char banner[] __initdata = KERN_INFO "S3C6410 TV scaler Driver, (c) 2008 Samsung Electronics\n";


int __init  s3c_tvscaler_pre_init(void)
{

    printk(banner);
    
    if(platform_driver_register(&s3c_tvscaler_driver) != 0)
    {
        printk("platform device register Failed \n");
        return -1;
    }
    
    printk(" S3C6410 TV scaler Driver module init OK. \n");

       return 0;
}

void  s3c_tvscaler_exit(void)
{
       platform_driver_unregister(&s3c_tvscaler_driver);
       printk("S3C: tvscaler module exit\n");
}

module_init(s3c_tvscaler_pre_init);
module_exit(s3c_tvscaler_exit);

也可以使用系统预定宏来注册Platform_driver
module_platform_driver(komeda_platform_driver);
代码路径:linux-5.4.6/include/linux

/* module_platform_driver() - Helper macro for drivers that don't do
 * anything special in module init/exit.  This eliminates a lot of
 * boilerplate.  Each module may only use this macro once, and
 * calling it replaces module_init() and module_exit()
 */
#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register, \
            platform_driver_unregister)

module_driver在/include/linux/device.h中定义

/**
 * module_driver() - Helper macro for drivers that don't do anything
 * special in module init/exit. This eliminates a lot of boilerplate.
 * Each module may only use this macro once, and calling it replaces
 * module_init() and module_exit().
 *
 * @__driver: driver name
 * @__register: register function for this driver type
 * @__unregister: unregister function for this driver type
 * @...: Additional arguments to be passed to __register and __unregister.
 *
 * Use this macro to construct bus specific macros for registering
 * drivers, and do not use it on its own.
 */
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
    __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

展开后同样可以调用到 plarform_driver_register
Platform Driver 加载成功后,对应的设备节点如下:
/sys/bus/platform/devices
/sys/device/platform/

你可能感兴趣的:(Linux 内核学习(3)---- platform driver模型)