mirror of
https://github.com/torvalds/linux.git
synced 2026-06-05 04:56:13 +02:00
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 <quic_pdaly@quicinc.com>
This commit is contained in:
parent
14575325bc
commit
385221af95
|
|
@ -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
|
||||
|
|
|
|||
259
drivers/virtio/qti_virtio_mem.c
Normal file
259
drivers/virtio/qti_virtio_mem.c
Normal file
|
|
@ -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 <linux/module.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/slab.h>
|
||||
#include <uapi/linux/qti_virtio_mem.h>
|
||||
#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);
|
||||
10
drivers/virtio/qti_virtio_mem.h
Normal file
10
drivers/virtio/qti_virtio_mem.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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" },
|
||||
{ },
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user