Heng30的博客
搜索 分类 关于 订阅

Linux驱动如何使用input_dev创建驱动?

2025-03-24

struct input_dev是Linux内核中用于表示输入设备的数据结构,定义在<linux/input.h>头文件中。它用于管理和描述输入设备(如键盘、鼠标、触摸屏等)及其事件。

主要字段

  • name: 设备名称。

  • phys: 设备的物理地址。

  • uniq: 设备的唯一标识符。

  • id: 设备的 ID 信息(如厂商、产品 ID 等)。

  • evbit: 事件类型位图(如按键、相对坐标等)。

  • keybit: 按键位图,表示支持的按键。

  • relbit: 相对坐标位图(如鼠标移动)。

  • absbit: 绝对坐标位图(如触摸屏坐标)。

  • mscbit: 杂项事件位图。

  • ledbit: LED状态位图。

  • sndbit: 声音事件位图。

  • ffbit: 力反馈事件位图。

  • swbit: 开关事件位图。

  • propbit: 设备属性位图(如是否支持多点触控)。

设备树

// test.dts

/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    // 模拟数据
    gpioe:gpio@50006000 {
        #gpio-cells = <0x2>;
        reg =  <0x50006000 0x4 0x50006014 0x4 0x50000a28 0x4>;
    };

    // 模拟数据
    gpiof:gpio@50008000 {
        gpio-controller;
        #gpio-cells = <0x2>;
        interrupt-controller;
        #interrupt-cells = <0x2>;

        reg =  <0x50008000 0x4 0x50008014 0x4 0x50008a28 0x4>;
        gpio-ranges = <0x55 0x0 0x50 0x10>;
        status = "okay";
    };

    key {
        model = "This is key";
        dev-type = "KEY";

        interrupt-parent = <&gpiof>;

        interrupts = <9 0>,<7 0>,<8 0>;

        key-gpios = <&gpiof 9 0>,<&gpiof 7 0>,<&gpiof 8 0>;

        // 与驱动匹配的关键设置
        compatible = "my_device_002";

        // 表示启用设备节点
        status = "okay";
    };
};

驱动代码

// input-device.c

#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>

#define PLATFORM_DEVICE_BASENAME "my_device"
#define PLATFORM_DEVICE_NAME "my_device_002"

typedef struct {
    u32 key1_interrupts;
    u32 key1_gpios;

    volatile int current_irq;
    struct input_dev *input_device;
} simple_cdev_t;

static simple_cdev_t my_cdev = {0};

// 中断上文处理函数
static irqreturn_t _key_ISR(int irq, void *dev) {
    pr_info("key_ISR irq = %d\n", irq);

    my_cdev.current_irq = irq;

    // 表示还有中断下文要处理
    return IRQ_WAKE_THREAD;
}

// 中断下文处理函数
static irqreturn_t _thread_irq(int irq, void *dev) {
    pr_info("thread_irq\n");

    msleep(100); // 阻塞延时100毫秒, 会放弃cpu的使用权

    if (irq == my_cdev.key1_interrupts) {
        if (gpio_get_value(my_cdev.key1_gpios) == 0) {
            pr_info("key1 pressed\n");
            input_event(my_cdev.input_device, EV_KEY, KEY_A, 1);
            input_sync(my_cdev.input_device);
        } else {
            pr_info("key1 released\n");
            input_event(my_cdev.input_device, EV_KEY, KEY_A, 0);
            input_sync(my_cdev.input_device);
        }
    }

    // 表示中断处理完成
    return IRQ_HANDLED;
}

