android switch模块

来源:互联网 发布:产品经理和数据分析 编辑:程序博客网 时间:2024/03/29 16:20

原文地址:http://blog.csdn.net/wh_19910525/article/details/11692875

Android新增了一个switch处理模块,但是没有说明其具体用途,这里将对该模块进行详细的分析。switch是Android引进的一个新驱动,用于检测一些开关量。比如检测耳机插入和USB设备插入等。

  1. Switch的构架原理

switch模块包含两部分内容:首先是switchclass,它在Android中是作为一个module来实现的,可以进行动态加载;其次是switchclass中的一个具体的switch设备switchgpio,它表示针对gpio的一个switch设备,switchgpio 是基于platformdevice框架的,它们的实现分别位于下面两个源代码文件中:

- drivers\switch\switch_class.c

- drivers\swithc\switch_gpio.c

switch的运作方式是在sysfs文件系统中创建相应的entry,用户可以通过sysfs与之交互,也可以通过uevent机制与之交互,从而检测switch的状态。

  1. Switch class的实现

switchclass的实现对应于switch_class.c文件,首先需要分析switch设备的结构体,它位于include/linux/switch.h中,其结构体switch_dev的定义如下:

struct switch_dev {

const char *name;

struct device *dev;

int index;

int state;

ssize_t (*print_name)(structswitch_dev *sdev, char *buf);

ssize_t (*print_state)(structswitch_dev *sdev, char *buf);

};

其中name表示设备的名称;dev表示具体的设备对象;由于系统中可能存在多个switch设备,index则表示该设备是index个被注册的switch设备;state表示当前设备的状态;另外的两个函数指针都是用于操作sysfs文件系统的,其中print_name函数用于在sysfs中显示设备名称不,而print_state函数则用于显示设备的状态。 该结构体非常简单,下面我们继续分析具体的实现机制。

我们同样可以在switch_class.c中发现如下的初始化操作和退出操作:

static int __initswitch_class_init(void)

{

return create_switch_class();

}

static void __exitswitch_clas_exit(void)

{

class_destroy(switch_class);

}

module_init(switch_class_init);

module_exit(switch_class_exit);

整个操作都非常简单,初始化函数switch_class_init会调用create_switch_class来创建一个设备类,其具体实现如下:

static int craete_switch_class(void)

{

if (!switch_class) {

switch_class =class_create(THIS_MODULE, “switch”);

if (IS_ERR(switch_class))

return PTR_ERR(switch_class);

atomic_set(&deivce_count, 0);

}

return 0;

}

该函数通过调用class_create函数来创建一个switch设备类文件,创建之后通过atomic_set函数来设备设备的计数。

执行退出操作时,直接通过class_destroy函数来销毁初始化时创建的设备类。

我们说过,switch_class只是一个供所有具体的switch设备使用的“基础类”,因此,它提供了switch设备注册和缷载的函数switch_dev_register和switch_dev_unregister。这里首先来分析注册函数的实现,定义如下:

int switch_dev_register(structswitch_dev *sdev)

{

int ret;

//检测switch_class是否被创建

if (!switch_class) {

ret = create_switch_class();

if (ret < 0)

return ret;

}

//保存索引

sdev->index =atomic_inc_return(&device_count);

//创建设备

sdev->dev =device_create(switch_class, NULL,MKDEV(0, sdev->index), NULL,sdev->name);

if (IS_ERR(sdev->dev))

return PTR_ERR(sdev->dev);

//创建设备文件用于输出设备状态

ret = device_create_file(sdev->dev,&dev_attr_name);

if (ret < 0)

goto err_create_file_2;

//设置数据

dev_set_drvdata(sdev->dev, sdev);

sdev->state = 0;

return 0;

//出现错误,移除文件

err_create_file_2 :

device_remove_file(sdev->dev,&dev_attr_state);

//出现错误,销毁switch_class

err_create_file_1:

device_destroy(switch_class, MKDEV(0,sdev->index));

printk(KERN_ERR “switch: Failed toregister driver %s\n”, sdev->name);

return ret;

}

EXPORT_SYMBOL_GPL(switch_dev_register);

该函数用于创建一个具体的switch设备,其流程是:首先,判断是否已经创建switch_class,如果没有,则创建switch_class;其次,取得要创建的设备的索引,然后通过device_create创建设备;最后,通过device_create_file函数在sysfs中分别创建两个entry,如果创建失败,则分别删除已经创建的文件或者switch_class,一个用于输出设备状态state;另一个用于输出设备名称name。我们将详细介绍dev_set_drvdata,因为在linux内核中它也非常常见,它是一个内联函数,定义于include/linux/device.h中,代码如下:

