diff options
Diffstat (limited to 'drivers/xen/privcmd-buf.c')
| -rw-r--r-- | drivers/xen/privcmd-buf.c | 210 | 
1 files changed, 210 insertions, 0 deletions
diff --git a/drivers/xen/privcmd-buf.c b/drivers/xen/privcmd-buf.c new file mode 100644 index 000000000000..df1ed37c3269 --- /dev/null +++ b/drivers/xen/privcmd-buf.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +/****************************************************************************** + * privcmd-buf.c + * + * Mmap of hypercall buffers. + * + * Copyright (c) 2018 Juergen Gross + */ + +#define pr_fmt(fmt) "xen:" KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include "privcmd.h" + +MODULE_LICENSE("GPL"); + +static unsigned int limit = 64; +module_param(limit, uint, 0644); +MODULE_PARM_DESC(limit, "Maximum number of pages that may be allocated by " +			"the privcmd-buf device per open file"); + +struct privcmd_buf_private { +	struct mutex lock; +	struct list_head list; +	unsigned int allocated; +}; + +struct privcmd_buf_vma_private { +	struct privcmd_buf_private *file_priv; +	struct list_head list; +	unsigned int users; +	unsigned int n_pages; +	struct page *pages[]; +}; + +static int privcmd_buf_open(struct inode *ino, struct file *file) +{ +	struct privcmd_buf_private *file_priv; + +	file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); +	if (!file_priv) +		return -ENOMEM; + +	mutex_init(&file_priv->lock); +	INIT_LIST_HEAD(&file_priv->list); + +	file->private_data = file_priv; + +	return 0; +} + +static void privcmd_buf_vmapriv_free(struct privcmd_buf_vma_private *vma_priv) +{ +	unsigned int i; + +	vma_priv->file_priv->allocated -= vma_priv->n_pages; + +	list_del(&vma_priv->list); + +	for (i = 0; i < vma_priv->n_pages; i++) +		if (vma_priv->pages[i]) +			__free_page(vma_priv->pages[i]); + +	kfree(vma_priv); +} + +static int privcmd_buf_release(struct inode *ino, struct file *file) +{ +	struct privcmd_buf_private *file_priv = file->private_data; +	struct privcmd_buf_vma_private *vma_priv; + +	mutex_lock(&file_priv->lock); + +	while (!list_empty(&file_priv->list)) { +		vma_priv = list_first_entry(&file_priv->list, +					    struct privcmd_buf_vma_private, +					    list); +		privcmd_buf_vmapriv_free(vma_priv); +	} + +	mutex_unlock(&file_priv->lock); + +	kfree(file_priv); + +	return 0; +} + +static void privcmd_buf_vma_open(struct vm_area_struct *vma) +{ +	struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; + +	if (!vma_priv) +		return; + +	mutex_lock(&vma_priv->file_priv->lock); +	vma_priv->users++; +	mutex_unlock(&vma_priv->file_priv->lock); +} + +static void privcmd_buf_vma_close(struct vm_area_struct *vma) +{ +	struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; +	struct privcmd_buf_private *file_priv; + +	if (!vma_priv) +		return; + +	file_priv = vma_priv->file_priv; + +	mutex_lock(&file_priv->lock); + +	vma_priv->users--; +	if (!vma_priv->users) +		privcmd_buf_vmapriv_free(vma_priv); + +	mutex_unlock(&file_priv->lock); +} + +static vm_fault_t privcmd_buf_vma_fault(struct vm_fault *vmf) +{ +	pr_debug("fault: vma=%p %lx-%lx, pgoff=%lx, uv=%p\n", +		 vmf->vma, vmf->vma->vm_start, vmf->vma->vm_end, +		 vmf->pgoff, (void *)vmf->address); + +	return VM_FAULT_SIGBUS; +} + +static const struct vm_operations_struct privcmd_buf_vm_ops = { +	.open = privcmd_buf_vma_open, +	.close = privcmd_buf_vma_close, +	.fault = privcmd_buf_vma_fault, +}; + +static int privcmd_buf_mmap(struct file *file, struct vm_area_struct *vma) +{ +	struct privcmd_buf_private *file_priv = file->private_data; +	struct privcmd_buf_vma_private *vma_priv; +	unsigned long count = vma_pages(vma); +	unsigned int i; +	int ret = 0; + +	if (!(vma->vm_flags & VM_SHARED) || count > limit || +	    file_priv->allocated + count > limit) +		return -EINVAL; + +	vma_priv = kzalloc(sizeof(*vma_priv) + count * sizeof(void *), +			   GFP_KERNEL); +	if (!vma_priv) +		return -ENOMEM; + +	vma_priv->n_pages = count; +	count = 0; +	for (i = 0; i < vma_priv->n_pages; i++) { +		vma_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); +		if (!vma_priv->pages[i]) +			break; +		count++; +	} + +	mutex_lock(&file_priv->lock); + +	file_priv->allocated += count; + +	vma_priv->file_priv = file_priv; +	vma_priv->users = 1; + +	vma->vm_flags |= VM_IO | VM_DONTEXPAND; +	vma->vm_ops = &privcmd_buf_vm_ops; +	vma->vm_private_data = vma_priv; + +	list_add(&vma_priv->list, &file_priv->list); + +	if (vma_priv->n_pages != count) +		ret = -ENOMEM; +	else +		for (i = 0; i < vma_priv->n_pages; i++) { +			ret = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE, +					     vma_priv->pages[i]); +			if (ret) +				break; +		} + +	if (ret) +		privcmd_buf_vmapriv_free(vma_priv); + +	mutex_unlock(&file_priv->lock); + +	return ret; +} + +const struct file_operations xen_privcmdbuf_fops = { +	.owner = THIS_MODULE, +	.open = privcmd_buf_open, +	.release = privcmd_buf_release, +	.mmap = privcmd_buf_mmap, +}; +EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops); + +struct miscdevice xen_privcmdbuf_dev = { +	.minor = MISC_DYNAMIC_MINOR, +	.name = "xen/hypercall", +	.fops = &xen_privcmdbuf_fops, +};  | 
