【Linux】浅析Input子系统

news/2024/5/6 3:44:27/文章来源:https://blog.csdn.net/weixin_43444989/article/details/130111576

文章目录

  • 1. 框架
    • 1.1 数据结构
    • 1.2 evdev_handler
    • 1.3 evdev_init
    • 1.4 input_register_handler
  • 2. 应用如何打开节点并读取到事件数据
    • 2.1 evdev_fops
    • 2.2 evdev_open
    • 2.3 evdev_release
    • 2.4 evdev_read
    • 2.5 evdev_write
    • 2.6 evdev_poll
    • 2.7 evdev_fasync
    • 2.8 evdev_ioctl
    • 2.9 evdev_ioctl_compat
    • 2.10 总结
  • 3. Driver 如何注册input设备并上报事件数据
    • 3.1 申请input device
      • 3.1.1 input_allocate_device
    • 3.2 注册input device
      • 3.2.1 input_register_device
    • 3.3 给input device 赋予属性
      • 3.3.1 input_set_abs_params
    • 3.4 input_report
      • 3.4.1 驱动程序实例
      • 3.4.2 事件流转分析
        • 3.4.2.1 分析input_event
        • 3.4.2.2 dispositon分析
        • 3.4.2.3 接着分析input_handle_event
        • 3.4.2.4 分析 input_pass_values
        • 3.4.2.5 分析 input_to_handler
        • 3.4.2.6 分析 evdev_event(s)
        • 3.4.2.7 分析 evdev_pass_values
        • 3.4.2.8 环形缓冲区
        • 3.4.2.9 分析__pass_event
  • 4. 应用打开节点并注入事件数据
    • 4.1 事件注入
    • 4.2 事件流向input_handler
    • 4.3 事件流向input_device
  • 5. input.c
    • 5.1 核心数据结构
      • 5.1.1 input_dev
      • 5.1.2 input_handler
      • 5.1.3 input_handle
      • 5.1.4 ff_device
      • 5.1.5 input_device_id
      • 5.1.6 ops
    • 5.2 功能细节
      • 5.2.1 Init 和 exit
      • 5.2.2 softrepeat 事件重复上报机制
  • 6. input-mt.c
    • 6.1 数据结构
      • 6.1.1 input_mt_slot
      • 6.1.2 input_mt
    • 6.2 init
      • 6.2.1 input_mt_init_slots
    • 6.3 TypeB report 实例
      • 6.3.1 input_mt_slot
      • 6.3.2 input_mt_report_slot_state
      • 6.3.3 input_report_abs
      • 6.3.4 input_sync
  • 7. input_polldev.c
    • 7.1 数据结构
      • 7.1.1 input_polled_dev
      • 7.1.2 input_dev_poller
    • 7.2 驱动程序实例
    • 7.3 input_allocate_polled_device
    • 7.4 input_register_polled_device
      • 7.4.1 input_polled_device_work
      • 7.4.2 input_open_polled_device
      • 7.4.3 input_close_polled_device
    • 7.5 设定/查询轮询间隔参数
    • 7.6 实验
  • 8. input_poller.c
    • 8.1 数据结构
    • 8.2 驱动程序实例
    • 8.3 input_setup_polling
    • 8.4 input_dev_poller_finalize
    • 8.5 input_dev_poller_start
    • 8.6 input_dev_poller_stop
    • 8.7 设定/查询轮询间隔参数
      • 8.7.1 驱动作者调用函数
      • 8.7.2 应用程序访问节点
  • 9. Keymap
  • 10. input-compat.c

基于Linux Kernel 5.10
src path: https://elixir.bootlin.com/linux/v5.10/source/drivers/input

1. 框架

在这里插入图片描述
可以用三个数据结构来描述框架,这里简单写

// ===== 输入子系统, 核心层 =====
文件 /drivers/input/input.c 内, 关键函数input_init              // 初始化class_register          // 注册类register_chrdev         // 注册设备(主设备号13)input_register_device   // 注册硬件设备 input_devinput_register_handler  // 注册软件抽象 input_handlerinput_register_handle   // 注册连接     input_handle// ===== 软件抽象层, 系统已实现 =====
1. static 初始化一个 input_handler 全局变量
2. 注册此变量, input_register_handler
3. 实现 event connect 等函数struct input_handler {event,connect, disconnect, start        // 函数具体实现的指针int minor;                              // 次设备号const char *name;                       // 显示在proc/bus/input/handlersconst struct input_device_id *id_table; // 驱动支持的id表(用于匹配input_dev)const struct input_device_id *blacklist;// id表黑名单struct list_head    h_list;             // 存放input_handle(没有r)的链表struct list_head    node;               // 存放input_handler自身的链表
};// ===== 连接层, 系统已实现 =====
多个文件 /drivers/input/*dev.c 内*/
1. *dev_connect里分配 input_handle(没有r)变量
2. 设置/初始化此变量
3. 注册, input_register_handle(没有r)struct input_handle(没有r) {void *private;                          // 私有数据, 指向了父指针struct input_dev *dev;                  // 指向input_devstruct input_handler *handler;          // 指向 input_handlerstruct list_head    d_node;             // 存放input_dev->h_list的链表struct list_head    h_node;             // 存放input_handler->h_list的链表
};// ===== 硬件设备层, 自己写 =====
1. 分配一个input_dev变量
2. 设置/初始化此变量
3. 注册, input_register_device
4. 硬件相关代码, open, close, event, sync等等.struct input_dev {const char *name;                       // 设备描述const char *phys;                       // 设备路径struct input_id id;                     // 总线类型. 供应商/产品/版本信息. 用于匹配 input_handlerunsigned long evbit[NBITS(EV_MAX)];     // 记录支持的事件类型位图unsigned long keybit[NBITS(KEY_MAX)];   // 记录支持的按键值位图unsigned long relbit[NBITS(REL_MAX)];   // 记录支持的相对坐标位图, 如滚轮unsigned long absbit[NBITS(ABS_MAX)];   // 记录支持的绝对坐标位图, 如触摸屏struct list_head    h_list;             // 存放input_handle(没有r)的链表struct list_head    node;               // 存放input_dev自身的链表
};

可以用下面的图描述这三者关系
在这里插入图片描述
Linux 中 input_handler、input_handle和input_dev 之间的联系
在 Linux 中,input_handler、input_handle 和 input_dev 是与输入子系统相关的三个重要概念,它们之间的联系如下:

  1. input_dev:input_dev 是输入设备的抽象表示,它包含了输入设备的各种属性和状态信息,如设备名称、设备类型、设备 ID、事件类型、事件码等。input_dev 通常由输入设备驱动程序创建,并在注册到输入子系统后被使用。
  2. input_handle:input_handle 是输入设备的句柄,它用于标识一个输入设备的实例。每个输入设备都有一个唯一的 input_handle,它由输入子系统分配并在注册时返回给输入设备驱动程序。input_handle 可以用于在输入子系统中查找和操作输入设备。
  3. input_handler:input_handler 是输入事件的处理程序,它负责处理输入设备产生的事件。每个输入设备都可以有一个或多个 input_handler,它们按照优先级顺序依次处理输入事件。input_handler 可以是内核中的一个模块或者用户空间中的一个应用程序。
    综上所述,input_dev、input_handle 和 input_handler 是 Linux 输入子系统中的三个重要概念,它们之间的联系是:input_dev 表示输入设备的抽象表示,input_handle 是输入设备的句柄,用于标识一个输入设备的实例,input_handler 是输入事件的处理程序,负责处理输入设备产生的事件。

接下来用evdev举例看看这三者关系是如何建立的

1.1 数据结构

struct evdev {int open;                                // open,当用户open此设备时,open的值加1struct input_handle handle;              // 包括了匹配的 dev 与 handlerstruct evdev_client __rcu *grab;         // 可以指定grab evdev_client,这样只会有此client获得input事件数据struct list_head client_list;            // 用于把所有client链接在一起spinlock_t client_lock; /* protects client_list */struct mutex mutex;struct device dev;                       // 用来嵌入到设备模型中struct cdev cdev;                        // 字符设备bool exist;                              // evdev init成功
};
struct evdev_client {unsigned int head;              // 头指针unsigned int tail;              // 尾指针unsigned int packet_head;       // 包指针spinlock_t buffer_lock;         /* protects access to buffer, head and tail */wait_queue_head_t wait;         //等待队列头struct fasync_struct *fasync;   //异步通知机制struct evdev *evdev;            //client的evdevstruct list_head node;          //连接同一evdev的其他clientenum input_clock_type clk_type;bool revoked;                   //client被注销则置1unsigned long *evmasks[EV_CNT];unsigned int bufsize;           // 循环队列大小,此size向上对齐2的幂struct input_event buffer[];    // 循环队列数组
};

evdev和evdev_client的关系
在这里插入图片描述

1.2 evdev_handler

static struct input_handler evdev_handler = {.event                = evdev_event,.events                = evdev_events,.connect        = evdev_connect,.disconnect        = evdev_disconnect,.legacy_minors        = true,.minor                = EVDEV_MINOR_BASE,.name                = "evdev",.id_table        = evdev_ids,
};

1.3 evdev_init

static int __init evdev_init(void)
{return input_register_handler(&evdev_handler);
}

1.4 input_register_handler

/*** input_register_handler - register a new input handler* @handler: handler to be registered** This function registers a new input handler (interface) for input* devices in the system and attaches it to all input devices that* are compatible with the handler.*/
int input_register_handler(struct input_handler *handler)
{struct input_dev *dev;int error;error = mutex_lock_interruptible(&input_mutex);if (error)return error;INIT_LIST_HEAD(&handler->h_list);list_add_tail(&handler->node, &input_handler_list);/* 根据handler从input_dev_list匹配dev来执行connect */list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler);    /* wakeup thread in poll_wait */input_wakeup_procfs_readers();mutex_unlock(&input_mutex);return 0;
}
EXPORT_SYMBOL(input_register_handler);
👇
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{const struct input_device_id *id;int error;id = input_match_device(handler, dev);if (!id)return -ENODEV;error = handler->connect(handler, dev, id);if (error && error != -ENODEV)pr_err("failed to attach handler %s to device %s, error: %d\n",handler->name, kobject_name(&dev->dev.kobj), error);return error;
}
👇
static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev)
{const struct input_device_id *id;for (id = handler->id_table; id->flags || id->driver_info; id++) {if (input_match_device_id(dev, id) &&(!handler->match || handler->match(handler, dev))) {return id;}}return NULL;
}
👇
bool input_match_device_id(const struct input_dev *dev,const struct input_device_id *id)
{if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)if (id->bustype != dev->id.bustype)return false;if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)if (id->vendor != dev->id.vendor)return false;if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)if (id->product != dev->id.product)return false;if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)if (id->version != dev->id.version)return false;if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||!bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||!bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||!bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||!bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||!bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {return false;}return true;
}
EXPORT_SYMBOL(input_match_device_id);

看完了match再来看connect
evdev_connect

/** Create new evdev device. Note that input core serializes calls* to connect and disconnect.*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{struct evdev *evdev;int minor;int dev_no;int error;minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);if (minor < 0) {error = minor;pr_err("failed to reserve new minor: %d\n", error);return error;}evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);if (!evdev) {error = -ENOMEM;goto err_free_minor;}INIT_LIST_HEAD(&evdev->client_list);spin_lock_init(&evdev->client_lock);mutex_init(&evdev->mutex);evdev->exist = true;dev_no = minor;/* Normalize device number if it falls into legacy range */if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)dev_no -= EVDEV_MINOR_BASE;dev_set_name(&evdev->dev, "event%d", dev_no);//给 handle 匹配 device 和 handlerevdev->handle.dev = input_get_device(dev);evdev->handle.name = dev_name(&evdev->dev);evdev->handle.handler = handler;            //evdev_handlerevdev->handle.private = evdev;//创建deviceevdev->dev.devt = MKDEV(INPUT_MAJOR, minor);evdev->dev.class = &input_class;evdev->dev.parent = &dev->dev;evdev->dev.release = evdev_free;//初始化device,kobj mutex list pm node ...device_initialize(&evdev->dev);//将 handle 挂在 device 和 handler的链表上error = input_register_handle(&evdev->handle);if (error)goto err_free_evdev;//初始化字符设备 list kobj opscdev_init(&evdev->cdev, &evdev_fops);//调用 cdev_add 和 device_add//cdev_add: 给cdev添加 dev_t,添加到 cdev_map//device_add: 创建节点 /dev/input/eventXerror = cdev_device_add(&evdev->cdev, &evdev->dev);if (error)goto err_cleanup_evdev;return 0;err_cleanup_evdev:evdev_cleanup(evdev);input_unregister_handle(&evdev->handle);err_free_evdev:put_device(&evdev->dev);err_free_minor:input_free_minor(minor);return error;
}

至此三者的链接就建立完毕了,呈现出这样的关系

在这里插入图片描述

2. 应用如何打开节点并读取到事件数据

在这里,应用作为input事件的消费者,可以通过打开节点的方式获取到事件数据

2.1 evdev_fops

/dev/input/eventX
static const struct file_operations evdev_fops = {.owner                = THIS_MODULE,.read                = evdev_read,.write                = evdev_write,.poll                = evdev_poll,.open                = evdev_open,.release        = evdev_release,.unlocked_ioctl        = evdev_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl        = evdev_ioctl_compat,
#endif.fasync                = evdev_fasync,.llseek                = no_llseek,
};

2.2 evdev_open

核心动作是创建client将其初始化后挂入evdev的client_list

static int evdev_open(struct inode *inode, struct file *file)
{struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);//hint_events_per_packet估算buffersizestruct evdev_client *client;//一个监听线程对应一个clientint error;client = kvzalloc(struct_size(client, buffer, bufsize), GFP_KERNEL);if (!client)return -ENOMEM;init_waitqueue_head(&client->wait);   //等待队列client->bufsize = bufsize;            //设定bufsizespin_lock_init(&client->buffer_lock); //buffer_lockclient->evdev = evdev;                //建立链接evdev_attach_client(evdev, client);   //将client挂入evdev的client_list//evdev_open_device: evdev->open ++, input_open_device(&evdev->handle);//input_open_device:handle->open++error = evdev_open_device(evdev);if (error)goto err_free_client;file->private_data = client;//设定file的f_mode 使可读写等stream_open(inode, file);return 0;err_free_client:evdev_detach_client(evdev, client);kvfree(client);return error;
}

2.3 evdev_release

核心动作是将client从client_list删除

