/* SPDX-License-Identifier: GPL-2.0-only */ /* wrapfd.c * * Wrapfd * * Copyright (C) 2025 Google, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wrapfd.h" #define FDINFO_BUF_SIZE 100 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, unsigned long file_offs, unsigned long buf_offs, unsigned long len); int (*mmap_prepare)(struct wrap_content *content, struct vm_area_struct *vma); int (*mmap)(struct wrap_content *content, struct vm_area_struct *vma); vm_fault_t (*fault)(struct wrap_content *content, struct vm_fault *vmf); 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; }; /* 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; dmabuf_content = container_of(content, struct wrap_content_dmabuf, content); return anon_inode_getfd("[wrapfd]", &wrap_fops, ctx, dmabuf_content->writable ? O_RDWR : O_RDONLY); } static struct miscdevice wrapfd_misc; static unsigned int init_bio_data(struct sg_table *sgtbl, size_t offset, size_t len, struct bio_vec *bvec) { struct scatterlist *sg; unsigned int count = 0; size_t end_offs = 0; unsigned int i; size_t sg_len; for_each_sg(sgtbl->sgl, sg, sgtbl->nents, i) { end_offs += sg->length; if (end_offs <= offset) continue; sg_len = end_offs - offset; bvec[count].bv_page = sg_page(sg); bvec[count].bv_offset = sg->offset + sg->length - sg_len; if (sg_len >= len) { bvec[count++].bv_len = len; break; } bvec[count++].bv_len = sg_len; offset += sg_len; len -= sg_len; } return count; } static int dmabuf_content_load(struct wrap_content *content, struct file *file, unsigned long file_offs, unsigned long buf_offs, unsigned long len) { struct wrap_content_dmabuf *dmabuf_content; struct dma_buf_attachment *attachment; unsigned int bvec_size; struct sg_table *sgtbl; struct bio_vec *bvec; struct iov_iter iter; struct kiocb kiocb; int ret; dmabuf_content = container_of(content, struct wrap_content_dmabuf, content); if (file_offs + len > dmabuf_content->dmabuf->size - buf_offs) return -EINVAL; attachment = dma_buf_attach(dmabuf_content->dmabuf, wrapfd_misc.this_device); if (IS_ERR(attachment)) return PTR_ERR(attachment); sgtbl = dma_buf_map_attachment(attachment, DMA_FROM_DEVICE); if (IS_ERR(sgtbl)) { dma_buf_detach(dmabuf_content->dmabuf, attachment); return PTR_ERR(sgtbl); } dma_buf_mangle_sg_table(sgtbl); bvec = kvcalloc(sgtbl->nents, sizeof(*bvec), GFP_KERNEL); if (!bvec) { dma_buf_unmap_attachment(attachment, sgtbl, DMA_FROM_DEVICE); dma_buf_detach(dmabuf_content->dmabuf, attachment); return -ENOMEM; } bvec_size = init_bio_data(sgtbl, buf_offs, len, bvec); iov_iter_bvec(&iter, ITER_DEST, bvec, bvec_size, len); init_sync_kiocb(&kiocb, file); kiocb.ki_pos = file_offs; kiocb.ki_flags |= IOCB_DIRECT; while (kiocb.ki_pos < file_offs + len) { ret = vfs_iocb_iter_read(file, &kiocb, &iter); if (ret <= 0) break; } kvfree(bvec); dma_buf_unmap_attachment(attachment, sgtbl, DMA_FROM_DEVICE); dma_buf_detach(dmabuf_content->dmabuf, attachment); return ret < 0 ? ret : 0; } static int dmabuf_content_mmap_prepare(struct wrap_content *content, struct vm_area_struct *vma) { struct wrap_content_dmabuf *dmabuf_content; dmabuf_content = container_of(content, struct wrap_content_dmabuf, content); if (vma->vm_flags & VM_MAYWRITE) { if (!dmabuf_content->writable) return -EINVAL; } vm_flags_set(vma, VM_SHARED | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP); return 0; } 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 vm_fault_t dmabuf_content_fault(struct wrap_content *content, struct vm_fault *vmf) { return vmf->vma->vm_ops->fault(vmf); } 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 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 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_prepare = dmabuf_content_mmap_prepare, .mmap = dmabuf_content_mmap, .fault = dmabuf_content_fault, .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 { struct wrap_ctx *ctx; const struct vm_operations_struct *vm_ops; void *vm_private_data; }; struct wrap_ctx { struct wrap_content *content; spinlock_t lock; /* protects all fields below */ struct wrap_owner owner; bool allow_guests; int map_count; }; 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) { ctx->content = content; return content->ops->create_wrap(content, ctx); } 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 const struct vm_operations_struct wrap_vm_ops; static void wrap_vm_close(struct vm_area_struct *vma) { struct wrap_ctx_mapping *mapping = vma->vm_private_data; struct wrap_ctx *ctx = mapping->ctx; if (mapping->vm_ops && mapping->vm_ops->close) { vma->vm_private_data = mapping->vm_private_data; vma->vm_ops = mapping->vm_ops; vma->vm_ops->close(vma); } spin_lock(&ctx->lock); ctx->map_count--; spin_unlock(&ctx->lock); kfree(mapping); } static vm_fault_t wrap_vm_fault(struct vm_fault *vmf) { struct wrap_ctx_mapping *mapping = vmf->vma->vm_private_data; struct wrap_ctx *ctx = mapping->ctx; vm_fault_t ret; if (!mapping->vm_ops || !mapping->vm_ops->fault) return VM_FAULT_SIGBUS; vmf->vma->vm_private_data = mapping->vm_private_data; vmf->vma->vm_ops = mapping->vm_ops; ret = ctx->content->ops->fault(ctx->content, vmf); vmf->vma->vm_ops = &wrap_vm_ops; vmf->vma->vm_private_data = ctx; return ret; } static const struct vm_operations_struct wrap_vm_ops = { .close = wrap_vm_close, .fault = wrap_vm_fault, }; 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; int ret = 0; spin_lock(&ctx->lock); if (!ctx->allow_guests && is_owner(ctx) && !is_owner_task(ctx, current)) { ret = -EBUSY; goto unlock; } content = ctx->content; if (!content) { ret = -ENOENT; goto unlock; } ret = content->ops->mmap_prepare(content, vma); if (!ret) { /* * 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 = kmalloc(sizeof(*mapping), GFP_KERNEL); if (!mapping) { ret = -ENOMEM; goto err_dec; } ret = content->ops->mmap(content, vma); if (ret) { kfree(mapping); goto err_dec; } mapping->ctx = ctx; mapping->vm_ops = vma->vm_ops; mapping->vm_private_data = vma->vm_private_data; vma->vm_ops = &wrap_vm_ops; vma->vm_private_data = mapping; return 0; 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 wrap_file_get(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_put(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; 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; 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 (wrapfd_load.file_offs + wrapfd_load.len > i_size_read(file_inode(file))) { ret = -EINVAL; goto put_file; } spin_lock(&ctx->lock); ret = can_access(ctx, current, true); /* * Even though we drop the ctx->lock, the task is the owner, * if ret==0, so the content can't be erased or changed from * under us. */ spin_unlock(&ctx->lock); if (!ret) ret = ctx->content->ops->load(ctx->content, file, wrapfd_load.file_offs, wrapfd_load.buf_offs, wrapfd_load.len); 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; spin_lock(&ctx->lock); ret = can_access(ctx, current, true); 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_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); 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; 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 = can_access(ctx, current, true); if (ret) goto unlock; content = ctx->content; ctx->content = NULL; 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->ops->ioctl) return ctx->content->ops->ioctl(ctx->content, cmd, arg); return -ENOTTY; } 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: ret = wrap_file_get(ctx); break; case WRAPFD_DEV_IOC_PUT: ret = wrap_file_put(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\n"); else seq_printf(m, "owner:\t\n"); } seq_printf(m, "guests:\t%s\n", ctx->allow_guests ? "yes" : "no"); seq_printf(m, "maps:\t%d\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 int wrapfd_get(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(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; content = create_content_for(wrapfd_wrap.fd, wrapfd_wrap.prot); if (IS_ERR(content)) return PTR_ERR(content); wrapfd = publish_wrap(ctx, content); if (wrapfd < 0) { ctx->content = NULL; content->ops->free(content); } return wrapfd; } static int get_wrap_state(struct wrapfd_get_state __user *user_wrapfd_get_state) { struct wrapfd_get_state wrapfd_get_state; struct wrap_ctx *ctx; struct file *file; if (copy_from_user(&wrapfd_get_state, user_wrapfd_get_state, sizeof(wrapfd_get_state))) return -EFAULT; file = fget(wrapfd_get_state.fd); if (!file) return -EBADF; if (file->f_op != &wrap_fops) { fput(file); return -EINVAL; } ctx = file->private_data; if (ctx->content) { 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; } fput(file); if (copy_to_user(user_wrapfd_get_state, &wrapfd_get_state, sizeof(wrapfd_get_state))) return -EFAULT; return 0; } 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; case WRAPFD_DEV_IOC_GET_STATE: ret = get_wrap_state((struct wrapfd_get_state __user *)arg); break; default: return -ENOTTY; } 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; } dma_coerce_mask_and_coherent(wrapfd_misc.this_device, DMA_BIT_MASK(64)); wrapfd_misc.this_device->bus_dma_limit = DMA_BIT_MASK(64); return 0; } device_initcall(wrapfd_init);