diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/gpu/drm/lima | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/gpu/drm/lima')
34 files changed, 5210 insertions, 0 deletions
diff --git a/drivers/gpu/drm/lima/Kconfig b/drivers/gpu/drm/lima/Kconfig new file mode 100644 index 000000000..fa1d4f5df --- /dev/null +++ b/drivers/gpu/drm/lima/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 OR MIT +# Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> + +config DRM_LIMA + tristate "LIMA (DRM support for ARM Mali 400/450 GPU)" + depends on DRM + depends on ARM || ARM64 || COMPILE_TEST + depends on MMU + depends on COMMON_CLK + depends on OF + select DRM_SCHED + select DRM_GEM_SHMEM_HELPER + select PM_DEVFREQ + select DEVFREQ_GOV_SIMPLE_ONDEMAND + help + DRM driver for ARM Mali 400/450 GPUs. diff --git a/drivers/gpu/drm/lima/Makefile b/drivers/gpu/drm/lima/Makefile new file mode 100644 index 000000000..ca2097b8e --- /dev/null +++ b/drivers/gpu/drm/lima/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 OR MIT +# Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> + +lima-y := \ + lima_drv.o \ + lima_device.o \ + lima_pmu.o \ + lima_l2_cache.o \ + lima_mmu.o \ + lima_gp.o \ + lima_pp.o \ + lima_gem.o \ + lima_vm.o \ + lima_sched.o \ + lima_ctx.o \ + lima_dlbu.o \ + lima_bcast.o \ + lima_trace.o \ + lima_devfreq.o + +obj-$(CONFIG_DRM_LIMA) += lima.o diff --git a/drivers/gpu/drm/lima/lima_bcast.c b/drivers/gpu/drm/lima/lima_bcast.c new file mode 100644 index 000000000..6d000504e --- /dev/null +++ b/drivers/gpu/drm/lima/lima_bcast.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/io.h> +#include <linux/device.h> + +#include "lima_device.h" +#include "lima_bcast.h" +#include "lima_regs.h" + +#define bcast_write(reg, data) writel(data, ip->iomem + reg) +#define bcast_read(reg) readl(ip->iomem + reg) + +void lima_bcast_enable(struct lima_device *dev, int num_pp) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + struct lima_ip *ip = dev->ip + lima_ip_bcast; + int i, mask = bcast_read(LIMA_BCAST_BROADCAST_MASK) & 0xffff0000; + + for (i = 0; i < num_pp; i++) { + struct lima_ip *pp = pipe->processor[i]; + + mask |= 1 << (pp->id - lima_ip_pp0); + } + + bcast_write(LIMA_BCAST_BROADCAST_MASK, mask); +} + +static int lima_bcast_hw_init(struct lima_ip *ip) +{ + bcast_write(LIMA_BCAST_BROADCAST_MASK, ip->data.mask << 16); + bcast_write(LIMA_BCAST_INTERRUPT_MASK, ip->data.mask); + return 0; +} + +int lima_bcast_resume(struct lima_ip *ip) +{ + return lima_bcast_hw_init(ip); +} + +void lima_bcast_suspend(struct lima_ip *ip) +{ + +} + +int lima_bcast_mask_irq(struct lima_ip *ip) +{ + bcast_write(LIMA_BCAST_BROADCAST_MASK, 0); + bcast_write(LIMA_BCAST_INTERRUPT_MASK, 0); + return 0; +} + +int lima_bcast_reset(struct lima_ip *ip) +{ + return lima_bcast_hw_init(ip); +} + +int lima_bcast_init(struct lima_ip *ip) +{ + int i; + + for (i = lima_ip_pp0; i <= lima_ip_pp7; i++) { + if (ip->dev->ip[i].present) + ip->data.mask |= 1 << (i - lima_ip_pp0); + } + + return lima_bcast_hw_init(ip); +} + +void lima_bcast_fini(struct lima_ip *ip) +{ + +} + diff --git a/drivers/gpu/drm/lima/lima_bcast.h b/drivers/gpu/drm/lima/lima_bcast.h new file mode 100644 index 000000000..cd08841e4 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_bcast.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_BCAST_H__ +#define __LIMA_BCAST_H__ + +struct lima_ip; + +int lima_bcast_resume(struct lima_ip *ip); +void lima_bcast_suspend(struct lima_ip *ip); +int lima_bcast_init(struct lima_ip *ip); +void lima_bcast_fini(struct lima_ip *ip); + +void lima_bcast_enable(struct lima_device *dev, int num_pp); + +int lima_bcast_mask_irq(struct lima_ip *ip); +int lima_bcast_reset(struct lima_ip *ip); + +#endif diff --git a/drivers/gpu/drm/lima/lima_ctx.c b/drivers/gpu/drm/lima/lima_ctx.c new file mode 100644 index 000000000..68ede7a72 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_ctx.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/pid.h> +#include <linux/slab.h> + +#include "lima_device.h" +#include "lima_ctx.h" + +int lima_ctx_create(struct lima_device *dev, struct lima_ctx_mgr *mgr, u32 *id) +{ + struct lima_ctx *ctx; + int i, err; + + ctx = kzalloc_obj(*ctx); + if (!ctx) + return -ENOMEM; + ctx->dev = dev; + kref_init(&ctx->refcnt); + + for (i = 0; i < lima_pipe_num; i++) { + err = lima_sched_context_init(dev->pipe + i, ctx->context + i); + if (err) + goto err_out0; + } + + err = xa_alloc(&mgr->handles, id, ctx, xa_limit_32b, GFP_KERNEL); + if (err < 0) + goto err_out0; + + ctx->pid = task_pid_nr(current); + get_task_comm(ctx->pname, current); + + return 0; + +err_out0: + for (i--; i >= 0; i--) + lima_sched_context_fini(dev->pipe + i, ctx->context + i); + kfree(ctx); + return err; +} + +static void lima_ctx_do_release(struct kref *ref) +{ + struct lima_ctx *ctx = container_of(ref, struct lima_ctx, refcnt); + int i; + + for (i = 0; i < lima_pipe_num; i++) + lima_sched_context_fini(ctx->dev->pipe + i, ctx->context + i); + kfree(ctx); +} + +int lima_ctx_free(struct lima_ctx_mgr *mgr, u32 id) +{ + struct lima_ctx *ctx; + int ret = 0; + + mutex_lock(&mgr->lock); + ctx = xa_erase(&mgr->handles, id); + if (ctx) + kref_put(&ctx->refcnt, lima_ctx_do_release); + else + ret = -EINVAL; + mutex_unlock(&mgr->lock); + return ret; +} + +struct lima_ctx *lima_ctx_get(struct lima_ctx_mgr *mgr, u32 id) +{ + struct lima_ctx *ctx; + + mutex_lock(&mgr->lock); + ctx = xa_load(&mgr->handles, id); + if (ctx) + kref_get(&ctx->refcnt); + mutex_unlock(&mgr->lock); + return ctx; +} + +void lima_ctx_put(struct lima_ctx *ctx) +{ + kref_put(&ctx->refcnt, lima_ctx_do_release); +} + +void lima_ctx_mgr_init(struct lima_ctx_mgr *mgr) +{ + mutex_init(&mgr->lock); + xa_init_flags(&mgr->handles, XA_FLAGS_ALLOC); +} + +void lima_ctx_mgr_fini(struct lima_ctx_mgr *mgr) +{ + struct lima_ctx *ctx; + unsigned long id; + + xa_for_each(&mgr->handles, id, ctx) { + kref_put(&ctx->refcnt, lima_ctx_do_release); + } + + xa_destroy(&mgr->handles); + mutex_destroy(&mgr->lock); +} diff --git a/drivers/gpu/drm/lima/lima_ctx.h b/drivers/gpu/drm/lima/lima_ctx.h new file mode 100644 index 000000000..5b1063ce9 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_ctx.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_CTX_H__ +#define __LIMA_CTX_H__ + +#include <linux/xarray.h> +#include <linux/sched.h> + +#include "lima_device.h" + +struct lima_ctx { + struct kref refcnt; + struct lima_device *dev; + struct lima_sched_context context[lima_pipe_num]; + + /* debug info */ + char pname[TASK_COMM_LEN]; + pid_t pid; +}; + +struct lima_ctx_mgr { + struct mutex lock; + struct xarray handles; +}; + +int lima_ctx_create(struct lima_device *dev, struct lima_ctx_mgr *mgr, u32 *id); +int lima_ctx_free(struct lima_ctx_mgr *mgr, u32 id); +struct lima_ctx *lima_ctx_get(struct lima_ctx_mgr *mgr, u32 id); +void lima_ctx_put(struct lima_ctx *ctx); +void lima_ctx_mgr_init(struct lima_ctx_mgr *mgr); +void lima_ctx_mgr_fini(struct lima_ctx_mgr *mgr); + +#endif diff --git a/drivers/gpu/drm/lima/lima_devfreq.c b/drivers/gpu/drm/lima/lima_devfreq.c new file mode 100644 index 000000000..bc8fb4e38 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_devfreq.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * + * Based on panfrost_devfreq.c: + * Copyright 2019 Collabora ltd. + */ +#include <linux/clk.h> +#include <linux/devfreq.h> +#include <linux/devfreq_cooling.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/property.h> + +#include "lima_device.h" +#include "lima_devfreq.h" + +static void lima_devfreq_update_utilization(struct lima_devfreq *devfreq) +{ + ktime_t now, last; + + now = ktime_get(); + last = devfreq->time_last_update; + + if (devfreq->busy_count > 0) + devfreq->busy_time += ktime_sub(now, last); + else + devfreq->idle_time += ktime_sub(now, last); + + devfreq->time_last_update = now; +} + +static int lima_devfreq_target(struct device *dev, unsigned long *freq, + u32 flags) +{ + struct dev_pm_opp *opp; + + opp = devfreq_recommended_opp(dev, freq, flags); + if (IS_ERR(opp)) + return PTR_ERR(opp); + dev_pm_opp_put(opp); + + return dev_pm_opp_set_rate(dev, *freq); +} + +static void lima_devfreq_reset(struct lima_devfreq *devfreq) +{ + devfreq->busy_time = 0; + devfreq->idle_time = 0; + devfreq->time_last_update = ktime_get(); +} + +static int lima_devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *status) +{ + struct lima_device *ldev = dev_get_drvdata(dev); + struct lima_devfreq *devfreq = &ldev->devfreq; + unsigned long irqflags; + + status->current_frequency = clk_get_rate(ldev->clk_gpu); + + spin_lock_irqsave(&devfreq->lock, irqflags); + + lima_devfreq_update_utilization(devfreq); + + status->total_time = ktime_to_ns(ktime_add(devfreq->busy_time, + devfreq->idle_time)); + status->busy_time = ktime_to_ns(devfreq->busy_time); + + lima_devfreq_reset(devfreq); + + spin_unlock_irqrestore(&devfreq->lock, irqflags); + + dev_dbg(ldev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n", + status->busy_time, status->total_time, + status->busy_time / (status->total_time / 100), + status->current_frequency / 1000 / 1000); + + return 0; +} + +static struct devfreq_dev_profile lima_devfreq_profile = { + .timer = DEVFREQ_TIMER_DELAYED, + .polling_ms = 50, /* ~3 frames */ + .target = lima_devfreq_target, + .get_dev_status = lima_devfreq_get_dev_status, +}; + +void lima_devfreq_fini(struct lima_device *ldev) +{ + struct lima_devfreq *devfreq = &ldev->devfreq; + + if (devfreq->cooling) { + devfreq_cooling_unregister(devfreq->cooling); + devfreq->cooling = NULL; + } + + if (devfreq->devfreq) { + devm_devfreq_remove_device(ldev->dev, devfreq->devfreq); + devfreq->devfreq = NULL; + } +} + +int lima_devfreq_init(struct lima_device *ldev) +{ + struct thermal_cooling_device *cooling; + struct device *dev = ldev->dev; + struct devfreq *devfreq; + struct lima_devfreq *ldevfreq = &ldev->devfreq; + struct dev_pm_opp *opp; + unsigned long cur_freq; + int ret; + const char *regulator_names[] = { "mali", NULL }; + + if (!device_property_present(dev, "operating-points-v2")) + /* Optional, continue without devfreq */ + return 0; + + spin_lock_init(&ldevfreq->lock); + + /* + * clkname is set separately so it is not affected by the optional + * regulator setting which may return error. + */ + ret = devm_pm_opp_set_clkname(dev, "core"); + if (ret) + return ret; + + ret = devm_pm_opp_set_regulators(dev, regulator_names); + if (ret) { + /* Continue if the optional regulator is missing */ + if (ret != -ENODEV) + return ret; + } + + ret = devm_pm_opp_of_add_table(dev); + if (ret) + return ret; + + lima_devfreq_reset(ldevfreq); + + cur_freq = clk_get_rate(ldev->clk_gpu); + + opp = devfreq_recommended_opp(dev, &cur_freq, 0); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + lima_devfreq_profile.initial_freq = cur_freq; + dev_pm_opp_put(opp); + + /* + * Setup default thresholds for the simple_ondemand governor. + * The values are chosen based on experiments. + */ + ldevfreq->gov_data.upthreshold = 30; + ldevfreq->gov_data.downdifferential = 5; + + devfreq = devm_devfreq_add_device(dev, &lima_devfreq_profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + &ldevfreq->gov_data); + if (IS_ERR(devfreq)) { + dev_err(dev, "Couldn't initialize GPU devfreq\n"); + return PTR_ERR(devfreq); + } + + ldevfreq->devfreq = devfreq; + + cooling = of_devfreq_cooling_register(dev->of_node, devfreq); + if (IS_ERR(cooling)) + dev_info(dev, "Failed to register cooling device\n"); + else + ldevfreq->cooling = cooling; + + return 0; +} + +void lima_devfreq_record_busy(struct lima_devfreq *devfreq) +{ + unsigned long irqflags; + + if (!devfreq->devfreq) + return; + + spin_lock_irqsave(&devfreq->lock, irqflags); + + lima_devfreq_update_utilization(devfreq); + + devfreq->busy_count++; + + spin_unlock_irqrestore(&devfreq->lock, irqflags); +} + +void lima_devfreq_record_idle(struct lima_devfreq *devfreq) +{ + unsigned long irqflags; + + if (!devfreq->devfreq) + return; + + spin_lock_irqsave(&devfreq->lock, irqflags); + + lima_devfreq_update_utilization(devfreq); + + WARN_ON(--devfreq->busy_count < 0); + + spin_unlock_irqrestore(&devfreq->lock, irqflags); +} + +int lima_devfreq_resume(struct lima_devfreq *devfreq) +{ + unsigned long irqflags; + + if (!devfreq->devfreq) + return 0; + + spin_lock_irqsave(&devfreq->lock, irqflags); + + lima_devfreq_reset(devfreq); + + spin_unlock_irqrestore(&devfreq->lock, irqflags); + + return devfreq_resume_device(devfreq->devfreq); +} + +int lima_devfreq_suspend(struct lima_devfreq *devfreq) +{ + if (!devfreq->devfreq) + return 0; + + return devfreq_suspend_device(devfreq->devfreq); +} diff --git a/drivers/gpu/drm/lima/lima_devfreq.h b/drivers/gpu/drm/lima/lima_devfreq.h new file mode 100644 index 000000000..b8e50feae --- /dev/null +++ b/drivers/gpu/drm/lima/lima_devfreq.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> */ + +#ifndef __LIMA_DEVFREQ_H__ +#define __LIMA_DEVFREQ_H__ + +#include <linux/devfreq.h> +#include <linux/spinlock.h> +#include <linux/ktime.h> + +struct devfreq; +struct thermal_cooling_device; + +struct lima_device; + +struct lima_devfreq { + struct devfreq *devfreq; + struct thermal_cooling_device *cooling; + struct devfreq_simple_ondemand_data gov_data; + + ktime_t busy_time; + ktime_t idle_time; + ktime_t time_last_update; + int busy_count; + /* + * Protect busy_time, idle_time, time_last_update and busy_count + * because these can be updated concurrently, for example by the GP + * and PP interrupts. + */ + spinlock_t lock; +}; + +int lima_devfreq_init(struct lima_device *ldev); +void lima_devfreq_fini(struct lima_device *ldev); + +void lima_devfreq_record_busy(struct lima_devfreq *devfreq); +void lima_devfreq_record_idle(struct lima_devfreq *devfreq); + +int lima_devfreq_resume(struct lima_devfreq *devfreq); +int lima_devfreq_suspend(struct lima_devfreq *devfreq); + +#endif diff --git a/drivers/gpu/drm/lima/lima_device.c b/drivers/gpu/drm/lima/lima_device.c new file mode 100644 index 000000000..7c873e62c --- /dev/null +++ b/drivers/gpu/drm/lima/lima_device.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> + +#include "lima_device.h" +#include "lima_gp.h" +#include "lima_pp.h" +#include "lima_mmu.h" +#include "lima_pmu.h" +#include "lima_l2_cache.h" +#include "lima_dlbu.h" +#include "lima_bcast.h" +#include "lima_vm.h" + +struct lima_ip_desc { + char *name; + char *irq_name; + bool must_have[lima_gpu_num]; + int offset[lima_gpu_num]; + + int (*init)(struct lima_ip *ip); + void (*fini)(struct lima_ip *ip); + int (*resume)(struct lima_ip *ip); + void (*suspend)(struct lima_ip *ip); +}; + +#define LIMA_IP_DESC(ipname, mst0, mst1, off0, off1, func, irq) \ + [lima_ip_##ipname] = { \ + .name = #ipname, \ + .irq_name = irq, \ + .must_have = { \ + [lima_gpu_mali400] = mst0, \ + [lima_gpu_mali450] = mst1, \ + }, \ + .offset = { \ + [lima_gpu_mali400] = off0, \ + [lima_gpu_mali450] = off1, \ + }, \ + .init = lima_##func##_init, \ + .fini = lima_##func##_fini, \ + .resume = lima_##func##_resume, \ + .suspend = lima_##func##_suspend, \ + } + +static struct lima_ip_desc lima_ip_desc[lima_ip_num] = { + LIMA_IP_DESC(pmu, false, false, 0x02000, 0x02000, pmu, "pmu"), + LIMA_IP_DESC(l2_cache0, true, true, 0x01000, 0x10000, l2_cache, NULL), + LIMA_IP_DESC(l2_cache1, false, true, -1, 0x01000, l2_cache, NULL), + LIMA_IP_DESC(l2_cache2, false, false, -1, 0x11000, l2_cache, NULL), + LIMA_IP_DESC(gp, true, true, 0x00000, 0x00000, gp, "gp"), + LIMA_IP_DESC(pp0, true, true, 0x08000, 0x08000, pp, "pp0"), + LIMA_IP_DESC(pp1, false, false, 0x0A000, 0x0A000, pp, "pp1"), + LIMA_IP_DESC(pp2, false, false, 0x0C000, 0x0C000, pp, "pp2"), + LIMA_IP_DESC(pp3, false, false, 0x0E000, 0x0E000, pp, "pp3"), + LIMA_IP_DESC(pp4, false, false, -1, 0x28000, pp, "pp4"), + LIMA_IP_DESC(pp5, false, false, -1, 0x2A000, pp, "pp5"), + LIMA_IP_DESC(pp6, false, false, -1, 0x2C000, pp, "pp6"), + LIMA_IP_DESC(pp7, false, false, -1, 0x2E000, pp, "pp7"), + LIMA_IP_DESC(gpmmu, true, true, 0x03000, 0x03000, mmu, "gpmmu"), + LIMA_IP_DESC(ppmmu0, true, true, 0x04000, 0x04000, mmu, "ppmmu0"), + LIMA_IP_DESC(ppmmu1, false, false, 0x05000, 0x05000, mmu, "ppmmu1"), + LIMA_IP_DESC(ppmmu2, false, false, 0x06000, 0x06000, mmu, "ppmmu2"), + LIMA_IP_DESC(ppmmu3, false, false, 0x07000, 0x07000, mmu, "ppmmu3"), + LIMA_IP_DESC(ppmmu4, false, false, -1, 0x1C000, mmu, "ppmmu4"), + LIMA_IP_DESC(ppmmu5, false, false, -1, 0x1D000, mmu, "ppmmu5"), + LIMA_IP_DESC(ppmmu6, false, false, -1, 0x1E000, mmu, "ppmmu6"), + LIMA_IP_DESC(ppmmu7, false, false, -1, 0x1F000, mmu, "ppmmu7"), + LIMA_IP_DESC(dlbu, false, true, -1, 0x14000, dlbu, NULL), + LIMA_IP_DESC(bcast, false, true, -1, 0x13000, bcast, NULL), + LIMA_IP_DESC(pp_bcast, false, true, -1, 0x16000, pp_bcast, "pp"), + LIMA_IP_DESC(ppmmu_bcast, false, true, -1, 0x15000, mmu, NULL), +}; + +const char *lima_ip_name(struct lima_ip *ip) +{ + return lima_ip_desc[ip->id].name; +} + +static int lima_clk_enable(struct lima_device *dev) +{ + int err; + + err = clk_prepare_enable(dev->clk_bus); + if (err) + return err; + + err = clk_prepare_enable(dev->clk_gpu); + if (err) + goto error_out0; + + if (dev->reset) { + err = reset_control_deassert(dev->reset); + if (err) { + dev_err(dev->dev, + "reset controller deassert failed %d\n", err); + goto error_out1; + } + } + + return 0; + +error_out1: + clk_disable_unprepare(dev->clk_gpu); +error_out0: + clk_disable_unprepare(dev->clk_bus); + return err; +} + +static void lima_clk_disable(struct lima_device *dev) +{ + if (dev->reset) + reset_control_assert(dev->reset); + clk_disable_unprepare(dev->clk_gpu); + clk_disable_unprepare(dev->clk_bus); +} + +static int lima_clk_init(struct lima_device *dev) +{ + int err; + + dev->clk_bus = devm_clk_get(dev->dev, "bus"); + if (IS_ERR(dev->clk_bus)) { + err = PTR_ERR(dev->clk_bus); + if (err != -EPROBE_DEFER) + dev_err(dev->dev, "get bus clk failed %d\n", err); + dev->clk_bus = NULL; + return err; + } + + dev->clk_gpu = devm_clk_get(dev->dev, "core"); + if (IS_ERR(dev->clk_gpu)) { + err = PTR_ERR(dev->clk_gpu); + if (err != -EPROBE_DEFER) + dev_err(dev->dev, "get core clk failed %d\n", err); + dev->clk_gpu = NULL; + return err; + } + + dev->reset = devm_reset_control_array_get_optional_shared(dev->dev); + if (IS_ERR(dev->reset)) { + err = PTR_ERR(dev->reset); + if (err != -EPROBE_DEFER) + dev_err(dev->dev, "get reset controller failed %d\n", + err); + dev->reset = NULL; + return err; + } + + return lima_clk_enable(dev); +} + +static void lima_clk_fini(struct lima_device *dev) +{ + lima_clk_disable(dev); +} + +static int lima_regulator_enable(struct lima_device *dev) +{ + int ret; + + if (!dev->regulator) + return 0; + + ret = regulator_enable(dev->regulator); + if (ret < 0) { + dev_err(dev->dev, "failed to enable regulator: %d\n", ret); + return ret; + } + + return 0; +} + +static void lima_regulator_disable(struct lima_device *dev) +{ + if (dev->regulator) + regulator_disable(dev->regulator); +} + +static int lima_regulator_init(struct lima_device *dev) +{ + int ret; + + dev->regulator = devm_regulator_get_optional(dev->dev, "mali"); + if (IS_ERR(dev->regulator)) { + ret = PTR_ERR(dev->regulator); + dev->regulator = NULL; + if (ret == -ENODEV) + return 0; + if (ret != -EPROBE_DEFER) + dev_err(dev->dev, "failed to get regulator: %d\n", ret); + return ret; + } + + return lima_regulator_enable(dev); +} + +static void lima_regulator_fini(struct lima_device *dev) +{ + lima_regulator_disable(dev); +} + +static int lima_init_ip(struct lima_device *dev, int index) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + struct lima_ip_desc *desc = lima_ip_desc + index; + struct lima_ip *ip = dev->ip + index; + const char *irq_name = desc->irq_name; + int offset = desc->offset[dev->id]; + bool must = desc->must_have[dev->id]; + int err; + + if (offset < 0) + return 0; + + ip->dev = dev; + ip->id = index; + ip->iomem = dev->iomem + offset; + if (irq_name) { + err = must ? platform_get_irq_byname(pdev, irq_name) : + platform_get_irq_byname_optional(pdev, irq_name); + if (err < 0) + goto out; + ip->irq = err; + } + + err = desc->init(ip); + if (!err) { + ip->present = true; + return 0; + } + +out: + return must ? err : 0; +} + +static void lima_fini_ip(struct lima_device *ldev, int index) +{ + struct lima_ip_desc *desc = lima_ip_desc + index; + struct lima_ip *ip = ldev->ip + index; + + if (ip->present) + desc->fini(ip); +} + +static int lima_resume_ip(struct lima_device *ldev, int index) +{ + struct lima_ip_desc *desc = lima_ip_desc + index; + struct lima_ip *ip = ldev->ip + index; + int ret = 0; + + if (ip->present) + ret = desc->resume(ip); + + return ret; +} + +static void lima_suspend_ip(struct lima_device *ldev, int index) +{ + struct lima_ip_desc *desc = lima_ip_desc + index; + struct lima_ip *ip = ldev->ip + index; + + if (ip->present) + desc->suspend(ip); +} + +static int lima_init_gp_pipe(struct lima_device *dev) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_gp; + int err; + + pipe->ldev = dev; + + err = lima_sched_pipe_init(pipe, "gp"); + if (err) + return err; + + pipe->l2_cache[pipe->num_l2_cache++] = dev->ip + lima_ip_l2_cache0; + pipe->mmu[pipe->num_mmu++] = dev->ip + lima_ip_gpmmu; + pipe->processor[pipe->num_processor++] = dev->ip + lima_ip_gp; + + err = lima_gp_pipe_init(dev); + if (err) { + lima_sched_pipe_fini(pipe); + return err; + } + + return 0; +} + +static void lima_fini_gp_pipe(struct lima_device *dev) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_gp; + + lima_gp_pipe_fini(dev); + lima_sched_pipe_fini(pipe); +} + +static int lima_init_pp_pipe(struct lima_device *dev) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + int err, i; + + pipe->ldev = dev; + + err = lima_sched_pipe_init(pipe, "pp"); + if (err) + return err; + + for (i = 0; i < LIMA_SCHED_PIPE_MAX_PROCESSOR; i++) { + struct lima_ip *pp = dev->ip + lima_ip_pp0 + i; + struct lima_ip *ppmmu = dev->ip + lima_ip_ppmmu0 + i; + struct lima_ip *l2_cache; + + if (dev->id == lima_gpu_mali400) + l2_cache = dev->ip + lima_ip_l2_cache0; + else + l2_cache = dev->ip + lima_ip_l2_cache1 + (i >> 2); + + if (pp->present && ppmmu->present && l2_cache->present) { + pipe->mmu[pipe->num_mmu++] = ppmmu; + pipe->processor[pipe->num_processor++] = pp; + if (!pipe->l2_cache[i >> 2]) + pipe->l2_cache[pipe->num_l2_cache++] = l2_cache; + } + } + + if (dev->ip[lima_ip_bcast].present) { + pipe->bcast_processor = dev->ip + lima_ip_pp_bcast; + pipe->bcast_mmu = dev->ip + lima_ip_ppmmu_bcast; + } + + err = lima_pp_pipe_init(dev); + if (err) { + lima_sched_pipe_fini(pipe); + return err; + } + + return 0; +} + +static void lima_fini_pp_pipe(struct lima_device *dev) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + + lima_pp_pipe_fini(dev); + lima_sched_pipe_fini(pipe); +} + +int lima_device_init(struct lima_device *ldev) +{ + struct platform_device *pdev = to_platform_device(ldev->dev); + int err, i; + + dma_set_coherent_mask(ldev->dev, DMA_BIT_MASK(32)); + dma_set_max_seg_size(ldev->dev, UINT_MAX); + + err = lima_clk_init(ldev); + if (err) + return err; + + err = lima_regulator_init(ldev); + if (err) + goto err_out0; + + ldev->va_start = 0; + if (ldev->id == lima_gpu_mali450) { + ldev->va_end = LIMA_VA_RESERVE_START; + ldev->dlbu_cpu = dma_alloc_wc( + ldev->dev, LIMA_PAGE_SIZE, + &ldev->dlbu_dma, GFP_KERNEL | __GFP_NOWARN); + if (!ldev->dlbu_cpu) { + err = -ENOMEM; + goto err_out2; + } + } else + ldev->va_end = LIMA_VA_RESERVE_END; + + ldev->empty_vm = lima_vm_create(ldev); + if (!ldev->empty_vm) { + err = -ENOMEM; + goto err_out1; + } + + ldev->iomem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ldev->iomem)) { + dev_err(ldev->dev, "fail to ioremap iomem\n"); + err = PTR_ERR(ldev->iomem); + goto err_out3; + } + + for (i = 0; i < lima_ip_num; i++) { + err = lima_init_ip(ldev, i); + if (err) + goto err_out4; + } + + err = lima_init_gp_pipe(ldev); + if (err) + goto err_out4; + + err = lima_init_pp_pipe(ldev); + if (err) + goto err_out5; + + ldev->dump.magic = LIMA_DUMP_MAGIC; + ldev->dump.version_major = LIMA_DUMP_MAJOR; + ldev->dump.version_minor = LIMA_DUMP_MINOR; + INIT_LIST_HEAD(&ldev->error_task_list); + mutex_init(&ldev->error_task_list_lock); + + dev_info(ldev->dev, "bus rate = %lu\n", clk_get_rate(ldev->clk_bus)); + dev_info(ldev->dev, "mod rate = %lu", clk_get_rate(ldev->clk_gpu)); + + return 0; + +err_out5: + lima_fini_gp_pipe(ldev); +err_out4: + while (--i >= 0) + lima_fini_ip(ldev, i); +err_out3: + if (ldev->dlbu_cpu) + dma_free_wc(ldev->dev, LIMA_PAGE_SIZE, + ldev->dlbu_cpu, ldev->dlbu_dma); +err_out2: + lima_vm_put(ldev->empty_vm); +err_out1: + lima_regulator_fini(ldev); +err_out0: + lima_clk_fini(ldev); + return err; +} + +void lima_device_fini(struct lima_device *ldev) +{ + int i; + struct lima_sched_error_task *et, *tmp; + + list_for_each_entry_safe(et, tmp, &ldev->error_task_list, list) { + list_del(&et->list); + kvfree(et); + } + mutex_destroy(&ldev->error_task_list_lock); + + lima_fini_pp_pipe(ldev); + lima_fini_gp_pipe(ldev); + + for (i = lima_ip_num - 1; i >= 0; i--) + lima_fini_ip(ldev, i); + + if (ldev->dlbu_cpu) + dma_free_wc(ldev->dev, LIMA_PAGE_SIZE, + ldev->dlbu_cpu, ldev->dlbu_dma); + + lima_vm_put(ldev->empty_vm); + + lima_regulator_fini(ldev); + + lima_clk_fini(ldev); +} + +int lima_device_resume(struct device *dev) +{ + struct lima_device *ldev = dev_get_drvdata(dev); + int i, err; + + err = lima_clk_enable(ldev); + if (err) { + dev_err(dev, "resume clk fail %d\n", err); + return err; + } + + err = lima_regulator_enable(ldev); + if (err) { + dev_err(dev, "resume regulator fail %d\n", err); + goto err_out0; + } + + for (i = 0; i < lima_ip_num; i++) { + err = lima_resume_ip(ldev, i); + if (err) { + dev_err(dev, "resume ip %d fail\n", i); + goto err_out1; + } + } + + err = lima_devfreq_resume(&ldev->devfreq); + if (err) { + dev_err(dev, "devfreq resume fail\n"); + goto err_out1; + } + + return 0; + +err_out1: + while (--i >= 0) + lima_suspend_ip(ldev, i); + lima_regulator_disable(ldev); +err_out0: + lima_clk_disable(ldev); + return err; +} + +int lima_device_suspend(struct device *dev) +{ + struct lima_device *ldev = dev_get_drvdata(dev); + int i, err; + + /* check any task running */ + for (i = 0; i < lima_pipe_num; i++) { + if (atomic_read(&ldev->pipe[i].base.credit_count)) + return -EBUSY; + } + + err = lima_devfreq_suspend(&ldev->devfreq); + if (err) { + dev_err(dev, "devfreq suspend fail\n"); + return err; + } + + for (i = lima_ip_num - 1; i >= 0; i--) + lima_suspend_ip(ldev, i); + + lima_regulator_disable(ldev); + + lima_clk_disable(ldev); + + return 0; +} diff --git a/drivers/gpu/drm/lima/lima_device.h b/drivers/gpu/drm/lima/lima_device.h new file mode 100644 index 000000000..41b9d7b4b --- /dev/null +++ b/drivers/gpu/drm/lima/lima_device.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_DEVICE_H__ +#define __LIMA_DEVICE_H__ + +#include <drm/drm_device.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/mutex.h> + +#include "lima_sched.h" +#include "lima_dump.h" +#include "lima_devfreq.h" + +enum lima_gpu_id { + lima_gpu_mali400 = 0, + lima_gpu_mali450, + lima_gpu_num, +}; + +enum lima_ip_id { + lima_ip_pmu, + lima_ip_gpmmu, + lima_ip_ppmmu0, + lima_ip_ppmmu1, + lima_ip_ppmmu2, + lima_ip_ppmmu3, + lima_ip_ppmmu4, + lima_ip_ppmmu5, + lima_ip_ppmmu6, + lima_ip_ppmmu7, + lima_ip_gp, + lima_ip_pp0, + lima_ip_pp1, + lima_ip_pp2, + lima_ip_pp3, + lima_ip_pp4, + lima_ip_pp5, + lima_ip_pp6, + lima_ip_pp7, + lima_ip_l2_cache0, + lima_ip_l2_cache1, + lima_ip_l2_cache2, + lima_ip_dlbu, + lima_ip_bcast, + lima_ip_pp_bcast, + lima_ip_ppmmu_bcast, + lima_ip_num, +}; + +struct lima_device; + +struct lima_ip { + struct lima_device *dev; + enum lima_ip_id id; + bool present; + + void __iomem *iomem; + int irq; + + union { + /* gp/pp */ + bool async_reset; + /* l2 cache */ + spinlock_t lock; + /* pmu/bcast */ + u32 mask; + } data; +}; + +enum lima_pipe_id { + lima_pipe_gp, + lima_pipe_pp, + lima_pipe_num, +}; + +struct lima_device { + struct device *dev; + struct drm_device *ddev; + + enum lima_gpu_id id; + u32 gp_version; + u32 pp_version; + int num_pp; + + void __iomem *iomem; + struct clk *clk_bus; + struct clk *clk_gpu; + struct reset_control *reset; + struct regulator *regulator; + + struct lima_ip ip[lima_ip_num]; + struct lima_sched_pipe pipe[lima_pipe_num]; + + struct lima_vm *empty_vm; + uint64_t va_start; + uint64_t va_end; + + u32 *dlbu_cpu; + dma_addr_t dlbu_dma; + + struct lima_devfreq devfreq; + + /* debug info */ + struct lima_dump_head dump; + struct list_head error_task_list; + struct mutex error_task_list_lock; +}; + +static inline struct lima_device * +to_lima_dev(struct drm_device *dev) +{ + return dev->dev_private; +} + +int lima_device_init(struct lima_device *ldev); +void lima_device_fini(struct lima_device *ldev); + +const char *lima_ip_name(struct lima_ip *ip); + +typedef int (*lima_poll_func_t)(struct lima_ip *); + +static inline int lima_poll_timeout(struct lima_ip *ip, lima_poll_func_t func, + int sleep_us, int timeout_us) +{ + ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); + + might_sleep_if(sleep_us); + while (1) { + if (func(ip)) + return 0; + + if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) + return -ETIMEDOUT; + + if (sleep_us) + usleep_range((sleep_us >> 2) + 1, sleep_us); + } + return 0; +} + +int lima_device_suspend(struct device *dev); +int lima_device_resume(struct device *dev); + +#endif diff --git a/drivers/gpu/drm/lima/lima_dlbu.c b/drivers/gpu/drm/lima/lima_dlbu.c new file mode 100644 index 000000000..c1d5ea35d --- /dev/null +++ b/drivers/gpu/drm/lima/lima_dlbu.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/io.h> +#include <linux/device.h> + +#include "lima_device.h" +#include "lima_dlbu.h" +#include "lima_vm.h" +#include "lima_regs.h" + +#define dlbu_write(reg, data) writel(data, ip->iomem + reg) +#define dlbu_read(reg) readl(ip->iomem + reg) + +void lima_dlbu_enable(struct lima_device *dev, int num_pp) +{ + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + struct lima_ip *ip = dev->ip + lima_ip_dlbu; + int i, mask = 0; + + for (i = 0; i < num_pp; i++) { + struct lima_ip *pp = pipe->processor[i]; + + mask |= 1 << (pp->id - lima_ip_pp0); + } + + dlbu_write(LIMA_DLBU_PP_ENABLE_MASK, mask); +} + +void lima_dlbu_disable(struct lima_device *dev) +{ + struct lima_ip *ip = dev->ip + lima_ip_dlbu; + + dlbu_write(LIMA_DLBU_PP_ENABLE_MASK, 0); +} + +void lima_dlbu_set_reg(struct lima_ip *ip, u32 *reg) +{ + dlbu_write(LIMA_DLBU_TLLIST_VBASEADDR, reg[0]); + dlbu_write(LIMA_DLBU_FB_DIM, reg[1]); + dlbu_write(LIMA_DLBU_TLLIST_CONF, reg[2]); + dlbu_write(LIMA_DLBU_START_TILE_POS, reg[3]); +} + +static int lima_dlbu_hw_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + + dlbu_write(LIMA_DLBU_MASTER_TLLIST_PHYS_ADDR, dev->dlbu_dma | 1); + dlbu_write(LIMA_DLBU_MASTER_TLLIST_VADDR, LIMA_VA_RESERVE_DLBU); + + return 0; +} + +int lima_dlbu_resume(struct lima_ip *ip) +{ + return lima_dlbu_hw_init(ip); +} + +void lima_dlbu_suspend(struct lima_ip *ip) +{ + +} + +int lima_dlbu_init(struct lima_ip *ip) +{ + return lima_dlbu_hw_init(ip); +} + +void lima_dlbu_fini(struct lima_ip *ip) +{ + +} diff --git a/drivers/gpu/drm/lima/lima_dlbu.h b/drivers/gpu/drm/lima/lima_dlbu.h new file mode 100644 index 000000000..be71daaae --- /dev/null +++ b/drivers/gpu/drm/lima/lima_dlbu.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2018-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_DLBU_H__ +#define __LIMA_DLBU_H__ + +struct lima_ip; +struct lima_device; + +void lima_dlbu_enable(struct lima_device *dev, int num_pp); +void lima_dlbu_disable(struct lima_device *dev); + +void lima_dlbu_set_reg(struct lima_ip *ip, u32 *reg); + +int lima_dlbu_resume(struct lima_ip *ip); +void lima_dlbu_suspend(struct lima_ip *ip); +int lima_dlbu_init(struct lima_ip *ip); +void lima_dlbu_fini(struct lima_ip *ip); + +#endif diff --git a/drivers/gpu/drm/lima/lima_drv.c b/drivers/gpu/drm/lima/lima_drv.c new file mode 100644 index 000000000..9a0eb1092 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_drv.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <drm/drm_ioctl.h> +#include <drm/drm_drv.h> +#include <drm/drm_prime.h> +#include <drm/lima_drm.h> + +#include "lima_device.h" +#include "lima_drv.h" +#include "lima_gem.h" +#include "lima_vm.h" + +int lima_sched_timeout_ms; +uint lima_heap_init_nr_pages = 8; +uint lima_max_error_tasks; +uint lima_job_hang_limit; + +MODULE_PARM_DESC(sched_timeout_ms, "task run timeout in ms"); +module_param_named(sched_timeout_ms, lima_sched_timeout_ms, int, 0444); + +MODULE_PARM_DESC(heap_init_nr_pages, "heap buffer init number of pages"); +module_param_named(heap_init_nr_pages, lima_heap_init_nr_pages, uint, 0444); + +MODULE_PARM_DESC(max_error_tasks, "max number of error tasks to save"); +module_param_named(max_error_tasks, lima_max_error_tasks, uint, 0644); + +MODULE_PARM_DESC(job_hang_limit, "number of times to allow a job to hang before dropping it (default 0)"); +module_param_named(job_hang_limit, lima_job_hang_limit, uint, 0444); + +static int lima_ioctl_get_param(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_get_param *args = data; + struct lima_device *ldev = to_lima_dev(dev); + + if (args->pad) + return -EINVAL; + + switch (args->param) { + case DRM_LIMA_PARAM_GPU_ID: + switch (ldev->id) { + case lima_gpu_mali400: + args->value = DRM_LIMA_PARAM_GPU_ID_MALI400; + break; + case lima_gpu_mali450: + args->value = DRM_LIMA_PARAM_GPU_ID_MALI450; + break; + default: + args->value = DRM_LIMA_PARAM_GPU_ID_UNKNOWN; + break; + } + break; + + case DRM_LIMA_PARAM_NUM_PP: + args->value = ldev->pipe[lima_pipe_pp].num_processor; + break; + + case DRM_LIMA_PARAM_GP_VERSION: + args->value = ldev->gp_version; + break; + + case DRM_LIMA_PARAM_PP_VERSION: + args->value = ldev->pp_version; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int lima_ioctl_gem_create(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_gem_create *args = data; + + if (args->pad) + return -EINVAL; + + if (args->flags & ~(LIMA_BO_FLAG_HEAP)) + return -EINVAL; + + if (args->size == 0) + return -EINVAL; + + return lima_gem_create_handle(dev, file, args->size, args->flags, &args->handle); +} + +static int lima_ioctl_gem_info(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_gem_info *args = data; + + return lima_gem_get_info(file, args->handle, &args->va, &args->offset); +} + +static int lima_ioctl_gem_submit(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_gem_submit *args = data; + struct lima_device *ldev = to_lima_dev(dev); + struct lima_drm_priv *priv = file->driver_priv; + struct drm_lima_gem_submit_bo *bos; + struct lima_sched_pipe *pipe; + struct lima_sched_task *task; + struct lima_ctx *ctx; + struct lima_submit submit = {0}; + size_t size; + int err = 0; + + if (args->pipe >= lima_pipe_num || args->nr_bos == 0) + return -EINVAL; + + if (args->flags & ~(LIMA_SUBMIT_FLAG_EXPLICIT_FENCE)) + return -EINVAL; + + pipe = ldev->pipe + args->pipe; + if (args->frame_size != pipe->frame_size) + return -EINVAL; + + bos = kvcalloc(args->nr_bos, sizeof(*submit.bos) + sizeof(*submit.lbos), GFP_KERNEL); + if (!bos) + return -ENOMEM; + + size = args->nr_bos * sizeof(*submit.bos); + if (copy_from_user(bos, u64_to_user_ptr(args->bos), size)) { + err = -EFAULT; + goto out0; + } + + task = kmem_cache_zalloc(pipe->task_slab, GFP_KERNEL); + if (!task) { + err = -ENOMEM; + goto out0; + } + + task->frame = task + 1; + if (copy_from_user(task->frame, u64_to_user_ptr(args->frame), args->frame_size)) { + err = -EFAULT; + goto out1; + } + + err = pipe->task_validate(pipe, task); + if (err) + goto out1; + + ctx = lima_ctx_get(&priv->ctx_mgr, args->ctx); + if (!ctx) { + err = -ENOENT; + goto out1; + } + + submit.pipe = args->pipe; + submit.bos = bos; + submit.lbos = (void *)bos + size; + submit.nr_bos = args->nr_bos; + submit.task = task; + submit.ctx = ctx; + submit.flags = args->flags; + submit.in_sync[0] = args->in_sync[0]; + submit.in_sync[1] = args->in_sync[1]; + submit.out_sync = args->out_sync; + + err = lima_gem_submit(file, &submit); + + lima_ctx_put(ctx); +out1: + if (err) + kmem_cache_free(pipe->task_slab, task); +out0: + kvfree(bos); + return err; +} + +static int lima_ioctl_gem_wait(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_gem_wait *args = data; + + if (args->op & ~(LIMA_GEM_WAIT_READ|LIMA_GEM_WAIT_WRITE)) + return -EINVAL; + + return lima_gem_wait(file, args->handle, args->op, args->timeout_ns); +} + +static int lima_ioctl_ctx_create(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_ctx_create *args = data; + struct lima_drm_priv *priv = file->driver_priv; + struct lima_device *ldev = to_lima_dev(dev); + + if (args->_pad) + return -EINVAL; + + return lima_ctx_create(ldev, &priv->ctx_mgr, &args->id); +} + +static int lima_ioctl_ctx_free(struct drm_device *dev, void *data, struct drm_file *file) +{ + struct drm_lima_ctx_create *args = data; + struct lima_drm_priv *priv = file->driver_priv; + + if (args->_pad) + return -EINVAL; + + return lima_ctx_free(&priv->ctx_mgr, args->id); +} + +static int lima_drm_driver_open(struct drm_device *dev, struct drm_file *file) +{ + int err; + struct lima_drm_priv *priv; + struct lima_device *ldev = to_lima_dev(dev); + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + priv->vm = lima_vm_create(ldev); + if (!priv->vm) { + err = -ENOMEM; + goto err_out0; + } + + lima_ctx_mgr_init(&priv->ctx_mgr); + + file->driver_priv = priv; + return 0; + +err_out0: + kfree(priv); + return err; +} + +static void lima_drm_driver_postclose(struct drm_device *dev, struct drm_file *file) +{ + struct lima_drm_priv *priv = file->driver_priv; + + lima_ctx_mgr_fini(&priv->ctx_mgr); + lima_vm_put(priv->vm); + kfree(priv); +} + +static const struct drm_ioctl_desc lima_drm_driver_ioctls[] = { + DRM_IOCTL_DEF_DRV(LIMA_GET_PARAM, lima_ioctl_get_param, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_GEM_CREATE, lima_ioctl_gem_create, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_GEM_INFO, lima_ioctl_gem_info, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_GEM_SUBMIT, lima_ioctl_gem_submit, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_GEM_WAIT, lima_ioctl_gem_wait, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_CTX_CREATE, lima_ioctl_ctx_create, DRM_RENDER_ALLOW), + DRM_IOCTL_DEF_DRV(LIMA_CTX_FREE, lima_ioctl_ctx_free, DRM_RENDER_ALLOW), +}; + +DEFINE_DRM_GEM_FOPS(lima_drm_driver_fops); + +/* + * Changelog: + * + * - 1.1.0 - add heap buffer support + */ + +static const struct drm_driver lima_drm_driver = { + .driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ, + .open = lima_drm_driver_open, + .postclose = lima_drm_driver_postclose, + .ioctls = lima_drm_driver_ioctls, + .num_ioctls = ARRAY_SIZE(lima_drm_driver_ioctls), + .fops = &lima_drm_driver_fops, + .name = "lima", + .desc = "lima DRM", + .major = 1, + .minor = 1, + .patchlevel = 0, + + .gem_create_object = lima_gem_create_object, + .gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table, +}; + +struct lima_block_reader { + void *dst; + size_t base; + size_t count; + size_t off; + ssize_t read; +}; + +static bool lima_read_block(struct lima_block_reader *reader, + void *src, size_t src_size) +{ + size_t max_off = reader->base + src_size; + + if (reader->off < max_off) { + size_t size = min_t(size_t, max_off - reader->off, + reader->count); + + memcpy(reader->dst, src + (reader->off - reader->base), size); + + reader->dst += size; + reader->off += size; + reader->read += size; + reader->count -= size; + } + + reader->base = max_off; + + return !!reader->count; +} + +static ssize_t lima_error_state_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct lima_device *ldev = dev_get_drvdata(dev); + struct lima_sched_error_task *et; + struct lima_block_reader reader = { + .dst = buf, + .count = count, + .off = off, + }; + + mutex_lock(&ldev->error_task_list_lock); + + if (lima_read_block(&reader, &ldev->dump, sizeof(ldev->dump))) { + list_for_each_entry(et, &ldev->error_task_list, list) { + if (!lima_read_block(&reader, et->data, et->size)) + break; + } + } + + mutex_unlock(&ldev->error_task_list_lock); + return reader.read; +} + +static ssize_t lima_error_state_write(struct file *file, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct lima_device *ldev = dev_get_drvdata(dev); + struct lima_sched_error_task *et, *tmp; + + mutex_lock(&ldev->error_task_list_lock); + + list_for_each_entry_safe(et, tmp, &ldev->error_task_list, list) { + list_del(&et->list); + kvfree(et); + } + + ldev->dump.size = 0; + ldev->dump.num_tasks = 0; + + mutex_unlock(&ldev->error_task_list_lock); + + return count; +} + +static const struct bin_attribute lima_error_state_attr = { + .attr.name = "error", + .attr.mode = 0600, + .size = 0, + .read = lima_error_state_read, + .write = lima_error_state_write, +}; + +static int lima_pdev_probe(struct platform_device *pdev) +{ + struct lima_device *ldev; + struct drm_device *ddev; + const struct lima_compatible *comp; + int err; + + err = lima_sched_slab_init(); + if (err) + return err; + + ldev = devm_kzalloc(&pdev->dev, sizeof(*ldev), GFP_KERNEL); + if (!ldev) { + err = -ENOMEM; + goto err_out0; + } + + ldev->dev = &pdev->dev; + comp = of_device_get_match_data(&pdev->dev); + if (!comp) { + err = -ENODEV; + goto err_out0; + } + + ldev->id = comp->id; + + platform_set_drvdata(pdev, ldev); + + /* Allocate and initialize the DRM device. */ + ddev = drm_dev_alloc(&lima_drm_driver, &pdev->dev); + if (IS_ERR(ddev)) { + err = PTR_ERR(ddev); + goto err_out0; + } + + ddev->dev_private = ldev; + ldev->ddev = ddev; + + err = lima_device_init(ldev); + if (err) + goto err_out1; + + err = lima_devfreq_init(ldev); + if (err) { + dev_err(&pdev->dev, "Fatal error during devfreq init\n"); + goto err_out2; + } + + pm_runtime_set_active(ldev->dev); + pm_runtime_mark_last_busy(ldev->dev); + pm_runtime_set_autosuspend_delay(ldev->dev, 200); + pm_runtime_use_autosuspend(ldev->dev); + pm_runtime_enable(ldev->dev); + + /* + * Register the DRM device with the core and the connectors with + * sysfs. + */ + err = drm_dev_register(ddev, 0); + if (err < 0) + goto err_out3; + + if (sysfs_create_bin_file(&ldev->dev->kobj, &lima_error_state_attr)) + dev_warn(ldev->dev, "fail to create error state sysfs\n"); + + return 0; + +err_out3: + pm_runtime_disable(ldev->dev); + lima_devfreq_fini(ldev); +err_out2: + lima_device_fini(ldev); +err_out1: + drm_dev_put(ddev); +err_out0: + lima_sched_slab_fini(); + return err; +} + +static void lima_pdev_remove(struct platform_device *pdev) +{ + struct lima_device *ldev = platform_get_drvdata(pdev); + struct drm_device *ddev = ldev->ddev; + + sysfs_remove_bin_file(&ldev->dev->kobj, &lima_error_state_attr); + + drm_dev_unregister(ddev); + + /* stop autosuspend to make sure device is in active state */ + pm_runtime_set_autosuspend_delay(ldev->dev, -1); + pm_runtime_disable(ldev->dev); + + lima_devfreq_fini(ldev); + lima_device_fini(ldev); + + drm_dev_put(ddev); + lima_sched_slab_fini(); +} + +static const struct lima_compatible lima_mali400_data = { + .id = lima_gpu_mali400, +}; + +static const struct lima_compatible lima_mali450_data = { + .id = lima_gpu_mali450, +}; + +static const struct of_device_id dt_match[] = { + { .compatible = "arm,mali-400", .data = &lima_mali400_data }, + { .compatible = "arm,mali-450", .data = &lima_mali450_data }, + {} +}; +MODULE_DEVICE_TABLE(of, dt_match); + +static const struct dev_pm_ops lima_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(lima_device_suspend, lima_device_resume, NULL) +}; + +static struct platform_driver lima_platform_driver = { + .probe = lima_pdev_probe, + .remove = lima_pdev_remove, + .driver = { + .name = "lima", + .pm = &lima_pm_ops, + .of_match_table = dt_match, + }, +}; + +module_platform_driver(lima_platform_driver); + +MODULE_AUTHOR("Lima Project Developers"); +MODULE_DESCRIPTION("Lima DRM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SOFTDEP("pre: governor_simpleondemand"); diff --git a/drivers/gpu/drm/lima/lima_drv.h b/drivers/gpu/drm/lima/lima_drv.h new file mode 100644 index 000000000..6706c19b1 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_drv.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_DRV_H__ +#define __LIMA_DRV_H__ + +#include <drm/drm_file.h> + +#include "lima_ctx.h" +#include "lima_device.h" + +extern int lima_sched_timeout_ms; +extern uint lima_heap_init_nr_pages; +extern uint lima_max_error_tasks; +extern uint lima_job_hang_limit; + +struct lima_vm; +struct lima_bo; +struct lima_sched_task; + +struct drm_lima_gem_submit_bo; + +struct lima_drm_priv { + struct lima_vm *vm; + struct lima_ctx_mgr ctx_mgr; +}; + +struct lima_submit { + struct lima_ctx *ctx; + int pipe; + u32 flags; + + struct drm_lima_gem_submit_bo *bos; + struct lima_bo **lbos; + u32 nr_bos; + + u32 in_sync[2]; + u32 out_sync; + + struct lima_sched_task *task; +}; + +struct lima_compatible { + enum lima_gpu_id id; +}; + +static inline struct lima_drm_priv * +to_lima_drm_priv(struct drm_file *file) +{ + return file->driver_priv; +} + +#endif diff --git a/drivers/gpu/drm/lima/lima_dump.h b/drivers/gpu/drm/lima/lima_dump.h new file mode 100644 index 000000000..ca243d99c --- /dev/null +++ b/drivers/gpu/drm/lima/lima_dump.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2020 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_DUMP_H__ +#define __LIMA_DUMP_H__ + +#include <linux/types.h> + +/** + * dump file format for all the information to start a lima task + * + * top level format + * | magic code "LIMA" | format version | num tasks | data size | + * | reserved | reserved | reserved | reserved | + * | task 1 ID | task 1 size | num chunks | reserved | task 1 data | + * | task 2 ID | task 2 size | num chunks | reserved | task 2 data | + * ... + * + * task data format + * | chunk 1 ID | chunk 1 size | reserved | reserved | chunk 1 data | + * | chunk 2 ID | chunk 2 size | reserved | reserved | chunk 2 data | + * ... + * + */ + +#define LIMA_DUMP_MAJOR 1 +#define LIMA_DUMP_MINOR 0 + +#define LIMA_DUMP_MAGIC 0x414d494c + +struct lima_dump_head { + __u32 magic; + __u16 version_major; + __u16 version_minor; + __u32 num_tasks; + __u32 size; + __u32 reserved[4]; +}; + +#define LIMA_DUMP_TASK_GP 0 +#define LIMA_DUMP_TASK_PP 1 +#define LIMA_DUMP_TASK_NUM 2 + +struct lima_dump_task { + __u32 id; + __u32 size; + __u32 num_chunks; + __u32 reserved; +}; + +#define LIMA_DUMP_CHUNK_FRAME 0 +#define LIMA_DUMP_CHUNK_BUFFER 1 +#define LIMA_DUMP_CHUNK_PROCESS_NAME 2 +#define LIMA_DUMP_CHUNK_PROCESS_ID 3 +#define LIMA_DUMP_CHUNK_NUM 4 + +struct lima_dump_chunk { + __u32 id; + __u32 size; + __u32 reserved[2]; +}; + +struct lima_dump_chunk_buffer { + __u32 id; + __u32 size; + __u32 va; + __u32 reserved; +}; + +struct lima_dump_chunk_pid { + __u32 id; + __u32 size; + __u32 pid; + __u32 reserved; +}; + +#endif diff --git a/drivers/gpu/drm/lima/lima_gem.c b/drivers/gpu/drm/lima/lima_gem.c new file mode 100644 index 000000000..0c73860bb --- /dev/null +++ b/drivers/gpu/drm/lima/lima_gem.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/mm.h> +#include <linux/iosys-map.h> +#include <linux/sync_file.h> +#include <linux/pagemap.h> +#include <linux/shmem_fs.h> +#include <linux/dma-mapping.h> + +#include <drm/drm_file.h> +#include <drm/drm_syncobj.h> +#include <drm/drm_utils.h> + +#include <drm/lima_drm.h> + +#include "lima_drv.h" +#include "lima_gem.h" +#include "lima_vm.h" + +int lima_heap_alloc(struct lima_bo *bo, struct lima_vm *vm) +{ + struct page **pages; + struct address_space *mapping = bo->base.base.filp->f_mapping; + struct device *dev = bo->base.base.dev->dev; + size_t old_size = bo->heap_size; + size_t new_size = bo->heap_size ? bo->heap_size * 2 : + (lima_heap_init_nr_pages << PAGE_SHIFT); + struct sg_table sgt; + int i, ret; + + if (bo->heap_size >= bo->base.base.size) + return -ENOSPC; + + new_size = min(new_size, bo->base.base.size); + + dma_resv_lock(bo->base.base.resv, NULL); + + if (bo->base.pages) { + pages = bo->base.pages; + } else { + pages = kvmalloc_objs(*pages, bo->base.base.size >> PAGE_SHIFT, + GFP_KERNEL | __GFP_ZERO); + if (!pages) { + dma_resv_unlock(bo->base.base.resv); + return -ENOMEM; + } + + bo->base.pages = pages; + refcount_set(&bo->base.pages_use_count, 1); + + mapping_set_unevictable(mapping); + } + + for (i = old_size >> PAGE_SHIFT; i < new_size >> PAGE_SHIFT; i++) { + struct page *page = shmem_read_mapping_page(mapping, i); + + if (IS_ERR(page)) { + dma_resv_unlock(bo->base.base.resv); + return PTR_ERR(page); + } + pages[i] = page; + } + + dma_resv_unlock(bo->base.base.resv); + + ret = sg_alloc_table_from_pages(&sgt, pages, i, 0, + new_size, GFP_KERNEL); + if (ret) + return ret; + + if (bo->base.sgt) { + dma_unmap_sgtable(dev, bo->base.sgt, DMA_BIDIRECTIONAL, 0); + sg_free_table(bo->base.sgt); + } else { + bo->base.sgt = kmalloc_obj(*bo->base.sgt); + if (!bo->base.sgt) { + ret = -ENOMEM; + goto err_out0; + } + } + + ret = dma_map_sgtable(dev, &sgt, DMA_BIDIRECTIONAL, 0); + if (ret) + goto err_out1; + + *bo->base.sgt = sgt; + + if (vm) { + ret = lima_vm_map_bo(vm, bo, old_size >> PAGE_SHIFT); + if (ret) + goto err_out2; + } + + bo->heap_size = new_size; + return 0; + +err_out2: + dma_unmap_sgtable(dev, &sgt, DMA_BIDIRECTIONAL, 0); +err_out1: + kfree(bo->base.sgt); + bo->base.sgt = NULL; +err_out0: + sg_free_table(&sgt); + return ret; +} + +int lima_gem_create_handle(struct drm_device *dev, struct drm_file *file, + u32 size, u32 flags, u32 *handle) +{ + int err; + gfp_t mask; + struct drm_gem_shmem_object *shmem; + struct drm_gem_object *obj; + struct lima_bo *bo; + bool is_heap = flags & LIMA_BO_FLAG_HEAP; + + shmem = drm_gem_shmem_create(dev, size); + if (IS_ERR(shmem)) + return PTR_ERR(shmem); + + obj = &shmem->base; + + /* Mali Utgard GPU can only support 32bit address space */ + mask = mapping_gfp_mask(obj->filp->f_mapping); + mask &= ~__GFP_HIGHMEM; + mask |= __GFP_DMA32; + mapping_set_gfp_mask(obj->filp->f_mapping, mask); + + if (is_heap) { + bo = to_lima_bo(obj); + err = lima_heap_alloc(bo, NULL); + if (err) + goto out; + } else { + struct sg_table *sgt = drm_gem_shmem_get_pages_sgt(shmem); + + if (IS_ERR(sgt)) { + err = PTR_ERR(sgt); + goto out; + } + } + + err = drm_gem_handle_create(file, obj, handle); + +out: + /* drop reference from allocate - handle holds it now */ + drm_gem_object_put(obj); + + return err; +} + +static void lima_gem_free_object(struct drm_gem_object *obj) +{ + struct lima_bo *bo = to_lima_bo(obj); + + if (!list_empty(&bo->va)) + dev_err(obj->dev->dev, "lima gem free bo still has va\n"); + + drm_gem_shmem_free(&bo->base); +} + +static int lima_gem_object_open(struct drm_gem_object *obj, struct drm_file *file) +{ + struct lima_bo *bo = to_lima_bo(obj); + struct lima_drm_priv *priv = to_lima_drm_priv(file); + struct lima_vm *vm = priv->vm; + + return lima_vm_bo_add(vm, bo, true); +} + +static void lima_gem_object_close(struct drm_gem_object *obj, struct drm_file *file) +{ + struct lima_bo *bo = to_lima_bo(obj); + struct lima_drm_priv *priv = to_lima_drm_priv(file); + struct lima_vm *vm = priv->vm; + + lima_vm_bo_del(vm, bo); +} + +static int lima_gem_pin(struct drm_gem_object *obj) +{ + struct lima_bo *bo = to_lima_bo(obj); + + if (bo->heap_size) + return -EINVAL; + + return drm_gem_shmem_pin_locked(&bo->base); +} + +static int lima_gem_vmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + struct lima_bo *bo = to_lima_bo(obj); + + if (bo->heap_size) + return -EINVAL; + + return drm_gem_shmem_vmap_locked(&bo->base, map); +} + +static int lima_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + struct lima_bo *bo = to_lima_bo(obj); + + if (bo->heap_size) + return -EINVAL; + + return drm_gem_shmem_mmap(&bo->base, vma); +} + +static const struct drm_gem_object_funcs lima_gem_funcs = { + .free = lima_gem_free_object, + .open = lima_gem_object_open, + .close = lima_gem_object_close, + .print_info = drm_gem_shmem_object_print_info, + .pin = lima_gem_pin, + .unpin = drm_gem_shmem_object_unpin, + .get_sg_table = drm_gem_shmem_object_get_sg_table, + .vmap = lima_gem_vmap, + .vunmap = drm_gem_shmem_object_vunmap, + .mmap = lima_gem_mmap, + .vm_ops = &drm_gem_shmem_vm_ops, +}; + +struct drm_gem_object *lima_gem_create_object(struct drm_device *dev, size_t size) +{ + struct lima_bo *bo; + + bo = kzalloc_obj(*bo); + if (!bo) + return ERR_PTR(-ENOMEM); + + mutex_init(&bo->lock); + INIT_LIST_HEAD(&bo->va); + bo->base.map_wc = true; + bo->base.base.funcs = &lima_gem_funcs; + + return &bo->base.base; +} + +int lima_gem_get_info(struct drm_file *file, u32 handle, u32 *va, u64 *offset) +{ + struct drm_gem_object *obj; + struct lima_bo *bo; + struct lima_drm_priv *priv = to_lima_drm_priv(file); + struct lima_vm *vm = priv->vm; + + obj = drm_gem_object_lookup(file, handle); + if (!obj) + return -ENOENT; + + bo = to_lima_bo(obj); + + *va = lima_vm_get_va(vm, bo); + + *offset = drm_vma_node_offset_addr(&obj->vma_node); + + drm_gem_object_put(obj); + return 0; +} + +static int lima_gem_sync_bo(struct lima_sched_task *task, struct lima_bo *bo, + bool write, bool explicit) +{ + int err; + + err = dma_resv_reserve_fences(lima_bo_resv(bo), 1); + if (err) + return err; + + /* explicit sync use user passed dep fence */ + if (explicit) + return 0; + + return drm_sched_job_add_implicit_dependencies(&task->base, + &bo->base.base, + write); +} + +static int lima_gem_add_deps(struct drm_file *file, struct lima_submit *submit) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(submit->in_sync); i++) { + if (!submit->in_sync[i]) + continue; + + err = drm_sched_job_add_syncobj_dependency(&submit->task->base, file, + submit->in_sync[i], 0); + if (err) + return err; + } + + return 0; +} + +int lima_gem_submit(struct drm_file *file, struct lima_submit *submit) +{ + int i, err = 0; + struct ww_acquire_ctx ctx; + struct lima_drm_priv *priv = to_lima_drm_priv(file); + struct lima_vm *vm = priv->vm; + struct drm_syncobj *out_sync = NULL; + struct dma_fence *fence; + struct lima_bo **bos = submit->lbos; + + if (submit->out_sync) { + out_sync = drm_syncobj_find(file, submit->out_sync); + if (!out_sync) + return -ENOENT; + } + + for (i = 0; i < submit->nr_bos; i++) { + struct drm_gem_object *obj; + struct lima_bo *bo; + + obj = drm_gem_object_lookup(file, submit->bos[i].handle); + if (!obj) { + err = -ENOENT; + goto err_out0; + } + + bo = to_lima_bo(obj); + + /* increase refcnt of gpu va map to prevent unmapped when executing, + * will be decreased when task done + */ + err = lima_vm_bo_add(vm, bo, false); + if (err) { + drm_gem_object_put(obj); + goto err_out0; + } + + bos[i] = bo; + } + + err = drm_gem_lock_reservations((struct drm_gem_object **)bos, + submit->nr_bos, &ctx); + if (err) + goto err_out0; + + err = lima_sched_task_init( + submit->task, submit->ctx->context + submit->pipe, + bos, submit->nr_bos, vm, file->client_id); + if (err) + goto err_out1; + + err = lima_gem_add_deps(file, submit); + if (err) + goto err_out2; + + for (i = 0; i < submit->nr_bos; i++) { + err = lima_gem_sync_bo( + submit->task, bos[i], + submit->bos[i].flags & LIMA_SUBMIT_BO_WRITE, + submit->flags & LIMA_SUBMIT_FLAG_EXPLICIT_FENCE); + if (err) + goto err_out2; + } + + fence = lima_sched_context_queue_task(submit->task); + + for (i = 0; i < submit->nr_bos; i++) { + dma_resv_add_fence(lima_bo_resv(bos[i]), fence, + submit->bos[i].flags & LIMA_SUBMIT_BO_WRITE ? + DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ); + } + + drm_gem_unlock_reservations((struct drm_gem_object **)bos, + submit->nr_bos, &ctx); + + for (i = 0; i < submit->nr_bos; i++) + drm_gem_object_put(&bos[i]->base.base); + + if (out_sync) { + drm_syncobj_replace_fence(out_sync, fence); + drm_syncobj_put(out_sync); + } + + dma_fence_put(fence); + + return 0; + +err_out2: + lima_sched_task_fini(submit->task); +err_out1: + drm_gem_unlock_reservations((struct drm_gem_object **)bos, + submit->nr_bos, &ctx); +err_out0: + for (i = 0; i < submit->nr_bos; i++) { + if (!bos[i]) + break; + lima_vm_bo_del(vm, bos[i]); + drm_gem_object_put(&bos[i]->base.base); + } + if (out_sync) + drm_syncobj_put(out_sync); + return err; +} + +int lima_gem_wait(struct drm_file *file, u32 handle, u32 op, s64 timeout_ns) +{ + bool write = op & LIMA_GEM_WAIT_WRITE; + long ret, timeout; + + if (!op) + return 0; + + timeout = drm_timeout_abs_to_jiffies(timeout_ns); + + ret = drm_gem_dma_resv_wait(file, handle, write, timeout); + if (ret == -ETIME) + ret = timeout ? -ETIMEDOUT : -EBUSY; + + return ret; +} diff --git a/drivers/gpu/drm/lima/lima_gem.h b/drivers/gpu/drm/lima/lima_gem.h new file mode 100644 index 000000000..ccea06142 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_gem.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_GEM_H__ +#define __LIMA_GEM_H__ + +#include <drm/drm_gem_shmem_helper.h> + +struct lima_submit; +struct lima_vm; + +struct lima_bo { + struct drm_gem_shmem_object base; + + struct mutex lock; + struct list_head va; + + size_t heap_size; +}; + +static inline struct lima_bo * +to_lima_bo(struct drm_gem_object *obj) +{ + return container_of(to_drm_gem_shmem_obj(obj), struct lima_bo, base); +} + +static inline size_t lima_bo_size(struct lima_bo *bo) +{ + return bo->base.base.size; +} + +static inline struct dma_resv *lima_bo_resv(struct lima_bo *bo) +{ + return bo->base.base.resv; +} + +int lima_heap_alloc(struct lima_bo *bo, struct lima_vm *vm); +struct drm_gem_object *lima_gem_create_object(struct drm_device *dev, size_t size); +int lima_gem_create_handle(struct drm_device *dev, struct drm_file *file, + u32 size, u32 flags, u32 *handle); +int lima_gem_get_info(struct drm_file *file, u32 handle, u32 *va, u64 *offset); +int lima_gem_submit(struct drm_file *file, struct lima_submit *submit); +int lima_gem_wait(struct drm_file *file, u32 handle, u32 op, s64 timeout_ns); + +void lima_set_vma_flags(struct vm_area_struct *vma); + +#endif diff --git a/drivers/gpu/drm/lima/lima_gp.c b/drivers/gpu/drm/lima/lima_gp.c new file mode 100644 index 000000000..3282997a0 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_gp.c @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <drm/lima_drm.h> + +#include "lima_device.h" +#include "lima_gp.h" +#include "lima_regs.h" +#include "lima_gem.h" +#include "lima_vm.h" + +#define gp_write(reg, data) writel(data, ip->iomem + reg) +#define gp_read(reg) readl(ip->iomem + reg) + +static irqreturn_t lima_gp_irq_handler(int irq, void *data) +{ + struct lima_ip *ip = data; + struct lima_device *dev = ip->dev; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_gp; + struct lima_sched_task *task = pipe->current_task; + u32 state = gp_read(LIMA_GP_INT_STAT); + u32 status = gp_read(LIMA_GP_STATUS); + bool done = false; + + /* for shared irq case */ + if (!state) + return IRQ_NONE; + + if (state & LIMA_GP_IRQ_MASK_ERROR) { + if ((state & LIMA_GP_IRQ_MASK_ERROR) == + LIMA_GP_IRQ_PLBU_OUT_OF_MEM) { + dev_dbg(dev->dev, "%s out of heap irq status=%x\n", + lima_ip_name(ip), status); + } else { + dev_err(dev->dev, "%s error irq state=%x status=%x\n", + lima_ip_name(ip), state, status); + if (task) + task->recoverable = false; + } + + /* mask all interrupts before hard reset */ + gp_write(LIMA_GP_INT_MASK, 0); + + pipe->error = true; + done = true; + } else { + bool valid = state & (LIMA_GP_IRQ_VS_END_CMD_LST | + LIMA_GP_IRQ_PLBU_END_CMD_LST); + bool active = status & (LIMA_GP_STATUS_VS_ACTIVE | + LIMA_GP_STATUS_PLBU_ACTIVE); + done = valid && !active; + pipe->error = false; + } + + gp_write(LIMA_GP_INT_CLEAR, state); + + if (done) + lima_sched_pipe_task_done(pipe); + + return IRQ_HANDLED; +} + +static void lima_gp_soft_reset_async(struct lima_ip *ip) +{ + if (ip->data.async_reset) + return; + + gp_write(LIMA_GP_INT_MASK, 0); + gp_write(LIMA_GP_INT_CLEAR, LIMA_GP_IRQ_RESET_COMPLETED); + gp_write(LIMA_GP_CMD, LIMA_GP_CMD_SOFT_RESET); + ip->data.async_reset = true; +} + +static int lima_gp_soft_reset_async_wait(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + u32 v; + + if (!ip->data.async_reset) + return 0; + + err = readl_poll_timeout(ip->iomem + LIMA_GP_INT_RAWSTAT, v, + v & LIMA_GP_IRQ_RESET_COMPLETED, + 0, 100); + if (err) { + dev_err(dev->dev, "%s soft reset time out\n", + lima_ip_name(ip)); + return err; + } + + gp_write(LIMA_GP_INT_CLEAR, LIMA_GP_IRQ_MASK_ALL); + gp_write(LIMA_GP_INT_MASK, LIMA_GP_IRQ_MASK_USED); + + ip->data.async_reset = false; + return 0; +} + +static int lima_gp_task_validate(struct lima_sched_pipe *pipe, + struct lima_sched_task *task) +{ + struct drm_lima_gp_frame *frame = task->frame; + u32 *f = frame->frame; + (void)pipe; + + if (f[LIMA_GP_VSCL_START_ADDR >> 2] > + f[LIMA_GP_VSCL_END_ADDR >> 2] || + f[LIMA_GP_PLBUCL_START_ADDR >> 2] > + f[LIMA_GP_PLBUCL_END_ADDR >> 2] || + f[LIMA_GP_PLBU_ALLOC_START_ADDR >> 2] > + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2]) + return -EINVAL; + + if (f[LIMA_GP_VSCL_START_ADDR >> 2] == + f[LIMA_GP_VSCL_END_ADDR >> 2] && + f[LIMA_GP_PLBUCL_START_ADDR >> 2] == + f[LIMA_GP_PLBUCL_END_ADDR >> 2]) + return -EINVAL; + + return 0; +} + +static void lima_gp_task_run(struct lima_sched_pipe *pipe, + struct lima_sched_task *task) +{ + struct lima_ip *ip = pipe->processor[0]; + struct drm_lima_gp_frame *frame = task->frame; + u32 *f = frame->frame; + u32 cmd = 0; + int i; + + /* update real heap buffer size for GP */ + for (i = 0; i < task->num_bos; i++) { + struct lima_bo *bo = task->bos[i]; + + if (bo->heap_size && + lima_vm_get_va(task->vm, bo) == + f[LIMA_GP_PLBU_ALLOC_START_ADDR >> 2]) { + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2] = + f[LIMA_GP_PLBU_ALLOC_START_ADDR >> 2] + + bo->heap_size; + task->recoverable = true; + task->heap = bo; + break; + } + } + + if (f[LIMA_GP_VSCL_START_ADDR >> 2] != + f[LIMA_GP_VSCL_END_ADDR >> 2]) + cmd |= LIMA_GP_CMD_START_VS; + if (f[LIMA_GP_PLBUCL_START_ADDR >> 2] != + f[LIMA_GP_PLBUCL_END_ADDR >> 2]) + cmd |= LIMA_GP_CMD_START_PLBU; + + /* before any hw ops, wait last success task async soft reset */ + lima_gp_soft_reset_async_wait(ip); + + for (i = 0; i < LIMA_GP_FRAME_REG_NUM; i++) + writel(f[i], ip->iomem + LIMA_GP_VSCL_START_ADDR + i * 4); + + gp_write(LIMA_GP_CMD, LIMA_GP_CMD_UPDATE_PLBU_ALLOC); + gp_write(LIMA_GP_CMD, cmd); +} + +static int lima_gp_bus_stop_poll(struct lima_ip *ip) +{ + return !!(gp_read(LIMA_GP_STATUS) & LIMA_GP_STATUS_BUS_STOPPED); +} + +static int lima_gp_hard_reset_poll(struct lima_ip *ip) +{ + gp_write(LIMA_GP_PERF_CNT_0_LIMIT, 0xC01A0000); + return gp_read(LIMA_GP_PERF_CNT_0_LIMIT) == 0xC01A0000; +} + +static int lima_gp_hard_reset(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int ret; + + gp_write(LIMA_GP_PERF_CNT_0_LIMIT, 0xC0FFE000); + gp_write(LIMA_GP_INT_MASK, 0); + + gp_write(LIMA_GP_CMD, LIMA_GP_CMD_STOP_BUS); + ret = lima_poll_timeout(ip, lima_gp_bus_stop_poll, 10, 100); + if (ret) { + dev_err(dev->dev, "%s bus stop timeout\n", lima_ip_name(ip)); + return ret; + } + gp_write(LIMA_GP_CMD, LIMA_GP_CMD_RESET); + ret = lima_poll_timeout(ip, lima_gp_hard_reset_poll, 10, 100); + if (ret) { + dev_err(dev->dev, "%s hard reset timeout\n", lima_ip_name(ip)); + return ret; + } + + gp_write(LIMA_GP_PERF_CNT_0_LIMIT, 0); + gp_write(LIMA_GP_INT_CLEAR, LIMA_GP_IRQ_MASK_ALL); + gp_write(LIMA_GP_INT_MASK, LIMA_GP_IRQ_MASK_USED); + + /* + * if there was an async soft reset queued, + * don't wait for it in the next job + */ + ip->data.async_reset = false; + + return 0; +} + +static void lima_gp_task_fini(struct lima_sched_pipe *pipe) +{ + lima_gp_soft_reset_async(pipe->processor[0]); +} + +static void lima_gp_task_error(struct lima_sched_pipe *pipe) +{ + struct lima_ip *ip = pipe->processor[0]; + + dev_err(ip->dev->dev, "%s task error int_state=%x status=%x\n", + lima_ip_name(ip), gp_read(LIMA_GP_INT_STAT), + gp_read(LIMA_GP_STATUS)); + + lima_gp_hard_reset(ip); +} + +static void lima_gp_task_mmu_error(struct lima_sched_pipe *pipe) +{ + lima_sched_pipe_task_done(pipe); +} + +static void lima_gp_task_mask_irq(struct lima_sched_pipe *pipe) +{ + struct lima_ip *ip = pipe->processor[0]; + + gp_write(LIMA_GP_INT_MASK, 0); +} + +static int lima_gp_task_recover(struct lima_sched_pipe *pipe) +{ + struct lima_ip *ip = pipe->processor[0]; + struct lima_sched_task *task = pipe->current_task; + struct drm_lima_gp_frame *frame = task->frame; + u32 *f = frame->frame; + size_t fail_size = + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2] - + f[LIMA_GP_PLBU_ALLOC_START_ADDR >> 2]; + + if (fail_size == task->heap->heap_size) { + int ret; + + ret = lima_heap_alloc(task->heap, task->vm); + if (ret < 0) + return ret; + } + + gp_write(LIMA_GP_INT_MASK, LIMA_GP_IRQ_MASK_USED); + /* Resume from where we stopped, i.e. new start is old end */ + gp_write(LIMA_GP_PLBU_ALLOC_START_ADDR, + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2]); + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2] = + f[LIMA_GP_PLBU_ALLOC_START_ADDR >> 2] + task->heap->heap_size; + gp_write(LIMA_GP_PLBU_ALLOC_END_ADDR, + f[LIMA_GP_PLBU_ALLOC_END_ADDR >> 2]); + gp_write(LIMA_GP_CMD, LIMA_GP_CMD_UPDATE_PLBU_ALLOC); + return 0; +} + +static void lima_gp_print_version(struct lima_ip *ip) +{ + u32 version, major, minor; + char *name; + + version = gp_read(LIMA_GP_VERSION); + major = (version >> 8) & 0xFF; + minor = version & 0xFF; + switch (version >> 16) { + case 0xA07: + name = "mali200"; + break; + case 0xC07: + name = "mali300"; + break; + case 0xB07: + name = "mali400"; + break; + case 0xD07: + name = "mali450"; + break; + default: + name = "unknown"; + break; + } + dev_info(ip->dev->dev, "%s - %s version major %d minor %d\n", + lima_ip_name(ip), name, major, minor); +} + +static struct kmem_cache *lima_gp_task_slab; +static int lima_gp_task_slab_refcnt; + +static int lima_gp_hw_init(struct lima_ip *ip) +{ + ip->data.async_reset = false; + lima_gp_soft_reset_async(ip); + return lima_gp_soft_reset_async_wait(ip); +} + +int lima_gp_resume(struct lima_ip *ip) +{ + return lima_gp_hw_init(ip); +} + +void lima_gp_suspend(struct lima_ip *ip) +{ + +} + +int lima_gp_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + + lima_gp_print_version(ip); + + err = lima_gp_hw_init(ip); + if (err) + return err; + + err = devm_request_irq(dev->dev, ip->irq, lima_gp_irq_handler, + IRQF_SHARED, lima_ip_name(ip), ip); + if (err) { + dev_err(dev->dev, "%s fail to request irq\n", + lima_ip_name(ip)); + return err; + } + + dev->gp_version = gp_read(LIMA_GP_VERSION); + + return 0; +} + +void lima_gp_fini(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + + devm_free_irq(dev->dev, ip->irq, ip); +} + +int lima_gp_pipe_init(struct lima_device *dev) +{ + int frame_size = sizeof(struct drm_lima_gp_frame); + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_gp; + + if (!lima_gp_task_slab) { + lima_gp_task_slab = kmem_cache_create_usercopy( + "lima_gp_task", sizeof(struct lima_sched_task) + frame_size, + 0, SLAB_HWCACHE_ALIGN, sizeof(struct lima_sched_task), + frame_size, NULL); + if (!lima_gp_task_slab) + return -ENOMEM; + } + lima_gp_task_slab_refcnt++; + + pipe->frame_size = frame_size; + pipe->task_slab = lima_gp_task_slab; + + pipe->task_validate = lima_gp_task_validate; + pipe->task_run = lima_gp_task_run; + pipe->task_fini = lima_gp_task_fini; + pipe->task_error = lima_gp_task_error; + pipe->task_mmu_error = lima_gp_task_mmu_error; + pipe->task_recover = lima_gp_task_recover; + pipe->task_mask_irq = lima_gp_task_mask_irq; + + return 0; +} + +void lima_gp_pipe_fini(struct lima_device *dev) +{ + if (!--lima_gp_task_slab_refcnt) { + kmem_cache_destroy(lima_gp_task_slab); + lima_gp_task_slab = NULL; + } +} diff --git a/drivers/gpu/drm/lima/lima_gp.h b/drivers/gpu/drm/lima/lima_gp.h new file mode 100644 index 000000000..02ec9af78 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_gp.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_GP_H__ +#define __LIMA_GP_H__ + +struct lima_ip; +struct lima_device; + +int lima_gp_resume(struct lima_ip *ip); +void lima_gp_suspend(struct lima_ip *ip); +int lima_gp_init(struct lima_ip *ip); +void lima_gp_fini(struct lima_ip *ip); + +int lima_gp_pipe_init(struct lima_device *dev); +void lima_gp_pipe_fini(struct lima_device *dev); + +#endif diff --git a/drivers/gpu/drm/lima/lima_l2_cache.c b/drivers/gpu/drm/lima/lima_l2_cache.c new file mode 100644 index 000000000..184106ce5 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_l2_cache.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/iopoll.h> +#include <linux/device.h> + +#include "lima_device.h" +#include "lima_l2_cache.h" +#include "lima_regs.h" + +#define l2_cache_write(reg, data) writel(data, ip->iomem + reg) +#define l2_cache_read(reg) readl(ip->iomem + reg) + +static int lima_l2_cache_wait_idle(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + u32 v; + + err = readl_poll_timeout(ip->iomem + LIMA_L2_CACHE_STATUS, v, + !(v & LIMA_L2_CACHE_STATUS_COMMAND_BUSY), + 0, 1000); + if (err) { + dev_err(dev->dev, "%s wait command timeout\n", + lima_ip_name(ip)); + return err; + } + return 0; +} + +int lima_l2_cache_flush(struct lima_ip *ip) +{ + int ret; + + spin_lock(&ip->data.lock); + l2_cache_write(LIMA_L2_CACHE_COMMAND, LIMA_L2_CACHE_COMMAND_CLEAR_ALL); + ret = lima_l2_cache_wait_idle(ip); + spin_unlock(&ip->data.lock); + return ret; +} + +static int lima_l2_cache_hw_init(struct lima_ip *ip) +{ + int err; + + err = lima_l2_cache_flush(ip); + if (err) + return err; + + l2_cache_write(LIMA_L2_CACHE_ENABLE, + LIMA_L2_CACHE_ENABLE_ACCESS | + LIMA_L2_CACHE_ENABLE_READ_ALLOCATE); + l2_cache_write(LIMA_L2_CACHE_MAX_READS, 0x1c); + + return 0; +} + +int lima_l2_cache_resume(struct lima_ip *ip) +{ + return lima_l2_cache_hw_init(ip); +} + +void lima_l2_cache_suspend(struct lima_ip *ip) +{ + +} + +int lima_l2_cache_init(struct lima_ip *ip) +{ + int i; + u32 size; + struct lima_device *dev = ip->dev; + + /* l2_cache2 only exists when one of PP4-7 present */ + if (ip->id == lima_ip_l2_cache2) { + for (i = lima_ip_pp4; i <= lima_ip_pp7; i++) { + if (dev->ip[i].present) + break; + } + if (i > lima_ip_pp7) + return -ENODEV; + } + + spin_lock_init(&ip->data.lock); + + size = l2_cache_read(LIMA_L2_CACHE_SIZE); + dev_info(dev->dev, "%s %uK, %u-way, %ubyte cache line, %ubit external bus\n", + lima_ip_name(ip), + 1 << (((size >> 16) & 0xff) - 10), + 1 << ((size >> 8) & 0xff), + 1 << (size & 0xff), + 1 << ((size >> 24) & 0xff)); + + return lima_l2_cache_hw_init(ip); +} + +void lima_l2_cache_fini(struct lima_ip *ip) +{ + +} diff --git a/drivers/gpu/drm/lima/lima_l2_cache.h b/drivers/gpu/drm/lima/lima_l2_cache.h new file mode 100644 index 000000000..1aeeefd53 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_l2_cache.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_L2_CACHE_H__ +#define __LIMA_L2_CACHE_H__ + +struct lima_ip; + +int lima_l2_cache_resume(struct lima_ip *ip); +void lima_l2_cache_suspend(struct lima_ip *ip); +int lima_l2_cache_init(struct lima_ip *ip); +void lima_l2_cache_fini(struct lima_ip *ip); + +int lima_l2_cache_flush(struct lima_ip *ip); + +#endif diff --git a/drivers/gpu/drm/lima/lima_mmu.c b/drivers/gpu/drm/lima/lima_mmu.c new file mode 100644 index 000000000..6611e2836 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_mmu.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/device.h> + +#include "lima_device.h" +#include "lima_mmu.h" +#include "lima_vm.h" +#include "lima_regs.h" + +#define mmu_write(reg, data) writel(data, ip->iomem + reg) +#define mmu_read(reg) readl(ip->iomem + reg) + +#define lima_mmu_send_command(cmd, addr, val, cond) \ +({ \ + int __ret; \ + \ + mmu_write(LIMA_MMU_COMMAND, cmd); \ + __ret = readl_poll_timeout(ip->iomem + (addr), val, \ + cond, 0, 100); \ + if (__ret) \ + dev_err(dev->dev, \ + "%s command %x timeout\n", \ + lima_ip_name(ip), cmd); \ + __ret; \ +}) + +static irqreturn_t lima_mmu_irq_handler(int irq, void *data) +{ + struct lima_ip *ip = data; + struct lima_device *dev = ip->dev; + u32 status = mmu_read(LIMA_MMU_INT_STATUS); + struct lima_sched_pipe *pipe; + + /* for shared irq case */ + if (!status) + return IRQ_NONE; + + if (status & LIMA_MMU_INT_PAGE_FAULT) { + u32 fault = mmu_read(LIMA_MMU_PAGE_FAULT_ADDR); + + dev_err(dev->dev, "%s page fault at 0x%x from bus id %d of type %s\n", + lima_ip_name(ip), fault, LIMA_MMU_STATUS_BUS_ID(status), + status & LIMA_MMU_STATUS_PAGE_FAULT_IS_WRITE ? "write" : "read"); + } + + if (status & LIMA_MMU_INT_READ_BUS_ERROR) + dev_err(dev->dev, "%s irq bus error\n", lima_ip_name(ip)); + + /* mask all interrupts before resume */ + mmu_write(LIMA_MMU_INT_MASK, 0); + mmu_write(LIMA_MMU_INT_CLEAR, status); + + pipe = dev->pipe + (ip->id == lima_ip_gpmmu ? lima_pipe_gp : lima_pipe_pp); + lima_sched_pipe_mmu_error(pipe); + + return IRQ_HANDLED; +} + +static int lima_mmu_hw_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + u32 v; + + mmu_write(LIMA_MMU_COMMAND, LIMA_MMU_COMMAND_HARD_RESET); + err = lima_mmu_send_command(LIMA_MMU_COMMAND_HARD_RESET, + LIMA_MMU_DTE_ADDR, v, v == 0); + if (err) + return err; + + mmu_write(LIMA_MMU_INT_MASK, + LIMA_MMU_INT_PAGE_FAULT | LIMA_MMU_INT_READ_BUS_ERROR); + mmu_write(LIMA_MMU_DTE_ADDR, dev->empty_vm->pd.dma); + return lima_mmu_send_command(LIMA_MMU_COMMAND_ENABLE_PAGING, + LIMA_MMU_STATUS, v, + v & LIMA_MMU_STATUS_PAGING_ENABLED); +} + +int lima_mmu_resume(struct lima_ip *ip) +{ + if (ip->id == lima_ip_ppmmu_bcast) + return 0; + + return lima_mmu_hw_init(ip); +} + +void lima_mmu_suspend(struct lima_ip *ip) +{ + +} + +int lima_mmu_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + + if (ip->id == lima_ip_ppmmu_bcast) + return 0; + + mmu_write(LIMA_MMU_DTE_ADDR, 0xCAFEBABE); + if (mmu_read(LIMA_MMU_DTE_ADDR) != 0xCAFEB000) { + dev_err(dev->dev, "%s dte write test fail\n", lima_ip_name(ip)); + return -EIO; + } + + err = devm_request_irq(dev->dev, ip->irq, lima_mmu_irq_handler, + IRQF_SHARED, lima_ip_name(ip), ip); + if (err) { + dev_err(dev->dev, "%s fail to request irq\n", lima_ip_name(ip)); + return err; + } + + return lima_mmu_hw_init(ip); +} + +void lima_mmu_fini(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + + if (ip->id == lima_ip_ppmmu_bcast) + return; + + devm_free_irq(dev->dev, ip->irq, ip); +} + +void lima_mmu_flush_tlb(struct lima_ip *ip) +{ + mmu_write(LIMA_MMU_COMMAND, LIMA_MMU_COMMAND_ZAP_CACHE); +} + +void lima_mmu_switch_vm(struct lima_ip *ip, struct lima_vm *vm) +{ + struct lima_device *dev = ip->dev; + u32 v; + + lima_mmu_send_command(LIMA_MMU_COMMAND_ENABLE_STALL, + LIMA_MMU_STATUS, v, + v & LIMA_MMU_STATUS_STALL_ACTIVE); + + mmu_write(LIMA_MMU_DTE_ADDR, vm->pd.dma); + + /* flush the TLB */ + mmu_write(LIMA_MMU_COMMAND, LIMA_MMU_COMMAND_ZAP_CACHE); + + lima_mmu_send_command(LIMA_MMU_COMMAND_DISABLE_STALL, + LIMA_MMU_STATUS, v, + !(v & LIMA_MMU_STATUS_STALL_ACTIVE)); +} + +void lima_mmu_page_fault_resume(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + u32 status = mmu_read(LIMA_MMU_STATUS); + u32 v; + + if (status & LIMA_MMU_STATUS_PAGE_FAULT_ACTIVE) { + dev_info(dev->dev, "%s resume\n", lima_ip_name(ip)); + + mmu_write(LIMA_MMU_INT_MASK, 0); + mmu_write(LIMA_MMU_DTE_ADDR, 0xCAFEBABE); + lima_mmu_send_command(LIMA_MMU_COMMAND_HARD_RESET, + LIMA_MMU_DTE_ADDR, v, v == 0); + mmu_write(LIMA_MMU_INT_MASK, LIMA_MMU_INT_PAGE_FAULT | LIMA_MMU_INT_READ_BUS_ERROR); + mmu_write(LIMA_MMU_DTE_ADDR, dev->empty_vm->pd.dma); + lima_mmu_send_command(LIMA_MMU_COMMAND_ENABLE_PAGING, + LIMA_MMU_STATUS, v, + v & LIMA_MMU_STATUS_PAGING_ENABLED); + } +} diff --git a/drivers/gpu/drm/lima/lima_mmu.h b/drivers/gpu/drm/lima/lima_mmu.h new file mode 100644 index 000000000..f0c97ac75 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_mmu.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_MMU_H__ +#define __LIMA_MMU_H__ + +struct lima_ip; +struct lima_vm; + +int lima_mmu_resume(struct lima_ip *ip); +void lima_mmu_suspend(struct lima_ip *ip); +int lima_mmu_init(struct lima_ip *ip); +void lima_mmu_fini(struct lima_ip *ip); + +void lima_mmu_flush_tlb(struct lima_ip *ip); +void lima_mmu_switch_vm(struct lima_ip *ip, struct lima_vm *vm); +void lima_mmu_page_fault_resume(struct lima_ip *ip); + +#endif diff --git a/drivers/gpu/drm/lima/lima_pmu.c b/drivers/gpu/drm/lima/lima_pmu.c new file mode 100644 index 000000000..113cb9b21 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_pmu.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/iopoll.h> +#include <linux/device.h> + +#include "lima_device.h" +#include "lima_pmu.h" +#include "lima_regs.h" + +#define pmu_write(reg, data) writel(data, ip->iomem + reg) +#define pmu_read(reg) readl(ip->iomem + reg) + +static int lima_pmu_wait_cmd(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + u32 v; + + err = readl_poll_timeout(ip->iomem + LIMA_PMU_INT_RAWSTAT, + v, v & LIMA_PMU_INT_CMD_MASK, + 100, 100000); + if (err) { + dev_err(dev->dev, "%s timeout wait pmu cmd\n", + lima_ip_name(ip)); + return err; + } + + pmu_write(LIMA_PMU_INT_CLEAR, LIMA_PMU_INT_CMD_MASK); + return 0; +} + +static u32 lima_pmu_get_ip_mask(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + u32 ret = 0; + int i; + + ret |= LIMA_PMU_POWER_GP0_MASK; + + if (dev->id == lima_gpu_mali400) { + ret |= LIMA_PMU_POWER_L2_MASK; + for (i = 0; i < 4; i++) { + if (dev->ip[lima_ip_pp0 + i].present) + ret |= LIMA_PMU_POWER_PP_MASK(i); + } + } else { + if (dev->ip[lima_ip_pp0].present) + ret |= LIMA450_PMU_POWER_PP0_MASK; + for (i = lima_ip_pp1; i <= lima_ip_pp3; i++) { + if (dev->ip[i].present) { + ret |= LIMA450_PMU_POWER_PP13_MASK; + break; + } + } + for (i = lima_ip_pp4; i <= lima_ip_pp7; i++) { + if (dev->ip[i].present) { + ret |= LIMA450_PMU_POWER_PP47_MASK; + break; + } + } + } + + return ret; +} + +static int lima_pmu_hw_init(struct lima_ip *ip) +{ + int err; + u32 stat; + + pmu_write(LIMA_PMU_INT_MASK, 0); + + /* If this value is too low, when in high GPU clk freq, + * GPU will be in unstable state. + */ + pmu_write(LIMA_PMU_SW_DELAY, 0xffff); + + /* status reg 1=off 0=on */ + stat = pmu_read(LIMA_PMU_STATUS); + + /* power up all ip */ + if (stat) { + pmu_write(LIMA_PMU_POWER_UP, stat); + err = lima_pmu_wait_cmd(ip); + if (err) + return err; + } + return 0; +} + +static void lima_pmu_hw_fini(struct lima_ip *ip) +{ + u32 stat; + + if (!ip->data.mask) + ip->data.mask = lima_pmu_get_ip_mask(ip); + + stat = ~pmu_read(LIMA_PMU_STATUS) & ip->data.mask; + if (stat) { + pmu_write(LIMA_PMU_POWER_DOWN, stat); + + /* Don't wait for interrupt on Mali400 if all domains are + * powered off because the HW won't generate an interrupt + * in this case. + */ + if (ip->dev->id == lima_gpu_mali400) + pmu_write(LIMA_PMU_INT_CLEAR, LIMA_PMU_INT_CMD_MASK); + else + lima_pmu_wait_cmd(ip); + } +} + +int lima_pmu_resume(struct lima_ip *ip) +{ + return lima_pmu_hw_init(ip); +} + +void lima_pmu_suspend(struct lima_ip *ip) +{ + lima_pmu_hw_fini(ip); +} + +int lima_pmu_init(struct lima_ip *ip) +{ + return lima_pmu_hw_init(ip); +} + +void lima_pmu_fini(struct lima_ip *ip) +{ + lima_pmu_hw_fini(ip); +} diff --git a/drivers/gpu/drm/lima/lima_pmu.h b/drivers/gpu/drm/lima/lima_pmu.h new file mode 100644 index 000000000..652dc7af3 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_pmu.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_PMU_H__ +#define __LIMA_PMU_H__ + +struct lima_ip; + +int lima_pmu_resume(struct lima_ip *ip); +void lima_pmu_suspend(struct lima_ip *ip); +int lima_pmu_init(struct lima_ip *ip); +void lima_pmu_fini(struct lima_ip *ip); + +#endif diff --git a/drivers/gpu/drm/lima/lima_pp.c b/drivers/gpu/drm/lima/lima_pp.c new file mode 100644 index 000000000..eaab4788d --- /dev/null +++ b/drivers/gpu/drm/lima/lima_pp.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/slab.h> + +#include <drm/lima_drm.h> + +#include "lima_device.h" +#include "lima_pp.h" +#include "lima_dlbu.h" +#include "lima_bcast.h" +#include "lima_vm.h" +#include "lima_regs.h" + +#define pp_write(reg, data) writel(data, ip->iomem + reg) +#define pp_read(reg) readl(ip->iomem + reg) + +static void lima_pp_handle_irq(struct lima_ip *ip, u32 state) +{ + struct lima_device *dev = ip->dev; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + + if (state & LIMA_PP_IRQ_MASK_ERROR) { + u32 status = pp_read(LIMA_PP_STATUS); + + dev_err(dev->dev, "%s error irq state=%x status=%x\n", + lima_ip_name(ip), state, status); + + pipe->error = true; + + /* mask all interrupts before hard reset */ + pp_write(LIMA_PP_INT_MASK, 0); + } + + pp_write(LIMA_PP_INT_CLEAR, state); +} + +static irqreturn_t lima_pp_irq_handler(int irq, void *data) +{ + struct lima_ip *ip = data; + struct lima_device *dev = ip->dev; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + u32 state = pp_read(LIMA_PP_INT_STATUS); + + /* for shared irq case */ + if (!state) + return IRQ_NONE; + + lima_pp_handle_irq(ip, state); + + if (atomic_dec_and_test(&pipe->task)) + lima_sched_pipe_task_done(pipe); + + return IRQ_HANDLED; +} + +static irqreturn_t lima_pp_bcast_irq_handler(int irq, void *data) +{ + int i; + irqreturn_t ret = IRQ_NONE; + struct lima_ip *pp_bcast = data; + struct lima_device *dev = pp_bcast->dev; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + struct drm_lima_m450_pp_frame *frame; + + /* for shared irq case */ + if (!pipe->current_task) + return IRQ_NONE; + + frame = pipe->current_task->frame; + + for (i = 0; i < frame->num_pp; i++) { + struct lima_ip *ip = pipe->processor[i]; + u32 status, state; + + if (pipe->done & (1 << i)) + continue; + + /* status read first in case int state change in the middle + * which may miss the interrupt handling + */ + status = pp_read(LIMA_PP_STATUS); + state = pp_read(LIMA_PP_INT_STATUS); + + if (state) { + lima_pp_handle_irq(ip, state); + ret = IRQ_HANDLED; + } else { + if (status & LIMA_PP_STATUS_RENDERING_ACTIVE) + continue; + } + + pipe->done |= (1 << i); + if (atomic_dec_and_test(&pipe->task)) + lima_sched_pipe_task_done(pipe); + } + + return ret; +} + +static void lima_pp_soft_reset_async(struct lima_ip *ip) +{ + if (ip->data.async_reset) + return; + + pp_write(LIMA_PP_INT_MASK, 0); + pp_write(LIMA_PP_INT_RAWSTAT, LIMA_PP_IRQ_MASK_ALL); + pp_write(LIMA_PP_CTRL, LIMA_PP_CTRL_SOFT_RESET); + ip->data.async_reset = true; +} + +static int lima_pp_soft_reset_poll(struct lima_ip *ip) +{ + return !(pp_read(LIMA_PP_STATUS) & LIMA_PP_STATUS_RENDERING_ACTIVE) && + pp_read(LIMA_PP_INT_RAWSTAT) == LIMA_PP_IRQ_RESET_COMPLETED; +} + +static int lima_pp_soft_reset_async_wait_one(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int ret; + + ret = lima_poll_timeout(ip, lima_pp_soft_reset_poll, 0, 100); + if (ret) { + dev_err(dev->dev, "%s reset time out\n", lima_ip_name(ip)); + return ret; + } + + pp_write(LIMA_PP_INT_CLEAR, LIMA_PP_IRQ_MASK_ALL); + pp_write(LIMA_PP_INT_MASK, LIMA_PP_IRQ_MASK_USED); + return 0; +} + +static int lima_pp_soft_reset_async_wait(struct lima_ip *ip) +{ + int i, err = 0; + + if (!ip->data.async_reset) + return 0; + + if (ip->id == lima_ip_pp_bcast) { + struct lima_device *dev = ip->dev; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + struct drm_lima_m450_pp_frame *frame = pipe->current_task->frame; + + for (i = 0; i < frame->num_pp; i++) + err |= lima_pp_soft_reset_async_wait_one(pipe->processor[i]); + } else + err = lima_pp_soft_reset_async_wait_one(ip); + + ip->data.async_reset = false; + return err; +} + +static void lima_pp_write_frame(struct lima_ip *ip, u32 *frame, u32 *wb) +{ + int i, j, n = 0; + + for (i = 0; i < LIMA_PP_FRAME_REG_NUM; i++) + writel(frame[i], ip->iomem + LIMA_PP_FRAME + i * 4); + + for (i = 0; i < 3; i++) { + for (j = 0; j < LIMA_PP_WB_REG_NUM; j++) + writel(wb[n++], ip->iomem + LIMA_PP_WB(i) + j * 4); + } +} + +static int lima_pp_bus_stop_poll(struct lima_ip *ip) +{ + return !!(pp_read(LIMA_PP_STATUS) & LIMA_PP_STATUS_BUS_STOPPED); +} + +static int lima_pp_hard_reset_poll(struct lima_ip *ip) +{ + pp_write(LIMA_PP_PERF_CNT_0_LIMIT, 0xC01A0000); + return pp_read(LIMA_PP_PERF_CNT_0_LIMIT) == 0xC01A0000; +} + +static int lima_pp_hard_reset(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int ret; + + pp_write(LIMA_PP_PERF_CNT_0_LIMIT, 0xC0FFE000); + pp_write(LIMA_PP_INT_MASK, 0); + + pp_write(LIMA_PP_CTRL, LIMA_PP_CTRL_STOP_BUS); + ret = lima_poll_timeout(ip, lima_pp_bus_stop_poll, 10, 100); + if (ret) { + dev_err(dev->dev, "%s bus stop timeout\n", lima_ip_name(ip)); + return ret; + } + + pp_write(LIMA_PP_CTRL, LIMA_PP_CTRL_FORCE_RESET); + ret = lima_poll_timeout(ip, lima_pp_hard_reset_poll, 10, 100); + if (ret) { + dev_err(dev->dev, "%s hard reset timeout\n", lima_ip_name(ip)); + return ret; + } + + pp_write(LIMA_PP_PERF_CNT_0_LIMIT, 0); + pp_write(LIMA_PP_INT_CLEAR, LIMA_PP_IRQ_MASK_ALL); + pp_write(LIMA_PP_INT_MASK, LIMA_PP_IRQ_MASK_USED); + + /* + * if there was an async soft reset queued, + * don't wait for it in the next job + */ + ip->data.async_reset = false; + + return 0; +} + +static void lima_pp_print_version(struct lima_ip *ip) +{ + u32 version, major, minor; + char *name; + + version = pp_read(LIMA_PP_VERSION); + major = (version >> 8) & 0xFF; + minor = version & 0xFF; + switch (version >> 16) { + case 0xC807: + name = "mali200"; + break; + case 0xCE07: + name = "mali300"; + break; + case 0xCD07: + name = "mali400"; + break; + case 0xCF07: + name = "mali450"; + break; + default: + name = "unknown"; + break; + } + dev_info(ip->dev->dev, "%s - %s version major %d minor %d\n", + lima_ip_name(ip), name, major, minor); +} + +static int lima_pp_hw_init(struct lima_ip *ip) +{ + ip->data.async_reset = false; + lima_pp_soft_reset_async(ip); + return lima_pp_soft_reset_async_wait(ip); +} + +int lima_pp_resume(struct lima_ip *ip) +{ + return lima_pp_hw_init(ip); +} + +void lima_pp_suspend(struct lima_ip *ip) +{ + +} + +int lima_pp_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + + lima_pp_print_version(ip); + + err = lima_pp_hw_init(ip); + if (err) + return err; + + err = devm_request_irq(dev->dev, ip->irq, lima_pp_irq_handler, + IRQF_SHARED, lima_ip_name(ip), ip); + if (err) { + dev_err(dev->dev, "%s fail to request irq\n", + lima_ip_name(ip)); + return err; + } + + dev->pp_version = pp_read(LIMA_PP_VERSION); + + return 0; +} + +void lima_pp_fini(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + + devm_free_irq(dev->dev, ip->irq, ip); +} + +int lima_pp_bcast_resume(struct lima_ip *ip) +{ + /* PP has been reset by individual PP resume */ + ip->data.async_reset = false; + return 0; +} + +void lima_pp_bcast_suspend(struct lima_ip *ip) +{ + +} + +int lima_pp_bcast_init(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + int err; + + err = devm_request_irq(dev->dev, ip->irq, lima_pp_bcast_irq_handler, + IRQF_SHARED, lima_ip_name(ip), ip); + if (err) { + dev_err(dev->dev, "%s fail to request irq\n", + lima_ip_name(ip)); + return err; + } + + return 0; +} + +void lima_pp_bcast_fini(struct lima_ip *ip) +{ + struct lima_device *dev = ip->dev; + + devm_free_irq(dev->dev, ip->irq, ip); +} + +static int lima_pp_task_validate(struct lima_sched_pipe *pipe, + struct lima_sched_task *task) +{ + u32 num_pp; + + if (pipe->bcast_processor) { + struct drm_lima_m450_pp_frame *f = task->frame; + + num_pp = f->num_pp; + + if (f->_pad) + return -EINVAL; + } else { + struct drm_lima_m400_pp_frame *f = task->frame; + + num_pp = f->num_pp; + } + + if (num_pp == 0 || num_pp > pipe->num_processor) + return -EINVAL; + + return 0; +} + +static void lima_pp_task_run(struct lima_sched_pipe *pipe, + struct lima_sched_task *task) +{ + if (pipe->bcast_processor) { + struct drm_lima_m450_pp_frame *frame = task->frame; + struct lima_device *dev = pipe->bcast_processor->dev; + struct lima_ip *ip = pipe->bcast_processor; + int i; + + pipe->done = 0; + atomic_set(&pipe->task, frame->num_pp); + + if (frame->use_dlbu) { + lima_dlbu_enable(dev, frame->num_pp); + + frame->frame[LIMA_PP_FRAME >> 2] = LIMA_VA_RESERVE_DLBU; + lima_dlbu_set_reg(dev->ip + lima_ip_dlbu, frame->dlbu_regs); + } else + lima_dlbu_disable(dev); + + lima_bcast_enable(dev, frame->num_pp); + + lima_pp_soft_reset_async_wait(ip); + + lima_pp_write_frame(ip, frame->frame, frame->wb); + + for (i = 0; i < frame->num_pp; i++) { + struct lima_ip *ip = pipe->processor[i]; + + pp_write(LIMA_PP_STACK, frame->fragment_stack_address[i]); + if (!frame->use_dlbu) + pp_write(LIMA_PP_FRAME, frame->plbu_array_address[i]); + } + + pp_write(LIMA_PP_CTRL, LIMA_PP_CTRL_START_RENDERING); + } else { + struct drm_lima_m400_pp_frame *frame = task->frame; + int i; + + atomic_set(&pipe->task, frame->num_pp); + + for (i = 0; i < frame->num_pp; i++) { + struct lima_ip *ip = pipe->processor[i]; + + frame->frame[LIMA_PP_FRAME >> 2] = + frame->plbu_array_address[i]; + frame->frame[LIMA_PP_STACK >> 2] = + frame->fragment_stack_address[i]; + + lima_pp_soft_reset_async_wait(ip); + + lima_pp_write_frame(ip, frame->frame, frame->wb); + + pp_write(LIMA_PP_CTRL, LIMA_PP_CTRL_START_RENDERING); + } + } +} + +static void lima_pp_task_fini(struct lima_sched_pipe *pipe) +{ + if (pipe->bcast_processor) + lima_pp_soft_reset_async(pipe->bcast_processor); + else { + int i; + + for (i = 0; i < pipe->num_processor; i++) + lima_pp_soft_reset_async(pipe->processor[i]); + } +} + +static void lima_pp_task_error(struct lima_sched_pipe *pipe) +{ + int i; + + for (i = 0; i < pipe->num_processor; i++) { + struct lima_ip *ip = pipe->processor[i]; + + dev_err(ip->dev->dev, "%s task error %d int_state=%x status=%x\n", + lima_ip_name(ip), i, pp_read(LIMA_PP_INT_STATUS), + pp_read(LIMA_PP_STATUS)); + + lima_pp_hard_reset(ip); + } + + if (pipe->bcast_processor) + lima_bcast_reset(pipe->bcast_processor); +} + +static void lima_pp_task_mmu_error(struct lima_sched_pipe *pipe) +{ + if (atomic_dec_and_test(&pipe->task)) + lima_sched_pipe_task_done(pipe); +} + +static void lima_pp_task_mask_irq(struct lima_sched_pipe *pipe) +{ + int i; + + for (i = 0; i < pipe->num_processor; i++) { + struct lima_ip *ip = pipe->processor[i]; + + pp_write(LIMA_PP_INT_MASK, 0); + } + + if (pipe->bcast_processor) + lima_bcast_mask_irq(pipe->bcast_processor); +} + +static struct kmem_cache *lima_pp_task_slab; +static int lima_pp_task_slab_refcnt; + +int lima_pp_pipe_init(struct lima_device *dev) +{ + int frame_size; + struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_pp; + + if (dev->id == lima_gpu_mali400) + frame_size = sizeof(struct drm_lima_m400_pp_frame); + else + frame_size = sizeof(struct drm_lima_m450_pp_frame); + + if (!lima_pp_task_slab) { + lima_pp_task_slab = kmem_cache_create_usercopy( + "lima_pp_task", sizeof(struct lima_sched_task) + frame_size, + 0, SLAB_HWCACHE_ALIGN, sizeof(struct lima_sched_task), + frame_size, NULL); + if (!lima_pp_task_slab) + return -ENOMEM; + } + lima_pp_task_slab_refcnt++; + + pipe->frame_size = frame_size; + pipe->task_slab = lima_pp_task_slab; + + pipe->task_validate = lima_pp_task_validate; + pipe->task_run = lima_pp_task_run; + pipe->task_fini = lima_pp_task_fini; + pipe->task_error = lima_pp_task_error; + pipe->task_mmu_error = lima_pp_task_mmu_error; + pipe->task_mask_irq = lima_pp_task_mask_irq; + + return 0; +} + +void lima_pp_pipe_fini(struct lima_device *dev) +{ + if (!--lima_pp_task_slab_refcnt) { + kmem_cache_destroy(lima_pp_task_slab); + lima_pp_task_slab = NULL; + } +} diff --git a/drivers/gpu/drm/lima/lima_pp.h b/drivers/gpu/drm/lima/lima_pp.h new file mode 100644 index 000000000..16ec96de1 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_pp.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_PP_H__ +#define __LIMA_PP_H__ + +struct lima_ip; +struct lima_device; + +int lima_pp_resume(struct lima_ip *ip); +void lima_pp_suspend(struct lima_ip *ip); +int lima_pp_init(struct lima_ip *ip); +void lima_pp_fini(struct lima_ip *ip); + +int lima_pp_bcast_resume(struct lima_ip *ip); +void lima_pp_bcast_suspend(struct lima_ip *ip); +int lima_pp_bcast_init(struct lima_ip *ip); +void lima_pp_bcast_fini(struct lima_ip *ip); + +int lima_pp_pipe_init(struct lima_device *dev); +void lima_pp_pipe_fini(struct lima_device *dev); + +#endif diff --git a/drivers/gpu/drm/lima/lima_regs.h b/drivers/gpu/drm/lima/lima_regs.h new file mode 100644 index 000000000..0124c90e0 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_regs.h @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright 2010-2017 ARM Limited. All rights reserved. + * Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> + */ + +#ifndef __LIMA_REGS_H__ +#define __LIMA_REGS_H__ + +/* This file's register definition is collected from the + * official ARM Mali Utgard GPU kernel driver source code + */ + +/* PMU regs */ +#define LIMA_PMU_POWER_UP 0x00 +#define LIMA_PMU_POWER_DOWN 0x04 +#define LIMA_PMU_POWER_GP0_MASK BIT(0) +#define LIMA_PMU_POWER_L2_MASK BIT(1) +#define LIMA_PMU_POWER_PP_MASK(i) BIT(2 + i) + +/* + * On Mali450 each block automatically starts up its corresponding L2 + * and the PPs are not fully independent controllable. + * Instead PP0, PP1-3 and PP4-7 can be turned on or off. + */ +#define LIMA450_PMU_POWER_PP0_MASK BIT(1) +#define LIMA450_PMU_POWER_PP13_MASK BIT(2) +#define LIMA450_PMU_POWER_PP47_MASK BIT(3) + +#define LIMA_PMU_STATUS 0x08 +#define LIMA_PMU_INT_MASK 0x0C +#define LIMA_PMU_INT_RAWSTAT 0x10 +#define LIMA_PMU_INT_CLEAR 0x18 +#define LIMA_PMU_INT_CMD_MASK BIT(0) +#define LIMA_PMU_SW_DELAY 0x1C + +/* L2 cache regs */ +#define LIMA_L2_CACHE_SIZE 0x0004 +#define LIMA_L2_CACHE_STATUS 0x0008 +#define LIMA_L2_CACHE_STATUS_COMMAND_BUSY BIT(0) +#define LIMA_L2_CACHE_STATUS_DATA_BUSY BIT(1) +#define LIMA_L2_CACHE_COMMAND 0x0010 +#define LIMA_L2_CACHE_COMMAND_CLEAR_ALL BIT(0) +#define LIMA_L2_CACHE_CLEAR_PAGE 0x0014 +#define LIMA_L2_CACHE_MAX_READS 0x0018 +#define LIMA_L2_CACHE_ENABLE 0x001C +#define LIMA_L2_CACHE_ENABLE_ACCESS BIT(0) +#define LIMA_L2_CACHE_ENABLE_READ_ALLOCATE BIT(1) +#define LIMA_L2_CACHE_PERFCNT_SRC0 0x0020 +#define LIMA_L2_CACHE_PERFCNT_VAL0 0x0024 +#define LIMA_L2_CACHE_PERFCNT_SRC1 0x0028 +#define LIMA_L2_CACHE_ERFCNT_VAL1 0x002C + +/* GP regs */ +#define LIMA_GP_VSCL_START_ADDR 0x00 +#define LIMA_GP_VSCL_END_ADDR 0x04 +#define LIMA_GP_PLBUCL_START_ADDR 0x08 +#define LIMA_GP_PLBUCL_END_ADDR 0x0c +#define LIMA_GP_PLBU_ALLOC_START_ADDR 0x10 +#define LIMA_GP_PLBU_ALLOC_END_ADDR 0x14 +#define LIMA_GP_CMD 0x20 +#define LIMA_GP_CMD_START_VS BIT(0) +#define LIMA_GP_CMD_START_PLBU BIT(1) +#define LIMA_GP_CMD_UPDATE_PLBU_ALLOC BIT(4) +#define LIMA_GP_CMD_RESET BIT(5) +#define LIMA_GP_CMD_FORCE_HANG BIT(6) +#define LIMA_GP_CMD_STOP_BUS BIT(9) +#define LIMA_GP_CMD_SOFT_RESET BIT(10) +#define LIMA_GP_INT_RAWSTAT 0x24 +#define LIMA_GP_INT_CLEAR 0x28 +#define LIMA_GP_INT_MASK 0x2C +#define LIMA_GP_INT_STAT 0x30 +#define LIMA_GP_IRQ_VS_END_CMD_LST BIT(0) +#define LIMA_GP_IRQ_PLBU_END_CMD_LST BIT(1) +#define LIMA_GP_IRQ_PLBU_OUT_OF_MEM BIT(2) +#define LIMA_GP_IRQ_VS_SEM_IRQ BIT(3) +#define LIMA_GP_IRQ_PLBU_SEM_IRQ BIT(4) +#define LIMA_GP_IRQ_HANG BIT(5) +#define LIMA_GP_IRQ_FORCE_HANG BIT(6) +#define LIMA_GP_IRQ_PERF_CNT_0_LIMIT BIT(7) +#define LIMA_GP_IRQ_PERF_CNT_1_LIMIT BIT(8) +#define LIMA_GP_IRQ_WRITE_BOUND_ERR BIT(9) +#define LIMA_GP_IRQ_SYNC_ERROR BIT(10) +#define LIMA_GP_IRQ_AXI_BUS_ERROR BIT(11) +#define LIMA_GP_IRQ_AXI_BUS_STOPPED BIT(12) +#define LIMA_GP_IRQ_VS_INVALID_CMD BIT(13) +#define LIMA_GP_IRQ_PLB_INVALID_CMD BIT(14) +#define LIMA_GP_IRQ_RESET_COMPLETED BIT(19) +#define LIMA_GP_IRQ_SEMAPHORE_UNDERFLOW BIT(20) +#define LIMA_GP_IRQ_SEMAPHORE_OVERFLOW BIT(21) +#define LIMA_GP_IRQ_PTR_ARRAY_OUT_OF_BOUNDS BIT(22) +#define LIMA_GP_WRITE_BOUND_LOW 0x34 +#define LIMA_GP_PERF_CNT_0_ENABLE 0x3C +#define LIMA_GP_PERF_CNT_1_ENABLE 0x40 +#define LIMA_GP_PERF_CNT_0_SRC 0x44 +#define LIMA_GP_PERF_CNT_1_SRC 0x48 +#define LIMA_GP_PERF_CNT_0_VALUE 0x4C +#define LIMA_GP_PERF_CNT_1_VALUE 0x50 +#define LIMA_GP_PERF_CNT_0_LIMIT 0x54 +#define LIMA_GP_STATUS 0x68 +#define LIMA_GP_STATUS_VS_ACTIVE BIT(1) +#define LIMA_GP_STATUS_BUS_STOPPED BIT(2) +#define LIMA_GP_STATUS_PLBU_ACTIVE BIT(3) +#define LIMA_GP_STATUS_BUS_ERROR BIT(6) +#define LIMA_GP_STATUS_WRITE_BOUND_ERR BIT(8) +#define LIMA_GP_VERSION 0x6C +#define LIMA_GP_VSCL_START_ADDR_READ 0x80 +#define LIMA_GP_PLBCL_START_ADDR_READ 0x84 +#define LIMA_GP_CONTR_AXI_BUS_ERROR_STAT 0x94 + +#define LIMA_GP_IRQ_MASK_ALL \ + ( \ + LIMA_GP_IRQ_VS_END_CMD_LST | \ + LIMA_GP_IRQ_PLBU_END_CMD_LST | \ + LIMA_GP_IRQ_PLBU_OUT_OF_MEM | \ + LIMA_GP_IRQ_VS_SEM_IRQ | \ + LIMA_GP_IRQ_PLBU_SEM_IRQ | \ + LIMA_GP_IRQ_HANG | \ + LIMA_GP_IRQ_FORCE_HANG | \ + LIMA_GP_IRQ_PERF_CNT_0_LIMIT | \ + LIMA_GP_IRQ_PERF_CNT_1_LIMIT | \ + LIMA_GP_IRQ_WRITE_BOUND_ERR | \ + LIMA_GP_IRQ_SYNC_ERROR | \ + LIMA_GP_IRQ_AXI_BUS_ERROR | \ + LIMA_GP_IRQ_AXI_BUS_STOPPED | \ + LIMA_GP_IRQ_VS_INVALID_CMD | \ + LIMA_GP_IRQ_PLB_INVALID_CMD | \ + LIMA_GP_IRQ_RESET_COMPLETED | \ + LIMA_GP_IRQ_SEMAPHORE_UNDERFLOW | \ + LIMA_GP_IRQ_SEMAPHORE_OVERFLOW | \ + LIMA_GP_IRQ_PTR_ARRAY_OUT_OF_BOUNDS) + +#define LIMA_GP_IRQ_MASK_ERROR \ + ( \ + LIMA_GP_IRQ_PLBU_OUT_OF_MEM | \ + LIMA_GP_IRQ_FORCE_HANG | \ + LIMA_GP_IRQ_WRITE_BOUND_ERR | \ + LIMA_GP_IRQ_SYNC_ERROR | \ + LIMA_GP_IRQ_AXI_BUS_ERROR | \ + LIMA_GP_IRQ_VS_INVALID_CMD | \ + LIMA_GP_IRQ_PLB_INVALID_CMD | \ + LIMA_GP_IRQ_SEMAPHORE_UNDERFLOW | \ + LIMA_GP_IRQ_SEMAPHORE_OVERFLOW | \ + LIMA_GP_IRQ_PTR_ARRAY_OUT_OF_BOUNDS) + +#define LIMA_GP_IRQ_MASK_USED \ + ( \ + LIMA_GP_IRQ_VS_END_CMD_LST | \ + LIMA_GP_IRQ_PLBU_END_CMD_LST | \ + LIMA_GP_IRQ_MASK_ERROR) + +/* PP regs */ +#define LIMA_PP_FRAME 0x0000 +#define LIMA_PP_RSW 0x0004 +#define LIMA_PP_STACK 0x0030 +#define LIMA_PP_STACK_SIZE 0x0034 +#define LIMA_PP_ORIGIN_OFFSET_X 0x0040 +#define LIMA_PP_WB(i) (0x0100 * (i + 1)) +#define LIMA_PP_WB_SOURCE_SELECT 0x0000 +#define LIMA_PP_WB_SOURCE_ADDR 0x0004 + +#define LIMA_PP_VERSION 0x1000 +#define LIMA_PP_CURRENT_REND_LIST_ADDR 0x1004 +#define LIMA_PP_STATUS 0x1008 +#define LIMA_PP_STATUS_RENDERING_ACTIVE BIT(0) +#define LIMA_PP_STATUS_BUS_STOPPED BIT(4) +#define LIMA_PP_CTRL 0x100c +#define LIMA_PP_CTRL_STOP_BUS BIT(0) +#define LIMA_PP_CTRL_FLUSH_CACHES BIT(3) +#define LIMA_PP_CTRL_FORCE_RESET BIT(5) +#define LIMA_PP_CTRL_START_RENDERING BIT(6) +#define LIMA_PP_CTRL_SOFT_RESET BIT(7) +#define LIMA_PP_INT_RAWSTAT 0x1020 +#define LIMA_PP_INT_CLEAR 0x1024 +#define LIMA_PP_INT_MASK 0x1028 +#define LIMA_PP_INT_STATUS 0x102c +#define LIMA_PP_IRQ_END_OF_FRAME BIT(0) +#define LIMA_PP_IRQ_END_OF_TILE BIT(1) +#define LIMA_PP_IRQ_HANG BIT(2) +#define LIMA_PP_IRQ_FORCE_HANG BIT(3) +#define LIMA_PP_IRQ_BUS_ERROR BIT(4) +#define LIMA_PP_IRQ_BUS_STOP BIT(5) +#define LIMA_PP_IRQ_CNT_0_LIMIT BIT(6) +#define LIMA_PP_IRQ_CNT_1_LIMIT BIT(7) +#define LIMA_PP_IRQ_WRITE_BOUNDARY_ERROR BIT(8) +#define LIMA_PP_IRQ_INVALID_PLIST_COMMAND BIT(9) +#define LIMA_PP_IRQ_CALL_STACK_UNDERFLOW BIT(10) +#define LIMA_PP_IRQ_CALL_STACK_OVERFLOW BIT(11) +#define LIMA_PP_IRQ_RESET_COMPLETED BIT(12) +#define LIMA_PP_WRITE_BOUNDARY_LOW 0x1044 +#define LIMA_PP_BUS_ERROR_STATUS 0x1050 +#define LIMA_PP_PERF_CNT_0_ENABLE 0x1080 +#define LIMA_PP_PERF_CNT_0_SRC 0x1084 +#define LIMA_PP_PERF_CNT_0_LIMIT 0x1088 +#define LIMA_PP_PERF_CNT_0_VALUE 0x108c +#define LIMA_PP_PERF_CNT_1_ENABLE 0x10a0 +#define LIMA_PP_PERF_CNT_1_SRC 0x10a4 +#define LIMA_PP_PERF_CNT_1_LIMIT 0x10a8 +#define LIMA_PP_PERF_CNT_1_VALUE 0x10ac +#define LIMA_PP_PERFMON_CONTR 0x10b0 +#define LIMA_PP_PERFMON_BASE 0x10b4 + +#define LIMA_PP_IRQ_MASK_ALL \ + ( \ + LIMA_PP_IRQ_END_OF_FRAME | \ + LIMA_PP_IRQ_END_OF_TILE | \ + LIMA_PP_IRQ_HANG | \ + LIMA_PP_IRQ_FORCE_HANG | \ + LIMA_PP_IRQ_BUS_ERROR | \ + LIMA_PP_IRQ_BUS_STOP | \ + LIMA_PP_IRQ_CNT_0_LIMIT | \ + LIMA_PP_IRQ_CNT_1_LIMIT | \ + LIMA_PP_IRQ_WRITE_BOUNDARY_ERROR | \ + LIMA_PP_IRQ_INVALID_PLIST_COMMAND | \ + LIMA_PP_IRQ_CALL_STACK_UNDERFLOW | \ + LIMA_PP_IRQ_CALL_STACK_OVERFLOW | \ + LIMA_PP_IRQ_RESET_COMPLETED) + +#define LIMA_PP_IRQ_MASK_ERROR \ + ( \ + LIMA_PP_IRQ_FORCE_HANG | \ + LIMA_PP_IRQ_BUS_ERROR | \ + LIMA_PP_IRQ_WRITE_BOUNDARY_ERROR | \ + LIMA_PP_IRQ_INVALID_PLIST_COMMAND | \ + LIMA_PP_IRQ_CALL_STACK_UNDERFLOW | \ + LIMA_PP_IRQ_CALL_STACK_OVERFLOW) + +#define LIMA_PP_IRQ_MASK_USED \ + ( \ + LIMA_PP_IRQ_END_OF_FRAME | \ + LIMA_PP_IRQ_MASK_ERROR) + +/* MMU regs */ +#define LIMA_MMU_DTE_ADDR 0x0000 +#define LIMA_MMU_STATUS 0x0004 +#define LIMA_MMU_STATUS_PAGING_ENABLED BIT(0) +#define LIMA_MMU_STATUS_PAGE_FAULT_ACTIVE BIT(1) +#define LIMA_MMU_STATUS_STALL_ACTIVE BIT(2) +#define LIMA_MMU_STATUS_IDLE BIT(3) +#define LIMA_MMU_STATUS_REPLAY_BUFFER_EMPTY BIT(4) +#define LIMA_MMU_STATUS_PAGE_FAULT_IS_WRITE BIT(5) +#define LIMA_MMU_STATUS_BUS_ID(x) ((x >> 6) & 0x1F) +#define LIMA_MMU_STATUS_STALL_NOT_ACTIVE BIT(31) +#define LIMA_MMU_COMMAND 0x0008 +#define LIMA_MMU_COMMAND_ENABLE_PAGING 0x00 +#define LIMA_MMU_COMMAND_DISABLE_PAGING 0x01 +#define LIMA_MMU_COMMAND_ENABLE_STALL 0x02 +#define LIMA_MMU_COMMAND_DISABLE_STALL 0x03 +#define LIMA_MMU_COMMAND_ZAP_CACHE 0x04 +#define LIMA_MMU_COMMAND_PAGE_FAULT_DONE 0x05 +#define LIMA_MMU_COMMAND_HARD_RESET 0x06 +#define LIMA_MMU_PAGE_FAULT_ADDR 0x000C +#define LIMA_MMU_ZAP_ONE_LINE 0x0010 +#define LIMA_MMU_INT_RAWSTAT 0x0014 +#define LIMA_MMU_INT_CLEAR 0x0018 +#define LIMA_MMU_INT_MASK 0x001C +#define LIMA_MMU_INT_PAGE_FAULT BIT(0) +#define LIMA_MMU_INT_READ_BUS_ERROR BIT(1) +#define LIMA_MMU_INT_STATUS 0x0020 + +#define LIMA_VM_FLAG_PRESENT BIT(0) +#define LIMA_VM_FLAG_READ_PERMISSION BIT(1) +#define LIMA_VM_FLAG_WRITE_PERMISSION BIT(2) +#define LIMA_VM_FLAG_OVERRIDE_CACHE BIT(3) +#define LIMA_VM_FLAG_WRITE_CACHEABLE BIT(4) +#define LIMA_VM_FLAG_WRITE_ALLOCATE BIT(5) +#define LIMA_VM_FLAG_WRITE_BUFFERABLE BIT(6) +#define LIMA_VM_FLAG_READ_CACHEABLE BIT(7) +#define LIMA_VM_FLAG_READ_ALLOCATE BIT(8) +#define LIMA_VM_FLAG_MASK 0x1FF + +#define LIMA_VM_FLAGS_CACHE ( \ + LIMA_VM_FLAG_PRESENT | \ + LIMA_VM_FLAG_READ_PERMISSION | \ + LIMA_VM_FLAG_WRITE_PERMISSION | \ + LIMA_VM_FLAG_OVERRIDE_CACHE | \ + LIMA_VM_FLAG_WRITE_CACHEABLE | \ + LIMA_VM_FLAG_WRITE_BUFFERABLE | \ + LIMA_VM_FLAG_READ_CACHEABLE | \ + LIMA_VM_FLAG_READ_ALLOCATE) + +#define LIMA_VM_FLAGS_UNCACHE ( \ + LIMA_VM_FLAG_PRESENT | \ + LIMA_VM_FLAG_READ_PERMISSION | \ + LIMA_VM_FLAG_WRITE_PERMISSION) + +/* DLBU regs */ +#define LIMA_DLBU_MASTER_TLLIST_PHYS_ADDR 0x0000 +#define LIMA_DLBU_MASTER_TLLIST_VADDR 0x0004 +#define LIMA_DLBU_TLLIST_VBASEADDR 0x0008 +#define LIMA_DLBU_FB_DIM 0x000C +#define LIMA_DLBU_TLLIST_CONF 0x0010 +#define LIMA_DLBU_START_TILE_POS 0x0014 +#define LIMA_DLBU_PP_ENABLE_MASK 0x0018 + +/* BCAST regs */ +#define LIMA_BCAST_BROADCAST_MASK 0x0 +#define LIMA_BCAST_INTERRUPT_MASK 0x4 + +#endif diff --git a/drivers/gpu/drm/lima/lima_sched.c b/drivers/gpu/drm/lima/lima_sched.c new file mode 100644 index 000000000..9a1e6b9ec --- /dev/null +++ b/drivers/gpu/drm/lima/lima_sched.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/hardirq.h> +#include <linux/iosys-map.h> +#include <linux/kthread.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_print.h> + +#include "lima_devfreq.h" +#include "lima_drv.h" +#include "lima_sched.h" +#include "lima_vm.h" +#include "lima_mmu.h" +#include "lima_l2_cache.h" +#include "lima_gem.h" +#include "lima_trace.h" + +struct lima_fence { + struct dma_fence base; + struct lima_sched_pipe *pipe; +}; + +static struct kmem_cache *lima_fence_slab; +static int lima_fence_slab_refcnt; + +int lima_sched_slab_init(void) +{ + if (!lima_fence_slab) { + lima_fence_slab = kmem_cache_create( + "lima_fence", sizeof(struct lima_fence), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!lima_fence_slab) + return -ENOMEM; + } + + lima_fence_slab_refcnt++; + return 0; +} + +void lima_sched_slab_fini(void) +{ + if (!--lima_fence_slab_refcnt) { + kmem_cache_destroy(lima_fence_slab); + lima_fence_slab = NULL; + } +} + +static inline struct lima_fence *to_lima_fence(struct dma_fence *fence) +{ + return container_of(fence, struct lima_fence, base); +} + +static const char *lima_fence_get_driver_name(struct dma_fence *fence) +{ + return "lima"; +} + +static const char *lima_fence_get_timeline_name(struct dma_fence *fence) +{ + struct lima_fence *f = to_lima_fence(fence); + + return f->pipe->base.name; +} + +static void lima_fence_release_rcu(struct rcu_head *rcu) +{ + struct dma_fence *f = container_of(rcu, struct dma_fence, rcu); + struct lima_fence *fence = to_lima_fence(f); + + kmem_cache_free(lima_fence_slab, fence); +} + +static void lima_fence_release(struct dma_fence *fence) +{ + struct lima_fence *f = to_lima_fence(fence); + + call_rcu(&f->base.rcu, lima_fence_release_rcu); +} + +static const struct dma_fence_ops lima_fence_ops = { + .get_driver_name = lima_fence_get_driver_name, + .get_timeline_name = lima_fence_get_timeline_name, + .release = lima_fence_release, +}; + +static struct lima_fence *lima_fence_create(struct lima_sched_pipe *pipe) +{ + struct lima_fence *fence; + + fence = kmem_cache_zalloc(lima_fence_slab, GFP_KERNEL); + if (!fence) + return NULL; + + fence->pipe = pipe; + dma_fence_init(&fence->base, &lima_fence_ops, &pipe->fence_lock, + pipe->fence_context, ++pipe->fence_seqno); + + return fence; +} + +static inline struct lima_sched_task *to_lima_task(struct drm_sched_job *job) +{ + return container_of(job, struct lima_sched_task, base); +} + +static inline struct lima_sched_pipe *to_lima_pipe(struct drm_gpu_scheduler *sched) +{ + return container_of(sched, struct lima_sched_pipe, base); +} + +int lima_sched_task_init(struct lima_sched_task *task, + struct lima_sched_context *context, + struct lima_bo **bos, int num_bos, + struct lima_vm *vm, + u64 drm_client_id) +{ + int err, i; + + task->bos = kmemdup(bos, sizeof(*bos) * num_bos, GFP_KERNEL); + if (!task->bos) + return -ENOMEM; + + for (i = 0; i < num_bos; i++) + drm_gem_object_get(&bos[i]->base.base); + + err = drm_sched_job_init(&task->base, &context->base, 1, vm, + drm_client_id); + if (err) { + kfree(task->bos); + return err; + } + + drm_sched_job_arm(&task->base); + + task->num_bos = num_bos; + task->vm = lima_vm_get(vm); + + return 0; +} + +void lima_sched_task_fini(struct lima_sched_task *task) +{ + int i; + + drm_sched_job_cleanup(&task->base); + + if (task->bos) { + for (i = 0; i < task->num_bos; i++) + drm_gem_object_put(&task->bos[i]->base.base); + kfree(task->bos); + } + + lima_vm_put(task->vm); +} + +int lima_sched_context_init(struct lima_sched_pipe *pipe, + struct lima_sched_context *context) +{ + struct drm_gpu_scheduler *sched = &pipe->base; + + return drm_sched_entity_init(&context->base, DRM_SCHED_PRIORITY_NORMAL, + &sched, 1, NULL); +} + +void lima_sched_context_fini(struct lima_sched_pipe *pipe, + struct lima_sched_context *context) +{ + drm_sched_entity_destroy(&context->base); +} + +struct dma_fence *lima_sched_context_queue_task(struct lima_sched_task *task) +{ + struct dma_fence *fence = dma_fence_get(&task->base.s_fence->finished); + + trace_lima_task_submit(task); + drm_sched_entity_push_job(&task->base); + return fence; +} + +static int lima_pm_busy(struct lima_device *ldev) +{ + int ret; + + /* resume GPU if it has been suspended by runtime PM */ + ret = pm_runtime_resume_and_get(ldev->dev); + if (ret < 0) + return ret; + + lima_devfreq_record_busy(&ldev->devfreq); + return 0; +} + +static void lima_pm_idle(struct lima_device *ldev) +{ + lima_devfreq_record_idle(&ldev->devfreq); + + /* GPU can do auto runtime suspend */ + pm_runtime_mark_last_busy(ldev->dev); + pm_runtime_put_autosuspend(ldev->dev); +} + +static struct dma_fence *lima_sched_run_job(struct drm_sched_job *job) +{ + struct lima_sched_task *task = to_lima_task(job); + struct lima_sched_pipe *pipe = to_lima_pipe(job->sched); + struct lima_device *ldev = pipe->ldev; + struct lima_fence *fence; + int i, err; + + /* after GPU reset */ + if (job->s_fence->finished.error < 0) + return NULL; + + fence = lima_fence_create(pipe); + if (!fence) + return NULL; + + err = lima_pm_busy(ldev); + if (err < 0) { + dma_fence_put(&fence->base); + return NULL; + } + + task->fence = &fence->base; + + /* for caller usage of the fence, otherwise irq handler + * may consume the fence before caller use it + */ + dma_fence_get(task->fence); + + pipe->current_task = task; + + /* this is needed for MMU to work correctly, otherwise GP/PP + * will hang or page fault for unknown reason after running for + * a while. + * + * Need to investigate: + * 1. is it related to TLB + * 2. how much performance will be affected by L2 cache flush + * 3. can we reduce the calling of this function because all + * GP/PP use the same L2 cache on mali400 + * + * TODO: + * 1. move this to task fini to save some wait time? + * 2. when GP/PP use different l2 cache, need PP wait GP l2 + * cache flush? + */ + for (i = 0; i < pipe->num_l2_cache; i++) + lima_l2_cache_flush(pipe->l2_cache[i]); + + lima_vm_put(pipe->current_vm); + pipe->current_vm = lima_vm_get(task->vm); + + if (pipe->bcast_mmu) + lima_mmu_switch_vm(pipe->bcast_mmu, pipe->current_vm); + else { + for (i = 0; i < pipe->num_mmu; i++) + lima_mmu_switch_vm(pipe->mmu[i], pipe->current_vm); + } + + trace_lima_task_run(task); + + pipe->error = false; + pipe->task_run(pipe, task); + + return task->fence; +} + +static void lima_sched_build_error_task_list(struct lima_sched_task *task) +{ + struct lima_sched_error_task *et; + struct lima_sched_pipe *pipe = to_lima_pipe(task->base.sched); + struct lima_ip *ip = pipe->processor[0]; + int pipe_id = ip->id == lima_ip_gp ? lima_pipe_gp : lima_pipe_pp; + struct lima_device *dev = ip->dev; + struct lima_sched_context *sched_ctx = + container_of(task->base.entity, + struct lima_sched_context, base); + struct lima_ctx *ctx = + container_of(sched_ctx, struct lima_ctx, context[pipe_id]); + struct lima_dump_task *dt; + struct lima_dump_chunk *chunk; + struct lima_dump_chunk_pid *pid_chunk; + struct lima_dump_chunk_buffer *buffer_chunk; + u32 size, task_size, mem_size; + int i; + struct iosys_map map; + int ret; + + mutex_lock(&dev->error_task_list_lock); + + if (dev->dump.num_tasks >= lima_max_error_tasks) { + dev_info(dev->dev, "fail to save task state from %s pid %d: " + "error task list is full\n", ctx->pname, ctx->pid); + goto out; + } + + /* frame chunk */ + size = sizeof(struct lima_dump_chunk) + pipe->frame_size; + /* process name chunk */ + size += sizeof(struct lima_dump_chunk) + sizeof(ctx->pname); + /* pid chunk */ + size += sizeof(struct lima_dump_chunk); + /* buffer chunks */ + for (i = 0; i < task->num_bos; i++) { + struct lima_bo *bo = task->bos[i]; + + size += sizeof(struct lima_dump_chunk); + size += bo->heap_size ? bo->heap_size : lima_bo_size(bo); + } + + task_size = size + sizeof(struct lima_dump_task); + mem_size = task_size + sizeof(*et); + et = kvmalloc(mem_size, GFP_KERNEL); + if (!et) { + dev_err(dev->dev, "fail to alloc task dump buffer of size %x\n", + mem_size); + goto out; + } + + et->data = et + 1; + et->size = task_size; + + dt = et->data; + memset(dt, 0, sizeof(*dt)); + dt->id = pipe_id; + dt->size = size; + + chunk = (struct lima_dump_chunk *)(dt + 1); + memset(chunk, 0, sizeof(*chunk)); + chunk->id = LIMA_DUMP_CHUNK_FRAME; + chunk->size = pipe->frame_size; + memcpy(chunk + 1, task->frame, pipe->frame_size); + dt->num_chunks++; + + chunk = (void *)(chunk + 1) + chunk->size; + memset(chunk, 0, sizeof(*chunk)); + chunk->id = LIMA_DUMP_CHUNK_PROCESS_NAME; + chunk->size = sizeof(ctx->pname); + memcpy(chunk + 1, ctx->pname, sizeof(ctx->pname)); + dt->num_chunks++; + + pid_chunk = (void *)(chunk + 1) + chunk->size; + memset(pid_chunk, 0, sizeof(*pid_chunk)); + pid_chunk->id = LIMA_DUMP_CHUNK_PROCESS_ID; + pid_chunk->pid = ctx->pid; + dt->num_chunks++; + + buffer_chunk = (void *)(pid_chunk + 1) + pid_chunk->size; + for (i = 0; i < task->num_bos; i++) { + struct lima_bo *bo = task->bos[i]; + void *data; + + memset(buffer_chunk, 0, sizeof(*buffer_chunk)); + buffer_chunk->id = LIMA_DUMP_CHUNK_BUFFER; + buffer_chunk->va = lima_vm_get_va(task->vm, bo); + + if (bo->heap_size) { + buffer_chunk->size = bo->heap_size; + + data = vmap(bo->base.pages, bo->heap_size >> PAGE_SHIFT, + VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + if (!data) { + kvfree(et); + goto out; + } + + memcpy(buffer_chunk + 1, data, buffer_chunk->size); + + vunmap(data); + } else { + buffer_chunk->size = lima_bo_size(bo); + + ret = drm_gem_vmap(&bo->base.base, &map); + if (ret) { + kvfree(et); + goto out; + } + + memcpy(buffer_chunk + 1, map.vaddr, buffer_chunk->size); + + drm_gem_vunmap(&bo->base.base, &map); + } + + buffer_chunk = (void *)(buffer_chunk + 1) + buffer_chunk->size; + dt->num_chunks++; + } + + list_add(&et->list, &dev->error_task_list); + dev->dump.size += et->size; + dev->dump.num_tasks++; + + dev_info(dev->dev, "save error task state success\n"); + +out: + mutex_unlock(&dev->error_task_list_lock); +} + +static enum drm_gpu_sched_stat lima_sched_timedout_job(struct drm_sched_job *job) +{ + struct lima_sched_pipe *pipe = to_lima_pipe(job->sched); + struct lima_sched_task *task = to_lima_task(job); + struct lima_device *ldev = pipe->ldev; + struct lima_ip *ip = pipe->processor[0]; + int i; + + /* + * If the GPU managed to complete this jobs fence, the timeout is + * spurious. Bail out. + */ + if (dma_fence_is_signaled(task->fence)) { + DRM_WARN("%s spurious timeout\n", lima_ip_name(ip)); + return DRM_GPU_SCHED_STAT_RESET; + } + + /* + * Lima IRQ handler may take a long time to process an interrupt + * if there is another IRQ handler hogging the processing. + * In order to catch such cases and not report spurious Lima job + * timeouts, synchronize the IRQ handler and re-check the fence + * status. + */ + for (i = 0; i < pipe->num_processor; i++) + synchronize_irq(pipe->processor[i]->irq); + if (pipe->bcast_processor) + synchronize_irq(pipe->bcast_processor->irq); + + if (dma_fence_is_signaled(task->fence)) { + DRM_WARN("%s unexpectedly high interrupt latency\n", lima_ip_name(ip)); + return DRM_GPU_SCHED_STAT_RESET; + } + + /* + * The task might still finish while this timeout handler runs. + * To prevent a race condition on its completion, mask all irqs + * on the running core until the next hard reset completes. + */ + pipe->task_mask_irq(pipe); + + if (!pipe->error) + DRM_ERROR("%s job timeout\n", lima_ip_name(ip)); + + drm_sched_stop(&pipe->base, &task->base); + + drm_sched_increase_karma(&task->base); + + if (lima_max_error_tasks) + lima_sched_build_error_task_list(task); + + pipe->task_error(pipe); + + if (pipe->bcast_mmu) + lima_mmu_page_fault_resume(pipe->bcast_mmu); + else { + for (i = 0; i < pipe->num_mmu; i++) + lima_mmu_page_fault_resume(pipe->mmu[i]); + } + + lima_vm_put(pipe->current_vm); + pipe->current_vm = NULL; + pipe->current_task = NULL; + + lima_pm_idle(ldev); + + drm_sched_resubmit_jobs(&pipe->base); + drm_sched_start(&pipe->base, 0); + + return DRM_GPU_SCHED_STAT_RESET; +} + +static void lima_sched_free_job(struct drm_sched_job *job) +{ + struct lima_sched_task *task = to_lima_task(job); + struct lima_sched_pipe *pipe = to_lima_pipe(job->sched); + struct lima_vm *vm = task->vm; + struct lima_bo **bos = task->bos; + int i; + + dma_fence_put(task->fence); + + for (i = 0; i < task->num_bos; i++) + lima_vm_bo_del(vm, bos[i]); + + lima_sched_task_fini(task); + kmem_cache_free(pipe->task_slab, task); +} + +static const struct drm_sched_backend_ops lima_sched_ops = { + .run_job = lima_sched_run_job, + .timedout_job = lima_sched_timedout_job, + .free_job = lima_sched_free_job, +}; + +static void lima_sched_recover_work(struct work_struct *work) +{ + struct lima_sched_pipe *pipe = + container_of(work, struct lima_sched_pipe, recover_work); + int i; + + for (i = 0; i < pipe->num_l2_cache; i++) + lima_l2_cache_flush(pipe->l2_cache[i]); + + if (pipe->bcast_mmu) { + lima_mmu_flush_tlb(pipe->bcast_mmu); + } else { + for (i = 0; i < pipe->num_mmu; i++) + lima_mmu_flush_tlb(pipe->mmu[i]); + } + + if (pipe->task_recover(pipe)) + drm_sched_fault(&pipe->base); +} + +int lima_sched_pipe_init(struct lima_sched_pipe *pipe, const char *name) +{ + unsigned int timeout = lima_sched_timeout_ms > 0 ? + lima_sched_timeout_ms : 10000; + const struct drm_sched_init_args args = { + .ops = &lima_sched_ops, + .num_rqs = DRM_SCHED_PRIORITY_COUNT, + .credit_limit = 1, + .hang_limit = lima_job_hang_limit, + .timeout = msecs_to_jiffies(timeout), + .name = name, + .dev = pipe->ldev->dev, + }; + + pipe->fence_context = dma_fence_context_alloc(1); + spin_lock_init(&pipe->fence_lock); + + INIT_WORK(&pipe->recover_work, lima_sched_recover_work); + + return drm_sched_init(&pipe->base, &args); +} + +void lima_sched_pipe_fini(struct lima_sched_pipe *pipe) +{ + drm_sched_fini(&pipe->base); +} + +void lima_sched_pipe_task_done(struct lima_sched_pipe *pipe) +{ + struct lima_sched_task *task = pipe->current_task; + struct lima_device *ldev = pipe->ldev; + + if (pipe->error) { + if (task && task->recoverable) + schedule_work(&pipe->recover_work); + else + drm_sched_fault(&pipe->base); + } else { + pipe->task_fini(pipe); + dma_fence_signal(task->fence); + + lima_pm_idle(ldev); + } +} diff --git a/drivers/gpu/drm/lima/lima_sched.h b/drivers/gpu/drm/lima/lima_sched.h new file mode 100644 index 000000000..1a08faf8a --- /dev/null +++ b/drivers/gpu/drm/lima/lima_sched.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_SCHED_H__ +#define __LIMA_SCHED_H__ + +#include <drm/gpu_scheduler.h> +#include <linux/list.h> +#include <linux/xarray.h> + +struct lima_device; +struct lima_vm; + +struct lima_sched_error_task { + struct list_head list; + void *data; + u32 size; +}; + +struct lima_sched_task { + struct drm_sched_job base; + + struct lima_vm *vm; + void *frame; + + struct lima_bo **bos; + int num_bos; + + bool recoverable; + struct lima_bo *heap; + + /* pipe fence */ + struct dma_fence *fence; +}; + +struct lima_sched_context { + struct drm_sched_entity base; +}; + +#define LIMA_SCHED_PIPE_MAX_MMU 8 +#define LIMA_SCHED_PIPE_MAX_L2_CACHE 2 +#define LIMA_SCHED_PIPE_MAX_PROCESSOR 8 + +struct lima_ip; + +struct lima_sched_pipe { + struct drm_gpu_scheduler base; + + u64 fence_context; + u32 fence_seqno; + spinlock_t fence_lock; + + struct lima_device *ldev; + + struct lima_sched_task *current_task; + struct lima_vm *current_vm; + + struct lima_ip *mmu[LIMA_SCHED_PIPE_MAX_MMU]; + int num_mmu; + + struct lima_ip *l2_cache[LIMA_SCHED_PIPE_MAX_L2_CACHE]; + int num_l2_cache; + + struct lima_ip *processor[LIMA_SCHED_PIPE_MAX_PROCESSOR]; + int num_processor; + + struct lima_ip *bcast_processor; + struct lima_ip *bcast_mmu; + + u32 done; + bool error; + atomic_t task; + + int frame_size; + struct kmem_cache *task_slab; + + int (*task_validate)(struct lima_sched_pipe *pipe, struct lima_sched_task *task); + void (*task_run)(struct lima_sched_pipe *pipe, struct lima_sched_task *task); + void (*task_fini)(struct lima_sched_pipe *pipe); + void (*task_error)(struct lima_sched_pipe *pipe); + void (*task_mmu_error)(struct lima_sched_pipe *pipe); + int (*task_recover)(struct lima_sched_pipe *pipe); + void (*task_mask_irq)(struct lima_sched_pipe *pipe); + + struct work_struct recover_work; +}; + +int lima_sched_task_init(struct lima_sched_task *task, + struct lima_sched_context *context, + struct lima_bo **bos, int num_bos, + struct lima_vm *vm, + u64 drm_client_id); +void lima_sched_task_fini(struct lima_sched_task *task); + +int lima_sched_context_init(struct lima_sched_pipe *pipe, + struct lima_sched_context *context); +void lima_sched_context_fini(struct lima_sched_pipe *pipe, + struct lima_sched_context *context); +struct dma_fence *lima_sched_context_queue_task(struct lima_sched_task *task); + +int lima_sched_pipe_init(struct lima_sched_pipe *pipe, const char *name); +void lima_sched_pipe_fini(struct lima_sched_pipe *pipe); +void lima_sched_pipe_task_done(struct lima_sched_pipe *pipe); + +static inline void lima_sched_pipe_mmu_error(struct lima_sched_pipe *pipe) +{ + pipe->error = true; + pipe->task_mmu_error(pipe); +} + +int lima_sched_slab_init(void); +void lima_sched_slab_fini(void); + +#endif diff --git a/drivers/gpu/drm/lima/lima_trace.c b/drivers/gpu/drm/lima/lima_trace.c new file mode 100644 index 000000000..ea1c7289b --- /dev/null +++ b/drivers/gpu/drm/lima/lima_trace.c @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2020 Qiang Yu <yuq825@gmail.com> */ + +#include "lima_sched.h" + +#define CREATE_TRACE_POINTS +#include "lima_trace.h" diff --git a/drivers/gpu/drm/lima/lima_trace.h b/drivers/gpu/drm/lima/lima_trace.h new file mode 100644 index 000000000..98a7fb2fa --- /dev/null +++ b/drivers/gpu/drm/lima/lima_trace.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2020 Qiang Yu <yuq825@gmail.com> */ + +#if !defined(_LIMA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _LIMA_TRACE_H_ + +#include <linux/tracepoint.h> + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM lima +#define TRACE_INCLUDE_FILE lima_trace + +DECLARE_EVENT_CLASS(lima_task, + TP_PROTO(struct lima_sched_task *task), + TP_ARGS(task), + TP_STRUCT__entry( + __field(unsigned int, context) + __field(unsigned int, seqno) + __string(pipe, task->base.sched->name) + ), + + TP_fast_assign( + __entry->context = task->base.s_fence->finished.context; + __entry->seqno = task->base.s_fence->finished.seqno; + __assign_str(pipe); + ), + + TP_printk("context=%u seqno=%u pipe=%s", + __entry->context, __entry->seqno, + __get_str(pipe)) +); + +DEFINE_EVENT(lima_task, lima_task_submit, + TP_PROTO(struct lima_sched_task *task), + TP_ARGS(task) +); + +DEFINE_EVENT(lima_task, lima_task_run, + TP_PROTO(struct lima_sched_task *task), + TP_ARGS(task) +); + +#endif + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/lima +#include <trace/define_trace.h> diff --git a/drivers/gpu/drm/lima/lima_vm.c b/drivers/gpu/drm/lima/lima_vm.c new file mode 100644 index 000000000..c1b75b15c --- /dev/null +++ b/drivers/gpu/drm/lima/lima_vm.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include "lima_device.h" +#include "lima_vm.h" +#include "lima_gem.h" +#include "lima_regs.h" + +struct lima_bo_va { + struct list_head list; + unsigned int ref_count; + + struct drm_mm_node node; + + struct lima_vm *vm; +}; + +#define LIMA_VM_PD_SHIFT 22 +#define LIMA_VM_PT_SHIFT 12 +#define LIMA_VM_PB_SHIFT (LIMA_VM_PD_SHIFT + LIMA_VM_NUM_PT_PER_BT_SHIFT) +#define LIMA_VM_BT_SHIFT LIMA_VM_PT_SHIFT + +#define LIMA_VM_PT_MASK ((1 << LIMA_VM_PD_SHIFT) - 1) +#define LIMA_VM_BT_MASK ((1 << LIMA_VM_PB_SHIFT) - 1) + +#define LIMA_PDE(va) (va >> LIMA_VM_PD_SHIFT) +#define LIMA_PTE(va) ((va & LIMA_VM_PT_MASK) >> LIMA_VM_PT_SHIFT) +#define LIMA_PBE(va) (va >> LIMA_VM_PB_SHIFT) +#define LIMA_BTE(va) ((va & LIMA_VM_BT_MASK) >> LIMA_VM_BT_SHIFT) + + +static void lima_vm_unmap_range(struct lima_vm *vm, u32 start, u32 end) +{ + u32 addr; + + for (addr = start; addr <= end; addr += LIMA_PAGE_SIZE) { + u32 pbe = LIMA_PBE(addr); + u32 bte = LIMA_BTE(addr); + + vm->bts[pbe].cpu[bte] = 0; + } +} + +static int lima_vm_map_page(struct lima_vm *vm, dma_addr_t pa, u32 va) +{ + u32 pbe = LIMA_PBE(va); + u32 bte = LIMA_BTE(va); + + if (!vm->bts[pbe].cpu) { + dma_addr_t pts; + u32 *pd; + int j; + + vm->bts[pbe].cpu = dma_alloc_wc( + vm->dev->dev, LIMA_PAGE_SIZE << LIMA_VM_NUM_PT_PER_BT_SHIFT, + &vm->bts[pbe].dma, GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO); + if (!vm->bts[pbe].cpu) + return -ENOMEM; + + pts = vm->bts[pbe].dma; + pd = vm->pd.cpu + (pbe << LIMA_VM_NUM_PT_PER_BT_SHIFT); + for (j = 0; j < LIMA_VM_NUM_PT_PER_BT; j++) { + pd[j] = pts | LIMA_VM_FLAG_PRESENT; + pts += LIMA_PAGE_SIZE; + } + } + + vm->bts[pbe].cpu[bte] = pa | LIMA_VM_FLAGS_CACHE; + + return 0; +} + +static struct lima_bo_va * +lima_vm_bo_find(struct lima_vm *vm, struct lima_bo *bo) +{ + struct lima_bo_va *bo_va, *ret = NULL; + + list_for_each_entry(bo_va, &bo->va, list) { + if (bo_va->vm == vm) { + ret = bo_va; + break; + } + } + + return ret; +} + +int lima_vm_bo_add(struct lima_vm *vm, struct lima_bo *bo, bool create) +{ + struct lima_bo_va *bo_va; + struct sg_dma_page_iter sg_iter; + int offset = 0, err; + + mutex_lock(&bo->lock); + + bo_va = lima_vm_bo_find(vm, bo); + if (bo_va) { + bo_va->ref_count++; + mutex_unlock(&bo->lock); + return 0; + } + + /* should not create new bo_va if not asked by caller */ + if (!create) { + mutex_unlock(&bo->lock); + return -ENOENT; + } + + bo_va = kzalloc_obj(*bo_va); + if (!bo_va) { + err = -ENOMEM; + goto err_out0; + } + + bo_va->vm = vm; + bo_va->ref_count = 1; + + mutex_lock(&vm->lock); + + err = drm_mm_insert_node(&vm->mm, &bo_va->node, lima_bo_size(bo)); + if (err) + goto err_out1; + + for_each_sgtable_dma_page(bo->base.sgt, &sg_iter, 0) { + err = lima_vm_map_page(vm, sg_page_iter_dma_address(&sg_iter), + bo_va->node.start + offset); + if (err) + goto err_out2; + + offset += PAGE_SIZE; + } + + mutex_unlock(&vm->lock); + + list_add_tail(&bo_va->list, &bo->va); + + mutex_unlock(&bo->lock); + return 0; + +err_out2: + if (offset) + lima_vm_unmap_range(vm, bo_va->node.start, bo_va->node.start + offset - 1); + drm_mm_remove_node(&bo_va->node); +err_out1: + mutex_unlock(&vm->lock); + kfree(bo_va); +err_out0: + mutex_unlock(&bo->lock); + return err; +} + +void lima_vm_bo_del(struct lima_vm *vm, struct lima_bo *bo) +{ + struct lima_bo_va *bo_va; + u32 size; + + mutex_lock(&bo->lock); + + bo_va = lima_vm_bo_find(vm, bo); + if (--bo_va->ref_count > 0) { + mutex_unlock(&bo->lock); + return; + } + + mutex_lock(&vm->lock); + + size = bo->heap_size ? bo->heap_size : bo_va->node.size; + lima_vm_unmap_range(vm, bo_va->node.start, + bo_va->node.start + size - 1); + + drm_mm_remove_node(&bo_va->node); + + mutex_unlock(&vm->lock); + + list_del(&bo_va->list); + + mutex_unlock(&bo->lock); + + kfree(bo_va); +} + +u32 lima_vm_get_va(struct lima_vm *vm, struct lima_bo *bo) +{ + struct lima_bo_va *bo_va; + u32 ret; + + mutex_lock(&bo->lock); + + bo_va = lima_vm_bo_find(vm, bo); + ret = bo_va->node.start; + + mutex_unlock(&bo->lock); + + return ret; +} + +struct lima_vm *lima_vm_create(struct lima_device *dev) +{ + struct lima_vm *vm; + + vm = kzalloc_obj(*vm); + if (!vm) + return NULL; + + vm->dev = dev; + mutex_init(&vm->lock); + kref_init(&vm->refcount); + + vm->pd.cpu = dma_alloc_wc(dev->dev, LIMA_PAGE_SIZE, &vm->pd.dma, + GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO); + if (!vm->pd.cpu) + goto err_out0; + + if (dev->dlbu_cpu) { + int err = lima_vm_map_page( + vm, dev->dlbu_dma, LIMA_VA_RESERVE_DLBU); + if (err) + goto err_out1; + } + + drm_mm_init(&vm->mm, dev->va_start, dev->va_end - dev->va_start); + + return vm; + +err_out1: + dma_free_wc(dev->dev, LIMA_PAGE_SIZE, vm->pd.cpu, vm->pd.dma); +err_out0: + kfree(vm); + return NULL; +} + +void lima_vm_release(struct kref *kref) +{ + struct lima_vm *vm = container_of(kref, struct lima_vm, refcount); + int i; + + drm_mm_takedown(&vm->mm); + + for (i = 0; i < LIMA_VM_NUM_BT; i++) { + if (vm->bts[i].cpu) + dma_free_wc(vm->dev->dev, LIMA_PAGE_SIZE << LIMA_VM_NUM_PT_PER_BT_SHIFT, + vm->bts[i].cpu, vm->bts[i].dma); + } + + if (vm->pd.cpu) + dma_free_wc(vm->dev->dev, LIMA_PAGE_SIZE, vm->pd.cpu, vm->pd.dma); + + kfree(vm); +} + +void lima_vm_print(struct lima_vm *vm) +{ + int i, j, k; + u32 *pd, *pt; + + if (!vm->pd.cpu) + return; + + pd = vm->pd.cpu; + for (i = 0; i < LIMA_VM_NUM_BT; i++) { + if (!vm->bts[i].cpu) + continue; + + pt = vm->bts[i].cpu; + for (j = 0; j < LIMA_VM_NUM_PT_PER_BT; j++) { + int idx = (i << LIMA_VM_NUM_PT_PER_BT_SHIFT) + j; + + printk(KERN_INFO "lima vm pd %03x:%08x\n", idx, pd[idx]); + + for (k = 0; k < LIMA_PAGE_ENT_NUM; k++) { + u32 pte = *pt++; + + if (pte) + printk(KERN_INFO " pt %03x:%08x\n", k, pte); + } + } + } +} + +int lima_vm_map_bo(struct lima_vm *vm, struct lima_bo *bo, int pageoff) +{ + struct lima_bo_va *bo_va; + struct sg_dma_page_iter sg_iter; + int offset = 0, err; + u32 base; + + mutex_lock(&bo->lock); + + bo_va = lima_vm_bo_find(vm, bo); + if (!bo_va) { + err = -ENOENT; + goto err_out0; + } + + mutex_lock(&vm->lock); + + base = bo_va->node.start + (pageoff << PAGE_SHIFT); + for_each_sgtable_dma_page(bo->base.sgt, &sg_iter, pageoff) { + err = lima_vm_map_page(vm, sg_page_iter_dma_address(&sg_iter), + base + offset); + if (err) + goto err_out1; + + offset += PAGE_SIZE; + } + + mutex_unlock(&vm->lock); + + mutex_unlock(&bo->lock); + return 0; + +err_out1: + if (offset) + lima_vm_unmap_range(vm, base, base + offset - 1); + mutex_unlock(&vm->lock); +err_out0: + mutex_unlock(&bo->lock); + return err; +} diff --git a/drivers/gpu/drm/lima/lima_vm.h b/drivers/gpu/drm/lima/lima_vm.h new file mode 100644 index 000000000..3a7c74822 --- /dev/null +++ b/drivers/gpu/drm/lima/lima_vm.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright 2017-2019 Qiang Yu <yuq825@gmail.com> */ + +#ifndef __LIMA_VM_H__ +#define __LIMA_VM_H__ + +#include <drm/drm_mm.h> +#include <linux/kref.h> + +#define LIMA_PAGE_SIZE 4096 +#define LIMA_PAGE_MASK (LIMA_PAGE_SIZE - 1) +#define LIMA_PAGE_ENT_NUM (LIMA_PAGE_SIZE / sizeof(u32)) + +#define LIMA_VM_NUM_PT_PER_BT_SHIFT 3 +#define LIMA_VM_NUM_PT_PER_BT (1 << LIMA_VM_NUM_PT_PER_BT_SHIFT) +#define LIMA_VM_NUM_BT (LIMA_PAGE_ENT_NUM >> LIMA_VM_NUM_PT_PER_BT_SHIFT) + +#define LIMA_VA_RESERVE_START 0x0FFF00000ULL +#define LIMA_VA_RESERVE_DLBU LIMA_VA_RESERVE_START +#define LIMA_VA_RESERVE_END 0x100000000ULL + +struct lima_device; + +struct lima_vm_page { + u32 *cpu; + dma_addr_t dma; +}; + +struct lima_vm { + struct mutex lock; + struct kref refcount; + + struct drm_mm mm; + + struct lima_device *dev; + + struct lima_vm_page pd; + struct lima_vm_page bts[LIMA_VM_NUM_BT]; +}; + +int lima_vm_bo_add(struct lima_vm *vm, struct lima_bo *bo, bool create); +void lima_vm_bo_del(struct lima_vm *vm, struct lima_bo *bo); + +u32 lima_vm_get_va(struct lima_vm *vm, struct lima_bo *bo); + +struct lima_vm *lima_vm_create(struct lima_device *dev); +void lima_vm_release(struct kref *kref); + +static inline struct lima_vm *lima_vm_get(struct lima_vm *vm) +{ + kref_get(&vm->refcount); + return vm; +} + +static inline void lima_vm_put(struct lima_vm *vm) +{ + if (vm) + kref_put(&vm->refcount, lima_vm_release); +} + +void lima_vm_print(struct lima_vm *vm); +int lima_vm_map_bo(struct lima_vm *vm, struct lima_bo *bo, int pageoff); + +#endif |