static int evdev_release(struct inode *inode, struct file *file)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;unsigned int i;mutex_lock(&evdev->mutex);if (evdev->exist && !client->revoked)input_flush_device(&evdev->handle, file);/* 如果此client grab此evdev则执行ungrab操作 */evdev_ungrab(evdev, client);mutex_unlock(&evdev->mutex);/* 将client从 client_list 上删除 */evdev_detach_client(evdev, client);for (i = 0; i < EV_CNT; ++i)bitmap_free(client->evmasks[i]);kvfree(client);/* 检查执行input_close_device */evdev_close_device(evdev);return 0;
}
static void evdev_close_device(struct evdev *evdev)
{mutex_lock(&evdev->mutex);/* evdev存在 并且 evdev没有open的device了,则close此handle对应的device */if (evdev->exist && !--evdev->open)input_close_device(&evdev->handle);mutex_unlock(&evdev->mutex);
}
/*** input_close_device - close input device* @handle: handle through which device is being accessed** This function should be called by input handlers when they* want to stop receive events from given input device.*/
void input_close_device(struct input_handle *handle)
{struct input_dev *dev = handle->dev;mutex_lock(&dev->mutex);__input_release_device(handle);if (!--dev->users) {if (dev->poller)input_dev_poller_stop(dev->poller);if (dev->close)dev->close(dev);}if (!--handle->open) {/** synchronize_rcu() makes sure that input_pass_event()* completed and that no more input events are delivered* through this handle*/synchronize_rcu();}mutex_unlock(&dev->mutex);
}
EXPORT_SYMBOL(input_close_device);

2.4 evdev_read

应用程序实例

int main(int argc, char **argv)
{int len;struct input_event event;......while (1){len = read(fd, &event, sizeof(event));        //如果使用阻塞方式未读取到信息内核将处于休眠态//如果使用非阻塞方式且未读取到信息将进入else分支if (len == sizeof(event)){printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);}else{        printf("read err %d\n", len);}}
}

内核函数分析

static ssize_t evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_event event;   //存放读取的eventsize_t read = 0;            //读到数据字节数int error;if (count != 0 && count < input_event_size())return -EINVAL;for (;;) {/* evdev不存在或client被注销 */if (!evdev->exist || client->revoked)return -ENODEV;/* 是非阻塞访问,并且缓冲区为空 */if (client->packet_head == client->tail &&(file->f_flags & O_NONBLOCK))return -EAGAIN;/** count == 0 is special - no IO is done but we check* for error conditions (see above).*//* 读0字节直接返回 */if (count == 0)break;/* * 读取到字节数+一个event字节数 <= 用户读取的字节数 且 缓冲区中有event* 则会将缓冲区读取到的event发送到用户空间,并更新读取到的字节数  */while (read + input_event_size() <= count &&evdev_fetch_next_event(client, &event)) {if (input_event_to_user(buffer + read, &event))return -EFAULT;read += input_event_size();}/* 如果之前没读取到数据 */if (read)break;/* 如果用户阻塞读取 */if (!(file->f_flags & O_NONBLOCK)) {/* 将用户读取线程插入等待队列,满足以下条件之一结束等待*    1.缓冲区不为空*    2.evdev不存在,此时应该是被注销了*    3.client被注销*/error = wait_event_interruptible(client->wait,client->packet_head != client->tail ||!evdev->exist || client->revoked);if (error)return error;}}return read;
}

2.5 evdev_write

对应事件注入,应用程序实例如下

    struct input_event ev;memset(&ev, 0, sizeof(struct input_event));ev.type = EV_ABS;gettimeofday(&ev.time, NULL); ev.code = ABS_X;ev.value = 0x000001f8;if (write(fd, &ev, sizeof(struct input_event)) < 0) {printf("write error\n");}

内核函数分析

static ssize_t evdev_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_event event;int retval = 0;//从用户空间读取字节数/* 用户写字节数 < event 字节数 返回inval */if (count != 0 && count < input_event_size())return -EINVAL;retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;/* evdev如果不存在和client被注销返回 nodev */if (!evdev->exist || client->revoked) {retval = -ENODEV;goto out;}/* * 如从用户空间读取字节数+一个event字节数 <= 用户写入的字节数* 则按event注入节点*/while (retval + input_event_size() <= count) {if (input_event_from_user(buffer + retval, &event)) {retval = -EFAULT;goto out;}retval += input_event_size();input_inject_event(&evdev->handle,event.type, event.code, event.value);cond_resched();//调度}out:mutex_unlock(&evdev->mutex);return retval;
}

2.6 evdev_poll

/* ./03_input_read_poll /dev/input/event0 */
int main(int argc, char **argv)
{//int fd;int err;int len;int ret;int i;unsigned char byte;int bit;struct input_id id;unsigned int evbit[2];struct input_event event;char *ev_names[] = {"EV_SYN ","EV_KEY ","EV_REL ","EV_ABS ","EV_MSC ","EV_SW        ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","EV_LED ","EV_SND ","NULL ","EV_REP ","EV_FF        ","EV_PWR ",};/*输入参数解析和设备ID信息获取输出*/if (argc < 2){printf("Usage: %s <dev>\n", argv[0]);return -1;}int num_devices = argc - 1;int fd[num_devices];struct pollfd fds[num_devices];nfds_t nfds = num_devices;//poll设备数量//遍历输出各节点的input_id和ev_bit信息for(int i = 0; i < num_devices; i ++){fd[i] = open(argv[i+1], O_RDWR | O_NONBLOCK);        if (fd[i] < 0){printf("open %s err\n", argv[i]);return -1;}err = ioctl(fd[i], EVIOCGID, &id);if (err == 0){printf("dev:%s\n", argv[i+1] );                printf("bustype = 0x%x\n", id.bustype );printf("vendor        = 0x%x\n", id.vendor  );printf("product = 0x%x\n", id.product );printf("version = 0x%x\n", id.version );}len = ioctl(fd[i], EVIOCGBIT(0, sizeof(evbit)), &evbit);if (len > 0 && len <= sizeof(evbit)){printf("support ev type: ");for (int j = 0; j < len; j++){byte = ((unsigned char *)evbit)[j];for (bit = 0; bit < 8; bit++){if (byte & (1<<bit)) {printf("%s ", ev_names[j*8 + bit]);}}}printf("\n");}printf("\n");}/*使用poll函数完成多个设备节点信息实时读取*/while (1){//poll所有的设备for(int i = 0; i < num_devices; i ++){fds[i].fd = fd[i];fds[i].events  = POLLIN;fds[i].revents = 0;ret = poll(fds, nfds, 100);}if (ret > 0){//输出revents == POLLIN的设备的input_eventfor(int i = 0; i < num_devices; i++){if (fds[i].revents == POLLIN){while (read(fd[i], &event, sizeof(event)) == sizeof(event)){printf("get event:dev:%s type = 0x%x, code = 0x%x, value = 0x%x\n", argv[i+1], event.type, event.code, event.value);}}}}else if (ret == 0){//printf("time out\n");}else{printf("poll err\n");}}return 0;
}

内核函数分析

/* No kernel lock - fine */
static __poll_t evdev_poll(struct file *file, poll_table *wait)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;__poll_t mask;/* 休眠直到 wakeup client->wait */poll_wait(file, &client->wait, wait);/* 设备存在且client未被注销 */if (evdev->exist && !client->revoked)mask = EPOLLOUT | EPOLLWRNORM;elsemask = EPOLLHUP | EPOLLERR;/* 如果缓冲区不为空则返回 EPOLLIN | EPOLLRDNORM */if (client->packet_head != client->tail)mask |= EPOLLIN | EPOLLRDNORM;return mask;
}/*EPOLLIN         读就绪EPOLLOUT        写就绪EPOLLPRI        有数据紧急读取EPOLLERR        assoc. fd有错误情况发生EPOLLHUP        assoc. fd发生挂起EPOLLRT         设置边缘触发(ET)(默认的是水平触发)EPOLLONESHOT    设置为 one-short 行为,一个事件(event)被拉出后,对应的fd在内部被禁用EPOLLRDNORM     和 EPOLLIN 相等EPOLLRDBAND     优先读取的数据带(data band)EPOLLWRNORM     和 EPOLLOUT 相等EPOLLWRBAND     优先写的数据带(data band)EPOLLMSG        忽视
*/

2.7 evdev_fasync

应用程序实例

/* 存放驱动设备文件 */
static int fd;
/* SGIO信号对应函数 */
static void sig_func(int sig)
{int val;read(fd, &val, 4);printf("get button : 0x%x\n", val);
}int main(int argc, char **argv)
{int val;struct pollfd fds[1];int timeout_ms = 5000;int ret;int        flags;/* 1. 判断参数 */if (argc != 2) {printf("Usage: %s <dev>\n", argv[0]);return -1;}/* 给信号注册函数,当线程收到SIGIO信号时执行sig_func函数 */signal(SIGIO, sig_func);/* 2. 打开文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}/* 向内核的文件系统层次传递PID */fcntl(fd, F_SETOWN, getpid());/* 读取驱动程序中的flag */flags = fcntl(fd, F_GETFL);/* 设置驱动flag中FASYNC位为1,此操作会导致驱动中fasync函数被调用 */fcntl(fd, F_SETFL, flags | FASYNC);while (1){sleep(2);}close(fd);return 0;
}

内核函数分析

static int evdev_fasync(int fd, struct file *file, int on)
{struct evdev_client *client = file->private_data;/* 初始化client->fasync */return fasync_helper(fd, file, on, &client->fasync);
}
👇
下面的函数在device上报事件数据时会调用
static void __pass_event(struct evdev_client *client,const struct input_event *event)
{client->buffer[client->head++] = *event;client->head &= client->bufsize - 1;if (unlikely(client->head == client->tail)) {/** This effectively "drops" all unconsumed events, leaving* EV_SYN/SYN_DROPPED plus the newest event in the queue.*/client->tail = (client->head - 2) & (client->bufsize - 1);client->buffer[client->tail] = (struct input_event) {.input_event_sec = event->input_event_sec,.input_event_usec = event->input_event_usec,.type = EV_SYN,.code = SYN_DROPPED,.value = 0,};client->packet_head = client->tail;}if (event->type == EV_SYN && event->code == SYN_REPORT) {client->packet_head = client->head;/* 向监听线程发SIGIO信号 */kill_fasync(&client->fasync, SIGIO, POLL_IN);}
}

2.8 evdev_ioctl

应用程序实例

int main(int argc, char **argv)
{int fd;int err;int len;int i;unsigned char byte;int bit;struct input_id id;unsigned int evbit[2];char *ev_names[] = {"EV_SYN ","EV_KEY ","EV_REL ","EV_ABS ","EV_MSC ","EV_SW        ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","NULL ","EV_LED ","EV_SND ","NULL ","EV_REP ","EV_FF        ","EV_PWR ",};if (argc != 2){printf("Usage: %s <dev>\n", argv[0]);return -1;}fd = open(argv[1], O_RDWR);if (fd < 0){printf("open %s err\n", argv[1]);return -1;}//获取fd设备节点的id信息err = ioctl(fd, EVIOCGID, &id);if (err == 0){printf("bustype = 0x%x\n", id.bustype );printf("vendor        = 0x%x\n", id.vendor  );printf("product = 0x%x\n", id.product );printf("version = 0x%x\n", id.version );}//获取fd设备节点的evbit信息len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);if (len > 0 && len <= sizeof(evbit)){//根据evbit信息判断输出节点支持的事件类型printf("support ev type: ");for (i = 0; i < len; i++){byte = ((unsigned char *)evbit)[i];for (bit = 0; bit < 8; bit++){if (byte & (1<<bit)) {printf("%s ", ev_names[i*8 + bit]);}}}printf("\n");}return 0;
}

内核函数分析

static long evdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{return evdev_ioctl_handler(file, cmd, (void __user *)arg, 0);
}
👇
static long evdev_ioctl_handler(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;int retval;retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;if (!evdev->exist || client->revoked) {retval = -ENODEV;goto out;}retval = evdev_do_ioctl(file, cmd, p, compat_mode);out:mutex_unlock(&evdev->mutex);return retval;
}
👇
static long evdev_do_ioctl(struct file *file, unsigned int cmd,void __user *p, int compat_mode)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_dev *dev = evdev->handle.dev;struct input_absinfo abs;struct input_mask mask;struct ff_effect effect;int __user *ip = (int __user *)p;unsigned int i, t, u, v;unsigned int size;int error;/* First we check for fixed-length commands */switch (cmd) {case EVIOCGVERSION:return put_user(EV_VERSION, ip);case EVIOCGID:if (copy_to_user(p, &dev->id, sizeof(struct input_id)))return -EFAULT;return 0;case EVIOCGREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (put_user(dev->rep[REP_DELAY], ip))return -EFAULT;if (put_user(dev->rep[REP_PERIOD], ip + 1))return -EFAULT;return 0;case EVIOCSREP:if (!test_bit(EV_REP, dev->evbit))return -ENOSYS;if (get_user(u, ip))return -EFAULT;if (get_user(v, ip + 1))return -EFAULT;input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u);input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v);return 0;case EVIOCRMFF:return input_ff_erase(dev, (int)(unsigned long) p, file);case EVIOCGEFFECTS:i = test_bit(EV_FF, dev->evbit) ?dev->ff->max_effects : 0;if (put_user(i, ip))return -EFAULT;return 0;case EVIOCGRAB:if (p)return evdev_grab(evdev, client);elsereturn evdev_ungrab(evdev, client);case EVIOCREVOKE:if (p)return -EINVAL;elsereturn evdev_revoke(evdev, client, file);case EVIOCGMASK: {void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (void __user *)(unsigned long)mask.codes_ptr;return evdev_get_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSMASK: {const void __user *codes_ptr;if (copy_from_user(&mask, p, sizeof(mask)))return -EFAULT;codes_ptr = (const void __user *)(unsigned long)mask.codes_ptr;return evdev_set_mask(client,mask.type, codes_ptr, mask.codes_size,compat_mode);}case EVIOCSCLOCKID:if (copy_from_user(&i, p, sizeof(unsigned int)))return -EFAULT;return evdev_set_clk_type(client, i);case EVIOCGKEYCODE:return evdev_handle_get_keycode(dev, p);case EVIOCSKEYCODE:return evdev_handle_set_keycode(dev, p);case EVIOCGKEYCODE_V2:return evdev_handle_get_keycode_v2(dev, p);case EVIOCSKEYCODE_V2:return evdev_handle_set_keycode_v2(dev, p);}size = _IOC_SIZE(cmd);/* Now check variable-length commands */
#define EVIOC_MASK_SIZE(nr)        ((nr) & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT))switch (EVIOC_MASK_SIZE(cmd)) {case EVIOCGPROP(0):return bits_to_user(dev->propbit, INPUT_PROP_MAX,size, p, compat_mode);case EVIOCGMTSLOTS(0):return evdev_handle_mt_request(dev, size, ip);case EVIOCGKEY(0):return evdev_handle_get_val(client, dev, EV_KEY, dev->key,KEY_MAX, size, p, compat_mode);case EVIOCGLED(0):return evdev_handle_get_val(client, dev, EV_LED, dev->led,LED_MAX, size, p, compat_mode);case EVIOCGSND(0):return evdev_handle_get_val(client, dev, EV_SND, dev->snd,SND_MAX, size, p, compat_mode);case EVIOCGSW(0):return evdev_handle_get_val(client, dev, EV_SW, dev->sw,SW_MAX, size, p, compat_mode);case EVIOCGNAME(0):return str_to_user(dev->name, size, p);case EVIOCGPHYS(0):return str_to_user(dev->phys, size, p);case EVIOCGUNIQ(0):return str_to_user(dev->uniq, size, p);case EVIOC_MASK_SIZE(EVIOCSFF):if (input_ff_effect_from_user(p, size, &effect))return -EFAULT;error = input_ff_upload(dev, &effect, file);if (error)return error;if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))return -EFAULT;return 0;}/* Multi-number variable-length handlers */if (_IOC_TYPE(cmd) != 'E')return -EINVAL;if (_IOC_DIR(cmd) == _IOC_READ) {if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))return handle_eviocgbit(dev,_IOC_NR(cmd) & EV_MAX, size,p, compat_mode);if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;abs = dev->absinfo[t];if (copy_to_user(p, &abs, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;return 0;}}if (_IOC_DIR(cmd) == _IOC_WRITE) {if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {if (!dev->absinfo)return -EINVAL;t = _IOC_NR(cmd) & ABS_MAX;if (copy_from_user(&abs, p, min_t(size_t,size, sizeof(struct input_absinfo))))return -EFAULT;if (size < sizeof(struct input_absinfo))abs.resolution = 0;/* We can't change number of reserved MT slots */if (t == ABS_MT_SLOT)return -EINVAL;/** Take event lock to ensure that we are not* changing device parameters in the middle* of event.*/spin_lock_irq(&dev->event_lock);dev->absinfo[t] = abs;spin_unlock_irq(&dev->event_lock);return 0;}}return -EINVAL;
}