static inline voiddev_set_drvdata(struct device *dev, void *data)

{

dev->driver_data = data;

}

所以,上面的switch_dev_register函数中使用它是表示sdev已经赋值到sdev->dev->driver_data中。分析完了注册函数,下面我们来看一下卸载函数switch_dev_unregister,其定义如下:

void switch_dev_unregister(structswitch_dev *sdev)

{

device_remove_file(sdev->dev,&dev_attr_name);

device_remove_file(sdev->dev,&dev_attr_state);

device_destroy(switch_class, MKDEV(0,sdev->index));

dev_set_drvdata(sdev->dev, NULL);

}

EXPORT_SYMBOL_GPL(switch_dev_unregister);

该函数主要用于释放注册时所创建的设备和空间。首先,通过device_remove_file函数删除用于输出状态和名称的entry;然后,销毁switch_class;最后,再次使用dev_set_drvdata将sdev->dev->driver_data设置为NULL;

在初始化时我们创建了输出设备状态和名称的文件,那么我们就需要实现显示名称和状态的两个函数state_show和name_show。当用户读取sysfs中对应的switchentry(/sys/class/#dev_name/name和/sys/class/#dev_name/state)时,系统会自动调用这两个函数为用户返回switch设备的名称和状态,其函数定义如下:

static ssize_t state_show(structdevice *dev, struct device_attribute *attr, char *buf)

{

//得到switch_dev设备数据

struct switch_dev *sdev = (structswitch_dev *)dev_get_drvdata(dev);

//安全性检查

if (sdev->print_state) {

//输出状态

int ret = sdev->print_state(sdev,buf);

if (ret >= 0)

return ret;

}

return sprintf(buf, “%d\n”,sdev->state);

}

static ssize_t name_show(struct device*dev, struct device_attribute *attr, char *buf)

{

struct switch_dev *sdev = (structswitch_dev *)dev_get_drvdata(dev);

if (sdev->print_name) {

//输出名字

int ret = sdev->print_name(sdev,buf);

if (ret >= 0)

return ret;

}

return sprintf(buf, “%s\n”,sdev->name);

}

static DEVICE_ATTR(state, S_IRUGO |S_IWUSR, state_show, NULL);

static DEVICE_ATTR(name, S_IRUGO |S_IWUSR, name_show, NULL);

这两个函数中都使用了dev_get_drvdata来取得switch设备数据,输出状态使用了print_state函数,输出名称使用了print_name函数。不知道大家是否还有印象,这两个函数是定义在switch_dev中的两个函数指针。

既然switch设备有状态,那么就需要对状态进行操作,主要包括获取状态和设置状态。获取状态的操作很简单,它是switch.h中的一个内联函数,直接返回设备的状态,定义如下:

static inline intswitch_get_state(struct switch_dev *sdev)

{

return sdev->state;

}

设置状态的操作则稍微复杂一点,下面是设置设备状态的函数switch_set_state的实现:

voidswitch_set_state(struct switch_dev *sdev, int state)

{

charname_buf[120];

charstate_buf[120];

char*prop_buf;

char *envp[3];

int env_offset= 0;

int length;

//判断当前状态

if(sdev->state != state) {

//改变状态

sdev->state= state;

prop_buf =(char *)get_zeroed_page(GFP_KERNEL);

if (prop_buf){

//显示名称

length =name_show(sdev->dev, NULL, prop_buf);

if (length >0) {

if(prop_buf[length – 1] == '\n')

prop_buf[length– 1] = 0;

sprintf(name_buf,sizeof(name_buf),”SWITCH_NAME=%s”, prop_buf);

envp[env_offset++]= name_buf;

}

//显示状态

length =state_show(sdev->dev, NULL, prop_buf);

if (length >0) {

if(prop_buf[length – 1] == '\n')

prop_buf[length– 1] = 0;

snprintf(state_buf,sizeof(state_buf), “SWITCH_STATE=%s”, prop_buf);

envp[env_offset++]= state_buf;

}

envp[env_offset]= NULL;

//触发uevent事件

kobject_uevent_env(&sdev->dev->kobj,KOBJ_CHANGE, envp);

free_page((unsignedlong)prop_buf);

} else {

printk(KERN_ERR“out of memory in switch_set_state\n”);

kobject_uevent(&sdev->dev->kobj,KOBJ_CHANGE);

}

}

}

EXPORT_SYMBOL_GPL(switch_set_state);

该函数用于设置当前设备的状态。开始之前,首先检测当前设备的状态是否与要设置的状态相同,如果相同,则不需要再次设置;否则,调用get_zeroed_page()返回一片已经用0擦写过的内存页,并将其转化为指定的类型(char*),用于显示状态和名称,并将其写入到state_buf和name_buf缓冲区中一并作为uevent事件的信息,以用来通知用户的当前switch设备的名称和状态,最后通过kobject_uevent_env和参数envp发送uevent事件。

  1. Gpio switch设备驱动

上面分析了switch模块中switchclass的实现,下面就来分析一个具体的gpio的switch设备驱动的实现。首先,我们来看一下其设备信息的结构体,如下所示;

struct gpio_switch_data {

struct switch_dev sdev;

unsigned gpio;

const char *name_on;

const char *name_off;

const char *state_on;

const char *state_off;

int irq;

struct work_struct work;

};

该结构体非常简单,这里需要说明的是其中4个char*的成员变量,它们是设备名称和状态的开关,判断是否需要输出设备的名称和状态。sdev表示一个switch设备;gpio表示gpio电平;irq表示gpio终端指示;work用于表示gpio_switch_work工作,具体分析时我们还会介绍其细节。另外,还有一个结构体gpio_switch_platform_data用来储存gpio_switch设备的相关数据,其定义如下:

struct gpio_switch_platform_data {

const char *name; //设备名称

unsigned gpio; //电平

const char *name_on;

const char *name_off;

const char *state_on;

const char *state_off;

};

该结构体的数据和gpio_switch_data中的数据所表达的意思几乎差不多,只是多了一个设备的名称,其实就是表示gpioswitch设备的platform_data数据。下面我们将分析其具体实现。 

其初始化和退出过程就不详细介绍了,具体实现如下:

static struct platform_drivergpio_switch_driver = {

.probe = gpio_switch_probe,

.remove =__devexit_p(gpio_switch_remove),

.driver = {

.name = “switch-gpio”,

.owner = THIS_MODULE,

},

};

static int __initgpio_switch_init(void)

{

returnplatform_driver_register(&gpio_switch_driver);

}

static void __exitgpio_switch_exit(void)

{

platform_driver_unregister(&gpio_switch_driver);

}

module_init(gpio_switch_init);

module_exit(gpio_switch_exit);

由于gpioswitch是基于platformdevice/driver框架的,因此初始化时会通过gpio_switch_init来调用platform_driver_register,然后进入gpio_switch_driver所指定的gpio_switch_probe函数中完成初始化过程。gpio_switch_driver中还指定了驱动的名称和owner,以及设备退出时需要处理gpio_switch_remove。因为我们说过,switchclass在Android中是作为一个module来实现的,所以”.owner”被指定为THIS_MODULE。

我们主要来分析初始化函数gpio_switch_probe的实现,如下所示:

static int gpio_switch_probe(structplatform_device *pdev)

{

//取得gpioswitch的platform_data数据的使用权

struct gpio_switch_platform_data*pdata = pdev->dev.platform_data;

struct gpio_switch_data *switch_data;

int ret = 0;

if (!pdata)

return -EBUSY;

//创建gpio_switch

switch_data = kzalloc(sizeof(structgpio_switch_data), GFP_KERNEL);

if (switch_data)

return -ENOMEM;

//初始化gpio_switch

switch_data->sdev.name =pdata->name;

switch_data->gpio = pdata->gpio;

switch_data->name_on =pdata->name_on;

switch_data->name_off =pdata->name_off;

switch_data->state_on =pdata->state_on;

switch_data->state_off =pdata->state_off;

switch_data->sdev.print_state =switch_gpio_print_state;

//注册switch设备switch_dev

ret =switch_dev_register(&switch_data->sdev);

if (ret < 0)

goto erro_request_gpio;

//设置gpio方向为输入

ret =gpio_direction_input(switch_data->gpio);

if (ret < 0)

goto err_set_gpio_input;

//指定gpio_switch_work

INIT_WORK(&switch_data->work,gpio_switch_work);

//为gpio分配中断

switch_data->irq =gpio_to_irq(switch_data->gpio);

if (switch_data->irq < 0) {

ret = switch_data->irq;

goto err_detect_irq_num_failed;

}

//指明中断服务程序

ret = request_irq(switch_data->irq,gpio_irq_handle, IRQF_TRIGGER_LOW, pdev->name, switch_data);

if (ret < 0)

goto err_request_irq;

//初始化gpio_switch_work

gpio_switch_work(&switch_data->work);

return 0;

//错误处理

err_request_irq:

err_detect_irq_num_failed:

err_set_gpio_input:

gpio_free(switch_data->gpio);

err_request_gpio:

switch_dev_unregister(&switch_data->sdev);

err_switch_dev_register:

kfree(switch_data);

return ret;

}

关于初始化函数的原理和要点,注解已经写得很清楚,这里就不再具体分析了。初始化的过程主要包括以下几个步骤:

1)获取gpio数据使用权。

