前面我们已经提到过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模块的完整分析就结束了。