2.9 evdev_ioctl_compat

逻辑同上evdev_ioctl,多了数据类型转换和标志位compat_mode置1

static long evdev_ioctl_compat(struct file *file,unsigned int cmd, unsigned long arg)
{return evdev_ioctl_handler(file, cmd, compat_ptr(arg), 1);
}

2.10 总结

从上面的分析不难看出,应用程序读取节点event数据的核心逻辑就是判断client->packet_head != client->tail client->buffer不为空,就从buffer中读取event事件。
如果client->buffer为空,poll和阻塞read会选择将线程挂入client->wait休眠,device再次上报事件时会唤醒这些线程。

3. Driver 如何注册input设备并上报事件数据

在这里,device作为事件的生产者,需要通过driver中的逻辑完成input事件的上报
申请input device > 设定input device 的属性 > 注册input device > 上报事件

3.1 申请input device

3.1.1 input_allocate_device

/*** input_allocate_device - allocate memory for new input device** Returns prepared struct input_dev or %NULL.** NOTE: Use input_free_device() to free devices that have not been* registered; input_unregister_device() should be used for already* registered devices.*/
struct input_dev *input_allocate_device(void)
{static atomic_t input_no = ATOMIC_INIT(-1);struct input_dev *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class;device_initialize(&dev->dev);mutex_init(&dev->mutex);spin_lock_init(&dev->event_lock);timer_setup(&dev->timer, NULL, 0);INIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);dev_set_name(&dev->dev, "input%lu",(unsigned long)atomic_inc_return(&input_no));__module_get(THIS_MODULE);}return dev;
}
EXPORT_SYMBOL(input_allocate_device);

3.2 注册input device

3.2.1 input_register_device

/*** input_register_device - register device with input core* @dev: device to be registered** This function registers device with input core. The device must be* allocated with input_allocate_device() and all it's capabilities* set up before registering.* If function fails the device must be freed with input_free_device().* Once device has been successfully registered it can be unregistered* with input_unregister_device(); input_free_device() should not be* called in this case.** Note that this function is also used to register managed input devices* (ones allocated with devm_input_allocate_device()). Such managed input* devices need not be explicitly unregistered or freed, their tear down* is controlled by the devres infrastructure. It is also worth noting* that tear down of managed input devices is internally a 2-step process:* registered managed input device is first unregistered, but stays in* memory and can still handle input_event() calls (although events will* not be delivered anywhere). The freeing of managed input device will* happen later, when devres stack is unwound to the point where device* allocation was made.*/int input_register_device(struct input_dev *dev){struct input_devres *devres = NULL;struct input_handler *handler;unsigned int packet_size;const char *path;int error;if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) {dev_err(&dev->dev,"Absolute device without dev->absinfo, refusing to register\n");return -EINVAL;}if (dev->devres_managed) {//devres_managed device生命周期跟随driverdevres = devres_alloc(devm_input_device_unregister,sizeof(*devres), GFP_KERNEL);if (!devres)return -ENOMEM;devres->input = dev;}/* Every input device generates EV_SYN/SYN_REPORT events. */__set_bit(EV_SYN, dev->evbit);//设定支持EV_SYN/* KEY_RESERVED is not supposed to be transmitted to userspace. */__clear_bit(KEY_RESERVED, dev->keybit);//设定不支持KEY_RESERVED/* Make sure that bitmasks not mentioned in dev->evbit are clean. */input_cleanse_bitmasks(dev);//清除不支持type的evbitpacket_size = input_estimate_events_per_packet(dev);//估算packet_sizeif (dev->hint_events_per_packet < packet_size)//支持driver自定义input_dev的packet_sizedev->hint_events_per_packet = packet_size;dev->max_vals = dev->hint_events_per_packet + 2;//一帧最多可以插入的valuedev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);if (!dev->vals) {error = -ENOMEM;goto err_devres_free;}/** If delay and period are pre-set by the driver, then autorepeating* is handled by the driver itself and we don't do it in input.c.*/if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])input_enable_softrepeat(dev, 250, 33);//设定dev,250ms第一次自动上报,33ms为周期自动上报if (!dev->getkeycode)dev->getkeycode = input_default_getkeycode;if (!dev->setkeycode)dev->setkeycode = input_default_setkeycode;if (dev->poller)input_dev_poller_finalize(dev->poller);error = device_add(&dev->dev);if (error)goto err_free_vals;path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device",path ? path : "N/A");kfree(path);error = mutex_lock_interruptible(&input_mutex);if (error)goto err_device_del;list_add_tail(&dev->node, &input_dev_list);list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();mutex_unlock(&input_mutex);if (dev->devres_managed) {dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",__func__, dev_name(&dev->dev));devres_add(dev->dev.parent, devres);}return 0;err_device_del:device_del(&dev->dev);
err_free_vals:kfree(dev->vals);dev->vals = NULL;
err_devres_free:devres_free(devres);return error;
}
EXPORT_SYMBOL(input_register_device);

3.3 给input device 赋予属性

属性分为 type code value 三种,举个例子

type:
#define EV_SYN                        0x00                                                                                                //同步事件 Synchronize
code:
/** Synchronization events.*/
#define SYN_REPORT                0                                                                                                //用于将事件同步并分离为同时发生的输入数据变化的数据包。
#define SYN_CONFIG                1                                                                                                        //To Be Discussed 待讨论
#define SYN_MT_REPORT                2                                                                                //用于同步和分离触摸事件。
#define SYN_DROPPED                3                                                                                        //用于指示 evdev 客户端的事件队列中的缓冲区溢出。
#define SYN_MAX                        0xf                                                                                                //SYN类型事件最大个数和提供位掩码支持
#define SYN_CNT                        (SYN_MAX+1)                                                //SYN类型事件支持事件个数 count
value:
//value根据事件的含义不同而不同
//例如 SYN_CONFIG 表示这是一个config事件它的value可以随意指定

Ref: https://blog.csdn.net/weixin_43444989/article/details/122597029
可以直接操作位图赋值

    ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);ts->input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT);

也可以使用一些接口

//设定type为EV_ABS的code的 最小值、最大值、噪声和flat
//噪声值见input_defuzz_abs_event
void input_set_abs_params(struct input_dev *dev, unsigned int axis,int min, int max, int fuzz, int flat)
//将设备标记为能够处理特定事件 ,设定 code
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
//键盘相关 没用过
int input_set_keycode(struct input_dev *dev,const struct input_keymap_entry *ke)
//设定时间戳,在input_report_key API 中会被调用
void input_set_timestamp(struct input_dev *dev, ktime_t timestamp)

3.3.1 input_set_abs_params

相关数据结构

/*** struct input_absinfo - used by EVIOCGABS/EVIOCSABS ioctls* @value: latest reported value for the axis.* @minimum: specifies minimum value for the axis.* @maximum: specifies maximum value for the axis.* @fuzz: specifies fuzz value that is used to filter noise from*        the event stream.* @flat: values that are within this value will be discarded by*        joydev interface and reported as 0 instead.* @resolution: specifies resolution for the values reported for*        the axis.** Note that input core does not clamp reported values to the* [minimum, maximum] limits, such task is left to userspace.** The default resolution for main axes (ABS_X, ABS_Y, ABS_Z)* is reported in units per millimeter (units/mm), resolution* for rotational axes (ABS_RX, ABS_RY, ABS_RZ) is reported* in units per radian.* When INPUT_PROP_ACCELEROMETER is set the resolution changes.* The main axes (ABS_X, ABS_Y, ABS_Z) are then reported in* in units per g (units/g) and in units per degree per second* (units/deg/s) for rotational axes (ABS_RX, ABS_RY, ABS_RZ).*/
struct input_absinfo {__s32 value;__s32 minimum;__s32 maximum;__s32 fuzz;__s32 flat;__s32 resolution;
};

函数分析

#define ABS_MAX                        0x3f
#define ABS_CNT                        (ABS_MAX+1)/*** input_alloc_absinfo - allocates array of input_absinfo structs* @dev: the input device emitting absolute events** If the absinfo struct the caller asked for is already allocated, this* functions will not do anything.*/
void input_alloc_absinfo(struct input_dev *dev)
{/* 如果input_dev->absinfo存在则直接返回 */if (dev->absinfo)return;/* 给所有的ABS event申请absinfo */dev->absinfo = kcalloc(ABS_CNT, sizeof(*dev->absinfo), GFP_KERNEL);if (!dev->absinfo) {dev_err(dev->dev.parent ?: &dev->dev,"%s: unable to allocate memory\n", __func__);/** We will handle this allocation failure in* input_register_device() when we refuse to register input* device with ABS bits but without absinfo.* * 当我们拒绝注册具有 ABS 位但没有 absinfo 的输入设备时,* 我们将在 input_register_device() 中处理此分配失败。*/}
}
EXPORT_SYMBOL(input_alloc_absinfo);void input_set_abs_params(struct input_dev *dev, unsigned int axis,int min, int max, int fuzz, int flat)
{struct input_absinfo *absinfo;/* 为input_dev申请absinfo空间 */input_alloc_absinfo(dev);/* 如果申请失败直接返回 */if (!dev->absinfo)return;/* 将ABS event 设定参数存入absinfo结构体对应成员内 */absinfo = &dev->absinfo[axis];absinfo->minimum = min;absinfo->maximum = max;absinfo->fuzz = fuzz;absinfo->flat = flat;/* input_dev 位图置位 */__set_bit(EV_ABS, dev->evbit);__set_bit(axis, dev->absbit);
}
EXPORT_SYMBOL(input_set_abs_params);

3.4 input_report

3.4.1 驱动程序实例

input_report_abs(ts->pen_input_dev, ABS_X, pen_x);
input_report_abs(ts->pen_input_dev, ABS_Y, pen_y);
input_report_abs(ts->pen_input_dev, ABS_PRESSURE, pen_pressure);
...
input_sync(ts->pen_input_dev);

3.4.2 事件流转分析

input_report_abs input_sync是封装了input_event函数

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{input_event(dev, EV_ABS, code, value);
}
static inline void input_sync(struct input_dev *dev)
{input_event(dev, EV_SYN, SYN_REPORT, 0);
}

3.4.2.1 分析input_event

input_event开始着手分析事件流转

/*** input_event() - report new input event* @dev: device that generated the event* @type: type of the event* @code: event code* @value: value of the event** This function should be used by drivers implementing various input* devices to report input events. See also input_inject_event().** NOTE: input_event() may be safely used right after input device was* allocated with input_allocate_device(), even before it is registered* with input_register_device(), but the event will not reach any of the* input handlers. Such early invocation of input_event() may be used* to 'seed' initial state of a switch or initial position of absolute* axis, etc.*/
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{unsigned long flags;if (is_event_supported(type, dev->evbit, EV_MAX)) {spin_lock_irqsave(&dev->event_lock, flags);input_handle_event(dev, type, code, value);spin_unlock_irqrestore(&dev->event_lock, flags);}
}
EXPORT_SYMBOL(input_event);
👇
#define INPUT_IGNORE_EVENT        0        //忽略
#define INPUT_PASS_TO_HANDLERS        1    //交给handler处理
#define INPUT_PASS_TO_DEVICE        2      //交给device处理
#define INPUT_SLOT                4        //需要刷新挂起的slot事件
#define INPUT_FLUSH                8       //需要handler立即处理 只有SYN_REPORT有
#define INPUT_PASS_TO_ALL        (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{/* 获取disposition,根据disposition来判断event的传递方向 */int disposition = input_get_disposition(dev, type, code, &value);...
}

3.4.2.2 dispositon分析

Disposition 可以理解为事件的性质,该标志决定了事件的流向