2)设置gpio方向为输入

3)注册switch_dev设备

4)为gpio分配中断,并指定中断服务程序;

5)初始化gpio_switch_work;

6)读取gpio初始状态。

同理,退出函数也就很简单了,定义如下:

static int __devexitgpio_switch_remove(struct platform_device *pdev)

{

struct gpio_switch_data *switch_data= platform_get_drvdata(pdev);

//清除gpio_switch_work

cancel_work_sync(&switch_data->work);

//释放gpio

gpio_free(switch_data->gpio);

//缷载gpio_switch_data

switch_dev_unregister(&switch_data->sdev);

//释放空间

kfree(switch_data);

return 0;

}

初始化时我们指定了中断服务程序,当GPIO触发中断事件时,就会进入中断服务程序进行处理,其定义如下:

static irqreturn_tgpio_irq_handler(int irq, void *dev_id)

{

struct gpio_switch_data *switch_data= (struct gpio_switch_data *)dev_id;

schedule_work(&switch_data->work);

return IRQ_HANDLED;

}

该函数很简单,取得gpio_switch_data并执行work。这里的work就是我们在初始化时指定的gpio_switch_work,其处理方式如下:

static void gpio_switch_work(structwork_struct *work)

{

int state;

struct gpio_switch_data *data =container_of (work, struct gpio_switch_data, work);

//读取gpio

state = gpio_get_value(data->gpio);

switch_set_state(&data->sdev,state);

}

