Skip to content

Latest commit

 

History

History
787 lines (627 loc) · 28.7 KB

File metadata and controls

787 lines (627 loc) · 28.7 KB

十七、输入设备驱动

输入设备是可以与系统交互的设备。这样的设备有按钮、键盘、触摸屏、鼠标等等。它们通过发送事件来工作,由输入核心捕获并在系统上广播。本章将解释输入核心用来处理输入设备的每个结构。也就是说,我们将看到如何从用户空间管理事件。

在本章中,我们将涵盖以下主题:

  • 输入核心数据结构
  • 分配和注册输入设备,以及轮询设备系列
  • 生成事件并将其报告给输入核心
  • 来自用户空间的输入设备
  • 编写驱动示例

输入设备结构

首先,为了与输入子系统接口,要包含的主文件是linux/input.h:

#include <linux/input.h> 

无论它是什么类型的输入设备,无论它发送什么类型的事件,输入设备在内核中都被表示为struct input_dev的一个实例:

struct input_dev { 
  const char *name; 
  const char *phys; 

  unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; 
  unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; 
  unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; 
  unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 
  unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; 

  unsigned int repeat_key; 

  int rep[REP_CNT]; 
  struct input_absinfo *absinfo; 
  unsigned long key[BITS_TO_LONGS(KEY_CNT)]; 

  int (*open)(struct input_dev *dev); 
  void (*close)(struct input_dev *dev); 

  unsigned int users; 
  struct device dev; 

  unsigned int num_vals; 
  unsigned int max_vals; 
  struct input_value *vals; 

  bool devres_managed; 
}; 

这些字段的含义如下:

  • name代表设备的名称。
  • phys是系统层次结构中设备的物理路径。
  • evbit是设备支持的事件类型的位图。一些类型的区域如下:
    • EV_KEY用于支持发送按键事件的设备(键盘、按钮等)
    • EV_REL用于支持发送相对位置的设备(鼠标、数字化仪等)
    • EV_ABS用于支持发送绝对位置的设备(操纵杆)

事件列表可以在include/linux/input-event-codes.h文件的内核源代码中找到。根据我们的输入设备能力,使用set_bit()宏来设置适当的位。当然,一个设备可以支持多种类型的事件。例如,鼠标将设置EV_KEYEV_REL

set_bit(EV_KEY, my_input_dev->evbit); 
set_bit(EV_REL, my_input_dev->evbit); 
  • keybit用于EV_KEY类型的启用设备,该设备显示的按键/按钮位图。例如:BTN_0KEY_AKEY_B等等。按键/按钮的完整列表在include/linux/input-event-codes.h文件中。
  • relbit用于EV_REL类型启用的设备,设备相对轴的位图。例如:REL_XREL_YREL_ZREL_RX等等。完整列表请看include/linux/input-event-codes.h
  • absbit是针对一个EV_ABS类型的启用设备,设备绝对轴的位图。比如ABS_YABS_X等等。完整列表请查看相同的先前文件。
  • mscbitEV_MSC类型启用设备,设备支持的杂项事件位图。
  • repeat_key存储最后一次按键的按键码;用于实现软件自动重复。
  • rep,自动重复参数(延迟、速率)的当前值。
  • absinfo是保存绝对轴(当前值、最小值、最大值、平坦值、模糊值、分辨率)信息的&struct input_absinfo元素的数组。您应该使用input_set_abs_params()功能来设置这些值。
void input_set_abs_params(struct input_dev *dev, unsigned int axis, 
                             int min, int max, int fuzz, int flat) 
  • minmax指定下限值和上限值。fuzz表示指定输入设备的指定通道上的预期噪声。下面是一个示例,其中我们仅设置了每个通道的边界:
#define ABSMAX_ACC_VAL 0x01FF 
#define ABSMIN_ACC_VAL -(ABSMAX_ACC_VAL) 
[...] 
set_bit(EV_ABS, idev->evbit); 
input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 
input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 
input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 
  • key反映设备按键/按钮的当前状态。
  • open是第一个用户调用input_open_device()时调用的方法。使用此方法准备设备,如中断请求、轮询线程启动等。
  • 当最后一个用户调用input_close_device()时,调用close。在这里,您可以停止轮询(这会消耗大量资源)。
  • users存储打开该设备的用户(输入处理程序)数量。它由input_open_device()input_close_device()使用,以确保dev->open()仅在第一个用户打开设备时调用,而dev->close()在最后一个用户关闭设备时调用。
  • dev是与此设备关联的结构设备(对于设备型号)。
  • num_vals是当前帧中排队的值的数量。
  • max_vals是一帧中排队值的最大数量。
  • Vals是当前帧中排队的值数组。
  • devres_managed表示设备使用devres框架管理,不需要显式取消注册或释放。

