// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2012-2021 ARM Limited. All rights reserved. * * This program is free software and is provided to you under the terms of the * GNU General Public License version 2 as published by the Free Software * Foundation, and any use by you of this program is subject to the terms * of such GNU license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you can access it online at * http://www.gnu.org/licenses/gpl-2.0.html. * */ #include #include #include #include #include #include #include #include #include #include #include #if (KERNEL_VERSION(4, 8, 0) > LINUX_VERSION_CODE) #include #endif #include /* Maximum size allowed in a single DMA_BUF_TE_ALLOC call */ #define DMA_BUF_TE_ALLOC_MAX_SIZE ((8ull << 30) >> PAGE_SHIFT) /* 8 GB */ /* Since kernel version 5.0 CONFIG_ARCH_NO_SG_CHAIN replaced CONFIG_ARCH_HAS_SG_CHAIN */ #if KERNEL_VERSION(5, 0, 0) > LINUX_VERSION_CODE #if (!defined(ARCH_HAS_SG_CHAIN) && !defined(CONFIG_ARCH_HAS_SG_CHAIN)) #define NO_SG_CHAIN #endif #elif defined(CONFIG_ARCH_NO_SG_CHAIN) #define NO_SG_CHAIN #endif struct dma_buf_te_alloc { /* the real alloc */ size_t nr_pages; struct page **pages; /* the debug usage tracking */ int nr_attached_devices; int nr_device_mappings; int nr_cpu_mappings; /* failure simulation */ int fail_attach; int fail_map; int fail_mmap; bool contiguous; dma_addr_t contig_dma_addr; void *contig_cpu_addr; }; struct dma_buf_te_attachment { struct sg_table *sg; bool attachment_mapped; }; static struct miscdevice te_device; #if (KERNEL_VERSION(4, 19, 0) > LINUX_VERSION_CODE) static int dma_buf_te_attach(struct dma_buf *buf, struct device *dev, struct dma_buf_attachment *attachment) #else static int dma_buf_te_attach(struct dma_buf *buf, struct dma_buf_attachment *attachment) #endif { struct dma_buf_te_alloc *alloc; alloc = buf->priv; if (alloc->fail_attach) return -EFAULT; attachment->priv = kzalloc(sizeof(struct dma_buf_te_attachment), GFP_KERNEL); if (!attachment->priv) return -ENOMEM; /* dma_buf is externally locked during call */ alloc->nr_attached_devices++; return 0; } static void dma_buf_te_detach(struct dma_buf *buf, struct dma_buf_attachment *attachment) { struct dma_buf_te_alloc *alloc = buf->priv; struct dma_buf_te_attachment *pa = attachment->priv; /* dma_buf is externally locked during call */ WARN(pa->attachment_mapped, "WARNING: dma-buf-test-exporter detected detach with open device mappings"); alloc->nr_attached_devices--; kfree(pa); } static struct sg_table *dma_buf_te_map(struct dma_buf_attachment *attachment, enum dma_data_direction direction) { struct sg_table *sg; struct scatterlist *iter; struct dma_buf_te_alloc *alloc; struct dma_buf_te_attachment *pa = attachment->priv; size_t i; int ret; alloc = attachment->dmabuf->priv; if (alloc->fail_map) return ERR_PTR(-ENOMEM); if (WARN(pa->attachment_mapped, "WARNING: Attempted to map already mapped attachment.")) return ERR_PTR(-EBUSY); #ifdef NO_SG_CHAIN /* if the ARCH can't chain we can't have allocs larger than a single sg can hold */ if (alloc->nr_pages > SG_MAX_SINGLE_ALLOC) return ERR_PTR(-EINVAL); #endif /* NO_SG_CHAIN */ sg = kmalloc(sizeof(struct sg_table), GFP_KERNEL); if (!sg) return ERR_PTR(-ENOMEM); /* from here we access the allocation object, so lock the dmabuf pointing to it */ mutex_lock(&attachment->dmabuf->lock); if (alloc->contiguous) ret = sg_alloc_table(sg, 1, GFP_KERNEL); else ret = sg_alloc_table(sg, alloc->nr_pages, GFP_KERNEL); if (ret) { mutex_unlock(&attachment->dmabuf->lock); kfree(sg); return ERR_PTR(ret); } if (alloc->contiguous) { sg_dma_len(sg->sgl) = alloc->nr_pages * PAGE_SIZE; sg_set_page(sg->sgl, pfn_to_page(PFN_DOWN(alloc->contig_dma_addr)), alloc->nr_pages * PAGE_SIZE, 0); sg_dma_address(sg->sgl) = alloc->contig_dma_addr; } else { for_each_sg(sg->sgl, iter, alloc->nr_pages, i) sg_set_page(iter, alloc->pages[i], PAGE_SIZE, 0); } if (!dma_map_sg(attachment->dev, sg->sgl, sg->nents, direction)) { mutex_unlock(&attachment->dmabuf->lock); sg_free_table(sg); kfree(sg); return ERR_PTR(-ENOMEM); } alloc->nr_device_mappings++; pa->attachment_mapped = true; pa->sg = sg; mutex_unlock(&attachment->dmabuf->lock); return sg; } static void dma_buf_te_unmap(struct dma_buf_attachment *attachment, struct sg_table *sg, enum dma_data_direction direction) { struct dma_buf_te_alloc *alloc; struct dma_buf_te_attachment *pa = attachment->priv; alloc = attachment->dmabuf->priv; mutex_lock(&attachment->dmabuf->lock); WARN(!pa->attachment_mapped, "WARNING: Unmatched unmap of attachment."); alloc->nr_device_mappings--; pa->attachment_mapped = false; pa->sg = NULL; mutex_unlock(&attachment->dmabuf->lock); dma_unmap_sg(attachment->dev, sg->sgl, sg->nents, direction); sg_free_table(sg); kfree(sg); } static void dma_buf_te_release(struct dma_buf *buf) { size_t i; struct dma_buf_te_alloc *alloc; alloc = buf->priv; /* no need for locking */ if (alloc->contiguous) { #if (KERNEL_VERSION(4, 8, 0) <= LINUX_VERSION_CODE) dma_free_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, alloc->contig_cpu_addr, alloc->contig_dma_addr, DMA_ATTR_WRITE_COMBINE); #else DEFINE_DMA_ATTRS(attrs); dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); dma_free_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, alloc->contig_cpu_addr, alloc->contig_dma_addr, &attrs); #endif } else { for (i = 0; i < alloc->nr_pages; i++) __free_page(alloc->pages[i]); } #if (KERNEL_VERSION(4, 12, 0) <= LINUX_VERSION_CODE) kvfree(alloc->pages); #else kfree(alloc->pages); #endif kfree(alloc); } static int dma_buf_te_sync(struct dma_buf *dmabuf, enum dma_data_direction direction, bool start_cpu_access) { struct dma_buf_attachment *attachment; mutex_lock(&dmabuf->lock); list_for_each_entry(attachment, &dmabuf->attachments, node) { struct dma_buf_te_attachment *pa = attachment->priv; struct sg_table *sg = pa->sg; if (!sg) { dev_dbg(te_device.this_device, "no mapping for device %s\n", dev_name(attachment->dev)); continue; } if (start_cpu_access) { dev_dbg(te_device.this_device, "sync cpu with device %s\n", dev_name(attachment->dev)); dma_sync_sg_for_cpu(attachment->dev, sg->sgl, sg->nents, direction); } else { dev_dbg(te_device.this_device, "sync device %s with cpu\n", dev_name(attachment->dev)); dma_sync_sg_for_device(attachment->dev, sg->sgl, sg->nents, direction); } } mutex_unlock(&dmabuf->lock); return 0; } #if (KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE) static int dma_buf_te_begin_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction) #else static int dma_buf_te_begin_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len, enum dma_data_direction direction) #endif { return dma_buf_te_sync(dmabuf, direction, true); } #if (KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE) static int dma_buf_te_end_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction) { return dma_buf_te_sync(dmabuf, direction, false); } #else static void dma_buf_te_end_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len, enum dma_data_direction direction) { dma_buf_te_sync(dmabuf, direction, false); } #endif static void dma_buf_te_mmap_open(struct vm_area_struct *vma) { struct dma_buf *dma_buf; struct dma_buf_te_alloc *alloc; dma_buf = vma->vm_private_data; alloc = dma_buf->priv; mutex_lock(&dma_buf->lock); alloc->nr_cpu_mappings++; mutex_unlock(&dma_buf->lock); } static void dma_buf_te_mmap_close(struct vm_area_struct *vma) { struct dma_buf *dma_buf; struct dma_buf_te_alloc *alloc; dma_buf = vma->vm_private_data; alloc = dma_buf->priv; BUG_ON(alloc->nr_cpu_mappings <= 0); mutex_lock(&dma_buf->lock); alloc->nr_cpu_mappings--; mutex_unlock(&dma_buf->lock); } #if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE static int dma_buf_te_mmap_fault(struct vm_area_struct *vma, struct vm_fault *vmf) #elif KERNEL_VERSION(5, 1, 0) > LINUX_VERSION_CODE static int dma_buf_te_mmap_fault(struct vm_fault *vmf) #else static vm_fault_t dma_buf_te_mmap_fault(struct vm_fault *vmf) #endif { struct dma_buf_te_alloc *alloc; struct dma_buf *dmabuf; struct page *pageptr; #if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE dmabuf = vma->vm_private_data; #else dmabuf = vmf->vma->vm_private_data; #endif alloc = dmabuf->priv; if (vmf->pgoff > alloc->nr_pages) return VM_FAULT_SIGBUS; pageptr = alloc->pages[vmf->pgoff]; BUG_ON(!pageptr); get_page(pageptr); vmf->page = pageptr; return 0; } struct vm_operations_struct dma_buf_te_vm_ops = { .open = dma_buf_te_mmap_open, .close = dma_buf_te_mmap_close, .fault = dma_buf_te_mmap_fault }; static int dma_buf_te_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma) { struct dma_buf_te_alloc *alloc; alloc = dmabuf->priv; if (alloc->fail_mmap) return -ENOMEM; vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; vma->vm_ops = &dma_buf_te_vm_ops; vma->vm_private_data = dmabuf; /* we fault in the pages on access */ /* call open to do the ref-counting */ dma_buf_te_vm_ops.open(vma); return 0; } #if KERNEL_VERSION(4, 19, 0) > LINUX_VERSION_CODE static void *dma_buf_te_kmap_atomic(struct dma_buf *buf, unsigned long page_num) { /* IGNORE */ return NULL; } #endif static void *dma_buf_te_kmap(struct dma_buf *buf, unsigned long page_num) { struct dma_buf_te_alloc *alloc; alloc = buf->priv; if (page_num >= alloc->nr_pages) return NULL; return kmap(alloc->pages[page_num]); } static void dma_buf_te_kunmap(struct dma_buf *buf, unsigned long page_num, void *addr) { struct dma_buf_te_alloc *alloc; alloc = buf->priv; if (page_num >= alloc->nr_pages) return; kunmap(alloc->pages[page_num]); return; } static struct dma_buf_ops dma_buf_te_ops = { /* real handlers */ .attach = dma_buf_te_attach, .detach = dma_buf_te_detach, .map_dma_buf = dma_buf_te_map, .unmap_dma_buf = dma_buf_te_unmap, .release = dma_buf_te_release, .mmap = dma_buf_te_mmap, .begin_cpu_access = dma_buf_te_begin_cpu_access, .end_cpu_access = dma_buf_te_end_cpu_access, #if KERNEL_VERSION(4, 12, 0) > LINUX_VERSION_CODE .kmap = dma_buf_te_kmap, .kunmap = dma_buf_te_kunmap, /* nop handlers for mandatory functions we ignore */ .kmap_atomic = dma_buf_te_kmap_atomic #else #if KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE .map = dma_buf_te_kmap, .unmap = dma_buf_te_kunmap, #endif #if KERNEL_VERSION(4, 19, 0) > LINUX_VERSION_CODE /* nop handlers for mandatory functions we ignore */ .map_atomic = dma_buf_te_kmap_atomic #endif #endif }; static int do_dma_buf_te_ioctl_version(struct dma_buf_te_ioctl_version __user *buf) { struct dma_buf_te_ioctl_version v; if (copy_from_user(&v, buf, sizeof(v))) return -EFAULT; if (v.op != DMA_BUF_TE_ENQ) return -EFAULT; v.op = DMA_BUF_TE_ACK; v.major = DMA_BUF_TE_VER_MAJOR; v.minor = DMA_BUF_TE_VER_MINOR; if (copy_to_user(buf, &v, sizeof(v))) return -EFAULT; else return 0; } static int do_dma_buf_te_ioctl_alloc(struct dma_buf_te_ioctl_alloc __user *buf, bool contiguous) { struct dma_buf_te_ioctl_alloc alloc_req; struct dma_buf_te_alloc *alloc; struct dma_buf *dma_buf; size_t i = 0; size_t max_nr_pages = DMA_BUF_TE_ALLOC_MAX_SIZE; int fd; if (copy_from_user(&alloc_req, buf, sizeof(alloc_req))) { dev_err(te_device.this_device, "%s: couldn't get user data", __func__); goto no_input; } if (!alloc_req.size) { dev_err(te_device.this_device, "%s: no size specified", __func__); goto invalid_size; } #ifdef NO_SG_CHAIN /* Whilst it is possible to allocate larger buffer, we won't be able to * map it during actual usage (mmap() still succeeds). We fail here so * userspace code can deal with it early than having driver failure * later on. */ if (max_nr_pages > SG_MAX_SINGLE_ALLOC) max_nr_pages = SG_MAX_SINGLE_ALLOC; #endif /* NO_SG_CHAIN */ if (alloc_req.size > max_nr_pages) { dev_err(te_device.this_device, "%s: buffer size of %llu pages exceeded the mapping limit of %zu pages", __func__, alloc_req.size, max_nr_pages); goto invalid_size; } alloc = kzalloc(sizeof(struct dma_buf_te_alloc), GFP_KERNEL); if (alloc == NULL) { dev_err(te_device.this_device, "%s: couldn't alloc object", __func__); goto no_alloc_object; } alloc->nr_pages = alloc_req.size; alloc->contiguous = contiguous; #if (KERNEL_VERSION(4, 12, 0) <= LINUX_VERSION_CODE) alloc->pages = kvzalloc(sizeof(struct page *) * alloc->nr_pages, GFP_KERNEL); #else alloc->pages = kzalloc(sizeof(struct page *) * alloc->nr_pages, GFP_KERNEL); #endif if (!alloc->pages) { dev_err(te_device.this_device, "%s: couldn't alloc %zu page structures", __func__, alloc->nr_pages); goto free_alloc_object; } if (contiguous) { dma_addr_t dma_aux; #if (KERNEL_VERSION(4, 8, 0) <= LINUX_VERSION_CODE) alloc->contig_cpu_addr = dma_alloc_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, &alloc->contig_dma_addr, GFP_KERNEL | __GFP_ZERO, DMA_ATTR_WRITE_COMBINE); #else DEFINE_DMA_ATTRS(attrs); dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); alloc->contig_cpu_addr = dma_alloc_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, &alloc->contig_dma_addr, GFP_KERNEL | __GFP_ZERO, &attrs); #endif if (!alloc->contig_cpu_addr) { dev_err(te_device.this_device, "%s: couldn't alloc contiguous buffer %zu pages", __func__, alloc->nr_pages); goto free_page_struct; } dma_aux = alloc->contig_dma_addr; for (i = 0; i < alloc->nr_pages; i++) { alloc->pages[i] = pfn_to_page(PFN_DOWN(dma_aux)); dma_aux += PAGE_SIZE; } } else { for (i = 0; i < alloc->nr_pages; i++) { alloc->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); if (alloc->pages[i] == NULL) { dev_err(te_device.this_device, "%s: couldn't alloc page", __func__); goto no_page; } } } /* alloc ready, let's export it */ { struct dma_buf_export_info export_info = { .exp_name = "dma_buf_te", .owner = THIS_MODULE, .ops = &dma_buf_te_ops, .size = alloc->nr_pages << PAGE_SHIFT, .flags = O_CLOEXEC | O_RDWR, .priv = alloc, }; dma_buf = dma_buf_export(&export_info); } if (IS_ERR_OR_NULL(dma_buf)) { dev_err(te_device.this_device, "%s: couldn't export dma_buf", __func__); goto no_export; } /* get fd for buf */ fd = dma_buf_fd(dma_buf, O_CLOEXEC); if (fd < 0) { dev_err(te_device.this_device, "%s: couldn't get fd from dma_buf", __func__); goto no_fd; } return fd; no_fd: dma_buf_put(dma_buf); no_export: /* i still valid */ no_page: if (contiguous) { #if (KERNEL_VERSION(4, 8, 0) <= LINUX_VERSION_CODE) dma_free_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, alloc->contig_cpu_addr, alloc->contig_dma_addr, DMA_ATTR_WRITE_COMBINE); #else DEFINE_DMA_ATTRS(attrs); dma_set_attr(DMA_ATTR_WRITE_COMBINE, &attrs); dma_free_attrs(te_device.this_device, alloc->nr_pages * PAGE_SIZE, alloc->contig_cpu_addr, alloc->contig_dma_addr, &attrs); #endif } else { while (i-- > 0) __free_page(alloc->pages[i]); } free_page_struct: #if (KERNEL_VERSION(4, 12, 0) <= LINUX_VERSION_CODE) kvfree(alloc->pages); #else kfree(alloc->pages); #endif free_alloc_object: kfree(alloc); no_alloc_object: invalid_size: no_input: return -EFAULT; } static int do_dma_buf_te_ioctl_status(struct dma_buf_te_ioctl_status __user *arg) { struct dma_buf_te_ioctl_status status; struct dma_buf *dmabuf; struct dma_buf_te_alloc *alloc; int res = -EINVAL; if (copy_from_user(&status, arg, sizeof(status))) return -EFAULT; dmabuf = dma_buf_get(status.fd); if (IS_ERR_OR_NULL(dmabuf)) return -EINVAL; /* verify it's one of ours */ if (dmabuf->ops != &dma_buf_te_ops) goto err_have_dmabuf; /* ours, get the current status */ alloc = dmabuf->priv; /* lock while reading status to take a snapshot */ mutex_lock(&dmabuf->lock); status.attached_devices = alloc->nr_attached_devices; status.device_mappings = alloc->nr_device_mappings; status.cpu_mappings = alloc->nr_cpu_mappings; mutex_unlock(&dmabuf->lock); if (copy_to_user(arg, &status, sizeof(status))) goto err_have_dmabuf; /* All OK */ res = 0; err_have_dmabuf: dma_buf_put(dmabuf); return res; } static int do_dma_buf_te_ioctl_set_failing(struct dma_buf_te_ioctl_set_failing __user *arg) { struct dma_buf *dmabuf; struct dma_buf_te_ioctl_set_failing f; struct dma_buf_te_alloc *alloc; int res = -EINVAL; if (copy_from_user(&f, arg, sizeof(f))) return -EFAULT; dmabuf = dma_buf_get(f.fd); if (IS_ERR_OR_NULL(dmabuf)) return -EINVAL; /* verify it's one of ours */ if (dmabuf->ops != &dma_buf_te_ops) goto err_have_dmabuf; /* ours, set the fail modes */ alloc = dmabuf->priv; /* lock to set the fail modes atomically */ mutex_lock(&dmabuf->lock); alloc->fail_attach = f.fail_attach; alloc->fail_map = f.fail_map; alloc->fail_mmap = f.fail_mmap; mutex_unlock(&dmabuf->lock); /* success */ res = 0; err_have_dmabuf: dma_buf_put(dmabuf); return res; } static u32 dma_te_buf_fill(struct dma_buf *dma_buf, unsigned int value) { struct dma_buf_attachment *attachment; struct sg_table *sgt; struct scatterlist *sg; unsigned int count; unsigned int offset = 0; int ret = 0; size_t i; attachment = dma_buf_attach(dma_buf, te_device.this_device); if (IS_ERR_OR_NULL(attachment)) return -EBUSY; sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL); if (IS_ERR_OR_NULL(sgt)) { ret = PTR_ERR(sgt); goto no_import; } ret = dma_buf_begin_cpu_access(dma_buf, #if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE 0, dma_buf->size, #endif DMA_BIDIRECTIONAL); if (ret) goto no_cpu_access; for_each_sg(sgt->sgl, sg, sgt->nents, count) { for (i = 0; i < sg_dma_len(sg); i = i + PAGE_SIZE) { void *addr = NULL; #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE addr = dma_buf_te_kmap(dma_buf, i >> PAGE_SHIFT); #else addr = dma_buf_kmap(dma_buf, i >> PAGE_SHIFT); #endif if (!addr) { ret = -EPERM; goto no_kmap; } memset(addr, value, PAGE_SIZE); #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE dma_buf_te_kunmap(dma_buf, i >> PAGE_SHIFT, addr); #else dma_buf_kunmap(dma_buf, i >> PAGE_SHIFT, addr); #endif } offset += sg_dma_len(sg); } no_kmap: dma_buf_end_cpu_access(dma_buf, #if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE 0, dma_buf->size, #endif DMA_BIDIRECTIONAL); no_cpu_access: dma_buf_unmap_attachment(attachment, sgt, DMA_BIDIRECTIONAL); no_import: dma_buf_detach(dma_buf, attachment); return ret; } static int do_dma_buf_te_ioctl_fill(struct dma_buf_te_ioctl_fill __user *arg) { struct dma_buf *dmabuf; struct dma_buf_te_ioctl_fill f; int ret; if (copy_from_user(&f, arg, sizeof(f))) return -EFAULT; dmabuf = dma_buf_get(f.fd); if (IS_ERR_OR_NULL(dmabuf)) return -EINVAL; ret = dma_te_buf_fill(dmabuf, f.value); dma_buf_put(dmabuf); return ret; } static long dma_buf_te_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case DMA_BUF_TE_VERSION: return do_dma_buf_te_ioctl_version((struct dma_buf_te_ioctl_version __user *)arg); case DMA_BUF_TE_ALLOC: return do_dma_buf_te_ioctl_alloc((struct dma_buf_te_ioctl_alloc __user *)arg, false); case DMA_BUF_TE_ALLOC_CONT: return do_dma_buf_te_ioctl_alloc((struct dma_buf_te_ioctl_alloc __user *)arg, true); case DMA_BUF_TE_QUERY: return do_dma_buf_te_ioctl_status((struct dma_buf_te_ioctl_status __user *)arg); case DMA_BUF_TE_SET_FAILING: return do_dma_buf_te_ioctl_set_failing((struct dma_buf_te_ioctl_set_failing __user *)arg); case DMA_BUF_TE_FILL: return do_dma_buf_te_ioctl_fill((struct dma_buf_te_ioctl_fill __user *)arg); default: return -ENOTTY; } } static const struct file_operations dma_buf_te_fops = { .owner = THIS_MODULE, .unlocked_ioctl = dma_buf_te_ioctl, .compat_ioctl = dma_buf_te_ioctl, }; static int __init dma_buf_te_init(void) { int res; te_device.minor = MISC_DYNAMIC_MINOR; te_device.name = "dma_buf_te"; te_device.fops = &dma_buf_te_fops; res = misc_register(&te_device); if (res) { printk(KERN_WARNING"Misc device registration failed of 'dma_buf_te'\n"); return res; } te_device.this_device->coherent_dma_mask = DMA_BIT_MASK(32); dev_info(te_device.this_device, "dma_buf_te ready\n"); return 0; } static void __exit dma_buf_te_exit(void) { misc_deregister(&te_device); } module_init(dma_buf_te_init); module_exit(dma_buf_te_exit); MODULE_LICENSE("GPL");