uinput module analysis -- 3 combined with application code analysis

Posted by pjoshi on Mon, 07 Feb 2022 12:56:47 +0100

The application code is as follows:

#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 fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
 
 
        /*
         * 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_KEYBIT, KEY_SPACE);
 
        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);
        emit(fd, EV_KEY, KEY_SPACE, 0);
        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(10);
 
        ioctl(fd, UI_DEV_DESTROY);
        close(fd);
 
        return 0;
}

Start with open

open

open("/dev/uinput", O_WRONLY | O_NONBLOCK);

Corresponding driver code

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;

	mutex_init(&newdev->mutex);
	spin_lock_init(&newdev->requests_lock);
	init_waitqueue_head(&newdev->requests_waitq);
	init_waitqueue_head(&newdev->waitq);
	newdev->state = UIST_NEW_DEVICE;

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

	return 0;
}

You can see that it is simply uinput_device applies for space, initializes the lock and work queue, and sets the status to UIST_NEW_DEVICE

ioctl

ioctl(fd, UI_SET_EVBIT, EV_KEY);

The corresponding relationship is as follows

uinput_ioctl() -> uinput_ioctl_handler()

static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
				 unsigned long arg, void __user *p)
{
	int			retval;
	struct uinput_device	*udev = file->private_data;
	struct uinput_ff_upload ff_up;
	struct uinput_ff_erase  ff_erase;
	struct uinput_request   *req;
	char			*phys;
	const char		*name;
	unsigned int		size;

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

	if (!udev->dev) {
		udev->dev = input_allocate_device();
		if (!udev->dev) {
			retval = -ENOMEM;
			goto out;
		}
	}
        ...
        switch (cmd) {
	case UI_SET_EVBIT:
		retval = uinput_set_bit(arg, evbit, EV_MAX);
		goto out;
        ...
}

During ioctl, it will judge whether udev - > dev is empty. This dev is input_dev, if it is empty, it will apply for allocation of input_dev, which is the same as the input device registration previously analyzed.

uinput_ set_ The prototype of bit is as follows:

#define uinput_set_bit(_arg, _bit, _max)		\
({							\
	int __ret = 0;					\
	if (udev->state == UIST_CREATED)		\
		__ret =  -EINVAL;			\
	else if ((_arg) > (_max))			\
		__ret = -EINVAL;			\
	else set_bit((_arg), udev->dev->_bit);		\
	__ret;						\
})

You can see that it is to operate the evbit of the input device and set the supported events

For ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);

	case UI_SET_KEYBIT:
		retval = uinput_set_bit(arg, keybit, KEY_MAX);
		goto out;

In fact, keybit is set to indicate the supported key values, which is no different from the previous analysis

        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);

Corresponding to uinput in ioctl_ dev_ setup()

static int uinput_dev_setup(struct uinput_device *udev,
			    struct uinput_setup __user *arg)
{
	struct uinput_setup setup;
	struct input_dev *dev;

	if (udev->state == UIST_CREATED)
		return -EINVAL;

	if (copy_from_user(&setup, arg, sizeof(setup)))
		return -EFAULT;

	if (!setup.name[0])
		return -EINVAL;

	dev = udev->dev;
	dev->id = setup.id;
	udev->ff_effects_max = setup.ff_effects_max;

	kfree(dev->name);
	dev->name = kstrndup(setup.name, UINPUT_MAX_NAME_SIZE, GFP_KERNEL);
	if (!dev->name)
		return -ENOMEM;

	udev->state = UIST_SETUP_COMPLETE;
	return 0;
}

Compare GPIO keys C kernel registration method

In fact, what is operated here is the content of the red box. The status changes to UIST_SETUP_COMPLETE

Keep looking

ioctl(fd, UI_DEV_CREATE);

Corresponding input_create_device

static int uinput_create_device(struct uinput_device *udev)
{
	struct input_dev *dev = udev->dev;
	int error, nslot;

	if (udev->state != UIST_SETUP_COMPLETE) {
		printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME);
		return -EINVAL;
	}

	...

	dev->event = uinput_dev_event;

	input_set_drvdata(udev->dev, udev);

	error = input_register_device(udev->dev);
	if (error)
		goto fail2;

	udev->state = UIST_CREATED;

	return 0;

 fail2:	input_ff_destroy(dev);
 fail1: uinput_destroy_device(udev);
	return error;
}

The above code ignores EV_ABS and ev_ For FF related content, here we mainly focus on EV_KEY

Note that dev - > event here is the assigned uinput_dev_event, remember first

Remember to register the input device. As analyzed earlier, you will find the handler, register the event, and register the handle

Finally, the status becomes UIST_CREATED

In the whole process, the status changes to UIST_ NEW_ DEVICE --> UIST_ SETUP_ COMPLETE --> UIST_ CREATED

The above has registered an input device, and the following analysis is to simulate button reporting

emit(fd, EV_KEY, KEY_SPACE, 1);

Corresponding uinput_write

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

	if (count == 0)
		return 0;

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

	retval = udev->state == UIST_CREATED ?
			uinput_inject_events(udev, buffer, count) :
			uinput_setup_device_legacy(udev, buffer, count);

	mutex_unlock(&udev->mutex);

	return retval;
}

For UIST status_ When created, call uinput directly_ inject_ Events means injecting events.

When the status is not UIST_ When created, that is to say, the input device will report an event before it is registered, then uinput will be executed_ setup_ device_ Legacy, this function will allocate input device space to register input. This is a method that does not follow the specified process, so we do not analyze it. We mainly analyze the process of entering events after registering input devices normally

static ssize_t uinput_inject_events(struct uinput_device *udev,
				    const char __user *buffer, size_t count)
{
	struct input_event ev;
	size_t bytes = 0;

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

	while (bytes + input_event_size() <= count) {
		/*
		 * Note that even if some events were fetched successfully
		 * we are still going to return EFAULT instead of partial
		 * count to let userspace know that it got it's buffers
		 * all wrong.
		 */
		if (input_event_from_user(buffer + bytes, &ev))
			return -EFAULT;

		input_event(udev->dev, ev.type, ev.code, ev.value);
		bytes += input_event_size();
		cond_resched();
	}

	return bytes;
}

It can be seen that the data in user space is copied through input_event report, then

emit(fd, EV_SYN, SYN_REPORT, 0);

This statement is also executed for the input in the kernel_ Sync is actually input_ Encapsulation of event.

The above completes the analysis of the reporting of simulated key events by uinput.

In general, uinput is easy to understand because it is implemented based on input, but the idea is very ingenious, and there are still a lot of Daniel.