xref: /linux/drivers/xen/privcmd-buf.c (revision 4b132aacb0768ac1e652cf517097ea6f237214b9)
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_DESCRIPTION("Xen Mmap of hypercall buffers");
23 MODULE_LICENSE("GPL");
24 
25 struct privcmd_buf_private {
26 	struct mutex lock;
27 	struct list_head list;
28 };
29 
30 struct privcmd_buf_vma_private {
31 	struct privcmd_buf_private *file_priv;
32 	struct list_head list;
33 	unsigned int users;
34 	unsigned int n_pages;
35 	struct page *pages[];
36 };
37 
38 static int privcmd_buf_open(struct inode *ino, struct file *file)
39 {
40 	struct privcmd_buf_private *file_priv;
41 
42 	file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL);
43 	if (!file_priv)
44 		return -ENOMEM;
45 
46 	mutex_init(&file_priv->lock);
47 	INIT_LIST_HEAD(&file_priv->list);
48 
49 	file->private_data = file_priv;
50 
51 	return 0;
52 }
53 
54 static void privcmd_buf_vmapriv_free(struct privcmd_buf_vma_private *vma_priv)
55 {
56 	unsigned int i;
57 
58 	list_del(&vma_priv->list);
59 
60 	for (i = 0; i < vma_priv->n_pages; i++)
61 		__free_page(vma_priv->pages[i]);
62 
63 	kfree(vma_priv);
64 }
65 
66 static int privcmd_buf_release(struct inode *ino, struct file *file)
67 {
68 	struct privcmd_buf_private *file_priv = file->private_data;
69 	struct privcmd_buf_vma_private *vma_priv;
70 
71 	mutex_lock(&file_priv->lock);
72 
73 	while (!list_empty(&file_priv->list)) {
74 		vma_priv = list_first_entry(&file_priv->list,
75 					    struct privcmd_buf_vma_private,
76 					    list);
77 		privcmd_buf_vmapriv_free(vma_priv);
78 	}
79 
80 	mutex_unlock(&file_priv->lock);
81 
82 	kfree(file_priv);
83 
84 	return 0;
85 }
86 
87 static void privcmd_buf_vma_open(struct vm_area_struct *vma)
88 {
89 	struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data;
90 
91 	if (!vma_priv)
92 		return;
93 
94 	mutex_lock(&vma_priv->file_priv->lock);
95 	vma_priv->users++;
96 	mutex_unlock(&vma_priv->file_priv->lock);
97 }
98 
99 static void privcmd_buf_vma_close(struct vm_area_struct *vma)
100 {
101 	struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data;
102 	struct privcmd_buf_private *file_priv;
103 
104 	if (!vma_priv)
105 		return;
106 
107 	file_priv = vma_priv->file_priv;
108 
109 	mutex_lock(&file_priv->lock);
110 
111 	vma_priv->users--;
112 	if (!vma_priv->users)
113 		privcmd_buf_vmapriv_free(vma_priv);
114 
115 	mutex_unlock(&file_priv->lock);
116 }
117 
118 static vm_fault_t privcmd_buf_vma_fault(struct vm_fault *vmf)
119 {
120 	pr_debug("fault: vma=%p %lx-%lx, pgoff=%lx, uv=%p\n",
121 		 vmf->vma, vmf->vma->vm_start, vmf->vma->vm_end,
122 		 vmf->pgoff, (void *)vmf->address);
123 
124 	return VM_FAULT_SIGBUS;
125 }
126 
127 static const struct vm_operations_struct privcmd_buf_vm_ops = {
128 	.open = privcmd_buf_vma_open,
129 	.close = privcmd_buf_vma_close,
130 	.fault = privcmd_buf_vma_fault,
131 };
132 
133 static int privcmd_buf_mmap(struct file *file, struct vm_area_struct *vma)
134 {
135 	struct privcmd_buf_private *file_priv = file->private_data;
136 	struct privcmd_buf_vma_private *vma_priv;
137 	unsigned long count = vma_pages(vma);
138 	unsigned int i;
139 	int ret = 0;
140 
141 	if (!(vma->vm_flags & VM_SHARED))
142 		return -EINVAL;
143 
144 	vma_priv = kzalloc(struct_size(vma_priv, pages, count), 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 	vm_flags_set(vma, 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 		ret = vm_map_pages_zero(vma, vma_priv->pages,
170 						vma_priv->n_pages);
171 
172 	if (ret)
173 		privcmd_buf_vmapriv_free(vma_priv);
174 
175 	mutex_unlock(&file_priv->lock);
176 
177 	return ret;
178 }
179 
180 const struct file_operations xen_privcmdbuf_fops = {
181 	.owner = THIS_MODULE,
182 	.open = privcmd_buf_open,
183 	.release = privcmd_buf_release,
184 	.mmap = privcmd_buf_mmap,
185 };
186 EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops);
187 
188 struct miscdevice xen_privcmdbuf_dev = {
189 	.minor = MISC_DYNAMIC_MINOR,
190 	.name = "xen/hypercall",
191 	.fops = &xen_privcmdbuf_fops,
192 };
193