1 // SPDX-License-Identifier: GPL-2.0 OR MIT 2 3 /****************************************************************************** 4 * privcmd-buf.c 5 * 6 * Mmap of hypercall buffers. 7 * 8 * Copyright (c) 2018 Juergen Gross 9 */ 10 11 #define pr_fmt(fmt) "xen:" KBUILD_MODNAME ": " fmt 12 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/list.h> 16 #include <linux/miscdevice.h> 17 #include <linux/mm.h> 18 #include <linux/slab.h> 19 20 #include "privcmd.h" 21 22 MODULE_LICENSE("GPL"); 23 24 struct privcmd_buf_private { 25 struct mutex lock; 26 struct list_head list; 27 }; 28 29 struct privcmd_buf_vma_private { 30 struct privcmd_buf_private *file_priv; 31 struct list_head list; 32 unsigned int users; 33 unsigned int n_pages; 34 struct page *pages[]; 35 }; 36 37 static int privcmd_buf_open(struct inode *ino, struct file *file) 38 { 39 struct privcmd_buf_private *file_priv; 40 41 file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); 42 if (!file_priv) 43 return -ENOMEM; 44 45 mutex_init(&file_priv->lock); 46 INIT_LIST_HEAD(&file_priv->list); 47 48 file->private_data = file_priv; 49 50 return 0; 51 } 52 53 static void privcmd_buf_vmapriv_free(struct privcmd_buf_vma_private *vma_priv) 54 { 55 unsigned int i; 56 57 list_del(&vma_priv->list); 58 59 for (i = 0; i < vma_priv->n_pages; i++) 60 __free_page(vma_priv->pages[i]); 61 62 kfree(vma_priv); 63 } 64 65 static int privcmd_buf_release(struct inode *ino, struct file *file) 66 { 67 struct privcmd_buf_private *file_priv = file->private_data; 68 struct privcmd_buf_vma_private *vma_priv; 69 70 mutex_lock(&file_priv->lock); 71 72 while (!list_empty(&file_priv->list)) { 73 vma_priv = list_first_entry(&file_priv->list, 74 struct privcmd_buf_vma_private, 75 list); 76 privcmd_buf_vmapriv_free(vma_priv); 77 } 78 79 mutex_unlock(&file_priv->lock); 80 81 kfree(file_priv); 82 83 return 0; 84 } 85 86 static void privcmd_buf_vma_open(struct vm_area_struct *vma) 87 { 88 struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; 89 90 if (!vma_priv) 91 return; 92 93 mutex_lock(&vma_priv->file_priv->lock); 94 vma_priv->users++; 95 mutex_unlock(&vma_priv->file_priv->lock); 96 } 97 98 static void privcmd_buf_vma_close(struct vm_area_struct *vma) 99 { 100 struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; 101 struct privcmd_buf_private *file_priv; 102 103 if (!vma_priv) 104 return; 105 106 file_priv = vma_priv->file_priv; 107 108 mutex_lock(&file_priv->lock); 109 110 vma_priv->users--; 111 if (!vma_priv->users) 112 privcmd_buf_vmapriv_free(vma_priv); 113 114 mutex_unlock(&file_priv->lock); 115 } 116 117 static vm_fault_t privcmd_buf_vma_fault(struct vm_fault *vmf) 118 { 119 pr_debug("fault: vma=%p %lx-%lx, pgoff=%lx, uv=%p\n", 120 vmf->vma, vmf->vma->vm_start, vmf->vma->vm_end, 121 vmf->pgoff, (void *)vmf->address); 122 123 return VM_FAULT_SIGBUS; 124 } 125 126 static const struct vm_operations_struct privcmd_buf_vm_ops = { 127 .open = privcmd_buf_vma_open, 128 .close = privcmd_buf_vma_close, 129 .fault = privcmd_buf_vma_fault, 130 }; 131 132 static int privcmd_buf_mmap(struct file *file, struct vm_area_struct *vma) 133 { 134 struct privcmd_buf_private *file_priv = file->private_data; 135 struct privcmd_buf_vma_private *vma_priv; 136 unsigned long count = vma_pages(vma); 137 unsigned int i; 138 int ret = 0; 139 140 if (!(vma->vm_flags & VM_SHARED)) 141 return -EINVAL; 142 143 vma_priv = kzalloc(sizeof(*vma_priv) + count * sizeof(void *), 144 GFP_KERNEL); 145 if (!vma_priv) 146 return -ENOMEM; 147 148 for (i = 0; i < count; i++) { 149 vma_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); 150 if (!vma_priv->pages[i]) 151 break; 152 vma_priv->n_pages++; 153 } 154 155 mutex_lock(&file_priv->lock); 156 157 vma_priv->file_priv = file_priv; 158 vma_priv->users = 1; 159 160 vma->vm_flags |= VM_IO | VM_DONTEXPAND; 161 vma->vm_ops = &privcmd_buf_vm_ops; 162 vma->vm_private_data = vma_priv; 163 164 list_add(&vma_priv->list, &file_priv->list); 165 166 if (vma_priv->n_pages != count) 167 ret = -ENOMEM; 168 else 169 for (i = 0; i < vma_priv->n_pages; i++) { 170 ret = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE, 171 vma_priv->pages[i]); 172 if (ret) 173 break; 174 } 175 176 if (ret) 177 privcmd_buf_vmapriv_free(vma_priv); 178 179 mutex_unlock(&file_priv->lock); 180 181 return ret; 182 } 183 184 const struct file_operations xen_privcmdbuf_fops = { 185 .owner = THIS_MODULE, 186 .open = privcmd_buf_open, 187 .release = privcmd_buf_release, 188 .mmap = privcmd_buf_mmap, 189 }; 190 EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops); 191 192 struct miscdevice xen_privcmdbuf_dev = { 193 .minor = MISC_DYNAMIC_MINOR, 194 .name = "xen/hypercall", 195 .fops = &xen_privcmdbuf_fops, 196 }; 197