android switch模块

发布时间:2017-1-19 6:12:07 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"android switch模块",主要涉及到android switch模块方面的内容,对于android switch模块感兴趣的同学可以参考一下。

Android新增了一个switch处理模块,但是没有说明其具体用途,这里将对该模块进行详细的分析。switch是Android引进的一个新驱动,用于检测一些开关量。比如检测耳机插入和USB设备插入等。 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的状态。 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事件。 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模块的完整分析就结束了。

上一篇:note : IRP hook on R0
下一篇:Visual Basic COM基础讲座之

相关文章

相关评论