mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
4.1 KiB
191 lines
4.1 KiB
// SPDX-License-Identifier: GPL-2.0 OR MIT |
|
|
|
/****************************************************************************** |
|
* privcmd-buf.c |
|
* |
|
* Mmap of hypercall buffers. |
|
* |
|
* Copyright (c) 2018 Juergen Gross |
|
*/ |
|
|
|
#define pr_fmt(fmt) "xen:" KBUILD_MODNAME ": " fmt |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/list.h> |
|
#include <linux/miscdevice.h> |
|
#include <linux/mm.h> |
|
#include <linux/slab.h> |
|
|
|
#include "privcmd.h" |
|
|
|
MODULE_LICENSE("GPL"); |
|
|
|
struct privcmd_buf_private { |
|
struct mutex lock; |
|
struct list_head list; |
|
}; |
|
|
|
struct privcmd_buf_vma_private { |
|
struct privcmd_buf_private *file_priv; |
|
struct list_head list; |
|
unsigned int users; |
|
unsigned int n_pages; |
|
struct page *pages[]; |
|
}; |
|
|
|
static int privcmd_buf_open(struct inode *ino, struct file *file) |
|
{ |
|
struct privcmd_buf_private *file_priv; |
|
|
|
file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); |
|
if (!file_priv) |
|
return -ENOMEM; |
|
|
|
mutex_init(&file_priv->lock); |
|
INIT_LIST_HEAD(&file_priv->list); |
|
|
|
file->private_data = file_priv; |
|
|
|
return 0; |
|
} |
|
|
|
static void privcmd_buf_vmapriv_free(struct privcmd_buf_vma_private *vma_priv) |
|
{ |
|
unsigned int i; |
|
|
|
list_del(&vma_priv->list); |
|
|
|
for (i = 0; i < vma_priv->n_pages; i++) |
|
__free_page(vma_priv->pages[i]); |
|
|
|
kfree(vma_priv); |
|
} |
|
|
|
static int privcmd_buf_release(struct inode *ino, struct file *file) |
|
{ |
|
struct privcmd_buf_private *file_priv = file->private_data; |
|
struct privcmd_buf_vma_private *vma_priv; |
|
|
|
mutex_lock(&file_priv->lock); |
|
|
|
while (!list_empty(&file_priv->list)) { |
|
vma_priv = list_first_entry(&file_priv->list, |
|
struct privcmd_buf_vma_private, |
|
list); |
|
privcmd_buf_vmapriv_free(vma_priv); |
|
} |
|
|
|
mutex_unlock(&file_priv->lock); |
|
|
|
kfree(file_priv); |
|
|
|
return 0; |
|
} |
|
|
|
static void privcmd_buf_vma_open(struct vm_area_struct *vma) |
|
{ |
|
struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; |
|
|
|
if (!vma_priv) |
|
return; |
|
|
|
mutex_lock(&vma_priv->file_priv->lock); |
|
vma_priv->users++; |
|
mutex_unlock(&vma_priv->file_priv->lock); |
|
} |
|
|
|
static void privcmd_buf_vma_close(struct vm_area_struct *vma) |
|
{ |
|
struct privcmd_buf_vma_private *vma_priv = vma->vm_private_data; |
|
struct privcmd_buf_private *file_priv; |
|
|
|
if (!vma_priv) |
|
return; |
|
|
|
file_priv = vma_priv->file_priv; |
|
|
|
mutex_lock(&file_priv->lock); |
|
|
|
vma_priv->users--; |
|
if (!vma_priv->users) |
|
privcmd_buf_vmapriv_free(vma_priv); |
|
|
|
mutex_unlock(&file_priv->lock); |
|
} |
|
|
|
static vm_fault_t privcmd_buf_vma_fault(struct vm_fault *vmf) |
|
{ |
|
pr_debug("fault: vma=%p %lx-%lx, pgoff=%lx, uv=%p\n", |
|
vmf->vma, vmf->vma->vm_start, vmf->vma->vm_end, |
|
vmf->pgoff, (void *)vmf->address); |
|
|
|
return VM_FAULT_SIGBUS; |
|
} |
|
|
|
static const struct vm_operations_struct privcmd_buf_vm_ops = { |
|
.open = privcmd_buf_vma_open, |
|
.close = privcmd_buf_vma_close, |
|
.fault = privcmd_buf_vma_fault, |
|
}; |
|
|
|
static int privcmd_buf_mmap(struct file *file, struct vm_area_struct *vma) |
|
{ |
|
struct privcmd_buf_private *file_priv = file->private_data; |
|
struct privcmd_buf_vma_private *vma_priv; |
|
unsigned long count = vma_pages(vma); |
|
unsigned int i; |
|
int ret = 0; |
|
|
|
if (!(vma->vm_flags & VM_SHARED)) |
|
return -EINVAL; |
|
|
|
vma_priv = kzalloc(struct_size(vma_priv, pages, count), GFP_KERNEL); |
|
if (!vma_priv) |
|
return -ENOMEM; |
|
|
|
for (i = 0; i < count; i++) { |
|
vma_priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); |
|
if (!vma_priv->pages[i]) |
|
break; |
|
vma_priv->n_pages++; |
|
} |
|
|
|
mutex_lock(&file_priv->lock); |
|
|
|
vma_priv->file_priv = file_priv; |
|
vma_priv->users = 1; |
|
|
|
vma->vm_flags |= VM_IO | VM_DONTEXPAND; |
|
vma->vm_ops = &privcmd_buf_vm_ops; |
|
vma->vm_private_data = vma_priv; |
|
|
|
list_add(&vma_priv->list, &file_priv->list); |
|
|
|
if (vma_priv->n_pages != count) |
|
ret = -ENOMEM; |
|
else |
|
ret = vm_map_pages_zero(vma, vma_priv->pages, |
|
vma_priv->n_pages); |
|
|
|
if (ret) |
|
privcmd_buf_vmapriv_free(vma_priv); |
|
|
|
mutex_unlock(&file_priv->lock); |
|
|
|
return ret; |
|
} |
|
|
|
const struct file_operations xen_privcmdbuf_fops = { |
|
.owner = THIS_MODULE, |
|
.open = privcmd_buf_open, |
|
.release = privcmd_buf_release, |
|
.mmap = privcmd_buf_mmap, |
|
}; |
|
EXPORT_SYMBOL_GPL(xen_privcmdbuf_fops); |
|
|
|
struct miscdevice xen_privcmdbuf_dev = { |
|
.minor = MISC_DYNAMIC_MINOR, |
|
.name = "xen/hypercall", |
|
.fops = &xen_privcmdbuf_fops, |
|
};
|
|
|