linux 驱动之input子系统(gpio-keys)实现

1.概述

  Gpio-keys 是基于input子系统实现的一个通用按键驱动,该驱动也符合linux驱动实现模型,即driver和device分离模型.一般按键驱动,都是基于gpio-keys进行开发的.

2. gpio-keys 代码分析(基于 linux 4.14.40)

 (1)整体来说分为以下四步

static int gpio_keys_probe(struct platform_device *pdev)
{
 
    ...........
    ...........
    /*第一,从设备树获取button,即gpio相关控制方式和属性*/
	pdata = gpio_keys_get_devtree_pdata(dev);
    ...........
    ...........
    /*第二,分配一个input 设备*/
	input = devm_input_allocate_device(dev);
    ...........
    ...........
    /*第三,注册gpio相关信息和分配资源*/
	for (i = 0; i < pdata->nbuttons; i++) {
		error = gpio_keys_setup_key(pdev, input, ddata,
					    button, i, child);
    }
    ...........
    ...........
    /*第四,注册input 设备到kernel*/
	error = input_register_device(input);
    ...........
    ...........
	return 0;
}

 (2)从注册一个input设备来看,二,四步是一个通用的步骤,第三步和第一步是关键,先看第三步.

static int gpio_keys_setup_key(struct platform_device *pdev,
				struct input_dev *input,
				struct gpio_keys_drvdata *ddata,
				const struct gpio_keys_button *button,
				int idx,
				struct fwnode_handle *child)
{

    .............
    .............
    /*第一,请求gpio并获取irq 资源*/
	error = devm_gpio_request_one(dev, button->gpio, flags, desc);

	irq = gpiod_to_irq(bdata->gpiod);
    .............
    .............
    /*第二,注册一个delayed work任务,这个任务的目的,当中断发生时,需要向
    通过input子系统发消息,这个过程有可能会sleep或者schedule,所以必须通一个
    delay work去完成这件事.
    */
    INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
	isr = gpio_keys_gpio_isr;
	irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;}
    .............
    .............
    /*第三,注册中断*/
	error = devm_request_any_context_irq(dev, bdata->irq, isr, irqflags,
					     desc, bdata);

	return 0;
}

 (3)下面来看中断和delay work做了什么事

static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
	struct gpio_button_data *bdata = dev_id;
    ...............
    ...............
    /*中断中,唯一做的事,就是把delay work加入到了system_wq队列中去.还做了一个延迟
    这个延迟目的是为了去抖动.*/
	mod_delayed_work(system_wq,
			 &bdata->work,
			 msecs_to_jiffies(bdata->software_debounce));

	return IRQ_HANDLED;
}

static void gpio_keys_gpio_work_func(struct work_struct *work)
{
    /*调用上报消息,接着向下看*/
    ...........
	gpio_keys_gpio_report_event(bdata);
    ...........
}

static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
    ............
    /*第一步,获取gpio 状态.*/
	state = gpiod_get_value_cansleep(bdata->gpiod);
	............
    /*发送消息到input*/
    input_event(input, type, *bdata->code, state);
	input_sync(input);
    ............
}

 (4)下面请看第一步工作,以及设备树的配置

 (红色为必要部分)

  •          label : 一个key 的别名 
  •     linux,code :  键值,作为按键的唯一识别号
  •     linux,input-type: input类型(EV_KEY(按键), EV_ABS(相对坐标), EV_REL(绝对坐标)...) 默认为EV_KEY
  •          wakeup-source: 与pm相关,默认为disable
  •          linux,can-disable:是否共享中断line ((默认)0:shared, 1: not shared)
  •          debounce-interval : 去抖延时  
  •     gpios :  gpio 的相关信息
/*用于gpio资源的申请及初始化,可列举很多*/
gpio_keys_pins_default: gpio_keys_pins_default {
      pinctrl-single,pins = <
      AM4372_IOPAD(0x9a0, PIN_INPUT | MUX_MODE9) /* (L23) mcasp0_aclkr.gpio0[18] */
      >;
};     
/*创建gpio-keys 设备描述*/
 gpio_keys: gpio_keys {
                compatible = "gpio-keys";
                pinctrl-names = "default";
                pinctrl-0 = <&gpio_keys_pins_default>;
                #address-cells = <1>;
                #size-cells = <0>;
/*添加一个button, 用于做系统reset*/
                button0 {
                        label = "reset";
                        linux,code = <0x198>; /*KEY_RESTART*/
                        gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
                };
   };

