Linux Platform平台设备驱动模型

Linux2.6起引入了一套新的驱动管理和注册模型,即平台设备platform_device和平台驱动platform_driver.

Linux中大部分的设备驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver表示平台设备模型与传统的devicedriver模型相比,一个十分明显的优势在于平台设备模型将设备本身的资源注册进内核,由内核统一管理。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。通过平台设备模型开发底层驱动的大致流程为下图:

平台设备是指处理器上集成的额外功能的附加设备,如Watch DogIICIISRTCADC等设备。这些额外功能设备是为了节约硬件成本、减少产品功耗、缩小产品形状而集成到处理器内部的。需要注意的是,平台设备并不是与字符设备、块设备和网络设备并列的概念,而是一种平行的概念,其从另一个角度对设备进行了概括。如果从内核开发者的角度来看,平台设备的引入,是为了更容易开发字符设备、块设备和网络设备驱动

当一个驱动注册[platform_driver_register()]的时候,他会遍历所有总线上的设备来寻找匹配,在启动的过程驱动的注册一般比较晚,或者在模块载入的时候 当一个驱动注册[platform_driver_probe()]的时候, 功能上和使用platform_driver_register()是一样的,唯一的区别是它不能被以后其他的device probe了,也就是说这个driver只能和一个device绑定。

总线设备驱动模型主要包含总线、设备、驱动三个部分。

       现实总线:一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言(例如USB、I2C等典型的设备),这自然不是问题。

扫描二维码关注公众号,回复: 4855719 查看本文章

       虚拟总线(platform总线):但是在嵌入式系统里面,对于一些设备(内部的设备)可能没有现成的总线,如SoC 系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。

        注意1:platform总线设备驱动模型与之前的三类驱动(字符、块设备、网络设备)没有必然的联系。设备只是搭载到了platform总线上,仅此而已。platform总线相比与常规的总线模型其优势主要是platform总线是由内核实现的,而不用自己定义总线类型,总线设备来加载总线。platform总线是内核已经实现好的。

         注意2:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,例如,在 S3C6410处理器中,把内部集成的I2C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。

1. 注册总线bus类型:
       在系统初始化阶段,会首先向内核注册各种常用的总线类型,比如pci, usb, spi, i2c, platform等等,当然你也可以自己发明一种总线类型注册上去。这部分代码一般放在./arch/arm/mach-xxx/board-xxx.c中。
       有两个重要的链表挂在bus上,一个是设备device链表,一个是驱动driver链表。
       它包含的最关键的函数:match()


2. 注册设备:在此之后,会将系统的设备列表,基本上整个系统的device都在这里了,一一地注册进内核,就是调用xxx_device_regisger注册的过程。
           (xxx_device_regisger:将自己加到设备device链表,然后使用总线bus匹配对应的driver )


3. 注册驱动:然后是对于各个device设备driver的注册:xxx_drvier_register()。
            (xxx_drvier_register:将自己加到设备device链表,然后使用总线bus匹配对应的driver )


4. 设备和驱动的匹配:
       大部分device和driver的匹配方式就是看名字是否相同,这部分属于总线分内的事情。match的工作是由总线(bus)来完成,匹配工作发生在xxx_device_register()或xxx_drvier_register()
       设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;
        ===>>如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;
        ===>>如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致其probe暂不调用,而要等到设备注册成功并与自己匹配绑定后才会调用。
           (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!)  (这一点很重要!!!!!) (这一点很重要!!!)

内核版本2.6.30。编进内核的SPI驱动,通过看代码我明白了,大致过程是这样: 1、先创建一个spi_board_info结构描述spi设备信息,调用spi_register_board_info将这个结构添加到board_list中。 2、然后调用spi_register_master注册SPI控制器驱动,此时会调用scan_boardinfo扫描board_list,根据 spi_board_info调用spi_new_device生成spi_device结构,用spi_add_device添加设备。 3、调用spi_register_driver注册spi_driver,通过与device匹配驱动设备。
下面是问题: 1、上述做法需要在注册spi控制器驱动即spi master前,先要定义好spi_board_info结构并注册。但是如果我要编写一个驱动模块,我应该怎样注册spi_device? 2、spi控制器是通过片选信号来区分多个spi设备的,这个片选信号是像I2C地址一样由设备定义好的吗?如何知道某个设备该用怎样的片选信号?