分配和注册输入设备

在用输入设备注册和发送事件之前,应分配input_allocate_device()功能。为了释放之前为未注册的输入设备分配的内存,应该使用input_free_device()功能。如果设备已经注册,则应使用input_unregister_device()。像每个需要内存分配的函数一样,我们可以使用资源管理版本的函数:

struct input_dev *input_allocate_device(void) 
struct input_dev *devm_input_allocate_device(struct device *dev) 

void input_free_device(struct input_dev *dev) 
static void devm_input_device_unregister(struct device *dev, 
                                         void *res) 
int input_register_device(struct input_dev *dev) 
void input_unregister_device(struct input_dev *dev) 

设备分配可能会休眠,因此不能在原子上下文中或在保持自旋锁的情况下调用。

以下是位于 I2C 公交车上的输入设备的probe功能摘录:

struct input_dev *idev; 
int error; 

idev = input_allocate_device(); 
if (!idev) 
    return -ENOMEM; 

idev->name = BMA150_DRIVER; 
idev->phys = BMA150_DRIVER "/input0"; 
idev->id.bustype = BUS_I2C; 
idev->dev.parent = &client->dev; 

set_bit(EV_ABS, idev->evbit); 
input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 
input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 
input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, 
                     ABSMAX_ACC_VAL, 0, 0); 

error = input_register_device(idev); 
if (error) { 
    input_free_device(idev); 
    return error; 
} 

error = request_threaded_irq(client->irq, 
            NULL, my_irq_thread, 
            IRQF_TRIGGER_RISING | IRQF_ONESHOT, 
            BMA150_DRIVER, NULL); 
if (error) { 
    dev_err(&client->dev, "irq request failed %d, error %d\n", 
            client->irq, error); 
    input_unregister_device(bma150->input); 
    goto err_free_mem; 
} 

轮询输入设备子类

轮询输入设备是一种特殊类型的输入设备,它依赖于轮询来感知设备状态变化,而通用输入设备类型依赖于 IRQ 来感知变化并向输入内核发送事件。

轮询输入设备在内核中被描述为struct input_polled_dev结构的一个实例,它是通用struct input_dev结构的包装器:

struct input_polled_dev { 
    void *private; 

    void (*open)(struct input_polled_dev *dev); 
    void (*close)(struct input_polled_dev *dev); 
    void (*poll)(struct input_polled_dev *dev); 
    unsigned int poll_interval; /* msec */ 
    unsigned int poll_interval_max; /* msec */ 
    unsigned int poll_interval_min; /* msec */ 

    struct input_dev *input; 

    bool devres_managed; 
}; 

以下是该结构中元素的含义:

  • private是司机的私人数据。
  • open是一种可选方法,用于准备设备进行轮询(启用设备并可能刷新设备状态)。
  • close是一个可选方法,当设备不再被轮询时调用。它用于将设备置于低功耗模式。
  • poll是每当设备需要轮询时调用的强制方法。以poll_interval的频率调用。
  • poll_interval是调用poll()方法的频率。默认为 500 毫秒,除非在注册设备时被覆盖。
  • poll_interval_max指定轮询间隔的上限。默认为poll_interval的初始值。
  • poll_interval_min指定轮询间隔的下限。默认为 0。
  • input是轮询设备所围绕的输入设备。它必须由驱动正确初始化(标识、名称、位)。轮询输入设备只是提供了一个接口来使用轮询而不是 IRQ 来检测设备状态的变化。

使用input_allocate_polled_device()input_free_polled_device()分配/释放struct input_polled_dev结构。您应该注意初始化嵌入其中的struct input_dev的强制字段。轮询间隔也应该设置,否则,默认为 500 毫秒。也可以使用资源管理版本。两个原型如下:

struct input_polled_dev *devm_input_allocate_polled_device(struct             device *dev) 
struct input_polled_dev *input_allocate_polled_device(void) 
void input_free_polled_device(struct input_polled_dev *dev) 