该函数的处理过程很简单,先直接读取gpio电平,取得状态;然后通过switch_set_state来设置和改变状态,这时便会调用我们实现的switch_gpio_print_state和switch_gpio_print_name函数。但是我们发现,源代码中并没有实现switch_gpio_print_name函数,因此,这里只关心设备的状态,名称在注册之后没有更改过,暂时也就不会去处理它了。从前面的name_show函数的实现我们可以看到,如果没有实现switch_gpio_print_name函数,设备的名字则会被输出到name_show函数的参数buf的缓冲区里,但这并不影响什么。

switch_gpio_print_state的具体实现如下:

static ssize_tswitch_gpio_print_state(struct switch_dev *sdev, char *buf)

{

struct gpio_switch_data *switch_data=

container_of (sdev, structgpio_switch_data, sdev);

const char *state;

if (switch_get_state(sdev))

state = switch_data->state_on;

else

state = switch_data->state_off;

if (state)

return sprintf(buf, “%s\n”,state);

return – 1;

}

该函数通过状态开(state_on)关(state_off)来确定是否将GPIO状态输出到sysfs。大家应该明白状态开关的用处了吧,名称的状态开关的作用也是一样,只不过这里没有实现罢了。到这里,对switch模块的完整分析就结束了。


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 在异地学驾照怎么办 户口迁移后 驾照怎么办 无驾照开车违章怎么办 身份证地址错了怎么办 学生迁户口学籍怎么办 买新车临时牌怎么办 户口迁移换驾照怎么办 户口迁了身份证怎么办 户口迁移了医保怎么办 房屋卖了户口怎么办 驾照过期注销了怎么办 负全责不赔偿怎么办 青岛驾驶证过期了怎么办 驾驶证过了年检怎么办 驾驶证审证逾期怎么办 d驾驶证3年没捡怎么办 c1驾驶证3年没审怎么办 驾驶证换证外地怎么办 考驾驶证快到期怎么办 驾照到期人在国外怎么办 驾驶证3年没审怎么办c3 驾驶证几年没审怎么办 驾照体检过期了怎么办 b2驾照超过年检怎么办 驾驶证过审一年怎么办 驾照一年未年审怎么办 b驾照年审过期怎么办 摩托车驾驶证过期一年怎么办 驾驶证过期一年半怎么办 驾照过期了几天怎么办 驾照过期超过一年怎么办 考试驾照过期了怎么办 驾校考试过期了怎么办 驾驶证明过期了怎么办 驾驶证年过期了怎么办 驾照过期六个月怎么办 移动预约号码取消怎么办 身份证换地址驾驶证怎么办 刚来成都怎么办居住证 我在外地怎么办身份证 身份证丢在外地怎么办