diff --git a/Code/Makefile b/Code/Makefile new file mode 100644 index 0000000..fd2971f --- /dev/null +++ b/Code/Makefile @@ -0,0 +1,10 @@ +obj-m += vinput.o +obj-m += vkbd.o + +PWD := $(CURDIR) + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/Code/vinput.c b/Code/vinput.c new file mode 100644 index 0000000..21bd9aa --- /dev/null +++ b/Code/vinput.c @@ -0,0 +1,403 @@ +/* + * vinput.c + */ + +#include +#include +#include +#include +#include + +#include + +#include "vinput.h" + +#define DRIVER_NAME "vinput" + +#define dev_to_vinput(dev) container_of(dev, struct vinput, dev) + +static DECLARE_BITMAP(vinput_ids, VINPUT_MINORS); + +static LIST_HEAD(vinput_devices); +static LIST_HEAD(vinput_vdevices); + +static int vinput_dev; +static struct spinlock vinput_lock; +static struct class vinput_class; + +/* Search the name of vinput device in the vinput_devices linked list, + * which added at vinput_register(). + */ +static struct vinput_device *vinput_get_device_by_type(const char *type) +{ + int found = 0; + struct vinput_device *vinput; + struct list_head *curr; + + spin_lock(&vinput_lock); + list_for_each (curr, &vinput_devices) { + vinput = list_entry(curr, struct vinput_device, list); + if (vinput && strncmp(type, vinput->name, strlen(vinput->name)) == 0) { + found = 1; + break; + } + } + spin_unlock(&vinput_lock); + + if (found) + return vinput; + return ERR_PTR(-ENODEV); +} + +/* Search the id of virtual device in the vinput_vdevices linked list, + * which added at vinput_alloc_vdevice(). + */ +static struct vinput *vinput_get_vdevice_by_id(long id) +{ + struct vinput *vinput = NULL; + struct list_head *curr; + + spin_lock(&vinput_lock); + list_for_each (curr, &vinput_vdevices) { + vinput = list_entry(curr, struct vinput, list); + if (vinput && vinput->id == id) + break; + } + spin_unlock(&vinput_lock); + + if (vinput && vinput->id == id) + return vinput; + return ERR_PTR(-ENODEV); +} + +static int vinput_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct vinput *vinput = NULL; + + vinput = vinput_get_vdevice_by_id(iminor(inode)); + + if (IS_ERR(vinput)) + err = PTR_ERR(vinput); + else + file->private_data = vinput; + + return err; +} + +static int vinput_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t vinput_read(struct file *file, char __user *buffer, size_t count, + loff_t *offset) +{ + int len; + char buff[VINPUT_MAX_LEN + 1]; + struct vinput *vinput = file->private_data; + + len = vinput->type->ops->read(vinput, buff, count); + + if (*offset > len) + count = 0; + else if (count + *offset > VINPUT_MAX_LEN) + count = len - *offset; + + if (raw_copy_to_user(buffer, buff + *offset, count)) + count = -EFAULT; + + *offset += count; + + return count; +} + +static ssize_t vinput_write(struct file *file, const char __user *buffer, + size_t count, loff_t *offset) +{ + char buff[VINPUT_MAX_LEN + 1]; + struct vinput *vinput = file->private_data; + + memset(buff, 0, sizeof(char) * (VINPUT_MAX_LEN + 1)); + + if (count > VINPUT_MAX_LEN) { + dev_warn(&vinput->dev, "Too long. %d bytes allowed\n", VINPUT_MAX_LEN); + return -EINVAL; + } + + if (raw_copy_from_user(buff, buffer, count)) + return -EFAULT; + + return vinput->type->ops->send(vinput, buff, count); +} + +static const struct file_operations vinput_fops = { + .owner = THIS_MODULE, + .open = vinput_open, + .release = vinput_release, + .read = vinput_read, + .write = vinput_write, +}; + +static void vinput_unregister_vdevice(struct vinput *vinput) +{ + input_unregister_device(vinput->input); + if (vinput->type->ops->kill) + vinput->type->ops->kill(vinput); +} + +static void vinput_destroy_vdevice(struct vinput *vinput) +{ + /* Remove from the list first */ + spin_lock(&vinput_lock); + list_del(&vinput->list); + clear_bit(vinput->id, vinput_ids); + spin_unlock(&vinput_lock); + + module_put(THIS_MODULE); + + kfree(vinput); +} + +static void vinput_release_dev(struct device *dev) +{ + struct vinput *vinput = dev_to_vinput(dev); + int id = vinput->id; + + vinput_destroy_vdevice(vinput); + + pr_debug("released vinput%d.\n", id); +} + +static struct vinput *vinput_alloc_vdevice(void) +{ + int err; + struct vinput *vinput = kzalloc(sizeof(struct vinput), GFP_KERNEL); + + try_module_get(THIS_MODULE); + + memset(vinput, 0, sizeof(struct vinput)); + + spin_lock_init(&vinput->lock); + + spin_lock(&vinput_lock); + vinput->id = find_first_zero_bit(vinput_ids, VINPUT_MINORS); + if (vinput->id >= VINPUT_MINORS) { + err = -ENOBUFS; + goto fail_id; + } + set_bit(vinput->id, vinput_ids); + list_add(&vinput->list, &vinput_vdevices); + spin_unlock(&vinput_lock); + + /* allocate the input device */ + vinput->input = input_allocate_device(); + if (vinput->input == NULL) { + pr_err("vinput: Cannot allocate vinput input device\n"); + err = -ENOMEM; + goto fail_input_dev; + } + + /* initialize device */ + vinput->dev.class = &vinput_class; + vinput->dev.release = vinput_release_dev; + vinput->dev.devt = MKDEV(vinput_dev, vinput->id); + dev_set_name(&vinput->dev, DRIVER_NAME "%lu", vinput->id); + + return vinput; + +fail_input_dev: + spin_lock(&vinput_lock); + list_del(&vinput->list); +fail_id: + spin_unlock(&vinput_lock); + module_put(THIS_MODULE); + kfree(vinput); + + return ERR_PTR(err); +} + +static int vinput_register_vdevice(struct vinput *vinput) +{ + int err = 0; + + /* register the input device */ + vinput->input->name = vinput->type->name; + vinput->input->phys = "vinput"; + vinput->input->dev.parent = &vinput->dev; + + vinput->input->id.bustype = BUS_VIRTUAL; + vinput->input->id.product = 0x0000; + vinput->input->id.vendor = 0x0000; + vinput->input->id.version = 0x0000; + + err = vinput->type->ops->init(vinput); + + if (err == 0) + dev_info(&vinput->dev, "Registered virtual input %s %ld\n", + vinput->type->name, vinput->id); + + return err; +} + +static ssize_t export_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t len) +{ + int err; + struct vinput *vinput; + struct vinput_device *device; + + device = vinput_get_device_by_type(buf); + if (IS_ERR(device)) { + pr_info("vinput: This virtual device isn't registered\n"); + err = PTR_ERR(device); + goto fail; + } + + vinput = vinput_alloc_vdevice(); + if (IS_ERR(vinput)) { + err = PTR_ERR(vinput); + goto fail; + } + + vinput->type = device; + err = device_register(&vinput->dev); + if (err < 0) + goto fail_register; + + err = vinput_register_vdevice(vinput); + if (err < 0) + goto fail_register_vinput; + + return len; + +fail_register_vinput: + device_unregister(&vinput->dev); +fail_register: + vinput_destroy_vdevice(vinput); +fail: + return err; +} +/* This macro generates class_attr_export structure and export_store() */ +static CLASS_ATTR_WO(export); + +static ssize_t unexport_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t len) +{ + int err; + unsigned long id; + struct vinput *vinput; + + err = kstrtol(buf, 10, &id); + if (err) { + err = -EINVAL; + goto failed; + } + + vinput = vinput_get_vdevice_by_id(id); + if (IS_ERR(vinput)) { + pr_err("vinput: No such vinput device %ld\n", id); + err = PTR_ERR(vinput); + goto failed; + } + + vinput_unregister_vdevice(vinput); + device_unregister(&vinput->dev); + + return len; +failed: + return err; +} +/* This macro generates class_attr_unexport structure and unexport_store() */ +static CLASS_ATTR_WO(unexport); + +static struct attribute *vinput_class_attrs[] = { + &class_attr_export.attr, + &class_attr_unexport.attr, + NULL, +}; + +/* This macro generates vinput_class_groups structure */ +ATTRIBUTE_GROUPS(vinput_class); + +static struct class vinput_class = { + .name = "vinput", + .owner = THIS_MODULE, + .class_groups = vinput_class_groups, +}; + +int vinput_register(struct vinput_device *dev) +{ + spin_lock(&vinput_lock); + list_add(&dev->list, &vinput_devices); + spin_unlock(&vinput_lock); + + pr_info("vinput: registered new virtual input device '%s'\n", dev->name); + + return 0; +} +EXPORT_SYMBOL(vinput_register); + +void vinput_unregister(struct vinput_device *dev) +{ + struct list_head *curr, *next; + + /* Remove from the list first */ + spin_lock(&vinput_lock); + list_del(&dev->list); + spin_unlock(&vinput_lock); + + /* unregister all devices of this type */ + list_for_each_safe (curr, next, &vinput_vdevices) { + struct vinput *vinput = list_entry(curr, struct vinput, list); + if (vinput && vinput->type == dev) { + vinput_unregister_vdevice(vinput); + device_unregister(&vinput->dev); + } + } + + pr_info("vinput: unregistered virtual input device '%s'\n", dev->name); +} +EXPORT_SYMBOL(vinput_unregister); + +static int __init vinput_init(void) +{ + int err = 0; + + pr_info("vinput: Loading virtual input driver\n"); + + vinput_dev = register_chrdev(0, DRIVER_NAME, &vinput_fops); + if (vinput_dev < 0) { + pr_err("vinput: Unable to allocate char dev region\n"); + err = vinput_dev; + goto failed_alloc; + } + + spin_lock_init(&vinput_lock); + + err = class_register(&vinput_class); + if (err < 0) { + pr_err("vinput: Unable to register vinput class\n"); + goto failed_class; + } + + return 0; +failed_class: + class_unregister(&vinput_class); +failed_alloc: + return err; +} + +static void __exit vinput_end(void) +{ + pr_info("vinput: Unloading virtual input driver\n"); + + unregister_chrdev(vinput_dev, DRIVER_NAME); + class_unregister(&vinput_class); +} + +module_init(vinput_init); +module_exit(vinput_end); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Emulate input events"); diff --git a/Code/vinput.h b/Code/vinput.h new file mode 100644 index 0000000..c77c138 --- /dev/null +++ b/Code/vinput.h @@ -0,0 +1,49 @@ +/* + * vinput.h + */ + +#ifndef VINPUT_H +#define VINPUT_H + +#include +#include + +#define VINPUT_MAX_LEN 128 +#define MAX_VINPUT 32 +#define VINPUT_MINORS MAX_VINPUT + +#define dev_to_vinput(dev) container_of(dev, struct vinput, dev) + +struct vinput_device; + +struct vinput { + long id; + long devno; + long last_entry; + spinlock_t lock; + + void *priv_data; + + struct device dev; + struct list_head list; + struct input_dev *input; + struct vinput_device *type; +}; + +struct vinput_ops { + int (*init)(struct vinput *); + int (*kill)(struct vinput *); + int (*send)(struct vinput *, char *, int); + int (*read)(struct vinput *, char *, int); +}; + +struct vinput_device { + char name[16]; + struct list_head list; + struct vinput_ops *ops; +}; + +int vinput_register(struct vinput_device *dev); +void vinput_unregister(struct vinput_device *dev); + +#endif diff --git a/Code/vkbd.c b/Code/vkbd.c new file mode 100644 index 0000000..d1cb833 --- /dev/null +++ b/Code/vkbd.c @@ -0,0 +1,110 @@ +/* + * vkbd.c + */ + +#include +#include +#include +#include + +#include "vinput.h" + +#define VINPUT_KBD "vkbd" +#define VINPUT_RELEASE 0 +#define VINPUT_PRESS 1 + +static unsigned short vkeymap[KEY_MAX]; + +static int vinput_vkbd_init(struct vinput *vinput) +{ + int i; + + /* Set up the input bitfield */ + vinput->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); + vinput->input->keycodesize = sizeof(unsigned short); + vinput->input->keycodemax = KEY_MAX; + vinput->input->keycode = vkeymap; + + for (i = 0; i < KEY_MAX; i++) + set_bit(vkeymap[i], vinput->input->keybit); + + /* vinput will help us allocate new input device structure via + * input_allocate_device(). So, we can register it straightforwardly. + */ + return input_register_device(vinput->input); +} + +static int vinput_vkbd_read(struct vinput *vinput, char *buff, int len) +{ + spin_lock(&vinput->lock); + len = snprintf(buff, len, "%+ld\n", vinput->last_entry); + spin_unlock(&vinput->lock); + + return len; +} + +static int vinput_vkbd_send(struct vinput *vinput, char *buff, int len) +{ + int ret; + long key = 0; + short type = VINPUT_PRESS; + + /* Determine which event was received (press or release) + * and store the state. + */ + if (buff[0] == '+') + ret = kstrtol(buff + 1, 10, &key); + else + ret = kstrtol(buff, 10, &key); + if (ret) + dev_err(&vinput->dev, "error during kstrtol: -%d\n", ret); + spin_lock(&vinput->lock); + vinput->last_entry = key; + spin_unlock(&vinput->lock); + + if (key < 0) { + type = VINPUT_RELEASE; + key = -key; + } + + dev_info(&vinput->dev, "Event %s code %ld\n", + (type == VINPUT_RELEASE) ? "VINPUT_RELEASE" : "VINPUT_PRESS", key); + + /* Report the state received to input subsystem. */ + input_report_key(vinput->input, key, type); + /* Tell input subsystem that it finished the report. */ + input_sync(vinput->input); + + return len; +} + +static struct vinput_ops vkbd_ops = { + .init = vinput_vkbd_init, + .send = vinput_vkbd_send, + .read = vinput_vkbd_read, +}; + +static struct vinput_device vkbd_dev = { + .name = VINPUT_KBD, + .ops = &vkbd_ops, +}; + +static int __init vkbd_init(void) +{ + int i; + + for (i = 0; i < KEY_MAX; i++) + vkeymap[i] = i; + return vinput_register(&vkbd_dev); +} + +static void __exit vkbd_end(void) +{ + vinput_unregister(&vkbd_dev); +} + +module_init(vkbd_init); +module_exit(vkbd_end); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Emulate keyboard input events through /dev/vinput"); diff --git a/README.md b/README.md index 7ea165a..0a193ed 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # Homework_Lesson_8 +## Assignment +Create a virtual keyboard using the kernel module, +which simulates a key press on a virtual input device /dev/vinput0. +Echo some keycode to the virtual input device and get the key message on the standard output. +Compile and run the program. +Submit the source code and the screen shots of the standard output and the kernel log message, +respectively, when the keycode is echoed to the virtual input device. \ No newline at end of file diff --git a/output.txt b/output.txt new file mode 100644 index 0000000..d2090e9 --- /dev/null +++ b/output.txt @@ -0,0 +1,13 @@ +vkbd ++36 +-36 +0 +================================================== +[Mon Nov 28 11:09:58 2022] vinput: Loading virtual input driver +[Mon Nov 28 11:09:58 2022] vinput: registered new virtual input device 'vkbd' +[Mon Nov 28 11:09:58 2022] input: vkbd as /devices/virtual/vinput/vinput0/input19 +[Mon Nov 28 11:09:58 2022] vinput vinput0: Registered virtual input vkbd 0 +[Mon Nov 28 11:09:59 2022] vinput vinput0: Event VINPUT_PRESS code 36 +[Mon Nov 28 11:10:04 2022] vinput vinput0: Event VINPUT_RELEASE code 36 +[Mon Nov 28 11:10:05 2022] vinput: unregistered virtual input device 'vkbd' +[Mon Nov 28 11:10:05 2022] vinput: Unloading virtual input driver diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..2aac5c6 --- /dev/null +++ b/run.sh @@ -0,0 +1,18 @@ +cd ./Code +make +sudo insmod vinput.ko +sudo insmod vkbd.ko +echo "vkbd" | sudo tee /sys/class/vinput/export > ../output.txt +sleep 1 +echo "+36" | sudo tee /dev/vinput0 >> ../output.txt +sleep 5 +echo "-36" | sudo tee /dev/vinput0 >> ../output.txt +sleep 1 +echo "0" | sudo tee /sys/class/vinput/unexport >> ../output.txt +echo "==================================================" >> ../output.txt +sudo rmmod vkbd +sudo rmmod vinput +sleep 5 +cd ../ +sudo dmesg -T -l info | tail -8 >> output.txt +echo "Done" \ No newline at end of file