// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) "qti_virtio_mem: %s: " fmt, __func__ #include #include #include #include #include #include #include #include "qti_virtio_mem.h" struct qti_virtio_mem_hint { struct list_head list; struct kref kref; struct file *filp; s64 size; char name[QTI_VIRTIO_MEM_IOC_MAX_NAME_LEN]; }; #define QTI_VIRTIO_MEM_MAX_DEVS 1 static dev_t qvm_dev_no; static struct class *qvm_class; static struct cdev qvm_char_dev; /* Protects qvm_hint_total and qvm_list */ static DEFINE_MUTEX(qvm_lock); static LIST_HEAD(qvm_list); /* Sum of all hints */ static s64 qvm_hint_total; static int qti_virtio_mem_hint_update(struct qti_virtio_mem_hint *hint, s64 new_size, bool sync) { int ret; s64 total = 0; mutex_lock(&qvm_lock); total = qvm_hint_total + new_size - hint->size; ret = virtio_mem_update_config_size(total, sync); if (ret) { pr_debug("Hint %s: Invalid request %llx would result in %llx\n", hint->name, new_size, total); mutex_unlock(&qvm_lock); return ret; } hint->size = new_size; qvm_hint_total = total; pr_debug("Hint %s: Updated size %llx, new_requested_size %llx\n", hint->name, hint->size, qvm_hint_total); mutex_unlock(&qvm_lock); return ret; } static void qti_virtio_mem_hint_kref_release(struct kref *kref) { struct qti_virtio_mem_hint *hint; hint = container_of(kref, struct qti_virtio_mem_hint, kref); WARN_ON(qti_virtio_mem_hint_update(hint, 0, false)); mutex_lock(&qvm_lock); list_del(&hint->list); mutex_unlock(&qvm_lock); kfree(hint); } static void *qti_virtio_mem_hint_create(char *name, s64 size) { struct qti_virtio_mem_hint *hint; hint = kzalloc(sizeof(*hint), GFP_KERNEL); if (!hint) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&hint->list); kref_init(&hint->kref); hint->size = 0; if (!name || !strlen(name)) name = "(none)"; strscpy(hint->name, name, ARRAY_SIZE(hint->name)); if (qti_virtio_mem_hint_update(hint, size, true)) { kfree(hint); return ERR_PTR(-EINVAL); } mutex_lock(&qvm_lock); list_add(&hint->list, &qvm_list); mutex_unlock(&qvm_lock); return hint; } static void qti_virtio_mem_hint_release(void *handle) { struct qti_virtio_mem_hint *hint = handle; kref_put(&hint->kref, qti_virtio_mem_hint_kref_release); } static int qti_virtio_mem_hint_file_release(struct inode *inode, struct file *filp) { qti_virtio_mem_hint_release(filp->private_data); return 0; } static const struct file_operations qti_virtio_mem_hint_fops = { .release = qti_virtio_mem_hint_file_release, }; static int qti_virtio_mem_hint_create_fd(char *name, u64 size) { struct qti_virtio_mem_hint *hint; int fd; hint = qti_virtio_mem_hint_create(name, size); if (IS_ERR(hint)) return PTR_ERR(hint); hint->filp = anon_inode_getfile("virtio_mem_hint", &qti_virtio_mem_hint_fops, hint, O_RDWR); if (IS_ERR(hint->filp)) { int ret = PTR_ERR(hint->filp); qti_virtio_mem_hint_release(hint); return ret; } fd = get_unused_fd_flags(O_CLOEXEC); if (fd < 0) { fput(hint->filp); return fd; } fd_install(fd, hint->filp); return fd; } union qti_virtio_mem_ioc_arg { struct qti_virtio_mem_ioc_hint_create_arg hint_create; }; static int qti_virtio_mem_ioc_hint_create(struct qti_virtio_mem_ioc_hint_create_arg *arg) { int fd; /* Validate arguments */ if (arg->size <= 0 || arg->reserved0 || arg->reserved1) return -EINVAL; /* ensure name is null-terminated */ arg->name[QTI_VIRTIO_MEM_IOC_MAX_NAME_LEN - 1] = '\0'; fd = qti_virtio_mem_hint_create_fd(arg->name, arg->size); if (fd < 0) return fd; arg->fd = fd; return 0; } static long qti_virtio_mem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret; unsigned int dir = _IOC_DIR(cmd); union qti_virtio_mem_ioc_arg ioctl_arg; if (_IOC_SIZE(cmd) > sizeof(ioctl_arg)) return -EINVAL; if (copy_from_user(&ioctl_arg, (void __user *)arg, _IOC_SIZE(cmd))) return -EFAULT; if (!(dir & _IOC_WRITE)) memset(&ioctl_arg, 0, sizeof(ioctl_arg)); switch (cmd) { case QTI_VIRTIO_MEM_IOC_HINT_CREATE: { ret = qti_virtio_mem_ioc_hint_create(&ioctl_arg.hint_create); if (ret) return ret; break; } default: return -ENOTTY; } if (dir & _IOC_READ) { if (copy_to_user((void __user *)arg, &ioctl_arg, _IOC_SIZE(cmd))) return -EFAULT; } return 0; } static const struct file_operations qti_virtio_mem_dev_fops = { .unlocked_ioctl = qti_virtio_mem_ioctl, .compat_ioctl = compat_ptr_ioctl, }; static int __init qti_virtio_mem_init(void) { int ret; struct device *dev; ret = alloc_chrdev_region(&qvm_dev_no, 0, QTI_VIRTIO_MEM_MAX_DEVS, "qti_virtio_mem"); if (ret < 0) goto err_chrdev_region; qvm_class = class_create(THIS_MODULE, "qti_virtio_mem"); if (IS_ERR(qvm_class)) { ret = PTR_ERR(qvm_class); goto err_class_create; } cdev_init(&qvm_char_dev, &qti_virtio_mem_dev_fops); ret = cdev_add(&qvm_char_dev, qvm_dev_no, 1); if (ret < 0) goto err_cdev_add; dev = device_create(qvm_class, NULL, qvm_dev_no, NULL, "qti_virtio_mem"); if (IS_ERR(dev)) { ret = PTR_ERR(dev); goto err_dev_create; } return 0; err_dev_create: cdev_del(&qvm_char_dev); err_cdev_add: class_destroy(qvm_class); err_class_create: unregister_chrdev_region(qvm_dev_no, QTI_VIRTIO_MEM_MAX_DEVS); err_chrdev_region: return ret; } module_init(qti_virtio_mem_init); static void __exit qti_virtio_mem_exit(void) { WARN(!list_empty(&qvm_list), "Unloading module with nonzero hint objects\n"); device_destroy(qvm_class, qvm_dev_no); cdev_del(&qvm_char_dev); class_destroy(qvm_class); unregister_chrdev_region(qvm_dev_no, QTI_VIRTIO_MEM_MAX_DEVS); } module_exit(qti_virtio_mem_exit);