diff options
author | Kristofer Jonsson <kristofer.jonsson@arm.com> | 2020-08-20 17:25:23 +0200 |
---|---|---|
committer | Kristofer Jonsson <kristofer.jonsson@arm.com> | 2020-08-27 13:58:01 +0200 |
commit | 116a635581f292cb4882ea1a086f842904f85c3c (patch) | |
tree | 96ed12cebd8dbf9f1dc7b8f116be7d528779a2bb /kernel/ethosu_buffer.c | |
parent | cd13a572fe223fe95cd58c5b55b659885fb7b4cd (diff) | |
download | ethos-u-linux-driver-stack-116a635581f292cb4882ea1a086f842904f85c3c.tar.gz |
Change-Id: I14b6becc908a0ac215769c32ee9c43db192ae6c8
Diffstat (limited to 'kernel/ethosu_buffer.c')
-rw-r--r-- | kernel/ethosu_buffer.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/kernel/ethosu_buffer.c b/kernel/ethosu_buffer.c new file mode 100644 index 0000000..bcc7242 --- /dev/null +++ b/kernel/ethosu_buffer.c @@ -0,0 +1,309 @@ +/* + * (C) COPYRIGHT 2020 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 licence. + * + * 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. + * + * SPDX-License-Identifier: GPL-2.0-only + */ + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include "ethosu_buffer.h" + +#include "ethosu_device.h" +#include "uapi/ethosu.h" + +#include <linux/anon_inodes.h> +#include <linux/dma-mapping.h> +#include <linux/of_address.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/uaccess.h> + +/**************************************************************************** + * Variables + ****************************************************************************/ + +static int ethosu_buffer_release(struct inode *inode, + struct file *file); + +static int ethosu_buffer_mmap(struct file *file, + struct vm_area_struct *vma); + +static long ethosu_buffer_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg); + +static const struct file_operations ethosu_buffer_fops = { + .release = ðosu_buffer_release, + .mmap = ðosu_buffer_mmap, + .unlocked_ioctl = ðosu_buffer_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ðosu_buffer_ioctl, +#endif +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/* + * The 'dma-ranges' device tree property for shared dma memory does not seem + * to be fully supported for coherent memory. Therefor we apply the DMA range + * offset ourselves. + */ +static dma_addr_t ethosu_buffer_dma_ranges(struct device *dev, + dma_addr_t dma_addr) +{ + struct device_node *node = dev->of_node; + const __be32 *ranges; + int len; + int naddr; + int nsize; + int inc; + int i; + + if (!node) + return dma_addr; + + /* Get the #address-cells and #size-cells properties */ + naddr = of_n_addr_cells(node); + nsize = of_n_size_cells(node); + + /* Read the 'dma-ranges' property */ + ranges = of_get_property(node, "dma-ranges", &len); + if (!ranges || len <= 0) + return dma_addr; + + dev_dbg(dev, "ranges=%p, len=%d, naddr=%d, nsize=%d\n", + ranges, len, naddr, nsize); + + len /= sizeof(*ranges); + inc = naddr + naddr + nsize; + + for (i = 0; (i + inc) <= len; i += inc) { + dma_addr_t daddr; + dma_addr_t paddr; + dma_addr_t size; + + daddr = of_read_number(&ranges[i], naddr); + paddr = of_read_number(&ranges[i + naddr], naddr); + size = of_read_number(&ranges[i + naddr + naddr], nsize); + + dev_dbg(dev, "daddr=0x%llx, paddr=0x%llx, size=0x%llx\n", + daddr, paddr, size); + + if (dma_addr >= paddr && dma_addr < (paddr + size)) + return dma_addr + daddr - paddr; + } + + return dma_addr; +} + +static bool ethosu_buffer_verify(struct file *file) +{ + return file->f_op == ðosu_buffer_fops; +} + +static void ethosu_buffer_destroy(struct kref *kref) +{ + struct ethosu_buffer *buf = + container_of(kref, struct ethosu_buffer, kref); + + dev_info(buf->edev->dev, "Buffer destroy. handle=0x%pK\n", buf); + + dma_free_coherent(buf->edev->dev, buf->capacity, buf->cpu_addr, + buf->dma_addr_orig); + devm_kfree(buf->edev->dev, buf); +} + +static int ethosu_buffer_release(struct inode *inode, + struct file *file) +{ + struct ethosu_buffer *buf = file->private_data; + + dev_info(buf->edev->dev, "Buffer release. handle=0x%pK\n", buf); + + ethosu_buffer_put(buf); + + return 0; +} + +static int ethosu_buffer_mmap(struct file *file, + struct vm_area_struct *vma) +{ + struct ethosu_buffer *buf = file->private_data; + int ret; + + dev_info(buf->edev->dev, "Buffer mmap. handle=0x%pK\n", buf); + + ret = dma_mmap_coherent(buf->edev->dev, vma, buf->cpu_addr, + buf->dma_addr_orig, + buf->capacity); + + return ret; +} + +static long ethosu_buffer_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct ethosu_buffer *buf = file->private_data; + void __user *udata = (void __user *)arg; + int ret = -EINVAL; + + ret = mutex_lock_interruptible(&buf->edev->mutex); + if (ret) + return ret; + + dev_info(buf->edev->dev, "Ioctl. cmd=%u, arg=%lu\n", cmd, arg); + + switch (cmd) { + case ETHOSU_IOCTL_BUFFER_SET: { + struct ethosu_uapi_buffer uapi; + + if (copy_from_user(&uapi, udata, sizeof(uapi))) + break; + + dev_info(buf->edev->dev, + "Ioctl: Buffer set. size=%u, offset=%u\n", + uapi.size, uapi.offset); + + ret = ethosu_buffer_resize(buf, uapi.size, uapi.offset); + break; + } + case ETHOSU_IOCTL_BUFFER_GET: { + struct ethosu_uapi_buffer uapi; + + uapi.size = buf->size; + uapi.offset = buf->offset; + + dev_info(buf->edev->dev, + "Ioctl: Buffer get. size=%u, offset=%u\n", + uapi.size, uapi.offset); + + if (copy_to_user(udata, &uapi, sizeof(uapi))) + break; + + ret = 0; + break; + } + default: { + dev_err(buf->edev->dev, "Invalid ioctl. cmd=%u, arg=%lu", + cmd, arg); + break; + } + } + + mutex_unlock(&buf->edev->mutex); + + return ret; +} + +int ethosu_buffer_create(struct ethosu_device *edev, + size_t capacity) +{ + struct ethosu_buffer *buf; + int ret = -ENOMEM; + + buf = devm_kzalloc(edev->dev, sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf->edev = edev; + buf->capacity = capacity; + buf->offset = 0; + buf->size = 0; + kref_init(&buf->kref); + + buf->cpu_addr = dma_alloc_coherent(buf->edev->dev, capacity, + &buf->dma_addr_orig, GFP_KERNEL); + if (!buf->cpu_addr) + goto free_buf; + + buf->dma_addr = ethosu_buffer_dma_ranges(buf->edev->dev, + buf->dma_addr_orig); + + ret = anon_inode_getfd("ethosu-buffer", ðosu_buffer_fops, buf, + O_RDWR | O_CLOEXEC); + if (ret < 0) + goto free_dma; + + buf->file = fget(ret); + fput(buf->file); + + dev_info(buf->edev->dev, + "Buffer create. handle=0x%pK, capacity=%zu, cpu_addr=0x%pK, dma_addr=0x%llx, dma_addr_orig=0x%llx, phys_addr=0x%llx\n", + buf, capacity, buf->cpu_addr, buf->dma_addr, + buf->dma_addr_orig, virt_to_phys(buf->cpu_addr)); + + return ret; + +free_dma: + dma_free_coherent(buf->edev->dev, buf->capacity, buf->cpu_addr, + buf->dma_addr_orig); + +free_buf: + devm_kfree(buf->edev->dev, buf); + + return ret; +} + +struct ethosu_buffer *ethosu_buffer_get_from_fd(int fd) +{ + struct ethosu_buffer *buf; + struct file *file; + + file = fget(fd); + if (!file) + return ERR_PTR(-EINVAL); + + if (!ethosu_buffer_verify(file)) { + fput(file); + + return ERR_PTR(-EINVAL); + } + + buf = file->private_data; + ethosu_buffer_get(buf); + fput(file); + + return buf; +} + +void ethosu_buffer_get(struct ethosu_buffer *buf) +{ + kref_get(&buf->kref); +} + +void ethosu_buffer_put(struct ethosu_buffer *buf) +{ + kref_put(&buf->kref, ethosu_buffer_destroy); +} + +int ethosu_buffer_resize(struct ethosu_buffer *buf, + size_t size, + size_t offset) +{ + if ((size + offset) > buf->capacity) + return -EINVAL; + + buf->size = size; + buf->offset = offset; + + return 0; +} |