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 
privcmd_buf_open(struct inode * ino,struct file * file)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 
privcmd_buf_vmapriv_free(struct privcmd_buf_vma_private * vma_priv)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 
privcmd_buf_release(struct inode * ino,struct file * file)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 
privcmd_buf_vma_open(struct vm_area_struct * vma)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 
privcmd_buf_vma_close(struct vm_area_struct * vma)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 
privcmd_buf_vma_fault(struct vm_fault * vmf)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 
privcmd_buf_mmap(struct file * file,struct vm_area_struct * vma)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