linux/drivers/xen/privcmd-buf.c
<<
>>
Prefs
   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