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(size: sizeof(*file_priv), GFP_KERNEL); |
42 | if (!file_priv) |
43 | return -ENOMEM; |
44 | |
45 | mutex_init(&file_priv->lock); |
46 | INIT_LIST_HEAD(list: &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(entry: &vma_priv->list); |
58 | |
59 | for (i = 0; i < vma_priv->n_pages; i++) |
60 | __free_page(vma_priv->pages[i]); |
61 | |
62 | kfree(objp: 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(head: &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(lock: &file_priv->lock); |
80 | |
81 | kfree(objp: 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(lock: &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(lock: &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(struct_size(vma_priv, pages, count), GFP_KERNEL); |
144 | if (!vma_priv) |
145 | return -ENOMEM; |
146 | |
147 | for (i = 0; i < count; i++) { |
148 | vma_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); |
149 | if (!vma_priv->pages[i]) |
150 | break; |
151 | vma_priv->n_pages++; |
152 | } |
153 | |
154 | mutex_lock(&file_priv->lock); |
155 | |
156 | vma_priv->file_priv = file_priv; |
157 | vma_priv->users = 1; |
158 | |
159 | vm_flags_set(vma, VM_IO | VM_DONTEXPAND); |
160 | vma->vm_ops = &privcmd_buf_vm_ops; |
161 | vma->vm_private_data = vma_priv; |
162 | |
163 | list_add(new: &vma_priv->list, head: &file_priv->list); |
164 | |
165 | if (vma_priv->n_pages != count) |
166 | ret = -ENOMEM; |
167 | else |
168 | ret = vm_map_pages_zero(vma, pages: vma_priv->pages, |
169 | num: vma_priv->n_pages); |
170 | |
171 | if (ret) |
172 | privcmd_buf_vmapriv_free(vma_priv); |
173 | |
174 | mutex_unlock(lock: &file_priv->lock); |
175 | |
176 | return ret; |
177 | } |
178 | |
179 | const struct file_operations xen_privcmdbuf_fops = { |
180 | .owner = THIS_MODULE, |
181 | .open = privcmd_buf_open, |
182 | .release = privcmd_buf_release, |
183 | .mmap = privcmd_buf_mmap, |
184 | }; |
185 | EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops); |
186 | |
187 | struct miscdevice xen_privcmdbuf_dev = { |
188 | .minor = MISC_DYNAMIC_MINOR, |
189 | .name = "xen/hypercall" , |
190 | .fops = &xen_privcmdbuf_fops, |
191 | }; |
192 | |