For resource-managed devices, the field input_dev->devres_managed will be set to true by the input core.

在分配和适当的字段初始化后,可以使用input_register_polled_device()注册轮询的输入设备,成功后返回 0。反向操作(取消注册)通过input_unregister_polled_device()功能完成:

int input_register_polled_device(struct input_polled_dev *dev) 
void  input_unregister_polled_device(struct input_polled_dev *dev) 

这种设备的probe()功能的典型示例如下:

static int button_probe(struct platform_device *pdev) 
{ 
    struct my_struct *ms; 
    struct input_dev *input_dev; 
    int retval; 

    ms = devm_kzalloc(&pdev->dev, sizeof(*ms), GFP_KERNEL); 
    if (!ms) 
        return -ENOMEM; 

    ms->poll_dev = input_allocate_polled_device(); 
    if (!ms->poll_dev){ 
        kfree(ms); 
        return -ENOMEM; 
    } 

    /* This gpio is not mapped to IRQ */ 
    ms->reset_btn_desc = gpiod_get(dev, "reset", GPIOD_IN); 

    ms->poll_dev->private = ms ; 
    ms->poll_dev->poll = my_btn_poll; 
    ms->poll_dev->poll_interval = 200; /* Poll every 200ms */ 
    ms->poll_dev->open = my_btn_open; /* consist */ 

    input_dev = ms->poll_dev->input; 
    input_dev->name = "System Reset Btn"; 

    /* The gpio belong to an expander sitting on I2C */ 
    input_dev->id.bustype = BUS_I2C;  
    input_dev->dev.parent = &pdev->dev; 

    /* Declare the events generated by this driver */ 
    set_bit(EV_KEY, input_dev->evbit); 
    set_bit(BTN_0, input_dev->keybit); /* buttons */ 

    retval = input_register_polled_device(mcp->poll_dev); 
    if (retval) { 
        dev_err(&pdev->dev, "Failed to register input device\n"); 
        input_free_polled_device(ms->poll_dev); 
        kfree(ms);   
    } 
    return retval; 
} 

以下是我们的struct my_struct结构的样子:

struct my_struct { 
    struct gpio_desc *reset_btn_desc; 
    struct input_polled_dev *poll_dev; 
} 

以下是open函数的外观:

static void my_btn_open(struct input_polled_dev *poll_dev) 
{ 
    struct my_strut *ms = poll_dev->private; 
    dev_dbg(&ms->poll_dev->input->dev, "reset open()\n"); 
} 

open方法用于准备设备所需的资源。对于这个例子,我们并不真正需要这个方法。

生成和报告输入事件

设备分配和注册是必不可少的,但它们不是输入设备驱动的主要目标,输入设备驱动被设计为甚至向输入核心报告。根据您的设备可以支持的事件类型,内核提供适当的 API 向内核报告这些事件。

给定一个EV_XXX功能的设备,相应的报告功能将是input_report_xxx()。下表显示了最重要的事件类型及其报告功能之间的映射:

| 事件类型 | 报告功能 | 代码示例 | | EV_KEY | input_report_key() | input_report_key(poll_dev->input, BTN_0, gpiod_get_value(ms-> reset_btn_desc) & 1); | | EV_REL | input_report_rel() | input_report_rel(nunchuk->input, REL_X, (nunchuk->report.joy_x - 128)/10); | | EV_ABS | input_report_abs() | input_report_abs(bma150->input, ABS_X, x_value)input_report_abs(bma150->input, ABS_Y, y_value)input_report_abs(bma150->input, ABS_Z, z_value); |

它们各自的原型如下:

void input_report_abs(struct input_dev *dev, 
                      unsigned int code, int value) 
void input_report_key(struct input_dev *dev, 
                      unsigned int code, int value) 
void input_report_rel(struct input_dev *dev, 
                      unsigned int code, int value) 

可用报表函数的列表可以在内核源文件的include/linux/input.h中找到。它们都有相同的骨架:

  • dev是负责事件的输入设备。
  • code代表事件代码,例如REL_XKEY_BACKSPACE。完整列表在include/linux/input-event-codes.h中。
  • value是事件承载的价值。对于EV_REL事件类型,它携带相对变化。对于一个EV_ABS(操纵杆等等。)事件类型,它包含一个绝对新值。对于EV_KEY事件类型,应设置为0键释放、1键按压、2自动重复。

