| // 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"); | 
 |  | 
 | struct privcmd_buf_private { | 
 | 	struct mutex lock; | 
 | 	struct list_head list; | 
 | }; | 
 |  | 
 | 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; | 
 |  | 
 | 	list_del(&vma_priv->list); | 
 |  | 
 | 	for (i = 0; i < vma_priv->n_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)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	vma_priv = kzalloc(sizeof(*vma_priv) + count * sizeof(void *), | 
 | 			   GFP_KERNEL); | 
 | 	if (!vma_priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	for (i = 0; i < count; i++) { | 
 | 		vma_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); | 
 | 		if (!vma_priv->pages[i]) | 
 | 			break; | 
 | 		vma_priv->n_pages++; | 
 | 	} | 
 |  | 
 | 	mutex_lock(&file_priv->lock); | 
 |  | 
 | 	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, | 
 | }; |