input_sync dispositon分析
👇
disposition = INPUT_PASS_TO_HANDLERS | INPUT_FLUSH;
input_report_abs dispositon分析
👇
if (is_event_supported(code, dev->absbit, ABS_MAX))disposition = input_handle_abs_event(dev, code, &value);
👇
static int input_handle_abs_event(struct input_dev *dev,unsigned int code, int *pval)
{struct input_mt *mt = dev->mt;bool is_mt_event;int *pold;/* 如果是ABS_MT_SLOT则ingnor,在真实touch数据上报时一并上报 */if (code == ABS_MT_SLOT) {dev->mt/** "Stage" the event; we'll flush it later, when we* get actual touch data.*//* *  mt和 slot value 都存在,且slot value < 设备最大支持slot数*  设定mt->slot = slot value,即设定当前传输的slot value*/if (mt && *pval >= 0 && *pval < mt->num_slots)mt->slot = *pval;return INPUT_IGNORE_EVENT;}/* * code为除 ABS_MT_SLOT 以外的其他ABS_MT事件则置1* 这里是为了过滤非MT事件 */is_mt_event = input_is_mt_value(code);if (!is_mt_event) {/* * 是ABS_MT_SLOT或其他ABS类型事件* 则 pold 指向event的absinfo value*/pold = &dev->absinfo[code].value;} else if (mt) {/* * 是非ABS_MT_SLOT的其他ABS_MT事件* 则pold 指向第slot value个slot的事件value* .abs[]:此int数组存放ABS_MT事件的value*/pold = &mt->slots[mt->slot].abs[code - ABS_MT_FIRST];} else {/** Bypass filtering for multi-touch events when* not employing slots.* 不使用slot时绕过多点触摸事件的过滤。*/pold = NULL;}if (pold) {/* 根据设定噪声值结合*pold调整*pval */*pval = input_defuzz_abs_event(*pval, *pold,dev->absinfo[code].fuzz);/* 实现重复键值不报功能 */if (*pold == *pval)return INPUT_IGNORE_EVENT;*pold = *pval;}/* Flush pending "slot" event *//* * code是ABS_MT事件 且 dev使用mt 且 dev当前传输的slot value不等于absinfo中保存的上一次slot value* 则将dev当前传输的slot value存入absinfo**/if (is_mt_event && mt && mt->slot != input_abs_get_val(dev, ABS_MT_SLOT)) {input_abs_set_val(dev, ABS_MT_SLOT, mt->slot);return INPUT_PASS_TO_HANDLERS | INPUT_SLOT;}return INPUT_PASS_TO_HANDLERS;
}

tips:关于input_abs_set_xxx和input_abs_get_xxx
在input.h中使用宏定义了这一系列函数,作用可以概括为从input_dev->absinfo中设定或者获取数据

#define INPUT_GENERATE_ABS_ACCESSORS(_suffix, _item)                        \
static inline int input_abs_get_##_suffix(struct input_dev *dev,        \unsigned int axis)                \
{                                                                        \return dev->absinfo ? dev->absinfo[axis]._item : 0;                \
}                                                                        \\
static inline void input_abs_set_##_suffix(struct input_dev *dev,        \unsigned int axis, int val)        \
{                                                                        \input_alloc_absinfo(dev);                                        \if (dev->absinfo)                                                \dev->absinfo[axis]._item = val;                                \
}INPUT_GENERATE_ABS_ACCESSORS(val, value)
INPUT_GENERATE_ABS_ACCESSORS(min, minimum)
INPUT_GENERATE_ABS_ACCESSORS(max, maximum)
INPUT_GENERATE_ABS_ACCESSORS(fuzz, fuzz)
INPUT_GENERATE_ABS_ACCESSORS(flat, flat)
INPUT_GENERATE_ABS_ACCESSORS(res, resolution)

3.4.2.3 接着分析input_handle_event


#define INPUT_IGNORE_EVENT        0        //忽略
#define INPUT_PASS_TO_HANDLERS        1    //交给handler处理
#define INPUT_PASS_TO_DEVICE        2      //交给device处理
#define INPUT_SLOT                4        //需要刷新挂起的slot事件
#define INPUT_FLUSH                8       //需要handler立即处理 只有SYN_REPORT有
#define INPUT_PASS_TO_ALL        (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{/* 获取disposition,根据disposition来判断event的传递方向 */int disposition = input_get_disposition(dev, type, code, &value);/* add_input_randomness对事件发送没有一点用处,只是用来对随机数熵池增加一些贡献,因为按键输入是一种随机事件,所以对熵池是有贡献的 */if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)add_input_randomness(type, code, value);/* 如果是向节点注入事件,则调用dev的event函数指针 */if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)dev->event(dev, type, code, value);/* 如果dev的input_values为0则直接返回 */if (!dev->vals)return;/* 交给handler处理 */if (disposition & INPUT_PASS_TO_HANDLERS) {struct input_value *v;/* 需要刷新挂起的slot事件 */if (disposition & INPUT_SLOT) {v = &dev->vals[dev->num_vals++];v->type = EV_ABS;v->code = ABS_MT_SLOT;v->value = dev->mt->slot;//slot value}v = &dev->vals[dev->num_vals++];v->type = type;v->code = code;v->value = value;}/* EV_SYN SYN_REPORT  事件触发INPUT_FLUSH */if (disposition & INPUT_FLUSH) {/* 如果需要上报的input_values数量大于等于2,则input_pass_values */if (dev->num_vals >= 2)input_pass_values(dev, dev->vals, dev->num_vals);/* 清空 input_dev->vals 计数变量 */dev->num_vals = 0;/** Reset the timestamp on flush so we won't end up* with a stale one. Note we only need to reset the* monolithic one as we use its presence when deciding* whether to generate a synthetic timestamp.*/dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);} else if (dev->num_vals >= dev->max_vals - 2) {/* * 如果需要上报的input_values数量大于等于 max_vals - 2 * 则将EV_SYN SYN_REPORT 1 塞入input_values 并立即执行 input_pass_values*/dev->vals[dev->num_vals++] = input_value_sync;input_pass_values(dev, dev->vals, dev->num_vals);dev->num_vals = 0;}}

3.4.2.4 分析 input_pass_values

/** Pass values first through all filters and then, if event has not been* filtered out, through all open handles. This function is called with* dev->event_lock held and interrupts disabled.*/
static void input_pass_values(struct input_dev *dev,struct input_value *vals, unsigned int count)
{struct input_handle *handle;struct input_value *v;if (!count)return;rcu_read_lock();/* * 检查grab是否存在* grab:当前已抓取设备的输入句柄(通过 EVIOCGRAB ioctl)input_grab_device*       当句柄抓住设备时,它成为来自设备的所有输入事件的唯一接收者*/handle = rcu_dereference(dev->grab);if (handle) {count = input_to_handler(handle, vals, count);} else {/* * 根据input_dev.h_list上存放的input_handle.d_node地址* 从而遍历input_handle*/list_for_each_entry_rcu(handle, &dev->h_list, d_node)if (handle->open) {/* 如果使用handle的client不为0 */count = input_to_handler(handle, vals, count);if (!count)break;}}rcu_read_unlock();/* trigger auto repeat for key events *//* 分析见Softrepeat 事件重复上报机制 */if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {for (v = vals; v != vals + count; v++) {if (v->type == EV_KEY && v->value != 2) {if (v->value)input_start_autorepeat(dev, v->code);elseinput_stop_autorepeat(dev);}}}
}

3.4.2.5 分析 input_to_handler

/** Pass event first through all filters and then, if event has not been* filtered out, through all open handles. This function is called with* dev->event_lock held and interrupts disabled.*/
static unsigned int input_to_handler(struct input_handle *handle,struct input_value *vals, unsigned int count)
{struct input_handler *handler = handle->handler;struct input_value *end = vals;struct input_value *v;/* handler如果有filter则优先从filter传递事件 */if (handler->filter) {for (v = vals; v != vals + count; v++) {if (handler->filter(handle, v->type, v->code, v->value))continue;if (end != v)*end = *v;end++;}count = end - vals;}if (!count)return 0;/* handler如果有events 从events直接传输count个input_value */if (handler->events)handler->events(handle, vals, count);/* 否则遍历传输每个input_value */else if (handler->event)for (v = vals; v != vals + count; v++)handler->event(handle, v->type, v->code, v->value);return count;
}

3.4.2.6 分析 evdev_event(s)

/** Pass incoming event to all connected clients.*/
static void evdev_event(struct input_handle *handle,unsigned int type, unsigned int code, int value)
{struct input_value vals[] = { { type, code, value } };evdev_events(handle, vals, 1);
}

这里主要分析evdev_events


/** Pass incoming events to all connected clients.*/
static void evdev_events(struct input_handle *handle,const struct input_value *vals, unsigned int count)
{struct evdev *evdev = handle->private;struct evdev_client *client;ktime_t *ev_time = input_get_timestamp(handle->dev);rcu_read_lock();/* * 检查是否有client grab 此handler*/client = rcu_dereference(evdev->grab);/* 如果有client grab 此handler */if (client)/* 发送input事件数据到此client */evdev_pass_values(client, vals, count, ev_time);else/* 发送input事件数据到所有client */list_for_each_entry_rcu(client, &evdev->client_list, node)evdev_pass_values(client, vals, count, ev_time);rcu_read_unlock();
}

3.4.2.7 分析 evdev_pass_values

static void evdev_pass_values(struct evdev_client *client,const struct input_value *vals, unsigned int count,ktime_t *ev_time)
{const struct input_value *v;struct input_event event;struct timespec64 ts;bool wakeup = false;/* 如果client被注销则直接返回 */if (client->revoked)return;ts = ktime_to_timespec64(ev_time[client->clk_type]);event.input_event_sec = ts.tv_sec;event.input_event_usec = ts.tv_nsec / NSEC_PER_USEC;/* Interrupts are disabled, just acquire the lock. */spin_lock(&client->buffer_lock);for (v = vals; v != vals + count; v++) {/* 过滤事件,事件类型通过 EVIOCSMASK ioctl来设定 */if (__evdev_is_filtered(client, v->type, v->code))continue;if (v->type == EV_SYN && v->code == SYN_REPORT) {/* drop empty SYN_REPORT *//*  * 事件为EV_SYN SYN_REPORT 且 buffer中为空* 则跳出上报*/if (client->packet_head == client->head)continue;/* 只有事件为EV_SYN SYN_REPORT 才需要唤醒client */wakeup = true;}/* 向buffer中插入input事件数据 */event.type = v->type;event.code = v->code;event.value = v->value;__pass_event(client, &event);}spin_unlock(&client->buffer_lock);/* 当SYN_REPORT事件插入buffer后就可以唤醒client了 */if (wakeup)/* 唤醒在poll等待队列的进程 */wake_up_interruptible_poll(&client->wait,EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);
}

3.4.2.8 环形缓冲区

在分析__pass_event函数之前,需要知道client的buffer是如何运作的
在事件处理层(evdev.c)中结构体evdev_client定义了一个环形缓冲区(circular buffer),其原理是用数组的方式实现了一个先进先出的循环队列(circular queue),用以缓存内核驱动上报给用户层的input_event事件

struct evdev_client {unsigned int head;              // 头指针unsigned int tail;              // 尾指针unsigned int packet_head;       // 包指针spinlock_t buffer_lock;         /* protects access to buffer, head and tail */wait_queue_head_t wait;struct fasync_struct *fasync;struct evdev *evdev;struct list_head node;enum input_clock_type clk_type;bool revoked;                   unsigned long *evmasks[EV_CNT];unsigned int bufsize;           // 循环队列大小,此size值向上对齐2的幂struct input_event buffer[];    // 循环队列数组
};

evdev_client对象维护了三个偏移量:head、tail以及packet_head。head、tail作为循环队列的头尾指针记录入口与出口偏移,那么包指针packet_head有什么作用呢?
packet_head:内核驱动处理一次输入,可能上报一到多个input_event事件,为表示处理完成,会在上报这些input_event事件后再上报一次同步事件。头指针head以input_event事件为单位,记录缓冲区的入口偏移量,而包指针packet_head则以“数据包”(一到多个input_event事件)为单位,记录缓冲区的入口偏移量。

在这里插入图片描述

  • 循环队列入队算法:
head++;
head &= bufsize - 1;
  • 循环队列出队算法:
tail++;
tail &= bufsize - 1;
  • 循环队列已满条件:
head == tail
  • 循环队列为空条件:
packet_head == tail
  • “求余”和“求与”
    为解决头尾指针的上溢和下溢现象,使队列的元素空间可重复使用,一般循环队列的出入队算法都采用“求余”操作:
head = (head + 1) % bufsize; // 入队
tail = (tail + 1) % bufsize; // 出队

为避免计算代价高昂的“求余”操作,使内核运作更高效,input子系统的环形缓冲区采用了“求与”算法,这要求bufsize必须为2的幂,在后文中可以看到bufsize的值实际上是为64或者8的n倍,符合“求与”运算的要求。

3.4.2.9 分析__pass_event

static void __pass_event(struct evdev_client *client,const struct input_event *event)
{/* input event 数据入队列 */client->buffer[client->head++] = *event;client->head &= client->bufsize - 1;/* * 如果缓冲区已满* 则丢弃缓冲区中的事件并向缓冲区中塞入SYN_DROPPED事件*/if (unlikely(client->head == client->tail)) {/** This effectively "drops" all unconsumed events, leaving* EV_SYN/SYN_DROPPED plus the newest event in the queue.*//* 只留下本次插入的input事件和SYN_DROPPED事件在缓冲区中 */client->tail = (client->head - 2) & (client->bufsize - 1);client->buffer[client->tail] = (struct input_event) {.input_event_sec = event->input_event_sec,.input_event_usec = event->input_event_usec,.type = EV_SYN,.code = SYN_DROPPED,.value = 0,};client->packet_head = client->tail;}/* 如果是SYN_REPORT事件 */if (event->type == EV_SYN && event->code == SYN_REPORT) {/* 刷新包指针 */client->packet_head = client->head;/* 发送SIGIO和POLL_IN信号给client线程 */kill_fasync(&client->fasync, SIGIO, POLL_IN);}
}

4. 应用打开节点并注入事件数据

在这里,应用作为input事件的生产者,此时根据事件的不同input事件的消费者可以是应用也可以是device
前面在evdev_write简单写了,现在详细写下
应用注入事件后,事件的流转有两个方向

  • 流向input_handler即向所有client上报注入的事件
  • 流向input_device即回调device的event函数

4.1 事件注入

书接上回evdev_write

static ssize_t evdev_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;struct input_event event;int retval = 0;//从用户空间读取字节数/* 用户写字节数 < event 字节数 返回inval */if (count != 0 && count < input_event_size())return -EINVAL;retval = mutex_lock_interruptible(&evdev->mutex);if (retval)return retval;/* evdev如果不存在和client被注销返回 nodev */if (!evdev->exist || client->revoked) {retval = -ENODEV;goto out;}/* * 如从用户空间读取字节数+一个event字节数 <= 用户写入的字节数* 则按event注入节点*/while (retval + input_event_size() <= count) {if (input_event_from_user(buffer + retval, &event)) {retval = -EFAULT;goto out;}retval += input_event_size();input_inject_event(&evdev->handle,event.type, event.code, event.value);cond_resched();//调度}out:mutex_unlock(&evdev->mutex);return retval;
}

