From 385221af9546cb97b07f09c225dd1943deac7cc4 Mon Sep 17 00:00:00 2001 From: Patrick Daly Date: Fri, 24 Jun 2022 15:27:50 -0700 Subject: [PATCH] virtio/virtio_mem: Add ioctl interface Allow userspace to request additional memory by calling an ioctl on qti_virtio_mem char device. Change-Id: I47e9fa27f23620af209675928c2b5788a7d81477 Signed-off-by: Patrick Daly --- drivers/virtio/Makefile | 2 +- drivers/virtio/qti_virtio_mem.c | 259 ++++++++++++++++++++++++++++++++ drivers/virtio/qti_virtio_mem.h | 10 ++ drivers/virtio/virtio_mem.c | 35 +++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 drivers/virtio/qti_virtio_mem.c create mode 100644 drivers/virtio/qti_virtio_mem.h diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 8e98d24917cc..3abb65aabe5c 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -10,5 +10,5 @@ virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o obj-$(CONFIG_VIRTIO_VDPA) += virtio_vdpa.o -obj-$(CONFIG_VIRTIO_MEM) += virtio_mem.o +obj-$(CONFIG_VIRTIO_MEM) += virtio_mem.o qti_virtio_mem.o obj-$(CONFIG_VIRTIO_DMA_SHARED_BUFFER) += virtio_dma_buf.o diff --git a/drivers/virtio/qti_virtio_mem.c b/drivers/virtio/qti_virtio_mem.c new file mode 100644 index 000000000000..e9b76cdef699 --- /dev/null +++ b/drivers/virtio/qti_virtio_mem.c @@ -0,0 +1,259 @@ +// 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); diff --git a/drivers/virtio/qti_virtio_mem.h b/drivers/virtio/qti_virtio_mem.h new file mode 100644 index 000000000000..6013e456c76e --- /dev/null +++ b/drivers/virtio/qti_virtio_mem.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#ifndef QTI_VIRTIO_MEM_PRIVATE_H +#define QTI_VIRTIO_MEM_PRIVATE_H + +int virtio_mem_update_config_size(s64 size, bool sync); + +#endif diff --git a/drivers/virtio/virtio_mem.c b/drivers/virtio/virtio_mem.c index 3f918fc0c8ed..01a8f20fb0ce 100644 --- a/drivers/virtio/virtio_mem.c +++ b/drivers/virtio/virtio_mem.c @@ -265,6 +265,9 @@ struct virtio_mem { struct list_head next; }; +/* For now, only allow one virtio-mem device */ +static struct virtio_mem *virtio_mem_dev; + /* * We have to share a single online_page callback among all virtio-mem * devices. We use RCU to iterate the list in the callback. @@ -2677,6 +2680,8 @@ static int virtio_mem_probe(struct platform_device *vdev) if (rc) goto out_free_vm; + virtio_mem_dev = vm; + /* trigger a config update to start processing the requested_size */ if (!vm->in_kdump) { atomic_set(&vm->config_changed, 1); @@ -2790,6 +2795,36 @@ static void __maybe_unused virtio_mem_config_changed(struct platform_device *vde virtio_mem_retry(vm); } +int virtio_mem_update_config_size(s64 size, bool sync) +{ + unsigned long flags; + struct virtio_mem *vm = virtio_mem_dev; + + /* In future, may support multiple virtio_mem_devices for different zones */ + if (!vm) + return -EINVAL; + + /* Round up if request not properly aligned. */ + if (vm->in_sbm) + size = ALIGN(size, vm->sbm.sb_size); + else + size = ALIGN(size, vm->bbm.bb_size); + + if (size < 0 || size > vm->region_size) + return -EINVAL; + + spin_lock_irqsave(&vm->config_lock, flags); + vm->new_requested_size = size; + spin_unlock_irqrestore(&vm->config_lock, flags); + + virtio_mem_config_changed(vm->vdev); + + if (sync) + flush_work(&vm->wq); + + return 0; +} + static const struct of_device_id virtio_mem_id_table[] = { { .compatible = "qcom,virtio-mem" }, { },