|  | // 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, | 
|  | }; |