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
22MODULE_LICENSE("GPL");
23
24struct privcmd_buf_private {
25 struct mutex lock;
26 struct list_head list;
27};
28
29struct 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
37static 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
53static 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
65static 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
86static 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
98static 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
117static 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
126static 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
132static 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
184const 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};
190EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops);
191
192struct miscdevice xen_privcmdbuf_dev = {
193 .minor = MISC_DYNAMIC_MINOR,
194 .name = "xen/hypercall",
195 .fops = &xen_privcmdbuf_fops,
196};
197