3. 内核模块选择配置

| with configuration data saying which GPIOs are used.                                                     |
  |                                                                                                          |
  | To compile this driver as a module, choose M here: the                                                   |
  | module will be called gpio_keys.                                                                         |
  |                                                                                                          |
  | Symbol: KEYBOARD_GPIO [=y]                                                                               |
  | Type  : tristate                                                                                         |
  | Prompt: GPIO Buttons                                                                                     |
  |   Location:                                                                                              |
  |     -> Device Drivers                                                                                    |
  |       -> Input device support                                                                            |
  |         -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])                            |
  |           -> Keyboards (INPUT_KEYBOARD [=y])                                                             |
  |   Defined at drivers/input/keyboard/Kconfig:214                                                          |
  |   Depends on: !UML && INPUT [=y] && INPUT_KEYBOARD [=y] && (GPIOLIB [=y] || COMPILE_TEST [=n])   

4. cat /proc/bus/input/devices 

 通过这个可以看到接到input事件的handler是哪一个

$: cat  /proc/bus/input/devices 
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio_keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio_keys/input/input0
U: Uniq=
H: Handlers=event0 
B: PROP=0
B: EV=3
B: KEY=1000000 0 0 0 0 0 0 0 0 0 0 0 0

/下面的信息有助于你编写udev rules
$:udevadm info -a -p /sys//devices/platform/gpio_keys/input/input0

  looking at device '/devices/platform/gpio_keys/input/input0':
    KERNEL=="input0"
    SUBSYSTEM=="input"
    DRIVER==""
    ATTR{name}=="gpio_keys"
    ATTR{phys}=="gpio-keys/input0"
    ATTR{properties}=="0"
    ATTR{uniq}==""

  looking at parent device '/devices/platform/gpio_keys':
    KERNELS=="gpio_keys"
    SUBSYSTEMS=="platform"
    DRIVERS=="gpio-keys"
    ATTRS{disabled_keys}==""
    ATTRS{disabled_switches}==""
    ATTRS{driver_override}=="(null)"
    ATTRS{keys}=="408"
    ATTRS{switches}==""

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""

4.如何接收按键消息,以下例程供参考

#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <time.h>
#include <sys/time.h>
#include <sys/types.h> 
#include <unistd.h>

#define KEY_DEVICE_FILE "/dev/input/event0"

struct input_event {
	struct timeval time;
	unsigned short type;
	unsigned short code;
	int value;
};

int main()
{
	int fd_key = -1;
	struct input_event key_evt;
	int ret = 0;

	fd_key= open(KEY_DEVICE_FILE, O_RDONLY);

	if(fd_key< 0) {
           return fd_key;
	}

monitor_key:

	ret = read(fd_key, (char*)&key_evt, sizeof(struct input_event));
	if(ret < 0)
	{
		printf("read key event failed:%d\n", ret);
	}else{
		if(key_evt.code == 408 && key_evt.value == 1)
		{
			printf("The press of reset button is detected\n");
			return 0;
		}
#if 0
		printf("%lld seconds %ld microseconds", key_evt.time.tv_sec,
							key_evt.time.tv_usec);
		printf("type:%d code:%d value:%d\n", key_evt.type, key_evt.code, key_evt.value);	
#endif
	}
	goto monitor_key;
	return 0;

}

 

  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
