diff --git a/.ci/non-working b/.ci/non-working index 4635711..1826ab6 100644 --- a/.ci/non-working +++ b/.ci/non-working @@ -1,2 +1,3 @@ bottomhalf intrpt +vkbd diff --git a/.mailmap b/.mailmap index f53af0f..9d3dc85 100644 --- a/.mailmap +++ b/.mailmap @@ -5,4 +5,5 @@ Jim Huang Jim Huang Jim Huang Jim Huang linD026 linD026 <66012716+linD026@users.noreply.github.com> linD026 linD026 <0086d026@email.ntou.edu.tw> -linD026 linzhien <0086d026@email.ntou.edu.tw> \ No newline at end of file +linD026 linzhien <0086d026@email.ntou.edu.tw> +mengxinayan <31788564+mengxinayan@users.noreply.github.com> 萌新阿岩 <31788564+mengxinayan@users.noreply.github.com> diff --git a/contrib.tex b/contrib.tex index 5ee243f..345fb32 100644 --- a/contrib.tex +++ b/contrib.tex @@ -3,9 +3,11 @@ Arush Sharma, % <46960231+arushsharma24@users.noreply.github.com> asas1asas200, % Benno Bielmeier, % <32938211+bbenno@users.noreply.github.com> +Bob Lee, % Brad Baker, % ccs100203, % Chih-Yu Chen, % <34228283+chihyu1206@users.noreply.github.com> +Ching-Hua (Vivian) Lin, % ChinYikMing, % Cyril Brulebois, % Daniele Paolo Scarpazza, % <> @@ -22,10 +24,15 @@ Hsin-Hsiang Peng, % Ignacio Martin, % <> JianXing Wu, % linD026, % +lyctw, % +manbing, % Marconi Jiang, % +mengxinayan, % <31788564+mengxinayan@users.noreply.github.com> RinHizakura, % Roman Lakeev, % <> Stacy Prowell, % +Steven Lung, % <1030steven@gmail.com> +Tristan Lelong, % Tucker Polomik, % VxTeemo, % Wei-Lun Tsai, % diff --git a/examples/.clang-format b/examples/.clang-format index 9e4fbaa..e1e184f 100644 --- a/examples/.clang-format +++ b/examples/.clang-format @@ -56,6 +56,10 @@ DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false +ForEachMacros: + - 'list_for_each' + - 'list_for_each_safe' + IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' diff --git a/examples/Makefile b/examples/Makefile index 2c2a125..65f933e 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -29,6 +29,8 @@ obj-m += example_atomic.o obj-m += example_mutex.o obj-m += bottomhalf.o obj-m += ioctl.o +obj-m += vinput.o +obj-m += vkbd.o PWD := $(CURDIR) diff --git a/examples/vinput.c b/examples/vinput.c new file mode 100644 index 0000000..75e2a76 --- /dev/null +++ b/examples/vinput.c @@ -0,0 +1,398 @@ +#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"); + 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/examples/vinput.h b/examples/vinput.h new file mode 100644 index 0000000..394b061 --- /dev/null +++ b/examples/vinput.h @@ -0,0 +1,45 @@ +#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/examples/vkbd.c b/examples/vkbd.c new file mode 100644 index 0000000..7b1a6a0 --- /dev/null +++ b/examples/vkbd.c @@ -0,0 +1,106 @@ +#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/lkmpg.tex b/lkmpg.tex index c216afb..51fa31b 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -1857,6 +1857,88 @@ Here is an example of symmetrically encrypting a string using the AES algorithm \samplec{examples/cryptosk.c} +\section{Virtual Input Device Driver} +\label{sec:vinput} +The input device driver is a module that provides a way to communicate with the interaction device via the event. +For example, the keyboard can send the press or release event to tell the kernel what we want to do. +The input device driver will allocate a new input structure with \cpp|input_allocate_device()| and sets up input bitfields, device id, version, etc. +After that, registers it by calling \cpp|input_register_device()|. + +Here is an example, vinput, +It is an API to allow easy development of virtual input drivers. +The drivers needs to export a \cpp|vinput_device()| that contains the virtual device name and \cpp|vinput_ops| structure that describes: + +\begin{itemize} + \item the init function: \cpp|init()| + \item the input event injection function: \cpp|send()| + \item the readback function: \cpp|read()| +\end{itemize} + +Then using \cpp|vinput_register_device()| and \cpp|vinput_unregister_device()| will add a new device to the list of support virtual input devices. + +\begin{code} +int init(struct vinput *); +\end{code} + +This function is passed a \cpp|struct vinput| already initialized with an allocated \cpp|struct input_dev|. +The \cpp|init()| function is responsible for initializing the capabilities of the input device and register it. + +\begin{code} +int send(struct vinput *, char *, int); +\end{code} + +This function will receive a user string to interpret and inject the event using the \cpp|input_report_XXXX| or \cpp|input_event| call. +The string is already copied from user. + +\begin{code} +int read(struct vinput *, char *, int); +\end{code} + +This function is used for debugging and should fill the buffer parameter with the last event sent in the virtual input device format. +The buffer will then be copied to user. + +vinput devices are created and destroyed using sysfs. +And, event injection is done through a \verb|/dev| node. +The device name will be used by the userland to export a new virtual input device. +To create a \verb|vinputX| sysfs entry and \verb|/dev| node. + +\begin{codebash} +echo "vkbd" | sudo tee /sys/class/vinput/export +\end{codebash} + +To unexport the device, just echo its id in unexport: + +\begin{codebash} +echo "0" | sudo tee /sys/class/vinput/unexport +\end{codebash} + +\samplec{examples/vinput.h} +\samplec{examples/vinput.c} + +Here the virtual keyboard is one of example to use vinput. +It supports all \cpp|KEY_MAX| keycodes. +The injection format is the \cpp|KEY_CODE| such as defined in \src{include/linux/input.h}. +A positive value means \cpp|KEY_PRESS| while a negative value is a \cpp|KEY_RELEASE|. +The keyboard supports repetition when the key stays pressed for too long. +The following demonstrates how simulation work. + +Simulate a key press on "g" (\cpp|KEY_G| = 34): + +\begin{codebash} +echo "+34" | sudo tee /dev/vinput0 +\end{codebash} + +Simulate a key release on "g" (\cpp|KEY_G| = 34:) + +\begin{codebash} +echo "-34" | sudo tee /dev/vinput0 +\end{codebash} + +\samplec{examples/vkbd.c} + +% TODO: Add description of attribute +% TODO: Add vts.c and vmouse.c example + \section{Standardizing the interfaces: The Device Model} \label{sec:device_model} Up to this point we have seen all kinds of modules doing all kinds of things, but there was no consistency in their interfaces with the rest of the kernel. diff --git a/scripts/Contributors b/scripts/Contributors index 5968ebe..fa00ec1 100644 --- a/scripts/Contributors +++ b/scripts/Contributors @@ -3,9 +3,11 @@ Arush Sharma,<46960231+arushsharma24@users.noreply.github.com> asas1asas200, Benno Bielmeier,<32938211+bbenno@users.noreply.github.com> +Bob Lee, Brad Baker, ccs100203, Chih-Yu Chen,<34228283+chihyu1206@users.noreply.github.com> +Ching-Hua (Vivian) Lin, ChinYikMing, Cyril Brulebois, Daniele Paolo Scarpazza,<> @@ -22,10 +24,15 @@ Hsin-Hsiang Peng, Ignacio Martin,<> JianXing Wu, linD026,,<0086d026@email.ntou.edu.tw>,<66012716+linD026@users.noreply.github.com> +lyctw, +manbing, Marconi Jiang, +mengxinayan,<31788564+mengxinayan@users.noreply.github.com> RinHizakura, Roman Lakeev,<> Stacy Prowell, +Steven Lung,<1030steven@gmail.com> +Tristan Lelong, Tucker Polomik, VxTeemo, Wei-Lun Tsai, diff --git a/scripts/Include b/scripts/Include index bc0cc00..b54c0ce 100644 --- a/scripts/Include +++ b/scripts/Include @@ -4,4 +4,5 @@ Dimo Velev,<> Francois Audeon,<> Horst Schirmeier,<> Ignacio Martin,<> -Roman Lakeev,<> \ No newline at end of file +Roman Lakeev,<> +Tristan Lelong,