报告完所有更改后,驾驶员应在输入设备上调用input_sync(),以指示该事件已完成。输入子系统会将这些收集到一个数据包中,并通过/dev/input/event<X>发送出去,T1 是代表我们在系统上的struct input_dev的字符设备,其中<X>是输入核心分配给驱动的接口号:

void input_sync(struct input_dev *dev) 

我们来看一个例子,这是drivers/input/misc/bma150.cbma150数字加速度传感器驱动的摘录:

static void threaded_report_xyz(struct bma150_data *bma150) 
{ 
  u8 data[BMA150_XYZ_DATA_SIZE]; 
  s16 x, y, z; 
  s32 ret; 

  ret = i2c_smbus_read_i2c_block_data(bma150->client, 
      BMA150_ACC_X_LSB_REG, BMA150_XYZ_DATA_SIZE, data); 
  if (ret != BMA150_XYZ_DATA_SIZE) 
    return; 

  x = ((0xc0 & data[0]) >> 6) | (data[1] << 2); 
  y = ((0xc0 & data[2]) >> 6) | (data[3] << 2); 
  z = ((0xc0 & data[4]) >> 6) | (data[5] << 2); 

  /* sign extension */ 
  x = (s16) (x << 6) >> 6; 
  y = (s16) (y << 6) >> 6; 
  z = (s16) (z << 6) >> 6; 

  input_report_abs(bma150->input, ABS_X, x); 
  input_report_abs(bma150->input, ABS_Y, y); 
  input_report_abs(bma150->input, ABS_Z, z); 
  /* Indicate this event is complete */ 
  input_sync(bma150->input); 
} 

在前面的示例中,input_sync()告诉核心将这三个报告视为同一个事件。这是有意义的,因为位置有三个轴(X,Y,Z),我们不希望 X,Y 或 Z 分别报告。

报告事件的最佳位置是在轮询设备的poll功能中,或者在启用了 IRQ 的设备的 IRQ 例程(线程部分或无线程部分)中。如果您执行一些可能会休眠的操作,您应该在处理的 IRQ 的线程部分中处理您的报告:

static void my_btn_poll(struct input_polled_dev *poll_dev) 
{ 
    struct my_struct *ms = poll_dev->private; 
    struct i2c_client *client = mcp->client; 

    input_report_key(poll_dev->input, BTN_0, 
                     gpiod_get_value(ms->reset_btn_desc) & 1); 
    input_sync(poll_dev->input); 
} 

用户空间界面

每个注册的输入设备都由一个/dev/input/event<X> char 设备表示,我们可以从这个设备中读取用户空间中的事件。读取该文件的应用将接收struct input_event格式的事件数据包:

struct input_event { 
  struct timeval time; 
  __u16 type; 
  __u16 code; 
  __s32 value; 
} 

让我们看看结构中每个柠檬的含义:

  • time是时间戳,它返回事件发生的时间。
  • type为事件类型。例如,按下或释放某个键时为EV_KEY,相对时刻为EV_REL,绝对时刻为EV_ABS。更多类型在include/linux/input-event-codes.h中定义。
  • code是事件代码,例如:REL_XKEY_BACKSPACE,同样完整的列表在include/linux/input-event-codes.h中。
  • value是事件承载的价值。对于EV_REL事件类型,它携带相对变化。对于一个EV_ABS(操纵杆等等)事件类型,它包含了绝对的新值。对于EV_KEY事件类型,应设置为0键释放,1键按压,2自动重复。

用户空间应用可以使用阻塞和非阻塞读取,也可以使用poll()select()系统调用,以便在打开该设备后获得事件通知。以下是select()系统调用的一个例子,完整的源代码在图书源代码库中提供:

#include <unistd.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <linux/input.h> 
#include <sys/select.h> 

#define INPUT_DEVICE "/dev/input/event1" 

