uinput模块分析 -- 4 input设备的event方法分析

上一篇文章中,我们记录了一个dev->event 就是 uinput_dev_event

在之前分析input_handle_event的时候,有下面的代码

static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
	int disposition = input_get_disposition(dev, type, code, &value);

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		add_input_randomness(type, code, value);

	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);
        ...
}

当部署中有INPUT_PASS_TO_DEVICE的时候,会调用event

我们所有的分析 都是基于EV_KEY的,但是EV_KEY不会有这个部署,所以这里我们选择EV_LED

简单说一下led handler

代码位置 kernel/drivers/input/input-leds.c

input_leds_handler的id_table如下

static const struct input_device_id input_leds_ids[] = {
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
		.evbit = { BIT_MASK(EV_LED) },
	},
	{ },
};

我们后面要使用uinput的话,那么支持的事件包加上EV_LED

既然支持EV_LED,那么就要配置ledbit,表示支持那些led,支持列表如下

static const struct {
	const char *name;
	const char *trigger;
} input_led_info[LED_CNT] = {
	[LED_NUML]	= { "numlock", VT_TRIGGER("kbd-numlock") },
	[LED_CAPSL]	= { "capslock", VT_TRIGGER("kbd-capslock") },
	[LED_SCROLLL]	= { "scrolllock", VT_TRIGGER("kbd-scrolllock") },
	[LED_COMPOSE]	= { "compose" },
	[LED_KANA]	= { "kana", VT_TRIGGER("kbd-kanalock") },
	[LED_SLEEP]	= { "sleep" } ,
	[LED_SUSPEND]	= { "suspend" },
	[LED_MUTE]	= { "mute" },
	[LED_MISC]	= { "misc" },
	[LED_MAIL]	= { "mail" },
	[LED_CHARGING]	= { "charging" },
};

我们以大小写的键盘灯为例 LED_CAPSL

于是新的应用代码如下:

1,先支持EV_LED,编码为LED_CAPSL

        ioctl(fd, UI_SET_EVBIT, EV_KEY);
        ioctl(fd, UI_SET_EVBIT, EV_LED);
        ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
        ioctl(fd, UI_SET_LEDBIT, LED_CAPSL);

注意不要写成 ioctl(fd, UI_SET_EVBIT, EV_KEY | EV_LED); 会有大问题的!可以实验

这时候cat /proc/bus/input/devices

I: Bus=0003 Vendor=1234 Product=5678 Version=0000
N: Name="Example device"
P: Phys=
S: Sysfs=/devices/virtual/input/input10
U: Uniq=
H: Handlers=leds event2 
B: PROP=0
B: EV=20003
B: KEY=2000000 0
B: LED=2

可以看到匹配了2个handler,分别是event2和leds

上报事件中加上EV_LED

        emit(fd, EV_KEY, KEY_SPACE, 1);
        emit(fd, EV_SYN, SYN_REPORT, 0);
        sleep(1);
        emit(fd, EV_KEY, KEY_SPACE, 0);
        emit(fd, EV_SYN, SYN_REPORT, 0);
        sleep(1);
        emit(fd, EV_LED, LED_CAPSL, 0);
        emit(fd, EV_SYN, SYN_REPORT, 0);

首先可以知道的是hexdump event2的时候,按键上报是没有问题的,led上报能看到吗?

从event2肯定是看不到的,因为event2所属的handler是evdev,而led事件的handler是leds

leds handler事件上报要用到自己的event或者events

static void input_leds_event(struct input_handle *handle, unsigned int type,
			     unsigned int code, int value)
{
}

但是驱动代码中只有event且为空,

那么怎么看led的事件?

这就回到了开头的dev->event

static int uinput_dev_event(struct input_dev *dev,
			    unsigned int type, unsigned int code, int value)
{
	struct uinput_device	*udev = input_get_drvdata(dev);
	struct timespec64	ts;

	ktime_get_ts64(&ts);

	udev->buff[udev->head] = (struct input_event) {
		.input_event_sec = ts.tv_sec,
		.input_event_usec = ts.tv_nsec / NSEC_PER_USEC,
		.type = type,
		.code = code,
		.value = value,
	};

	udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE;

	wake_up_interruptible(&udev->waitq);

	return 0;
}

可以看到事件被放到了uinput_device的缓冲区

我们可以通过uinput_read去读

为了测试这个读我写了这样的代码

