Rtwo/kernel/motorola/sm8550/drivers/virtio/qti_virtio_mem.c

260 lines
5.7 KiB
C
Raw Normal View History

2025-09-30 20:22:48 -04:00
// 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);