aboutsummaryrefslogtreecommitdiff
path: root/kernel/ethosu_buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/ethosu_buffer.c')
-rw-r--r--kernel/ethosu_buffer.c309
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 = &ethosu_buffer_release,
+ .mmap = &ethosu_buffer_mmap,
+ .unlocked_ioctl = &ethosu_buffer_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = &ethosu_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 == &ethosu_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", &ethosu_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;
+}