gpio-keys 是 Linux 内核中的一个模块,用于将 GPIO 按键映射为键盘上的按键,以便用户可以使用 GPIO 按键来与系统进行交互。在本文中,我将对 gpio-keys 模块的代码进行分析。 首先,我们需要了解的是 gpio-keys 模块的注册和注销过程。在模块初始化期间,我们需要调用 `gpio_keys_probe()` 函数来注册模块,该函数会注册一个 platform 设备,并将其与 gpio_keys_driver 结构体相关联。这个结构体包含了模块的名称、ID、设备树匹配以及一些回调函数。注册完成后,内核就会调用 `gpio_keys_irq()` 函数来设置 GPIO 中断并处理按键事件。 下面是 `gpio_keys_probe()` 函数的代码: ```c static int gpio_keys_probe(struct platform_device *pdev) { struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; struct gpio_keys_button *button; struct input_dev *input; int error, i; ... /* Allocate input device */ input = input_allocate_device(); if (!input) { dev_err(&pdev->dev, "Failed to allocate input device\n"); error = -ENOMEM; goto err_free_desc; } /* Set input device properties */ input->name = pdata->input_name ?: "gpio-keys"; input->dev.parent = &pdev->dev; set_bit(EV_KEY, input->evbit); for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { input_set_capability(input, EV_KEY, button->code); } /* Register input device */ error = input_register_device(input); if (error) { dev_err(&pdev->dev, "Failed to register input device\n"); goto err_free_dev; } /* Allocate and configure gpio_keys_device */ gkd = devm_kzalloc(&pdev->dev, sizeof(*gkd), GFP_KERNEL); if (!gkd) { error = -ENOMEM; goto err_free_dev; } gkd->pdev = pdev; gkd->input = input; gkd->n_buttons = pdata->nbuttons; gkd->buttons = pdata->buttons; /* Request and configure GPIOs */ for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { error = gpio_request(button->gpio, button->desc); if (error) { dev_err(&pdev->dev, "Failed to request gpio %d: %d\n", button->gpio, error); goto err_free_gpio; } error = gpio_direction_input(button->gpio); if (error) { dev_err(&pdev->dev, "Failed to configure gpio %d: %d\n", button->gpio, error); goto err_free_gpio; } } /* Register IRQ handlers */ for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { error = gpio_request(button->gpio, button->desc); if (error) { dev_err(&pdev->dev, "Failed to request gpio %d: %d\n", button->gpio, error); goto err_free_irq; } error = request_irq(gpio_to_irq(button->gpio), gpio_keys_irq, button->irqflags, button->desc, gkd); if (error) { dev_err(&pdev->dev, "Failed to register IRQ for gpio %d: %d\n", button->gpio, error); goto err_free_irq; } } /* Store private data */ platform_set_drvdata(pdev, gkd); return 0; err_free_irq: while (--i >= 0) { button--; free_irq(gpio_to_irq(button->gpio), gkd); } goto err_free_gpio; err_free_gpio: while (--i >= 0) { button--; gpio_free(button->gpio); } err_free_dev: input_free_device(input); err_free_desc: for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { kfree(button->desc); } return error; } ``` 在 `gpio_keys_probe()` 函数中,我们首先为输入设备分配内存,然后设置输入设备的属性,如名称、上级设备、事件类型和按键能力。接下来,我们为每个按键分配 GPIO,并将其配置为输入模式。最后,我们为每个按键注册中断处理程序,并将私有数据存储在 platform 设备的私有数据区域中。 一旦模块已注册并初始化,内核就可以使用 `gpio_keys_irq()` 函数来处理按键事件。该函数会检查哪个按键被按下或释放,并将事件发送到输入子系统。 下面是 `gpio_keys_irq()` 函数的代码: ```c static irqreturn_t gpio_keys_irq(int irq, void *dev_id) { struct gpio_keys_device *gkd = dev_id; struct gpio_keys_button *button; struct input_dev *input = gkd->input; unsigned int state; int i; /* Check each button */ for (i = 0, button = gkd->buttons; i < gkd->n_buttons; i++, button++) { state = gpio_get_value(button->gpio); if (state != button->active_low) continue; /* Send event to input subsystem */ input_report_key(input, button->code, 1); input_sync(input); input_report_key(input, button->code, 0); input_sync(input); } return IRQ_HANDLED; } ``` 在 `gpio_keys_irq()` 函数中,我们遍历每个按键并检查其状态。如果按键被按下,则我们发送 “按下” 事件;如果按键被释放,则我们发送 “释放” 事件。最后,我们将事件同步到输入子系统。 总的来说,gpio-keys 模块是一个非常有用的内核模块,它允许用户通过 GPIO 按键与系统进行交互。通过分析其代码,我们可以更好地了解内核模块是如何工作的,并且可以更好地理解 linux 内核的编程模式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值