input_inject_event是handler注入事件的入口

/*** input_inject_event() - send input event from input handler* @handle: input handle to send event through* @type: type of the event* @code: event code* @value: value of the event** Similar to input_event() but will ignore event if device is* "grabbed" and handle injecting event is not the one that owns* the device.*/
void input_inject_event(struct input_handle *handle,unsigned int type, unsigned int code, int value)
{struct input_dev *dev = handle->dev;struct input_handle *grab;unsigned long flags;/* 如果事件合法且device支持此事件 */if (is_event_supported(type, dev->evbit, EV_MAX)) {spin_lock_irqsave(&dev->event_lock, flags);rcu_read_lock();/* 此device是否有handle grab */grab = rcu_dereference(dev->grab);/* 没有handle grab device 或者 此handle grab device */if (!grab || grab == handle)/* 开始事件流转 */input_handle_event(dev, type, code, value);rcu_read_unlock();spin_unlock_irqrestore(&dev->event_lock, flags);}
}
EXPORT_SYMBOL(input_inject_event);

4.2 事件流向input_handler

流向input_handler的事件需要disposition满足下列条件指一

  • INPUT_PASS_TO_HANDLERS
  • INPUT_PASS_TO_ALL
    所有设备支持的事件都会流向input_handler,事件的后续流向见上面 《3.4.2.2 接着分析input_handle_event》章节

4.3 事件流向input_device

流向input_handler的事件需要disposition满足下列条件指一

  • INPUT_PASS_TO_DEVICE
  • INPUT_PASS_TO_ALL
    只有下面列举的事件会流向input_device

在这里插入图片描述
如果input_device 定义了event函数则会回调此函数


#define INPUT_IGNORE_EVENT        0        //忽略
#define INPUT_PASS_TO_HANDLERS        1    //交给handler处理
#define INPUT_PASS_TO_DEVICE        2      //交给device处理
#define INPUT_SLOT                4        //需要刷新挂起的slot事件
#define INPUT_FLUSH                8       //需要handler立即处理 只有SYN_REPORT有
#define INPUT_PASS_TO_ALL        (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{/* 获取disposition,根据disposition来判断event的传递方向 */int disposition = input_get_disposition(dev, type, code, &value);.../* 如果是向节点注入事件,则调用dev的event函数指针 */if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)dev->event(dev, type, code, value);...
}

5. input.c

5.1 核心数据结构

5.1.1 input_dev

struct input_dev {const char *name;  // 输入设备私有指针,一般指向用于描述设备驱动层的设备结构const char *phys;  // 提供给用户的输入设备的名称const char *uniq;  // 提供给编程者的设备节点的名称  文件路径,比如 input/buttonsstruct input_id id;// 指定唯一的ID号,就像MAC地址一样unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];//位图,记录设备支持的事件类型(可以多选)/* *  #define EV_SYN          0x00    //同步事件 *  #define EV_KEY          0x01    //按键事件 *  #define EV_REL          0x02    //相对坐标 *  #define EV_ABS          0x03    //绝对坐标 *  #define EV_MSC          0x04    //其它 *  #define EV_SW           0x05    //开关事件 *  #define EV_LED          0x11    //LED事件 *  #define EV_SND          0x12 *  #define EV_REP          0x14<span style="white-space:pre">    </span>//重复上报 *  #define EV_FF           0x15 *  #define EV_PWR          0x16 *  #define EV_FF_STATUS    0x17 *  #define EV_MAX          0x1f */ 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 long ledbit[BITS_TO_LONGS(LED_CNT)];  //位图,记录设备支持的指示灯  unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];  //位图,记录设备支持的声音或警报  unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    //位图,记录设备支持的作用力功能  unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    //位图,记录设备支持的开关功能 unsigned int hint_events_per_packet;           //用来估算evdev buffersizeunsigned int keycodemax;                       //设备支持的最大按键值个数  unsigned int keycodesize;                      //每个按键的字节大小  void *keycode;                                 //指向按键池,即指向按键值数组首地址  int (*setkeycode)(struct input_dev *dev,       //修改按键值 const struct input_keymap_entry *ke,unsigned int *old_keycode);int (*getkeycode)(struct input_dev *dev,       //获取按键值  struct input_keymap_entry *ke);struct ff_device *ff;                           //如果设备支持力反馈效果,则与设备关联的力反馈结构struct input_dev_poller *poller;                //如果设备设置为使用轮询模式,则与设备关联的结构unsigned int repeat_key;                        //支持重复按键  struct timer_list timer;                         //设置当有连击时的延时定时器  int rep[REP_CNT];                                //自动重复参数的当前值(延迟、速率)struct input_mt *mt;                            //指向多点触控状态的指针/**  struct input_mt {*       int trkid;*       int num_slots;*       int slot;*       unsigned int flags;*       unsigned int frame;*       int *red;*       struct input_mt_slot slots[];*   };**   struct input_mt_slot {*       int abs[ABS_MT_LAST - ABS_MT_FIRST + 1];*       unsigned int frame;*       unsigned int key;*   };**/struct input_absinfo *absinfo;                   //&struct input_absinfo 元素数组,其中包含有关绝对轴的信息(当前值、最小值、最大值、平坦度、模糊度、分辨率)/* *    struct input_absinfo {*       __s32 value;*       __s32 minimum;*       __s32 maximum;*       __s32 fuzz;*       __s32 flat;*       __s32 resolution;*   };*/unsigned long key[BITS_TO_LONGS(KEY_CNT)];      //位图,按键的状态unsigned long led[BITS_TO_LONGS(LED_CNT)];      //位图,led的状态 unsigned long snd[BITS_TO_LONGS(SND_CNT)];      //位图,声音的状态  unsigned long sw[BITS_TO_LONGS(SW_CNT)];        //位图,开关的状态  int (*open)(struct input_dev *dev);                      //输入设备打开函数  void (*close)(struct input_dev *dev);                    //输入设备关闭函数  int (*flush)(struct input_dev *dev, struct file *file);  //输入设备断开后刷新函数  int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件处理  struct input_handle __rcu *grab;    //当前已抓取设备的输入句柄(通过 EVIOCGRAB ioctl)。 当句柄抓住设备时,它成为来自设备的所有输入事件的唯一接收者spinlock_t event_lock; //当输入核心接收并处理设备的新事件(在 input_event() 中)时,将采用此自旋锁。在设备注册到 input core 之后访问和/或修改设备参数(例如 keymap 或 absmin、absmax、absfuzz 等)的代码必须使用此锁。struct mutex mutex;    //用于open、close函数的连续访问互斥  unsigned int users;    //存储打开此设备的用户(input handler)的数量。 input_open_device() 和 input_close_device() 使用它来确保 dev->open() 仅在第一个用户打开设备时调用,而 dev->close() 在最后一个用户关闭设备时调用bool going_away;       //标记处于注销过程中的设备并导致 input_open_device*() 失败并显示 -ENODEV。struct device dev;     //此设备的驱动程序模型视图struct list_head        h_list; //与设备关联的输入句柄列表。 访问列表时,必须保持 dev->mutexstruct list_head        node;   //用于将设备放入 input_dev_listunsigned int num_vals;   //当前帧中插入value的数量unsigned int max_vals;   //当前帧中插入value的最大数量struct input_value *vals;//当前帧中插入的value数组bool devres_managed;    //表示设备由 devres 框架管理,不需要显式注销或释放。ktime_t timestamp[INPUT_CLK_MAX]; //存储由驱动程序调用的 input_set_timestamp 设置的时间戳
};

5.1.2 input_handler

struct input_handler {void *private;                        //特定于驱动程序的数据//事件处理程序void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//事件序列处理程序void (*events)(struct input_handle *handle,const struct input_value *vals, unsigned int count);//将普通事件处理程序与“过滤器”分开bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//在将设备的 id 与处理程序的 id_table 进行比较后调用,以在设备和处理程序之间进行细粒度匹配bool (*match)(struct input_handler *handler, struct input_dev *dev);//在将处理程序附加到输入设备时调用int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//从输入设备断开处理程序void (*disconnect)(struct input_handle *handle);//启动给定句柄的处理程序。这个函数由input core 在 connect() 方法之后调用,并且当一个进程* “抓取”一个设备释放它void (*start)(struct input_handle *handle);bool legacy_minors;        //使用传统次要范围的驱动程序设置为 %true int minor;const char *name;        //处理程序的名称,显示在 /proc/bus/input/handlers const struct input_device_id *id_table;    //驱动支持的id表(用于匹配input_dev)struct list_head        h_list;            //存放input_handle(没有r)的链表struct list_head        node;              //存放input_handler自身的链表
};

5.1.3 input_handle

struct input_handle {void *private;                        // 私有数据, 指向了父指针int open;                             //计数器显示句柄是否“打开”,即应该从其设备传递事件.const char *name;                     //给定的名称到创建它的处理程序的句柄struct input_dev *dev;                //指向input_devstruct input_handler *handler;        //指向 input_handlerstruct list_head        d_node;       //存放input_dev->h_list的链表struct list_head        h_node;       //存放input_handler->h_list的链表
};

5.1.4 ff_device

struct ff_device {        //输入设备的强制反馈部分//调用以将新效果上传到设备int (*upload)(struct input_dev *dev, struct ff_effect *effect,struct ff_effect *old);//调用以从设备中删除效果int (*erase)(struct input_dev *dev, int effect_id);//调用以请求设备开始播放指定效果int (*playback)(struct input_dev *dev, int effect_id, int value);//调用设置指定增益void (*set_gain)(struct input_dev *dev, u16 gain);//调用自动居中设备void (*set_autocenter)(struct input_dev *dev, u16 magnitude);//当父输入设备被销毁时由输入核心调用void (*destroy)(struct ff_device *);//驱动程序特定的数据,将自动释放void *private;//设备真正支持的力反馈功能位图(不像 input_dev->ffbit 中的那样模拟)unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//用于序列化访问设备的互斥锁struct mutex mutex;//设备支持的最大效果数int max_effects;//指向当前加载到设备中的效果数组的指针struct ff_effect *effects;//效果所有者数组;当文件句柄拥有效果关闭时,效果会自动删除struct file *effect_owners[];/* 每个强制反馈设备都必须实现upload()playback()* 方法;erase()是可选的。set_gain() 和 set_autocenter() * 只有在驱动程序设置 FF_GAIN 和 FF_AUTOCENTER * 位时才需要实现。* * 请注意,在调用 play()、set_gain() 和 set_autocenter() 时* dev->event_lock 自旋锁保持并中断,因此可能不会睡眠。*/
};

5.1.5 input_device_id

struct input_device_id {kernel_ulong_t flags;/* 这些ID需要驱动程序作者指定 */__u16 bustype;__u16 vendor;__u16 product;__u16 version;kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];kernel_ulong_t propbit[INPUT_DEVICE_ID_PROP_MAX / BITS_PER_LONG + 1];kernel_ulong_t driver_info;
};

5.1.6 ops

static const struct proc_ops input_handlers_proc_ops = {.proc_open        = input_proc_handlers_open,.proc_read        = seq_read,.proc_lseek        = seq_lseek,.proc_release        = seq_release,
};static const struct seq_operations input_devices_seq_ops = {.start        = input_devices_seq_start,.next        = input_devices_seq_next,.stop        = input_seq_stop,.show        = input_devices_seq_show,
};static const struct proc_ops input_devices_proc_ops = {.proc_open        = input_proc_devices_open,.proc_poll        = input_proc_devices_poll,.proc_read        = seq_read,.proc_lseek        = seq_lseek,.proc_release        = seq_release,
};static const struct seq_operations input_handlers_seq_ops = {.start        = input_handlers_seq_start,.next        = input_handlers_seq_next,.stop        = input_seq_stop,.show        = input_handlers_seq_show,
};static const struct dev_pm_ops input_dev_pm_ops = {.suspend        = input_dev_suspend,.resume                = input_dev_resume,.freeze                = input_dev_freeze,.poweroff        = input_dev_poweroff,.restore        = input_dev_resume,
};static const struct device_type input_dev_type = {.groups                = input_dev_attr_groups,.release        = input_dev_release,.uevent                = input_dev_uevent,
#ifdef CONFIG_PM_SLEEP.pm                = &input_dev_pm_ops,
#endif
};

5.2 功能细节

5.2.1 Init 和 exit

static int __init input_init(void)
{int err;err = class_register(&input_class); // create /dev/inputif (err) {pr_err("unable to register input_dev class\n");return err;}err = input_proc_init(); //create /proc/bus/input/device + handlerif (err)goto fail1;err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");if (err) {pr_err("unable to register char major %d", INPUT_MAJOR);goto fail2;}return 0;fail2:        input_proc_exit();fail1:        class_unregister(&input_class);return err;
}
static void __exit input_exit(void)
{input_proc_exit();unregister_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES);class_unregister(&input_class);
}

5.2.2 softrepeat 事件重复上报机制

如果 设置了 __set_bit(EV_REP, input->evbit); 也就是重复报告,它的工作机制是这样的:

如果按键报告了input_event(input, type, button->code, 1); 之后, 
在250ms (可以改)后,依然没有报告 input_event(input, type, button->code, 0);
则 input 会每隔 33ms 继续报告一次 input_event(input, type, button->code, 2);
直到 报告了 input_event(input, type, button->code, 0); 才停止 ,
这就是我们按住一个按键不松开时,会一直打印键值的原因; 

使用一个timer实现事件重复上报机制