int main(int argc, char **argv) 
{    
    int fd; 
    struct input_event event; 
    ssize_t bytesRead; 

    int ret; 
    fd_set readfds; 

    fd = open(INPUT_DEVICE, O_RDONLY); 
    /* Let's open our input device */ 
    if(fd < 0){ 
        fprintf(stderr, "Error opening %s for reading", INPUT_DEVICE); 
        exit(EXIT_FAILURE); 
    } 

    while(1){  
        /* Wait on fd for input */ 
        FD_ZERO(&readfds); 
        FD_SET(fd, &readfds); 

        ret = select(fd + 1, &readfds, NULL, NULL, NULL); 
        if (ret == -1) { 
            fprintf(stderr, "select call on %s: an error ocurred", 
                    INPUT_DEVICE); 
            break; 
        } 
        else if (!ret) { /* If we have decided to use timeout */ 
            fprintf(stderr, "select on %s: TIMEOUT", INPUT_DEVICE); 
            break; 
        } 

        /* File descriptor is now ready */ 
        if (FD_ISSET(fd, &readfds)) { 
            bytesRead = read(fd, &event, 
                             sizeof(struct input_event)); 
            if(bytesRead == -1) 
                /* Process read input error*/ 
            if(bytesRead != sizeof(struct input_event)) 
                /* Read value is not an input even */ 

            /*  
             * We could have done a switch/case if we had 
             * many codes to look for 
             */ 
            if(event.code == BTN_0) { 
                /* it concerns our button */ 
                if(event.value == 0){ 
                    /* Process Release */ 
                    [...] 
                } 
                else if(event.value == 1){ 
                    /* Process KeyPress */ 
                    [...] 
                } 
            } 
        } 
    } 
    close(fd); 
    return EXIT_SUCCESS; 
} 

把它们放在一起

到目前为止,我们已经描述了为输入设备编写驱动时使用的结构,以及如何从用户空间管理它们。

  1. 使用input_allocate_polled_device()input_allocate_device(),根据输入设备的类型分配一个新的输入设备,无论是否轮询。

  2. 是否填写必填字段(如有必要):

  3. 如有必要,写下你的open()功能,在其中你应该准备和设置设备使用的资源。这个函数只被调用一次。在该功能中,设置 GPIO,需要时请求中断,初始化设备。

  4. 编写你的close()函数,在这个函数中你将释放和释放你在open()函数中所做的事情。例如,释放 GPIO、IRQ,将设备置于省电模式。

  5. 将您的open()close()功能(或两者)传递到input_dev.openinput_dev.close字段。

  6. 如果轮询,使用input_register_polled_device()注册您的设备,否则使用input_register_device()注册。

  7. 在您的 IRQ 功能(线程化或非线程化)或poll()功能中,根据事件类型收集和报告事件,使用input_report_key()input_report_rel()input_report_abs()或其他,然后调用输入设备上的input_sync()指示帧结束(报告完成)。

如果没有提供 IRQ,通常的方法是使用传统的输入设备,否则返回轮询设备:

if(client->irq > 0){ 
    /* Use generic input device */ 
} else { 
    /* Use polled device */ 
} 

要了解如何从用户空间管理此类设备,请参考本书来源中提供的示例。

驱动因素示例

人们可以从以下两个驱动因素中总结出一件事。第一个是轮询输入设备,基于非映射到 IRQ 的 GPIO。被轮询的输入核心将轮询 GPIO 以检测任何变化。此驱动被配置为发送 0 密钥代码。每个 GPIO 状态对应于按键或按键释放:

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/slab.h> 
#include <linux/of.h>                   /* For DT*/ 
#include <linux/platform_device.h>      /* For platform devices */ 
#include <linux/gpio/consumer.h>        /* For GPIO Descriptor interface */ 
#include <linux/input.h> 
#include <linux/input-polldev.h> 

struct poll_btn_data { 
   struct gpio_desc *btn_gpiod; 
   struct input_polled_dev *poll_dev; 
}; 

static void polled_btn_open(struct input_polled_dev *poll_dev) 
{ 
    /* struct poll_btn_data *priv = poll_dev->private; */ 
    pr_info("polled device opened()\n"); 
} 

static void polled_btn_close(struct input_polled_dev *poll_dev) 
{ 
    /* struct poll_btn_data *priv = poll_dev->private; */ 
    pr_info("polled device closed()\n"); 
} 

static void polled_btn_poll(struct input_polled_dev *poll_dev) 
{ 
    struct poll_btn_data *priv = poll_dev->private; 

    input_report_key(poll_dev->input, BTN_0, gpiod_get_value(priv->btn_gpiod) & 1); 
    input_sync(poll_dev->input); 
} 

