首页 > 代码库 > linux驱动之platform平台总线工作原理(一)
linux驱动之platform平台总线工作原理(一)
5、5、4、platform平台总线工作原理
5、5、4、1、何为平台总线
(1)platform总线相对于i2c、usb、spi、pci等总线是不同的,他们属于物理总线,platform总线是属于虚拟总线、抽象出来的,platform总线下的设备并不对应于真实存在的一种设备,这种总线在真实的物理是是没有的。比如i2c在物理上有i2c总线,但是platform总线在物理上并没有这种总线。
(2)CPU和外部通信时,有两种连接方式,一种叫做地址总线式连接,一种叫做专用接口式连接,有一些设备是通过地址总线连接的,cpu我们用的是32位cpu,可寻址空间有4G,有寄存器的,对应于一个真是存在的物理地址,而在哈弗架构中,寄存器又和内存同一编址了,所以控制这些寄存器则可以通过cpu进行寻址进行找到从而进行写寄存器,也就是写内存来操作,这些个方式就是为地址总线式方式进行访问的,但有一些设备是不能通过地址总线式进行访问的,不能通过地址总线式的方式进行链接的,比如soc外面的nandflash,nandflash和cpu之间就不是通过地址总线进行链接的,而是通过一个专用的nand用的接口来进行链接的,还有sd卡则是通过sd卡的协议来进行链接的,还有usb设备,usb设备是通过usb的协议接口,来和cpu进行链接的,这些属于专用接口,专用接口和地址总线式的设备访问起来的区别在于,地址总线式的设备,将来进行访问时,我们直接通过地址来访问设备里内容就可以了,比如像dm9000网卡,我们就可以直接通过cpu的地址,来操作地址的方法,像写寄存器一样的就可以操作到dm9000网卡中的寄存器,因为dm9000就是扩展到地址总线的接口中去的,dm9000中的寄存器的地址通过地址映射的接口接到了cpu的地址总线中,这就是地址总线式接口的。
(3)平台总线设备,就是将扩展到32地址总线上的那一类设备,扩展到地址总线上的设备用平台总线的方式进行访问。平台总线对应地址总线式连接设备,也就是SoC内部集成的各种内部外设,因为这些内部外设,很多的都是扩展到地址总线上去的,那么对应于软件层,就是利用platform平台总线的方式进行链接访问工作的。除了那些不是扩展到cpu地址总线上的,如i2c设备、spi设备、usb设备、他们用的是个自的总线,如i2c总线等,而扩展到cpu地址总线上的设备,用的就是平台总线,在软件上,现在目前大多数的设备都已经被扩展到了地址总线上去,所以platform平台总线用的还是很多的,很多设备会用。
(4)思考:为什么要有平台总线?进一步思考,为什么要有总线的概念?
因为有一些外设他是直接扩展到cpu的地址总线上进行链接的,所以这一类设备可以说根本就没有总线的概念,比如led,蜂鸣器(定时器),这类是可以直接通过地址就能访问到的,他们本没有总线的概念,不想i2c总线,他本身就对应于真实存在的i2c总线设备,但是我们又不能搞的有总线又没有总线,弄的不统一,因为这样会觉得很乱,你还要区分谁有总线,谁没有总线,所以弄的所有设备都有总线,真正的有总线的设备,像i2c设备,你有i2c总线你就用i2c总线的规则去玩,但是没有真正总线的设备,用地址去访问的设备,就给他们提供了一种平台总线,来给他们进行连接使用的,所以我们说平台总线是虚拟的。
总线的概念之所以有,就是为了让总线下面的设备和驱动进行管理起来。无论是虚拟的总线还是真实的总线,他们都有设备和驱动,他们需要被管理,所以有了总线的概念,因为总线在linux中,下面有这着两个分支,一个是设备分支,一个是驱动分支,设备和驱动都有名字,总线利用名字将设备和驱动进行匹配来管理,因为光有设备没有驱动无法工作,光有驱动没有设备也无法工作,但是设备会有很多,驱动会有更多,不能杂乱无章的进行管理,所以有了总线来进行管理,同一个总线下的是一类设备,平台总线下的设备就是扩展到cpu地址总线上去的设备,下面的驱动就是这些设备的驱动。平台总线和其他总线工作上并没有什么区别,因为平台总线就是借鉴与其他总线来实现的,所以我们现在只拿平台总线来进行分析,道理和其他i2c等总线一样
5.5.4.2、平台总线下的两员大将
(1)platform工作体系都定义在/drivers/base/platform.c中,base这里面的文件不需要我们去动的改代码的,是由内核的人写的一种基础架构,platform.c中就是platform的实现原理,他作为内核中的一种机制,他也是被内核人员实现的。他本身的实现我们只需要明白了,看懂了就可以了,不需要自己去写,我们只是用他,怎么去用他来写平台设备和平台设备对应的驱动。总线的目的是为了管理设备和驱动,所以他下面有两个分支,一个是设备,一个是驱动,所以我们研究总线,就是在研究他如何管理他下面的设备和驱动的。我们学的是驱动,就是系统中按上一个设备,则要将对应的驱动和这个设备对应上去,这个是我们学习驱动的目的。所以我们要研究总线下的设备和驱动是怎么写出来的,是怎么找到彼此进行工作的。
(2)两个结构体:platform_device和platform_driver,一个是平台总线的设备结构体,一个是平台总线的驱动结构体。一个设备结构体platform_device,就是这个platform总线下的一个具体的抽象出来的设备,这个结构体类型定义出来的变量,每一个定义出来的变量,就是平台总线中的一种设备,platform_driver结构体类型定义出来的每一个变量,就是平台总线下的每一个驱动,有几个平台总线的设备就定义几个platform_device的变量,有几个平台总线的驱动就定义几个platform_driver的变量
(3)两个接口函数:platform_device_register和platform_driver_register。
用platform_device和platform_driver定义出来了设备和驱动对应的变量后,就用这两个接口函数,分别将定义出来的设备和驱动进行注册,两个都一注册,剩下的就不需要我们来管了
首先来分析一下platform_device结构体:
struct platform_device {
const char *name;//平台总线下面设备的名字
int id;
bool id_auto;
struct devicedev;//每个设备通用的属性的部分的一些内容,被包含在这个结构体中,所以每一个platform_device平台总线的设备里面都需要有一个这个struct device结构体。
u32 num_resources;//设备使用到的resource的个数,什么叫做resource?resource是一种资源,在内核中凡是有限有数量的东西都叫做资源,比如说中断号,中断号就是一种资源 在比如说io,所用到的io的哪一段地址,一般这里的资源是io和中断号
struct resource *resource;//一个确切的资源,比如说一个io用的一个内存地址范围就是一种资源,比如一个中断号就对应一种资源,一个内存地址的范围就是资源,他们都由这个
//代表资源的结构体来定义出来,所以我们就一个设备用到的资源定义成了一个资源数组,资源数组里面有好多个元素,每一个元素都是一个资源,都是
//一个struct resource类型的结构体变量,比如一个设备里面的struct resurce有三个,则是一个有三个元素的struct resource数组,第一个元素可能
//表示这个设备用到的中断号,第二个元素是他用到的内存地址,第三个元素是gpio号,多少到多少,总之来说这个struvt resource就是用来记录当前这
//设备用到了哪些资源的,因为每一个设备用的资源可能是多个,所以每一设备内部定义了一个struct resource类型的结构体指针,也就是资源数组
//而num_resouces则是表示用到的资源个数的,他就是struct resource类型的结构体指针中,数组的元素个数。
//所以struct resource *resource就是这个设备所用到的资源数组的首地址。num_resource就是资源个数,可以看成是数组的索引,这个两个元素加起来
//就告诉我们了资源数组的首地址和资源数组中的元素个数,所以我们可以将这个资源数组中的内容全部取出来了,因为你知道了数组首地址和数组元素个
//数,为什么要定义成两个呢,一个代表数组元素个数,一个指向数组首地址的指针呢,而不是直接定义一个结构体数组呢,那是因为数组的缺陷性,无法
//事先知道数组中的元素个数,如果元素个数一定写死则下次还需要重新更改,这样定义成两个分开的形式,就会弥补这个问题
const struct platform_device_id *id_entry;//这也是一个数组,为了省内存,不让platform_device占用过多内存,所以用的都是指针来指向的方式,而不是直接使用数组的方式
//设备id表,用于好多个设备型号不一样的同一系列产品 ,比如nand这系列产品,可能发布了5中10种nand,但是他们的区别不大,
//只是大小不一样,但是操作是一样,对于驱动来说,一个驱动就可以操作这5款nandflash ,但我们还想知道这5款是怎么回事,
/所以用了一个platform_device_id来作为一个设备id表,这是一个数组,里面放好多id号码,当用驱动时则根据id表中的id号和名字来
//匹配,因为这用于同一系列产品,产品只是细微的不同,而这些不同的细微差异这用这个id表来表示,在nandflash中就用的很明晰了
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;//自留地,留个我们自己用的,可以将设备特有的一些东西放到这里,用来提供扩展性的,因为有可能你的设备是一种特有的,所以可以让你自己
//来扩展
};
因为一个platform_device就对应于一个具体的平台总线下的设备,所以里面肯定要有一个struct device这个结构体,这个结构体是设备的抽象,是所有设备共有的信息部分,所以没一个设备都需要有一个这个struct device结构体,因为要用到他这个设备共有的部分
在分析platform_driver结构体:
struct platform_driver {
int (*probe)(struct platform_device *);//驱动探测函数,当我们的驱动找到一个设备的时候,这个驱动就会调用这个probe函数来试图去探测这个设备是不是我这个驱动对应的设备
//怎么来知道这个设备是不是我这个驱动要控制的呢,很简单,就写一个和这个设备进行简单通信的代码就行(一般就
//是写对这个设备的初始化代码),看这个设备有没有什么反馈
//信息,反馈信息是否正确,就知道了这个设备是不是我这个驱动要去控制的了,类似insmod时对应的函数,类似于对设备的一个初始化操作
//可以看成是对设备的安装。但要先探测是当前驱动能控制的设备,在去初始化这个设备
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;//设备id表,因为一个驱动可能知识好多个设备,比如nand驱动,nand一个驱动可能支持好多个设备,那么就需要将支持的那些设备
//全部写到这个驱动的id表中,将来只要设备id表中的id和这个驱动id表中的id对应起来,就都能够使用这个驱动来操作
};
这个platform_driver就是总线下对应的驱动的模板了,里面的那些函数指针将来要指向的那些函数,就是我们写驱动时,要写的函数了,函数写好后,将函数指针指向写好的函数,然后调用注册
用的接口函数,来注册这个写好填充好的platform_driver结构体。
本文出自 “whylinux” 博客,谢绝转载!
linux驱动之platform平台总线工作原理(一)