TimedGPIO驱动程序分析

TimedGPIO驱动程序是android系统基于linux内核新增加的一类驱动程序,这类驱动程序主要是运用了内核定时器,与内核定时器进行绑定,使得控制GPIO口的高低电平与时间打上关系,既可以实现在一定的时间实现GPIO口为高或者低电平。TimedGPIO驱动被实现为平台设备驱动,TimedGPIO驱动源码位于如下目录:\kernel\drivers\staging\android

TimedGPIO驱动程序主要包括如下几个文件:

Timed_gpio.c

Timed_gpio.h

Timed_output.c

Timed_output.h

Timed_gpio.c文件为具体的驱动程序,Timed_output.c为向sys文件系统注册类的框架代码,

下面将具体分析每一个文件的作用及实现的具体功能。

首先分析注册类的框架代码:Timed_output.cTimed_output.h

Timed_output.h文件分析:

structtimed_output_dev{

constchar *name;

void (*enable)(structtimed_output_dev*sdev,inttimeout);

int (*get_time)(structtimed_output_dev*sdev);

structdevice *dev;

int index;

int state;

};

externinttimed_output_dev_register(structtimed_output_dev*dev);

externvoidtimed_output_dev_unregister(structtimed_output_dev*dev);

Timed_output.h文件主要定义了一个结构体timed_output_dev设备结构体,该结构体表示一个具体的设备,

Name:代表TimeGPIO设备的名字,enable:为一个函数指针,主要用于设置定时器的过期时间,

Enable:用于获取离过期还剩余的时间。Index:为设备索引号,代表同一名字的设备的数量,state带表当前设备的状态值。

timed_output_dev_register,timed_output_dev_unregister这两个函数声明用于timed_output设备的注册和卸载。

Timed_output.c文件分析:

该文件主要用于向系统注册timed_output驱动程序框架,其中主要实现了Timed_output.h文件中定义的结构体及函数。

timed_output框架注册函数的实现,这个函数用于将驱动程序注册到kernel中,后面分析TimedGPIO驱动的具体实现时会调用此函数向系统注册TimedGPIO驱动。

inttimed_output_dev_register(structtimed_output_dev*tdev)

{

intret;

if(!tdev||!tdev->name||!tdev->enable||!tdev->get_time)

return-EINVAL;

ret=create_timed_output_class();调用此函数在sys/class下生成timed_output类

if(ret<0)

returnret;

tdev->index=atomic_inc_return(&device_count);

tdev->dev=device_create(timed_output_class,NULL,

MKDEV(0,tdev->index),NULL,tdev->name);

if(IS_ERR(tdev->dev))

returnPTR_ERR(tdev->dev);

ret=device_create_file(tdev->dev,&dev_attr_enable);

if(ret<0)

gotoerr_create_file;

dev_set_drvdata(tdev->dev,tdev);

tdev->state=0;

return0;

err_create_file:

device_destroy(timed_output_class,MKDEV(0,tdev->index));

printk(KERN_ERR"timed_output:Failedtoregisterdriver%s\n",

tdev->name);

returnret;

}

此函数用于在sys/class下面创建类,类的名字为timed_output

staticintcreate_timed_output_class(void)

{

if(!timed_output_class){

timed_output_class=class_create(THIS_MODULE,"timed_output");

if(IS_ERR(timed_output_class))

returnPTR_ERR(timed_output_class);

atomic_set(&device_count,0);

}

return0;

}

其中下面两个函数最为关键,是内核空间和用户空间的传值过程的具体实现函数,

enable_show函数调用get_time函数并将返回的剩余时间写入buf并传递到用户空间。

staticssize_tenable_show(structdevice*dev,structdevice_attribute*attr,

char*buf)

{

structtimed_output_dev*tdev=dev_get_drvdata(dev);

intremaining=tdev->get_time(tdev);

returnsprintf(buf,"%d\n",remaining);

}

enable_store函数用于将用户空间传递来的buf值写入内核空间。

staticssize_tenable_store(

structdevice*dev,structdevice_attribute*attr,

constchar*buf,size_tsize)

{

structtimed_output_dev*tdev=dev_get_drvdata(dev);

intvalue;

if(sscanf(buf,"%d",&value)!=1)

return-EINVAL;

tdev->enable(tdev,value);

returnsize;

}

Timed_gpio.h文件分析:

#ifndef_LINUX_TIMED_GPIO_H

#define_LINUX_TIMED_GPIO_H

#defineTIMED_GPIO_NAME"timed-gpio"//Time_GPIO驱动的名字,将显示在/sys/class/timed_output目录下

structtimed_gpio{

constchar*name;//GPIO的名字

unsigned gpio;//具体的GPIO管脚

int max_timeout;//最大的超时时间

u8 active_low;//IO口电平状态表示位

};

timed_gpio结构体仅用于定义单个的GPIO的相关信息

gpio_platform_data结构体用于定义一组GPIO的相关信息

structtimed_gpio_platform_data{

int num_gpios;

structtimed_gpio*gpios;

};

#endif

下面将分析具体的Timed_gpio驱动程序

timed_gpio_driver定义如下:

