当前位置: 首页 > 工具软件 > switchClass > 使用案例 >

android switch模块

百里丁雨
2023-12-01

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


 类似资料: