1、总线式设备驱动组织方式
1.1、总线
(1)外设与Soc连接都是通过接口,不同的接口本质就是通信协议不一样,随着内核要管理的设备越来越多,于是抽象出各种总线,希望将同一接口协议的设备连接在一起管理;
(2)总线分为物理总线和虚拟总线。物理总线是客观上存在的,比如usb总线,是真的有usb协议的设备连接在上面;虚拟总线是软件上模拟的,比如platform总线,没有哪种设备用的是platform协议,是内核为了统一用总线上方式管理设备虚拟出来的总线;
(3)使用总线的好处:实现数据和方法的分离,设备里包含了数据,驱动是硬件操作的方法。比如LED设备,一个设备可能有好几个LED,不同的LED设备之间寄存器数目、操作方法是一样的,不一样的是寄存器的基地址不同、连接的gpio口不同,于是我们可以把不同的数据放在总线上设备信息里(描述设备的结构体里有描述资源),然后传给LED驱动,这样就可以实现一个驱动适配几个设备。
1.2、设备
(1)内核中struct device结构体是硬件设备在内核驱动框架中的抽象,是硬件设备的共性部分;
(2)通常struct device结构体不会单独使用,会被包含在一个表示具体设备的结构体里,比如:struct platform_device结构体;
(3)用device_register()函数向内核驱动框架注册一个设备;
1.3、驱动程序
(1)struct device_driver是驱动程序在内核驱动框架中的抽象;
(2)通常struct device_driver结构体不会单独使用,会被包含在一个表示具体驱动的结构体里,比如:struct platform_driver结构体;
(3)用driver_register()函数向内核驱动框架注册一个驱动;
2、platform总线介绍
(1)CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。专用接口:比如USB接口,有USB设备接在USB接口上,再把多个USB设备挂载在USB总线上;地址总线式:比如操作LED灯,其实就是操作一个GPIO口,GOIO口的寄存器CPU是可以像地址一样直接访问的,不需要接口的概念也很方便读写;
(2)platform总线是虚拟总线,是软件层虚拟出来方便管理设备的,主要是管理类似LED灯这种比较简单,不需要专用接口的设备;
(3)如果没有platform总线,那一部分设备是总线式管理,一部分设备不是总线式管理,管理起来就没有统一化这么方便;
(4)现在的设备很多都有电源管理的需求,也就是休眠和唤醒功能,如果设备都接在总线上,我们可以直接调用总线的休眠/唤醒函数,就将挂载在总线上的所有设备都休眠/唤醒;
3、platform总线的注册
3.1、函数调用关系
kernel_init();
do_basic_setup();
driver_init();
platform_bus_init();
device_register(&platform_bus);
bus_register(&platform_bus_type);
(1)kernel_init()函数是内核的1号进程,在内核的启动阶段被kernel_thread()函数调用去创建进程;
(2)device_register(&platform_bus):注册platform设备,将来在会看到/sys/devices/platform目录;
(3)bus_register(&platform_bus_type):向总线系统注册platform总线,将来会看到/sys/bus/platform目录;
3.2、struct bus_type结构体
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; //总线私有数据
};
(1)name:总线的名字;
(2)match:总线的匹配函数,就是总线上的设备和驱动如何匹配,这是总线最重要的函数;
(3)该结构体是内核中描述一种总线的结构体;
3.3、platform_bus全局变量 & platform_bus_type全局变量
struct device platform_bus = {
.init_name = "platform",
};
struct bus_type platform_bus_type = {
.name = "platform", //总线的名字
.dev_attrs = platform_dev_attrs, //总线下设备的属性
.match = platform_match, //总线上设备和驱动的匹配函数
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops, //电源管理相关
};
重要的是name和match,我们在"/sys/bus"目录下看到platform文件夹就是因为在注册总线时这里赋值为"platform";
4、向platform总线注册设备
4.1、struct platform_device结构体
struct platform_device {
const char * name; //设备的名字
int id; //设备的标号:有可能存在好几个名字一样的设备,这时就要靠标号来区分
struct device dev; //内核中用来表示设备的基础结构体
u32 num_resources; //设备占用资源的数量
struct resource * resource; //设备占用资源的具体说明
const struct platform_device_id *id_entry; //用来保存匹配上的struct platform_driver结构体中的id_tabele中变量
/* arch specific additions */
struct pdev_archdata archdata;
};
4.2、设备的资源描述
struct resource {
resource_size_t start; //表示资源的起始值
resource_size_t end; //表示资源的最后一个字节的地址, 如果是中断,end和satrt相同
const char *name; // 资源的名字,可不写
unsigned long flags; //资源的类型
struct resource *parent, *sibling, *child; //资源的关联性
};
flags的类型说明
#define IORESOURCE_MEM 0x00000200 //内存
#define IORESOURCE_IRQ 0x00000400 //中断
4.3、platform总线设备注册函数
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
4.4、向platform总线注册设备的函数调用
//获取开发板对应的struct machine_desc结构体,将开发板的初始化
//函数赋值给init_machine全局变量
start_kernel()
struct machine_desc *mdesc;
mdesc = setup_machine(machine_arch_type);
init_machine = mdesc->init_machine;
//在customize_machine()函数中调用init_machine函数指针,也就是调用mdesc->init_machine函数
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine);
//X210开发板的mdesc->init_machine函数
//目录:arch/arm/mach-s5pv210/mach-x210.c
smdkc110_machine_init
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));
(1)arch_initcall(customize_machine):把customize_machine放到".initcall3,init"段,在内核启动过程中会被调用;
(2)smdkc110_devices:是一个struct platform_device类型的数组,里面记录了内核需要向platform总线注册的所有设备;
(3)效果:在内核启动过程中,已经向platform总线注册了设备;
(2)怎么解析得到开发板对应的struct machine_desc结构体,参见博客:《内核启动过程中机器码的确定》;
5、向platform总线注册驱动
5.1、struct 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;
};
5.2、platform总线驱动注册函数
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; //这个很关键,说明这个驱动是注册到platform总线上的
if (drv->probe)
drv->driver.probe = platform_drv_probe; //总线的探测函数,这个函数最终调用的是我们注册驱动时的drv->probe函数
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
(1)将我们构建的struct platform_driver结构体调用driver_register()函数注册到内核;
(2)driver_register()函数是内核统一的驱动注册函数,但是在注册前对总线信息、探测函数进行了赋值,说明这个驱动是注册到platform总线上的;
6、驱动和设备的匹配 & 驱动的prob函数调用
6.1、platform总线上设备和驱动的匹配函数
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);
/* match against the id table first */
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);
}
(1)函数的功能就是通过名字来匹配设备和驱动,是platform总线的匹配函数,在注册platform总线时提供;
(2)platform_match_id:检查驱动id_table中的名字和设备的名字是否有一样的,因为有的驱动同时支持好几个名字的设备;
(3)strcmp(pdev->name, drv->name):直接比较驱动的名字和设备的名字是否一样;
(4)platform_match()函数被赋值给platform_bus_type.match,在platform_bus_type变量定义时赋值,是platform总线的设备和驱动匹配函数;
6.2、驱动的注册
platform_driver_register();
driver_register();
bus_add_driver();
driver_attach();
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //将驱动的名字和platform总线上所有的设备名字进行一一比对
fn(dev, data); //fn函数指针就是__driver_attach()函数
driver_match_device(); //判断驱动的名字和设备的名字是否匹配,不匹配就返回
drv->bus->match(); //drv->bus->match函数指针就是platform_match()函数
driver_probe_device();
really_probe();
drv->probe(); //调用我们驱动的probe函数
6.3、整体思路整理
(1)platform总线上的设备是在内核启动阶段就已经注册了,所以设备的注册是早于驱动的注册;
(2)驱动在注册时会逐个和platform总线上的设备匹配,调用platform_match()函数进行名字的匹配;
(3)如果驱动和设备的名字能够匹配上,则调用驱动的probe函数;
(4)如果platform总线上有多个设备的名字和驱动程序匹配,则驱动的probe函数会被调用多次,在sysfs中会有后缀来区分
这几个设备;
7、platform设备如何传递数据给platform驱动
//设备要传给驱动的数据
static struct s3c24xx_led_platdata mini2440_led1_pdata = {
.name = "led1",
.gpio = S3C2410_GPB(5),
.flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
.def_trigger = "heartbeat",
};
//表示platform总线的设备结构体
static struct platform_device mini2440_led1 = {
.name = "s3c24xx_led",
.id = 1,
.dev = {
.platform_data = &mini2440_led1_pdata, //要传给驱动的数据
},
};
//驱动的prob函数
static int s3c24xx_led_probe(struct platform_device *dev)
{
······
struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; //解析出设备传递的数据
······
}
//表示platform总线的驱动结构体
static struct platform_driver s3c24xx_led_driver = {
.probe = s3c24xx_led_probe,
.remove = s3c24xx_led_remove,
.driver = {
.name = "s3c24xx_led",
.owner = THIS_MODULE,
},
};
platform总线设备注册时将要传给驱动的数据一起注册,保存在static struct platform_device.dev.platform_data指针变量中;驱动会在prob函数中将其解析出来;