static const struct of_device_id btn_dt_ids[] = { 
    { .compatible = "packt,input-polled-button", }, 
    { /* sentinel */ } 
}; 

static int polled_btn_probe(struct platform_device *pdev) 
{ 
    struct poll_btn_data *priv; 
    struct input_polled_dev *poll_dev; 
    struct input_dev *input_dev; 
    int ret; 

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 
    if (!priv) 
        return -ENOMEM; 

    poll_dev = input_allocate_polled_device(); 
    if (!poll_dev){ 
        devm_kfree(&pdev->dev, priv); 
        return -ENOMEM; 
    } 

    /* We assume this GPIO is active high */ 
    priv->btn_gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN); 

    poll_dev->private = priv; 
    poll_dev->poll_interval = 200; /* Poll every 200ms */ 
    poll_dev->poll = polled_btn_poll; 
    poll_dev->open = polled_btn_open; 
    poll_dev->close = polled_btn_close; 
    priv->poll_dev = poll_dev; 

    input_dev = poll_dev->input; 
    input_dev->name = "Packt input polled Btn"; 
    input_dev->dev.parent = &pdev->dev; 

    /* Declare the events generated by this driver */ 
    set_bit(EV_KEY, input_dev->evbit); 
    set_bit(BTN_0, input_dev->keybit); /* buttons */ 

    ret = input_register_polled_device(priv->poll_dev); 
    if (ret) { 
        pr_err("Failed to register input polled device\n"); 
        input_free_polled_device(poll_dev); 
        devm_kfree(&pdev->dev, priv); 
        return ret; 
    } 

    platform_set_drvdata(pdev, priv); 
    return 0; 
} 

static int polled_btn_remove(struct platform_device *pdev) 
{ 
   struct poll_btn_data *priv = platform_get_drvdata(pdev); 
   input_unregister_polled_device(priv->poll_dev); 
    input_free_polled_device(priv->poll_dev); 
    gpiod_put(priv->btn_gpiod); 
   return 0; 
} 

static struct platform_driver mypdrv = { 
    .probe      = polled_btn_probe, 
    .remove     = polled_btn_remove, 
    .driver     = { 
        .name     = "input-polled-button", 
        .of_match_table = of_match_ptr(btn_dt_ids),   
        .owner    = THIS_MODULE, 
    }, 
}; 
module_platform_driver(mypdrv); 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("John Madieu <[email protected]>"); 
MODULE_DESCRIPTION("Polled input device"); 

第二个驱动根据按钮的 GPIO 映射到的 IRQ 向输入核心发送事件。使用 IRQ 检测按键按下或释放时,最好在边沿变化时触发中断:

#include <linux/kernel.h> 
#include <linux/module.h> 
#include <linux/slab.h> 
#include <linux/of.h>                   /* For DT*/ 
#include <linux/platform_device.h>      /* For platform devices */ 
#include <linux/gpio/consumer.h>        /* For GPIO Descriptor interface */ 
#include <linux/input.h> 
#include <linux/interrupt.h> 

struct btn_data { 
   struct gpio_desc *btn_gpiod; 
   struct input_dev *i_dev; 
   struct platform_device *pdev; 
   int irq; 
}; 

static int btn_open(struct input_dev *i_dev) 
{ 
    pr_info("input device opened()\n"); 
    return 0; 
} 

static void btn_close(struct input_dev *i_dev) 
{ 
    pr_info("input device closed()\n"); 
} 

static irqreturn_t packt_btn_interrupt(int irq, void *dev_id) 
{ 
    struct btn_data *priv = dev_id; 

   input_report_key(priv->i_dev, BTN_0, gpiod_get_value(priv->btn_gpiod) & 1); 
    input_sync(priv->i_dev); 
   return IRQ_HANDLED; 
} 

static const struct of_device_id btn_dt_ids[] = { 
    { .compatible = "packt,input-button", }, 
    { /* sentinel */ } 
}; 

