上一篇文章中,我们记录了一个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的作用。