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

android switch_dev

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

  2. 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模块的完整分析就结束了。

 

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


 类似资料:

相关阅读

相关文章

相关问答