lkmpg/examples/vinput.c
Bob Lee 9b89d0ca04
Fix incorrect failure handling in vinput_init
After class_register failure, the wrong function class_unregister was
used. The correct cleanup function should be unregister_chrdev, as
register_chrdev was used before class_register.
2024-10-21 00:33:06 +08:00

422 lines
10 KiB
C

/*
* vinput.c
*/
#include <linux/cdev.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#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))
return -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 = {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
.owner = THIS_MODULE,
#endif
.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);
if (!vinput) {
pr_err("vinput: Cannot allocate vinput input device\n");
return ERR_PTR(-ENOMEM);
}
try_module_get(THIS_MODULE);
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;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
static ssize_t export_store(const struct class *class,
const struct class_attribute *attr,
#else
static ssize_t export_store(struct class *class, struct class_attribute *attr,
#endif
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);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
static ssize_t unexport_store(const struct class *class,
const struct class_attribute *attr,
#else
static ssize_t unexport_store(struct class *class, struct class_attribute *attr,
#endif
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",
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
.owner = THIS_MODULE,
#endif
.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:
unregister_chrdev(vinput_dev, DRIVER_NAME);
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");