/*** input_allocate_device - allocate memory for new input device** Returns prepared struct input_dev or %NULL.** NOTE: Use input_free_device() to free devices that have not been* registered; input_unregister_device() should be used for already* registered devices.*/
struct input_dev *input_allocate_device(void)
{static atomic_t input_no = ATOMIC_INIT(-1);struct input_dev *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class;device_initialize(&dev->dev);mutex_init(&dev->mutex);spin_lock_init(&dev->event_lock);timer_setup(&dev->timer, NULL, 0);//timer在这里setupINIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);dev_set_name(&dev->dev, "input%lu",(unsigned long)atomic_inc_return(&input_no));__module_get(THIS_MODULE);}return dev;
}
EXPORT_SYMBOL(input_allocate_device);这里是入口
👇
int input_register_device(struct input_dev *dev)
{/** If delay and period are pre-set by the driver, then autorepeating* is handled by the driver itself and we don't do it in input.c.*/if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])//如果用户设定时间参数input.c不会执行重复上报,需要用户自己实现input_enable_softrepeat(dev, 250, 33);
}
/*** input_enable_softrepeat - enable software autorepeat* @dev: input device* @delay: repeat delay* @period: repeat period** Enable software autorepeat on the input device.*/
void input_enable_softrepeat(struct input_dev *dev, int delay, int period)
{dev->timer.function = input_repeat_key;//Softrepeat 函数 dev->rep[REP_DELAY] = delay;//第一次上报timer时间dev->rep[REP_PERIOD] = period;//自动上报timer时间
}
EXPORT_SYMBOL(input_enable_softrepeat);
👇
static void input_repeat_key(struct timer_list *t)
{struct input_dev *dev = from_timer(dev, t, timer);unsigned long flags;spin_lock_irqsave(&dev->event_lock, flags);if (test_bit(dev->repeat_key, dev->key) &&is_event_supported(dev->repeat_key, dev->keybit, KEY_MAX)) {struct input_value vals[] =  {{ EV_KEY, dev->repeat_key, 2 },//type code valueinput_value_sync};input_set_timestamp(dev, ktime_get());input_pass_values(dev, vals, ARRAY_SIZE(vals));if (dev->rep[REP_PERIOD])//设定timer,下次上报mod_timer(&dev->timer, jiffies +msecs_to_jiffies(dev->rep[REP_PERIOD]));}spin_unlock_irqrestore(&dev->event_lock, flags);
}
第一次上报在什么时候?
👇
/** Pass values first through all filters and then, if event has not been* filtered out, through all open handles. This function is called with* dev->event_lock held and interrupts disabled.*/
static void input_pass_values(struct input_dev *dev,struct input_value *vals, unsigned int count)
{struct input_handle *handle;struct input_value *v;
....../* trigger auto repeat for key events */if (test_bit(EV_REP, dev->evbit) && test_bit(EV_KEY, dev->evbit)) {for (v = vals; v != vals + count; v++) {//遍历input_valueif (v->type == EV_KEY && v->value != 2) {//上报1后if (v->value)//没有上报0input_start_autorepeat(dev, v->code);else//上报0了input_stop_autorepeat(dev);}}}
}
设定dev->rep[REP_DELAY]ms后第一次上报
👇
static void input_start_autorepeat(struct input_dev *dev, int code)
{if (test_bit(EV_REP, dev->evbit) &&dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] &&dev->timer.function) {dev->repeat_key = code;mod_timer(&dev->timer,jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));}
}
后面在按周期循环执行
👇
static void input_repeat_key(struct timer_list *t)
直到上报0了,执行
👇
static void input_stop_autorepeat(struct input_dev *dev)
{del_timer(&dev->timer);
}

6. input-mt.c

首先需要了解多点触控协议 https://tinylab-1.gitbook.io/linux-doc/zh-cn/input/multi-touch-protocol

6.1 数据结构

6.1.1 input_mt_slot

mt设备slot数据

#define ABS_MT_SLOT                0x2f        /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR        0x30        /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR        0x31        /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR        0x32        /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR        0x33        /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION        0x34        /* Ellipse orientation */
#define ABS_MT_POSITION_X        0x35        /* Center X touch position */
#define ABS_MT_POSITION_Y        0x36        /* Center Y touch position */
#define ABS_MT_TOOL_TYPE        0x37        /* Type of touching device */
#define ABS_MT_BLOB_ID                0x38        /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID        0x39        /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE                0x3a        /* Pressure on contact area */
#define ABS_MT_DISTANCE                0x3b        /* Contact hover distance */
#define ABS_MT_TOOL_X                0x3c        /* Center X tool position */
#define ABS_MT_TOOL_Y                0x3d        /* Center Y tool position */#define ABS_MT_FIRST                ABS_MT_TOUCH_MAJOR
#define ABS_MT_LAST                ABS_MT_TOOL_Y
/*** struct input_mt_slot - represents the state of an input MT slot* @abs: holds current values of ABS_MT axes for this slot* @frame: last frame at which input_mt_report_slot_state() was called* @key: optional driver designation of this slot*/
struct input_mt_slot {int abs[ABS_MT_LAST - ABS_MT_FIRST + 1];//存放ABS_MT事件的valueunsigned int frame;                     //该slot的frame序号unsigned int key;                       //通过此key驱动可以调用接口查询匹配的slot, dev->mt->slot->key
};

6.1.2 input_mt

相当于input设备的mt私有数据

#define INPUT_MT_POINTER               0x0001        /* pointer device, e.g. trackpad */
#define INPUT_MT_DIRECT                0x0002        /* direct device, e.g. touchscreen */
#define INPUT_MT_DROP_UNUSED           0x0004        /* drop contacts not seen in frame */
#define INPUT_MT_TRACK                 0x0008        /* use in-kernel tracking */
#define INPUT_MT_SEMI_MT               0x0010        /* semi-mt device, finger count handled manually *//*** struct input_mt - state of tracked contacts* @trkid: stores MT tracking ID for the next contact* @num_slots: number of MT slots the device uses* @slot: MT slot currently being transmitted* @flags: input_mt operation flags* @frame: increases every time input_mt_sync_frame() is called* @red: reduced cost matrix for in-kernel tracking* @slots: array of slots holding current values of tracked contacts*/
struct input_mt {int trkid;            //跟踪多点触控使用的ID,每一次触控追踪都会分配一个新的IDint num_slots;        //设备使用的slot数量int slot;             //设备当前传输使用的slotunsigned int flags;   //见上述宏unsigned int frame;   //每次调用 input_mt_sync_frame() 时增加int *red;             //减少内核跟踪的成本矩阵struct input_mt_slot slots[];//存放ABS_MT事件数据
};

6.2 init

//e.g. init
ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
ts->input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
ts->input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT);
input_mt_init_slots(ts->input_dev, 10, 0);

6.2.1 input_mt_init_slots


/*** input_mt_init_slots() - initialize MT input slots* @dev: input device supporting MT events and finger tracking* @num_slots: number of slots used by the device* @flags: mt tasks to handle in core** This function allocates all necessary memory for MT slot handling* in the input device, prepares the ABS_MT_SLOT and* ABS_MT_TRACKING_ID events for use and sets up appropriate buffers.* Depending on the flags set, it also performs pointer emulation and* frame synchronization.** May be called repeatedly. Returns -EINVAL if attempting to* reinitialize with a different number of slots.*/
int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,unsigned int flags)
{struct input_mt *mt = dev->mt;int i;/* 如果num_slots == 0 直接返回 */if (!num_slots)return 0;/* 如果尝试使用不同数量的slot重新初始化,则返回 -EINVAL。 */if (mt)return mt->num_slots != num_slots ? -EINVAL : 0;/* 依据mt->slots[num_slots]申请该结构体变量所需的内存大小 */mt = kzalloc(struct_size(mt, slots, num_slots), GFP_KERNEL);if (!mt)goto err_mem;/* 存储num_slots和flags到input_mt */mt->num_slots = num_slots;mt->flags = flags;/* 设定input_dev->absinfo input_dev->absbit input_dev->evbit */input_set_abs_params(dev, ABS_MT_SLOT, 0, num_slots - 1, 0, 0);input_set_abs_params(dev, ABS_MT_TRACKING_ID, 0, TRKID_MAX, 0, 0);/* 根据flags设定设备属性,参数及内核跟踪功能 */if (flags & (INPUT_MT_POINTER | INPUT_MT_DIRECT)) {__set_bit(EV_KEY, dev->evbit);__set_bit(BTN_TOUCH, dev->keybit);copy_abs(dev, ABS_X, ABS_MT_POSITION_X);copy_abs(dev, ABS_Y, ABS_MT_POSITION_Y);copy_abs(dev, ABS_PRESSURE, ABS_MT_PRESSURE);}if (flags & INPUT_MT_POINTER) {__set_bit(BTN_TOOL_FINGER, dev->keybit);__set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);if (num_slots >= 3)__set_bit(BTN_TOOL_TRIPLETAP, dev->keybit);if (num_slots >= 4)__set_bit(BTN_TOOL_QUADTAP, dev->keybit);if (num_slots >= 5)__set_bit(BTN_TOOL_QUINTTAP, dev->keybit);__set_bit(INPUT_PROP_POINTER, dev->propbit);}if (flags & INPUT_MT_DIRECT)__set_bit(INPUT_PROP_DIRECT, dev->propbit);if (flags & INPUT_MT_SEMI_MT)__set_bit(INPUT_PROP_SEMI_MT, dev->propbit);if (flags & INPUT_MT_TRACK) {unsigned int n2 = num_slots * num_slots;mt->red = kcalloc(n2, sizeof(*mt->red), GFP_KERNEL);if (!mt->red)goto err_mem;}/* Mark slots as 'inactive' */for (i = 0; i < num_slots; i++)input_mt_set_value(&mt->slots[i], ABS_MT_TRACKING_ID, -1);/* Mark slots as 'unused' */mt->frame = 1;dev->mt = mt;return 0;
err_mem:kfree(mt);return -ENOMEM;
}
EXPORT_SYMBOL(input_mt_init_slots);

6.3 TypeB report 实例


/******************** report ********************/
input_mt_slot(ts->input_dev, input_id - 1);
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y);
input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, input_w);
input_report_abs(ts->input_dev, ABS_MT_PRESSURE, input_p);
...
input_sync(ts->input_dev);/******************** release ********************/
/* 遍历slot寻找到release的point后执行release动作 */
for (i = 0; i < ts->max_touch_num; i++) {if (press_id[i] != 1) {input_mt_slot(ts->input_dev, i);input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0);input_report_abs(ts->input_dev, ABS_MT_PRESSURE, 0);input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);}
}
...
input_sync(ts->input_dev);/******************** event e.g. ********************/
/dev/input/event10: EV_ABS       ABS_MT_TRACKING_ID   0000005d            
/dev/input/event10: EV_ABS       ABS_MT_POSITION_X    000017a2            
/dev/input/event10: EV_ABS       ABS_MT_POSITION_Y    00005122            
/dev/input/event10: EV_ABS       ABS_MT_TOUCH_MAJOR   00000006            
/dev/input/event10: EV_ABS       ABS_MT_PRESSURE      00000001            
/dev/input/event10: EV_KEY       BTN_TOUCH            DOWN                
/dev/input/event10: EV_SYN       SYN_REPORT           00000000            
/dev/input/event10: EV_ABS       ABS_MT_TOUCH_MAJOR   00000000            
/dev/input/event10: EV_ABS       ABS_MT_PRESSURE      00000000            
/dev/input/event10: EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
/dev/input/event10: EV_KEY       BTN_TOUCH            UP                  
/dev/input/event10: EV_SYN       SYN_REPORT           00000000 

6.3.1 input_mt_slot

input_mt_slot(ts->input_dev, input_id - 1);
static inline void input_mt_slot(struct input_dev *dev, int slot)
{input_event(dev, EV_ABS, ABS_MT_SLOT, slot);
}
|->input_mt_slot|->input_event|->input_handle_event|->input_get_disposition|->input_handle_abs_event|->[pending slot event] ... if (disposition & INPUT_SLOT)

详细分析见 《3.4.2 事件流转分析》
在这里直接说结论:

  • ABS_MT_SLOT事件不会单独被上报即使其之后跟随SYN_REPORT事件,实现如下
    • ABS_MT_SLOT事件value会被存入input_dev->mt->slot 并 ignore
    • 下一次ABS_MT事件来时,如果input_dev->mt->slot(当前slot值)和input_dev->absinfo[ABS_MT_SLOT](上一次slot值)如果不相等,则更新 input_dev->absinfo[ABS_MT_SLOT]并将ABS_MT_SLOT挂起存入input_dev->vals
  • 下次SYN_REPORT来时会将ABS_MT_SLOT事件流转至evdev_client->buffer中

6.3.2 input_mt_report_slot_state

input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);
|->input_mt_report_slot_state|->input_mt_get_value|->input_mt_new_trkid|->input_event
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);
|->input_mt_report_slot_state|->input_event
/*** input_mt_report_slot_state() - report contact state* @dev: input device with allocated MT slots* @tool_type: the tool type to use in this slot* @active: true if contact is active, false otherwise** Reports a contact via ABS_MT_TRACKING_ID, and optionally* ABS_MT_TOOL_TYPE. If active is true and the slot is currently* inactive, or if the tool type is changed, a new tracking id is* assigned to the slot. The tool type is only reported if the* corresponding absbit field is set.** 通过 ABS_MT_TRACKING_ID 和可选的 ABS_MT_TOOL_TYPE 报告联系人。* 如果 active 为 true 并且插槽当前处于非活动状态,或者如果工具类型已更改,* 则会为插槽分配一个新的跟踪 ID。 只有设置了相应的 absbit 字段,才会报告工具类型。** Returns true if contact is active.*/
bool input_mt_report_slot_state(struct input_dev *dev,unsigned int tool_type, bool active)
{struct input_mt *mt = dev->mt;struct input_mt_slot *slot;int id;/* 如果mt数据不存在则返回 */if (!mt)return false;/* 获取当前slot的slot数据 */slot = &mt->slots[mt->slot];/* 对齐slot frame序号到当前frame */slot->frame = mt->frame;/* 如果active == 0 */if (!active) {/* 上报当前slot为非使用状态 */input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1);return false;}/* 获取当前slot->abs 中保存的 ABS_MT_TRACKING_ID value */id = input_mt_get_value(slot, ABS_MT_TRACKING_ID);/* 如果此slot未启用则分配ID */if (id < 0)id = input_mt_new_trkid(mt);/* 上报ABS_MT_TRACKING_ID */input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, id);/* 上报ABS_MT_TOOL_TYPE */input_event(dev, EV_ABS, ABS_MT_TOOL_TYPE, tool_type);return true;
}
EXPORT_SYMBOL(input_mt_report_slot_state);

6.3.3 input_report_abs

input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x);
|->input_report_abs|->input_event|->input_handle_event|->input_get_disposition|->input_handle_abs_event|->[store value to input_dev->vals] ... if (disposition & INPUT_PASS_TO_HANDLERS)

详细分析见《3.4.2 事件流转分析》
在这里直接说结论:

  • ABS_MT事件数据会被挂入input_dev->vals中
  • ABS_MT事件数据会随着下一次SYN_REPORT事件到来被流转到evdev_client->buffer中

6.3.4 input_sync

input_sync(ts->input_dev);
|->input_sync|->input_event|->input_handle_event|->input_get_disposition|->input_pass_values|->input_to_handler|->evdev_events|->evdev_pass_values|->__evdev_is_filtered|->__pass_event|->[store values to evdev_client->buffer]|->kill_fasync|->wake_up_interruptible_poll