int main(void)
{
        struct input_event ie;
        int ret;

        int fd = open("/dev/uinput", O_RDONLY);

        memset(&ie, 0, sizeof(ie));

        printf("will read!\n");
        ret = read(fd, &ie, sizeof(ie));
        if (ret == sizeof(ie)) {
                printf("type = 0x%08x, code = 0x%08x, val = 0x%08x",
                                ie.type, ie.code, ie.value);
        }
        printf("read over! ret = %d\n", ret);

        close(fd);

        return 0;
}

发现一直读不到数据,且直接返回,于是回头去看驱动uinput_open代码发现

static int uinput_open(struct inode *inode, struct file *file)
{
	struct uinput_device *newdev;

	newdev = kzalloc(sizeof(struct uinput_device), GFP_KERNEL);
	if (!newdev)
		return -ENOMEM;

	...
        newdev->state = UIST_NEW_DEVICE;

    	file->private_data = newdev;
	nonseekable_open(inode, file);

	return 0;
}

每次open,这个udev都是一个新的,read的时候

static ssize_t uinput_read(struct file *file, char __user *buffer,
			   size_t count, loff_t *ppos)
{
	struct uinput_device *udev = file->private_data;
	ssize_t retval;

	if (count != 0 && count < input_event_size())
		return -EINVAL;

	do {
		retval = mutex_lock_interruptible(&udev->mutex);
		if (retval)
			return retval;

		if (udev->state != UIST_CREATED)
			retval = -ENODEV;
		else if (udev->head == udev->tail &&
			 (file->f_flags & O_NONBLOCK))
			retval = -EAGAIN;
		else
			retval = uinput_events_to_user(udev, buffer, count);
        ...
}

可以看到在判断状态的地方就会返回了,因为此时状态为UIST_NEW_DEVICE

 

解决方法只能是在一个文件中操作了,于是最终的代码如下

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/uinput.h>

void emit(int fd, int type, int code, int val)
{
        struct input_event ie;

        ie.type = type;
        ie.code = code;
        ie.value = val;
        /* timestamp values below are ignored */
        ie.time.tv_sec = 0;
        ie.time.tv_usec = 0;

        write(fd, &ie, sizeof(ie));
}

int main(void)
{
        struct uinput_setup usetup;
        int ret;
        struct input_event event = {0};

        int fd = open("/dev/uinput", O_RDWR);


        /*
         * The ioctls below will enable the device that is about to be
         * created, to pass key events, in this case the space key.
         */
        ioctl(fd, UI_SET_EVBIT, EV_KEY);
        ioctl(fd, UI_SET_EVBIT, EV_LED);
        ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);
        ioctl(fd, UI_SET_LEDBIT, LED_CAPSL);

        memset(&usetup, 0, sizeof(usetup));
        usetup.id.bustype = BUS_USB;
        usetup.id.vendor = 0x1234; /* sample vendor */
        usetup.id.product = 0x5678; /* sample product */
        strcpy(usetup.name, "Example device");

        ioctl(fd, UI_DEV_SETUP, &usetup);
        ioctl(fd, UI_DEV_CREATE);

        /*
         * On UI_DEV_CREATE the kernel will create the device node for this
         * device. We are inserting a pause here so that userspace has time
         * to detect, initialize the new device, and can start listening to
         * the event, otherwise it will not notice the event we are about
         * to send. This pause is only needed in our example code!
         */
        sleep(10);

        /* Key press, report the event, send key release, and report again */
        emit(fd, EV_KEY, KEY_SPACE, 1);
        emit(fd, EV_SYN, SYN_REPORT, 0);
        sleep(1);
        emit(fd, EV_KEY, KEY_SPACE, 0);
        emit(fd, EV_SYN, SYN_REPORT, 0);
        sleep(1);
        emit(fd, EV_LED, LED_CAPSL, 1);
        emit(fd, EV_SYN, SYN_REPORT, 0);

        /*
         * Give userspace some time to read the events before we destroy the
         * device with UI_DEV_DESTOY.
         */
        sleep(3);

        ret = read(fd, &event, sizeof(event));
        if (ret == sizeof(event)) {
                printf("type = 0x%08x, code = 0x%08x, val = 0x%08x",
                                event.type, event.code, event.value);
        }

        ioctl(fd, UI_DEV_DESTROY);
        close(fd);

        return 0;
}

测试打印

type = 0x00000011, code = 0x00000001, val = 0x00000001

 

经过以上的分析,就明白了uinput_dev_event的作用。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dianlong_lee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值