blob: f2208c7dac84c4263aa976fd306c66c8a57f3c64 [file]
/* SPDX-License-Identifier: GPL-2.0-only */
/* wrapfd.c
*
* Wrapfd
*
* Copyright (C) 2025 Google, Inc.
*/
#include <linux/anon_inodes.h>
#include <linux/bvec.h>
#include <linux/dma-buf.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/pagemap.h>
#include <linux/scatterlist.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/uio.h>
#include <linux/wrapfd.h>
#include <uapi/linux/wrapfd.h>
struct wrap_ctx;
struct wrap_content;
static const struct file_operations wrap_fops;
struct wrap_content_operations {
int (*create_wrap)(struct wrap_content *content, struct wrap_ctx *ctx);
int (*load)(struct wrap_content *content, struct file *file,
loff_t file_offs, loff_t buf_offs, loff_t len);
int (*mmap_prepare)(struct wrap_content *content,
struct vm_area_struct *vma);
int (*mmap)(struct wrap_content *content, struct vm_area_struct *vma);
void (*free)(struct wrap_content *content);
struct wrap_content *(*make_writable)(struct wrap_content *content,
bool writable);
bool (*is_writable)(struct wrap_content *content);
void (*show_fdinfo)(struct wrap_content *content, struct seq_file *m);
int (*get_mappable)(struct wrap_content *content, struct device *dev,
union wrapfd_mappable *mappable);
void (*put_mappable)(struct wrap_content *content,
union wrapfd_mappable *mappable);
int (*ioctl)(struct wrap_content *content,
unsigned int cmd, unsigned long arg);
};
/* Abstract wrap content to be embedded in a concrete content object. */
struct wrap_content {
struct wrap_content_operations *ops;
bool close_on_exec;
};
/* dmabuf content */
struct wrap_content_dmabuf {
struct wrap_content content;
struct dma_buf *dmabuf;
bool writable;
};
static int dmabuf_content_create_wrap(struct wrap_content *content,
struct wrap_ctx *ctx)
{
struct wrap_content_dmabuf *dmabuf_content;
struct file *file;
int fd;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
fd = get_unused_fd_flags(0);
if (fd < 0)
return fd;
file = anon_inode_create_getfile("[wrapfd]", &wrap_fops, ctx,
dmabuf_content->writable ? O_RDWR : O_RDONLY, NULL);
if (IS_ERR(file)) {
put_unused_fd(fd);
return PTR_ERR(file);
}
/*
* Anonymous inodes are created with size == 0. To ensure that calls
* like fstat() work as expected, copy the size from the buffer we are
* wrapping.
*/
i_size_write(file_inode(file), dmabuf_content->dmabuf->size);
fd_install(fd, file);
return fd;
}
static int dmabuf_content_load(struct wrap_content *content, struct file *file,
loff_t file_offs, loff_t buf_offs, loff_t len)
{
struct wrap_content_dmabuf *dmabuf_content;
struct iosys_map map;
struct iov_iter iter;
struct kiocb kiocb;
loff_t bytes_read;
struct kvec iov;
loff_t end;
int ret;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
if (check_add_overflow(buf_offs, len, &end))
return -EINVAL;
if (end > dmabuf_content->dmabuf->size)
return -EINVAL;
ret = dma_buf_begin_cpu_access(dmabuf_content->dmabuf,
DMA_BIDIRECTIONAL);
if (ret)
return ret;
ret = dma_buf_vmap(dmabuf_content->dmabuf, &map);
if (ret)
goto err_end_access;
if (map.is_iomem) {
ret = -EINVAL;
goto err_unmap;
}
iov.iov_base = (u8 *)map.vaddr + buf_offs;
init_sync_kiocb(&kiocb, file);
kiocb.ki_pos = file_offs;
kiocb.ki_flags |= IOCB_DIRECT;
while (len) {
loff_t count = min_t(loff_t, MAX_RW_COUNT, len);
iov.iov_len = count;
iov_iter_kvec(&iter, ITER_DEST, &iov, 1, iov.iov_len);
bytes_read = 0;
while (bytes_read < count) {
ssize_t sz = vfs_iocb_iter_read(file, &kiocb, &iter);
if (sz <= 0) {
ret = sz;
goto err_unmap;
}
bytes_read += sz;
}
iov.iov_base += count;
len -= count;
}
err_unmap:
dma_buf_vunmap(dmabuf_content->dmabuf, &map);
err_end_access:
dma_buf_end_cpu_access(dmabuf_content->dmabuf, DMA_BIDIRECTIONAL);
if (ret < 0)
return ret;
if (len)
return -EINVAL; /* File was too short / early EOF */
return 0;
}
static struct wrap_content *
dmabuf_content_make_writable(struct wrap_content *content, bool writable)
{
struct wrap_content_dmabuf *dmabuf_content;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
dmabuf_content->writable = writable;
return content;
}
static bool dmabuf_content_is_writable(struct wrap_content *content)
{
struct wrap_content_dmabuf *dmabuf_content;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
return dmabuf_content->writable;
}
static int dmabuf_content_mmap(struct wrap_content *content,
struct vm_area_struct *vma)
{
struct wrap_content_dmabuf *dmabuf_content;
int ret;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
ret = dma_buf_mmap(dmabuf_content->dmabuf, vma, 0);
if (ret)
return ret;
return 0;
}
static void dmabuf_content_free(struct wrap_content *content)
{
struct wrap_content_dmabuf *dmabuf_content;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
if (dmabuf_content->dmabuf)
dma_buf_put(dmabuf_content->dmabuf);
kfree(dmabuf_content);
}
static void dmabuf_content_show_fdinfo(struct wrap_content *content,
struct seq_file *m)
{
struct wrap_content_dmabuf *dmabuf_content;
struct dma_buf *dmabuf;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
seq_printf(m, "type:\tdmabuf\n");
dmabuf = dmabuf_content->dmabuf;
seq_printf(m, "size:\t%zu\n", dmabuf->size);
/* Don't count the temporary reference taken inside procfs seq_show */
seq_printf(m, "count:\t%ld\n", file_count(dmabuf->file) - 1);
seq_printf(m, "exp_name:\t%s\n", dmabuf->exp_name);
spin_lock(&dmabuf->name_lock);
if (dmabuf->name)
seq_printf(m, "name:\t%s\n", dmabuf->name);
spin_unlock(&dmabuf->name_lock);
}
static int
dmabuf_content_get_mappable(struct wrap_content *content, struct device *dev,
union wrapfd_mappable *mappable)
{
struct wrap_content_dmabuf *dmabuf_content;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
mappable->dmabuf = dmabuf_content->dmabuf;
return 0;
}
static int dmabuf_content_ioctl(struct wrap_content *content,
unsigned int cmd, unsigned long arg)
{
struct wrap_content_dmabuf *dmabuf_content;
struct file *file;
dmabuf_content = container_of(content, struct wrap_content_dmabuf,
content);
file = dmabuf_content->dmabuf->file;
return file->f_op->unlocked_ioctl(file, cmd, arg);
}
static struct wrap_content_operations dmabuf_content_ops = {
.create_wrap = dmabuf_content_create_wrap,
.load = dmabuf_content_load,
.mmap = dmabuf_content_mmap,
.make_writable = dmabuf_content_make_writable,
.is_writable = dmabuf_content_is_writable,
.free = dmabuf_content_free,
.show_fdinfo = dmabuf_content_show_fdinfo,
.get_mappable = dmabuf_content_get_mappable,
.ioctl = dmabuf_content_ioctl,
};
static struct wrap_content *alloc_dmabuf_content(struct dma_buf *dmabuf,
bool writable)
{
struct wrap_content_dmabuf *dmabuf_content;
dmabuf_content = kmalloc(sizeof(*dmabuf_content), GFP_KERNEL);
if (!dmabuf_content)
return NULL;
get_dma_buf(dmabuf);
dmabuf_content->dmabuf = dmabuf;
dmabuf_content->writable = writable;
dmabuf_content->content.ops = &dmabuf_content_ops;
return &dmabuf_content->content;
}
/* Generic wrapfd */
struct wrap_owner {
struct task_struct *task;
struct device *dev;
};
struct wrap_ctx_mapping {
refcount_t refcnt;
struct wrap_ctx *ctx;
const struct vm_operations_struct *content_vm_ops;
struct vm_operations_struct vm_ops;
};
#define OP_BLOCKED_MODIFICATION BIT(0)
#define OP_BLOCKED_MAPPING BIT(1)
struct wrap_ctx {
struct wrap_content *content;
spinlock_t lock; /* protects all fields below */
struct wrap_owner owner;
bool allow_guests;
unsigned long map_count;
/*
* Mask of blocked operations when lock is not held due to possiblity
* of sleep during the ongoing operation.
*/
unsigned int block_mask;
};
static struct wrap_ctx *create_wrap_ctx(void)
{
struct wrap_ctx *ctx;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return NULL;
spin_lock_init(&ctx->lock);
return ctx;
}
static inline bool is_owner(struct wrap_ctx *ctx)
{
assert_spin_locked(&ctx->lock);
return ctx->owner.task || ctx->owner.dev;
}
static inline bool is_owner_task(struct wrap_ctx *ctx,
struct task_struct *task)
{
assert_spin_locked(&ctx->lock);
return ctx->owner.task && ctx->owner.task->mm == task->mm;
}
static inline bool is_owner_dev(struct wrap_ctx *ctx,
struct device *dev)
{
assert_spin_locked(&ctx->lock);
return ctx->owner.dev == dev;
}
static inline int publish_wrap(struct wrap_ctx *ctx,
struct wrap_content *content)
{
int ret;
ctx->content = content;
ret = content->ops->create_wrap(content, ctx);
/* Set FD_CLOEXEC flag for wrapfd the same as its content */
if (ret >= 0)
set_close_on_exec(ret, content->close_on_exec ? 1 : 0);
return ret;
}
static int can_access(struct wrap_ctx *ctx, struct task_struct *task,
bool check_content)
{
assert_spin_locked(&ctx->lock);
if (!is_owner_task(ctx, task))
return -EBUSY;
if (ctx->map_count > 0)
return -EINVAL;
if (check_content && !ctx->content)
return -ENOENT;
return 0;
}
static bool can_map(struct wrap_ctx *ctx)
{
assert_spin_locked(&ctx->lock);
return (ctx->block_mask & OP_BLOCKED_MAPPING) == 0;
}
static int block_operations(struct wrap_ctx *ctx, unsigned int mask)
{
int ret;
assert_spin_locked(&ctx->lock);
/* Any request should at least block modifications. */
if (WARN_ON((mask & OP_BLOCKED_MODIFICATION) == 0))
return -EINVAL;
ret = can_access(ctx, current, true);
if (ret)
return ret;
/*
* The task is the owner, the content can't be modified by other
* processes but racing threads of the owner process can still
* modify it. Use block_mask bitmask to prevent that.
*/
if (ctx->block_mask & mask)
return -EAGAIN;
ctx->block_mask |= mask;
return 0;
}
static void unblock_operations(struct wrap_ctx *ctx)
{
assert_spin_locked(&ctx->lock);
if (WARN_ON(!ctx->block_mask))
return;
ctx->block_mask = 0;
}
static void wrap_vm_open(struct vm_area_struct *vma)
{
struct wrap_ctx_mapping *mapping;
mapping = container_of(vma->vm_ops, struct wrap_ctx_mapping, vm_ops);
if (mapping->content_vm_ops && mapping->content_vm_ops->open)
mapping->content_vm_ops->open(vma);
spin_lock(&mapping->ctx->lock);
mapping->ctx->map_count++;
refcount_inc(&mapping->refcnt);
spin_unlock(&mapping->ctx->lock);
}
static void wrap_vm_close(struct vm_area_struct *vma)
{
struct wrap_ctx_mapping *mapping;
struct wrap_ctx *ctx;
mapping = container_of(vma->vm_ops, struct wrap_ctx_mapping, vm_ops);
if (mapping->content_vm_ops && mapping->content_vm_ops->close)
mapping->content_vm_ops->close(vma);
ctx = mapping->ctx;
spin_lock(&ctx->lock);
if (ctx->map_count > 0)
ctx->map_count--;
else
pr_warn("wrapfd map count underflow\n");
if (refcount_dec_and_test(&mapping->refcnt))
kfree(mapping);
spin_unlock(&ctx->lock);
}
static int wrap_mmap(struct file *file, struct vm_area_struct *vma)
{
struct wrap_ctx *ctx = file->private_data;
struct wrap_ctx_mapping *mapping;
struct wrap_content *content;
bool make_rdonly = false;
int ret = 0;
spin_lock(&ctx->lock);
if (!ctx->allow_guests && is_owner(ctx) &&
!is_owner_task(ctx, current)) {
ret = -EBUSY;
goto unlock;
}
/*
* If mappings are blocked the content is being rewrapped or emptied.
* Treat this as if the wrap is already empty.
*/
if (!can_map(ctx)) {
ret = -ENOENT;
goto unlock;
}
content = ctx->content;
if (!content) {
ret = -ENOENT;
goto unlock;
}
/* Handle read-only content */
if (content->ops->is_writable &&
!content->ops->is_writable(content)) {
if (vma->vm_flags & VM_WRITE) {
ret = -EACCES;
goto unlock;
}
make_rdonly = !!(vma->vm_flags & VM_MAYWRITE);
}
if (content->ops->mmap_prepare) {
ret = content->ops->mmap_prepare(content, vma);
if (ret) {
ret = -EINVAL;
goto unlock;
}
}
/*
* Increased map_count prevents changes in the
* ownership, rewrapping or emptying the content.
* Therefore content is stable.
*/
ctx->map_count++;
unlock:
spin_unlock(&ctx->lock);
if (ret)
goto err;
/* If we reached here then ctx->map_count has been incremented */
mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
if (!mapping) {
ret = -ENOMEM;
goto err_dec;
}
ret = content->ops->mmap(content, vma);
if (ret)
goto err_free_mapping;
if (make_rdonly) {
/*
* content->ops->mmap should not be mapping read-only content
* as writable. Either content->ops->is_writable reports
* incorrect value or content->ops->mmap is misbehaving.
*/
if (unlikely(vma->vm_flags & VM_WRITE)) {
pr_warn("wrapfd read-only content was mapped as writable\n");
ret = -EACCES;
goto err_free_mapping;
}
vm_flags_clear(vma, VM_MAYWRITE);
}
spin_lock(&ctx->lock);
mapping->content_vm_ops = vma->vm_ops;
if (vma->vm_ops)
mapping->vm_ops = *vma->vm_ops;
mapping->vm_ops.open = wrap_vm_open;
mapping->vm_ops.close = wrap_vm_close;
vma->vm_ops = &mapping->vm_ops;
mapping->ctx = ctx;
refcount_set(&mapping->refcnt, 1);
spin_unlock(&ctx->lock);
return 0;
err_free_mapping:
kfree(mapping);
err_dec:
spin_lock(&ctx->lock);
ctx->map_count--;
spin_unlock(&ctx->lock);
err:
return ret;
}
static int wrap_release(struct inode *ignored, struct file *file)
{
struct wrap_ctx *ctx = file->private_data;
if (ctx->content)
ctx->content->ops->free(ctx->content);
kfree(ctx);
return 0;
}
static int get_wrap_state(struct wrap_ctx *ctx,
struct wrapfd_get_state __user *user_wrapfd_get_state)
{
struct wrapfd_get_state wrapfd_get_state;
if (copy_from_user(&wrapfd_get_state, user_wrapfd_get_state,
sizeof(wrapfd_get_state)))
return -EFAULT;
if (wrapfd_get_state.reserved || wrapfd_get_state.pad)
return -EINVAL;
spin_lock(&ctx->lock);
/*
* If mappings are blocked the content is being rewrapped or emptied.
* Treat this as if the wrap is already empty.
*/
if (ctx->content && can_map(ctx)) {
if (ctx->content->ops->is_writable(ctx->content))
wrapfd_get_state.state = WRAPFD_CONTENT_RDWR;
else
wrapfd_get_state.state = WRAPFD_CONTENT_RDONLY;
} else {
wrapfd_get_state.state = WRAPFD_CONTENT_EMPTY;
}
spin_unlock(&ctx->lock);
if (copy_to_user(user_wrapfd_get_state, &wrapfd_get_state,
sizeof(wrapfd_get_state)))
return -EFAULT;
return 0;
}
static int wrap_file_acquire_ownership(struct wrap_ctx *ctx)
{
int ret = 0;
spin_lock(&ctx->lock);
if (is_owner_task(ctx, current))
goto unlock;
if (is_owner(ctx)) {
ret = -EBUSY;
goto unlock;
}
if (ctx->map_count > 0) {
ret = -EINVAL;
goto unlock;
}
if (!ctx->content) {
ret = -ENOENT;
goto unlock;
}
ctx->owner.task = current;
unlock:
spin_unlock(&ctx->lock);
return ret;
}
static int wrap_file_release_ownership(struct wrap_ctx *ctx)
{
int ret = 0;
spin_lock(&ctx->lock);
ret = can_access(ctx, current, false);
if (ret)
goto unlock;
ctx->owner.task = NULL;
ctx->allow_guests = false;
unlock:
spin_unlock(&ctx->lock);
return ret;
}
static int wrap_file_load(struct wrap_ctx *ctx,
struct wrapfd_load __user *user_wrapfd_load)
{
struct wrapfd_load wrapfd_load;
struct file *file;
loff_t end;
int ret = 0;
if (copy_from_user(&wrapfd_load, user_wrapfd_load,
sizeof(wrapfd_load)))
return -EFAULT;
if (!PAGE_ALIGNED(wrapfd_load.file_offs))
return -EINVAL;
if (!PAGE_ALIGNED(wrapfd_load.buf_offs))
return -EINVAL;
if (wrapfd_load.reserved || wrapfd_load.pad)
return -EINVAL;
file = fget(wrapfd_load.fd);
if (!file)
return -EBADF;
if (!(file->f_mode & FMODE_READ)) {
ret = -EBADF;
goto put_file;
}
if (!file->f_op->read_iter) {
ret = -EINVAL;
goto put_file;
}
if (!(file->f_mode & FMODE_CAN_READ)) {
ret = -EINVAL;
goto put_file;
}
if (!(file->f_mode & FMODE_CAN_ODIRECT)) {
ret = -EINVAL;
goto put_file;
}
/* Align the size to the page boundary */
wrapfd_load.len = PAGE_ALIGN(wrapfd_load.len);
if (check_add_overflow(wrapfd_load.file_offs, wrapfd_load.len,
&end)) {
ret = -EINVAL;
goto put_file;
}
if (end > i_size_read(file_inode(file))) {
ret = -EINVAL;
goto put_file;
}
spin_lock(&ctx->lock);
ret = block_operations(ctx, OP_BLOCKED_MODIFICATION);
spin_unlock(&ctx->lock);
if (ret)
goto put_file;
ret = ctx->content->ops->load(ctx->content, file,
wrapfd_load.file_offs,
wrapfd_load.buf_offs,
wrapfd_load.len);
spin_lock(&ctx->lock);
unblock_operations(ctx);
spin_unlock(&ctx->lock);
put_file:
fput(file);
return ret;
}
static int wrap_file_rewrap(struct wrap_ctx *ctx,
struct wrapfd_rewrap __user *user_wrapfd_rewrap)
{
struct wrapfd_rewrap wrapfd_rewrap;
struct wrap_content *new_content;
struct wrap_content *content;
struct wrap_ctx *new_ctx;
int ret = 0;
if (copy_from_user(&wrapfd_rewrap, user_wrapfd_rewrap,
sizeof(wrapfd_rewrap)))
return -EFAULT;
if (wrapfd_rewrap.prot & ~(PROT_WRITE | PROT_READ))
return -EINVAL;
if (wrapfd_rewrap.reserved || wrapfd_rewrap.pad)
return -EINVAL;
spin_lock(&ctx->lock);
ret = block_operations(ctx,
OP_BLOCKED_MODIFICATION | OP_BLOCKED_MAPPING);
if (!ret) {
content = ctx->content;
ctx->content = NULL;
}
spin_unlock(&ctx->lock);
if (ret)
goto out;
new_content = content->ops->make_writable(content,
(wrapfd_rewrap.prot & PROT_WRITE) != 0);
if (!new_content) {
ret = -ENOMEM;
goto restore_content;
}
new_content->close_on_exec = content->close_on_exec;
new_ctx = create_wrap_ctx();
if (!new_ctx) {
ret = -ENOMEM;
goto free_new_content;
}
ret = publish_wrap(new_ctx, new_content);
if (ret < 0)
goto free_new_ctx;
if (new_content != content)
content->ops->free(content);
spin_lock(&ctx->lock);
unblock_operations(ctx);
spin_unlock(&ctx->lock);
return ret;
free_new_ctx:
kfree(new_ctx);
free_new_content:
if (new_content != content)
new_content->ops->free(new_content);
restore_content:
/*
* Restore original wrap. We are the owner and the wrap
* is empty, so it could not have changed from under us.
*/
spin_lock(&ctx->lock);
ctx->content = content;
unblock_operations(ctx);
spin_unlock(&ctx->lock);
out:
return ret;
}
static int wrap_file_empty(struct wrap_ctx *ctx)
{
struct wrap_content *content;
int ret = 0;
spin_lock(&ctx->lock);
ret = block_operations(ctx,
OP_BLOCKED_MODIFICATION | OP_BLOCKED_MAPPING);
if (ret)
goto unlock;
content = ctx->content;
ctx->content = NULL;
unblock_operations(ctx);
unlock:
spin_unlock(&ctx->lock);
if (!ret)
content->ops->free(content);
return ret;
}
static int wrap_file_allow_guests(struct wrap_ctx *ctx, bool allow)
{
int ret = 0;
spin_lock(&ctx->lock);
ret = can_access(ctx, current, true);
if (ret)
goto unlock;
ctx->allow_guests = allow;
unlock:
spin_unlock(&ctx->lock);
return ret;
}
static int wrap_file_ioctl(struct wrap_ctx *ctx,
unsigned int cmd, unsigned long arg)
{
if (!ctx->content)
return -ENOENT; /* Wrap is empty */
if (ctx->content->ops->ioctl)
return ctx->content->ops->ioctl(ctx->content, cmd, arg);
return -ENOIOCTLCMD;
}
static long wrap_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct wrap_ctx *ctx = file->private_data;
long ret;
switch (cmd) {
case WRAPFD_DEV_IOC_GET_STATE:
ret = get_wrap_state(ctx,
(struct wrapfd_get_state __user *)arg);
break;
case WRAPFD_DEV_IOC_ACQUIRE_OWNERSHIP:
ret = wrap_file_acquire_ownership(ctx);
break;
case WRAPFD_DEV_IOC_RELEASE_OWNERSHIP:
ret = wrap_file_release_ownership(ctx);
break;
case WRAPFD_DEV_IOC_LOAD:
ret = wrap_file_load(ctx, (struct wrapfd_load __user *)arg);
break;
case WRAPFD_DEV_IOC_REWRAP:
ret = wrap_file_rewrap(ctx,
(struct wrapfd_rewrap __user *)arg);
break;
case WRAPFD_DEV_IOC_EMPTY:
ret = wrap_file_empty(ctx);
break;
case WRAPFD_DEV_IOC_ALLOW_GUESTS:
ret = wrap_file_allow_guests(ctx, true);
break;
case WRAPFD_DEV_IOC_PROHIBIT_GUESTS:
ret = wrap_file_allow_guests(ctx, false);
break;
default:
ret = wrap_file_ioctl(ctx, cmd, arg);
break;
}
return ret;
}
#ifdef CONFIG_PROC_FS
static void wrap_show_fdinfo(struct seq_file *m, struct file *file)
{
struct wrap_ctx *ctx = file->private_data;
spin_lock(&ctx->lock);
if (ctx->owner.task) {
seq_printf(m, "owner:\t%d\n", ctx->owner.task->pid);
} else {
if (ctx->owner.dev)
seq_printf(m, "owner:\t<device>\n");
else
seq_printf(m, "owner:\t<none>\n");
}
seq_printf(m, "guests:\t%s\n", ctx->allow_guests ? "yes" : "no");
seq_printf(m, "maps:\t%lu\n", ctx->map_count);
seq_printf(m, "empty:\t%s\n", ctx->content ? "no" : "yes");
if (ctx->content) {
struct wrap_content *content = ctx->content;
seq_printf(m, "rdonly:\t%s\n",
content->ops->is_writable(content) ? "no" : "yes");
content->ops->show_fdinfo(content, m);
}
spin_unlock(&ctx->lock);
}
#endif
bool is_wrapfd_vma(struct vm_area_struct *vma)
{
return (vma && (vma->vm_ops->open == wrap_vm_open));
}
int wrapfd_get_mappable(struct file *file, struct device *dev,
union wrapfd_mappable *mappable)
{
struct wrap_ctx *ctx;
int ret;
if (WARN_ON(!dev))
return -ENODEV;
if (file->f_op != &wrap_fops)
return -EBADF;
ctx = file->private_data;
spin_lock(&ctx->lock);
if (is_owner(ctx) && !is_owner_dev(ctx, dev)) {
ret = -EBUSY;
goto unlock;
}
if (ctx->map_count > 0) {
ret = -EINVAL;
goto unlock;
}
if (!ctx->content) {
ret = -ENOENT;
goto unlock;
}
ctx->owner.dev = dev;
/* Device is the owner, context can't change from under us. */
ret = 0;
unlock:
spin_unlock(&ctx->lock);
if (!ret) {
ret = ctx->content->ops->get_mappable(ctx->content, dev,
mappable);
if (ret) {
spin_lock(&ctx->lock);
ctx->owner.dev = NULL;
spin_unlock(&ctx->lock);
}
}
return ret;
}
int wrapfd_put_mappable(struct file *file, struct device *dev,
union wrapfd_mappable *mappable)
{
struct wrap_ctx *ctx;
int ret;
if (WARN_ON(!dev))
return -ENODEV;
if (file->f_op != &wrap_fops)
return -EBADF;
ctx = file->private_data;
spin_lock(&ctx->lock);
if (!is_owner_dev(ctx, dev)) {
ret = -EBUSY;
goto unlock;
}
ctx->owner.dev = NULL;
ret = 0;
unlock:
spin_unlock(&ctx->lock);
if (!ret && ctx->content->ops->put_mappable)
ctx->content->ops->put_mappable(ctx->content, mappable);
return ret;
}
static const struct file_operations wrap_fops = {
.owner = THIS_MODULE,
.mmap = wrap_mmap,
.release = wrap_release,
.unlocked_ioctl = wrap_ioctl,
.compat_ioctl = wrap_ioctl,
#ifdef CONFIG_PROC_FS
.show_fdinfo = wrap_show_fdinfo,
#endif
};
static struct wrap_content *create_content_for(int fd, unsigned long prot)
{
struct wrap_content *content;
struct dma_buf *dmabuf;
dmabuf = dma_buf_get(fd);
if (!IS_ERR(dmabuf)) {
bool writable = !!(prot & PROT_WRITE);
content = alloc_dmabuf_content(dmabuf, writable);
dma_buf_put(dmabuf);
return content ? content : ERR_PTR(-ENOMEM);
}
return ERR_PTR(-EINVAL);
}
static int wrap_file(struct wrap_ctx *ctx,
struct wrapfd_wrap __user *user_wrapfd_wrap)
{
struct wrapfd_wrap wrapfd_wrap;
struct wrap_content *content;
int wrapfd;
if (copy_from_user(&wrapfd_wrap, user_wrapfd_wrap,
sizeof(wrapfd_wrap)))
return -EFAULT;
if (wrapfd_wrap.prot & ~(PROT_WRITE | PROT_READ))
return -EINVAL;
if (wrapfd_wrap.reserved)
return -EINVAL;
content = create_content_for(wrapfd_wrap.fd, wrapfd_wrap.prot);
if (IS_ERR(content))
return PTR_ERR(content);
content->close_on_exec = get_close_on_exec(wrapfd_wrap.fd);
wrapfd = publish_wrap(ctx, content);
if (wrapfd < 0) {
ctx->content = NULL;
content->ops->free(content);
}
return wrapfd;
}
static long wrapfd_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct wrap_ctx *ctx;
int ret;
VM_WARN_ON_ONCE(!current->mm);
switch (cmd) {
case WRAPFD_DEV_IOC_WRAP:
ctx = create_wrap_ctx();
if (!ctx)
return -ENOMEM;
ret = wrap_file(ctx, (struct wrapfd_wrap __user *)arg);
if (ret < 0)
kfree(ctx);
break;
default:
return -ENOIOCTLCMD;
}
return ret;
}
static const struct file_operations wrapfd_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = wrapfd_dev_ioctl,
.compat_ioctl = wrapfd_dev_ioctl,
.llseek = noop_llseek,
};
static struct miscdevice wrapfd_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "wrapfd",
.fops = &wrapfd_dev_fops,
};
static int __init wrapfd_init(void)
{
int ret;
ret = misc_register(&wrapfd_misc);
if (ret) {
pr_err("failed to register misc device!\n");
return ret;
}
return 0;
}
device_initcall(wrapfd_init);