// 当安装该驱动并名称匹配成功后,平台设备驱动会自动调用该函数
static int _probe(struct platform_device *device) {
    pr_info("input_device_probe\n");

    struct device_node *key_node = device->dev.of_node;

    // 初始化gpio资源, 第二个参数要与testdtree.dts中的资源名称要一致
    my_cdev.key1_gpios = of_get_named_gpio(key_node, "key-gpios", 0);
    gpio_request(my_cdev.key1_gpios, "key1-gpios");
    gpio_direction_input(my_cdev.key1_gpios);

    // 获取按键中断资源
    my_cdev.key1_interrupts = of_irq_get(key_node, 0);

    // 会创建`/proc/irq/key1-interrupts`目录
    // 中断下文运行在不同的线程,实现并发处理
    int key1_irq_ret = request_threaded_irq(
        my_cdev.key1_interrupts, _key_ISR, _thread_irq,
        IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key1-interrupts", NULL);

    if (key1_irq_ret) {
        pr_err("request_irq for key1 failed\n");
        return key1_irq_ret;
    }

    // 初始化输入设备
    // 输入设备的用途是将按键的信息通过设备文件暴露到用户层
    my_cdev.input_device = input_allocate_device();
    if (!my_cdev.input_device) {
        pr_err("input_allocate_device failed\n");
        return -ENOMEM;
    }

    // 对输入设备匹配方式和事件相关进行设置
    // 设备匹配成功后会自动创建设备号,设备文件等
    // 注册成功后可以在`/proc/bus/input/devices`文件查看相应的信息
    // 设备文件在`/dev/input`目录下
    my_cdev.input_device->id.bustype = ID_BUS;

    // 注册按键事件
    set_bit(EV_KEY, my_cdev.input_device->evbit);

    // 分别为每个按键注册事件
    set_bit(KEY_A, my_cdev.input_device->keybit);
    // set_bit(KEY_B, my_cdev_v1.input_device->keybit);
    // set_bit(KEY_C, my_cdev_v1.input_device->keybit);

    my_cdev.input_device->name = "KEY";
    my_cdev.input_device->uniq = "KEY";
    my_cdev.input_device->phys = "soc/pf9/key";

    // 注册到输入子系统中
    int ird_ret = input_register_device(my_cdev.input_device);
    if (ird_ret) {
        input_free_device(my_cdev.input_device);
        pr_err("input_register_device failed\n");
        return ird_ret;
    }

    return 0;
}

// 当平台设备驱动卸载时,会自动调用该函数
static int _remove(struct platform_device *device) {
    pr_info("input_device_remove\n");

    input_unregister_device(my_cdev.input_device);
    input_free_device(my_cdev.input_device);

    free_irq(my_cdev.key1_interrupts, NULL);
    gpio_free(my_cdev.key1_gpios);

    return 0;
}

// 会与设备树安装的platform_device进行匹配
struct of_device_id of_node_match_table[] = {
    [0] =
        {
            .compatible = PLATFORM_DEVICE_NAME,
        },
    [1] = {}, // 空元素代表结束
};

struct platform_driver my_input_driver = {
    .probe = _probe,
    .remove = _remove,

    // 通过id_table匹配, 实现一个驱动对多个设备
    .driver =
        {
            .name = PLATFORM_DEVICE_BASENAME,

            // 设备树的匹配方式
            .of_match_table = of_node_match_table,
        },
};

static int __init input_device_init(void) {
    pr_info("input_device_init\n");

    int ret = platform_driver_register(&my_input_driver);
    if (ret < 0) {
        pr_info(KERN_WARNING "input_device_register failed\n");
        return -1;
    }
    return 0;
}

static void __exit input_device_exit(void) {
    pr_info("input_device_exit\n");
    platform_driver_unregister(&my_input_driver);
}

module_init(input_device_init);
module_exit(input_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple input_device kernel module");

编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += input-device.o

all: dt driver

driver:
        make -C $(kernel-dir) modules M=$(top-dir)

dt:
        dtc -@ -I dts -O dtb -o test.dtbo test.dts

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试程序

// reader.c
// 编译:gcc -g -o reader main.c

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <linux/input.h>

struct input_event event;

int main(int argc, char *argv[]) {
    if (argc != 2) {
        perror("please input a device to read and write");
        return -1;
    }

    FILE *fp = fopen(argv[1], "r");
    assert(fp);

    while (true) {
        size_t rlen = fread(&event, 1, sizeof(struct input_event), fp);
        assert(rlen >= 0);

        if (rlen == 0)
            break;

        printf("len = %ld, code = %d, value = %d\n", rlen, event.code,
               event.value);

        if (event.code == KEY_A && event.value == 1) {
            printf("A\n");
        } else if (event.code == KEY_B && event.value == 1) {
            printf("B\n");
        } else if (event.code == KEY_C && event.value == 1) {
            printf("C\n");
        }

        sleep(1);
    }

    fclose(fp);
    return 0;
}