From 116a635581f292cb4882ea1a086f842904f85c3c Mon Sep 17 00:00:00 2001 From: Kristofer Jonsson Date: Thu, 20 Aug 2020 17:25:23 +0200 Subject: Initial commit Change-Id: I14b6becc908a0ac215769c32ee9c43db192ae6c8 --- kernel/.gitignore | 28 ++++ kernel/CMakeLists.txt | 45 ++++++ kernel/Kbuild | 28 ++++ kernel/Kconfig | 24 +++ kernel/ethosu_buffer.c | 309 ++++++++++++++++++++++++++++++++++++++ kernel/ethosu_buffer.h | 106 +++++++++++++ kernel/ethosu_core_interface.h | 93 ++++++++++++ kernel/ethosu_device.c | 268 +++++++++++++++++++++++++++++++++ kernel/ethosu_device.h | 73 +++++++++ kernel/ethosu_driver.c | 161 ++++++++++++++++++++ kernel/ethosu_inference.c | 332 +++++++++++++++++++++++++++++++++++++++++ kernel/ethosu_inference.h | 110 ++++++++++++++ kernel/ethosu_mailbox.c | 295 ++++++++++++++++++++++++++++++++++++ kernel/ethosu_mailbox.h | 107 +++++++++++++ kernel/ethosu_network.c | 201 +++++++++++++++++++++++++ kernel/ethosu_network.h | 81 ++++++++++ kernel/uapi/ethosu.h | 104 +++++++++++++ 17 files changed, 2365 insertions(+) create mode 100644 kernel/.gitignore create mode 100644 kernel/CMakeLists.txt create mode 100644 kernel/Kbuild create mode 100644 kernel/Kconfig create mode 100644 kernel/ethosu_buffer.c create mode 100644 kernel/ethosu_buffer.h create mode 100644 kernel/ethosu_core_interface.h create mode 100644 kernel/ethosu_device.c create mode 100644 kernel/ethosu_device.h create mode 100644 kernel/ethosu_driver.c create mode 100644 kernel/ethosu_inference.c create mode 100644 kernel/ethosu_inference.h create mode 100644 kernel/ethosu_mailbox.c create mode 100644 kernel/ethosu_mailbox.h create mode 100644 kernel/ethosu_network.c create mode 100644 kernel/ethosu_network.h create mode 100644 kernel/uapi/ethosu.h (limited to 'kernel') diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..dca5994 --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,28 @@ +# +# (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 +# + +*.cmd +*.ko +*.mod +*.mod.c +*.mod.o +*.o +/modules.order +/Module.symvers diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt new file mode 100644 index 0000000..705a30a --- /dev/null +++ b/kernel/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# (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 +# + +cmake_minimum_required(VERSION 3.0.2) + +# Set the project name and version +project("ethosu_kernel" VERSION 1.0) + +# Make sure KDIR is set +set(KDIR "" CACHE PATH "Path to Linux kernel sources") +if (NOT EXISTS ${KDIR}) + message(FATAL_ERROR "Can't build kernel module without KDIR.") +endif() + +# Depend on all h and c files +file(GLOB_RECURSE SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.c" "*.h") + +# Build the kernel module +add_custom_target(kernel ALL + COMMAND ${CMAKE_MAKE_PROGRAM} -C ${KDIR} M=${CMAKE_CURRENT_SOURCE_DIR} CONFIG_ETHOSU=m CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 modules + BYPRODUCTS ethosu.ko + DEPENDS ${SOURCES} Kbuild Kconfig + COMMENT "Building ethosu.ko" + VERBATIM) + +# Install the kernel object and headers +install(FILES ethosu.ko DESTINATION "modules") +install(FILES "uapi/ethosu.h" DESTINATION "include/uapi") diff --git a/kernel/Kbuild b/kernel/Kbuild new file mode 100644 index 0000000..933efee --- /dev/null +++ b/kernel/Kbuild @@ -0,0 +1,28 @@ +# +# (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 +# + +obj-$(CONFIG_ETHOSU) = ethosu.o + +ethosu-objs := ethosu_driver.o \ + ethosu_buffer.o \ + ethosu_device.o \ + ethosu_inference.o \ + ethosu_mailbox.o \ + ethosu_network.o diff --git a/kernel/Kconfig b/kernel/Kconfig new file mode 100644 index 0000000..695bd37 --- /dev/null +++ b/kernel/Kconfig @@ -0,0 +1,24 @@ +# +# (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 +# + +config ETHOSU + tristate "Arm Ethos-U NPU support" + help + Arm Ethos-U NPU driver. \ No newline at end of file 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 +#include +#include +#include +#include +#include + +/**************************************************************************** + * 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; +} diff --git a/kernel/ethosu_buffer.h b/kernel/ethosu_buffer.h new file mode 100644 index 0000000..14f26c2 --- /dev/null +++ b/kernel/ethosu_buffer.h @@ -0,0 +1,106 @@ +/* + * (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 + */ + +#ifndef ETHOSU_BUFFER_H +#define ETHOSU_BUFFER_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Types + ****************************************************************************/ + +struct ethosu_device; +struct device; + +/** + * struct ethosu_buffer - Buffer + * @dev: Device + * @file: File + * @kref: Reference counting + * @capacity: Maximum capacity of the buffer + * @offset: Offset to first byte of buffer + * @size: Size of the data in the buffer + * @cpu_addr: Kernel mapped address + * @dma_addr: DMA address + * @dma_addr_orig: Original DMA address before range mapping + * + * 'offset + size' must not be larger than 'capacity'. + */ +struct ethosu_buffer { + struct ethosu_device *edev; + struct file *file; + struct kref kref; + size_t capacity; + size_t offset; + size_t size; + void *cpu_addr; + dma_addr_t dma_addr; + dma_addr_t dma_addr_orig; +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/** + * ethosu_buffer_create() - Create buffer + * + * This function must be called in the context of a user space process. + * + * Return: fd on success, else error code. + */ +int ethosu_buffer_create(struct ethosu_device *edev, + size_t capacity); + +/** + * ethosu_buffer_get_from_fd() - Get buffer handle from fd + * + * This function must be called from a user space context. + * + * Return: Pointer on success, else ERR_PTR. + */ +struct ethosu_buffer *ethosu_buffer_get_from_fd(int fd); + +/** + * ethosu_buffer_get() - Put buffer + */ +void ethosu_buffer_get(struct ethosu_buffer *buf); + +/** + * ethosu_buffer_put() - Put buffer + */ +void ethosu_buffer_put(struct ethosu_buffer *buf); + +/** + * ethosu_buffer_resize() - Resize and validate buffer + * + * Return: 0 on success, else error code. + */ +int ethosu_buffer_resize(struct ethosu_buffer *buf, + size_t size, + size_t offset); + +#endif /* ETHOSU_BUFFER_H */ diff --git a/kernel/ethosu_core_interface.h b/kernel/ethosu_core_interface.h new file mode 100644 index 0000000..827ce4f --- /dev/null +++ b/kernel/ethosu_core_interface.h @@ -0,0 +1,93 @@ +/* + * (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 + */ + +#ifndef ETHOSU_CORE_INTERFACE_H +#define ETHOSU_CORE_INTERFACE_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/** + * enum ethosu_core_msg_type - Message types + * + * Types for the messages sent between the host and the core subsystem. + */ +enum ethosu_core_msg_type { + ETHOSU_CORE_MSG_PING = 1, + ETHOSU_CORE_MSG_PONG, + ETHOSU_CORE_MSG_INFERENCE_REQ, + ETHOSU_CORE_MSG_INFERENCE_RSP, + ETHOSU_CORE_MSG_MAX +}; + +/** + * struct ethosu_core_msg - Message header + */ +struct ethosu_core_msg { + uint32_t type; + uint32_t length; +}; + +/** + * struct ethosu_core_queue_header - Message queue header + */ +struct ethosu_core_queue_header { + uint32_t size; + uint32_t read; + uint32_t write; +}; + +/** + * struct ethosu_core_queue - Message queue + * + * Dynamically sized message queue. + */ +struct ethosu_core_queue { + struct ethosu_core_queue_header header; + uint8_t data[]; +}; + +enum ethosu_core_status { + ETHOSU_CORE_STATUS_OK, + ETHOSU_CORE_STATUS_ERROR +}; + +struct ethosu_core_buffer { + uint32_t ptr; + uint32_t size; +}; + +struct ethosu_core_inference_req { + uint64_t user_arg; + struct ethosu_core_buffer ifm; + struct ethosu_core_buffer ofm; + struct ethosu_core_buffer network; +}; + +struct ethosu_core_inference_rsp { + uint64_t user_arg; + uint32_t ofm_size; + uint32_t status; +}; + +#endif diff --git a/kernel/ethosu_device.c b/kernel/ethosu_device.c new file mode 100644 index 0000000..5cdea2c --- /dev/null +++ b/kernel/ethosu_device.c @@ -0,0 +1,268 @@ +/* + * (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_device.h" + +#include "ethosu_buffer.h" +#include "ethosu_core_interface.h" +#include "ethosu_inference.h" +#include "ethosu_network.h" +#include "uapi/ethosu.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Defines + ****************************************************************************/ + +#define MINOR_VERSION 0 /* Minor version starts at 0 */ +#define MINOR_COUNT 1 /* Allocate 1 minor version */ +#define DMA_ADDR_BITS 32 /* Number of address bits */ + +/**************************************************************************** + * Types + ****************************************************************************/ + +/**************************************************************************** + * Functions + ****************************************************************************/ + +static int ethosu_handle_msg(struct ethosu_device *edev) +{ + struct ethosu_core_msg header; + + union { + struct ethosu_core_inference_rsp inf; + } data; + int ret; + + /* Read message */ + ret = ethosu_mailbox_read(&edev->mailbox, &header, &data, sizeof(data)); + if (ret) + return ret; + + switch (header.type) { + case ETHOSU_CORE_MSG_PING: + dev_info(edev->dev, "Msg: Ping\n"); + ret = ethosu_mailbox_ping(&edev->mailbox); + break; + case ETHOSU_CORE_MSG_PONG: + dev_info(edev->dev, "Msg: Pong\n"); + break; + case ETHOSU_CORE_MSG_INFERENCE_RSP: + dev_info(edev->dev, + "Msg: Inference response. user_arg=0x%llx, ofm_size=%u, status=%u\n", + data.inf.user_arg, data.inf.ofm_size, + data.inf.status); + ethosu_inference_rsp(edev, &data.inf); + break; + default: + dev_warn(edev->dev, + "Msg: Unsupported msg type. type=%u, length=%u", + header.type, header.length); + break; + } + + return ret; +} + +static int ethosu_open(struct inode *inode, + struct file *file) +{ + struct ethosu_device *edev = + container_of(inode->i_cdev, struct ethosu_device, cdev); + + file->private_data = edev; + + dev_info(edev->dev, "Opening device node.\n"); + + return nonseekable_open(inode, file); +} + +static long ethosu_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct ethosu_device *edev = file->private_data; + void __user *udata = (void __user *)arg; + int ret = -EINVAL; + + ret = mutex_lock_interruptible(&edev->mutex); + if (ret) + return ret; + + dev_info(edev->dev, "Ioctl. cmd=%u, arg=%lu\n", cmd, arg); + + switch (cmd) { + case ETHOSU_IOCTL_PING: { + dev_info(edev->dev, "Ioctl: Send ping\n"); + ret = ethosu_mailbox_ping(&edev->mailbox); + break; + } + case ETHOSU_IOCTL_BUFFER_CREATE: { + struct ethosu_uapi_buffer_create uapi; + + dev_info(edev->dev, "Ioctl: Buffer create\n"); + + if (copy_from_user(&uapi, udata, sizeof(uapi))) + break; + + dev_info(edev->dev, "Ioctl: Buffer. capacity=%u\n", + uapi.capacity); + + ret = ethosu_buffer_create(edev, uapi.capacity); + break; + } + case ETHOSU_IOCTL_NETWORK_CREATE: { + struct ethosu_uapi_network_create uapi; + + if (copy_from_user(&uapi, udata, sizeof(uapi))) + break; + + dev_info(edev->dev, "Ioctl: Network. fd=%u\n", uapi.fd); + + ret = ethosu_network_create(edev, &uapi); + break; + } + default: { + dev_err(edev->dev, "Invalid ioctl. cmd=%u, arg=%lu", + cmd, arg); + break; + } + } + + mutex_unlock(&edev->mutex); + + return ret; +} + +static void ethosu_mbox_rx(void *user_arg) +{ + struct ethosu_device *edev = user_arg; + int ret; + + mutex_lock(&edev->mutex); + + do { + ret = ethosu_handle_msg(edev); + } while (ret == 0); + + mutex_unlock(&edev->mutex); +} + +int ethosu_dev_init(struct ethosu_device *edev, + struct device *dev, + struct class *class, + struct resource *in_queue, + struct resource *out_queue) +{ + static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = ðosu_open, + .unlocked_ioctl = ðosu_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ðosu_ioctl, +#endif + }; + struct device *sysdev; + int ret; + + edev->dev = dev; + edev->class = class; + mutex_init(&edev->mutex); + INIT_LIST_HEAD(&edev->inference_list); + + ret = of_reserved_mem_device_init(edev->dev); + if (ret) + return ret; + + dma_set_mask_and_coherent(edev->dev, DMA_BIT_MASK(DMA_ADDR_BITS)); + + ret = ethosu_mailbox_init(&edev->mailbox, dev, in_queue, out_queue, + ethosu_mbox_rx, edev); + if (ret) + goto release_reserved_mem; + + ret = alloc_chrdev_region(&edev->devt, MINOR_VERSION, MINOR_COUNT, + "ethosu"); + if (ret) { + dev_err(edev->dev, "Failed to allocate chrdev region.\n"); + goto deinit_mailbox; + } + + cdev_init(&edev->cdev, &fops); + edev->cdev.owner = THIS_MODULE; + + ret = cdev_add(&edev->cdev, edev->devt, MINOR_COUNT); + if (ret) { + dev_err(edev->dev, "Failed to add character device.\n"); + goto region_unregister; + } + + sysdev = device_create(edev->class, NULL, edev->devt, edev, + "ethosu%d", MAJOR(edev->devt)); + if (IS_ERR(sysdev)) { + dev_err(edev->dev, "Failed to create device.\n"); + ret = PTR_ERR(sysdev); + goto del_cdev; + } + + dev_info(edev->dev, + "Created Arm Ethos-U device. name=%s, major=%d, minor=%d\n", + dev_name(sysdev), MAJOR(edev->devt), MINOR(edev->devt)); + + return 0; + +del_cdev: + cdev_del(&edev->cdev); + +region_unregister: + unregister_chrdev_region(edev->devt, 1); + +deinit_mailbox: + ethosu_mailbox_deinit(&edev->mailbox); + +release_reserved_mem: + of_reserved_mem_device_release(edev->dev); + + return ret; +} + +void ethosu_dev_deinit(struct ethosu_device *edev) +{ + ethosu_mailbox_deinit(&edev->mailbox); + device_destroy(edev->class, edev->cdev.dev); + cdev_del(&edev->cdev); + unregister_chrdev_region(edev->devt, MINOR_COUNT); + of_reserved_mem_device_release(edev->dev); + + dev_info(edev->dev, "%s\n", __FUNCTION__); +} diff --git a/kernel/ethosu_device.h b/kernel/ethosu_device.h new file mode 100644 index 0000000..4e4f59d --- /dev/null +++ b/kernel/ethosu_device.h @@ -0,0 +1,73 @@ +/* + * (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 + */ + +#ifndef ETHOSU_DEVICE_H +#define ETHOSU_DEVICE_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include "ethosu_mailbox.h" + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Types + ****************************************************************************/ + +/** + * struct ethosu_device - Device structure + */ +struct ethosu_device { + struct device *dev; + struct cdev cdev; + struct class *class; + dev_t devt; + struct mutex mutex; + struct ethosu_mailbox mailbox; + struct list_head inference_list; +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/** + * ethosu_dev_init() - Initialize the device + * + * Return: 0 on success, else error code. + */ +int ethosu_dev_init(struct ethosu_device *edev, + struct device *dev, + struct class *class, + struct resource *in_queue, + struct resource *out_queue); + +/** + * ethosu_dev_deinit() - Initialize the device + */ +void ethosu_dev_deinit(struct ethosu_device *edev); + +#endif /* ETHOSU_DEVICE_H */ diff --git a/kernel/ethosu_driver.c b/kernel/ethosu_driver.c new file mode 100644 index 0000000..3fc752b --- /dev/null +++ b/kernel/ethosu_driver.c @@ -0,0 +1,161 @@ +/* + * (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 + */ + +#include +#include +#include +#include +#include + +#include "ethosu_device.h" + +/**************************************************************************** + * Defines + ****************************************************************************/ + +#define ETHOSU_DRIVER_VERSION "1.0" +#define ETHOSU_DRIVER_NAME "ethosu" + +/**************************************************************************** + * Variables + ****************************************************************************/ + +struct class *ethosu_class; + +/**************************************************************************** + * Arm Ethos-U + ****************************************************************************/ + +static int ethosu_pdev_probe(struct platform_device *pdev) +{ + struct ethosu_device *edev; + struct resource *in_queue_res; + struct resource *out_queue_res; + int ret; + + dev_info(&pdev->dev, "Probe\n"); + + /* Get path to TCM memory */ + in_queue_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "in_queue"); + if (IS_ERR(in_queue_res)) { + dev_err(&pdev->dev, "Failed to get in_queue resource.\n"); + + return PTR_ERR(in_queue_res); + } + + out_queue_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "out_queue"); + if (IS_ERR(out_queue_res)) { + dev_err(&pdev->dev, "Failed to get out_queue resource.\n"); + + return PTR_ERR(out_queue_res); + } + + /* Allocate memory for Arm Ethos-U device */ + edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + + platform_set_drvdata(pdev, edev); + + /* Initialize device */ + ret = ethosu_dev_init(edev, &pdev->dev, ethosu_class, in_queue_res, + out_queue_res); + if (ret) + goto free_dev; + + return 0; + +free_dev: + devm_kfree(&pdev->dev, edev); + + return ret; +} + +static int ethosu_pdev_remove(struct platform_device *pdev) +{ + struct ethosu_device *edev = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "Remove\n"); + + ethosu_dev_deinit(edev); + + return 0; +} + +static const struct of_device_id ethosu_pdev_match[] = { + { .compatible = "arm,ethosu" }, + { /* Sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, ethosu_pdev_match); + +static struct platform_driver ethosu_pdev_driver = { + .probe = ðosu_pdev_probe, + .remove = ðosu_pdev_remove, + .driver = { + .name = ETHOSU_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ethosu_pdev_match), + }, +}; + +/**************************************************************************** + * Module init and exit + ****************************************************************************/ + +static int __init ethosu_init(void) +{ + int ret; + + ethosu_class = class_create(THIS_MODULE, ETHOSU_DRIVER_NAME); + if (IS_ERR(ethosu_class)) { + printk("Failed to create class '%s'.\n", ETHOSU_DRIVER_NAME); + + return PTR_ERR(ethosu_class); + } + + ret = platform_driver_register(ðosu_pdev_driver); + if (ret) { + printk("Failed to register Arm Ethos-U platform driver.\n"); + goto destroy_class; + } + + return 0; + +destroy_class: + class_destroy(ethosu_class); + + return ret; +} + +static void __exit ethosu_exit(void) +{ + platform_driver_unregister(ðosu_pdev_driver); + class_destroy(ethosu_class); +} + +module_init(ethosu_init) +module_exit(ethosu_exit) +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Arm Ltd"); +MODULE_DESCRIPTION("Arm Ethos-U NPU Driver"); +MODULE_VERSION(ETHOSU_DRIVER_VERSION); diff --git a/kernel/ethosu_inference.c b/kernel/ethosu_inference.c new file mode 100644 index 0000000..8efc22d --- /dev/null +++ b/kernel/ethosu_inference.c @@ -0,0 +1,332 @@ +/* + * (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_inference.h" + +#include "ethosu_buffer.h" +#include "ethosu_core_interface.h" +#include "ethosu_device.h" +#include "ethosu_network.h" +#include "uapi/ethosu.h" + +#include +#include +#include +#include + +/**************************************************************************** + * Variables + ****************************************************************************/ + +static int ethosu_inference_release(struct inode *inode, + struct file *file); + +static unsigned int ethosu_inference_poll(struct file *file, + poll_table *wait); + +static long ethosu_inference_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg); + +static const struct file_operations ethosu_inference_fops = { + .release = ðosu_inference_release, + .poll = ðosu_inference_poll, + .unlocked_ioctl = ðosu_inference_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ðosu_inference_ioctl, +#endif +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +static const char *status_to_string(const enum ethosu_uapi_status status) +{ + switch (status) { + case ETHOSU_UAPI_STATUS_OK: { + return "Ok"; + } + case ETHOSU_UAPI_STATUS_ERROR: { + return "Error"; + } + default: { + return "Unknown"; + } + } +} + +static int ethosu_inference_send(struct ethosu_inference *inf) +{ + int ret; + + if (inf->pending) + return -EINVAL; + + inf->status = ETHOSU_UAPI_STATUS_ERROR; + + ret = ethosu_mailbox_inference(&inf->edev->mailbox, inf, inf->ifm, + inf->ofm, inf->net->buf); + if (ret) + return ret; + + inf->pending = true; + + ethosu_inference_get(inf); + + return 0; +} + +static int ethosu_inference_find(struct ethosu_inference *inf, + struct list_head *inference_list) +{ + struct ethosu_inference *cur; + + list_for_each_entry(cur, inference_list, list) { + if (cur == inf) + return 0; + } + + return -EINVAL; +} + +static bool ethosu_inference_verify(struct file *file) +{ + return file->f_op == ðosu_inference_fops; +} + +static void ethosu_inference_kref_destroy(struct kref *kref) +{ + struct ethosu_inference *inf = + container_of(kref, struct ethosu_inference, kref); + + dev_info(inf->edev->dev, + "Inference destroy. handle=0x%pK, status=%d\n", + inf, inf->status); + + list_del(&inf->list); + ethosu_buffer_put(inf->ifm); + ethosu_buffer_put(inf->ofm); + ethosu_network_put(inf->net); + devm_kfree(inf->edev->dev, inf); +} + +static int ethosu_inference_release(struct inode *inode, + struct file *file) +{ + struct ethosu_inference *inf = file->private_data; + + dev_info(inf->edev->dev, + "Inference release. handle=0x%pK, status=%d\n", + inf, inf->status); + + ethosu_inference_put(inf); + + return 0; +} + +static unsigned int ethosu_inference_poll(struct file *file, + poll_table *wait) +{ + struct ethosu_inference *inf = file->private_data; + int ret = 0; + + poll_wait(file, &inf->waitq, wait); + + if (!inf->pending) + ret |= POLLIN; + + return ret; +} + +static long ethosu_inference_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct ethosu_inference *inf = file->private_data; + int ret = -EINVAL; + + ret = mutex_lock_interruptible(&inf->edev->mutex); + if (ret) + return ret; + + dev_info(inf->edev->dev, "Ioctl: cmd=%u, arg=%lu\n", cmd, arg); + + switch (cmd) { + case ETHOSU_IOCTL_INFERENCE_STATUS: { + ret = inf->status; + + dev_info(inf->edev->dev, + "Ioctl: Inference status. status=%s (%d)\n", + status_to_string(ret), ret); + break; + } + default: { + dev_err(inf->edev->dev, "Invalid ioctl. cmd=%u, arg=%lu", + cmd, arg); + break; + } + } + + mutex_unlock(&inf->edev->mutex); + + return ret; +} + +int ethosu_inference_create(struct ethosu_device *edev, + struct ethosu_network *net, + struct ethosu_uapi_inference_create *uapi) +{ + struct ethosu_inference *inf; + int fd; + int ret = -ENOMEM; + + inf = devm_kzalloc(edev->dev, sizeof(*inf), GFP_KERNEL); + if (!inf) + return -ENOMEM; + + inf->edev = edev; + inf->net = net; + inf->pending = false; + inf->status = ETHOSU_UAPI_STATUS_ERROR; + kref_init(&inf->kref); + init_waitqueue_head(&inf->waitq); + + /* Get pointer to IFM buffer */ + inf->ifm = ethosu_buffer_get_from_fd(uapi->ifm_fd); + if (IS_ERR(inf->ifm)) { + ret = PTR_ERR(inf->ifm); + goto free_inf; + } + + /* Get pointer to OFM buffer */ + inf->ofm = ethosu_buffer_get_from_fd(uapi->ofm_fd); + if (IS_ERR(inf->ofm)) { + ret = PTR_ERR(inf->ofm); + goto put_ifm; + } + + /* Increment network reference count */ + ethosu_network_get(net); + + /* Create file descriptor */ + ret = fd = anon_inode_getfd("ethosu-inference", ðosu_inference_fops, + inf, O_RDWR | O_CLOEXEC); + if (ret < 0) + goto put_net; + + /* Store pointer to file structure */ + inf->file = fget(ret); + fput(inf->file); + + /* Add inference to inference list */ + list_add(&inf->list, &edev->inference_list); + + /* Send inference request to Arm Ethos-U subsystem */ + (void)ethosu_inference_send(inf); + + dev_info(edev->dev, "Inference create. handle=0x%pK, fd=%d", + inf, fd); + + return fd; + +put_net: + ethosu_network_put(inf->net); + ethosu_buffer_put(inf->ofm); + +put_ifm: + ethosu_buffer_put(inf->ifm); + +free_inf: + devm_kfree(edev->dev, inf); + + return ret; +} + +struct ethosu_inference *ethosu_inference_get_from_fd(int fd) +{ + struct ethosu_inference *inf; + struct file *file; + + file = fget(fd); + if (!file) + return ERR_PTR(-EINVAL); + + if (!ethosu_inference_verify(file)) { + fput(file); + + return ERR_PTR(-EINVAL); + } + + inf = file->private_data; + ethosu_inference_get(inf); + fput(file); + + return inf; +} + +void ethosu_inference_get(struct ethosu_inference *inf) +{ + kref_get(&inf->kref); +} + +void ethosu_inference_put(struct ethosu_inference *inf) +{ + kref_put(&inf->kref, ðosu_inference_kref_destroy); +} + +void ethosu_inference_rsp(struct ethosu_device *edev, + struct ethosu_core_inference_rsp *rsp) +{ + struct ethosu_inference *inf = + (struct ethosu_inference *)rsp->user_arg; + int ret; + + ret = ethosu_inference_find(inf, &edev->inference_list); + if (ret) { + dev_warn(edev->dev, + "Handle not found in inference list. handle=0x%p\n", + rsp); + + return; + } + + inf->pending = false; + + if (rsp->status == ETHOSU_CORE_STATUS_OK) { + inf->status = ETHOSU_UAPI_STATUS_OK; + + ret = ethosu_buffer_resize(inf->ofm, + inf->ofm->size + rsp->ofm_size, + inf->ofm->offset); + if (ret) + inf->status = ETHOSU_UAPI_STATUS_ERROR; + } else { + inf->status = ETHOSU_UAPI_STATUS_ERROR; + } + + wake_up_interruptible(&inf->waitq); + + ethosu_inference_put(inf); +} diff --git a/kernel/ethosu_inference.h b/kernel/ethosu_inference.h new file mode 100644 index 0000000..b42f5ca --- /dev/null +++ b/kernel/ethosu_inference.h @@ -0,0 +1,110 @@ +/* + * (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 + */ + +#ifndef ETHOSU_INFERENCE_H +#define ETHOSU_INFERENCE_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include "uapi/ethosu.h" + +#include +#include +#include + +/**************************************************************************** + * Types + ****************************************************************************/ + +struct ethosu_buffer; +struct ethosu_core_inference_rsp; +struct ethosu_device; +struct ethosu_network; +struct ethosu_uapi_inference_create; +struct file; + +/** + * struct ethosu_inference - Inference struct + * @edev: Arm Ethos-U device + * @file: File handle + * @kref: Reference counter + * @waitq: Wait queue + * @ifm: Pointer to IFM buffer + * @ofm: Pointer to OFM buffer + * @net: Pointer to network + * @pending: Pending response from the firmware + * @status: Inference status + */ +struct ethosu_inference { + struct ethosu_device *edev; + struct file *file; + struct kref kref; + wait_queue_head_t waitq; + struct ethosu_buffer *ifm; + struct ethosu_buffer *ofm; + struct ethosu_network *net; + bool pending; + enum ethosu_uapi_status status; + struct list_head list; +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/** + * ethosu_inference_create() - Create inference + * + * This function must be called in the context of a user space process. + * + * Return: fd on success, else error code. + */ +int ethosu_inference_create(struct ethosu_device *edev, + struct ethosu_network *net, + struct ethosu_uapi_inference_create *uapi); + +/** + * ethosu_inference_get_from_fd() - Get inference handle from fd + * + * This function must be called from a user space context. + * + * Return: Pointer on success, else ERR_PTR. + */ +struct ethosu_inference *ethosu_inference_get_from_fd(int fd); + +/** + * ethosu_inference_get() - Get inference + */ +void ethosu_inference_get(struct ethosu_inference *inf); + +/** + * ethosu_inference_put() - Put inference + */ +void ethosu_inference_put(struct ethosu_inference *inf); + +/** + * ethosu_inference_rsp() - Handle inference response + */ +void ethosu_inference_rsp(struct ethosu_device *edev, + struct ethosu_core_inference_rsp *rsp); + +#endif /* ETHOSU_INFERENCE_H */ diff --git a/kernel/ethosu_mailbox.c b/kernel/ethosu_mailbox.c new file mode 100644 index 0000000..23a356e --- /dev/null +++ b/kernel/ethosu_mailbox.c @@ -0,0 +1,295 @@ +/* + * (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_mailbox.h" + +#include "ethosu_buffer.h" +#include "ethosu_core_interface.h" +#include "ethosu_device.h" + +#include +#include + +/**************************************************************************** + * Functions + ****************************************************************************/ + +static void ethosu_core_set_size(struct ethosu_buffer *buf, + struct ethosu_core_buffer *cbuf) +{ + cbuf->ptr = (uint32_t)buf->dma_addr + buf->offset; + cbuf->size = (uint32_t)buf->size; +} + +static void ethosu_core_set_capacity(struct ethosu_buffer *buf, + struct ethosu_core_buffer *cbuf) +{ + cbuf->ptr = (uint32_t)buf->dma_addr + buf->offset + buf->size; + cbuf->size = (uint32_t)buf->capacity - buf->offset - buf->size; +} + +static size_t ethosu_queue_available(struct ethosu_core_queue *queue) +{ + size_t size = queue->header.write - queue->header.read; + + if (queue->header.read > queue->header.write) + size += queue->header.size; + + return size; +} + +static size_t ethosu_queue_capacity(struct ethosu_core_queue *queue) +{ + return queue->header.size - ethosu_queue_available(queue); +} + +static int ethosu_queue_write(struct ethosu_mailbox *mbox, + const struct kvec *vec, + size_t length) +{ + struct ethosu_core_queue *queue = mbox->in_queue; + uint8_t *dst = &queue->data[0]; + uint32_t wpos = queue->header.write; + size_t total_size; + size_t i; + int ret; + + for (i = 0, total_size = 0; i < length; i++) + total_size += vec[i].iov_len; + + if (total_size > ethosu_queue_capacity(queue)) + return -EINVAL; + + for (i = 0; i < length; i++) { + const uint8_t *src = vec[i].iov_base; + const uint8_t *end = src + vec[i].iov_len; + + while (src < end) { + dst[wpos] = *src++; + wpos = (wpos + 1) % queue->header.size; + } + } + + queue->header.write = wpos; + + ret = mbox_send_message(mbox->tx, queue); + if (ret < 0) + return ret; + + return 0; +} + +static int ethosu_queue_write_msg(struct ethosu_mailbox *mbox, + uint32_t type, + void *data, + size_t length) +{ + struct ethosu_core_msg msg = { .type = type, .length = length }; + const struct kvec vec[2] = { + { &msg, sizeof(msg) }, + { data, length } + }; + + return ethosu_queue_write(mbox, vec, 2); +} + +static int ethosu_queue_read(struct ethosu_mailbox *mbox, + void *data, + size_t length) +{ + struct ethosu_core_queue *queue = mbox->out_queue; + uint8_t *src = &queue->data[0]; + uint8_t *dst = (uint8_t *)data; + const uint8_t *end = dst + length; + uint32_t rpos = queue->header.read; + + if (length > ethosu_queue_available(queue)) + return -ENOMSG; + + while (dst < end) { + *dst++ = src[rpos]; + rpos = (rpos + 1) % queue->header.size; + } + + queue->header.read = rpos; + + return 0; +} + +int ethosu_mailbox_read(struct ethosu_mailbox *mbox, + struct ethosu_core_msg *header, + void *data, + size_t length) +{ + int ret; + + /* Read message header */ + ret = ethosu_queue_read(mbox, header, sizeof(*header)); + if (ret) + return ret; + + dev_info(mbox->dev, "mbox: Read msg header. type=%u, length=%u", + header->type, header->length); + + /* Check that payload is not larger than allocated buffer */ + if (header->length > length) + return -ENOMEM; + + /* Ready payload data */ + ret = ethosu_queue_read(mbox, data, header->length); + if (ret) + return -EBADMSG; + + return 0; +} + +int ethosu_mailbox_ping(struct ethosu_mailbox *mbox) +{ + return ethosu_queue_write_msg(mbox, ETHOSU_CORE_MSG_PING, NULL, 0); +} + +int ethosu_mailbox_inference(struct ethosu_mailbox *mbox, + void *user_arg, + struct ethosu_buffer *ifm, + struct ethosu_buffer *ofm, + struct ethosu_buffer *network) +{ + struct ethosu_core_inference_req inf; + + inf.user_arg = (ptrdiff_t)user_arg; + ethosu_core_set_size(ifm, &inf.ifm); + ethosu_core_set_capacity(ofm, &inf.ofm); + ethosu_core_set_size(network, &inf.network); + + return ethosu_queue_write_msg(mbox, ETHOSU_CORE_MSG_INFERENCE_REQ, + &inf, sizeof(inf)); +} + +static void ethosu_mailbox_rx_work(struct work_struct *work) +{ + struct ethosu_mailbox *mbox = container_of(work, typeof(*mbox), work); + + mbox->callback(mbox->user_arg); +} + +static void ethosu_mailbox_rx_callback(struct mbox_client *client, + void *message) +{ + struct ethosu_mailbox *mbox = + container_of(client, typeof(*mbox), client); + + dev_info(mbox->dev, "mbox: Received message.\n"); + + queue_work(mbox->wq, &mbox->work); +} + +static void ethosu_mailbox_tx_done(struct mbox_client *client, + void *message, + int r) +{ + if (r) + dev_warn(client->dev, "mbox: Failed sending message (%d)\n", r); + else + dev_info(client->dev, "mbox: Message sent\n"); +} + +int ethosu_mailbox_init(struct ethosu_mailbox *mbox, + struct device *dev, + struct resource *in_queue, + struct resource *out_queue, + ethosu_mailbox_cb callback, + void *user_arg) +{ + int ret; + + mbox->dev = dev; + mbox->callback = callback; + mbox->user_arg = user_arg; + + mbox->client.dev = dev; + mbox->client.rx_callback = ethosu_mailbox_rx_callback; + mbox->client.tx_prepare = NULL; /* preparation of data is handled + * through the + * queue functions */ + mbox->client.tx_done = ethosu_mailbox_tx_done; + mbox->client.tx_block = true; + mbox->client.knows_txdone = false; + mbox->client.tx_tout = 500; + + mbox->in_queue = devm_ioremap_resource(mbox->dev, in_queue); + if (IS_ERR(mbox->in_queue)) + return PTR_ERR(mbox->in_queue); + + mbox->out_queue = devm_ioremap_resource(mbox->dev, out_queue); + if (IS_ERR(mbox->out_queue)) { + ret = PTR_ERR(mbox->out_queue); + goto unmap_in_queue; + } + + mbox->wq = create_singlethread_workqueue("ethosu_workqueue"); + if (!mbox->wq) { + dev_err(mbox->dev, "Failed to create work queue\n"); + ret = -EINVAL; + goto unmap_out_queue; + } + + INIT_WORK(&mbox->work, ethosu_mailbox_rx_work); + + mbox->tx = mbox_request_channel_byname(&mbox->client, "tx"); + if (IS_ERR(mbox->tx)) { + dev_warn(mbox->dev, "mbox: Failed to request tx channel\n"); + ret = PTR_ERR(mbox->tx); + goto workqueue_destroy; + } + + mbox->rx = mbox_request_channel_byname(&mbox->client, "rx"); + if (IS_ERR(mbox->rx)) { + dev_info(dev, "mbox: Using same channel for RX and TX\n"); + mbox->rx = mbox->tx; + } + + return 0; + +workqueue_destroy: + destroy_workqueue(mbox->wq); + +unmap_out_queue: + devm_iounmap(mbox->dev, mbox->out_queue); + +unmap_in_queue: + devm_iounmap(mbox->dev, mbox->in_queue); + + return ret; +} + +void ethosu_mailbox_deinit(struct ethosu_mailbox *mbox) +{ + if (mbox->rx != mbox->tx) + mbox_free_channel(mbox->rx); + + mbox_free_channel(mbox->tx); + destroy_workqueue(mbox->wq); + devm_iounmap(mbox->dev, mbox->out_queue); + devm_iounmap(mbox->dev, mbox->in_queue); +} diff --git a/kernel/ethosu_mailbox.h b/kernel/ethosu_mailbox.h new file mode 100644 index 0000000..c5865d0 --- /dev/null +++ b/kernel/ethosu_mailbox.h @@ -0,0 +1,107 @@ +/* + * (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 + */ + +#ifndef ETHOSU_MAILBOX_H +#define ETHOSU_MAILBOX_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Types + ****************************************************************************/ + +struct device; +struct ethosu_buffer; +struct ethosu_device; +struct ethosu_core_msg; +struct ethosu_core_queue; +struct resource; + +typedef void (*ethosu_mailbox_cb)(void *user_arg); + +struct ethosu_mailbox { + struct device *dev; + struct workqueue_struct *wq; + struct work_struct work; + struct ethosu_core_queue __iomem *in_queue; + struct ethosu_core_queue __iomem *out_queue; + struct mbox_client client; + struct mbox_chan *rx; + struct mbox_chan *tx; + ethosu_mailbox_cb callback; + void *user_arg; +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/** + * ethosu_mailbox_init() - Initialize mailbox + * + * Return: 0 on success, else error code. + */ +int ethosu_mailbox_init(struct ethosu_mailbox *mbox, + struct device *dev, + struct resource *in_queue, + struct resource *out_queue, + ethosu_mailbox_cb callback, + void *user_arg); + +/** + * ethosu_mailbox_deinit() - Deinitialize mailbox + */ +void ethosu_mailbox_deinit(struct ethosu_mailbox *mbox); + +/** + * ethosu_mailbox_read() - Read message from mailbox + * + * Return: 0 message read, else error code. + */ +int ethosu_mailbox_read(struct ethosu_mailbox *mbox, + struct ethosu_core_msg *header, + void *data, + size_t length); + +/** + * ethosu_mailbox_ping() - Send ping message + * + * Return: 0 on success, else error code. + */ +int ethosu_mailbox_ping(struct ethosu_mailbox *mbox); + +/** + * ethosu_mailbox_inference() - Send inference + * + * Return: 0 on success, else error code. + */ +int ethosu_mailbox_inference(struct ethosu_mailbox *mbox, + void *user_arg, + struct ethosu_buffer *ifm, + struct ethosu_buffer *ofm, + struct ethosu_buffer *network); + +#endif /* ETHOSU_MAILBOX_H */ diff --git a/kernel/ethosu_network.c b/kernel/ethosu_network.c new file mode 100644 index 0000000..851d4b7 --- /dev/null +++ b/kernel/ethosu_network.c @@ -0,0 +1,201 @@ +/* + * (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_network.h" + +#include "ethosu_buffer.h" +#include "ethosu_device.h" +#include "ethosu_inference.h" +#include "uapi/ethosu.h" + +#include +#include +#include +#include + +/**************************************************************************** + * Variables + ****************************************************************************/ + +static int ethosu_network_release(struct inode *inode, + struct file *file); + +static long ethosu_network_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg); + +static const struct file_operations ethosu_network_fops = { + .release = ðosu_network_release, + .unlocked_ioctl = ðosu_network_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ðosu_network_ioctl, +#endif +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +static bool ethosu_network_verify(struct file *file) +{ + return file->f_op == ðosu_network_fops; +} + +static void ethosu_network_destroy(struct kref *kref) +{ + struct ethosu_network *net = + container_of(kref, struct ethosu_network, kref); + + dev_info(net->edev->dev, "Network destroy. handle=0x%pK\n", net); + + ethosu_buffer_put(net->buf); + devm_kfree(net->edev->dev, net); +} + +static int ethosu_network_release(struct inode *inode, + struct file *file) +{ + struct ethosu_network *net = file->private_data; + + dev_info(net->edev->dev, "Network release. handle=0x%pK\n", net); + + ethosu_network_put(net); + + return 0; +} + +static long ethosu_network_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct ethosu_network *net = file->private_data; + void __user *udata = (void __user *)arg; + int ret = -EINVAL; + + ret = mutex_lock_interruptible(&net->edev->mutex); + if (ret) + return ret; + + dev_info(net->edev->dev, "Ioctl: cmd=%u, arg=%lu\n", cmd, arg); + + switch (cmd) { + case ETHOSU_IOCTL_INFERENCE_CREATE: { + struct ethosu_uapi_inference_create uapi; + + if (copy_from_user(&uapi, udata, sizeof(uapi))) + break; + + dev_info(net->edev->dev, + "Ioctl: Inference. ifm_fd=%u, ofm_fd=%u\n", + uapi.ifm_fd, uapi.ofm_fd); + + ret = ethosu_inference_create(net->edev, net, &uapi); + break; + } + default: { + dev_err(net->edev->dev, "Invalid ioctl. cmd=%u, arg=%lu", + cmd, arg); + break; + } + } + + mutex_unlock(&net->edev->mutex); + + return ret; +} + +int ethosu_network_create(struct ethosu_device *edev, + struct ethosu_uapi_network_create *uapi) +{ + struct ethosu_buffer *buf; + struct ethosu_network *net; + int ret = -ENOMEM; + + buf = ethosu_buffer_get_from_fd(uapi->fd); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + net = devm_kzalloc(edev->dev, sizeof(*net), GFP_KERNEL); + if (!net) { + ret = -ENOMEM; + goto put_buf; + } + + net->edev = edev; + net->buf = buf; + kref_init(&net->kref); + + ret = anon_inode_getfd("ethosu-network", ðosu_network_fops, net, + O_RDWR | O_CLOEXEC); + if (ret < 0) + goto free_net; + + net->file = fget(ret); + fput(net->file); + + dev_info(edev->dev, "Network create. handle=0x%pK", + net); + + return ret; + +free_net: + devm_kfree(edev->dev, net); + +put_buf: + ethosu_buffer_put(buf); + + return ret; +} + +struct ethosu_network *ethosu_network_get_from_fd(int fd) +{ + struct ethosu_network *net; + struct file *file; + + file = fget(fd); + if (!file) + return ERR_PTR(-EINVAL); + + if (!ethosu_network_verify(file)) { + fput(file); + + return ERR_PTR(-EINVAL); + } + + net = file->private_data; + ethosu_network_get(net); + fput(file); + + return net; +} + +void ethosu_network_get(struct ethosu_network *net) +{ + kref_get(&net->kref); +} + +void ethosu_network_put(struct ethosu_network *net) +{ + kref_put(&net->kref, ethosu_network_destroy); +} diff --git a/kernel/ethosu_network.h b/kernel/ethosu_network.h new file mode 100644 index 0000000..bb70afc --- /dev/null +++ b/kernel/ethosu_network.h @@ -0,0 +1,81 @@ +/* + * (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 + */ + +#ifndef ETHOSU_NETWORK_H +#define ETHOSU_NETWORK_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Types + ****************************************************************************/ + +struct ethosu_buffer; +struct ethosu_device; +struct ethosu_uapi_network_create; +struct device; +struct file; + +struct ethosu_network { + struct ethosu_device *edev; + struct file *file; + struct kref kref; + struct ethosu_buffer *buf; +}; + +/**************************************************************************** + * Functions + ****************************************************************************/ + +/** + * ethosu_network_create() - Create network + * + * This function must be called in the context of a user space process. + * + * Return: fd on success, else error code. + */ +int ethosu_network_create(struct ethosu_device *edev, + struct ethosu_uapi_network_create *uapi); + +/** + * ethosu_network_get_from_fd() - Get network handle from fd + * + * This function must be called from a user space context. + * + * Return: Pointer on success, else ERR_PTR. + */ +struct ethosu_network *ethosu_network_get_from_fd(int fd); + +/** + * ethosu_network_get() - Get network + */ +void ethosu_network_get(struct ethosu_network *net); + +/** + * ethosu_network_put() - Put network + */ +void ethosu_network_put(struct ethosu_network *net); + +#endif /* ETHOSU_NETWORK_H */ diff --git a/kernel/uapi/ethosu.h b/kernel/uapi/ethosu.h new file mode 100644 index 0000000..2eca2c4 --- /dev/null +++ b/kernel/uapi/ethosu.h @@ -0,0 +1,104 @@ +/* + * (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 + */ + +#ifndef ETHOSU_H +#define ETHOSU_H + +/**************************************************************************** + * Includes + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Defines + ****************************************************************************/ + +#define ETHOSU_IOCTL_BASE 0x01 +#define ETHOSU_IO(nr) _IO(ETHOSU_IOCTL_BASE, nr) +#define ETHOSU_IOR(nr, type) _IOR(ETHOSU_IOCTL_BASE, nr, type) +#define ETHOSU_IOW(nr, type) _IOW(ETHOSU_IOCTL_BASE, nr, type) +#define ETHOSU_IOWR(nr, type) _IOWR(ETHOSU_IOCTL_BASE, nr, type) + +#define ETHOSU_IOCTL_PING ETHOSU_IO(0x00) +#define ETHOSU_IOCTL_BUFFER_CREATE ETHOSU_IOR(0x10, \ + struct ethosu_uapi_buffer_create) +#define ETHOSU_IOCTL_BUFFER_SET ETHOSU_IOR(0x11, \ + struct ethosu_uapi_buffer) +#define ETHOSU_IOCTL_BUFFER_GET ETHOSU_IOW(0x12, \ + struct ethosu_uapi_buffer) +#define ETHOSU_IOCTL_NETWORK_CREATE ETHOSU_IOR(0x20, \ + struct ethosu_uapi_network_create) +#define ETHOSU_IOCTL_INFERENCE_CREATE ETHOSU_IOR(0x30, \ + struct ethosu_uapi_inference_create) +#define ETHOSU_IOCTL_INFERENCE_STATUS ETHOSU_IO(0x31) + +/**************************************************************************** + * Types + ****************************************************************************/ + +/** + * enum ethosu_uapi_status - Status + */ +enum ethosu_uapi_status { + ETHOSU_UAPI_STATUS_OK, + ETHOSU_UAPI_STATUS_ERROR +}; + +/** + * struct ethosu_uapi_buffer_create - Create buffer request + * @capacity: Maximum capacity of the buffer + */ +struct ethosu_uapi_buffer_create { + __u32 capacity; +}; + +/** + * struct ethosu_uapi_buffer - Buffer descriptor + * @offset: Offset to where the data starts + * @size: Size of the data + * + * 'offset + size' must not exceed the capacity of the buffer. + */ +struct ethosu_uapi_buffer { + __u32 offset; + __u32 size; +}; + +/** + * struct ethosu_uapi_network_create - Create network request + * @fd: Buffer file descriptor + */ +struct ethosu_uapi_network_create { + __u32 fd; +}; + +/** + * struct ethosu_uapi_inference_create - Create network request + * @ifm_fd: IFM buffer file descriptor + * @ofm_fd: OFM buffer file descriptor + */ +struct ethosu_uapi_inference_create { + __u32 ifm_fd; + __u32 ofm_fd; +}; + +#endif -- cgit v1.2.1