详细分析见《3.4.2 事件流转分析》
在这里直接说结论:

  • 只有在input_dev->vals中存在>=2个成员,事件才会被流转,目的是不上报单一的SYN_REPORT事件给evdev_client->buffer
  • evdev接收到SYN_REPORT事件会唤醒block read 和 poll 节点的evdev_client并会发送SIGIO信号给evdev_client->fasync保存的PID所指向的线程

7. input_polldev.c

轮询输入设备提供了一个框架,用于支持不会触发中断但需要定期扫描或轮询以检测其状态变化的简单硬件输入设备。
它可以定期轮询硬件状态的输入设备驱动程序

7.1 数据结构

7.1.1 input_polled_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 */         // 指定 poll() 方法应该被调用的频率。默认为 500 毫秒,除非在注册设备时被覆盖unsigned int poll_interval_max; /* msec */     // 指定轮询间隔的上限。默认为 poll_interval 的初始值unsigned int poll_interval_min; /* msec */     // 指定轮询间隔的下限。默认为 0struct input_dev *input;                       // 与轮询设备关联的输入设备结构。必须由驱动程序正确初始化(id、name、phys、bits)/* private: */struct delayed_work work;                      // 延迟工作结构体,用于延迟执行轮询操作bool devres_managed;                           // 是否由设备资源管理器管理该结构体
};

7.1.2 input_dev_poller

struct input_dev_poller {void (*poll)(struct input_dev *dev);// 指向轮询器的回调函数,用于检查输入设备是否有新的事件unsigned int poll_interval;         // 轮询器的轮询间隔,以毫秒为单位unsigned int poll_interval_max;     // 轮询器的最小轮询间隔,以毫秒为单位unsigned int poll_interval_min;     // 轮询器的最大轮询间隔,以毫秒为单位struct input_dev *input;            // 指向该轮询器所属的输入设备的指针struct delayed_work work;           // 用于将轮询器的回调函数添加到工作队列中,以便在后台执行
};

7.2 驱动程序实例