回复 1# frank529 急用先学,最近刚好看了这一部分: 1、上述做法需要在注册spi控制器驱动即spi master前,先要定义好spi_board_info结构并注册。但是如果我要编写一个驱动模块,我应该怎样注册spi_device? spi_device就是就是通过    spi_register_board_info 来注册的。 通过spi_board_info结构,给定设备的下面信息: 设备名 波特率 设备挂在哪个spi总线上,如powerpc 5000有SPI A, B, C ,D 片选信号
#define SPI_AK4104      \ 659{                       \ 660        .modalias       = "ak4104-codec",       \设备名 661        .max_speed_hz   = 10000,                \波特率 662        .bus_num        = 0,                    \SPI A, B, C ,D 663        .chip_select    = 0,                    \片选(0~7) 664        .controller_data = (void *) GPIO_SPDIF_CS,\平台数据 665} #define SPI_DAC7512     \ 679{       \ 680        .modalias       = "dac7512",            \设备名 681        .max_speed_hz   = 1000000,              \波特率 682        .bus_num        = 0,           \ SPI A, B, C ,D 683        .chip_select    = 2,            \片选(0~7) 684        .controller_data = (void *) GPIO_MCLK_DAC_CS,   \ 685}
static struct spi_board_info connector_spi_devices[] __initdata = {          SPI_AK4104,          SPI_DAC7512, }; 调用下面函数,就把上面两个设备登记到/sys/bus/spi下了
    spi_register_board_info(ARRAY_AND_SIZE(connector_spi_devices)); 2、spi控制器是通过片选信号来区分多个spi设备的,这个片选信号是像I2C地址一样由设备定义好的吗?如何知道某个设备该用怎样的片选信号? 硬件连线决定的。 上面的例子,spi bus A上挂了上面两个设备:AK4104/DAC7512 SPI接口有下列信号线: SCK_A: 时钟,同两个设备的时钟信号接在一起 SIN_A:  data in;  同两个设备的data out信号接在一起 SOUT_A data out; 同两个设备的data in信号接在一起 PCS0_A:  bus A chip select 0, 接AK4104的片选 PCS2_A:  :  bus A chip select 2,接DAC7512的片选
如果先选中chip select 0, 下面的操作就会作用于设备AK4104 如果先选中chip select 2, 下面的操作就会作用于设备DAC7512

在 linux I2C 驱动之----i2c_client 的注册中介绍了i2c_client的注册,现在再来说说i2c_driver的注册过程。

   每一个驱动程序都有 module_init(xxxx_init) 这个语句,它代表系统启动的时候会自动执行 xxxx 这个函数,也就是说驱动的人口函数是由module_init来定义的。当然还有module_exit(XXXX),它代表系统卸载驱动时调用(linux系统允许动态加载卸载驱动),这一部分这不细说了。

       上文提到的 xxxx_init 是驱动的入口函数,在此函数中,我们一般注册驱动的driver,比如我今天说的 i2c_driver:

        static int __init xxxx_init(void)        {            return i2c_add_driver(&xxxx_driver);        }

其中xxxx_driver就是我们今天的主角 i2c_driver :

      static struct i2c_driver xxxx_driver =      {          .driver =          {              .owner = THIS_MODULE,               .name = "xxxx",          },          .id_table = xxxx_idtable,         .probe = xxxx_probe,       }

完整的 i2c_driver 非常复杂,上面的定义只是完成了最基本的部分,当然也是必须,也就代表完成了上面的部分你的驱动就可以工作了,当然还有其他的,这里不细说了。i2c_driver 中的driver.name 要和i2c_client一致,因为这是他们配备的一个依据,id_table 是i2c_device_id结构体的一个对象,里面定义了i2c驱动对应设备的i2c地址,probe函数是个非常重要的函数,等下我再来细说。

     现在我来说说 i2c_add_driver 的执行过程。

   (注意:此函数是linux系统i2c 子系统已经为我们做好了的,我们完全不需要了解它的执行过程,只需要明白,当系统中已经注册了和上文提到的i2c_driver  xxxx_driver 对应的i2c_client,所谓对应的是说,name一样,i2c设备的地址也一样,那么i2c_driver 与i2c_client配备成功,接着就会调用i2c_driver 的probe函数,在此函数中可以做一系列的初始化工作。)

     i2c_add_driver 函数只是调用了i2c_register_driver函数,在i2c_register_driver里调用了driver_register(&i2c_driver->driver),

注意 driver_register 是linux系统设备模型里面的函数了,每一类linux设备驱动的注册最终都会调用它,传递的参数也由原来的i2c_driver 变成了 device_driver。driver_register做了一些判断,最后调用了 bus_add_driver,然后调用 driver_attach,然后调用

bus_for_each_dev,这个函数就是搜索总线上所有的device,我们这里是i2c总线,也即搜索i2c总线上的i2c_client,调用__driver_attach判断i2c_driver i2c_client是否配备,再调用driver_match_device,driver_match_device最终调用了bus的match函数,我们这也就是i2c bus的match函数,再调用

i2c_match_id,这里到最后的id 配备了,如果配备成功则正常返回到__driver_attach函数,调用driver_probe_device,再调用really_probe,最后调用bus的probe函数,在bus的probe函数里利用to_i2c_driver 将device_driver转换成了i2c_driver,最后调用了i2c_driver的probe函数,这个部分有点复杂,其中我可能也没说的太清楚,建议用source insight自己跟一边,因为其中有很多是公用的,跟一边后,其他驱动注册也就容易了。

1、driver_register把驱动注册到总线

2、驱动注册到总线的实现函数

3、driver_attach()

4、really_probe是我们真正要找的函数

猜你喜欢

转载自www.cnblogs.com/DF11G/p/10248802.html