static int btn_probe(struct platform_device *pdev) 
{ 
    struct btn_data *priv; 
    struct gpio_desc *gpiod; 
    struct input_dev *i_dev; 
    int ret; 

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 
    if (!priv) 
        return -ENOMEM; 

    i_dev = input_allocate_device(); 
    if (!i_dev) 
        return -ENOMEM; 

    i_dev->open = btn_open; 
    i_dev->close = btn_close; 
    i_dev->name = "Packt Btn"; 
    i_dev->dev.parent = &pdev->dev; 
    priv->i_dev = i_dev; 
    priv->pdev = pdev; 

    /* Declare the events generated by this driver */ 
    set_bit(EV_KEY, i_dev->evbit); 
    set_bit(BTN_0, i_dev->keybit); /* buttons */ 

    /* We assume this GPIO is active high */ 
    gpiod = gpiod_get(&pdev->dev, "button", GPIOD_IN); 
    if (IS_ERR(gpiod)) 
        return -ENODEV; 

    priv->irq = gpiod_to_irq(priv->btn_gpiod); 
    priv->btn_gpiod = gpiod; 

    ret = input_register_device(priv->i_dev); 
    if (ret) { 
        pr_err("Failed to register input device\n"); 
        goto err_input; 
    } 

    ret = request_any_context_irq(priv->irq, 
                           packt_btn_interrupt, 
                           (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING), 
                           "packt-input-button", priv); 
    if (ret < 0) { 
        dev_err(&pdev->dev, 
            "Unable to acquire interrupt for GPIO line\n"); 
        goto err_btn; 
    } 

    platform_set_drvdata(pdev, priv); 
    return 0; 

err_btn: 
    gpiod_put(priv->btn_gpiod); 
err_input: 
    printk("will call input_free_device\n"); 
    input_free_device(i_dev); 
    printk("will call devm_kfree\n"); 
    return ret; 
} 

static int btn_remove(struct platform_device *pdev) 
{ 
    struct btn_data *priv; 
    priv = platform_get_drvdata(pdev); 
    input_unregister_device(priv->i_dev); 
    input_free_device(priv->i_dev); 
    free_irq(priv->irq, priv); 
    gpiod_put(priv->btn_gpiod); 
    return 0; 
} 

static struct platform_driver mypdrv = { 
    .probe      = btn_probe, 
    .remove     = btn_remove, 
    .driver     = { 
    .name     = "input-button", 
    .of_match_table = of_match_ptr(btn_dt_ids),   
    .owner    = THIS_MODULE, 
    }, 
}; 
module_platform_driver(mypdrv); 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("John Madieu <[email protected]>"); 
MODULE_DESCRIPTION("Input device (IRQ based)"); 

对于这两个例子,当设备与模块匹配时,将在/dev/input目录中创建一个节点。该节点对应于我们示例中的event0。可以使用udevadm工具显示设备信息:

# udevadm info /dev/input/event0
P: /devices/platform/input-button.0/input/input0/event0
N: input/event0
S: input/by-path/platform-input-button.0-event
E: DEVLINKS=/dev/input/by-path/platform-input-button.0-event
E: DEVNAME=/dev/input/event0
E: DEVPATH=/devices/platform/input-button.0/input/input0/event0
E: ID_INPUT=1
E: ID_PATH=platform-input-button.0
E: ID_PATH_TAG=platform-input-button_0
E: MAJOR=13
E: MINOR=64
E: SUBSYSTEM=input
E: USEC_INITIALIZED=74842430

给定输入设备的路径,实际上允许我们将事件键打印到屏幕上的工具是evtest:

# evtest /dev/input/event0
input device opened()
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "Packt Btn"
Supported events:
Event type 0 (EV_SYN)
Event type 1 (EV_KEY)
Event code 256 (BTN_0)

由于第二个模块是基于 IRQ 的,人们可以很容易地检查 IRQ 请求是否成功,以及它被触发了多少次:

$ cat /proc/interrupts | grep packt
160: 0 0 0 0 gpio-mxc 0 packt-input-button

最后,可以依次按下/释放按钮,检查 GPIO 的状态是否改变:

$ cat /sys/kernel/debug/gpio | grep button
gpio-193 (button-gpio ) in hi
$ cat /sys/kernel/debug/gpio | grep button
gpio-193 (button-gpio ) in lo

摘要

本章描述了整个输入框架,并强调了轮询和中断驱动输入设备之间的区别。到本章结束时,您已经具备了为任何输入驱动编写驱动的必要知识,无论它是什么类型,也无论它支持什么输入事件。还讨论了用户空间界面,并提供了一个示例。下一章讨论另一个重要的框架,RTC,它是 PC 和嵌入式设备中时间管理的关键元素。