#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/time64.h>
#include <linux/rtc.h>#define POLL_INTERVAL 500#define TEST_LOG(fmt, args...)                                      \
do {                                                                \struct timespec64 tv;                                           \unsigned long local_time;                                       \struct rtc_time tm;                                             \ktime_get_real_ts64(&tv);                                       \local_time = (u32)(tv.tv_sec - (sys_tz.tz_minuteswest * 60));   \rtc_time64_to_tm(local_time, &tm);                              \pr_err("[%02d:%02d:%02d.%03zu] %s %d: " fmt, tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_nsec/1000000, __func__, __LINE__, ##args); \
} while (0)static struct input_polled_dev *polled_dev;
static struct input_dev *input_dev;static void polled_dev_poll(struct input_polled_dev *dev)
{static state = 0;TEST_LOG("++\n");// 读取输入设备的状态state = !state; // 假设输入设备的状态为 0input_event(input_dev, EV_KEY, KEY_A, state);TEST_LOG("report event KEY_A %d\n", state);input_sync(input_dev);TEST_LOG("--\n");
}static int __init input_poller_init(void)
{int error;// 创建输入设备TEST_LOG("++\n");input_dev = input_allocate_device();if (!input_dev) {TEST_LOG("Failed to allocate input device\n");return -ENOMEM;}// 设置输入设备的属性input_dev->name = "My Input Device";input_dev->id.bustype = BUS_VIRTUAL;input_dev->evbit[0] = BIT_MASK(EV_KEY);input_dev->keybit[BIT_WORD(KEY_A)] = BIT_MASK(KEY_A);input_dev->propbit[0] = BIT(INPUT_PROP_DIRECT);// 创建 input-poller 设备polled_dev = input_allocate_polled_device();if (!polled_dev) {TEST_LOG("Failed to allocate input-poller device\n");input_unregister_device(input_dev);return -ENOMEM;}// 设置 input-poller 设备的属性polled_dev->poll_interval = POLL_INTERVAL;polled_dev->poll = polled_dev_poll;polled_dev->input = input_dev;// 注册 input-poller 设备error = input_register_polled_device(polled_dev);if (error) {TEST_LOG("Failed to register input-poller device, err = %d\n", error);input_free_polled_device(polled_dev);input_unregister_device(input_dev);return error;}TEST_LOG("--\n");return 0;
}static void __exit input_poller_exit(void)
{TEST_LOG("++\n");// 注销 input-poller 设备input_unregister_polled_device(polled_dev);// 注销输入设备input_unregister_device(input_dev);// 释放 input-poller 设备和输入设备的内存input_free_polled_device(polled_dev);input_free_device(input_dev);TEST_LOG("--\n");
}module_init(input_poller_init);
module_exit(input_poller_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Input Poller Driver");
#define POLL_INTERVAL 50
static struct input_polled_dev *polled_dev;
static struct input_dev *input_dev;static void polled_dev_poll(struct input_polled_dev *dev)
{// 读取输入设备的状态int state = 0; // 假设输入设备的状态为 0input_event(input_dev, EV_KEY, KEY_A, state);input_sync(input_dev);
}static int __init input_poller_init(void)
{...// 创建 input-poller 设备polled_dev = input_allocate_polled_device();// 设置 input-poller 设备的属性polled_dev->poll_interval = POLL_INTERVAL;polled_dev->poll = polled_dev_poll;polled_dev->input = input_dev;// 注册 input-poller 设备error = input_register_polled_device(polled_dev);...
}

7.3 input_allocate_polled_device

/*** input_allocate_polled_device - allocate memory for polled device** The function allocates memory for a polled device and also* for an input device associated with this polled device.*/
struct input_polled_dev *input_allocate_polled_device(void)
{struct input_polled_dev *dev;dev = kzalloc(sizeof(struct input_polled_dev), GFP_KERNEL);if (!dev)return NULL;/* 给input_polled_dev申请input_dev,用于创建节点文件 */dev->input = input_allocate_device();if (!dev->input) {kfree(dev);return NULL;}return dev;
}
EXPORT_SYMBOL(input_allocate_polled_device);

7.4 input_register_polled_device

/*** input_register_polled_device - register polled device* @dev: device to register** The function registers previously initialized polled input device* with input layer. The device should be allocated with call to* input_allocate_polled_device(). Callers should also set up poll()* method and set up capabilities (id, name, phys, bits) of the* corresponding input_dev structure.*/
int input_register_polled_device(struct input_polled_dev *dev)
{struct input_polled_devres *devres = NULL;struct input_dev *input = dev->input;int error;/* 设备资源管理器管理 */if (dev->devres_managed) {devres = devres_alloc(devm_input_polldev_unregister,sizeof(*devres), GFP_KERNEL);if (!devres)return -ENOMEM;devres->polldev = dev;}/* * &input->dev->driver_data = dev* 设定input_dev私有数据为input_polled_dev*/input_set_drvdata(input, dev);/* 初始化&dev->work */INIT_DELAYED_WORK(&dev->work, input_polled_device_work);/* 如果轮询间隔未定义,则设定500ms */if (!dev->poll_interval)dev->poll_interval = 500;/* 如果轮询间隔最大值未定义,则设定等于轮询间隔 */if (!dev->poll_interval_max)dev->poll_interval_max = dev->poll_interval;/* 设定input_dev的fops函数 */input->open = input_open_polled_device;input->close = input_close_polled_device;input->dev.groups = input_polldev_attribute_groups;/* 向input core 注册此input_dev */error = input_register_device(input);if (error) {devres_free(devres);return error;}/** Take extra reference to the underlying input device so* that it survives call to input_unregister_polled_device()* and is deleted only after input_free_polled_device()* has been invoked. This is needed to ease task of freeing* sparse keymaps.** 对底层输入设备进行额外引用,以便它在调用 input_unregister_polled_device() 后仍然存在,* 并且仅在调用 input_free_polled_device() 之后被删除。 这是减轻释放稀疏键映射的任务所必需的。*/input_get_device(input);/* 设备资源管理器管理 */if (dev->devres_managed) {dev_dbg(input->dev.parent, "%s: registering %s with devres.\n",__func__, dev_name(&input->dev));devres_add(input->dev.parent, devres);}return 0;
}

7.4.1 input_polled_device_work

static void input_polled_device_work(struct work_struct *work)
{struct input_polled_dev *dev =container_of(work, struct input_polled_dev, work.work);/* 调用input_polled_dev->poll中保存的回调函数 */dev->poll(dev);/* 开启下一次轮询调用,间隔为 poll_interval ms */input_polldev_queue_work(dev);
}static void input_polldev_queue_work(struct input_polled_dev *dev)
{unsigned long delay;/* 计算delay事件 */delay = msecs_to_jiffies(dev->poll_interval);if (delay >= HZ)delay = round_jiffies_relative(delay);/* 在系统wq中轮询 */queue_delayed_work(system_freezable_wq, &dev->work, delay);
}

7.4.2 input_open_polled_device

|->evdev_open|->evdev_open_device|->input_open_device                轮询调用|->input_open_polled_device <----------------|->input_polldev_queue_work             ||->queue_delayed_work               ||->input_polldev_queue_work ----- 
static int input_open_polled_device(struct input_dev *input)
{struct input_polled_dev *dev = input_get_drvdata(input);/* 如果input_dev->open被赋值则调用input_dev->open */if (dev->open)dev->open(dev);/* Only start polling if polling is enabled */if (dev->poll_interval > 0) {/* 调用input_polled_dev->poll中保存的回调函数 */dev->poll(dev);/* 开启下一次轮询调用,间隔为 poll_interval ms */input_polldev_queue_work(dev);}return 0;
}

7.4.3 input_close_polled_device

|->evdev_release|->evdev_close_device|->input_close_device|->input_close_polled_device
static void input_close_polled_device(struct input_dev *input)
{struct input_polled_dev *dev = input_get_drvdata(input);/* 取消工作 */cancel_delayed_work_sync(&dev->work);/* 如果input_dev->close被赋值则调用input_dev->close */if (dev->close)dev->close(dev);
}

7.5 设定/查询轮询间隔参数

除了初始化设定,这里仅仅支持用户程序访问节点设定和查询参数

/* SYSFS interface */static ssize_t input_polldev_get_poll(struct device *dev,struct device_attribute *attr, char *buf)
{struct input_polled_dev *polldev = dev_get_drvdata(dev);return sprintf(buf, "%d\n", polldev->poll_interval);
}static ssize_t input_polldev_set_poll(struct device *dev,struct device_attribute *attr, const char *buf,size_t count)
{struct input_polled_dev *polldev = dev_get_drvdata(dev);struct input_dev *input = polldev->input;unsigned int interval;int err;err = kstrtouint(buf, 0, &interval);if (err)return err;if (interval < polldev->poll_interval_min)return -EINVAL;if (interval > polldev->poll_interval_max)return -EINVAL;mutex_lock(&input->mutex);polldev->poll_interval = interval;if (input->users) {cancel_delayed_work_sync(&polldev->work);if (polldev->poll_interval > 0)input_polldev_queue_work(polldev);}mutex_unlock(&input->mutex);return count;
}static DEVICE_ATTR(poll, S_IRUGO | S_IWUSR, input_polldev_get_poll,input_polldev_set_poll);static ssize_t input_polldev_get_max(struct device *dev,struct device_attribute *attr, char *buf)
{struct input_polled_dev *polldev = dev_get_drvdata(dev);return sprintf(buf, "%d\n", polldev->poll_interval_max);
}static DEVICE_ATTR(max, S_IRUGO, input_polldev_get_max, NULL);static ssize_t input_polldev_get_min(struct device *dev,struct device_attribute *attr, char *buf)
{struct input_polled_dev *polldev = dev_get_drvdata(dev);return sprintf(buf, "%d\n", polldev->poll_interval_min);
}static DEVICE_ATTR(min, S_IRUGO, input_polldev_get_min, NULL);static struct attribute *sysfs_attrs[] = {&dev_attr_poll.attr,&dev_attr_max.attr,&dev_attr_min.attr,NULL
};static struct attribute_group input_polldev_attribute_group = {.attrs = sysfs_attrs
};static const struct attribute_group *input_polldev_attribute_groups[] = {&input_polldev_attribute_group,NULL
};

7.6 实验

首先需要把input-poller模块编译进kernel或者编译成ko
如果是安卓设备在.config里添加即可,如果是直接使用内核的设备则需要在menuconfig里打开选项

CONFIG_INPUT_POLLDEV=m    //实验选用这个
or
CONFIG_INPUT_POLLDEV=y

然后将驱动程序编成ko,设备启动后可以看到被加载的ko
在这里插入图片描述

在这里插入图片描述

8. input_poller.c

input_poller.c是一个输入设备的事件驱动程序,它使用事件通知机制来检测输入设备是否有数据可读。当输入设备有数据可读时,它会触发一个事件通知,然后将数据传递给上层应用程序。
input_poller.c和input_polldev.c两者提供的机制没有什么区别,底层逻辑都是周期性调用其结构体成员中的poll函数指针指向的函数

8.1 数据结构

struct input_dev_poller {void (*poll)(struct input_dev *dev);unsigned int poll_interval;     //轮询间隔unsigned int poll_interval_max; //轮询间隔的最大值unsigned int poll_interval_min; //轮询间隔最小值struct input_dev *input;        //存放input_dev地址struct delayed_work work;       //延迟工作队列元素
};

8.2 驱动程序实例

https://elixir.bootlin.com/linux/v5.10/source/drivers/input/joystick/psxpad-spi.c#L350

static int psxpad_spi_probe(struct spi_device *spi)
{...err = input_setup_polling(idev, psxpad_spi_poll);if (err) {dev_err(&spi->dev, "failed to set up polling: %d\n", err);return err;}/* poll interval is about 60fps */input_set_poll_interval(idev, 16);input_set_min_poll_interval(idev, 8);input_set_max_poll_interval(idev, 32);/* register input poll device */err = input_register_device(idev);if (err) {dev_err(&spi->dev,"failed to register input device: %d\n", err);return err;}...
}

8.3 input_setup_polling

int input_setup_polling(struct input_dev *dev,void (*poll_fn)(struct input_dev *dev))
{struct input_dev_poller *poller;/* 创建input_dev_poller */poller = kzalloc(sizeof(*poller), GFP_KERNEL);if (!poller) {/** We want to show message even though kzalloc() may have* printed backtrace as knowing what instance of input* device we were dealing with is helpful.* 我们想要显示消息,即使 kzalloc() 可能已经打印回溯* 因为知道我们正在处理的输入设备实例是有帮助的。*/dev_err(dev->dev.parent ?: &dev->dev,"%s: unable to allocate poller structure\n", __func__);return -ENOMEM;}/* 初始化延迟工作队列元素 */INIT_DELAYED_WORK(&poller->work, input_dev_poller_work);/* 给input_dev_poller绑定input_dev和poll函数 */poller->input = dev;poller->poll = poll_fn;/* 给input_dev绑定input_dev_poller */dev->poller = poller;return 0;
}
EXPORT_SYMBOL(input_setup_polling);

8.4 input_dev_poller_finalize

在input_dev注册时会调用input_dev_poller_finalize,主要作用是在驱动作者未设定的轮询间隔时设定轮询间隔

|->input_register_device|->input_dev_poller_finalize
void input_dev_poller_finalize(struct input_dev_poller *poller)
{if (!poller->poll_interval)poller->poll_interval = 500;if (!poller->poll_interval_max)poller->poll_interval_max = poller->poll_interval;
}

8.5 input_dev_poller_start

这里介绍应用程序是如何启用轮询的

|->evdev_open|->evdev_open_device|->input_open_device                轮询调用|->input_dev_poller_start <----------------|->input_dev_poller_queue_work        ||->input_dev_poller_work          ||->input_dev_poller_start ----- 
void input_dev_poller_start(struct input_dev_poller *poller)
{/* Only start polling if polling is enabled */if (poller->poll_interval > 0) {/* 调用input_dev_poller的poll函数 */poller->poll(poller->input);/* 建立轮询调用 */input_dev_poller_queue_work(poller);}
}static void input_dev_poller_queue_work(struct input_dev_poller *poller)
{unsigned long delay;delay = msecs_to_jiffies(poller->poll_interval);if (delay >= HZ)delay = round_jiffies_relative(delay);/* poll_interval ms后执行 */queue_delayed_work(system_freezable_wq, &poller->work, delay);
}static void input_dev_poller_work(struct work_struct *work)
{struct input_dev_poller *poller =container_of(work, struct input_dev_poller, work.work);/* 再执行poll */poller->poll(poller->input);/* 实现轮询调用 */input_dev_poller_queue_work(poller);
}

8.6 input_dev_poller_stop

这里介绍应用程序是如何结束轮询的

|->evdev_release|->evdev_close_device|->input_close_device|->input_dev_poller_stop
void input_dev_poller_stop(struct input_dev_poller *poller)
{cancel_delayed_work_sync(&poller->work);
}

8.7 设定/查询轮询间隔参数

参数设定/查询的路径有两个

  • 驱动作者调用函数
  • 应用程序访问节点

8.7.1 驱动作者调用函数

void input_set_poll_interval(struct input_dev *dev, unsigned int interval)
{/* 如果dev的poller存在 */if (input_dev_ensure_poller(dev))dev->poller->poll_interval = interval;
}
EXPORT_SYMBOL(input_set_poll_interval);void input_set_min_poll_interval(struct input_dev *dev, unsigned int interval)
{if (input_dev_ensure_poller(dev))dev->poller->poll_interval_min = interval;
}
EXPORT_SYMBOL(input_set_min_poll_interval);void input_set_max_poll_interval(struct input_dev *dev, unsigned int interval)
{if (input_dev_ensure_poller(dev))dev->poller->poll_interval_max = interval;
}
EXPORT_SYMBOL(input_set_max_poll_interval);int input_get_poll_interval(struct input_dev *dev)
{if (!dev->poller)return -EINVAL;return dev->poller->poll_interval;
}
EXPORT_SYMBOL(input_get_poll_interval);

8.7.2 应用程序访问节点

/* SYSFS interface */static ssize_t input_dev_get_poll_interval(struct device *dev,struct device_attribute *attr,char *buf)
{struct input_dev *input = to_input_dev(dev);return sprintf(buf, "%d\n", input->poller->poll_interval);
}static ssize_t input_dev_set_poll_interval(struct device *dev,struct device_attribute *attr,const char *buf, size_t count)
{struct input_dev *input = to_input_dev(dev);struct input_dev_poller *poller = input->poller;unsigned int interval;int err;err = kstrtouint(buf, 0, &interval);if (err)return err;if (interval < poller->poll_interval_min)return -EINVAL;if (interval > poller->poll_interval_max)return -EINVAL;mutex_lock(&input->mutex);poller->poll_interval = interval;if (input->users) {cancel_delayed_work_sync(&poller->work);if (poller->poll_interval > 0)input_dev_poller_queue_work(poller);}mutex_unlock(&input->mutex);return count;
}static DEVICE_ATTR(poll, 0644,input_dev_get_poll_interval, input_dev_set_poll_interval);static ssize_t input_dev_get_poll_max(struct device *dev,struct device_attribute *attr, char *buf)
{struct input_dev *input = to_input_dev(dev);return sprintf(buf, "%d\n", input->poller->poll_interval_max);
}static DEVICE_ATTR(max, 0444, input_dev_get_poll_max, NULL);static ssize_t input_dev_get_poll_min(struct device *dev,struct device_attribute *attr, char *buf)
{struct input_dev *input = to_input_dev(dev);return sprintf(buf, "%d\n", input->poller->poll_interval_min);
}static DEVICE_ATTR(min, 0444, input_dev_get_poll_min, NULL);static umode_t input_poller_attrs_visible(struct kobject *kobj,struct attribute *attr, int n)
{struct device *dev = kobj_to_dev(kobj);struct input_dev *input = to_input_dev(dev);return input->poller ? attr->mode : 0;
}static struct attribute *input_poller_attrs[] = {&dev_attr_poll.attr,&dev_attr_max.attr,&dev_attr_min.attr,NULL
};struct attribute_group input_poller_attribute_group = {.is_visible        = input_poller_attrs_visible,.attrs                = input_poller_attrs,
};

9. Keymap

Linux input子系统中的Keymap是一个映射表,用于将输入设备上的按键映射到相应的字符或功能。它是一个非常重要的组件,因为它允许Linux系统正确地解释来自各种输入设备的输入信号。

Keymap通常由操作系统或应用程序提供,它们可以根据用户的需要进行自定义。例如,用户可以将某个按键映射到一个特定的字符或命令,或者将多个按键组合映射到一个功能键。

Keymap还可以用于解决不同键盘布局之间的差异。例如,美国键盘布局与德国键盘布局不同,因此Keymap可以确保在不同的键盘布局下,相同的按键映射到相同的字符或功能。

总之,Keymap是Linux输入子系统中的一个重要组件,它确保了输入设备的正确解释和使用。

10. input-compat.c

Linux input子系统中的input-compat是一个兼容性层,它的作用是为旧版的输入设备驱动程序提供兼容性支持,使它们能够在新版的Linux内核中正常工作。
具体来说,input-compat提供了一些函数和数据结构,用于将旧版的输入设备驱动程序与新版的输入子系统进行适配。例如,它提供了一个input_register_device函数,用于向输入子系统注册输入设备,同时还提供了一些与输入事件相关的数据结构,如input_event和input_absinfo等。
通过使用input-compat,旧版的输入设备驱动程序可以与新版的输入子系统进行兼容,从而实现输入设备的正常工作。这对于一些老旧的硬件设备来说尤为重要,因为它们的驱动程序可能已经过时,无法直接与新版的输入子系统兼容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_285735.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opcua 获取自定义结构体的成员值

示例中的节点值的数据类型为自定义的多层嵌套的结构体如下: 获取改结构体的成员值: import opcua from opcua import ua from opcua.ua import uatypesdef user_defined_vars(value_dict, # 自定义数据类型的变量值name_prefix, # 成员名前缀比如aa.bbdata_dict2, # 用…

API 接口设计

1、场景描述 比如说我们要做一款 APP&#xff0c;需要通过 api 接口给 app 提供数据。假设我们是做商城&#xff0c;比如我们卖书的。我们可以想象下这个 APP 大概有哪些内容&#xff1a; 1&#xff09;首页&#xff1a;banner 区域&#xff08;可以是一些热门书籍的图片做推广…

第十四届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组题解+个人总结

提示&#xff1a;此题解为本人自己解决&#xff0c;如有差错请大家多多指正。 文章目录题解总结一、幸运数1.试题2.解法3.代码二、[有奖问答](https://blog.csdn.net/A2105153335/article/details/130038980?spm1001.2014.3001.5501)三、[平方差](https://blog.csdn.net/A2105…

js flyout 2: VScroll

目录版权描述测试页面showFlyout问题1 - scroll 实现可能不准?问题2 - 容器内容重排可导致浮层错位关于重排小结附录 - 完整代码版权 本文为原创, 遵循 CC 4.0 BY-SA 版权协议, 转载需注明出处: https://blog.csdn.net/big_cheng/article/details/130101031. 文中代码属于 pu…

数据结构与算法01 稀疏数组

稀疏数组问题 当一个二维数组中大部分数据都是0&#xff0c;对这个数组直接进行存储会很浪费空间&#xff0c;因此利用稀疏数组进行压缩&#xff0c;稀疏数组第一行的第一个元素是原二维数组行数。&#xff0c;第一行的第二个元素是原二维数组的列数&#xff0c;如图为11行11列…

6.S081——虚拟内存部分——xv6源码完全解析系列(4)

0.briefly speaking 点击跳转到上一篇博客 好&#xff0c;现在进入下一个话题&#xff0c;就是物理内存分配器(kernel/kalloc.c)。在简单介绍完内核态的物理内存分配器之后&#xff0c;之后简单带过一下两个头文件riscv.h和memorylayout.h这两个头文件&#xff0c;因为它们都…

2.5d风格的游戏模式如何制作

文章目录一、 介绍二、 绘制瓦片地图三、 添加场景物体&#xff0c;添加碰撞器四、 创建玩家五、 创建玩家动画六、 玩家脚本七、 2d转换成2.5d八、 “Q”键向左转动视角、“E”键向右转动视角九、 下载工程文件一、 介绍 制作一个类似饥荒风格的2.5d游戏模板。 2.5D游戏是指以…

表id自增的方法

数据库主键id自增的方法&#xff0c;列举了几种如下 一、数据库自增&#xff08;部分数据库支持&#xff09; 创建表的时候设置id自增即可&#xff0c;或者后期修改表id自增 # mysql 语法 create table your_table_name(id bigint(20) not null auto_increment primary key …

Markdown 语法大全

Markdown是一种轻量级标记语言&#xff0c;常用于撰写博客、文档、论文等。它可以让你使用易读易写的纯文本格式来编写文档&#xff0c;然后通过转换成有效的HTML文档进行发布。以下是Markdown常用的语法&#xff1a; 这里写目录标题标题列表引用一级引用嵌套引用粗体和斜体删除…

Java集合——Set接口学习总结

一、HashSet实现类 1.常用方法 增加&#xff1a;add(E e)删除&#xff1a;remove(Object o)、clear()修改&#xff1a;查看&#xff1a;iterator()判断&#xff1a;contains(Object o)、isEmpty()常用遍历方式&#xff1a;Set<String> set new HashSet<String>()…

Spark 对hadoopnamenode-log文件进行数据清洗并存入mysql数据库

一.查找需要清洗的文件 1.1查看hadoopnamenode-log文件位置 1.2 开启Hadoop集群和Hive元数据、Hive远程连接 具体如何开启可以看我之前的文章&#xff1a;(10条消息) SparkSQL-liunx系统Spark连接Hive_难以言喻wyy的博客-CSDN博客 1.3 将这个文件传入到hdfs中&#xff1a; hd…

windows系统管理_windows server 2016 用户管理

用户账户的概述 **计算机用户账户&#xff1a;**由将用户定义到某一系统的所有信息组成的记录,账户为用户或计算机提供安 全凭证&#xff0c;包括用户名和用户登陆所需要的密码&#xff0c;以及用户使用以便用户和计算机能够登录到网络并 访问域资源的权利和权限。不同的身份拥…

【Obsidian】基础使用手册(包括如何将Obsidian页面设置为中文)

&#x1f497; 未来的游戏开发程序媛&#xff0c;现在的努力学习菜鸡 &#x1f4a6;本专栏是我关于工具类软件的笔记 &#x1f236;本篇是Obsidian的基础使用 Obsidian的基础使用将页面设置为中文常用的默认快捷键常用的格式标题代码块表格字体样式列表任务列表官方下载地址&am…

【音视频第11天】GCC论文阅读(2)

A Google Congestion Control Algorithm for Real-Time Communication draft-alvestrand-rmcat-congestion-03论文理解 看中文的GCC算法一脸懵。看一看英文版的&#xff0c;找一找感觉。 目录Abstract1. Introduction1.1 Mathematical notation conventions2. System model3.Fe…

获取淘宝商品分类详情API,抓取淘宝全品类目API接口分享(代码展示、参数说明)

商品分类技巧 淘宝店铺分类怎么设置&#xff1f;我们登录卖家账号的时候&#xff0c;我们看到自己的商品&#xff0c;会想要给商品进行分类&#xff0c;一个好的分类可以帮助提高商品的曝光率。那么在给商品分类前&#xff0c;如果您毫无头绪&#xff0c;以下几点可以给您带来…

车载网络 - Autosar网络管理 - 网络管理简介

一、什么是CAN网络管理及它的作用 现在的车辆是由大量的ECU节点组成的&#xff0c;为了能使各ECU能够正确并及时地进行CAN通信&#xff0c;需要有一套机制来统一协调总线上各节点的休眠唤醒&#xff0c;这套机制就是CAN网络管理&#xff08;NM&#xff09;。 网络管理的目的是保…

【算法题解】24. 模拟机器人行走

这是一道 中等难度 的题 https://leetcode.cn/problems/walking-robot-simulation/description/ 题目 机器人在一个无限大小的 XY 网格平面上行走&#xff0c;从点 (0, 0) 处开始出发&#xff0c;面向北方。该机器人可以接收以下三种类型的命令 commands &#xff1a; -2 &am…

WPF mvvm框架Stylet使用教程-基础用法

Stylet框架基础用法 安装Nuget包 在“管理Nuget程序包”中搜索Stylet&#xff0c;查看Stylet包支持的net版本&#xff0c;然后选择第二个Stylet.Start包进行安装&#xff0c;该包会自动安装stylet并且生成基本的配置 注意事项&#xff1a;安装时要把需要安装的程序设为启动项…

PyCharm2021安装教程

PyCharm是一种Python IDE&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外&#xff0c;该IDE提供了一些高级功能&#xff0c;以用于支持Djang…

IntersectionObserver与无限滚动加载

学习链接 IntersectionObserver MDN Api IntersectionObserver API详解 Intersection observer 的概念和用法 过去&#xff0c;要检测一个元素是否可见或者两个元素是否相交并不容易&#xff0c;比如实现图片懒加载、内容无限滚动等功能时&#xff0c;都需要通过​getBound…