ZHOUYI NPU is an AI accelerator chip which is integrated into ARM SOC, such as Allwinner R329 SOC. Add driver support for this AI accelerator here. Signed-off-by: Cai Huoqing <caihuoqing@xxxxxxxxx> --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/zynpu/Kconfig | 34 + drivers/staging/zynpu/Makefile | 7 + drivers/staging/zynpu/z1.c | 233 ++++++ drivers/staging/zynpu/z2.c | 297 ++++++++ drivers/staging/zynpu/zhouyi.h | 70 ++ drivers/staging/zynpu/zhouyi_base.c | 71 ++ drivers/staging/zynpu/zynpu.h | 252 +++++++ drivers/staging/zynpu/zynpu_core.c | 254 +++++++ drivers/staging/zynpu/zynpu_drv.c | 349 +++++++++ drivers/staging/zynpu/zynpu_fops.c | 245 +++++++ drivers/staging/zynpu/zynpu_io.c | 133 ++++ drivers/staging/zynpu/zynpu_io.h | 119 ++++ drivers/staging/zynpu/zynpu_irq.c | 123 ++++ drivers/staging/zynpu/zynpu_irq.h | 85 +++ drivers/staging/zynpu/zynpu_job_manager.c | 467 +++++++++++++ drivers/staging/zynpu/zynpu_job_manager.h | 200 ++++++ drivers/staging/zynpu/zynpu_mm.c | 704 +++++++++++++++++++ drivers/staging/zynpu/zynpu_mm.h | 142 ++++ drivers/staging/zynpu/zynpu_session.c | 817 ++++++++++++++++++++++ drivers/staging/zynpu/zynpu_session.h | 283 ++++++++ drivers/staging/zynpu/zynpu_sysfs.c | 205 ++++++ 23 files changed, 5093 insertions(+) create mode 100644 drivers/staging/zynpu/Kconfig create mode 100644 drivers/staging/zynpu/Makefile create mode 100644 drivers/staging/zynpu/z1.c create mode 100644 drivers/staging/zynpu/z2.c create mode 100644 drivers/staging/zynpu/zhouyi.h create mode 100644 drivers/staging/zynpu/zhouyi_base.c create mode 100644 drivers/staging/zynpu/zynpu.h create mode 100644 drivers/staging/zynpu/zynpu_core.c create mode 100644 drivers/staging/zynpu/zynpu_drv.c create mode 100644 drivers/staging/zynpu/zynpu_fops.c create mode 100644 drivers/staging/zynpu/zynpu_io.c create mode 100644 drivers/staging/zynpu/zynpu_io.h create mode 100644 drivers/staging/zynpu/zynpu_irq.c create mode 100644 drivers/staging/zynpu/zynpu_irq.h create mode 100644 drivers/staging/zynpu/zynpu_job_manager.c create mode 100644 drivers/staging/zynpu/zynpu_job_manager.h create mode 100644 drivers/staging/zynpu/zynpu_mm.c create mode 100644 drivers/staging/zynpu/zynpu_mm.h create mode 100644 drivers/staging/zynpu/zynpu_session.c create mode 100644 drivers/staging/zynpu/zynpu_session.h create mode 100644 drivers/staging/zynpu/zynpu_sysfs.c diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 8d41fdd40657..107e2c88d5d9 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -94,4 +94,6 @@ source "drivers/staging/qlge/Kconfig" source "drivers/staging/wfx/Kconfig" +source "drivers/staging/zynpu/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 02b01949b94e..022444b13ec5 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/ obj-$(CONFIG_QLGE) += qlge/ obj-$(CONFIG_WFX) += wfx/ +obj-$(CONFIG_ZYNPU) += zynpu/ diff --git a/drivers/staging/zynpu/Kconfig b/drivers/staging/zynpu/Kconfig new file mode 100644 index 000000000000..dc1b79acd30b --- /dev/null +++ b/drivers/staging/zynpu/Kconfig @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# ARM (China) ZHOUYI AI accelerator drivers configuration. +# + +config ZYNPU + tristate "ARM (China) ZHOUYI AI accelerator support" + depends on OF && HAS_IOMEM + select CMA + select DMA_CMA + help + Say yes here to build support for ZHOUYI AI accelerator. + + This driver can also be built as a module. If so, the module will be + called zhouyi_npu. + +choice + prompt "ZHOUYI NPU version" + depends on ZYNPU + default ZHOUYI_V1 + help + Select the chip version of ZHOUYI NPU. + + config ZHOUYI_V1 + bool "ZHOUYI V1" + help + select ZHOUYI Z1 support + + config ZHOUYI_V2 + bool "ZHOUYI V2" + help + select ZHOUYI Z2 support + +endchoice diff --git a/drivers/staging/zynpu/Makefile b/drivers/staging/zynpu/Makefile new file mode 100644 index 000000000000..f9fdb976b0e9 --- /dev/null +++ b/drivers/staging/zynpu/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +zhouyi_npu-y := zynpu_core.o zynpu_io.o zynpu_drv.o \ + zynpu_irq.o zynpu_session.o zynpu_job_manager.o \ + zynpu_mm.o zynpu_sysfs.o zynpu_fops.o zhouyi_base.o z1.o z2.o + +obj-$(CONFIG_ZYNPU) += zhouyi_npu.o diff --git a/drivers/staging/zynpu/z1.c b/drivers/staging/zynpu/z1.c new file mode 100644 index 000000000000..22f8947377a2 --- /dev/null +++ b/drivers/staging/zynpu/z1.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file z1.c + * Implementation of the zhouyi v1 ZYNPU hardware cntrol interfaces + */ + +#include "zynpu.h" +#include "zynpu_io.h" +#include "zhouyi.h" + +/** + * Zhouyi V1 ZYNPU Interrupts + */ +#define ZHOUYIV1_IRQ (ZHOUYI_IRQ) +#define ZHOUYIV1_IRQ_ENABLE_FLAG (ZHOUYIV1_IRQ) +#define ZHOUYIV1_IRQ_DISABLE_FLAG (ZHOUYI_IRQ_NONE) + +/** + * Zhouyi V1 ZYNPU Specific Host Control Register Map + */ +#define ZHOUYI_INTR_CAUSE_REG_OFFSET 0x20 +#define ZHOUYI_L2_CACHE_FEATURE_REG_OFFSET 0x6C + +static void zhouyi_v1_enable_interrupt(struct zynpu_core *core) +{ + if (core) + zynpu_write32(core->base0, ZHOUYI_CTRL_REG_OFFSET, + ZHOUYIV1_IRQ_ENABLE_FLAG); +} + +static void zhouyi_v1_disable_interrupt(struct zynpu_core *core) +{ + if (core) + zynpu_write32(core->base0, ZHOUYI_CTRL_REG_OFFSET, + ZHOUYIV1_IRQ_DISABLE_FLAG); +} + +static void zhouyi_v1_clear_qempty_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_qempty_interrupt(core->base0); +} + +static void zhouyi_v1_clear_done_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_done_interrupt(core->base0); +} + +static void zhouyi_v1_clear_excep_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_excep_interrupt(core->base0); +} + +static int zhouyi_v1_trigger(struct zynpu_core *core, + struct user_job_desc *udesc, int tid) +{ + int ret = 0; + unsigned int phys_addr = 0; + unsigned int phys_addr0 = 0; + unsigned int phys_addr1 = 0; + unsigned int start_pc = 0; + + if (!core) { + ret = -EINVAL; + goto finish; + } + + /* Load data addr 0 register */ + phys_addr0 = (unsigned int)udesc->data_0_addr; + zynpu_write32(core->base0, ZHOUYI_DATA_ADDR_0_REG_OFFSET, phys_addr0); + + /* Load data addr 1 register */ + phys_addr1 = (unsigned int)udesc->data_1_addr; + zynpu_write32(core->base0, ZHOUYI_DATA_ADDR_1_REG_OFFSET, phys_addr1); + + /* Load interrupt PC */ + zynpu_write32(core->base0, ZHOUYI_INTR_PC_REG_OFFSET, + (unsigned int)udesc->intr_handler_addr); + + /* Load start PC register */ + /* use write back and invalidate DCache*/ + /* because HW does not implement invalidate option in Zhouyi-z1 */ + phys_addr = (unsigned int)udesc->start_pc_addr; + start_pc = phys_addr | 0xD; + zynpu_write32(core->base0, ZHOUYI_START_PC_REG_OFFSET, start_pc); + + if (tid) + dev_info(core->dev, + "[%u] trigger Job 0x%x done: start pc = 0x%x, dreg0 = 0x%x, dreg1 = 0x%x", + tid, udesc->job_id, start_pc, phys_addr0, phys_addr1); + else + dev_info(core->dev, + "[IRQ] trigger Job 0x%x done: start pc = 0x%x, dreg0 = 0x%x, dreg1 = 0x%x", + udesc->job_id, start_pc, phys_addr0, phys_addr1); + +finish: + return ret; +} + +static bool zhouyi_v1_is_idle(struct zynpu_core *core) +{ + if (!core) { + pr_err("invalid input args core to be NULL!"); + return 0; + } + return ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 16) && + ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 17) && + ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 18); +} + +static int zhouyi_v1_read_status_reg(struct zynpu_core *core) +{ + return zhouyi_read_status_reg(core->base0); +} + +static void zhouyi_v1_print_hw_id_info(struct zynpu_core *core) +{ + int ret = 0; + + if (!core) { + pr_err("invalid input args io to be NULL!"); + return; + } + + ret = zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET); + dev_info(core->dev, "ZYNPU Initial Status: 0x%x.", ret); + + dev_info(core->dev, "###### ZHOUYI V1 HARDWARE INFORMATION #######"); + dev_info(core->dev, "# ISA Version Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_ISA_VERSION_REG_OFFSET)); + dev_info(core->dev, "# TPC Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_TPC_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# SPU Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_SPU_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# HWA Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_HWA_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Revision ID Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_REVISION_ID_REG_OFFSET)); + dev_info(core->dev, "# Memory Hierarchy Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_MEM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Instruction RAM Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_INST_RAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# TEC Local SRAM Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_LOCAL_SRAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Global SRAM Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_GLOBAL_SRAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Instruction Cache Feature Register:0x%x", + zynpu_read32(core->base0, ZHOUYI_INST_CACHE_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Data Cache Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_DATA_CACHE_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# L2 Cache Feature Register: 0x%x", + zynpu_read32(core->base0, ZHOUYI_L2_CACHE_FEATURE_REG_OFFSET)); + dev_info(core->dev, "#############################################"); +} + +static int zhouyi_v1_query_cap(struct zynpu_core *core, struct zynpu_cap *cap) +{ + if (!core) + return 0; + return zhouyi_query_cap(core->base0, cap); +} + +static void zhouyi_v1_io_rw(struct zynpu_core *core, struct zynpu_io_req *io_req) +{ + zhouyi_io_rw(core->base0, io_req); +} + +static int zhouyi_v1_upper_half(void *data) +{ + int ret = 0; + struct zynpu_priv *zynpu = (struct zynpu_priv*)data; + struct zynpu_core *core = zynpu->core0; + + if (!core) { + ret = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + goto finish; + } + + zhouyi_v1_disable_interrupt(core); + ret = zhouyi_v1_read_status_reg(core); + if (ret & ZHOUYI_IRQ_QEMPTY) { + zhouyi_v1_clear_qempty_interrupt(core); + } + + if (ret & ZHOUYI_IRQ_DONE) { + zhouyi_v1_clear_done_interrupt(core); + zynpu_job_manager_update_job_state_irq(zynpu, 0); + zynpu_irq_schedulework(core->irq_obj); + } + + if (ret & ZHOUYI_IRQ_EXCEP) { + zhouyi_v1_clear_excep_interrupt(core); + zynpu_job_manager_update_job_state_irq(zynpu, + zynpu_read32(core->base0, ZHOUYI_INTR_CAUSE_REG_OFFSET)); + zynpu_irq_schedulework(core->irq_obj); + } + zhouyi_v1_enable_interrupt(core); + + /* success */ + ret = 0; + +finish: + return ret; +} + +static void zhouyi_v1_bottom_half(void *data) +{ + struct zynpu_priv* zynpu = (struct zynpu_priv*)data; + zynpu_job_manager_update_job_queue_done_irq(&zynpu->job_manager); +} + +struct zynpu_io_operation zhouyi_v1_ops = { + .enable_interrupt = zhouyi_v1_enable_interrupt, + .disable_interrupt = zhouyi_v1_disable_interrupt, + .trigger = zhouyi_v1_trigger, + .is_idle = zhouyi_v1_is_idle, + .read_status_reg = zhouyi_v1_read_status_reg, + .print_hw_id_info = zhouyi_v1_print_hw_id_info, + .query_capability = zhouyi_v1_query_cap, + .io_rw = zhouyi_v1_io_rw, + .upper_half = zhouyi_v1_upper_half, + .bottom_half = zhouyi_v1_bottom_half, +}; diff --git a/drivers/staging/zynpu/z2.c b/drivers/staging/zynpu/z2.c new file mode 100644 index 000000000000..77f437d74595 --- /dev/null +++ b/drivers/staging/zynpu/z2.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file z2.c + * Implementation of the zhouyi v1 ZYNPU hardware cntrol interfaces + */ + +#include "zynpu.h" +#include "zynpu_io.h" +#include "zhouyi.h" + +/** + * Zhouyi v2 ZYNPU Specific Interrupts + */ +#define ZHOUYI_IRQ_FAULT 0x8 + +#define ZHOUYIV2_IRQ (ZHOUYI_IRQ | ZHOUYI_IRQ_FAULT) +#define ZHOUYIV2_IRQ_ENABLE_FLAG (ZHOUYIV2_IRQ) +#define ZHOUYIV2_IRQ_DISABLE_FLAG (ZHOUYI_IRQ_NONE) + +#define ZHOUYI_V2_ASE_READ_ENABLE (1<<31) +#define ZHOUYI_V2_ASE_RW_ENABLE (3<<30) +#define ZHOUYI_V2_ASE_CTRL_SIZE(value) \ + (ilog2((max((u64)4096, (u64)roundup_pow_of_two(value))>>12)) + 1) + +/** + * Zhouyi v2 ZYNPU Specific Host Control Register Map + */ +#define ZYNPU_ADDR_EXT0_CTRL_REG_OFFSET 0xC0 +#define ZYNPU_ADDR_EXT0_HIGH_BASE_REG_OFFSET 0xC4 +#define ZYNPU_ADDR_EXT0_LOW_BASE_REG_OFFSET 0xC8 +#define ZYNPU_ADDR_EXT1_CTRL_REG_OFFSET 0xCC +#define ZYNPU_ADDR_EXT1_HIGH_BASE_REG_OFFSET 0xD0 +#define ZYNPU_ADDR_EXT1_LOW_BASE_REG_OFFSET 0xD4 +#define ZYNPU_ADDR_EXT2_CTRL_REG_OFFSET 0xD8 +#define ZYNPU_ADDR_EXT2_HIGH_BASE_REG_OFFSET 0xDC +#define ZYNPU_ADDR_EXT2_LOW_BASE_REG_OFFSET 0xE0 + +static void zhouyi_v2_enable_interrupt(struct zynpu_core *core) +{ + if (core) + zynpu_write32(core->base0, ZHOUYI_CTRL_REG_OFFSET, + ZHOUYIV2_IRQ_ENABLE_FLAG); +} + +static void zhouyi_v2_disable_interrupt(struct zynpu_core *core) +{ + if (core) + zynpu_write32(core->base0, ZHOUYI_CTRL_REG_OFFSET, + ZHOUYIV2_IRQ_DISABLE_FLAG); +} + +static void zhouyi_v2_clear_qempty_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_qempty_interrupt(core->base0); +} + +static void zhouyi_v2_clear_done_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_done_interrupt(core->base0); +} + +static void zhouyi_v2_clear_excep_interrupt(struct zynpu_core *core) +{ + if (core) + zhouyi_clear_excep_interrupt(core->base0); +} + +static void zhouyi_v2_clear_fault_interrupt(struct zynpu_core *core) +{ + if (core) + zynpu_write32(core->base0, ZHOUYI_STAT_REG_OFFSET, ZHOUYI_IRQ_FAULT); +} + +static int zhouyi_v2_trigger(struct zynpu_core *core, struct user_job_desc *udesc, int tid) +{ + int ret = 0; + u32 start_pc = 0; + u32 intr_pc = 0; + u32 data_0_pa = 0; + u32 data_1_pa = 0; + u64 ase0_start = min3(udesc->start_pc_addr, + udesc->data_0_addr, udesc->data_1_addr); + u64 ase0_end = max3(udesc->start_pc_addr + udesc->code_size, + udesc->data_0_addr + udesc->rodata_size, + udesc->data_1_addr + udesc->stack_size); + + /* Used when ASID is disabled */ + u32 ase0_base_high = udesc->start_pc_addr >> 32; + + if (!core) { + ret = -EINVAL; + goto finish; + } + + if (udesc->enable_asid) { + start_pc = (u32)(udesc->start_pc_addr - ase0_start); + intr_pc = (u32)(udesc->intr_handler_addr - ase0_start); + data_0_pa = (u32)(udesc->data_0_addr - ase0_start); + data_1_pa = (u32)(udesc->data_1_addr - ase0_start); + } else { + start_pc = (u32)udesc->start_pc_addr; + intr_pc = (u32)udesc->intr_handler_addr; + data_0_pa = (u32)udesc->data_0_addr; + data_1_pa = (u32)udesc->data_1_addr; + } + + /* Load data addr 0 register */ + zynpu_write32(core->base0, ZHOUYI_DATA_ADDR_0_REG_OFFSET, data_0_pa); + + /* Load data addr 1 register */ + zynpu_write32(core->base0, ZHOUYI_DATA_ADDR_1_REG_OFFSET, data_1_pa); + + /* Load interrupt PC */ + zynpu_write32(core->base0, ZHOUYI_INTR_PC_REG_OFFSET, intr_pc); + + /* Load ASE registers */ + if (udesc->enable_asid) { + /* ASE 0 */ + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_CTRL_REG_OFFSET, + ZHOUYI_V2_ASE_RW_ENABLE | ZHOUYI_V2_ASE_CTRL_SIZE(ase0_end - ase0_start)); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_HIGH_BASE_REG_OFFSET, ase0_start >> 32); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_LOW_BASE_REG_OFFSET, 0); + dev_dbg(core->dev, "ASE 0 Ctrl 0x%x, ASE 0 PA 0x%llx", + zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_CTRL_REG_OFFSET), + ((u64)zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_HIGH_BASE_REG_OFFSET) << 32) + + zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_LOW_BASE_REG_OFFSET)); + /* ASE 1 */ + zynpu_write32(core->base0, ZYNPU_ADDR_EXT1_CTRL_REG_OFFSET, + ZHOUYI_V2_ASE_READ_ENABLE | ZHOUYI_V2_ASE_CTRL_SIZE(udesc->static_size)); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT1_HIGH_BASE_REG_OFFSET, udesc->static_addr >> 32); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT1_LOW_BASE_REG_OFFSET, (u32)udesc->static_addr); + dev_dbg(core->dev, "ASE 1 Ctrl 0x%x, ASE 1 PA 0x%llx", + zynpu_read32(core->base0, ZYNPU_ADDR_EXT1_CTRL_REG_OFFSET), + ((u64)zynpu_read32(core->base0, ZYNPU_ADDR_EXT1_HIGH_BASE_REG_OFFSET) << 32) + + zynpu_read32(core->base0, ZYNPU_ADDR_EXT1_LOW_BASE_REG_OFFSET)); + /* ASE 2 */ + zynpu_write32(core->base0, ZYNPU_ADDR_EXT2_CTRL_REG_OFFSET, + ZHOUYI_V2_ASE_RW_ENABLE | ZHOUYI_V2_ASE_CTRL_SIZE(udesc->reuse_size)); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT2_HIGH_BASE_REG_OFFSET, udesc->reuse_addr >> 32); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT2_LOW_BASE_REG_OFFSET, (u32)udesc->reuse_addr); + dev_dbg(core->dev, "ASE 2 Ctrl 0x%x, ASE 2 PA 0x%llx", + zynpu_read32(core->base0, ZYNPU_ADDR_EXT2_CTRL_REG_OFFSET), + ((u64)zynpu_read32(core->base0, ZYNPU_ADDR_EXT2_HIGH_BASE_REG_OFFSET) << 32) + + zynpu_read32(core->base0, ZYNPU_ADDR_EXT2_LOW_BASE_REG_OFFSET)); + } else { + /* default: ASE 0 */ + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_CTRL_REG_OFFSET, + ZHOUYI_V2_ASE_RW_ENABLE); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_HIGH_BASE_REG_OFFSET, ase0_base_high); + zynpu_write32(core->base0, ZYNPU_ADDR_EXT0_LOW_BASE_REG_OFFSET, 0); + dev_dbg(core->dev, "ASE 0 Ctrl 0x%x, ASE 0 PA 0x%llx", + zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_CTRL_REG_OFFSET), + ((u64)zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_HIGH_BASE_REG_OFFSET) << 32) + + zynpu_read32(core->base0, ZYNPU_ADDR_EXT0_LOW_BASE_REG_OFFSET)); + } + + /* Load start PC register */ + start_pc |= 0xD; + zynpu_write32(core->base0, ZHOUYI_START_PC_REG_OFFSET, start_pc); + + if (tid) + dev_info(core->dev, "[%u] trigger Job 0x%x done: start pc = 0x%x, dreg0 = 0x%x, dreg1 = 0x%x", + tid, udesc->job_id, start_pc, data_0_pa, data_1_pa); + else + dev_info(core->dev, "[IRQ] trigger Job 0x%x done: start pc = 0x%x, dreg0 = 0x%x, dreg1 = 0x%x", + udesc->job_id, start_pc, data_0_pa, data_1_pa); + +finish: + return ret; +} + +static bool zhouyi_v2_is_idle(struct zynpu_core *core) +{ + if (!core) { + pr_err("invalid input args core to be NULL!"); + return 0; + } + return ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 16) && + ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 17) && + ZYNPU_BIT(zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET), 18); +} + +static int zhouyi_v2_read_status_reg(struct zynpu_core *core) +{ + if (!core) + return 0; + return zhouyi_read_status_reg(core->base0); +} + +static void zhouyi_v2_print_hw_id_info(struct zynpu_core *core) +{ + int ret = 0; + + if (!core) { + pr_err("invalid input args core to be NULL!"); + return; + } + + ret = zynpu_read32(core->base0, ZHOUYI_STAT_REG_OFFSET); + dev_info(core->dev, "ZYNPU Initial Status: 0x%x.", ret); + + dev_info(core->dev, "###### ZHOUYI V2 HARDWARE INFORMATION #######"); + dev_info(core->dev, "# ISA Version Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_ISA_VERSION_REG_OFFSET)); + dev_info(core->dev, "# TPC Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_TPC_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# SPU Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_SPU_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# HWA Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_HWA_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Revision ID Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_REVISION_ID_REG_OFFSET)); + dev_info(core->dev, "# Memory Hierarchy Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_MEM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Instruction RAM Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_INST_RAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# TEC Local SRAM Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_LOCAL_SRAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Global SRAM Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_GLOBAL_SRAM_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Instruction Cache Feature Register:0x%x", zynpu_read32(core->base0, ZHOUYI_INST_CACHE_FEATURE_REG_OFFSET)); + dev_info(core->dev, "# Data Cache Feature Register: 0x%x", zynpu_read32(core->base0, ZHOUYI_DATA_CACHE_FEATURE_REG_OFFSET)); + dev_info(core->dev, "#############################################"); +} + +static int zhouyi_v2_query_cap(struct zynpu_core *core, struct zynpu_cap *cap) +{ + if (!core) + return 0; + return zhouyi_query_cap(core->base0, cap); +} + +static void zhouyi_v2_io_rw(struct zynpu_core *core, struct zynpu_io_req *io_req) +{ + if (core) + zhouyi_io_rw(core->base0, io_req); +} + +static int zhouyi_v2_upper_half(void *data) +{ + int ret = 0; + struct zynpu_priv* zynpu = (struct zynpu_priv*)data; + struct zynpu_core* core = zynpu->core0; + + if (!core) { + ret = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + goto finish; + } + + zhouyi_v2_disable_interrupt(core); + ret = zhouyi_v2_read_status_reg(core); + if (ret & ZHOUYI_IRQ_QEMPTY) { + zhouyi_v2_clear_qempty_interrupt(core); + } + + if (ret & ZHOUYI_IRQ_DONE) { + zhouyi_v2_clear_done_interrupt(core); + zynpu_job_manager_update_job_state_irq(zynpu, 0); + zynpu_irq_schedulework(core->irq_obj); + } + + if (ret & ZHOUYI_IRQ_EXCEP) { + zhouyi_v2_clear_excep_interrupt(core); + zynpu_job_manager_update_job_state_irq(zynpu, 1); + zynpu_irq_schedulework(core->irq_obj); + } + + if (ret & ZHOUYI_IRQ_FAULT) + zhouyi_v2_clear_fault_interrupt(core); + zhouyi_v2_enable_interrupt(core); + + /* success */ + ret = 0; + +finish: + return ret; +} + +static void zhouyi_v2_bottom_half(void *data) +{ + struct zynpu_priv *zynpu = (struct zynpu_priv*)data; + zynpu_job_manager_update_job_queue_done_irq(&zynpu->job_manager); +} + +struct zynpu_io_operation zhouyi_v2_ops = { + .enable_interrupt = zhouyi_v2_enable_interrupt, + .disable_interrupt = zhouyi_v2_disable_interrupt, + .trigger = zhouyi_v2_trigger, + .is_idle = zhouyi_v2_is_idle, + .read_status_reg = zhouyi_v2_read_status_reg, + .print_hw_id_info = zhouyi_v2_print_hw_id_info, + .query_capability = zhouyi_v2_query_cap, + .io_rw = zhouyi_v2_io_rw, + .upper_half = zhouyi_v2_upper_half, + .bottom_half = zhouyi_v2_bottom_half, +}; \ No newline at end of file diff --git a/drivers/staging/zynpu/zhouyi.h b/drivers/staging/zynpu/zhouyi.h new file mode 100644 index 000000000000..9a63fce4ffcd --- /dev/null +++ b/drivers/staging/zynpu/zhouyi.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zhouyi.h + * Header of the zhouyi ZYNPU hardware control and interrupt handle operations + */ + +#ifndef _ZYNPU_ZHOUYI_H_ +#define _ZYNPU_ZHOUYI_H_ + +#include <linux/device.h> +#include "zynpu_io.h" + +/** + * Zhouyi ZYNPU Common Interrupts + */ +#define ZHOUYI_IRQ_NONE 0x0 +#define ZHOUYI_IRQ_QEMPTY 0x1 +#define ZHOUYI_IRQ_DONE 0x2 +#define ZHOUYI_IRQ_EXCEP 0x4 + +#define ZHOUYI_IRQ (ZHOUYI_IRQ_QEMPTY | ZHOUYI_IRQ_DONE | ZHOUYI_IRQ_EXCEP) + +#define ZHOUYI_ZYNPU_IDLE_STATUS 0x70000 + +/** + * Zhouyi ZYNPU Common Host Control Register Map + */ +#define ZHOUYI_CTRL_REG_OFFSET 0x0 +#define ZHOUYI_STAT_REG_OFFSET 0x4 +#define ZHOUYI_START_PC_REG_OFFSET 0x8 +#define ZHOUYI_INTR_PC_REG_OFFSET 0xC +#define ZHOUYI_IPI_CTRL_REG_OFFSET 0x10 +#define ZHOUYI_DATA_ADDR_0_REG_OFFSET 0x14 +#define ZHOUYI_DATA_ADDR_1_REG_OFFSET 0x18 +#define ZHOUYI_CLK_CTRL_REG_OFFSET 0x3C +#define ZHOUYI_ISA_VERSION_REG_OFFSET 0x40 +#define ZHOUYI_TPC_FEATURE_REG_OFFSET 0x44 +#define ZHOUYI_SPU_FEATURE_REG_OFFSET 0x48 +#define ZHOUYI_HWA_FEATURE_REG_OFFSET 0x4C +#define ZHOUYI_REVISION_ID_REG_OFFSET 0x50 +#define ZHOUYI_MEM_FEATURE_REG_OFFSET 0x54 +#define ZHOUYI_INST_RAM_FEATURE_REG_OFFSET 0x58 +#define ZHOUYI_LOCAL_SRAM_FEATURE_REG_OFFSET 0x5C +#define ZHOUYI_GLOBAL_SRAM_FEATURE_REG_OFFSET 0x60 +#define ZHOUYI_INST_CACHE_FEATURE_REG_OFFSET 0x64 +#define ZHOUYI_DATA_CACHE_FEATURE_REG_OFFSET 0x68 + +struct zynpu_cap { + __u32 isa_version; + __u32 tpc_feature; + __u32 aiff_feature; + __u32 errcode; +}; + +int zhouyi_read_status_reg(struct io_region* io); +void zhouyi_clear_qempty_interrupt(struct io_region* io); +void zhouyi_clear_done_interrupt(struct io_region* io); +void zhouyi_clear_excep_interrupt(struct io_region* io); +int zhouyi_query_cap(struct io_region* io, struct zynpu_cap* cap); +void zhouyi_io_rw(struct io_region* io, struct zynpu_io_req* io_req); + +#endif /* _ZYNPU_ZHOUYI_H_ */ \ No newline at end of file diff --git a/drivers/staging/zynpu/zhouyi_base.c b/drivers/staging/zynpu/zhouyi_base.c new file mode 100644 index 000000000000..1a01b5fabc50 --- /dev/null +++ b/drivers/staging/zynpu/zhouyi_base.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zhouyi.c + * Implementations of the zhouyi ZYNPU hardware control and interrupt handle operations + */ + +#include "zhouyi.h" + +int zhouyi_read_status_reg(struct io_region *io) +{ + return zynpu_read32(io, ZHOUYI_STAT_REG_OFFSET); +} + +void zhouyi_clear_qempty_interrupt(struct io_region *io) +{ + zynpu_write32(io, ZHOUYI_STAT_REG_OFFSET, ZHOUYI_IRQ_QEMPTY); +} + +void zhouyi_clear_done_interrupt(struct io_region *io) +{ + zynpu_write32(io, ZHOUYI_STAT_REG_OFFSET, ZHOUYI_IRQ_DONE); +} + +void zhouyi_clear_excep_interrupt(struct io_region *io) +{ + zynpu_write32(io, ZHOUYI_STAT_REG_OFFSET, ZHOUYI_IRQ_EXCEP); +} + +int zhouyi_query_cap(struct io_region *io, struct zynpu_cap *cap) +{ + int ret = 0; + + if ((!io) || (!cap)) { + ret = -EINVAL; + goto finish; + } + + cap->isa_version = zynpu_read32(io, ZHOUYI_ISA_VERSION_REG_OFFSET); + cap->tpc_feature = zynpu_read32(io, ZHOUYI_TPC_FEATURE_REG_OFFSET); + cap->aiff_feature = zynpu_read32(io, ZHOUYI_HWA_FEATURE_REG_OFFSET); + + /* success */ + cap->errcode = 0; + +finish: + return ret; +} + +void zhouyi_io_rw(struct io_region *io, struct zynpu_io_req *io_req) +{ + if ((!io) || (!io_req)) { + pr_err("invalid input args io/io_req to be NULL!"); + return; + } + + /* TBD: offset r/w permission should be checked */ + + if (io_req->rw == ZYNPU_IO_READ) + io_req->value = zynpu_read32(io, io_req->offset); + else if (io_req->rw == ZYNPU_IO_WRITE) + zynpu_write32(io, io_req->offset, io_req->value); + io_req->errcode = 0; +} diff --git a/drivers/staging/zynpu/zynpu.h b/drivers/staging/zynpu/zynpu.h new file mode 100644 index 000000000000..95abb71c0eb8 --- /dev/null +++ b/drivers/staging/zynpu/zynpu.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu.h + * Header of the zynpu device struct + */ + +#ifndef _ZYNPU_H_ +#define _ZYNPU_H_ + +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/types.h> +#include "zynpu_irq.h" +#include "zynpu_io.h" +#include "zynpu_job_manager.h" +#include "zynpu_mm.h" +#include "zhouyi.h" + +#define ZYNPU_ERRCODE_NO_ERROR 0 +#define ZYNPU_ERRCODE_NO_MEMORY 1 +#define ZYNPU_ERRCODE_INTERNAL_NULLPTR 2 +#define ZYNPU_ERRCODE_INVALID_ARGS 3 +#define ZYNPU_ERRCODE_CREATE_KOBJ_ERR 4 +#define ZYNPU_ERRCODE_ITEM_NOT_FOUND 5 + +#define IPUIOC_MAGIC 'A' + +#define IPUIOC_QUERYCAP _IOR(IPUIOC_MAGIC, 0, struct zynpu_cap) +#define IPUIOC_REQBUF _IOWR(IPUIOC_MAGIC, 1, struct buf_request) +#define IPUIOC_RUNJOB _IOWR(IPUIOC_MAGIC, 2, struct user_job) +#define IPUIOC_FREEBUF _IOW(IPUIOC_MAGIC, 3, struct buf_desc) +#define IPUIOC_REQSHMMAP _IOR(IPUIOC_MAGIC, 4, __u64) +#define IPUIOC_REQIO _IOWR(IPUIOC_MAGIC, 5, struct zynpu_io_req) +#define IPUIOC_QUERYSTATUS _IOWR(IPUIOC_MAGIC, 6, struct job_status_query) +#define IPUIOC_KILL_TIMEOUT_JOB _IOW(IPUIOC_MAGIC, 7, __u32) + +enum zynpu_version { + ZYNPU_VERSION_ZHOUYI_V1 = 1, + ZYNPU_VERSION_ZHOUYI_V2 +}; + +/** + * struct zynpu_core - a general struct describe a hardware ZYNPU core + * + * @version: ZYNPU hardware version + * @freq_in_MHz: ZYNPU core working frequency + * @max_sched_num: maximum number of jobs can be scheduled in pipeline + * @base0: IO region of this ZYNPU core + * @irq_obj: interrupt object of this core + */ +struct zynpu_core { + int version; + int freq_in_MHz; + int max_sched_num; + struct io_region *base0; + struct zynpu_irq_object *irq_obj; + struct device *dev; +}; + +/** + * struct zynpu_io_operation - a struct contains ZYNPU hardware operation methods + * + * @enable_interrupt: Enable all ZYNPU interrupts + * @disable_interrupt: Disable all ZYNPU interrupts + * @trigger: trigger ZYNPU to run a job + * @is_idle: Is ZYNPU hardware idle or not + * @read_status_reg: Read status register value + * @print_hw_id_info: Print ZYNPU version ID registers information + * @query_capability: Query ZYNPU hardware capability information + * @io_rw: Direct IO read/write operations + */ +struct zynpu_io_operation { + void (*enable_interrupt)(struct zynpu_core* core); + void (*disable_interrupt)(struct zynpu_core* core); + int (*trigger)(struct zynpu_core* core, struct user_job_desc* udesc, int tid); + bool (*is_idle)(struct zynpu_core* core); + int (*read_status_reg)(struct zynpu_core* core); + void (*print_hw_id_info)(struct zynpu_core* core); + int (*query_capability)(struct zynpu_core* core, struct zynpu_cap* cap); + void (*io_rw)(struct zynpu_core* core, struct zynpu_io_req* io_req); + int (*upper_half)(void* data); + void (*bottom_half)(void* data); +}; + +struct zynpu_priv { + int board; + int version; + struct zynpu_core *core0; + struct zynpu_io_operation* core_ctrl; + int open_num; + struct device *dev; + struct file_operations zynpu_fops; + struct miscdevice *misc; + struct mutex lock; + struct zynpu_job_manager job_manager; + struct zynpu_memory_manager mm; + struct kobject *sys_kobj; + int is_suspend; +}; + +/* + * @brief register ZYNPU fops operations into fops struct + * + * @param fops: file_operations struct pointer + * + * @return ZYNPU_ERRCODE_NO_ERROR if successful; others if failed. + */ +int zynpu_fops_register(struct file_operations *fops); +/* + * @brief initialize sysfs debug interfaces in probe + * + * @param zynpu_priv: zynpu_priv struct pointer + * + * @return 0 if successful; others if failed. + */ +int zynpu_create_sysfs(void *zynpu_priv); +/* + * @brief de-initialize sysfs debug interfaces in remove + * + * @param zynpu_priv: zynpu_priv struct pointer + */ +void zynpu_destroy_sysfs(void *zynpu_priv); +/** + * @brief initialize an input ZYNPU private data struct + * + * @param zynpu: pointer to ZYNPU private data struct + * @param dev: device struct pointer + * + * @return 0 if successful; others if failed; + */ +int init_zynpu_priv(struct zynpu_priv *zynpu, struct device *dev); +/** + * @brief initialize ZYNPU core info in the ZYNPU private data struct + * + * @param zynpu: pointer to ZYNPU private data struct + * @param irqnum: ZYNPU interrupt number + * @param base: ZYNPU external registers phsical base address + * @param size: ZYNPU external registers address remap size + * + * @return 0 if successful; others if failed; + */ +int zynpu_priv_init_core(struct zynpu_priv *zynpu, int irqnum, u64 base, u64 size); +/** + * @brief initialize the SoC info in the ZYNPU private data struct + * + * @param zynpu: pointer to ZYNPU private data struct + * @param base: SoC registers phsical base address + * @param size: SoC external registers address remap size + * + * @return 0 if successful; others if failed; + */ +int zynpu_priv_init_soc(struct zynpu_priv *zynpu, u64 base, u64 size); +/** + * @brief add a reserved memory region into the ZYNPU private data struct + * + * @param zynpu: pointer to ZYNPU private data struct + * @param base: memory region start physical address + * @param size: memory region length size + * @param type: ZYNPU memory type + * + * @return 0 if successful; others if failed; + */ +int zynpu_priv_add_mem_region(struct zynpu_priv *zynpu, u64 base, u64 size, + enum zynpu_mem_type type); +/** + * @brief get ZYNPU hardware version number wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return version + */ +int zynpu_priv_get_version(struct zynpu_priv *zynpu); +/** + * @brief enable interrupt wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return void + */ +void zynpu_priv_enable_interrupt(struct zynpu_priv *zynpu); +/** + * @brief disable interrupt wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return void + */ +void zynpu_priv_disable_interrupt(struct zynpu_priv *zynpu); +/** + * @brief disable interrupt wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * @param udesc: descriptor of a job to be triggered on ZYNPU + * @param tid: user thread ID + * + * @return 0 if successful; others if failed; + */ +int zynpu_priv_trigger(struct zynpu_priv *zynpu, struct user_job_desc *udesc, int tid); +/** + * @brief check if ZYNPU is idle wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return 1 if ZYNPU is in IDLE state + */ +bool zynpu_priv_is_idle(struct zynpu_priv *zynpu); +/** + * @brief query ZYNPU capability wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * @param cap: pointer to the capability struct + * + * @return 0 if successful; others if failed; + */ +int zynpu_priv_query_capability(struct zynpu_priv *zynpu, struct zynpu_cap *cap); +/** + * @brief ZYNPU external register read/write wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * @param io_req: pointer to the io_req struct + * + * @return void + */ +void zynpu_priv_io_rw(struct zynpu_priv *zynpu, struct zynpu_io_req *io_req); +/** + * @brief print ZYNPU hardware ID information wrapper + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return void + */ +void zynpu_priv_print_hw_id_info(struct zynpu_priv *zynpu); +/** + * @brief deinit an ZYNPU private data struct + * + * @param zynpu: pointer to ZYNPU private data struct + * + * @return 0 if successful; others if failed; + */ +int deinit_zynpu_priv(struct zynpu_priv *zynpu); + +#endif /* _ZYNPU_H_ */ \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_core.c b/drivers/staging/zynpu/zynpu_core.c new file mode 100644 index 000000000000..d0ab97747a88 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_core.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu.c + * Implementation of the zynpu device struct creation & destroy + */ + +#include <linux/of.h> +#include "zynpu.h" + +extern struct zynpu_io_operation zhouyi_v1_ops; + +struct zynpu_priv z1_platform_priv = { + .version = ZYNPU_VERSION_ZHOUYI_V1, + .core_ctrl = &zhouyi_v1_ops, +}; + +static int init_misc_dev(struct zynpu_priv *zynpu) +{ + int ret = 0; + + if (!zynpu) + return -EINVAL; + + zynpu_fops_register(&zynpu->zynpu_fops); + + zynpu->misc = devm_kzalloc(zynpu->dev, sizeof(struct miscdevice), GFP_KERNEL); + if (!zynpu->misc) { + dev_err(zynpu->dev, "no memory in allocating misc struct\n"); + return -ENOMEM; + } + + /* misc init */ + zynpu->misc->minor = MISC_DYNAMIC_MINOR; + zynpu->misc->name = "zynpu"; + zynpu->misc->fops = &zynpu->zynpu_fops; + zynpu->misc->mode = 0666; + ret = misc_register(zynpu->misc); + if (ret) + dev_err(zynpu->dev, "ZYNPU misc register failed"); + + return ret; +} + +static void deinit_misc_dev(struct zynpu_priv *zynpu) +{ + if (zynpu) + misc_deregister(zynpu->misc); +} + +int init_zynpu_priv(struct zynpu_priv *zynpu, struct device *dev) +{ + int ret = 0; + + if ((!dev) || (!zynpu)) { + dev_err(dev, "invalid input args dts/zynpu to be NULL\n"); + return -EINVAL; + } + + zynpu->dev = dev; + mutex_init(&zynpu->lock); + zynpu->core0 = NULL; + zynpu->misc = NULL; + zynpu->is_suspend = 0; + + /* init memory manager */ + ret = zynpu_init_mm(&zynpu->mm, dev, zynpu->version); + if (ret) + goto err_handle; + + /* init misc device and fops */ + ret = init_misc_dev(zynpu); + if (ret) + goto err_handle; + + ret = zynpu_create_sysfs(zynpu); + if (ret) + goto err_handle; + + goto finish; + +err_handle: + deinit_zynpu_priv(zynpu); + +finish: + return ret; +} + +static int +create_zynpu_core(int version, int irqnum, u64 zynpu_base0, u64 base0_size, + u32 freq, zynpu_irq_uhandler_t uhandler, zynpu_irq_bhandler_t bhandler, + void *zynpu_priv, struct device *dev, struct zynpu_core **p_core) +{ + int ret = 0; + struct zynpu_core *core = NULL; + + if ((!base0_size) || (!zynpu_priv) || (!dev) || (!p_core)) { + if (dev) + dev_err(dev, "invalid input args base0_size/zynpu_priv/p_core to be NULL\n"); + return -EINVAL; + } + + core = devm_kzalloc(dev, sizeof(struct zynpu_core), GFP_KERNEL); + if (!core) { + dev_err(dev, "no memory in allocating zynpu_core struct\n"); + return -ENOMEM; + } + + core->base0 = NULL; + core->irq_obj = NULL; + + /* init core */ + core->max_sched_num = 1; + core->version = version; + + core->base0 = zynpu_create_ioregion(dev, zynpu_base0, base0_size); + if (!core->base0) { + dev_err(dev, "create IO region for core0 failed: base 0x%llx, size 0x%llx\n", + zynpu_base0, base0_size); + return -EFAULT; + } + + core->irq_obj = zynpu_create_irq_object(irqnum, uhandler, bhandler, + zynpu_priv, dev, "zynpu"); + if (!core->irq_obj) { + dev_err(dev, "create IRQ object for core0 failed: IRQ 0x%x\n", irqnum); + return -EFAULT; + } + + core->freq_in_MHz = freq; + core->dev = dev; + + /* success */ + *p_core = core; + return ret; +} + +static void destroy_zynpu_core(struct zynpu_core *core) +{ + if (core) { + if (core->base0) + zynpu_destroy_ioregion(core->base0); + if (core->irq_obj) + zynpu_destroy_irq_object(core->irq_obj); + } +} + +int zynpu_priv_init_core(struct zynpu_priv *zynpu, int irqnum, u64 base, u64 size) +{ + int ret = 0; + + if (!zynpu) + return -EINVAL; + + ret = create_zynpu_core(zynpu->version, irqnum, base, + size, 0, zynpu->core_ctrl->upper_half, + zynpu->core_ctrl->bottom_half, zynpu, + zynpu->dev, &zynpu->core0); + if (ret) + return ret; + + ret = zynpu_init_job_manager(&zynpu->job_manager, zynpu->dev, + zynpu->core0->max_sched_num); + if (ret) + return ret; + + return ret; +} + +int zynpu_priv_add_mem_region(struct zynpu_priv *zynpu, u64 base, u64 size, + enum zynpu_mem_type type) +{ + if (!zynpu) + return -EINVAL; + + return zynpu_mm_add_region(&zynpu->mm, base, size, type); +} + +int zynpu_priv_get_version(struct zynpu_priv *zynpu) +{ + if (zynpu) + return zynpu->core0->version; + return 0; +} + +void zynpu_priv_enable_interrupt(struct zynpu_priv *zynpu) +{ + if (zynpu) + zynpu->core_ctrl->enable_interrupt(zynpu->core0); +} + +void zynpu_priv_disable_interrupt(struct zynpu_priv *zynpu) +{ + if (zynpu) + zynpu->core_ctrl->disable_interrupt(zynpu->core0); +} + +int zynpu_priv_trigger(struct zynpu_priv *zynpu, struct user_job_desc *udesc, int tid) +{ + if (zynpu) + return zynpu->core_ctrl->trigger(zynpu->core0, udesc, tid); + return -EINVAL; +} + +bool zynpu_priv_is_idle(struct zynpu_priv *zynpu) +{ + if (zynpu) + return zynpu->core_ctrl->is_idle(zynpu->core0); + return 0; +} + +int zynpu_priv_query_capability(struct zynpu_priv *zynpu, struct zynpu_cap *cap) +{ + if (zynpu) + return zynpu->core_ctrl->query_capability(zynpu->core0, cap); + return -EINVAL; +} + +void zynpu_priv_io_rw(struct zynpu_priv *zynpu, struct zynpu_io_req *io_req) +{ + if (zynpu) + zynpu->core_ctrl->io_rw(zynpu->core0, io_req); +} + +void zynpu_priv_print_hw_id_info(struct zynpu_priv *zynpu) +{ + if (zynpu) + zynpu->core_ctrl->print_hw_id_info(zynpu->core0); +} + +int deinit_zynpu_priv(struct zynpu_priv *zynpu) +{ + if (!zynpu) + return 0; + + zynpu_destroy_sysfs(zynpu); + + zynpu_deinit_mm(&zynpu->mm); + if (zynpu->misc) + deinit_misc_dev(zynpu); + if (zynpu->core0) { + destroy_zynpu_core(zynpu->core0); + zynpu_deinit_job_manager(&zynpu->job_manager); + } + + return 0; +} \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_drv.c b/drivers/staging/zynpu/zynpu_drv.c new file mode 100644 index 000000000000..5a1967f9981b --- /dev/null +++ b/drivers/staging/zynpu/zynpu_drv.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_platform_driver.c + * Implementations of the ZYNPU platform driver probe/remove func + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include "zynpu.h" + +extern struct zynpu_priv z1_platform_priv; +extern struct zynpu_priv z2_platform_priv; + +static const struct of_device_id zynpu_of_match[] = { +#ifdef CONFIG_ZHOUYI_V1 + { + .compatible = "armchina,zhouyi-v1", + .data = (void*)&z1_platform_priv, + }, +#elif CONFIG_ZHOUYI_V2 + { + .compatible = "armchina,zhouyi-v2", + .data = (void*)&z2_platform_priv, + }, +#endif + { } +}; + +MODULE_DEVICE_TABLE(of, zynpu_of_match); + +struct clk *clk_pll_zynpu; +struct clk *clk_zynpu; +struct clk *clk_zynpu_slv; + +/** + * @brief remove operation registered to platfom_driver struct + * This function will be called while the module is unloading. + * @param p_dev: platform devide struct pointer + * @return 0 if successful; others if failed. + */ +static int zynpu_remove(struct platform_device *p_dev) +{ + int ret = 0; + struct device *dev = &p_dev->dev; + struct zynpu_priv *zynpu = platform_get_drvdata(p_dev); + + if (!IS_ERR_OR_NULL(clk_zynpu_slv)) + { + clk_disable_unprepare(clk_zynpu_slv); + dev_info(dev, "clk_zynpu_slv disable ok "); + } + + if (!IS_ERR_OR_NULL(clk_zynpu)) + { + clk_disable_unprepare(clk_zynpu); + dev_info(dev, "clk_zynpu disable ok"); + } + clk_zynpu = NULL; + clk_zynpu_slv = NULL; + clk_pll_zynpu = NULL; + + zynpu_priv_disable_interrupt(zynpu); + + ret = deinit_zynpu_priv(zynpu); + if (ret) + return ret; + + /* success */ + dev_info(dev, "ZYNPU KMD remove done"); + return 0; +} + +/** + * @brief probe operation registered to platfom_driver struct + * This function will be called while the module is loading. + * + * @param p_dev: platform devide struct pointer + * @return 0 if successful; others if failed. + */ +static int zynpu_probe(struct platform_device *p_dev) +{ + int ret = 0; + struct device *dev = &p_dev->dev; + struct device_node *dev_node = dev->of_node; + const struct of_device_id *of_id; + struct resource *res; + struct resource res_mem; + struct zynpu_priv *zynpu; + struct device_node *np; + int irqnum = 0; + int cma_reserve_size = 0; + u64 base = 0; + u64 base_size = 0; + + dev_info(dev, "ZYNPU KMD probe start...\n"); + + /* match */ + of_id = of_match_node(zynpu_of_match, dev_node); + if (!of_id) { + dev_err(dev, "[Probe 0/3] match node failed\n"); + return -EINVAL; + } + zynpu = (struct zynpu_priv *)of_id->data; + + if (zynpu->version == ZYNPU_VERSION_ZHOUYI_V1) + dev_info(dev, "[Probe 0/3] ZYNPU version: zhouyi-v1\n"); + else if (zynpu->version == ZYNPU_VERSION_ZHOUYI_V2) + dev_info(dev, "[Probe 0/3] ZYNPU version: zhouyi-v2\n"); + else + dev_err(dev, "[Probe 0/3] Unrecognized ZYNPU version: 0x%x\n", zynpu->version); + + ret = init_zynpu_priv(zynpu, dev); + if (ret) + return ret; + + /* get ZYNPU IO */ + res = platform_get_resource(p_dev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "[Probe 1/3] get platform io region failed\n"); + ret = -EINVAL; + goto probe_fail; + } + base = res->start; + base_size = res->end - res->start + 1; + dev_dbg(dev, "[Probe 1/3] get ZYNPU IO region: [0x%llx, 0x%llx]\n", + base, res->end); + + /* get interrupt number */ + res = platform_get_resource(p_dev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "[Probe 1/3] get irqnum failed\n"); + ret = -EINVAL; + goto probe_fail; + } + irqnum = res->start; + dev_dbg(dev, "[Probe 1/3] get IRQ number: 0x%x\n", irqnum); + + ret = zynpu_priv_init_core(zynpu, irqnum, base, base_size); + if (ret) { + goto probe_fail; + } + dev_info(dev, "[Probe 1/3] Probe stage 1/3 done: create core\n"); + + /* get CMA reserved buffer info */ + np = of_parse_phandle(dev->of_node, "memory-region", 0); + if (np) { + if (of_address_to_resource(np, 0, &res_mem)) + goto probe_fail; + dev_dbg(dev, "[Probe 2/3] get CMA region: [0x%llx, 0x%llx]\n", + res_mem.start, res_mem.end); + ret = zynpu_priv_add_mem_region(zynpu, res_mem.start, + res_mem.end - res_mem.start + 1, + ZYNPU_MEM_TYPE_CMA); + if (ret) { + dev_err(dev, "[Probe 2/3] add new region failed\n"); + goto probe_fail; + } + of_node_put(np); + + ret = of_property_read_u32(dev->of_node, "cma-reserved-bytes", &cma_reserve_size); + if (ret) { + dev_err(dev, "get cma reserved size property failed!"); + goto probe_fail; + } + + ret = zynpu_priv_add_mem_region(zynpu, res_mem.start, cma_reserve_size, ZYNPU_MEM_TYPE_CMA); + if (ret) { + dev_err(dev, "[Probe 2/3] add new region failed\n"); + goto probe_fail; + } + dev_info(dev, "[Probe 2/3] get CMA size 0x%x\n", cma_reserve_size); + } else { + dev_info(dev, "[Probe 2/3] No %s specified\n", "memory-region"); + } + + + /* get SRAM reserved buffer info, optional */ + np = of_parse_phandle(dev->of_node, "sram-region", 0); + if (np) { + if (of_address_to_resource(np, 0, &res_mem)) + goto probe_fail; + dev_dbg(dev, "[Probe 2/3] get SRAM region: [0x%llx, 0x%llx]\n", + res_mem.start, res_mem.end); + ret = zynpu_priv_add_mem_region(zynpu, res_mem.start, + res_mem.end - res_mem.start + 1, ZYNPU_MEM_TYPE_SRAM); + if (ret) { + dev_err(dev, "[Probe 2/3] add new region failed\n"); + goto probe_fail; + } + of_node_put(np); + dev_info(dev, "[Probe 2/3] Stage 2/3 done: add memory region(s)\n"); + } else { + dev_dbg(dev, "[Probe 2/3] No %s specified\n", "sram-region"); + } + + /* set clock enable */ + clk_zynpu = of_clk_get(dev_node, 0); + if (IS_ERR_OR_NULL(clk_zynpu)) { + dev_err(dev, "clk_zynpu get failed\n"); + ret = PTR_ERR(clk_zynpu); + goto probe_fail; + } + + clk_pll_zynpu = of_clk_get(dev_node, 1); + if (IS_ERR_OR_NULL(clk_pll_zynpu)) { + dev_err(dev, "clk_pll_zynpu get failed\n"); + ret = PTR_ERR(clk_pll_zynpu); + goto probe_fail; + } + + clk_zynpu_slv = of_clk_get(dev_node, 2); + if (IS_ERR_OR_NULL(clk_zynpu_slv)) { + dev_err(dev, "clk_zynpu_slv get failed\n"); + ret = PTR_ERR(clk_zynpu_slv); + goto probe_fail; + } + + if (clk_set_rate(clk_zynpu, 600 * 1000000)) { + dev_err(dev, "set clk_zynpu rate fail\n"); + ret = -EBUSY; + goto probe_fail; + } + + if (clk_prepare_enable(clk_zynpu)) { + dev_err(dev, "clk_zynpu enable failed\n"); + ret = -EBUSY; + goto probe_fail; + } + + if (clk_prepare_enable(clk_pll_zynpu)) { + dev_err(dev, "clk_zynpu_slv enable failed\n"); + ret = -EBUSY; + goto probe_fail; + } + if (clk_prepare_enable(clk_zynpu_slv)) { + dev_err(dev, "clk_zynpu_slv enable failed\n"); + ret = -EBUSY; + goto probe_fail; + } + dev_info(dev, "set zynpu clock ok!"); + + zynpu_priv_enable_interrupt(zynpu); + zynpu_priv_print_hw_id_info(zynpu); + dev_info(dev, "[Probe 3/3] Stage 3/3 done: IO read/write\n"); + + /* success */ + platform_set_drvdata(p_dev, zynpu); + dev_info(dev, "ZYNPU KMD probe done"); + goto finish; + + /* failed */ +probe_fail: + + if(!IS_ERR_OR_NULL(clk_zynpu_slv)) { + clk_disable_unprepare(clk_zynpu_slv); + } + if(!IS_ERR_OR_NULL(clk_zynpu)) { + clk_disable_unprepare(clk_zynpu); + } + clk_zynpu = NULL; + clk_zynpu_slv = NULL; + clk_pll_zynpu = NULL; + + deinit_zynpu_priv(zynpu); + +finish: + return ret; +} + +static int zynpu_suspend(struct platform_device *p_dev,pm_message_t state) +{ + struct device *dev = &p_dev->dev; + struct zynpu_priv *zynpu = platform_get_drvdata(p_dev); + + if (zynpu && zynpu_priv_is_idle(zynpu)) { + dev_info(dev, "zynpu in idle status !"); + } else { + dev_err(dev,"zynpu in busy status !"); + return -1; + } + + if (!IS_ERR_OR_NULL(clk_zynpu_slv)) { + clk_disable_unprepare(clk_zynpu_slv); + dev_info(dev, "disable clk_zynpu_slv ok"); + } + if (!IS_ERR_OR_NULL(clk_zynpu)) { + clk_disable_unprepare(clk_zynpu); + dev_info(dev, "disable clk_zynpu ok"); + } + dev_info(dev, "zynpu_suspend ok"); + + return 0; +} + +static int zynpu_resume(struct platform_device *p_dev) +{ + struct device *dev = &p_dev->dev; + struct zynpu_priv *zynpu = platform_get_drvdata(p_dev); + + if(NULL == zynpu) { + dev_err(dev, "zynpu is null ,resume fail...!"); + return -1; + } + + if (clk_set_parent(clk_zynpu, clk_pll_zynpu)) { + dev_err(dev, "set clk_zynpu parent fail\n"); + } + if (clk_set_rate(clk_zynpu, 600 * 1000000)) { + dev_err(dev, "set clk_zynpu rate fail\n"); + } + if (clk_prepare_enable(clk_zynpu_slv)) { + dev_err(dev, "clk_zynpu_slv enable failed\n"); + } + if (clk_prepare_enable(clk_zynpu)) { + dev_err(dev, "clk_zynpu enable failed\n"); + } + + zynpu_priv_enable_interrupt(zynpu); + zynpu_priv_print_hw_id_info(zynpu); + dev_info(dev, "zynpu_resume ok."); + + return 0; +} + +static struct platform_driver zynpu_platform_driver = { + .probe = zynpu_probe, + .remove = zynpu_remove, + .suspend = zynpu_suspend, + .resume = zynpu_resume, + .driver = { + .name = "armchina-zynpu", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(zynpu_of_match), + }, +}; + +module_platform_driver(zynpu_platform_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/zynpu/zynpu_fops.c b/drivers/staging/zynpu/zynpu_fops.c new file mode 100644 index 000000000000..ad22c0969ca5 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_fops.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_fops.c + * Implementations of KMD file operation API + */ + +#include <linux/fs.h> +#include <linux/mm_types.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include <linux/poll.h> +#include "zynpu_mm.h" +#include "zynpu_job_manager.h" +#include "zynpu_session.h" +#include "zynpu.h" + +static int zynpu_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct zynpu_priv *zynpu = NULL; + struct zynpu_session *session = NULL; + int pid = task_pid_nr(current); + + zynpu = container_of(filp->f_op, struct zynpu_priv, zynpu_fops); + + ret = zynpu_create_session(pid, zynpu, &session); + if (ret) { + return ret; + } else { + filp->private_data = session; + filp->f_pos = 0; + } + + /* success */ + return ret; +} + +static int zynpu_release(struct inode *inode, struct file *filp) +{ + struct zynpu_priv *zynpu = NULL; + struct zynpu_session *session = filp->private_data; + + if (!session) + return -EINVAL; + + zynpu = container_of(filp->f_op, struct zynpu_priv, zynpu_fops); + + /* jobs should be cleared prior to buffer free */ + zynpu_job_manager_cancel_session_jobs(&zynpu->job_manager, session); + + zynpu_mm_free_session_buffers(&zynpu->mm, session); + + zynpu_destroy_session(session); + + return 0; +} + +static long zynpu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int cp_ret = 0; + struct zynpu_session *session = filp->private_data; + struct zynpu_priv *zynpu = NULL; + + struct zynpu_cap cap; + struct buf_request buf_req; + struct zynpu_buffer buf; + struct user_job user_job; + struct session_job *kern_job = NULL; + struct buf_desc desc; + struct zynpu_io_req io_req; + struct job_status_query job; + u32 job_id; + + if (!session) + return -EINVAL; + + zynpu = container_of(filp->f_op, struct zynpu_priv, zynpu_fops); + + switch (cmd) + { + case IPUIOC_QUERYCAP: + ret = copy_from_user(&cap, (struct zynpu_cap __user*)arg, sizeof(struct zynpu_cap)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: QUERYCAP copy from user failed!"); + else { + zynpu_priv_query_capability(zynpu, &cap); + /* copy cap info/errcode to user for reference */ + cp_ret = copy_to_user((struct zynpu_cap __user*)arg, &cap, sizeof(struct zynpu_cap)); + if ((ZYNPU_ERRCODE_NO_ERROR == ret) && (cp_ret)) + ret = cp_ret; + } + break; + case IPUIOC_REQBUF: + ret = copy_from_user(&buf_req, (struct buf_request __user*)arg, sizeof(struct buf_request)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: REQBUF copy from user failed!"); + else { + ret = zynpu_mm_alloc(&zynpu->mm, &buf_req, &buf); + if (ZYNPU_ERRCODE_NO_ERROR == ret) { + ret = zynpu_session_add_buf(session, &buf_req, &buf); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: add buf failed!"); + } + + /* copy buf info/errcode to user for reference */ + cp_ret = copy_to_user((struct buf_request __user*)arg, &buf_req, sizeof(struct buf_request)); + if ((ZYNPU_ERRCODE_NO_ERROR == ret) && (cp_ret)) + ret = cp_ret; + } + break; + case IPUIOC_RUNJOB: + ret = copy_from_user(&user_job, (struct user_job __user*)arg, sizeof(struct user_job)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: RUNJOB copy from user failed!"); + else { + kern_job = zynpu_session_add_job(session, &user_job); + if (NULL == kern_job) + dev_err(zynpu->dev, "KMD ioctl: RUNJOB add failed!"); + else { + ret = zynpu_job_manager_schedule_new_job(&zynpu->job_manager, &user_job, kern_job, + session); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: RUNJOB run failed!"); + } + + /* copy job errcode to user for reference */ + cp_ret = copy_to_user((struct user_job __user*)arg, &user_job, sizeof(struct user_job)); + if ((ZYNPU_ERRCODE_NO_ERROR == ret) && (cp_ret)) + ret = cp_ret; + } + break; + case IPUIOC_KILL_TIMEOUT_JOB: + ret = copy_from_user(&job_id, (u32 __user*)arg, sizeof(__u32)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: KILL_TIMEOUT_JOB copy from user failed!"); + else + ret = zynpu_invalidate_timeout_job(&zynpu->job_manager, job_id); + break; + case IPUIOC_FREEBUF: + ret = copy_from_user(&desc, (struct buf_desc __user*)arg, sizeof(struct buf_desc)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: FREEBUF copy from user failed!"); + else { + /* detach first to validate the free buf request */ + ret = zynpu_session_detach_buf(session, &desc); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: detach session buffer failed!"); + else { + /* do free operation */ + ret = zynpu_mm_free(&zynpu->mm, &desc); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: free buf failed!"); + } + } + break; + case IPUIOC_REQIO: + ret = copy_from_user(&io_req, (struct zynpu_io_req __user *)arg, sizeof(struct zynpu_io_req)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: REQIO copy from user failed!"); + else { + zynpu_priv_io_rw(zynpu, &io_req); + ret = copy_to_user((struct zynpu_io_req __user *)arg, &io_req, sizeof(struct zynpu_io_req)); + } + break; + case IPUIOC_QUERYSTATUS: + ret = copy_from_user(&job, (struct job_status_query __user *)arg, + sizeof(struct job_status_query)); + if (ret) + dev_err(zynpu->dev, "KMD ioctl: QUERYSTATUS copy from user failed!"); + else { + ret = zynpu_session_get_job_status(session, &job); + if (ZYNPU_ERRCODE_NO_ERROR == ret) + ret = copy_to_user((struct job_status_query __user *)arg, &job, + sizeof(struct job_status_query)); + } + break; + default: + dev_err(zynpu->dev, "no matching ioctl call (cmd = 0x%lx)!", (unsigned long)cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +static int zynpu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret = 0; + struct zynpu_priv *zynpu = NULL; + struct zynpu_session *session = filp->private_data; + + if (!session) + return -EINVAL; + + zynpu = container_of(filp->f_op, struct zynpu_priv, zynpu_fops); + ret = zynpu_session_mmap_buf(session, vma, zynpu->dev); + if (ret) + dev_err(zynpu->dev, "mmap to userspace failed!"); + + return ret; +} + +static unsigned int zynpu_poll(struct file *filp, struct poll_table_struct *wait) +{ + unsigned int mask = 0; + struct zynpu_session *session = filp->private_data; + int tid = task_pid_nr(current); + + if (!session) + return 0; + + zynpu_session_add_poll_wait_queue(session, filp, wait, tid); + + if (zynpu_session_thread_has_end_job(session, tid)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +int zynpu_fops_register(struct file_operations *fops) +{ + if (!fops) + return -EINVAL; + + fops->owner = THIS_MODULE; + fops->open = zynpu_open; + fops->poll = zynpu_poll; + fops->unlocked_ioctl = zynpu_ioctl; +#ifdef CONFIG_COMPAT + fops->compat_ioctl = zynpu_ioctl; +#endif + fops->mmap = zynpu_mmap; + fops->release = zynpu_release; + + return 0; +} diff --git a/drivers/staging/zynpu/zynpu_io.c b/drivers/staging/zynpu/zynpu_io.c new file mode 100644 index 000000000000..5ea395ae8aa9 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_io.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_io.c + * Implementations of ZYNPU IO R/W API + */ + +#include <asm/io.h> +#include "zynpu_io.h" + +struct io_region *zynpu_create_ioregion(struct device *dev, u64 phys_base, u32 size) +{ + struct io_region *region = NULL; + + if ((!size) || (!dev)) { + dev_dbg(dev, "invalid input args size/dev!"); + goto fail; + } + + region = devm_kzalloc(dev, sizeof(struct io_region), GFP_KERNEL); + if (!region) + goto fail; + + if (!request_mem_region(phys_base, size, "zynpu")) { + dev_err(dev, "request IO region failed"); + goto fail; + } + + region->kern = ioremap(phys_base, size); + if (!region->kern) { + dev_err(dev, "ioremap failed"); + goto fail; + } + + region->phys = phys_base; + region->size = size; + + /* success */ + goto finish; + +fail: + dev_err(dev, "creating IO region [0x%llx, 0x%llx] failed", + phys_base, phys_base + size - 1); + +finish: + return region; +} + +void zynpu_destroy_ioregion(struct io_region *region) +{ + if (region && region->kern) { + iounmap(region->kern); + release_mem_region(region->phys, region->size); + region->kern = NULL; + region->phys = 0; + region->size = 0; + } +} + +u8 zynpu_read8(struct io_region *region, __IO offset) +{ + if (region && region->kern && (offset < region->size)) + return readb((void __iomem *)((__IO)(region->kern) + offset)); + else { + pr_err("KMD io error: read8 invalid operation or args!"); + return 0; + } +} + +u16 zynpu_read16(struct io_region *region, __IO offset) +{ + if (region && region->kern && (offset < region->size)) + return readw((void __iomem *)((__IO)(region->kern) + offset)); + else { + pr_err("KMD io error: read16 invalid operation or args!"); + return 0; + } +} + +u32 zynpu_read32(struct io_region *region, __IO offset) +{ + if (region && region->kern && (offset < region->size)) + return readl((void __iomem *)((__IO)(region->kern) + offset)); + else { + if (region) { + pr_err("KMD io error: read32 invalid operation or args! \ + (offset 0x%lx, region_max 0x%lx)", + (unsigned long)offset, (unsigned long)region->size); + } else { + pr_err("KMD io error: read32 invalid args to be NULL!"); + } + return 0; + } +} + +void zynpu_write8(struct io_region *region, __IO offset, unsigned int data) +{ + data = data & 0xFF; + if (region && region->kern && (offset < region->size)) + return writeb((u8)data, (void __iomem *)((__IO)(region->kern) + offset)); + else + pr_err("KMD io error: write8 invalid operation or args!"); +} + +void zynpu_write16(struct io_region *region, __IO offset, unsigned int data) +{ + data = data & 0xFFFF; + if (region && region->kern && (offset < region->size)) + return writew((u16)data, (void __iomem *)((__IO)(region->kern) + offset)); + else + pr_err("KMD io error: write16 invalid operation or args!"); +} + +void zynpu_write32(struct io_region *region, __IO offset, unsigned int data) +{ + if (region && region->kern && (offset < region->size)) + return writel((u32)data, (void __iomem *)((__IO)(region->kern) + offset)); + else { + if (region) + pr_err("KMD io error: write32 invalid operation or args! \ + (offset 0x%lx, region_max 0x%lx)", + (unsigned long)offset, (unsigned long)region->size); + else + pr_err("KMD io error: write32 invalid args to be NULL!"); + } +} \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_io.h b/drivers/staging/zynpu/zynpu_io.h new file mode 100644 index 000000000000..b9be82f9f5c1 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_io.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_io.h + * ZYNPU IO R/W API header file + */ + +#ifndef _ZYNPU_IO_H_ +#define _ZYNPU_IO_H_ + +#include <linux/device.h> +#include <asm/io.h> +#include <asm/types.h> + +typedef volatile unsigned long __IO; + +enum zynpu_rw_attr { + ZYNPU_IO_READ, + ZYNPU_IO_WRITE +}; + +struct zynpu_io_req { + __u32 offset; + enum zynpu_rw_attr rw; + __u32 value; + __u32 errcode; +}; + +/** + * struct io_region - a general struct describe IO region + * + * @phys: physical address base of an IO region + * @kern: kernel virtual address base remapped from phys + * @size: size of of an IO region in byte + */ +struct io_region { + u64 phys; + void *kern; + u32 size; +}; + +/** + * @brief create ZYNPU IO region using physical base address + * + * @param dev: device pointer + * @param phys_base: base address + * @param size: region size + * + * @return io_region pointer if successful; NULL if failed; + */ +struct io_region *zynpu_create_ioregion(struct device *dev, u64 phys_base, u32 size); +/** + * @brief destroy an ZYNPU IO region + * + * @param region: region pointer created by zynpu_create_ioregion + */ +void zynpu_destroy_ioregion(struct io_region *region); +/* + * @brief read ZYNPU register in byte (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @return register value + */ +u8 zynpu_read8(struct io_region *region, __IO offset); +/* + * @brief read ZYNPU register in half-word (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @return register value + */ +u16 zynpu_read16(struct io_region *region, __IO offset); +/* + * @brief read ZYNPU register in word (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @return register value + */ +u32 zynpu_read32(struct io_region *region, __IO offset); +/* + * @brief write ZYNPU register in byte (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @param data: data to be writen + * @return void + */ +void zynpu_write8(struct io_region *region, __IO offset, unsigned int data); +/* + * @brief write ZYNPU register in half-word (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @param data: data to be writen + * @return void + */ +void zynpu_write16(struct io_region *region, __IO offset, unsigned int data); +/* + * @brief write ZYNPU register in word (with memory barrier) + * + * @param region: IO region providing the base address + * @param offset: ZYNPU register offset + * @param data: data to be writen + * @return void + */ +void zynpu_write32(struct io_region *region, __IO offset, unsigned int data); + +#define ZYNPU_BIT(data, n) (((data)>>(n))&0x1) + +#endif /* _ZYNPU_IO_H_ */ diff --git a/drivers/staging/zynpu/zynpu_irq.c b/drivers/staging/zynpu/zynpu_irq.c new file mode 100644 index 000000000000..00a85e2159ca --- /dev/null +++ b/drivers/staging/zynpu/zynpu_irq.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_irq.c + * Implementation of the interrupt request and handlers' abstraction + */ + +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irqreturn.h> +#include "zynpu_irq.h" +#include "zynpu.h" + +static irqreturn_t zynpu_irq_handler_upper_half(int irq, void *dev_id) +{ + int ret = 0; + struct zynpu_irq_object *irq_obj = NULL; + struct zynpu_priv *zynpu = NULL; + + if (!dev_id) + return IRQ_NONE; + + zynpu = (struct zynpu_priv *)(((struct device *)dev_id)->driver_data); + irq_obj = zynpu->core0->irq_obj; + ret = irq_obj->uhandler(zynpu); + if (ret) + return IRQ_NONE; + + + return IRQ_HANDLED; +} + +static void zynpu_irq_handler_bottom_half(struct work_struct *work) +{ + struct zynpu_irq_object *irq_obj = NULL; + + if (work) { + irq_obj = container_of(work, struct zynpu_irq_object, work); + irq_obj->bhandler(irq_obj->zynpu_priv); + } +} + +struct zynpu_irq_object *zynpu_create_irq_object(u32 irqnum, zynpu_irq_uhandler_t uhandler, + zynpu_irq_bhandler_t bhandler, void *zynpu_priv, struct device *dev, char *description) +{ + int ret = 0; + struct zynpu_irq_object *irq_obj = NULL; + + if ((!zynpu_priv) || (!dev) || (!description)) { + ret = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + goto finish; + } + + irq_obj = kzalloc(sizeof(struct zynpu_irq_object), GFP_KERNEL); + if (!irq_obj) + goto finish; + + irq_obj->zynpu_wq = NULL; + irq_obj->irqnum = 0; + irq_obj->dev = dev; + + irq_obj->zynpu_wq = create_singlethread_workqueue("zynpu"); + if (!irq_obj->zynpu_wq) + goto err_handle; + + INIT_WORK(&irq_obj->work, zynpu_irq_handler_bottom_half); + + ret = request_irq(irqnum, zynpu_irq_handler_upper_half, + IRQF_SHARED | IRQF_TRIGGER_RISING, description, dev); + if (ret) { + dev_err(dev, "request IRQ (num %u) failed! (errno = %d)", irqnum, ret); + goto err_handle; + } + + irq_obj->irqnum = irqnum; + irq_obj->uhandler = uhandler; + irq_obj->bhandler = bhandler; + irq_obj->zynpu_priv = zynpu_priv; + + /* success */ + goto finish; + +err_handle: + zynpu_destroy_irq_object(irq_obj); + irq_obj = NULL; + +finish: + return irq_obj; +} + +void zynpu_irq_schedulework(struct zynpu_irq_object *irq_obj) +{ + if (irq_obj) + queue_work(irq_obj->zynpu_wq, &irq_obj->work); +} + +void zynpu_irq_flush_workqueue(struct zynpu_irq_object *irq_obj) +{ + /* only one workqueue currently */ + flush_workqueue(irq_obj->zynpu_wq); +} + +void zynpu_destroy_irq_object(struct zynpu_irq_object *irq_obj) +{ + if (irq_obj) { + if (irq_obj->zynpu_wq) { + flush_workqueue(irq_obj->zynpu_wq); + destroy_workqueue(irq_obj->zynpu_wq); + irq_obj->zynpu_wq = NULL; + } + if (irq_obj->irqnum) + free_irq(irq_obj->irqnum, irq_obj->dev); + kfree(irq_obj); + flush_scheduled_work(); + } +} \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_irq.h b/drivers/staging/zynpu/zynpu_irq.h new file mode 100644 index 000000000000..97b2fc0cd634 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_irq.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_irq.h + * Header of the interrupt request and handlers' abstraction + */ + +#ifndef _ZYNPU_IRQ_H_ +#define _ZYNPU_IRQ_H_ + +#include <linux/device.h> +#include <linux/workqueue.h> + +typedef int (*zynpu_irq_uhandler_t) (void *arg); +typedef void (*zynpu_irq_bhandler_t) (void *arg); +typedef void (*zynpu_irq_trigger_t) (void *arg); +typedef void (*zynpu_irq_ack_t) (void *arg); + +/** + * struct zynpu_irq_object - IRQ instance for each hw module in ZYNPU with interrupt function + * + * @irqnum: interrupt number used to request IRQ + * @zynpu_priv: zynpu_priv struct pointer + * @uhandler: real upper-half handler + * @bhandler: real bottom-half handler + * @work: work struct + * @dev: device pointer + * @zynpu_wq: workqueue struct pointer + */ +struct zynpu_irq_object { + u32 irqnum; + void *zynpu_priv; + zynpu_irq_uhandler_t uhandler; + zynpu_irq_bhandler_t bhandler; + struct work_struct work; + struct device *dev; + struct workqueue_struct *zynpu_wq; +}; + +/** + * @brief initialize an ZYNPU IRQ object for a HW module with interrupt function + * + * @param irqnum: interrupt number + * @param uhandler: upper-half handler + * @param bhandler: bottom-half handler + * @zynpu_priv: zynpu_priv struct pointer + * @param description: irq object description string + * + * @return irq_object pointer if successful; NULL if failed; + */ +struct zynpu_irq_object *zynpu_create_irq_object(u32 irqnum, zynpu_irq_uhandler_t uhandler, + zynpu_irq_bhandler_t bhandler, void *zynpu_priv, struct device *dev, char *description); +/** + * @brief workqueue schedule API + * + * @param irq_obj: interrupt object + * + * @return void + */ +void zynpu_irq_schedulework(struct zynpu_irq_object *irq_obj); +/** + * @brief workqueue flush API + * + * @param irq_obj: interrupt object + * + * @return void + */ +void zynpu_irq_flush_workqueue(struct zynpu_irq_object *irq_obj); +/** + * @brief workqueue terminate API + * + * @param irq_obj: interrupt object + * + * @return void + */ +void zynpu_destroy_irq_object(struct zynpu_irq_object *irq_obj); + +#endif //_ZYNPU_IRQ_H_ \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_job_manager.c b/drivers/staging/zynpu/zynpu_job_manager.c new file mode 100644 index 000000000000..8105fe0aba8a --- /dev/null +++ b/drivers/staging/zynpu/zynpu_job_manager.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_job_manager.c + * Job manager module implementation file + */ + +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/time.h> +#include "zynpu_job_manager.h" +#include "zynpu.h" + +static int init_zynpu_job(struct zynpu_job *zynpu_job, struct user_job_desc *desc, + struct session_job *kern_job, struct zynpu_session *session) +{ + int ret = 0; + + if (!zynpu_job) { + ret = -EINVAL; + goto finish; + } + + if (kern_job) + zynpu_job->uthread_id = kern_job->uthread_id; + + if (!desc) + memset(&zynpu_job->desc, 0, sizeof(struct user_job_desc)); + else + zynpu_job->desc = *desc; + zynpu_job->session = (struct zynpu_session *)session; + zynpu_job->session_job = (struct session_job *)kern_job; + zynpu_job->state = ZYNPU_JOB_STATE_IDLE; + zynpu_job->exception_flag = ZYNPU_EXCEP_NO_EXCEPTION; + zynpu_job->valid_flag = ZYNPU_JOB_FLAG_VALID; + INIT_LIST_HEAD(&zynpu_job->node); + +finish: + return ret; +} + +static void destroy_zynpu_job(struct zynpu_job *job) +{ + if (job) + kfree(job); +} + +static void remove_zynpu_job(struct zynpu_job *job) +{ + if (job) { + list_del(&job->node); + destroy_zynpu_job(job); + } + +} + +static struct zynpu_job *create_zynpu_job(struct user_job_desc *desc, + struct session_job *kern_job, struct zynpu_session *session) +{ + struct zynpu_job *new_zynpu_job = NULL; + + new_zynpu_job = kzalloc(sizeof(struct zynpu_job), GFP_KERNEL); + if (init_zynpu_job(new_zynpu_job, desc, kern_job, session) != 0) { + destroy_zynpu_job(new_zynpu_job); + new_zynpu_job = NULL; + } + + return new_zynpu_job; +} + +static void zynpu_job_manager_trigger_job_sched(struct zynpu_priv *zynpu, struct zynpu_job *zynpu_job) +{ + if (zynpu && zynpu_job) { + zynpu_priv_trigger(zynpu, &zynpu_job->desc, zynpu_job->uthread_id); + if (is_session_job_prof_enabled(zynpu_job->session_job)) + session_job_mark_sched(zynpu_job->session_job); + } +} + + +int zynpu_init_job_manager(struct zynpu_job_manager *job_manager, struct device *p_dev, int max_sched_num) +{ + int ret = 0; + + if ((!job_manager) || (!p_dev)) + return -EINVAL; + + if (job_manager->init_done) + return 0; + + job_manager->scheduled_queue_head = create_zynpu_job(NULL, NULL, NULL); + job_manager->pending_queue_head = create_zynpu_job(NULL, NULL, NULL); + if ((!job_manager->pending_queue_head) || (!job_manager->scheduled_queue_head)) + return -ENOMEM; + + job_manager->sched_num = 0; + job_manager->max_sched_num = max_sched_num; + spin_lock_init(&job_manager->lock); + job_manager->dev = p_dev; + job_manager->init_done = 1; + + return ret; +} + +static void delete_queue(struct zynpu_job *head) +{ + struct zynpu_job *cursor = head; + struct zynpu_job *next = NULL; + + if (head) { + list_for_each_entry_safe(cursor, next, &head->node, node) { + remove_zynpu_job(cursor); + } + } +} + +void zynpu_deinit_job_manager(struct zynpu_job_manager *job_manager) +{ + if (job_manager) { + delete_queue(job_manager->scheduled_queue_head); + delete_queue(job_manager->pending_queue_head); + job_manager->sched_num = 0; + } +} + +static void zynpu_schedule_pending_job_no_lock(struct zynpu_job_manager *job_manager) +{ + struct zynpu_job *curr = NULL; + struct zynpu_priv *zynpu = container_of(job_manager, struct zynpu_priv, job_manager); + + if (!job_manager) { + dev_err(job_manager->dev, "invalid input args user_job or kern_job or session to be NULL!"); + return; + } + + /* 1st pending job should be scheduled if any */ + if ((!list_empty(&job_manager->pending_queue_head->node)) && + (job_manager->sched_num < job_manager->max_sched_num) && + (zynpu_priv_is_idle(zynpu))) { + /* + detach head of pending queue and add it to the tail of scheduled job queue + + |--->>------->>---| + |(real head) |(tail) + -------------------------------- ---------------------------------- + | j <=> j <=> j <=> j <=> head | | [empty to fill] <=> j <=> head | + -------------------------------- ---------------------------------- + pending job queue scheduled job queue + */ + curr = list_next_entry(job_manager->pending_queue_head, node); + + zynpu_job_manager_trigger_job_sched(zynpu, curr); + curr->state = ZYNPU_JOB_STATE_SCHED; + list_move_tail(&curr->node, &job_manager->scheduled_queue_head->node); + job_manager->sched_num++; + } else { + /** + * do nothing because no pending job needs to be scheduled + * or ZYNPU is not available to accept more jobs + */ + if (list_empty(&job_manager->pending_queue_head->node)) { + if (!task_pid_nr(current)) + dev_dbg(job_manager->dev, "[IRQ] no pending job to trigger"); + else + dev_dbg(job_manager->dev, "[%u] no pending job to trigger", task_pid_nr(current)); + } + + if (job_manager->sched_num >= job_manager->max_sched_num) { + if (!task_pid_nr(current)) + dev_dbg(job_manager->dev, "[IRQ] ZYNPU busy and do not trigger"); + else + dev_dbg(job_manager->dev, "[%u] ZYNPU busy and do not trigger", task_pid_nr(current)); + } + + } +} + +int zynpu_job_manager_schedule_new_job(struct zynpu_job_manager *job_manager, struct user_job *user_job, + struct session_job *session_job, struct zynpu_session *session) +{ + int ret = 0; + struct zynpu_job *zynpu_job = NULL; + + if ((!job_manager) || (!user_job) || (!session_job) || (!session)) { + if (user_job) + user_job->errcode = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + ret = -EINVAL; + goto finish; + } + + zynpu_job = create_zynpu_job(&user_job->desc, session_job, session); + if (!zynpu_job) { + user_job->errcode = ZYNPU_ERRCODE_CREATE_KOBJ_ERR; + ret = -EFAULT; + goto finish; + } + + /* LOCK */ + spin_lock_irq(&job_manager->lock); + + /* pending the flushed job from userland and try to schedule it */ + zynpu_job->state = ZYNPU_JOB_STATE_PENDING; + list_add_tail(&zynpu_job->node, &job_manager->pending_queue_head->node); + zynpu_schedule_pending_job_no_lock(job_manager); + + spin_unlock_irq(&job_manager->lock); + /* UNLOCK */ + + /* success */ + user_job->errcode = 0; + +finish: + return ret; +} + +static int zynpu_invalidate_job_no_lock(struct zynpu_job_manager *job_manager, + struct zynpu_job *job) +{ + //struct zynpu_priv *zynpu = container_of(job_manager, struct zynpu_priv, job_manager); + + if ((!job_manager) || (!job)) + return -EINVAL; + + if (job->state == ZYNPU_JOB_STATE_SCHED) { + job->valid_flag = 0; + } else if (job->state == ZYNPU_JOB_STATE_PENDING) { + remove_zynpu_job(job); + } else + return -EINVAL; + + return 0; +} + +static void zynpu_invalidate_canceled_jobs_no_lock(struct zynpu_job_manager *job_manager, + struct zynpu_job *head, struct zynpu_session *session) +{ + struct zynpu_job *cursor = NULL; + struct zynpu_job *next = NULL; + + if ((!job_manager) || (!head) || (!session)) + return; + + list_for_each_entry_safe(cursor, next, &head->node, node) { + if (zynpu_get_session_pid(cursor->session) == zynpu_get_session_pid(session)) + zynpu_invalidate_job_no_lock(job_manager, cursor); + } +} + +int zynpu_job_manager_cancel_session_jobs(struct zynpu_job_manager *job_manager, + struct zynpu_session *session) +{ + int ret = 0; + + if (!session) { + ret = -EINVAL; + goto finish; + } + + /* LOCK */ + spin_lock_irq(&job_manager->lock); + + /** + * invalidate all active jobs of this session in job manager + */ + zynpu_invalidate_canceled_jobs_no_lock(job_manager, job_manager->pending_queue_head, session); + zynpu_invalidate_canceled_jobs_no_lock(job_manager, job_manager->scheduled_queue_head, session); + + spin_unlock_irq(&job_manager->lock); + /* UNLOCK */ + + /* delete all session_job */ + zynpu_session_delete_jobs(session); + +finish: + return ret; +} + +static int zynpu_invalidate_timeout_job_no_lock(struct zynpu_job_manager *job_manager, + struct zynpu_job *head, int job_id) +{ + int ret = -EINVAL; + struct zynpu_job *cursor = NULL; + struct zynpu_job *next = NULL; + + if ((!job_manager) || (!head)) + return -EINVAL; + + list_for_each_entry_safe(cursor, next, &head->node, node) { + if ((cursor->uthread_id == task_pid_nr(current)) && + (cursor->desc.job_id == job_id)) { + ret = zynpu_invalidate_job_no_lock(job_manager, cursor); + break; + } + } + + return ret; +} + +int zynpu_invalidate_timeout_job(struct zynpu_job_manager *job_manager, int job_id) +{ + int ret = 0; + + if (!job_manager) + return -EINVAL; + + /* LOCK */ + spin_lock_irq(&job_manager->lock); + ret = zynpu_invalidate_timeout_job_no_lock(job_manager, job_manager->pending_queue_head, job_id); + if (ret) { + ret = zynpu_invalidate_timeout_job_no_lock(job_manager, job_manager->scheduled_queue_head, job_id); + pr_debug("Timeout job invalidated from sched queue."); + } else { + pr_debug("Timeout job invalidated from pending queue."); + } + + spin_unlock_irq(&job_manager->lock); + /* UNLOCK */ + + return ret; +} + +void zynpu_job_manager_update_job_state_irq(void *zynpu_priv, int exception_flag) +{ + struct zynpu_job *curr = NULL; + struct zynpu_priv *zynpu = (struct zynpu_priv *)zynpu_priv; + struct zynpu_job_manager *job_manager = &zynpu->job_manager; + + /* LOCK */ + spin_lock(&job_manager->lock); + list_for_each_entry(curr, &job_manager->scheduled_queue_head->node, node) { + if (curr->state == ZYNPU_JOB_STATE_SCHED) { + curr->state = ZYNPU_JOB_STATE_END; + curr->exception_flag = exception_flag; + + if (curr->exception_flag) + pr_debug("[IRQ] job 0x%x of thread %u EXCEPTION", + curr->desc.job_id, curr->uthread_id); + else + pr_debug("[IRQ] job 0x%x of thread %u DONE", + curr->desc.job_id, curr->uthread_id); + + + if (is_session_job_prof_enabled(curr->session_job)) + session_job_mark_done(curr->session_job); + + if (job_manager->sched_num) + job_manager->sched_num--; + break; + } + } + + /* schedule a new pending job */ + zynpu_schedule_pending_job_no_lock(job_manager); + spin_unlock(&job_manager->lock); + /* UNLOCK */ +} + +void zynpu_job_manager_update_job_queue_done_irq(struct zynpu_job_manager *job_manager) +{ + struct zynpu_job *curr = NULL; + struct zynpu_job *next = NULL; + + /* LOCK */ + spin_lock(&job_manager->lock); + list_for_each_entry_safe(curr, next, &job_manager->scheduled_queue_head->node, node) { + if (ZYNPU_JOB_STATE_END != curr->state) + continue; + + /* + DO NOT call session API for invalid job because + session struct probably not exist on this occasion + */ + if (ZYNPU_JOB_FLAG_VALID == curr->valid_flag) { + pr_debug("[BH] handling job 0x%x of thread %u...", + curr->desc.job_id, curr->uthread_id); + zynpu_session_job_done(curr->session, curr->session_job, + curr->exception_flag); + } else { + pr_debug("[BH] this done job has been cancelled by user."); + } + + list_del(&curr->node); + destroy_zynpu_job(curr); + curr = NULL; + /* DO NOT minus sched_num here because upper half has done that */ + } + spin_unlock(&job_manager->lock); + /* UNLOCK */ +} + +static int print_job_info(char *buf, int buf_size, struct zynpu_job *job) +{ + int ret = 0; + char state_str[20]; + char excep_str[10]; + + if ((!buf) || (!job)) + return ret; + + if (job->state == ZYNPU_JOB_STATE_PENDING) + snprintf(state_str, 20, "Pending"); + else if (job->state == ZYNPU_JOB_STATE_SCHED) + snprintf(state_str, 20, "Executing"); + else if (job->state == ZYNPU_JOB_STATE_END) + snprintf(state_str, 20, "Done"); + + if (job->exception_flag) + snprintf(excep_str, 10, "Y"); + else + snprintf(excep_str, 10, "N"); + + return snprintf(buf, buf_size, "%-*d0x%-*x%-*s%-*s\n", 12, job->uthread_id, 10, + job->desc.job_id, 10, state_str, 5, excep_str); +} + +int zynpu_job_manager_sysfs_job_show(struct zynpu_job_manager *job_manager, char *buf) +{ + int ret = 0; + int tmp_size = 1024; + char tmp[1024]; + struct zynpu_job *curr = NULL; + int number = 0; + + if (!buf) + return ret; + + ret += snprintf(tmp, 1024, "-------------------------------------------\n"); + strcat(buf, tmp); + ret += snprintf(tmp, 1024, "%-*s%-*s%-*s%-*s\n", 12, "Thread ID", 12, "Job ID", + 10, "State", 5, "Exception"); + strcat(buf, tmp); + ret += snprintf(tmp, 1024, "-------------------------------------------\n"); + strcat(buf, tmp); + + /* LOCK */ + spin_lock_irq(&job_manager->lock); + list_for_each_entry(curr, &job_manager->pending_queue_head->node, node) { + ret += print_job_info(tmp, tmp_size, curr); + strcat(buf, tmp); + number++; + } + curr = NULL; + list_for_each_entry(curr, &job_manager->scheduled_queue_head->node, node) { + ret += print_job_info(tmp, tmp_size, curr); + strcat(buf, tmp); + number++; + } + spin_unlock_irq(&job_manager->lock); + /* UNLOCK */ + + if (!number) { + ret += snprintf(tmp, tmp_size, "No job.\n"); + strcat(buf, tmp); + } + + ret += snprintf(tmp, 1024, "-------------------------------------------\n"); + strcat(buf, tmp); + + return ret; +} \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_job_manager.h b/drivers/staging/zynpu/zynpu_job_manager.h new file mode 100644 index 000000000000..e71f29126c68 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_job_manager.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_job_manager.h + * Job manager module header file + */ + +#ifndef _ZYNPU_JOB_MANAGER_H_ +#define _ZYNPU_JOB_MANAGER_H_ + +#include <linux/list.h> +#include <linux/spinlock.h> + +#define ZYNPU_EXCEP_NO_EXCEPTION 0 +#ifdef __KERNEL__ + +#define ZYNPU_JOB_STATE_IDLE 0 +#define ZYNPU_JOB_STATE_PENDING 1 +#define ZYNPU_JOB_STATE_SCHED 2 +#define ZYNPU_JOB_STATE_END 3 + +#define ZYNPU_JOB_FLAG_INVALID 0 +#define ZYNPU_JOB_FLAG_VALID 1 +#endif + +#define ZYNPU_JOB_STATE_DONE 0x1 +#define ZYNPU_JOB_STATE_EXCEPTION 0x2 + +struct profiling_data { + ktime_t sched_kt; + ktime_t done_kt; +}; + +struct job_status_desc { + __u32 job_id; + __u32 thread_id; + __u32 state; + struct profiling_data pdata; +}; + +struct job_status_query { + __u32 max_cnt; + __u32 get_single_job; + __u32 job_id; + struct job_status_desc *status; + __u32 poll_cnt; + __u32 errcode; +}; + +struct user_job_desc { + __u64 start_pc_addr; + __u64 intr_handler_addr; + __u64 data_0_addr; + __u64 data_1_addr; + __u64 static_addr; + __u64 reuse_addr; + __u32 job_id; + __u32 code_size; + __u32 rodata_size; + __u32 stack_size; + __u32 static_size; + __u32 reuse_size; + __u32 enable_prof; + __u32 enable_asid; +}; + +struct user_job { + struct user_job_desc desc; + __u32 errcode; +}; + +/** + * struct zynpu_job - job element struct describing a job under scheduling in job manager + * Job status will be tracked as soon as interrupt or user evenets come in. + * + * @uthread_id: ID of user thread scheduled this job + * @desc: job desctiptor from userland + * @session: session pointer refernece of this job + * @session_job: corresponding job object in session + * @state: job state + * @exception_flag: exception flag + * @valid_flag: valid flag, indicating this job canceled by user or not + * @node: list head struct + */ + struct zynpu_job { + int uthread_id; + struct user_job_desc desc; + struct zynpu_session *session; + struct session_job *session_job; + int state; + int exception_flag; + int valid_flag; + struct list_head node; +}; + +/** + * struct zynpu_job_manager - job manager + * Maintain all jobs and update their status + * + * @scheduled_queue_head: scheduled job queue head + * @pending_queue_head: pending job queue head + * @sched_num: number of jobs have been scheduled + * @max_sched_num: maximum allowed scheduled job number + * @lock: spinlock + * @dev: device struct pointer + */ +struct zynpu_job_manager { + struct zynpu_job *scheduled_queue_head; + struct zynpu_job *pending_queue_head; + int sched_num; + int max_sched_num; + int init_done; + spinlock_t lock; + struct device *dev; +}; + +/** + * @brief initialize an existing job manager struct during driver probe phase + * + * @param job_manager: job_manager struct pointer allocated from user; + * @param p_dev: zynpu device struct pointer + * @param max_sched_num: maximum allowed scheduled job number; + * + * @return 0 if successful; others if failed; + */ +int zynpu_init_job_manager(struct zynpu_job_manager *job_manager, struct device *p_dev, int max_sched_num); +/** + * @brief de-init job manager + * + * @param job_manager: job_manager struct pointer allocated from user; + * + * @return void + */ +void zynpu_deinit_job_manager(struct zynpu_job_manager *job_manager); +/** + * @brief schedule new job flushed from userland + * + * @param job_manager: job_manager struct pointer; + * @param user_job: user_job struct; + * @param kern_job: session job; + * @param session: session pointer refernece of this job; + * + * @return 0 if successful; others if failed; + */ +int zynpu_job_manager_schedule_new_job(struct zynpu_job_manager *job_manager, struct user_job *user_job, + struct session_job *kern_job, struct zynpu_session *session); +/** + * @brief update job state and indicating if exception happens + * + * @param zynpu_priv: zynpu private struct + * @param exception_flag: exception flag + * + * @return void + */ +void zynpu_job_manager_update_job_state_irq(void *zynpu_priv, int exception_flag); +/** + * @brief done interrupt handler for job manager + * + * @param job_manager: job_manager struct pointer; + * + * @return void + */ +void zynpu_job_manager_update_job_queue_done_irq(struct zynpu_job_manager *job_manager); +/** + * @brief cancel all jobs flushed by a user thread + * + * @param job_manager: job_manager struct pointer allocated from user; + * @param session: session serviced for that user thread + * + * @return 0 if successful; others if failed; + */ +int zynpu_job_manager_cancel_session_jobs(struct zynpu_job_manager *job_manager, + struct zynpu_session *session); +/** + * @brief invalidate/kill a timeout job + * + * @param job_manager: job_manager struct pointer allocated from user; + * @param job_id: job ID + * + * @return 0 if successful; others if failed; + */ +int zynpu_invalidate_timeout_job(struct zynpu_job_manager *job_manager, int job_id); +/** + * @brief show KMD job info via sysfs + * + * @param job_manager: job_manager struct pointer allocated from user; + * @param buf: userspace buffer for KMD to fill the job info + * + * @return buf written bytes number; + */ +int zynpu_job_manager_sysfs_job_show(struct zynpu_job_manager *job_manager, char *buf); + +#endif /* _ZYNPU_JOB_MANAGER_H_ */ \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_mm.c b/drivers/staging/zynpu/zynpu_mm.c new file mode 100644 index 000000000000..a1d71cdedb4c --- /dev/null +++ b/drivers/staging/zynpu/zynpu_mm.c @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_mm.c + * Implementations of the ZYNPU memory management supports Address Space Extension (ASE) + */ + +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <asm/div64.h> +#include "zynpu.h" +#include "zynpu_mm.h" + +#define ZYNPU_CONFIG_SRAM_DATA_ASID ZYNPU_MM_DATA_TYPE_REUSE + +static inline int get_asid(struct zynpu_memory_manager *mm, enum zynpu_mm_data_type type) +{ + int asid = ZYNPU_ASE_ID_NONE; + + if (!mm) + return asid; + + switch (type) { + case ZYNPU_MM_DATA_TYPE_TEXT: + case ZYNPU_MM_DATA_TYPE_RO_STACK: + return ZYNPU_ASE_ID_0; + case ZYNPU_MM_DATA_TYPE_STATIC: + return ZYNPU_ASE_ID_1; + case ZYNPU_MM_DATA_TYPE_REUSE: + return ZYNPU_ASE_ID_2; + case ZYNPU_MM_DATA_TYPE_NONE: + return ZYNPU_ASE_ID_ALL; + default: + return asid; + } +} + +static inline void *zynpu_remap_region_nocache(struct zynpu_memory_manager *mm, u64 base, u64 bytes) +{ + if ((!mm) || (!bytes)) + return NULL; + + return memremap(base, bytes, MEMREMAP_WT); +} + +static inline void zynpu_unmap_region_nocache(void *va) +{ + if (va) + memunmap(va); +} + +static void *zynpu_alloc_cma_region_nocache(struct zynpu_memory_manager *mm, u64 *base, u64 bytes) +{ + int ret = 0; + void *va = NULL; + + if ((!mm) || (!bytes)) + return va; + + ret = dma_set_mask(mm->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(mm->dev, "DMA set mask failed!\n"); + goto finish; + } + + ret = dma_set_coherent_mask(mm->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(mm->dev, "DMA set coherent mask failed!\n"); + goto finish; + } + + va = dma_alloc_coherent(mm->dev, bytes, (dma_addr_t *)base, GFP_KERNEL); + if (!va) { + dev_err(mm->dev, "DMA alloc coherent failed: pa 0x%llx, bytes = 0x%llx\n", + *base, bytes); + } else { + dev_info(mm->dev, "DMA alloc coherent failed: pa 0x%llx, bytes = 0x%llx\n", + *base, bytes); + } + +finish: + return va; +} + +static void zynpu_free_cma_region_nocache(struct zynpu_memory_manager *mm, struct zynpu_mem_region *region) +{ + if ((!mm) || (!region)) + return; + + dma_free_coherent(mm->dev, region->tot_bytes, region->va, region->pa); +} + +static struct zynpu_block *create_block(u64 base, u64 bytes, int tid, enum zynpu_mm_data_type type, + enum zynpu_blk_state state) +{ + struct zynpu_block *blk = NULL; + + blk = kzalloc(sizeof(struct zynpu_block), GFP_KERNEL); + if (!blk) + return blk; + + blk->pa = base; + blk->bytes = bytes; + blk->tid = tid; + blk->type = type; + blk->state = state; + INIT_LIST_HEAD(&blk->list); + + return blk; +} + +static inline struct zynpu_block *create_block_list_head(u64 base, u64 bytes) +{ + return create_block(base, bytes, 0, ZYNPU_MM_DATA_TYPE_NONE, ZYNPU_BLOCK_STATE_FREE); +} + +static int zynpu_mm_find_block_candidate_no_lock(struct zynpu_block *head, u64 bytes, u64 alignment, + int data_type, struct zynpu_block **found, u64 *pa) +{ + struct zynpu_block *blk_cand = NULL; + u64 start = 0; + u64 end = 0; + int ret = -ENOMEM; + u64 result = 0; + + if (!found) + return -EINVAL; + + if ((!head) || (!bytes) || (!alignment) || (alignment % PAGE_SIZE)) { + ret = -EINVAL; + goto failed; + } + + /** + * allocate for text/ro/stack togetherly in non-reverse direction because + * for the same job, they must be allocated in the same ASE0 region and + * we wish to make them as closer as possible to make RW access control + * in ASE. + */ + if ((data_type == ZYNPU_MM_DATA_TYPE_TEXT) || + (data_type == ZYNPU_MM_DATA_TYPE_RO_STACK)) { + list_for_each_entry(blk_cand, &head->list, list) { + if (blk_cand->state != ZYNPU_BLOCK_STATE_ALLOCATED) { + start = ALIGN(blk_cand->pa, alignment); + end = start + bytes; + if (end <= (blk_cand->pa + blk_cand->bytes)) + goto success; + } + } + } else { + list_for_each_entry_reverse(blk_cand, &head->list, list) { + if (blk_cand->state != ZYNPU_BLOCK_STATE_ALLOCATED) { + result = blk_cand->pa + blk_cand->bytes - bytes; + do_div(result, alignment); + start = result * alignment; + end = start + bytes; + if ((start >= blk_cand->pa) && + (end <= (blk_cand->pa + blk_cand->bytes))) + goto success; + } + } + } + +failed: + *found = NULL; + *pa = 0; + return ret; + +success: + *found = blk_cand; + *pa = start; + return 0; +} + +static int zynpu_mm_split_block_no_lock(struct zynpu_block *target, u64 alloc_base, u64 alloc_bytes, + enum zynpu_mm_data_type type) +{ + u64 alloc_start = alloc_base; + u64 alloc_end = alloc_start + alloc_bytes - 1; + u64 target_start = target->pa; + u64 target_end = target->pa + target->bytes - 1; + struct zynpu_block *alloc_blk = NULL; + struct zynpu_block *remaining_blk = target; + + if ((!target) || (!alloc_bytes) || (alloc_end < target_start) || + (alloc_end > target_end)) + return -EINVAL; + + if ((alloc_start == target_start) && (alloc_end == target_end)) { + /* + alloc block: |<-----------alloc------------>| + equals to + target block to be split: |<----------target------------>| + */ + alloc_blk = target; + alloc_blk->tid = task_pid_nr(current); + alloc_blk->type = type; + alloc_blk->state = ZYNPU_BLOCK_STATE_ALLOCATED; + } else { + alloc_blk = create_block(alloc_start, alloc_bytes, task_pid_nr(current), + type, ZYNPU_BLOCK_STATE_ALLOCATED); + if (!alloc_blk) + return -ENOMEM; + if ((alloc_start == target_start) && (alloc_end < target_end)) { + /* + alloc block: |<---alloc--->|<--remaining-->| + smaller than and start from base of + target block to be split: |<----------target----------->| + */ + remaining_blk->pa += alloc_blk->bytes; + remaining_blk->bytes -= alloc_blk->bytes; + list_add_tail(&alloc_blk->list, &remaining_blk->list); + } else if ((alloc_start > target_start) && (alloc_end == target_end)) { + /* + alloc block: |<--remaining-->|<---alloc--->| + smaller than and end at end of + target block to be split: |<----------target----------->| + */ + remaining_blk->bytes -= alloc_blk->bytes; + list_add(&alloc_blk->list, &remaining_blk->list); + } else { + /* + alloc block: |<-fr_remaining->|<--alloc-->|<-bk_remaining->| + insides of + target block to be split: |<-------------------target------------------>| + */ + /* front remaining */ + remaining_blk->bytes = alloc_start - remaining_blk->pa; + list_add(&alloc_blk->list, &remaining_blk->list); + /* back remaining */ + remaining_blk = create_block(alloc_end + 1, target_end - alloc_end, + task_pid_nr(current), type, ZYNPU_BLOCK_STATE_FREE); + list_add(&remaining_blk->list, &alloc_blk->list); + } + } + + return 0; +} + +static int zynpu_mm_alloc_in_region_compact_no_lock(struct zynpu_memory_manager *mm, + struct zynpu_mem_region *region, struct buf_request *buf_req, struct zynpu_buffer *buf) +{ + int ret = 0; + u64 compact_bytes = 0; + u64 alignment = 0; + u64 alloc_pa = 0; + struct zynpu_block *blk_cand = NULL; + + if ((!region) || (!buf_req) || (!buf)) + return -EINVAL; + + /** + * Compact allocation means that the buffer size is round up as page size + * therefore the buffer size allocated is less than the region specified + * in ASE therefore memory is saved compared with strict allocation but + * the RW control is less strict + */ + + /** + * Compact alloc: + * roundup block size = roundup_pow_of_two(requested size) + * alloc block size = roundup_page_size(requested size) + * For example: + * |<-------------requested (9KB)----------->| + * |<--------------------------roundup (16KB)--------------------------->| + * |<----------------alloc (12KB)----------------->|<--remaining (4KB)-->| + * 0x10_0000_4000 0x10_0000_8000 + * + * Buffer returned to UMD: alloc block + * PA: 0x10_0000_4000 + * Size: 0x3000 + * + * ASE Registers: + * ASE_X_Control[7:0]: 3 (16KB) + * ASE_X_High_Base[31:0]: 0x10 + * ASE_X_Low_Base[31:0]: 0x4000 + * + * Base address used to calculate absolute address of buffer(s) inside alloc block: 0x0 + * + * The 4KB remaining block is free to be allocated later. + */ + compact_bytes = ALIGN(buf_req->bytes, PAGE_SIZE); + alignment = buf_req->align_in_page * 4 * 1024; + ret = zynpu_mm_find_block_candidate_no_lock(region->blk_head, + compact_bytes, alignment, buf_req->data_type, &blk_cand, &alloc_pa); + if (ret) + goto finish; + + /* found matching block candidate: update block list */ + if (zynpu_mm_split_block_no_lock(blk_cand, alloc_pa, compact_bytes, + (enum zynpu_mm_data_type)buf_req->data_type)) { + ret = -ENOMEM; + goto finish; + } + + /* success */ + buf->pa = alloc_pa; + buf->va = (void *)((unsigned long)region->va + alloc_pa - region->pa); + buf->bytes = compact_bytes; + buf_req->errcode = 0; + +finish: + return ret; +} + +static int zynpu_init_region(int id, struct zynpu_memory_manager *mm, u64 base, u64 bytes, + enum zynpu_mem_type type, struct zynpu_mem_region *region) +{ + struct zynpu_block *new_blk = NULL; + + if ((!mm) || (!bytes) || (!region)) + return -EINVAL; + + region->id = id; + + region->blk_head = create_block_list_head(0, 0); + new_blk = create_block_list_head(base, bytes); + list_add(&new_blk->list, ®ion->blk_head->list); + + mutex_init(®ion->lock); + region->pa = base; + region->tot_bytes = bytes; + region->tot_free_bytes = bytes; + region->type = type; + + region->alloc_in_region = zynpu_mm_alloc_in_region_compact_no_lock; + + INIT_LIST_HEAD(®ion->list); + + return 0; +} + +static int zynpu_update_mm_regions(struct zynpu_memory_manager *mm, struct zynpu_mem_region *head, + int *region_cnt, struct zynpu_mem_region *new_region) +{ + if ((!head) || (!region_cnt) || (!new_region)) + return -EINVAL; + + list_add(&new_region->list, &head->list); + (*region_cnt)++; + + return 0; +} + +static struct zynpu_mem_region *create_region_list_head(void) +{ + struct zynpu_mem_region *region = NULL; + + region = kzalloc(sizeof(struct zynpu_mem_region), GFP_KERNEL); + if (!region) + return region; + + mutex_init(®ion->lock); + INIT_LIST_HEAD(®ion->list); + return region; +} + +static int zynpu_mm_try_alloc_in_region(struct zynpu_memory_manager *mm, struct zynpu_mem_region *region, + struct buf_request *buf_req, struct zynpu_buffer *buf) +{ + int ret = 0; + + if ((!region) || (!buf_req) || (!buf)) + return -EINVAL; + + mutex_lock(®ion->lock); + ret = region->alloc_in_region(mm, region, buf_req, buf); + if (!ret) { + region->tot_free_bytes -= buf->bytes; + buf->region_id = region->id; + buf->type = region->type; + pr_debug("[zynpu_mm_try_alloc_in_region] alloc done: PA 0x%llx, size 0x%llx", + buf->pa, buf->bytes); + } + mutex_unlock(®ion->lock); + + return ret; +} + +static int zynpu_mm_free_in_region(struct zynpu_mem_region *region, struct buf_desc *buf) +{ + int ret = 0; + int found = 0; + struct zynpu_block *target = NULL; + struct zynpu_block *prev = NULL; + struct zynpu_block *next = NULL; + + if ((!region) || (!buf)) + return -EINVAL; + + mutex_lock(®ion->lock); + + list_for_each_entry(target, ®ion->blk_head->list, list) { + if ((target->pa == buf->pa) && (target->bytes == buf->bytes)) { + found = 1; + break; + } + } + + if (!found) { + ret = -EINVAL; + goto unlock; + } + + /* update target block to be free state */ + target->tid = 0; + target->type = ZYNPU_MM_DATA_TYPE_NONE; + target->state = ZYNPU_BLOCK_STATE_FREE; + + /* + merge prev block and next block if they are free/aligned + + block list: ... <=> |<--prev-->| <=> |<--target-->| <=> |<--next-->| <=> ... + free free free/aligned + + block list: ... <=> |<------------merged new block--------------->| <=> ... + free + */ + prev = list_prev_entry(target, list); + next = list_next_entry(target, list); + + if ((prev->bytes != 0) && (prev->state == ZYNPU_BLOCK_STATE_FREE)) { + prev->bytes += target->bytes; + list_del(&target->list); + kfree(target); + target = prev; + } + + if ((next->bytes != 0) && (next->state != ZYNPU_BLOCK_STATE_ALLOCATED)) { + target->bytes += next->bytes; + list_del(&next->list); + kfree(next); + next = NULL; + } + + region->tot_free_bytes += buf->bytes; + +unlock: + mutex_unlock(®ion->lock); + return ret; +} + +static int zynpu_mm_scan_regions_alloc(struct zynpu_memory_manager *mm, struct zynpu_mem_region *head, + struct buf_request *buf_req, struct zynpu_buffer *buf) +{ + int ret = -ENOMEM; + struct zynpu_mem_region *region = NULL; + + if ((!mm) || (!head) || (!buf_req) || (!buf)) + return -EINVAL; + + /** + * Z2: + * Find target region for ro/stack directly by matching region ID because + * they must be allocated in the same region as text region allocated before. + * Note: text should be requested to allocate first! + */ + if ((mm->version == ZYNPU_VERSION_ZHOUYI_V2) && + (buf_req->data_type == ZYNPU_MM_DATA_TYPE_RO_STACK)) { + list_for_each_entry(region, &head->list, list) { + if (buf_req->region_id == region->id) { + ret = zynpu_mm_try_alloc_in_region(mm, region, buf_req, buf); + if (!ret) + break; + } + } + } else { + list_for_each_entry(region, &head->list, list) { + if (region->tot_free_bytes >= ALIGN(buf_req->bytes, PAGE_SIZE)) { + ret = zynpu_mm_try_alloc_in_region(mm, region, buf_req, buf); + if (!ret) + break; + } + } + } + + return ret; +} + +static struct zynpu_mem_region *zynpu_mm_find_region(struct zynpu_mem_region *head, u64 pa, u64 bytes) +{ + struct zynpu_mem_region *region = NULL; + + if ((!head) || (!bytes)) + return region; + + list_for_each_entry(region, &head->list, list) { + if ((pa >= region->pa) && + ((pa + bytes) <= (region->pa + region->tot_bytes))) + return region; + } + + return NULL; +} + +static int zynpu_mm_deinit_region(struct zynpu_memory_manager *mm, struct zynpu_mem_region *region) +{ + struct zynpu_block *prev = NULL; + struct zynpu_block *next = NULL; + + if (!region) + return -EINVAL; + + mutex_lock(®ion->lock); + + list_for_each_entry_safe(prev, next, ®ion->blk_head->list, list) { + kfree(prev); + prev = NULL; + } + kfree(region->blk_head); + region->blk_head = NULL; + + if (region->type == ZYNPU_MEM_TYPE_SRAM) + zynpu_unmap_region_nocache(region->va); + else if (region->type == ZYNPU_MEM_TYPE_CMA) + zynpu_free_cma_region_nocache(mm, region); + else if (region->type == ZYNPU_MEM_TYPE_RESERVED) + zynpu_unmap_region_nocache(region->va); + + region->pa = 0; + region->va = NULL; + region->tot_bytes = 0; + region->tot_free_bytes = 0; + + mutex_unlock(®ion->lock); + //mutex_destroy(®ion->lock); + + return 0; +} + +int zynpu_init_mm(struct zynpu_memory_manager *mm, struct device *dev, int version) +{ + if ((!mm) || (!dev)) + return -EINVAL; + + mm->sram_head = create_region_list_head(); + mm->sram_cnt = 0; + mm->ddr_head = create_region_list_head(); + mm->ddr_cnt = 0; + mm->sram_global = get_asid(mm, ZYNPU_CONFIG_SRAM_DATA_ASID); + mm->dev = dev; + mm->version = version; + + /* success */ + return 0; +} + +void zynpu_deinit_mm(struct zynpu_memory_manager *mm) +{ + struct zynpu_mem_region *region = NULL; + + if (!mm) + return; + + if (mm->sram_head) { + list_for_each_entry(region, &mm->sram_head->list, list) { + zynpu_mm_deinit_region(mm, region); + } + } + + if (mm->ddr_head) { + list_for_each_entry(region, &mm->ddr_head->list, list) { + zynpu_mm_deinit_region(mm, region); + } + } + memset(mm, 0, sizeof(struct zynpu_memory_manager)); +} + +int zynpu_mm_add_region(struct zynpu_memory_manager *mm, u64 base, u64 bytes, + enum zynpu_mem_type type) +{ + int ret = 0; + int region_id = 0; + struct zynpu_mem_region *region = NULL; + + if ((!mm) || (!bytes)) + return -EINVAL; + + region = devm_kzalloc(mm->dev, sizeof(struct zynpu_mem_region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + if (type == ZYNPU_MEM_TYPE_SRAM) + region->va = zynpu_remap_region_nocache(mm, base, bytes); + else if (type == ZYNPU_MEM_TYPE_CMA) + region->va = zynpu_alloc_cma_region_nocache(mm, &base, bytes); + else if (type == ZYNPU_MEM_TYPE_RESERVED) + region->va = zynpu_remap_region_nocache(mm, base, bytes); + + if (!region->va) + return -ENOMEM; + + region_id = mm->sram_cnt + mm->ddr_cnt; + ret = zynpu_init_region(region_id, mm, base, bytes, type, region); + if (ret) + return ret; + + if (type == ZYNPU_MEM_TYPE_SRAM) + ret = zynpu_update_mm_regions(mm, mm->sram_head, &mm->sram_cnt, region); + else + ret = zynpu_update_mm_regions(mm, mm->ddr_head, &mm->ddr_cnt, region); + if (ret) + return ret; + + return 0; +} + +int zynpu_mm_alloc(struct zynpu_memory_manager *mm, struct buf_request *buf_req, + struct zynpu_buffer *buf) +{ + int ret = 0; + int asid = ZYNPU_ASE_ID_NONE; + + if ((!mm) || (!buf_req) || (!buf)) + return -EINVAL; + + if ((!buf_req->bytes) || (!buf_req->align_in_page)) + return -EINVAL; + + memset(buf, 0, sizeof(struct zynpu_buffer)); + asid = get_asid(mm, buf_req->data_type); + + if ((!mm->sram_cnt) && (!mm->ddr_cnt)) + return -ENOMEM; + + /** + * Try to alloc from SRAM first if the ASID is compatible; if failed then + * fall back to alloc from DDR. + */ + if (mm->sram_global & asid) { + ret = zynpu_mm_scan_regions_alloc(mm, mm->sram_head, buf_req, buf); + if (!ret) + goto finish; + } + + ret = zynpu_mm_scan_regions_alloc(mm, mm->ddr_head, buf_req, buf); + if (ret) { + buf_req->errcode = ZYNPU_ERRCODE_NO_MEMORY; + dev_err(mm->dev, "[MM] buffer allocation failed for: bytes 0x%llx, page align %d\n", + buf_req->bytes, buf_req->align_in_page); + } + +finish: + return ret; +} + +int zynpu_mm_free(struct zynpu_memory_manager *mm, struct buf_desc *buf) +{ + int ret = 0; + struct zynpu_mem_region *region = NULL; + + if ((!mm) || (!buf)) + return -EINVAL; + + region = zynpu_mm_find_region(mm->sram_head, buf->pa, buf->bytes); + if (!region) { + region = zynpu_mm_find_region(mm->ddr_head, buf->pa, buf->bytes); + if (!region) { + dev_err(mm->dev, "[MM] buffer to free not exists in any region: \ + pa 0x%llx, bytes 0x%llx\n", + buf->pa, buf->bytes); + return -EINVAL; + } + } + + ret = zynpu_mm_free_in_region(region, buf); + if (ret) { + dev_err(mm->dev, "[MM] buffer to free not exists in target region: \ + pa 0x%llx, bytes 0x%llx\n", + buf->pa, buf->bytes); + } + + return ret; +} + +int zynpu_mm_free_session_buffers(struct zynpu_memory_manager *mm, + struct zynpu_session *session) +{ + int ret = 0; + struct zynpu_buffer *buf = NULL; + struct buf_desc desc; + + while (NULL != (buf = zynpu_get_session_sbuf_head(session))) { + desc.pa = buf->pa; + desc.bytes = buf->bytes; + ret = zynpu_mm_free(mm, &desc); + if (ret) + goto finish; + ret = zynpu_session_detach_buf(session, &desc); + if (ret) + goto finish; + } + +finish: + return ret; +} \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_mm.h b/drivers/staging/zynpu/zynpu_mm.h new file mode 100644 index 000000000000..d165f1434e02 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_mm.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_mm.h + * Header of the ZYNPU memory management supports Address Space Extension (ASE) + */ + +#ifndef _ZYNPU_MM_H_ +#define _ZYNPU_MM_H_ + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include "zynpu_session.h" + +struct zynpu_mem_region; +struct zynpu_memory_manager; +typedef int (*alloc_in_region_t)(struct zynpu_memory_manager *mm, struct zynpu_mem_region *region, + struct buf_request *buf_req, struct zynpu_buffer *buf); + +enum zynpu_blk_state { + ZYNPU_BLOCK_STATE_FREE, + ZYNPU_BLOCK_STATE_ALLOCATED, +}; + +enum zynpu_asid { + ZYNPU_ASE_ID_NONE = 0x0, + ZYNPU_ASE_ID_0 = 0x1, + ZYNPU_ASE_ID_1 = 0x2, + ZYNPU_ASE_ID_2 = 0x4, + ZYNPU_ASE_ID_3 = 0x8, + ZYNPU_ASE_ID_ALL = 0xF, +}; + +enum zynpu_mem_type { + ZYNPU_MEM_TYPE_SRAM, + ZYNPU_MEM_TYPE_CMA, + ZYNPU_MEM_TYPE_RESERVED, +}; + +struct zynpu_block { + u64 pa; + u64 bytes; + int tid; + enum zynpu_mm_data_type type; + enum zynpu_blk_state state; + struct list_head list; +}; + +struct zynpu_mem_region { + int id; + struct zynpu_block *blk_head; + struct mutex lock; + u64 pa; + void *va; + u64 tot_bytes; + u64 tot_free_bytes; + //int flag; + enum zynpu_mem_type type; + alloc_in_region_t alloc_in_region; + struct list_head list; +}; + +struct zynpu_memory_manager { + struct zynpu_mem_region *sram_head; + int sram_cnt; + struct zynpu_mem_region *ddr_head; + int ddr_cnt; + enum zynpu_asid sram_global; + struct device *dev; + int version; +}; + +/* + * @brief initialize mm module during driver probe phase + * + * @param mm: memory manager struct allocated by user + * @param dev: device struct pointer + * @param zynpu_version: ZYNPU version + * + * @return 0 if successful; others if failed. + */ +int zynpu_init_mm(struct zynpu_memory_manager *mm, struct device *dev, int version); +/* + * @brief initialize mm module during driver probe phase + * + * @param mm: memory manager struct allocated by user + * @param base: base physical address of this region + * @param bytes: size of this region (in bytes) + * @param type: ZYNPU memory type + * + * @return 0 if successful; others if failed. + */ +int zynpu_mm_add_region(struct zynpu_memory_manager *mm, u64 base, u64 bytes, + enum zynpu_mem_type type); +/* + * @brief alloc memory buffer for user request + * + * @param mm: memory manager struct allocated by user + * @param buf_req: buffer request struct from userland + * @param buf: successfully allocated buffer descriptor + * + * @return 0 if successful; others if failed. + */ +int zynpu_mm_alloc(struct zynpu_memory_manager *mm, struct buf_request *buf_req, + struct zynpu_buffer *buf); +/* + * @brief free buffer allocated by zynpu_mm_alloc + * + * @param mm: memory manager struct allocated by user + * @param buf: buffer descriptor to be released + * + * @return 0 if successful; others if failed. + */ +int zynpu_mm_free(struct zynpu_memory_manager *mm, struct buf_desc *buf); +/* + * @brief free all the allocated buffers of a session + * + * @param mm: mm struct pointer + * @param session: session struct pointer + * + * @return 0 if successful; others if failed. + */ +int zynpu_mm_free_session_buffers(struct zynpu_memory_manager *mm, + struct zynpu_session *session); +/* + * @brief de-initialize mm module while kernel module unloading + * + * @param mm: memory manager struct allocated by user + * + * @return void + */ +void zynpu_deinit_mm(struct zynpu_memory_manager *mm); + +#endif /* _ZYNPU_MM_H_ */ \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_session.c b/drivers/staging/zynpu/zynpu_session.c new file mode 100644 index 000000000000..c6707c6aa5ef --- /dev/null +++ b/drivers/staging/zynpu/zynpu_session.c @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_session.c + * Implementation of session module + */ + +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/string.h> +#include "zynpu_session.h" +#include "zynpu_mm.h" +#include "zynpu.h" + +static void init_session_buf(struct session_buf *buf, + struct zynpu_buffer *desc, u64 dev_offset) +{ + if (buf) { + if (!desc) + memset(&buf->desc, 0, sizeof(struct zynpu_buffer)); + else { + buf->desc = *desc; + buf->type = desc->type; + } + buf->dev_offset = dev_offset; + buf->map_num = 0; + INIT_LIST_HEAD(&buf->head); + } +} + +static struct session_buf *create_session_buf(struct zynpu_buffer *desc, + u64 dev_offset) +{ + struct session_buf *sbuf = NULL; + + if (!desc) { + pr_err("descriptor is needed while creating new session buf!"); + goto finish; + } + + sbuf = kzalloc(sizeof(struct session_buf), GFP_KERNEL); + init_session_buf(sbuf, desc, dev_offset); + +finish: + return sbuf; +} + +static int destroy_session_buffer(struct session_buf *buf) +{ + int ret = 0; + + if (buf) + kfree(buf); + else { + pr_err("invalid null buf args or list not empty!"); + ret = -EINVAL; + } + + return ret; +} + +static void init_session_job(struct session_job *job, struct user_job_desc *desc) +{ + if (job) { + /* init job->desc to be all 0 if desc == NULL */ + job->uthread_id = task_pid_nr(current); + if (!desc) + memset(&job->desc, 0, sizeof(struct user_job_desc)); + else + job->desc = *desc; + job->state = 0; + job->exception_type = ZYNPU_EXCEP_NO_EXCEPTION; + INIT_LIST_HEAD(&job->head); + } +} + +static struct session_job *create_session_job(struct user_job_desc *desc) +{ + struct session_job *new_job = NULL; + + if (!desc) { + pr_err("descriptor is needed while creating new session job!"); + goto finish; + } + + new_job = kzalloc(sizeof(struct session_job), GFP_KERNEL); + init_session_job(new_job, desc); + +finish: + return new_job; +} + +static int destroy_session_job(struct session_job *job) +{ + int ret = 0; + + if (job) + kfree(job); + else { + pr_err("invalid null job args or list not empty!"); + ret = -EINVAL; + } + + return ret; +} + +static int is_session_all_jobs_end(struct zynpu_session *session) +{ + return (!session) ? 1 : list_empty(&session->job_list.head); +} + +static int is_session_all_buffers_freed(struct zynpu_session *session) +{ + return (!session) ? 1 : list_empty(&session->sbuf_list.head); +} + +static struct session_buf *find_buffer_bydesc_no_lock(struct zynpu_session *session, + struct buf_desc *buf_desc) +{ + struct session_buf *target_buf = NULL; + struct session_buf *session_buf = NULL; + struct list_head *node = NULL; + + if ((!session) || (!buf_desc)) { + pr_err("invalid input session or buf_desc args to be null!"); + goto finish; + } + + list_for_each(node, &session->sbuf_list.head) { + session_buf = list_entry(node, struct session_buf, head); + + if (session_buf && + (session_buf->desc.pa == buf_desc->pa) && + (session_buf->desc.bytes == buf_desc->bytes)) { + target_buf = session_buf; + pr_info("found matching buffer to be deleted."); + goto finish; + } + } + +finish: + return target_buf; +} + +static struct session_buf *find_buffer_byoffset_no_lock(struct zynpu_session *session, + u64 offset, int len) +{ + struct session_buf *target_buf = NULL; + struct session_buf *session_buf = NULL; + struct list_head *node = NULL; + + if (!session) { + pr_err("invalid input session args to be null!"); + goto finish; + } + + list_for_each(node, &session->sbuf_list.head) { + session_buf = list_entry(node, struct session_buf, head); + if (session_buf && + (session_buf->dev_offset == offset) && + (len <= session_buf->desc.bytes)) { + target_buf = session_buf; + goto finish; + } + } + +finish: + return target_buf; +} + +/* + * @brief get requested waitqueue for a user thread + * + * @param head: wait queue head + * @uthread_id: user thread ID + * + * @return waitqueue pointer; NULL if not found; + */ +static struct zynpu_thread_wait_queue* +get_thread_wait_queue_no_lock(struct zynpu_thread_wait_queue *head, int uthread_id) +{ + struct zynpu_thread_wait_queue *curr = NULL; + + if (!head) + return NULL; + + list_for_each_entry(curr, &head->node, node) { + if (curr->uthread_id == uthread_id) + return curr; + } + return NULL; +} + +/* + * @brief create a new waitqueue for a user thread if there is no existing one + * + * @param head: wait queue head + * @uthread_id: user thread ID + * + * @return waitqueue pointer if not existing one; NULL if there has been an existing one; + */ +static struct zynpu_thread_wait_queue * +create_thread_wait_queue_no_lock(struct zynpu_thread_wait_queue *head, int uthread_id) +{ + struct zynpu_thread_wait_queue *ret = NULL; + struct zynpu_thread_wait_queue *queue = get_thread_wait_queue_no_lock(head, uthread_id); + + /* new thread wait queue */ + if (!queue) { + queue = kzalloc(sizeof(struct zynpu_thread_wait_queue), GFP_KERNEL); + queue->uthread_id = uthread_id; + init_waitqueue_head(&queue->p_wait); + INIT_LIST_HEAD(&queue->node); + + if (queue && head) { + list_add_tail(&queue->node, &head->node); + } + ret = queue; + } + + queue->ref_cnt++; + return ret; +} + +/******************************************************************************** + * The following APIs are called in thread context for session obj management * + * and member query service * + * -- zynpu_create_session * + * -- zynpu_destroy_session * + * -- zynpu_get_session_pid * + ********************************************************************************/ +int zynpu_create_session(int pid, void *zynpu_priv, + struct zynpu_session **p_session) +{ + int ret = 0; + struct zynpu_session *session = NULL; + struct device *dev = NULL; + + if ((!zynpu_priv) || (!p_session)) { + pr_err("invalid input session or common args to be null!"); + goto finish; + } + + dev = ((struct zynpu_priv *)zynpu_priv)->dev; + + session = kzalloc(sizeof(struct zynpu_session), GFP_KERNEL); + if (!session) + return -ENOMEM; + + session->user_pid = pid; + init_session_buf(&session->sbuf_list, NULL, 0); + mutex_init(&session->sbuf_lock); + init_session_job(&session->job_list, NULL); + spin_lock_init(&session->job_lock); + session->zynpu_priv = zynpu_priv; + session->wait_queue_head = create_thread_wait_queue_no_lock(NULL, 0); + init_waitqueue_head(&session->com_wait); + session->single_thread_poll = 0; + + *p_session = session; + dev_dbg(dev, "[%d] new session created\n", pid); + +finish: + return ret; +} + +/* + * @brief delete a waitqueue list + * + * @param head: wait queue head + * + */ +static void delete_wait_queue(struct zynpu_thread_wait_queue *wait_queue_head) +{ + struct zynpu_thread_wait_queue *cursor = NULL; + struct zynpu_thread_wait_queue *next = NULL; + + if (wait_queue_head) { + list_for_each_entry_safe(cursor, next, &wait_queue_head->node, node) { + list_del(&cursor->node); + kfree(cursor); + } + } +} + +int zynpu_destroy_session(struct zynpu_session *session) +{ + int ret = 0; + struct device *dev = NULL; + int pid = 0; + + if (session && + is_session_all_jobs_end(session) && + is_session_all_buffers_freed(session)) { + dev = ((struct zynpu_priv *)session->zynpu_priv)->dev; + pid = session->user_pid; + delete_wait_queue(session->wait_queue_head); + kfree(session->wait_queue_head); + kfree(session); + dev_dbg(dev, "[%d] session destroyed\n", pid); + } else { + pr_warn("invalid input session args to be null or invalid operation!"); + ret = -EINVAL; + } + + return ret; +} + +int zynpu_get_session_pid(struct zynpu_session *session) +{ + if (session) + return session->user_pid; + else { + pr_warn("invalid input session args to be null!"); + return -EINVAL; + } +} + +/******************************************************************************** + * The following APIs are called in thread context for servicing user space * + * request in resource allocation/free and job scheduling via fops * + * -- zynpu_session_add_buf * + * -- zynpu_session_detach_buf * + * -- zynpu_get_session_sbuf_head * + * -- zynpu_session_mmap_buf * + * -- zynpu_session_add_job * + * -- zynpu_session_delete_jobs * + ********************************************************************************/ +int zynpu_session_add_buf(struct zynpu_session *session, + struct buf_request *buf_req, struct zynpu_buffer *buf) +{ + int ret = 0; + struct session_buf *new_sbuf = NULL; + + if ((!session) || (!buf_req) || (!buf)) { + pr_err("invalid input session or buf_req or buf args to be null!"); + if (buf_req) + buf_req->errcode = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + ret = -EINVAL; + goto finish; + } + + new_sbuf = create_session_buf(buf, buf->pa); + if (!new_sbuf) { + pr_err("create session buf failed!"); + buf_req->errcode = ZYNPU_ERRCODE_CREATE_KOBJ_ERR; + ret = -EFAULT; + } else { + mutex_lock(&session->sbuf_lock); + list_add(&new_sbuf->head, &session->sbuf_list.head); + + /* success */ + /* copy buffer descriptor to userland */ + buf_req->desc.pa = buf->pa; + buf_req->desc.dev_offset = buf->pa; + buf_req->desc.bytes = buf->bytes; + buf_req->desc.region_id = buf->region_id; + buf_req->errcode = 0; + mutex_unlock(&session->sbuf_lock); + } + +finish: + return ret; +} + +int zynpu_session_detach_buf(struct zynpu_session *session, struct buf_desc *buf_desc) +{ + int ret = 0; + struct session_buf *target_buf = NULL; + + if ((!session) || (!buf_desc)) { + pr_err("invalid input session or buf args to be null!"); + ret = -EINVAL; + goto finish; + } + + /* LOCK */ + mutex_lock(&session->sbuf_lock); + target_buf = find_buffer_bydesc_no_lock(session, buf_desc); + if (!target_buf) { + pr_err("no corresponding buffer found in this session!"); + ret = -ENOENT; + } else { + list_del(&target_buf->head); + ret = destroy_session_buffer(target_buf); + if (ret) + pr_err("destroy session failed!"); + else + target_buf = NULL; + } + mutex_unlock(&session->sbuf_lock); + /* UNLOCK */ + +finish: + return ret; +} + +int zynpu_session_mmap_buf(struct zynpu_session *session, struct vm_area_struct *vma, struct device *dev) +{ + int ret = 0; + u64 offset = 0; + int len = 0; + unsigned long vm_pgoff = 0; + struct session_buf *buf = NULL; + + if ((!session) || (!vma)) { + pr_err("invalid input session or vma args to be null!"); + ret = -EINVAL; + goto finish; + } + + offset = vma->vm_pgoff * PAGE_SIZE; + len = vma->vm_end - vma->vm_start; + + /* LOCK */ + mutex_lock(&session->sbuf_lock); + /* to find an allocated buffer with matching dev offset and length */ + buf = find_buffer_byoffset_no_lock(session, offset, len); + if (!buf) { + pr_err("invalid operation or args: no corresponding buffer found in this session!"); + ret = -ENOENT; + } else { + if (buf->map_num) { + pr_err("duplicated mmap operations on identical buffer!"); + ret = -ENOTTY; + } else { + vm_pgoff = vma->vm_pgoff; + vma->vm_pgoff = 0; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (buf->type == ZYNPU_MEM_TYPE_CMA) { + ret = dma_mmap_coherent(dev, vma, buf->desc.va, + (dma_addr_t)buf->desc.pa, buf->desc.bytes); + if (ret) + pr_err("CMA mmap to userspace failed!"); + } else if ((buf->type == ZYNPU_MEM_TYPE_SRAM) || + (buf->type == ZYNPU_MEM_TYPE_RESERVED)) { + ret = remap_pfn_range(vma, vma->vm_start, buf->desc.pa >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); + if (ret) + pr_err("SRAM mmap to userspace failed!"); + } + + vma->vm_pgoff = vm_pgoff; + if (!ret) + buf->map_num++; + } + } + mutex_unlock(&session->sbuf_lock); + /* UNLOCK */ + +finish: + return ret; +} + +struct zynpu_buffer *zynpu_get_session_sbuf_head(struct zynpu_session *session) +{ + struct session_buf *session_buf = NULL; + struct zynpu_buffer *buf_desc = NULL; + struct list_head *node = NULL; + + if (!session) { + pr_err("invalid input session or buf_req or buf args to be null!"); + goto finish; + } + + list_for_each(node, &session->sbuf_list.head) { + if (!list_empty(node)) { + session_buf = list_entry(node, struct session_buf, head); + buf_desc = &session_buf->desc; + goto finish; + } + } + +finish: + return buf_desc; +} + +struct session_job *zynpu_session_add_job(struct zynpu_session *session, struct user_job *user_job) +{ + struct session_job *kern_job = NULL; + + if ((!session) || (!user_job)) { + pr_err("invalid input session or user_job args to be null!"); + if (NULL != user_job) + user_job->errcode = ZYNPU_ERRCODE_INTERNAL_NULLPTR; + goto finish; + } + + kern_job = create_session_job(&user_job->desc); + if (!kern_job) { + pr_err("invalid input session or job args to be null!"); + user_job->errcode = ZYNPU_ERRCODE_CREATE_KOBJ_ERR; + } else { + /* THREAD LOCK */ + spin_lock_bh(&session->job_lock); + list_add(&kern_job->head, &session->job_list.head); + create_thread_wait_queue_no_lock(session->wait_queue_head, kern_job->uthread_id); + spin_unlock_bh(&session->job_lock); + /* THREAD UNLOCK */ + + /* success */ + user_job->errcode = 0; + } + +finish: + return kern_job; +} + +int zynpu_session_delete_jobs(struct zynpu_session *session) +{ + int ret = 0; + struct session_job *cursor = NULL; + struct session_job *next = NULL; + + if (!session) { + pr_err("invalid input session to be null!"); + ret = -EINVAL; + goto finish; + } + + /* THREAD LOCK */ + spin_lock_bh(&session->job_lock); + list_for_each_entry_safe(cursor, next, &session->job_list.head, head) { + list_del(&cursor->head); + destroy_session_job(cursor); + } + spin_unlock_bh(&session->job_lock); + /* THREAD UNLOCK */ + +finish: + return ret; +} + +/******************************************************************************** + * The following APIs are called in interrupt context to update end job status * + * They will be called by IRQ handlers in job manager module * + * Note that param session and session_job passed by job manager is assumed * + * to be valid and active (not cancelled by userland) * + * -- zynpu_session_job_done * + * -- zynpu_session_job_excep * + * -- zynpu_session_job_update_pdata * + ********************************************************************************/ +void zynpu_session_job_done(struct zynpu_session *session, struct session_job *job, + int excep_flag) +{ + struct zynpu_thread_wait_queue *queue = NULL; + wait_queue_head_t *thread_queue = NULL; + + if ((!session) || (!job)) { + pr_err("invalid input session or job args to be null!"); + return; + } + + if (ZYNPU_EXCEP_NO_EXCEPTION == excep_flag) + pr_debug("Done interrupt received..."); + else + pr_debug("Exception interrupt received..."); + + /* IRQ LOCK */ + spin_lock(&session->job_lock); + job->state = ZYNPU_JOB_STATE_END; + job->exception_type = ZYNPU_EXCEP_NO_EXCEPTION; + + if (session->single_thread_poll) { + queue = get_thread_wait_queue_no_lock(session->wait_queue_head, + job->uthread_id); + if (queue) + thread_queue = &queue->p_wait; + else { + pr_err("job waitqueue not found!"); + spin_unlock(&session->job_lock); + return; + } + } else { + thread_queue = &session->com_wait; + } + + if (thread_queue) + wake_up_interruptible(thread_queue); + else + pr_err("[%d] thread wait queue not found!", job->uthread_id); + + spin_unlock(&session->job_lock); + /* IRQ UNLOCK */ +} +/* +void zynpu_session_job_update_pdata(struct zynpu_session *session, struct session_job *job) +{ + struct zynpu_priv *zynpu = NULL; + if ((!session) || (!job)) { + pr_err("invalid input session or desc or scc args to be null!"); + return; + } + + zynpu = (struct zynpu_priv *)session->zynpu_priv; + + if (zynpu && job->desc.enable_prof) + zynpu_priv_read_profiling_reg(zynpu, &job->pdata); + + pr_info("TOT WDATA LSB: 0x%x\n", job->pdata.wdata_tot_lsb); + pr_info("TOT WDATA MSB: 0x%x\n", job->pdata.wdata_tot_msb); + pr_info("TOT RDATA LSB: 0x%x\n", job->pdata.rdata_tot_lsb); + pr_info("TOT RDATA MSB: 0x%x\n", job->pdata.rdata_tot_msb); + pr_info("TOT CYCLE LSB: 0x%x\n", job->pdata.tot_cycle_lsb); + pr_info("TOT CYCLE MSB: 0x%x\n", job->pdata.tot_cycle_msb); +} +*/ + +/******************************************************************************** + * The following APIs are called in thread context for user query service * + * after job end * + * -- zynpu_session_query_pdata * + * -- zynpu_session_thread_has_end_job * + * -- zynpu_session_get_job_status * + ********************************************************************************/ +int zynpu_session_thread_has_end_job(struct zynpu_session *session, int uthread_id) +{ + int ret = 0; + struct session_job *session_job = NULL; + struct list_head *node = NULL; + int wake_up_single = 0; + + if (!session) { + pr_err("invalid input session or excep args to be null!"); + goto finish; + } + + /** + * If uthread_id found in job_list, then the condition returns is specific to + * the status of jobs of this thread (thread-specific); otherwise, the condition + * is specific to the status of jobs of this session (fd-specific). + */ + spin_lock(&session->job_lock); + list_for_each(node, &session->job_list.head) { + session_job = list_entry(node, struct session_job, head); + if (session_job && (session_job->uthread_id == uthread_id)) { + wake_up_single = 1; + break; + } + } + + list_for_each(node, &session->job_list.head) { + session_job = list_entry(node, struct session_job, head); + if (session_job && (session_job->state == ZYNPU_JOB_STATE_END)) { + if (wake_up_single) { + if (session_job->uthread_id == uthread_id) { + ret = 1; + break; + } + } else { + ret = 1; + break; + } + } + } + spin_unlock(&session->job_lock); + +finish: + return ret; +} + +int zynpu_session_get_job_status(struct zynpu_session *session, struct job_status_query *job_status) +{ + int ret = 0; + int query_cnt; + struct job_status_desc *status = NULL; + struct session_job *cursor = NULL; + struct session_job *next = NULL; + int poll_iter = 0; + + if ((!session) || (!job_status)) { + pr_err("invalid input session or excep args to be null!"); + goto finish; + } + + if (job_status->max_cnt < 1) { + job_status->errcode = ZYNPU_ERRCODE_INVALID_ARGS; + ret = -EINVAL; + goto finish; + } + + if (job_status->get_single_job) + query_cnt = 1; + else + query_cnt = job_status->max_cnt; + + status = kzalloc(query_cnt * sizeof(struct job_status_desc), GFP_KERNEL); + if (!status) { + job_status->errcode = ZYNPU_ERRCODE_NO_MEMORY; + ret = -ENOMEM; + goto finish; + } + + job_status->poll_cnt = 0; + spin_lock(&session->job_lock); + list_for_each_entry_safe(cursor, next, &session->job_list.head, head) { + if (job_status->poll_cnt == job_status->max_cnt) + break; + + if ((((cursor->desc.job_id == job_status->job_id) && (job_status->get_single_job)) || + (!job_status->get_single_job)) && + (cursor->state == ZYNPU_JOB_STATE_END)) { + status[poll_iter].job_id = cursor->desc.job_id; + status[poll_iter].thread_id = session->user_pid; + status[0].state = (cursor->exception_type == ZYNPU_EXCEP_NO_EXCEPTION) ? + ZYNPU_JOB_STATE_DONE : ZYNPU_JOB_STATE_EXCEPTION; + status[poll_iter].pdata = cursor->pdata; + job_status->poll_cnt++; + list_del(&cursor->head); + destroy_session_job(cursor); + cursor = NULL; + if (job_status->get_single_job) + break; + } + } + spin_unlock(&session->job_lock); + + if (!job_status->poll_cnt) { + job_status->errcode = ZYNPU_ERRCODE_ITEM_NOT_FOUND; + ret = -ENOENT; + goto clean; + } + + ret = copy_to_user((struct job_status_desc __user *)job_status->status, status, + job_status->poll_cnt * sizeof(struct job_status_desc)); + if (ZYNPU_ERRCODE_NO_ERROR == ret) + job_status->errcode = 0; + +clean: + kfree(status); + +finish: + return ret; +} + +wait_queue_head_t *zynpu_session_get_wait_queue(struct zynpu_session *session, int uthread_id) +{ + struct zynpu_thread_wait_queue *queue = NULL; + + if (!session) { + pr_err("invalid input session to be null!"); + return NULL; + } + + /* LOCK */ + spin_lock(&session->job_lock); + queue = get_thread_wait_queue_no_lock(session->wait_queue_head, uthread_id); + spin_unlock(&session->job_lock); + /* UNLOCK */ + + if (queue) + return &queue->p_wait; + + return NULL; +} + +void zynpu_session_add_poll_wait_queue(struct zynpu_session *session, + struct file *filp, struct poll_table_struct *wait, int uthread_id) +{ + struct zynpu_thread_wait_queue *wait_queue = NULL; + struct session_job *curr = NULL; + + if ((!session) || (!filp) || (!wait)) { + pr_err("invalid input session to be null!"); + return; + } + + spin_lock_bh(&session->job_lock); + list_for_each_entry(curr, &session->job_list.head, head) { + if (curr->uthread_id == uthread_id) { + wait_queue = get_thread_wait_queue_no_lock(session->wait_queue_head, + uthread_id); + if (wait_queue) { + poll_wait(filp, &wait_queue->p_wait, wait); + session->single_thread_poll = 1; + } else { + pr_err("thread wait_queue not found!"); + } + break; + } + } + + if (!session->single_thread_poll) + poll_wait(filp, &session->com_wait, wait); + spin_unlock_bh(&session->job_lock); +} + +void session_job_mark_sched(struct session_job *job) +{ + if (job) + job->pdata.sched_kt = ktime_get(); +} + +void session_job_mark_done(struct session_job *job) +{ + if (job) + job->pdata.done_kt = ktime_get(); +} + +int is_session_job_prof_enabled(struct session_job *job) +{ + int ret = 0; + if (job) + ret = job->desc.enable_prof; + return ret; +} diff --git a/drivers/staging/zynpu/zynpu_session.h b/drivers/staging/zynpu/zynpu_session.h new file mode 100644 index 000000000000..3ab2b98b9da4 --- /dev/null +++ b/drivers/staging/zynpu/zynpu_session.h @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_session.h + * session module header file + */ + +#ifndef _ZYNPU_SESSION_H_ +#define _ZYNPU_SESSION_H_ + +#include <linux/list.h> +#include <linux/mm_types.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/wait.h> +#include "zynpu_job_manager.h" + +enum zynpu_mm_data_type { + ZYNPU_MM_DATA_TYPE_NONE, + ZYNPU_MM_DATA_TYPE_TEXT, + ZYNPU_MM_DATA_TYPE_RO_STACK, + ZYNPU_MM_DATA_TYPE_STATIC, + ZYNPU_MM_DATA_TYPE_REUSE, +}; + +struct buf_desc { + __u64 pa; + __u64 dev_offset; /* user space access this area via mapping this offset from the dev file start */ + __u64 bytes; + __u32 region_id; +}; + +struct buf_request { + __u64 bytes; /* bytes requested to allocate */ + __u32 align_in_page; /* alignment requirements (in 4KB) */ + __u32 data_type; /* type of data in the buffer to allocate */ + __u32 region_id; /* region ID specified (if applicable) */ + __u32 alloc_flag; /* Allocation flag: default, strict or compact */ + struct buf_desc desc; /* info of buffer successfully allocated */ + __u32 errcode; +}; + +struct zynpu_buffer { + u64 pa; + void *va; + u64 bytes; + u32 region_id; + u32 type; +}; + +/** + * struct waitqueue: maintain the waitqueue for a user thread + * + * @uthread_id: user thread owns this waitqueue + * @ref_cnt: strucr reference count + * @p_wait: wait queue head for polling + * @node: list head struct + */ +struct zynpu_thread_wait_queue { + int uthread_id; + int ref_cnt; + wait_queue_head_t p_wait; + struct list_head node; +}; + +/** + * struct session_buf: session private buffer list + * @desc: buffer descriptor struct + * @dev_offset: offset of this buffer in device file + * @type: buffer type: CMA/SRAM/RESERVED + * @map_num: memory mmapped number + * @head: list head struct + */ +struct session_buf { + struct zynpu_buffer desc; + u64 dev_offset; + u32 type; + int map_num; + struct list_head head; +}; + +/** + * struct session_job: session private job list + * @uthread_id: ID of user thread scheduled this job + * @desc: job descriptor struct + * @state: job state + * @exception_type: type of exception if any + * @pdata: profiling data struct + * @head: list head struct + */ +struct session_job { + int uthread_id; + struct user_job_desc desc; + int state; + int exception_type; + struct profiling_data pdata; + struct list_head head; +}; + +/** + * struct zynpu_session: private data struct for every file open operation + * @user_pid: ID of the user thread doing the open operation + * @sbuf_list: successfully allocated shared buffer of this session + * @sbuf_lock: mutex lock for sbuf list + * @job_list: job list of this session + * @job_lock: spinlock for job list + * @zynpu_priv: zynpu_priv struct pointer + * @wait_queue_head: thread waitqueue list head of this session + * @com_wait: session common waitqueue head + * @single_thread_poll: flag to indicate the polling method, thread vs. fd + */ +struct zynpu_session { + int user_pid; + struct session_buf sbuf_list; + struct mutex sbuf_lock; + struct session_job job_list; + spinlock_t job_lock; + void *zynpu_priv; + struct zynpu_thread_wait_queue *wait_queue_head; + wait_queue_head_t com_wait; + int single_thread_poll; +}; + +/* + * @brief create unique session DS for an open request + * + * @param pid: user mode thread pid + * @param zynpu: zynpu_priv struct pointer + * @param p_session: session struct pointer + * + * @return 0 if successful; others if failed. + */ +int zynpu_create_session(int pid, void *zynpu_priv, + struct zynpu_session **p_session); +/* + * @brief destroy an existing session + * + * @param session: session pointer + * + * @return ZYNPU_KMD_ERR_OK if successful; others if failed. + */ +int zynpu_destroy_session(struct zynpu_session *session); +/* + * @brief get pid of this session + * + * @param session: session pointer + * + * @return id if successful; 0 if failed. + */ +int zynpu_get_session_pid(struct zynpu_session *session); +/* + * @brief add an allocated buffer of this session + * + * @param session: session pointer + * @param buf_req: request buffer struct pointer + * @param buf: buffer allocated + * + * @return ZYNPU_KMD_ERR_OK if successful; others if failed. + */ +int zynpu_session_add_buf(struct zynpu_session *session, struct buf_request *buf_req, + struct zynpu_buffer *buf); +/* + * @brief remove an allocated buffer of this session + * + * @param session: session pointer + * @param buf: buffer to be removed + * + * @return ZYNPU_KMD_ERR_OK if successful; others if failed. + */ +int zynpu_session_detach_buf(struct zynpu_session *session, struct buf_desc *buf); +/* + * @brief mmap an allocated buffer of this session + * + * @param session: session pointer + * @param vma: vm_area_struct + * @param dev: device struct + * + * @return ZYNPU_KMD_ERR_OK if successful; others if failed. + */ +int zynpu_session_mmap_buf(struct zynpu_session *session, struct vm_area_struct *vma, struct device *dev); +/* + * @brief get first valid buffer descriptor of this session + * + * @param session: session pointer + * + * @return buffer if successful; NULL if failed. + */ +struct zynpu_buffer * zynpu_get_session_sbuf_head(struct zynpu_session *session); +/* + * @brief add a job descriptor of this session + * + * @param session: session pointer + * @param user_job: userspace job descriptor pointer + * + * @return non-NULL kernel job ptr if successful; NULL if failed. + */ +struct session_job * zynpu_session_add_job(struct zynpu_session *session, struct user_job *user_job); +/* + * @brief delete all jobs of a session + * + * @param session: session pointer + * + * @return ZYNPU_KMD_ERR_OK if successful; others if failed. + */ +int zynpu_session_delete_jobs(struct zynpu_session *session); +/* + * @brief job done interrupt bottom half handler + * + * @param session: session pointer + * @param job: session job pointer + * @param excep_flag: exception flag + * + */ +void zynpu_session_job_done(struct zynpu_session *session, struct session_job *job, + int excep_flag); +/* + * @brief update bandwidth profiling data after job done + * + * @param session: session pointer + * @param job: session job pointer + * + */ +//void zynpu_session_job_update_pdata(struct zynpu_session *session, struct session_job *job); +/* + * @brief check if any scheduled job of the specified thread is done/exception + * + * @param session: session pointer + * @param uthread_id: user thread ID + * + * @return 1 if has don job(s); 0 if no. + */ +int zynpu_session_thread_has_end_job(struct zynpu_session *session, int uthread_id); +/* + * @brief get one or multiple end jobs' status + * + * @param session: session pointer + * @param job_status: job status query struct + * + * @return 1 if has don job(s); 0 if no. + */ +int zynpu_session_get_job_status(struct zynpu_session *session, struct job_status_query *job_status); +/* + * @brief add waitqueue into session thread waitqueue list + * + * @param session: session pointer + * @param filp: file struct from file operation API + * @param wait: wait struct from poll file operation API + * @param uthread_id: user thread ID + * + */ +void zynpu_session_add_poll_wait_queue(struct zynpu_session *session, + struct file *filp, struct poll_table_struct *wait, int uthread_id); +/* + * @brief mark the scheduled time of a job + * + * @param job: session job pointer + */ +void session_job_mark_sched(struct session_job *job); +/* + * @brief mark the done time of a job + * + * @param job: session job pointer + */ +void session_job_mark_done(struct session_job *job); +/* + * @brief check if a job is enabled to do profiling + * + * @param job: session job pointer + */ +int is_session_job_prof_enabled(struct session_job *job); + +#endif //_ZYNPU_SESSION_H_ \ No newline at end of file diff --git a/drivers/staging/zynpu/zynpu_sysfs.c b/drivers/staging/zynpu/zynpu_sysfs.c new file mode 100644 index 000000000000..faed1800d7ed --- /dev/null +++ b/drivers/staging/zynpu/zynpu_sysfs.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* +* +* Zhouyi AI Accelerator driver +* +* Copyright (C) 2020 Arm (China) Ltd. +* Copyright (C) 2021 Cai Huoqing +*/ + +/** + * @file zynpu_sysfs.h + * sysfs interface implementation file + */ + +#include <linux/device.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "zynpu.h" + +static struct zynpu_priv *zynpu = NULL; + +static int print_reg_info(char *buf, const char *name, int offset) +{ + struct zynpu_io_req io_req; + + if (!zynpu) + return 0; + + io_req.rw = ZYNPU_IO_READ; + io_req.offset = offset; + zynpu_priv_io_rw(zynpu, &io_req); + return snprintf(buf, 1024, "0x%-*x%-*s0x%08x\n", 6, offset, 22, name, io_req.value); +} + +static ssize_t sysfs_zynpu_ext_register_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int ret = 0; + char tmp[1024]; + + if (!zynpu) + return 0; + + ret += snprintf(tmp, 1024, " ZYNPU External Register Values\n"); + strcat(buf, tmp); + ret += snprintf(tmp, 1024, "----------------------------------------\n"); + strcat(buf, tmp); + ret += snprintf(tmp, 1024, "%-*s%-*s%-*s\n", 8, "Offset", 22, "Name", 10, "Value"); + strcat(buf, tmp); + ret += snprintf(tmp, 1024, "----------------------------------------\n"); + strcat(buf, tmp); + + ret += print_reg_info(tmp, "Ctrl Reg", 0x0); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Status Reg", 0x4); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Start PC Reg", 0x8); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Intr PC Reg", 0xC); + strcat(buf, tmp); + ret += print_reg_info(tmp, "IPI Ctrl Reg", 0x10); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Data Addr 0 Reg", 0x14); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Data Addr 1 Reg", 0x18); + strcat(buf, tmp); + if (zynpu_priv_get_version(zynpu) == ZYNPU_VERSION_ZHOUYI_V1) { + ret += print_reg_info(tmp, "Intr Cause Reg", 0x1C); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Intr Status Reg", 0x20); + strcat(buf, tmp); + } else if (zynpu_priv_get_version(zynpu) == ZYNPU_VERSION_ZHOUYI_V2) { + ret += print_reg_info(tmp, "Data Addr 2 Reg", 0x1C); + strcat(buf, tmp); + ret += print_reg_info(tmp, "Data Addr 3 Reg", 0x20); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE0 Ctrl Reg", 0xc0); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE0 High Base Reg", 0xc4); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE0 Low Base Reg", 0xc8); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE1 Ctrl Reg", 0xcc); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE1 High Base Reg", 0xd0); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE1 Low Base Reg", 0xd4); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE2 Ctrl Reg", 0xd8); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE2 High Base Reg", 0xdc); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE2 Low Base Reg", 0xe0); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE3 Ctrl Reg", 0xe4); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE3 High Base Reg", 0xe8); + strcat(buf, tmp); + ret += print_reg_info(tmp, "ASE3 Low Base Reg", 0xec); + strcat(buf, tmp); + } + ret += snprintf(tmp, 1024, "----------------------------------------\n"); + strcat(buf, tmp); + return ret; +} + +static ssize_t sysfs_zynpu_ext_register_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int i = 0; + int ret = 0; + char *token = NULL; + char *buf_dup = NULL; + int value[3] = { 0 }; + int max_offset = 0; + struct zynpu_io_req io_req; + zynpu_priv_io_rw(zynpu, &io_req); + + if (!zynpu) + return 0; + + if (zynpu->is_suspend) + return 0; + + buf_dup = (char *)kzalloc(1024, GFP_KERNEL); + snprintf(buf_dup, 1024, buf); + + dev_dbg(dev, "[SYSFS] user input str: %s", buf_dup); + + for (i = 0; i < 3; i++) { + token = strsep(&buf_dup, "-"); + if (token == NULL) { + dev_err(dev, "[SYSFS] please echo as this format: <reg_offset>-<write time>-<write value>"); + goto finish; + } + + dev_dbg(dev, "[SYSFS] to convert str: %s", token); + + ret = kstrtouint(token, 0, &value[i]); + if (ret) { + dev_err(dev, "[SYSFS] convert str to int failed (%d): %s", ret, token); + goto finish; + } + } + + if (zynpu_priv_get_version(zynpu) == ZYNPU_VERSION_ZHOUYI_V1) + max_offset = 0x20; + else if (zynpu_priv_get_version(zynpu) == ZYNPU_VERSION_ZHOUYI_V2) + max_offset = 0xec; + + if (value[0] > max_offset) { + dev_err(dev, "[SYSFS] register offset too large which cannot be write: 0x%x", value[0]); + goto finish; + } + + dev_info(dev, "[SYSFS] offset 0x%x, time 0x%x, value 0x%x", + value[0], value[1], value[2]); + + io_req.rw = ZYNPU_IO_WRITE; + io_req.offset = value[0]; + io_req.value = value[2]; + for (i = 0; i < value[1]; i++) { + dev_info(dev, "[SYSFS] writing register 0x%x with value 0x%x", value[0], value[2]); + zynpu_priv_io_rw(zynpu, &io_req); + } + +finish: + return count; +} + +static ssize_t sysfs_zynpu_job_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + if (!zynpu) + return 0; + + return zynpu_job_manager_sysfs_job_show(&zynpu->job_manager, buf); +} + +static DEVICE_ATTR(ext_register, 0644, sysfs_zynpu_ext_register_show, sysfs_zynpu_ext_register_store); +static DEVICE_ATTR(job, 0444, sysfs_zynpu_job_show, NULL); + +int zynpu_create_sysfs(void *zynpu_priv) +{ + int ret = 0; + + if (!zynpu_priv) + return -EINVAL; + zynpu = (struct zynpu_priv *)zynpu_priv; + + device_create_file(zynpu->dev, &dev_attr_ext_register); + device_create_file(zynpu->dev, &dev_attr_job); + + return ret; +} + +void zynpu_destroy_sysfs(void *zynpu_priv) +{ + if (!zynpu_priv) + return; + + device_remove_file(zynpu->dev, &dev_attr_ext_register); + device_remove_file(zynpu->dev, &dev_attr_job); + + zynpu = NULL; +} -- 2.25.1