该函数指明了具体的初始化函数(pore)和移除函数(remove)以及驱动的名字额模块。

staticstructplatform_drivertimed_gpio_driver={

.probe =timed_gpio_probe,

.remove =timed_gpio_remove,

.driver ={

.name =TIMED_GPIO_NAME,

.owner =THIS_MODULE,

},

};

调用platform_driver_register函数向kernel注册平台驱动

staticint__inittimed_gpio_init(void)

{

returnplatform_driver_register(&timed_gpio_driver);

}

timed_gpio探测函数

staticinttimed_gpio_probe(structplatform_device*pdev)

{

structtimed_gpio_platform_data*pdata=pdev->dev.platform_data;

structtimed_gpio*cur_gpio;

structtimed_gpio_data*gpio_data,*gpio_dat;

inti,j,ret=0;

if(!pdata)

return-EBUSY;//为pdata->num_gpios个GPIO分配内存空间

gpio_data=kzalloc(sizeof(structtimed_gpio_data)*pdata->num_gpios,

GFP_KERNEL);

if(!gpio_data)

return-ENOMEM;

for(i=0;i<pdata->num_gpios;i++){

cur_gpio=&pdata->gpios[i];

gpio_dat=&gpio_data[i];

hrtimer_init(&gpio_dat->timer,CLOCK_MONOTONIC,//初始化定时器

HRTIMER_MODE_REL);

gpio_dat->timer.function=gpio_timer_func;//定时器回调函数

spin_lock_init(&gpio_dat->lock);

gpio_dat->dev.name=cur_gpio->name;

gpio_dat->dev.get_time=gpio_get_time;

gpio_dat->dev.enable=gpio_enable;

ret=gpio_request(cur_gpio->gpio,cur_gpio->name);//申请GPIO

if(ret>=0){

ret=timed_output_dev_register(&gpio_dat->dev);//调用timed_output框架注册函数

if(ret<0)

gpio_free(cur_gpio->gpio);

}

if(ret<0){

for(j=0;j<i;j++){

timed_output_dev_unregister(&gpio_data[i].dev);

gpio_free(gpio_data[i].gpio);

}

kfree(gpio_data);

returnret;

}

gpio_dat->gpio=cur_gpio->gpio;

gpio_dat->max_timeout=cur_gpio->max_timeout;

gpio_dat->active_low=cur_gpio->active_low;

gpio_direction_output(gpio_dat->gpio,gpio_dat->active_low);//初始化GPIO的输出值

}

platform_set_drvdata(pdev,gpio_data);

return0;

}

初始化过程:

1.首先调用kzalloc函数为GPIO分配内存空间

2.调用hrtimer_init函数初始化化内核定时器

3.设置GPIO的enable函数为gpio_enable

4.设置GPIO的get_time函数为gpio_get_time

5.调用timed_output_dev_register函数注册设备驱动。

6.初始化timed_gpio_data结构体

7.调用gpio_direction_output函数设置GPIO的初始值。

GPIO驱动移除函数,调用timed_output_dev_unregister卸载驱动程序

staticinttimed_gpio_remove(structplatform_device*pdev)

{

structtimed_gpio_platform_data*pdata=pdev->dev.platform_data;

structtimed_gpio_data*gpio_data=platform_get_drvdata(pdev);

inti;

for(i=0;i<pdata->num_gpios;i++){

timed_output_dev_unregister(&gpio_data[i].dev);

gpio_free(gpio_data[i].gpio);

}

kfree(gpio_data);

return0;

}

功能回调函数gpio_timer_func分析:定时器超时后将执行此函数,此函数根据active_low的值来设置GPIO的高低电平。

staticenumhrtimer_restartgpio_timer_func(structhrtimer*timer)

{

structtimed_gpio_data*data=

container_of(timer,structtimed_gpio_data,timer);

gpio_direction_output(data->gpio,data->active_low?1:0);

returnHRTIMER_NORESTART;

}

gpio_enable函数为关键函数接受用户空间传过来的value值用于在一定时间里控制GPIO

staticvoidgpio_enable(structtimed_output_dev*dev,intvalue)

{

structtimed_gpio_data *data=

container_of(dev,structtimed_gpio_data,dev);

unsignedlong flags;

spin_lock_irqsave(&data->lock,flags);

/*cancelprevioustimerandsetGPIOaccordingtovalue*/

hrtimer_cancel(&data->timer);

gpio_direction_output(data->gpio,data->active_low?!value:!!value);

if(value>0){

if(value>data->max_timeout)

value=data->max_timeout;

//启动定时器函数

hrtimer_start(&data->timer,

ktime_set(value/1000,(value%1000)*1000000),

HRTIMER_MODE_REL);

}

spin_unlock_irqrestore(&data->lock,flags);

}

到这里相信大家对TimeGPIO驱动已经用了深刻的印象和认识。下面将用一幅图来说明整个TimeGPIO驱动的调用过程。以总结回顾前面的分析。

读者可以根据我的分析结合源代码具体了解每一步的调用过程。

注:以上整个过程实现了并创建了设备节点/sys/class/timed_output/timed-gpio