Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0 or MIT
config DRM_PANTHOR
tristate "Panthor (DRM support for ARM Mali CSF-based GPUs)"
depends on DRM
depends on ARM || ARM64 || COMPILE_TEST
depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_LPAE
depends on MMU
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select DRM_EXEC
select DRM_GPUVM
select DRM_SCHED
select IOMMU_IO_PGTABLE_LPAE
select IOMMU_SUPPORT
select PM_DEVFREQ
help
DRM driver for ARM Mali CSF-based GPUs.
This driver is for Mali (or Immortalis) Valhall Gxxx GPUs.
Note that the Mali-G68 and Mali-G78, while Valhall architecture, will
be supported with the panfrost driver as they are not CSF GPUs.
@@ -0,0 +1,18 @@
# SPDX-License-Identifier: GPL-2.0 or MIT
panthor-y := \
panthor_devfreq.o \
panthor_device.o \
panthor_drv.o \
panthor_fw.o \
panthor_gem.o \
panthor_gpu.o \
panthor_heap.o \
panthor_hw.o \
panthor_mmu.o \
panthor_pwr.o \
panthor_sched.o
obj-$(CONFIG_DRM_PANTHOR) += panthor.o
CFLAGS_panthor_gpu.o := -I$(src)
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2019 Collabora ltd. */
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include "panthor_devfreq.h"
#include "panthor_device.h"
/**
* struct panthor_devfreq - Device frequency management
*/
struct panthor_devfreq {
/** @devfreq: devfreq device. */
struct devfreq *devfreq;
/** @gov_data: Governor data. */
struct devfreq_simple_ondemand_data gov_data;
/** @busy_time: Busy time. */
ktime_t busy_time;
/** @idle_time: Idle time. */
ktime_t idle_time;
/** @time_last_update: Last update time. */
ktime_t time_last_update;
/** @last_busy_state: True if the GPU was busy last time we updated the state. */
bool last_busy_state;
/**
* @lock: Lock used to protect busy_time, idle_time, time_last_update and
* last_busy_state.
*
* These fields can be accessed concurrently by panthor_devfreq_get_dev_status()
* and panthor_devfreq_record_{busy,idle}().
*/
spinlock_t lock;
};
static void panthor_devfreq_update_utilization(struct panthor_devfreq *pdevfreq)
{
ktime_t now, last;
now = ktime_get();
last = pdevfreq->time_last_update;
if (pdevfreq->last_busy_state)
pdevfreq->busy_time += ktime_sub(now, last);
else
pdevfreq->idle_time += ktime_sub(now, last);
pdevfreq->time_last_update = now;
}
static int panthor_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct dev_pm_opp *opp;
int err;
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp))
return PTR_ERR(opp);
dev_pm_opp_put(opp);
err = dev_pm_opp_set_rate(dev, *freq);
return err;
}
static void panthor_devfreq_reset(struct panthor_devfreq *pdevfreq)
{
pdevfreq->busy_time = 0;
pdevfreq->idle_time = 0;
pdevfreq->time_last_update = ktime_get();
}
static int panthor_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *status)
{
struct panthor_device *ptdev = dev_get_drvdata(dev);
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
unsigned long irqflags;
status->current_frequency = clk_get_rate(ptdev->clks.core);
spin_lock_irqsave(&pdevfreq->lock, irqflags);
panthor_devfreq_update_utilization(pdevfreq);
status->total_time = ktime_to_ns(ktime_add(pdevfreq->busy_time,
pdevfreq->idle_time));
status->busy_time = ktime_to_ns(pdevfreq->busy_time);
panthor_devfreq_reset(pdevfreq);
spin_unlock_irqrestore(&pdevfreq->lock, irqflags);
drm_dbg(&ptdev->base, "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 int panthor_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct panthor_device *ptdev = dev_get_drvdata(dev);
*freq = clk_get_rate(ptdev->clks.core);
return 0;
}
static struct devfreq_dev_profile panthor_devfreq_profile = {
.timer = DEVFREQ_TIMER_DELAYED,
.polling_ms = 50, /* ~3 frames */
.target = panthor_devfreq_target,
.get_dev_status = panthor_devfreq_get_dev_status,
.get_cur_freq = panthor_devfreq_get_cur_freq,
};
int panthor_devfreq_init(struct panthor_device *ptdev)
{
/* There's actually 2 regulators (mali and sram), but the OPP core only
* supports one.
*
* We assume the sram regulator is coupled with the mali one and let
* the coupling logic deal with voltage updates.
*/
static const char * const reg_names[] = { "mali", NULL };
struct thermal_cooling_device *cooling;
struct device *dev = ptdev->base.dev;
struct panthor_devfreq *pdevfreq;
struct opp_table *table;
struct dev_pm_opp *opp;
unsigned long cur_freq;
unsigned long freq = ULONG_MAX;
int ret;
pdevfreq = drmm_kzalloc(&ptdev->base, sizeof(*ptdev->devfreq), GFP_KERNEL);
if (!pdevfreq)
return -ENOMEM;
ptdev->devfreq = pdevfreq;
/*
* The power domain associated with the GPU may have already added an
* OPP table, complete with OPPs, as part of the platform bus
* initialization. If this is the case, the power domain is in charge of
* also controlling the performance, with a set_performance callback.
* Only add a new OPP table from DT if there isn't such a table present
* already.
*/
table = dev_pm_opp_get_opp_table(dev);
if (IS_ERR_OR_NULL(table)) {
ret = devm_pm_opp_set_regulators(dev, reg_names);
if (ret && ret != -ENODEV) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "Couldn't set OPP regulators\n");
return ret;
}
ret = devm_pm_opp_of_add_table(dev);
if (ret)
return ret;
} else {
dev_pm_opp_put_opp_table(table);
}
spin_lock_init(&pdevfreq->lock);
panthor_devfreq_reset(pdevfreq);
cur_freq = clk_get_rate(ptdev->clks.core);
/* Regulator coupling only takes care of synchronizing/balancing voltage
* updates, but the coupled regulator needs to be enabled manually.
*
* We use devm_regulator_get_enable_optional() and keep the sram supply
* enabled until the device is removed, just like we do for the mali
* supply, which is enabled when dev_pm_opp_set_opp(dev, opp) is called,
* and disabled when the opp_table is torn down, using the devm action.
*
* If we really care about disabling regulators on suspend, we should:
* - use devm_regulator_get_optional() here
* - call dev_pm_opp_set_opp(dev, NULL) before leaving this function
* (this disables the regulator passed to the OPP layer)
* - call dev_pm_opp_set_opp(dev, NULL) and
* regulator_disable(ptdev->regulators.sram) in
* panthor_devfreq_suspend()
* - call dev_pm_opp_set_opp(dev, default_opp) and
* regulator_enable(ptdev->regulators.sram) in
* panthor_devfreq_resume()
*
* But without knowing if it's beneficial or not (in term of power
* consumption), or how much it slows down the suspend/resume steps,
* let's just keep regulators enabled for the device lifetime.
*/
ret = devm_regulator_get_enable_optional(dev, "sram");
if (ret && ret != -ENODEV) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "Couldn't retrieve/enable sram supply\n");
return ret;
}
opp = devfreq_recommended_opp(dev, &cur_freq, 0);
if (IS_ERR(opp))
return PTR_ERR(opp);
panthor_devfreq_profile.initial_freq = cur_freq;
/*
* Set the recommend OPP this will enable and configure the regulator
* if any and will avoid a switch off by regulator_late_cleanup()
*/
ret = dev_pm_opp_set_opp(dev, opp);
dev_pm_opp_put(opp);
if (ret) {
DRM_DEV_ERROR(dev, "Couldn't set recommended OPP\n");
return ret;
}
/* Find the fastest defined rate */
opp = dev_pm_opp_find_freq_floor(dev, &freq);
if (IS_ERR(opp))
return PTR_ERR(opp);
ptdev->fast_rate = freq;
dev_pm_opp_put(opp);
/*
* Setup default thresholds for the simple_ondemand governor.
* The values are chosen based on experiments.
*/
pdevfreq->gov_data.upthreshold = 45;
pdevfreq->gov_data.downdifferential = 5;
pdevfreq->devfreq = devm_devfreq_add_device(dev, &panthor_devfreq_profile,
DEVFREQ_GOV_SIMPLE_ONDEMAND,
&pdevfreq->gov_data);
if (IS_ERR(pdevfreq->devfreq)) {
DRM_DEV_ERROR(dev, "Couldn't initialize GPU devfreq\n");
ret = PTR_ERR(pdevfreq->devfreq);
pdevfreq->devfreq = NULL;
return ret;
}
cooling = devfreq_cooling_em_register(pdevfreq->devfreq, NULL);
if (IS_ERR(cooling))
DRM_DEV_INFO(dev, "Failed to register cooling device\n");
return 0;
}
void panthor_devfreq_resume(struct panthor_device *ptdev)
{
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
if (!pdevfreq->devfreq)
return;
panthor_devfreq_reset(pdevfreq);
drm_WARN_ON(&ptdev->base, devfreq_resume_device(pdevfreq->devfreq));
}
void panthor_devfreq_suspend(struct panthor_device *ptdev)
{
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
if (!pdevfreq->devfreq)
return;
drm_WARN_ON(&ptdev->base, devfreq_suspend_device(pdevfreq->devfreq));
}
void panthor_devfreq_record_busy(struct panthor_device *ptdev)
{
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
unsigned long irqflags;
if (!pdevfreq->devfreq)
return;
spin_lock_irqsave(&pdevfreq->lock, irqflags);
panthor_devfreq_update_utilization(pdevfreq);
pdevfreq->last_busy_state = true;
spin_unlock_irqrestore(&pdevfreq->lock, irqflags);
}
void panthor_devfreq_record_idle(struct panthor_device *ptdev)
{
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
unsigned long irqflags;
if (!pdevfreq->devfreq)
return;
spin_lock_irqsave(&pdevfreq->lock, irqflags);
panthor_devfreq_update_utilization(pdevfreq);
pdevfreq->last_busy_state = false;
spin_unlock_irqrestore(&pdevfreq->lock, irqflags);
}
unsigned long panthor_devfreq_get_freq(struct panthor_device *ptdev)
{
struct panthor_devfreq *pdevfreq = ptdev->devfreq;
unsigned long freq = 0;
int ret;
if (!pdevfreq->devfreq)
return 0;
ret = pdevfreq->devfreq->profile->get_cur_freq(ptdev->base.dev, &freq);
if (ret)
return 0;
return freq;
}
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2019 Collabora ltd. */
#ifndef __PANTHOR_DEVFREQ_H__
#define __PANTHOR_DEVFREQ_H__
struct devfreq;
struct thermal_cooling_device;
struct panthor_device;
struct panthor_devfreq;
int panthor_devfreq_init(struct panthor_device *ptdev);
void panthor_devfreq_resume(struct panthor_device *ptdev);
void panthor_devfreq_suspend(struct panthor_device *ptdev);
void panthor_devfreq_record_busy(struct panthor_device *ptdev);
void panthor_devfreq_record_idle(struct panthor_device *ptdev);
unsigned long panthor_devfreq_get_freq(struct panthor_device *ptdev);
#endif /* __PANTHOR_DEVFREQ_H__ */
@@ -0,0 +1,613 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2023 Collabora ltd. */
/* Copyright 2025 ARM Limited. All rights reserved. */
#include <linux/clk.h>
#include <linux/mm.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <drm/drm_drv.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include "panthor_devfreq.h"
#include "panthor_device.h"
#include "panthor_fw.h"
#include "panthor_fw_regs.h"
#include "panthor_gem.h"
#include "panthor_gpu.h"
#include "panthor_hw.h"
#include "panthor_mmu.h"
#include "panthor_pwr.h"
#include "panthor_sched.h"
static int panthor_clk_init(struct panthor_device *ptdev)
{
ptdev->clks.core = devm_clk_get(ptdev->base.dev, NULL);
if (IS_ERR(ptdev->clks.core))
return dev_err_probe(ptdev->base.dev,
PTR_ERR(ptdev->clks.core),
"get 'core' clock failed");
ptdev->clks.stacks = devm_clk_get_optional(ptdev->base.dev, "stacks");
if (IS_ERR(ptdev->clks.stacks))
return dev_err_probe(ptdev->base.dev,
PTR_ERR(ptdev->clks.stacks),
"get 'stacks' clock failed");
ptdev->clks.coregroup = devm_clk_get_optional(ptdev->base.dev, "coregroup");
if (IS_ERR(ptdev->clks.coregroup))
return dev_err_probe(ptdev->base.dev,
PTR_ERR(ptdev->clks.coregroup),
"get 'coregroup' clock failed");
drm_info(&ptdev->base, "clock rate = %lu\n", clk_get_rate(ptdev->clks.core));
return 0;
}
static int panthor_init_power(struct device *dev)
{
struct dev_pm_domain_list *pd_list = NULL;
if (dev->pm_domain)
return 0;
return devm_pm_domain_attach_list(dev, NULL, &pd_list);
}
void panthor_device_unplug(struct panthor_device *ptdev)
{
/* This function can be called from two different path: the reset work
* and the platform device remove callback. drm_dev_unplug() doesn't
* deal with concurrent callers, so we have to protect drm_dev_unplug()
* calls with our own lock, and bail out if the device is already
* unplugged.
*/
mutex_lock(&ptdev->unplug.lock);
if (drm_dev_is_unplugged(&ptdev->base)) {
/* Someone beat us, release the lock and wait for the unplug
* operation to be reported as done.
**/
mutex_unlock(&ptdev->unplug.lock);
wait_for_completion(&ptdev->unplug.done);
return;
}
drm_WARN_ON(&ptdev->base, pm_runtime_get_sync(ptdev->base.dev) < 0);
/* Call drm_dev_unplug() so any access to HW blocks happening after
* that point get rejected.
*/
drm_dev_unplug(&ptdev->base);
/* We do the rest of the unplug with the unplug lock released,
* future callers will wait on ptdev->unplug.done anyway.
*/
mutex_unlock(&ptdev->unplug.lock);
/* Now, try to cleanly shutdown the GPU before the device resources
* get reclaimed.
*/
panthor_sched_unplug(ptdev);
panthor_fw_unplug(ptdev);
panthor_mmu_unplug(ptdev);
panthor_gem_shrinker_unplug(ptdev);
panthor_gpu_unplug(ptdev);
panthor_pwr_unplug(ptdev);
pm_runtime_dont_use_autosuspend(ptdev->base.dev);
pm_runtime_put_sync_suspend(ptdev->base.dev);
/* If PM is disabled, we need to call the suspend handler manually. */
if (!IS_ENABLED(CONFIG_PM))
panthor_device_suspend(ptdev->base.dev);
/* Report the unplug operation as done to unblock concurrent
* panthor_device_unplug() callers.
*/
complete_all(&ptdev->unplug.done);
}
static void panthor_device_reset_cleanup(struct drm_device *ddev, void *data)
{
struct panthor_device *ptdev = container_of(ddev, struct panthor_device, base);
disable_work_sync(&ptdev->reset.work);
destroy_workqueue(ptdev->reset.wq);
}
static void panthor_device_reset_work(struct work_struct *work)
{
struct panthor_device *ptdev = container_of(work, struct panthor_device, reset.work);
int ret = 0, cookie;
/* If the device is entering suspend, we don't reset. A slow reset will
* be forced at resume time instead.
*/
if (atomic_read(&ptdev->pm.state) != PANTHOR_DEVICE_PM_STATE_ACTIVE)
return;
if (!drm_dev_enter(&ptdev->base, &cookie))
return;
panthor_sched_pre_reset(ptdev);
panthor_fw_pre_reset(ptdev, true);
panthor_mmu_pre_reset(ptdev);
panthor_hw_soft_reset(ptdev);
panthor_hw_l2_power_on(ptdev);
panthor_mmu_post_reset(ptdev);
ret = panthor_fw_post_reset(ptdev);
atomic_set(&ptdev->reset.pending, 0);
panthor_sched_post_reset(ptdev, ret != 0);
drm_dev_exit(cookie);
if (ret) {
panthor_device_unplug(ptdev);
drm_err(&ptdev->base, "Failed to boot MCU after reset, making device unusable.");
}
}
static bool panthor_device_is_initialized(struct panthor_device *ptdev)
{
return !!ptdev->scheduler;
}
static void panthor_device_free_page(struct drm_device *ddev, void *data)
{
__free_page(data);
}
int panthor_device_init(struct panthor_device *ptdev)
{
u32 *dummy_page_virt;
struct resource *res;
struct page *p;
int ret;
ptdev->soc_data = of_device_get_match_data(ptdev->base.dev);
init_completion(&ptdev->unplug.done);
ret = drmm_mutex_init(&ptdev->base, &ptdev->unplug.lock);
if (ret)
return ret;
ret = drmm_mutex_init(&ptdev->base, &ptdev->pm.mmio_lock);
if (ret)
return ret;
#ifdef CONFIG_DEBUG_FS
ret = drmm_mutex_init(&ptdev->base, &ptdev->gems.lock);
if (ret)
return ret;
INIT_LIST_HEAD(&ptdev->gems.node);
#endif
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_SUSPENDED);
p = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!p)
return -ENOMEM;
ptdev->pm.dummy_latest_flush = p;
dummy_page_virt = page_address(p);
ret = drmm_add_action_or_reset(&ptdev->base, panthor_device_free_page,
ptdev->pm.dummy_latest_flush);
if (ret)
return ret;
/*
* Set the dummy page holding the latest flush to 1. This will cause the
* flush to avoided as we know it isn't necessary if the submission
* happens while the dummy page is mapped. Zero cannot be used because
* that means 'always flush'.
*/
*dummy_page_virt = 1;
INIT_WORK(&ptdev->reset.work, panthor_device_reset_work);
disable_work(&ptdev->reset.work);
ptdev->reset.wq = alloc_ordered_workqueue("panthor-reset-wq", 0);
if (!ptdev->reset.wq)
return -ENOMEM;
ret = drmm_add_action_or_reset(&ptdev->base, panthor_device_reset_cleanup, NULL);
if (ret)
return ret;
ret = panthor_clk_init(ptdev);
if (ret)
return ret;
ret = panthor_init_power(ptdev->base.dev);
if (ret < 0) {
drm_err(&ptdev->base, "init power domains failed, ret=%d", ret);
return ret;
}
ret = panthor_devfreq_init(ptdev);
if (ret)
return ret;
ptdev->iomem = devm_platform_get_and_ioremap_resource(to_platform_device(ptdev->base.dev),
0, &res);
if (IS_ERR(ptdev->iomem))
return PTR_ERR(ptdev->iomem);
ptdev->phys_addr = res->start;
ret = devm_pm_runtime_enable(ptdev->base.dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(ptdev->base.dev);
if (ret)
return ret;
/* If PM is disabled, we need to call panthor_device_resume() manually. */
if (!IS_ENABLED(CONFIG_PM)) {
ret = panthor_device_resume(ptdev->base.dev);
if (ret)
return ret;
}
ret = panthor_hw_init(ptdev);
if (ret)
goto err_rpm_put;
ret = panthor_pwr_init(ptdev);
if (ret)
goto err_rpm_put;
ret = panthor_gpu_init(ptdev);
if (ret)
goto err_unplug_pwr;
ret = panthor_gpu_coherency_init(ptdev);
if (ret)
goto err_unplug_gpu;
ret = panthor_gem_shrinker_init(ptdev);
if (ret)
goto err_unplug_gpu;
ret = panthor_mmu_init(ptdev);
if (ret)
goto err_unplug_shrinker;
ret = panthor_fw_init(ptdev);
if (ret)
goto err_unplug_mmu;
ret = panthor_sched_init(ptdev);
if (ret)
goto err_unplug_fw;
panthor_gem_init(ptdev);
/* Now that everything is initialized, we can enable the reset work. */
enable_work(&ptdev->reset.work);
/* ~3 frames */
pm_runtime_set_autosuspend_delay(ptdev->base.dev, 50);
pm_runtime_use_autosuspend(ptdev->base.dev);
ret = drm_dev_register(&ptdev->base, 0);
if (ret)
goto err_disable_autosuspend;
pm_runtime_put_autosuspend(ptdev->base.dev);
return 0;
err_disable_autosuspend:
pm_runtime_dont_use_autosuspend(ptdev->base.dev);
panthor_sched_unplug(ptdev);
err_unplug_fw:
panthor_fw_unplug(ptdev);
err_unplug_mmu:
panthor_mmu_unplug(ptdev);
err_unplug_shrinker:
panthor_gem_shrinker_unplug(ptdev);
err_unplug_gpu:
panthor_gpu_unplug(ptdev);
err_unplug_pwr:
panthor_pwr_unplug(ptdev);
err_rpm_put:
pm_runtime_put_sync_suspend(ptdev->base.dev);
return ret;
}
#define PANTHOR_EXCEPTION(id) \
[DRM_PANTHOR_EXCEPTION_ ## id] = { \
.name = #id, \
}
struct panthor_exception_info {
const char *name;
};
static const struct panthor_exception_info panthor_exception_infos[] = {
PANTHOR_EXCEPTION(OK),
PANTHOR_EXCEPTION(TERMINATED),
PANTHOR_EXCEPTION(KABOOM),
PANTHOR_EXCEPTION(EUREKA),
PANTHOR_EXCEPTION(ACTIVE),
PANTHOR_EXCEPTION(CS_RES_TERM),
PANTHOR_EXCEPTION(CS_CONFIG_FAULT),
PANTHOR_EXCEPTION(CS_UNRECOVERABLE),
PANTHOR_EXCEPTION(CS_ENDPOINT_FAULT),
PANTHOR_EXCEPTION(CS_BUS_FAULT),
PANTHOR_EXCEPTION(CS_INSTR_INVALID),
PANTHOR_EXCEPTION(CS_CALL_STACK_OVERFLOW),
PANTHOR_EXCEPTION(CS_INHERIT_FAULT),
PANTHOR_EXCEPTION(INSTR_INVALID_PC),
PANTHOR_EXCEPTION(INSTR_INVALID_ENC),
PANTHOR_EXCEPTION(INSTR_BARRIER_FAULT),
PANTHOR_EXCEPTION(DATA_INVALID_FAULT),
PANTHOR_EXCEPTION(TILE_RANGE_FAULT),
PANTHOR_EXCEPTION(ADDR_RANGE_FAULT),
PANTHOR_EXCEPTION(IMPRECISE_FAULT),
PANTHOR_EXCEPTION(OOM),
PANTHOR_EXCEPTION(CSF_FW_INTERNAL_ERROR),
PANTHOR_EXCEPTION(CSF_RES_EVICTION_TIMEOUT),
PANTHOR_EXCEPTION(GPU_BUS_FAULT),
PANTHOR_EXCEPTION(GPU_SHAREABILITY_FAULT),
PANTHOR_EXCEPTION(SYS_SHAREABILITY_FAULT),
PANTHOR_EXCEPTION(GPU_CACHEABILITY_FAULT),
PANTHOR_EXCEPTION(TRANSLATION_FAULT_0),
PANTHOR_EXCEPTION(TRANSLATION_FAULT_1),
PANTHOR_EXCEPTION(TRANSLATION_FAULT_2),
PANTHOR_EXCEPTION(TRANSLATION_FAULT_3),
PANTHOR_EXCEPTION(TRANSLATION_FAULT_4),
PANTHOR_EXCEPTION(PERM_FAULT_0),
PANTHOR_EXCEPTION(PERM_FAULT_1),
PANTHOR_EXCEPTION(PERM_FAULT_2),
PANTHOR_EXCEPTION(PERM_FAULT_3),
PANTHOR_EXCEPTION(ACCESS_FLAG_1),
PANTHOR_EXCEPTION(ACCESS_FLAG_2),
PANTHOR_EXCEPTION(ACCESS_FLAG_3),
PANTHOR_EXCEPTION(ADDR_SIZE_FAULT_IN),
PANTHOR_EXCEPTION(ADDR_SIZE_FAULT_OUT0),
PANTHOR_EXCEPTION(ADDR_SIZE_FAULT_OUT1),
PANTHOR_EXCEPTION(ADDR_SIZE_FAULT_OUT2),
PANTHOR_EXCEPTION(ADDR_SIZE_FAULT_OUT3),
PANTHOR_EXCEPTION(MEM_ATTR_FAULT_0),
PANTHOR_EXCEPTION(MEM_ATTR_FAULT_1),
PANTHOR_EXCEPTION(MEM_ATTR_FAULT_2),
PANTHOR_EXCEPTION(MEM_ATTR_FAULT_3),
};
const char *panthor_exception_name(struct panthor_device *ptdev, u32 exception_code)
{
if (exception_code >= ARRAY_SIZE(panthor_exception_infos) ||
!panthor_exception_infos[exception_code].name)
return "Unknown exception type";
return panthor_exception_infos[exception_code].name;
}
static vm_fault_t panthor_mmio_vm_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct panthor_device *ptdev = vma->vm_private_data;
u64 offset = (u64)vma->vm_pgoff << PAGE_SHIFT;
unsigned long pfn;
pgprot_t pgprot;
vm_fault_t ret;
bool active;
int cookie;
if (!drm_dev_enter(&ptdev->base, &cookie))
return VM_FAULT_SIGBUS;
mutex_lock(&ptdev->pm.mmio_lock);
active = atomic_read(&ptdev->pm.state) == PANTHOR_DEVICE_PM_STATE_ACTIVE;
switch (offset) {
case DRM_PANTHOR_USER_FLUSH_ID_MMIO_OFFSET:
if (active)
pfn = __phys_to_pfn(ptdev->phys_addr + CSF_GPU_LATEST_FLUSH_ID);
else
pfn = page_to_pfn(ptdev->pm.dummy_latest_flush);
break;
default:
ret = VM_FAULT_SIGBUS;
goto out_unlock;
}
pgprot = vma->vm_page_prot;
if (active)
pgprot = pgprot_noncached(pgprot);
ret = vmf_insert_pfn_prot(vma, vmf->address, pfn, pgprot);
out_unlock:
mutex_unlock(&ptdev->pm.mmio_lock);
drm_dev_exit(cookie);
return ret;
}
static const struct vm_operations_struct panthor_mmio_vm_ops = {
.fault = panthor_mmio_vm_fault,
};
int panthor_device_mmap_io(struct panthor_device *ptdev, struct vm_area_struct *vma)
{
u64 offset = (u64)vma->vm_pgoff << PAGE_SHIFT;
if ((vma->vm_flags & VM_SHARED) == 0)
return -EINVAL;
switch (offset) {
case DRM_PANTHOR_USER_FLUSH_ID_MMIO_OFFSET:
if (vma->vm_end - vma->vm_start != PAGE_SIZE ||
(vma->vm_flags & (VM_WRITE | VM_EXEC)))
return -EINVAL;
vm_flags_clear(vma, VM_MAYWRITE);
break;
default:
return -EINVAL;
}
/* Defer actual mapping to the fault handler. */
vma->vm_private_data = ptdev;
vma->vm_ops = &panthor_mmio_vm_ops;
vm_flags_set(vma,
VM_IO | VM_DONTCOPY | VM_DONTEXPAND |
VM_NORESERVE | VM_DONTDUMP | VM_PFNMAP);
return 0;
}
static int panthor_device_resume_hw_components(struct panthor_device *ptdev)
{
int ret;
panthor_pwr_resume(ptdev);
panthor_gpu_resume(ptdev);
panthor_mmu_resume(ptdev);
ret = panthor_fw_resume(ptdev);
if (!ret)
return 0;
panthor_mmu_suspend(ptdev);
panthor_gpu_suspend(ptdev);
panthor_pwr_suspend(ptdev);
return ret;
}
int panthor_device_resume(struct device *dev)
{
struct panthor_device *ptdev = dev_get_drvdata(dev);
int ret, cookie;
if (atomic_read(&ptdev->pm.state) != PANTHOR_DEVICE_PM_STATE_SUSPENDED)
return -EINVAL;
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_RESUMING);
ret = clk_prepare_enable(ptdev->clks.core);
if (ret)
goto err_set_suspended;
ret = clk_prepare_enable(ptdev->clks.stacks);
if (ret)
goto err_disable_core_clk;
ret = clk_prepare_enable(ptdev->clks.coregroup);
if (ret)
goto err_disable_stacks_clk;
panthor_devfreq_resume(ptdev);
if (panthor_device_is_initialized(ptdev) &&
drm_dev_enter(&ptdev->base, &cookie)) {
/* If there was a reset pending at the time we suspended the
* device, we force a slow reset.
*/
if (atomic_read(&ptdev->reset.pending)) {
ptdev->reset.fast = false;
atomic_set(&ptdev->reset.pending, 0);
}
ret = panthor_device_resume_hw_components(ptdev);
if (ret && ptdev->reset.fast) {
drm_err(&ptdev->base, "Fast reset failed, trying a slow reset");
ptdev->reset.fast = false;
ret = panthor_device_resume_hw_components(ptdev);
}
if (!ret)
panthor_sched_resume(ptdev);
drm_dev_exit(cookie);
if (ret)
goto err_suspend_devfreq;
}
/* Clear all IOMEM mappings pointing to this device after we've
* resumed. This way the fake mappings pointing to the dummy pages
* are removed and the real iomem mapping will be restored on next
* access.
*/
mutex_lock(&ptdev->pm.mmio_lock);
unmap_mapping_range(ptdev->base.anon_inode->i_mapping,
DRM_PANTHOR_USER_MMIO_OFFSET, 0, 1);
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_ACTIVE);
mutex_unlock(&ptdev->pm.mmio_lock);
return 0;
err_suspend_devfreq:
panthor_devfreq_suspend(ptdev);
clk_disable_unprepare(ptdev->clks.coregroup);
err_disable_stacks_clk:
clk_disable_unprepare(ptdev->clks.stacks);
err_disable_core_clk:
clk_disable_unprepare(ptdev->clks.core);
err_set_suspended:
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_SUSPENDED);
atomic_set(&ptdev->pm.recovery_needed, 1);
return ret;
}
int panthor_device_suspend(struct device *dev)
{
struct panthor_device *ptdev = dev_get_drvdata(dev);
int cookie;
if (atomic_read(&ptdev->pm.state) != PANTHOR_DEVICE_PM_STATE_ACTIVE)
return -EINVAL;
/* Clear all IOMEM mappings pointing to this device before we
* shutdown the power-domain and clocks. Failing to do that results
* in external aborts when the process accesses the iomem region.
* We change the state and call unmap_mapping_range() with the
* mmio_lock held to make sure the vm_fault handler won't set up
* invalid mappings.
*/
mutex_lock(&ptdev->pm.mmio_lock);
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_SUSPENDING);
unmap_mapping_range(ptdev->base.anon_inode->i_mapping,
DRM_PANTHOR_USER_MMIO_OFFSET, 0, 1);
mutex_unlock(&ptdev->pm.mmio_lock);
if (panthor_device_is_initialized(ptdev) &&
drm_dev_enter(&ptdev->base, &cookie)) {
cancel_work_sync(&ptdev->reset.work);
/* We prepare everything as if we were resetting the GPU.
* The end of the reset will happen in the resume path though.
*/
panthor_sched_suspend(ptdev);
panthor_fw_suspend(ptdev);
panthor_mmu_suspend(ptdev);
panthor_gpu_suspend(ptdev);
panthor_pwr_suspend(ptdev);
drm_dev_exit(cookie);
}
panthor_devfreq_suspend(ptdev);
clk_disable_unprepare(ptdev->clks.coregroup);
clk_disable_unprepare(ptdev->clks.stacks);
clk_disable_unprepare(ptdev->clks.core);
atomic_set(&ptdev->pm.state, PANTHOR_DEVICE_PM_STATE_SUSPENDED);
return 0;
}
@@ -0,0 +1,706 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2023 Collabora ltd. */
#ifndef __PANTHOR_DEVICE_H__
#define __PANTHOR_DEVICE_H__
#include <linux/atomic.h>
#include <linux/io-pgtable.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#include <drm/drm_mm.h>
#include <drm/gpu_scheduler.h>
#include <drm/panthor_drm.h>
struct panthor_csf;
struct panthor_csf_ctx;
struct panthor_device;
struct panthor_gpu;
struct panthor_group_pool;
struct panthor_heap_pool;
struct panthor_hw;
struct panthor_job;
struct panthor_mmu;
struct panthor_fw;
struct panthor_perfcnt;
struct panthor_pwr;
struct panthor_vm;
struct panthor_vm_pool;
/**
* struct panthor_soc_data - Panthor SoC Data
*/
struct panthor_soc_data {
/** @asn_hash_enable: True if GPU_L2_CONFIG_ASN_HASH_ENABLE must be set. */
bool asn_hash_enable;
/** @asn_hash: ASN_HASH values when asn_hash_enable is true. */
u32 asn_hash[3];
};
/**
* enum panthor_device_pm_state - PM state
*/
enum panthor_device_pm_state {
/** @PANTHOR_DEVICE_PM_STATE_SUSPENDED: Device is suspended. */
PANTHOR_DEVICE_PM_STATE_SUSPENDED = 0,
/** @PANTHOR_DEVICE_PM_STATE_RESUMING: Device is being resumed. */
PANTHOR_DEVICE_PM_STATE_RESUMING,
/** @PANTHOR_DEVICE_PM_STATE_ACTIVE: Device is active. */
PANTHOR_DEVICE_PM_STATE_ACTIVE,
/** @PANTHOR_DEVICE_PM_STATE_SUSPENDING: Device is being suspended. */
PANTHOR_DEVICE_PM_STATE_SUSPENDING,
};
enum panthor_irq_state {
/** @PANTHOR_IRQ_STATE_ACTIVE: IRQ is active and ready to process events. */
PANTHOR_IRQ_STATE_ACTIVE = 0,
/** @PANTHOR_IRQ_STATE_PROCESSING: IRQ is currently processing events. */
PANTHOR_IRQ_STATE_PROCESSING,
/** @PANTHOR_IRQ_STATE_SUSPENDED: IRQ is suspended. */
PANTHOR_IRQ_STATE_SUSPENDED,
/** @PANTHOR_IRQ_STATE_SUSPENDING: IRQ is being suspended. */
PANTHOR_IRQ_STATE_SUSPENDING,
};
/**
* struct panthor_irq - IRQ data
*
* Used to automate IRQ handling for the 3 different IRQs we have in this driver.
*/
struct panthor_irq {
/** @ptdev: Panthor device */
struct panthor_device *ptdev;
/** @iomem: CPU mapping of IRQ base address */
void __iomem *iomem;
/** @irq: IRQ number. */
int irq;
/** @mask: Values to write to xxx_INT_MASK if active. */
u32 mask;
/**
* @mask_lock: protects modifications to _INT_MASK and @mask.
*
* In paths where _INT_MASK is updated based on a state
* transition/check, it's crucial for the state update/check to be
* inside the locked section, otherwise it introduces a race window
* leading to potential _INT_MASK inconsistencies.
*/
spinlock_t mask_lock;
/** @state: one of &enum panthor_irq_state reflecting the current state. */
atomic_t state;
};
/**
* enum panthor_device_profiling_mode - Profiling state
*/
enum panthor_device_profiling_flags {
/** @PANTHOR_DEVICE_PROFILING_DISABLED: Profiling is disabled. */
PANTHOR_DEVICE_PROFILING_DISABLED = 0,
/** @PANTHOR_DEVICE_PROFILING_CYCLES: Sampling job cycles. */
PANTHOR_DEVICE_PROFILING_CYCLES = BIT(0),
/** @PANTHOR_DEVICE_PROFILING_TIMESTAMP: Sampling job timestamp. */
PANTHOR_DEVICE_PROFILING_TIMESTAMP = BIT(1),
/** @PANTHOR_DEVICE_PROFILING_ALL: Sampling everything. */
PANTHOR_DEVICE_PROFILING_ALL =
PANTHOR_DEVICE_PROFILING_CYCLES |
PANTHOR_DEVICE_PROFILING_TIMESTAMP,
};
/**
* struct panthor_device - Panthor device
*/
struct panthor_device {
/** @base: Base drm_device. */
struct drm_device base;
/** @soc_data: Optional SoC data. */
const struct panthor_soc_data *soc_data;
/** @phys_addr: Physical address of the iomem region. */
phys_addr_t phys_addr;
/** @iomem: CPU mapping of the IOMEM region. */
void __iomem *iomem;
/** @clks: GPU clocks. */
struct {
/** @core: Core clock. */
struct clk *core;
/** @stacks: Stacks clock. This clock is optional. */
struct clk *stacks;
/** @coregroup: Core group clock. This clock is optional. */
struct clk *coregroup;
} clks;
/** @coherent: True if the CPU/GPU are memory coherent. */
bool coherent;
/** @gpu_info: GPU information. */
struct drm_panthor_gpu_info gpu_info;
/** @csif_info: Command stream interface information. */
struct drm_panthor_csif_info csif_info;
/** @hw: GPU-specific data. */
struct panthor_hw *hw;
/** @pwr: Power control management data. */
struct panthor_pwr *pwr;
/** @gpu: GPU management data. */
struct panthor_gpu *gpu;
/** @fw: FW management data. */
struct panthor_fw *fw;
/** @mmu: MMU management data. */
struct panthor_mmu *mmu;
/** @scheduler: Scheduler management data. */
struct panthor_scheduler *scheduler;
/** @devfreq: Device frequency scaling management data. */
struct panthor_devfreq *devfreq;
/** @reclaim: Reclaim related stuff */
struct {
/** @reclaim.shrinker: Shrinker instance */
struct shrinker *shrinker;
/**
* @reclaim.unused: BOs with unused pages
*
* Basically all buffers that got mmapped, vmapped or GPU mapped and
* then unmapped. There should be no contention on these buffers,
* making them ideal to reclaim.
*/
struct drm_gem_lru unused;
/**
* @reclaim.mmapped: mmap()-ed buffers
*
* Those are relatively easy to reclaim since we don't need user
* agreement, we can simply teardown the mapping and let it fault on
* the next access.
*/
struct drm_gem_lru mmapped;
/**
* @reclaim.gpu_mapped_shared: shared BO LRU list
*
* That's the most tricky BO type to reclaim, because it involves
* tearing down all mappings in all VMs where this BO is mapped,
* which increases the risk of contention and thus decreases the
* likeliness of success.
*/
struct drm_gem_lru gpu_mapped_shared;
/**
* @reclaim.vms: VM LRU list
*
* VMs that have reclaimable BOs only mapped to a single VM are placed
* in this LRU. Reclaiming such BOs implies waiting for VM idleness
* (no in-flight GPU jobs targeting this VM), meaning we can't reclaim
* those if we're in a context where we can't block/sleep.
*/
struct list_head vms;
/**
* @reclaim.gpu_mapped_count: Global counter of pages that are GPU mapped
*
* Allows us to get the number of reclaimable pages without walking
* the vms and gpu_mapped_shared LRUs.
*/
long gpu_mapped_count;
/**
* @reclaim.retry_count: Number of times we ran the shrinker without being
* able to reclaim stuff
*
* Used to stop scanning GEMs when too many attempts were made
* without progress.
*/
atomic_t retry_count;
#ifdef CONFIG_DEBUG_FS
/**
* @reclaim.nr_pages_reclaimed_on_last_scan: Number of pages reclaimed on the last
* shrinker scan
*/
unsigned long nr_pages_reclaimed_on_last_scan;
#endif
} reclaim;
/** @unplug: Device unplug related fields. */
struct {
/** @lock: Lock used to serialize unplug operations. */
struct mutex lock;
/**
* @done: Completion object signaled when the unplug
* operation is done.
*/
struct completion done;
} unplug;
/** @reset: Reset related fields. */
struct {
/** @wq: Ordered worqueud used to schedule reset operations. */
struct workqueue_struct *wq;
/** @work: Reset work. */
struct work_struct work;
/** @pending: Set to true if a reset is pending. */
atomic_t pending;
/**
* @fast: True if the post_reset logic can proceed with a fast reset.
*
* A fast reset is just a reset where the driver doesn't reload the FW sections.
*
* Any time the firmware is properly suspended, a fast reset can take place.
* On the other hand, if the halt operation failed, the driver will reload
* all FW sections to make sure we start from a fresh state.
*/
bool fast;
} reset;
/** @pm: Power management related data. */
struct {
/** @state: Power state. */
atomic_t state;
/**
* @mmio_lock: Lock protecting MMIO userspace CPU mappings.
*
* This is needed to ensure we map the dummy IO pages when
* the device is being suspended, and the real IO pages when
* the device is being resumed. We can't just do with the
* state atomicity to deal with this race.
*/
struct mutex mmio_lock;
/**
* @dummy_latest_flush: Dummy LATEST_FLUSH page.
*
* Used to replace the real LATEST_FLUSH page when the GPU
* is suspended.
*/
struct page *dummy_latest_flush;
/** @recovery_needed: True when a resume attempt failed. */
atomic_t recovery_needed;
} pm;
/** @profile_mask: User-set profiling flags for job accounting. */
u32 profile_mask;
/** @fast_rate: Maximum device clock frequency. Set by DVFS */
unsigned long fast_rate;
#ifdef CONFIG_DEBUG_FS
/** @gems: Device-wide list of GEM objects owned by at least one file. */
struct {
/** @gems.lock: Protects the device-wide list of GEM objects. */
struct mutex lock;
/** @node: Used to keep track of all the device's DRM objects */
struct list_head node;
} gems;
#endif
};
struct panthor_gpu_usage {
u64 time;
u64 cycles;
};
/**
* struct panthor_file - Panthor file
*/
struct panthor_file {
/** @ptdev: Device attached to this file. */
struct panthor_device *ptdev;
/** @user_mmio: User MMIO related fields. */
struct {
/**
* @offset: Offset used for user MMIO mappings.
*
* This offset should not be used to check the type of mapping
* except in panthor_mmap(). After that point, MMIO mapping
* offsets have been adjusted to match
* DRM_PANTHOR_USER_MMIO_OFFSET and that macro should be used
* instead.
* Make sure this rule is followed at all times, because
* userspace is in control of the offset, and can change the
* value behind our back. Otherwise it can lead to erroneous
* branching happening in kernel space.
*/
u64 offset;
} user_mmio;
/** @vms: VM pool attached to this file. */
struct panthor_vm_pool *vms;
/** @groups: Scheduling group pool attached to this file. */
struct panthor_group_pool *groups;
/** @stats: cycle and timestamp measures for job execution. */
struct panthor_gpu_usage stats;
};
int panthor_device_init(struct panthor_device *ptdev);
void panthor_device_unplug(struct panthor_device *ptdev);
/**
* panthor_device_schedule_reset() - Schedules a reset operation
*/
static inline void panthor_device_schedule_reset(struct panthor_device *ptdev)
{
if (!atomic_cmpxchg(&ptdev->reset.pending, 0, 1) &&
atomic_read(&ptdev->pm.state) == PANTHOR_DEVICE_PM_STATE_ACTIVE)
queue_work(ptdev->reset.wq, &ptdev->reset.work);
}
/**
* panthor_device_reset_is_pending() - Checks if a reset is pending.
*
* Return: true if a reset is pending, false otherwise.
*/
static inline bool panthor_device_reset_is_pending(struct panthor_device *ptdev)
{
return atomic_read(&ptdev->reset.pending) != 0;
}
int panthor_device_mmap_io(struct panthor_device *ptdev,
struct vm_area_struct *vma);
int panthor_device_resume(struct device *dev);
int panthor_device_suspend(struct device *dev);
static inline int panthor_device_resume_and_get(struct panthor_device *ptdev)
{
int ret = pm_runtime_resume_and_get(ptdev->base.dev);
/* If the resume failed, we need to clear the runtime_error, which
* can done by forcing the RPM state to suspended. If multiple
* threads called panthor_device_resume_and_get(), we only want
* one of them to update the state, hence the cmpxchg. Note that a
* thread might enter panthor_device_resume_and_get() and call
* pm_runtime_resume_and_get() after another thread had attempted
* to resume and failed. This means we will end up with an error
* without even attempting a resume ourselves. The only risk here
* is to report an error when the second resume attempt might have
* succeeded. Given resume errors are not expected, this is probably
* something we can live with.
*/
if (ret && atomic_cmpxchg(&ptdev->pm.recovery_needed, 1, 0) == 1)
pm_runtime_set_suspended(ptdev->base.dev);
return ret;
}
enum drm_panthor_exception_type {
DRM_PANTHOR_EXCEPTION_OK = 0x00,
DRM_PANTHOR_EXCEPTION_TERMINATED = 0x04,
DRM_PANTHOR_EXCEPTION_KABOOM = 0x05,
DRM_PANTHOR_EXCEPTION_EUREKA = 0x06,
DRM_PANTHOR_EXCEPTION_ACTIVE = 0x08,
DRM_PANTHOR_EXCEPTION_CS_RES_TERM = 0x0f,
DRM_PANTHOR_EXCEPTION_MAX_NON_FAULT = 0x3f,
DRM_PANTHOR_EXCEPTION_CS_CONFIG_FAULT = 0x40,
DRM_PANTHOR_EXCEPTION_CS_UNRECOVERABLE = 0x41,
DRM_PANTHOR_EXCEPTION_CS_ENDPOINT_FAULT = 0x44,
DRM_PANTHOR_EXCEPTION_CS_BUS_FAULT = 0x48,
DRM_PANTHOR_EXCEPTION_CS_INSTR_INVALID = 0x49,
DRM_PANTHOR_EXCEPTION_CS_CALL_STACK_OVERFLOW = 0x4a,
DRM_PANTHOR_EXCEPTION_CS_INHERIT_FAULT = 0x4b,
DRM_PANTHOR_EXCEPTION_INSTR_INVALID_PC = 0x50,
DRM_PANTHOR_EXCEPTION_INSTR_INVALID_ENC = 0x51,
DRM_PANTHOR_EXCEPTION_INSTR_BARRIER_FAULT = 0x55,
DRM_PANTHOR_EXCEPTION_DATA_INVALID_FAULT = 0x58,
DRM_PANTHOR_EXCEPTION_TILE_RANGE_FAULT = 0x59,
DRM_PANTHOR_EXCEPTION_ADDR_RANGE_FAULT = 0x5a,
DRM_PANTHOR_EXCEPTION_IMPRECISE_FAULT = 0x5b,
DRM_PANTHOR_EXCEPTION_OOM = 0x60,
DRM_PANTHOR_EXCEPTION_CSF_FW_INTERNAL_ERROR = 0x68,
DRM_PANTHOR_EXCEPTION_CSF_RES_EVICTION_TIMEOUT = 0x69,
DRM_PANTHOR_EXCEPTION_GPU_BUS_FAULT = 0x80,
DRM_PANTHOR_EXCEPTION_GPU_SHAREABILITY_FAULT = 0x88,
DRM_PANTHOR_EXCEPTION_SYS_SHAREABILITY_FAULT = 0x89,
DRM_PANTHOR_EXCEPTION_GPU_CACHEABILITY_FAULT = 0x8a,
DRM_PANTHOR_EXCEPTION_TRANSLATION_FAULT_0 = 0xc0,
DRM_PANTHOR_EXCEPTION_TRANSLATION_FAULT_1 = 0xc1,
DRM_PANTHOR_EXCEPTION_TRANSLATION_FAULT_2 = 0xc2,
DRM_PANTHOR_EXCEPTION_TRANSLATION_FAULT_3 = 0xc3,
DRM_PANTHOR_EXCEPTION_TRANSLATION_FAULT_4 = 0xc4,
DRM_PANTHOR_EXCEPTION_PERM_FAULT_0 = 0xc8,
DRM_PANTHOR_EXCEPTION_PERM_FAULT_1 = 0xc9,
DRM_PANTHOR_EXCEPTION_PERM_FAULT_2 = 0xca,
DRM_PANTHOR_EXCEPTION_PERM_FAULT_3 = 0xcb,
DRM_PANTHOR_EXCEPTION_ACCESS_FLAG_1 = 0xd9,
DRM_PANTHOR_EXCEPTION_ACCESS_FLAG_2 = 0xda,
DRM_PANTHOR_EXCEPTION_ACCESS_FLAG_3 = 0xdb,
DRM_PANTHOR_EXCEPTION_ADDR_SIZE_FAULT_IN = 0xe0,
DRM_PANTHOR_EXCEPTION_ADDR_SIZE_FAULT_OUT0 = 0xe4,
DRM_PANTHOR_EXCEPTION_ADDR_SIZE_FAULT_OUT1 = 0xe5,
DRM_PANTHOR_EXCEPTION_ADDR_SIZE_FAULT_OUT2 = 0xe6,
DRM_PANTHOR_EXCEPTION_ADDR_SIZE_FAULT_OUT3 = 0xe7,
DRM_PANTHOR_EXCEPTION_MEM_ATTR_FAULT_0 = 0xe8,
DRM_PANTHOR_EXCEPTION_MEM_ATTR_FAULT_1 = 0xe9,
DRM_PANTHOR_EXCEPTION_MEM_ATTR_FAULT_2 = 0xea,
DRM_PANTHOR_EXCEPTION_MEM_ATTR_FAULT_3 = 0xeb,
};
/**
* panthor_exception_is_fault() - Checks if an exception is a fault.
*
* Return: true if the exception is a fault, false otherwise.
*/
static inline bool
panthor_exception_is_fault(u32 exception_code)
{
return exception_code > DRM_PANTHOR_EXCEPTION_MAX_NON_FAULT;
}
const char *panthor_exception_name(struct panthor_device *ptdev,
u32 exception_code);
#define INT_RAWSTAT 0x0
#define INT_CLEAR 0x4
#define INT_MASK 0x8
#define INT_STAT 0xc
/**
* PANTHOR_IRQ_HANDLER() - Define interrupt handlers and the interrupt
* registration function.
*
* The boiler-plate to gracefully deal with shared interrupts is
* auto-generated. All you have to do is call PANTHOR_IRQ_HANDLER()
* just after the actual handler. The handler prototype is:
*
* void (*handler)(struct panthor_device *, u32 status);
*/
#define PANTHOR_IRQ_HANDLER(__name, __handler) \
static irqreturn_t panthor_ ## __name ## _irq_raw_handler(int irq, void *data) \
{ \
struct panthor_irq *pirq = data; \
enum panthor_irq_state old_state; \
\
guard(spinlock_irqsave)(&pirq->mask_lock); \
old_state = atomic_cmpxchg(&pirq->state, \
PANTHOR_IRQ_STATE_ACTIVE, \
PANTHOR_IRQ_STATE_PROCESSING); \
if (old_state != PANTHOR_IRQ_STATE_ACTIVE) \
return IRQ_NONE; \
\
if (!gpu_read(pirq->iomem, INT_STAT)) { \
atomic_cmpxchg(&pirq->state, \
PANTHOR_IRQ_STATE_PROCESSING, \
PANTHOR_IRQ_STATE_ACTIVE); \
return IRQ_NONE; \
} \
\
gpu_write(pirq->iomem, INT_MASK, 0); \
return IRQ_WAKE_THREAD; \
} \
\
static irqreturn_t panthor_ ## __name ## _irq_threaded_handler(int irq, void *data) \
{ \
struct panthor_irq *pirq = data; \
struct panthor_device *ptdev = pirq->ptdev; \
irqreturn_t ret = IRQ_NONE; \
\
while (true) { \
/* It's safe to access pirq->mask without the lock held here. If a new \
* event gets added to the mask and the corresponding IRQ is pending, \
* we'll process it right away instead of adding an extra raw -> threaded \
* round trip. If an event is removed and the status bit is set, it will \
* be ignored, just like it would have been if the mask had been adjusted \
* right before the HW event kicks in. TLDR; it's all expected races we're \
* covered for. \
*/ \
u32 status = gpu_read(pirq->iomem, INT_RAWSTAT) & pirq->mask; \
\
if (!status) \
break; \
\
__handler(ptdev, status); \
ret = IRQ_HANDLED; \
} \
\
scoped_guard(spinlock_irqsave, &pirq->mask_lock) { \
enum panthor_irq_state old_state; \
\
old_state = atomic_cmpxchg(&pirq->state, \
PANTHOR_IRQ_STATE_PROCESSING, \
PANTHOR_IRQ_STATE_ACTIVE); \
if (old_state == PANTHOR_IRQ_STATE_PROCESSING) \
gpu_write(pirq->iomem, INT_MASK, pirq->mask); \
} \
\
return ret; \
} \
\
static inline void panthor_ ## __name ## _irq_suspend(struct panthor_irq *pirq) \
{ \
scoped_guard(spinlock_irqsave, &pirq->mask_lock) { \
atomic_set(&pirq->state, PANTHOR_IRQ_STATE_SUSPENDING); \
gpu_write(pirq->iomem, INT_MASK, 0); \
} \
synchronize_irq(pirq->irq); \
atomic_set(&pirq->state, PANTHOR_IRQ_STATE_SUSPENDED); \
} \
\
static inline void panthor_ ## __name ## _irq_resume(struct panthor_irq *pirq) \
{ \
guard(spinlock_irqsave)(&pirq->mask_lock); \
\
atomic_set(&pirq->state, PANTHOR_IRQ_STATE_ACTIVE); \
gpu_write(pirq->iomem, INT_CLEAR, pirq->mask); \
gpu_write(pirq->iomem, INT_MASK, pirq->mask); \
} \
\
static int panthor_request_ ## __name ## _irq(struct panthor_device *ptdev, \
struct panthor_irq *pirq, \
int irq, void __iomem *iomem) \
{ \
pirq->ptdev = ptdev; \
pirq->irq = irq; \
pirq->mask = 0; \
pirq->iomem = iomem; \
spin_lock_init(&pirq->mask_lock); \
atomic_set(&pirq->state, PANTHOR_IRQ_STATE_SUSPENDED); \
gpu_write(pirq->iomem, INT_MASK, 0); \
\
return devm_request_threaded_irq(ptdev->base.dev, irq, \
panthor_ ## __name ## _irq_raw_handler, \
panthor_ ## __name ## _irq_threaded_handler, \
IRQF_SHARED, KBUILD_MODNAME "-" # __name, \
pirq); \
} \
\
static inline void panthor_ ## __name ## _irq_enable_events(struct panthor_irq *pirq, u32 mask) \
{ \
guard(spinlock_irqsave)(&pirq->mask_lock); \
pirq->mask |= mask; \
\
/* The only situation where we need to write the new mask is if the IRQ is active. \
* If it's being processed, the mask will be restored for us in _irq_threaded_handler() \
* on the PROCESSING -> ACTIVE transition. \
* If the IRQ is suspended/suspending, the mask is restored at resume time. \
*/ \
if (atomic_read(&pirq->state) == PANTHOR_IRQ_STATE_ACTIVE) \
gpu_write(pirq->iomem, INT_MASK, pirq->mask); \
} \
\
static inline void panthor_ ## __name ## _irq_disable_events(struct panthor_irq *pirq, u32 mask)\
{ \
guard(spinlock_irqsave)(&pirq->mask_lock); \
pirq->mask &= ~mask; \
\
/* The only situation where we need to write the new mask is if the IRQ is active. \
* If it's being processed, the mask will be restored for us in _irq_threaded_handler() \
* on the PROCESSING -> ACTIVE transition. \
* If the IRQ is suspended/suspending, the mask is restored at resume time. \
*/ \
if (atomic_read(&pirq->state) == PANTHOR_IRQ_STATE_ACTIVE) \
gpu_write(pirq->iomem, INT_MASK, pirq->mask); \
}
extern struct workqueue_struct *panthor_cleanup_wq;
static inline void gpu_write(void __iomem *iomem, u32 reg, u32 data)
{
writel(data, iomem + reg);
}
static inline u32 gpu_read(void __iomem *iomem, u32 reg)
{
return readl(iomem + reg);
}
static inline u32 gpu_read_relaxed(void __iomem *iomem, u32 reg)
{
return readl_relaxed(iomem + reg);
}
static inline void gpu_write64(void __iomem *iomem, u32 reg, u64 data)
{
gpu_write(iomem, reg, lower_32_bits(data));
gpu_write(iomem, reg + 4, upper_32_bits(data));
}
static inline u64 gpu_read64(void __iomem *iomem, u32 reg)
{
return (gpu_read(iomem, reg) | ((u64)gpu_read(iomem, reg + 4) << 32));
}
static inline u64 gpu_read64_relaxed(void __iomem *iomem, u32 reg)
{
return (gpu_read_relaxed(iomem, reg) |
((u64)gpu_read_relaxed(iomem, reg + 4) << 32));
}
static inline u64 gpu_read64_counter(void __iomem *iomem, u32 reg)
{
u32 lo, hi1, hi2;
do {
hi1 = gpu_read(iomem, reg + 4);
lo = gpu_read(iomem, reg);
hi2 = gpu_read(iomem, reg + 4);
} while (hi1 != hi2);
return lo | ((u64)hi2 << 32);
}
#define gpu_read_poll_timeout(iomem, reg, val, cond, delay_us, timeout_us) \
read_poll_timeout(gpu_read, val, cond, delay_us, timeout_us, false, \
iomem, reg)
#define gpu_read_poll_timeout_atomic(iomem, reg, val, cond, delay_us, \
timeout_us) \
read_poll_timeout_atomic(gpu_read, val, cond, delay_us, timeout_us, \
false, iomem, reg)
#define gpu_read64_poll_timeout(iomem, reg, val, cond, delay_us, timeout_us) \
read_poll_timeout(gpu_read64, val, cond, delay_us, timeout_us, false, \
iomem, reg)
#define gpu_read64_poll_timeout_atomic(iomem, reg, val, cond, delay_us, \
timeout_us) \
read_poll_timeout_atomic(gpu_read64, val, cond, delay_us, timeout_us, \
false, iomem, reg)
#define gpu_read_relaxed_poll_timeout_atomic(iomem, reg, val, cond, delay_us, \
timeout_us) \
read_poll_timeout_atomic(gpu_read_relaxed, val, cond, delay_us, \
timeout_us, false, iomem, reg)
#define gpu_read64_relaxed_poll_timeout(iomem, reg, val, cond, delay_us, \
timeout_us) \
read_poll_timeout(gpu_read64_relaxed, val, cond, delay_us, timeout_us, \
false, iomem, reg)
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 Amazon.com, Inc. or its affiliates */
#ifndef __PANTHOR_DRV_H__
#define __PANTHOR_DRV_H__
#include <linux/types.h>
extern bool panthor_transparent_hugepage;
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,532 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2023 Collabora ltd. */
#ifndef __PANTHOR_MCU_H__
#define __PANTHOR_MCU_H__
#include <linux/types.h>
struct panthor_device;
struct panthor_kernel_bo;
#define MAX_CSGS 31
#define MAX_CS_PER_CSG 32
struct panthor_fw_ringbuf_input_iface {
u64 insert;
u64 extract;
};
struct panthor_fw_ringbuf_output_iface {
u64 extract;
u32 active;
};
struct panthor_fw_cs_control_iface {
#define CS_FEATURES_WORK_REGS(x) (((x) & GENMASK(7, 0)) + 1)
#define CS_FEATURES_SCOREBOARDS(x) (((x) & GENMASK(15, 8)) >> 8)
#define CS_FEATURES_COMPUTE BIT(16)
#define CS_FEATURES_FRAGMENT BIT(17)
#define CS_FEATURES_TILER BIT(18)
u32 features;
u32 input_va;
u32 output_va;
};
struct panthor_fw_cs_input_iface {
#define CS_STATE_MASK GENMASK(2, 0)
#define CS_STATE_STOP 0
#define CS_STATE_START 1
#define CS_EXTRACT_EVENT BIT(4)
#define CS_IDLE_SYNC_WAIT BIT(8)
#define CS_IDLE_PROTM_PENDING BIT(9)
#define CS_IDLE_EMPTY BIT(10)
#define CS_IDLE_RESOURCE_REQ BIT(11)
#define CS_TILER_OOM BIT(26)
#define CS_PROTM_PENDING BIT(27)
#define CS_FATAL BIT(30)
#define CS_FAULT BIT(31)
#define CS_REQ_MASK (CS_STATE_MASK | \
CS_EXTRACT_EVENT | \
CS_IDLE_SYNC_WAIT | \
CS_IDLE_PROTM_PENDING | \
CS_IDLE_EMPTY | \
CS_IDLE_RESOURCE_REQ)
#define CS_EVT_MASK (CS_TILER_OOM | \
CS_PROTM_PENDING | \
CS_FATAL | \
CS_FAULT)
u32 req;
#define CS_CONFIG_PRIORITY(x) ((x) & GENMASK(3, 0))
#define CS_CONFIG_DOORBELL(x) (((x) << 8) & GENMASK(15, 8))
u32 config;
u32 reserved1;
u32 ack_irq_mask;
u64 ringbuf_base;
u32 ringbuf_size;
u32 reserved2;
u64 heap_start;
u64 heap_end;
u64 ringbuf_input;
u64 ringbuf_output;
u32 instr_config;
u32 instrbuf_size;
u64 instrbuf_base;
u64 instrbuf_offset_ptr;
};
struct panthor_fw_cs_output_iface {
u32 ack;
u32 reserved1[15];
u64 status_cmd_ptr;
#define CS_STATUS_WAIT_SB_MASK GENMASK(15, 0)
#define CS_STATUS_WAIT_SB_SRC_MASK GENMASK(19, 16)
#define CS_STATUS_WAIT_SB_SRC_NONE (0 << 16)
#define CS_STATUS_WAIT_SB_SRC_WAIT (8 << 16)
#define CS_STATUS_WAIT_SYNC_COND_LE (0 << 24)
#define CS_STATUS_WAIT_SYNC_COND_GT (1 << 24)
#define CS_STATUS_WAIT_SYNC_COND_MASK GENMASK(27, 24)
#define CS_STATUS_WAIT_PROGRESS BIT(28)
#define CS_STATUS_WAIT_PROTM BIT(29)
#define CS_STATUS_WAIT_SYNC_64B BIT(30)
#define CS_STATUS_WAIT_SYNC BIT(31)
u32 status_wait;
u32 status_req_resource;
u64 status_wait_sync_ptr;
u32 status_wait_sync_value;
u32 status_scoreboards;
#define CS_STATUS_BLOCKED_REASON_UNBLOCKED 0
#define CS_STATUS_BLOCKED_REASON_SB_WAIT 1
#define CS_STATUS_BLOCKED_REASON_PROGRESS_WAIT 2
#define CS_STATUS_BLOCKED_REASON_SYNC_WAIT 3
#define CS_STATUS_BLOCKED_REASON_DEFERRED 4
#define CS_STATUS_BLOCKED_REASON_RESOURCE 5
#define CS_STATUS_BLOCKED_REASON_FLUSH 6
#define CS_STATUS_BLOCKED_REASON_MASK GENMASK(3, 0)
u32 status_blocked_reason;
u32 status_wait_sync_value_hi;
u32 reserved2[6];
#define CS_EXCEPTION_TYPE(x) ((x) & GENMASK(7, 0))
#define CS_EXCEPTION_DATA(x) (((x) >> 8) & GENMASK(23, 0))
u32 fault;
u32 fatal;
u64 fault_info;
u64 fatal_info;
u32 reserved3[10];
u32 heap_vt_start;
u32 heap_vt_end;
u32 reserved4;
u32 heap_frag_end;
u64 heap_address;
};
struct panthor_fw_csg_control_iface {
u32 features;
u32 input_va;
u32 output_va;
u32 suspend_size;
u32 protm_suspend_size;
u32 stream_num;
u32 stream_stride;
};
struct panthor_fw_csg_input_iface {
#define CSG_STATE_MASK GENMASK(2, 0)
#define CSG_STATE_TERMINATE 0
#define CSG_STATE_START 1
#define CSG_STATE_SUSPEND 2
#define CSG_STATE_RESUME 3
#define CSG_ENDPOINT_CONFIG BIT(4)
#define CSG_STATUS_UPDATE BIT(5)
#define CSG_SYNC_UPDATE BIT(28)
#define CSG_IDLE BIT(29)
#define CSG_DOORBELL BIT(30)
#define CSG_PROGRESS_TIMER_EVENT BIT(31)
#define CSG_REQ_MASK (CSG_STATE_MASK | \
CSG_ENDPOINT_CONFIG | \
CSG_STATUS_UPDATE)
#define CSG_EVT_MASK (CSG_SYNC_UPDATE | \
CSG_IDLE | \
CSG_PROGRESS_TIMER_EVENT)
u32 req;
u32 ack_irq_mask;
u32 doorbell_req;
u32 cs_irq_ack;
u32 reserved1[4];
u64 allow_compute;
u64 allow_fragment;
u32 allow_other;
#define CSG_EP_REQ_COMPUTE(x) ((x) & GENMASK(7, 0))
#define CSG_EP_REQ_FRAGMENT(x) (((x) << 8) & GENMASK(15, 8))
#define CSG_EP_REQ_TILER(x) (((x) << 16) & GENMASK(19, 16))
#define CSG_EP_REQ_EXCL_COMPUTE BIT(20)
#define CSG_EP_REQ_EXCL_FRAGMENT BIT(21)
#define CSG_EP_REQ_PRIORITY_MASK GENMASK(31, 28)
#define CSG_EP_REQ_PRIORITY(x) (((x) << 28) & CSG_EP_REQ_PRIORITY_MASK)
#define CSG_EP_REQ_PRIORITY_GET(x) (((x) & CSG_EP_REQ_PRIORITY_MASK) >> 28)
u32 endpoint_req;
u64 endpoint_req2;
u64 suspend_buf;
u64 protm_suspend_buf;
u32 config;
u32 iter_trace_config;
};
struct panthor_fw_csg_output_iface {
u32 ack;
u32 reserved1;
u32 doorbell_ack;
u32 cs_irq_req;
u32 status_endpoint_current;
u32 status_endpoint_req;
#define CSG_STATUS_STATE_IS_IDLE BIT(0)
u32 status_state;
u32 resource_dep;
};
struct panthor_fw_global_control_iface {
u32 version;
u32 features;
u32 input_va;
u32 output_va;
u32 group_num;
u32 group_stride;
u32 perfcnt_size;
u32 instr_features;
};
struct panthor_fw_global_input_iface {
#define GLB_HALT BIT(0)
#define GLB_CFG_PROGRESS_TIMER BIT(1)
#define GLB_CFG_ALLOC_EN BIT(2)
#define GLB_CFG_POWEROFF_TIMER BIT(3)
#define GLB_PROTM_ENTER BIT(4)
#define GLB_PERFCNT_EN BIT(5)
#define GLB_PERFCNT_SAMPLE BIT(6)
#define GLB_COUNTER_EN BIT(7)
#define GLB_PING BIT(8)
#define GLB_FWCFG_UPDATE BIT(9)
#define GLB_IDLE_EN BIT(10)
#define GLB_SLEEP BIT(12)
#define GLB_STATE_MASK GENMASK(14, 12)
#define GLB_STATE_ACTIVE 0
#define GLB_STATE_HALT 1
#define GLB_STATE_SLEEP 2
#define GLB_STATE_SUSPEND 3
#define GLB_STATE(x) (((x) << 12) & GLB_STATE_MASK)
#define GLB_STATE_GET(x) (((x) & GLB_STATE_MASK) >> 12)
#define GLB_INACTIVE_COMPUTE BIT(20)
#define GLB_INACTIVE_FRAGMENT BIT(21)
#define GLB_INACTIVE_TILER BIT(22)
#define GLB_PROTM_EXIT BIT(23)
#define GLB_PERFCNT_THRESHOLD BIT(24)
#define GLB_PERFCNT_OVERFLOW BIT(25)
#define GLB_IDLE BIT(26)
#define GLB_DBG_CSF BIT(30)
#define GLB_DBG_HOST BIT(31)
#define GLB_REQ_MASK GENMASK(10, 0)
#define GLB_EVT_MASK GENMASK(26, 20)
u32 req;
u32 ack_irq_mask;
u32 doorbell_req;
u32 reserved1;
u32 progress_timer;
#define GLB_TIMER_VAL(x) ((x) & GENMASK(30, 0))
#define GLB_TIMER_SOURCE_GPU_COUNTER BIT(31)
u32 poweroff_timer;
u64 core_en_mask;
u32 reserved2;
u32 perfcnt_as;
u64 perfcnt_base;
u32 perfcnt_extract;
u32 reserved3[3];
u32 perfcnt_config;
u32 perfcnt_csg_select;
u32 perfcnt_fw_enable;
u32 perfcnt_csg_enable;
u32 perfcnt_csf_enable;
u32 perfcnt_shader_enable;
u32 perfcnt_tiler_enable;
u32 perfcnt_mmu_l2_enable;
u32 reserved4[8];
u32 idle_timer;
};
enum panthor_fw_halt_status {
PANTHOR_FW_HALT_OK = 0,
PANTHOR_FW_HALT_ON_PANIC = 0x4e,
PANTHOR_FW_HALT_ON_WATCHDOG_EXPIRATION = 0x4f,
};
struct panthor_fw_global_output_iface {
u32 ack;
u32 reserved1;
u32 doorbell_ack;
u32 reserved2;
u32 halt_status;
u32 perfcnt_status;
u32 perfcnt_insert;
};
/**
* struct panthor_fw_cs_iface - Firmware command stream slot interface
*/
struct panthor_fw_cs_iface {
/**
* @lock: Lock protecting access to the panthor_fw_cs_input_iface::req
* field.
*
* Needed so we can update the req field concurrently from the interrupt
* handler and the scheduler logic.
*
* TODO: Ideally we'd want to use a cmpxchg() to update the req, but FW
* interface sections are mapped uncached/write-combined right now, and
* using cmpxchg() on such mappings leads to SError faults. Revisit when
* we have 'SHARED' GPU mappings hooked up.
*/
spinlock_t lock;
/**
* @control: Command stream slot control interface.
*
* Used to expose command stream slot properties.
*
* This interface is read-only.
*/
struct panthor_fw_cs_control_iface *control;
/**
* @input: Command stream slot input interface.
*
* Used for host updates/events.
*/
struct panthor_fw_cs_input_iface *input;
/**
* @output: Command stream slot output interface.
*
* Used for FW updates/events.
*
* This interface is read-only.
*/
const struct panthor_fw_cs_output_iface *output;
};
/**
* struct panthor_fw_csg_iface - Firmware command stream group slot interface
*/
struct panthor_fw_csg_iface {
/**
* @lock: Lock protecting access to the panthor_fw_csg_input_iface::req
* field.
*
* Needed so we can update the req field concurrently from the interrupt
* handler and the scheduler logic.
*
* TODO: Ideally we'd want to use a cmpxchg() to update the req, but FW
* interface sections are mapped uncached/write-combined right now, and
* using cmpxchg() on such mappings leads to SError faults. Revisit when
* we have 'SHARED' GPU mappings hooked up.
*/
spinlock_t lock;
/**
* @control: Command stream group slot control interface.
*
* Used to expose command stream group slot properties.
*
* This interface is read-only.
*/
const struct panthor_fw_csg_control_iface *control;
/**
* @input: Command stream slot input interface.
*
* Used for host updates/events.
*/
struct panthor_fw_csg_input_iface *input;
/**
* @output: Command stream group slot output interface.
*
* Used for FW updates/events.
*
* This interface is read-only.
*/
const struct panthor_fw_csg_output_iface *output;
};
/**
* struct panthor_fw_global_iface - Firmware global interface
*/
struct panthor_fw_global_iface {
/**
* @lock: Lock protecting access to the panthor_fw_global_input_iface::req
* field.
*
* Needed so we can update the req field concurrently from the interrupt
* handler and the scheduler/FW management logic.
*
* TODO: Ideally we'd want to use a cmpxchg() to update the req, but FW
* interface sections are mapped uncached/write-combined right now, and
* using cmpxchg() on such mappings leads to SError faults. Revisit when
* we have 'SHARED' GPU mappings hooked up.
*/
spinlock_t lock;
/**
* @control: Command stream group slot control interface.
*
* Used to expose global FW properties.
*
* This interface is read-only.
*/
const struct panthor_fw_global_control_iface *control;
/**
* @input: Global input interface.
*
* Used for host updates/events.
*/
struct panthor_fw_global_input_iface *input;
/**
* @output: Global output interface.
*
* Used for FW updates/events.
*
* This interface is read-only.
*/
const struct panthor_fw_global_output_iface *output;
};
/**
* panthor_fw_toggle_reqs() - Toggle acknowledge bits to send an event to the FW
* @__iface: The interface to operate on.
* @__in_reg: Name of the register to update in the input section of the interface.
* @__out_reg: Name of the register to take as a reference in the output section of the
* interface.
* @__mask: Mask to apply to the update.
*
* The Host -> FW event/message passing was designed to be lockless, with each side of
* the channel having its writeable section. Events are signaled as a difference between
* the host and FW side in the req/ack registers (when a bit differs, there's an event
* pending, when they are the same, nothing needs attention).
*
* This helper allows one to update the req register based on the current value of the
* ack register managed by the FW. Toggling a specific bit will flag an event. In order
* for events to be re-evaluated, the interface doorbell needs to be rung.
*
* Concurrent accesses to the same req register is covered.
*
* Anything requiring atomic updates to multiple registers requires a dedicated lock.
*/
#define panthor_fw_toggle_reqs(__iface, __in_reg, __out_reg, __mask) \
do { \
u32 __cur_val, __new_val, __out_val; \
spin_lock(&(__iface)->lock); \
__cur_val = READ_ONCE((__iface)->input->__in_reg); \
__out_val = READ_ONCE((__iface)->output->__out_reg); \
__new_val = ((__out_val ^ (__mask)) & (__mask)) | (__cur_val & ~(__mask)); \
WRITE_ONCE((__iface)->input->__in_reg, __new_val); \
spin_unlock(&(__iface)->lock); \
} while (0)
/**
* panthor_fw_update_reqs() - Update bits to reflect a configuration change
* @__iface: The interface to operate on.
* @__in_reg: Name of the register to update in the input section of the interface.
* @__val: Value to set.
* @__mask: Mask to apply to the update.
*
* Some configuration get passed through req registers that are also used to
* send events to the FW. Those req registers being updated from the interrupt
* handler, they require special helpers to update the configuration part as well.
*
* Concurrent accesses to the same req register is covered.
*
* Anything requiring atomic updates to multiple registers requires a dedicated lock.
*/
#define panthor_fw_update_reqs(__iface, __in_reg, __val, __mask) \
do { \
u32 __cur_val, __new_val; \
spin_lock(&(__iface)->lock); \
__cur_val = READ_ONCE((__iface)->input->__in_reg); \
__new_val = (__cur_val & ~(__mask)) | ((__val) & (__mask)); \
WRITE_ONCE((__iface)->input->__in_reg, __new_val); \
spin_unlock(&(__iface)->lock); \
} while (0)
#define panthor_fw_update_reqs64(__iface, __in_reg, __val, __mask) \
do { \
u64 __cur_val, __new_val; \
spin_lock(&(__iface)->lock); \
__cur_val = READ_ONCE((__iface)->input->__in_reg); \
__new_val = (__cur_val & ~(__mask)) | ((__val) & (__mask)); \
WRITE_ONCE((__iface)->input->__in_reg, __new_val); \
spin_unlock(&(__iface)->lock); \
} while (0)
struct panthor_fw_global_iface *
panthor_fw_get_glb_iface(struct panthor_device *ptdev);
struct panthor_fw_csg_iface *
panthor_fw_get_csg_iface(struct panthor_device *ptdev, u32 csg_slot);
struct panthor_fw_cs_iface *
panthor_fw_get_cs_iface(struct panthor_device *ptdev, u32 csg_slot, u32 cs_slot);
u64 panthor_fw_csg_endpoint_req_get(struct panthor_device *ptdev,
struct panthor_fw_csg_iface *csg_iface);
void panthor_fw_csg_endpoint_req_set(struct panthor_device *ptdev,
struct panthor_fw_csg_iface *csg_iface, u64 value);
void panthor_fw_csg_endpoint_req_update(struct panthor_device *ptdev,
struct panthor_fw_csg_iface *csg_iface, u64 value,
u64 mask);
int panthor_fw_csg_wait_acks(struct panthor_device *ptdev, u32 csg_id, u32 req_mask,
u32 *acked, u32 timeout_ms);
int panthor_fw_glb_wait_acks(struct panthor_device *ptdev, u32 req_mask, u32 *acked,
u32 timeout_ms);
void panthor_fw_ring_doorbell(struct panthor_device *ptdev, u32 doorbell_id);
void panthor_fw_ring_csg_doorbells(struct panthor_device *ptdev, u32 csg_slot);
struct panthor_kernel_bo *
panthor_fw_alloc_queue_iface_mem(struct panthor_device *ptdev,
struct panthor_fw_ringbuf_input_iface **input,
const struct panthor_fw_ringbuf_output_iface **output,
u32 *input_fw_va, u32 *output_fw_va);
struct panthor_kernel_bo *
panthor_fw_alloc_suspend_buf_mem(struct panthor_device *ptdev, size_t size);
struct panthor_vm *panthor_fw_vm(struct panthor_device *ptdev);
void panthor_fw_pre_reset(struct panthor_device *ptdev, bool on_hang);
int panthor_fw_post_reset(struct panthor_device *ptdev);
static inline void panthor_fw_suspend(struct panthor_device *ptdev)
{
panthor_fw_pre_reset(ptdev, false);
}
static inline int panthor_fw_resume(struct panthor_device *ptdev)
{
return panthor_fw_post_reset(ptdev);
}
int panthor_fw_init(struct panthor_device *ptdev);
void panthor_fw_unplug(struct panthor_device *ptdev);
#endif
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2026 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_FW_REGS_H__
#define __PANTHOR_FW_REGS_H__
#define MCU_CONTROL_BASE 0x700
#define MCU_CONTROL 0x0
#define MCU_CONTROL_ENABLE 1
#define MCU_CONTROL_AUTO 2
#define MCU_CONTROL_DISABLE 0
#define MCU_STATUS 0x4
#define MCU_STATUS_DISABLED 0
#define MCU_STATUS_ENABLED 1
#define MCU_STATUS_HALT 2
#define MCU_STATUS_FATAL 3
#define JOB_INT_BASE 0x1000
#define JOB_INT_GLOBAL_IF BIT(31)
#define JOB_INT_CSG_IF(x) BIT(x)
#define CSF_GPU_LATEST_FLUSH_ID 0x10000
#define CSF_DOORBELL(i) (0x80000 + ((i) * 0x10000))
#define CSF_GLB_DOORBELL_ID 0
#endif /* __PANTHOR_FW_REGS_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,332 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2023 Collabora ltd. */
/* Copyright 2025 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_GEM_H__
#define __PANTHOR_GEM_H__
#include <drm/drm_gem.h>
#include <drm/drm_mm.h>
#include <linux/iosys-map.h>
#include <linux/rwsem.h>
struct panthor_vm;
#define PANTHOR_BO_LABEL_MAXLEN 4096
enum panthor_debugfs_gem_state_flags {
PANTHOR_DEBUGFS_GEM_STATE_IMPORTED_BIT = 0,
PANTHOR_DEBUGFS_GEM_STATE_EXPORTED_BIT = 1,
PANTHOR_DEBUGFS_GEM_STATE_EVICTED_BIT = 2,
/** @PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED: GEM BO is PRIME imported. */
PANTHOR_DEBUGFS_GEM_STATE_FLAG_IMPORTED = BIT(PANTHOR_DEBUGFS_GEM_STATE_IMPORTED_BIT),
/** @PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED: GEM BO is PRIME exported. */
PANTHOR_DEBUGFS_GEM_STATE_FLAG_EXPORTED = BIT(PANTHOR_DEBUGFS_GEM_STATE_EXPORTED_BIT),
/** @PANTHOR_DEBUGFS_GEM_STATE_FLAG_EVICTED: GEM BO is evicted to swap. */
PANTHOR_DEBUGFS_GEM_STATE_FLAG_EVICTED = BIT(PANTHOR_DEBUGFS_GEM_STATE_EVICTED_BIT),
};
enum panthor_debugfs_gem_usage_flags {
PANTHOR_DEBUGFS_GEM_USAGE_KERNEL_BIT = 0,
PANTHOR_DEBUGFS_GEM_USAGE_FW_MAPPED_BIT = 1,
/** @PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL: BO is for kernel use only. */
PANTHOR_DEBUGFS_GEM_USAGE_FLAG_KERNEL = BIT(PANTHOR_DEBUGFS_GEM_USAGE_KERNEL_BIT),
/** @PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED: BO is mapped on the FW VM. */
PANTHOR_DEBUGFS_GEM_USAGE_FLAG_FW_MAPPED = BIT(PANTHOR_DEBUGFS_GEM_USAGE_FW_MAPPED_BIT),
};
/**
* struct panthor_gem_debugfs - GEM object's DebugFS list information
*/
struct panthor_gem_debugfs {
/**
* @node: Node used to insert the object in the device-wide list of
* GEM objects, to display information about it through a DebugFS file.
*/
struct list_head node;
/** @creator: Information about the UM process which created the GEM. */
struct {
/** @creator.process_name: Group leader name in owning thread's process */
char process_name[TASK_COMM_LEN];
/** @creator.tgid: PID of the thread's group leader within its process */
pid_t tgid;
} creator;
/** @flags: Combination of panthor_debugfs_gem_usage_flags flags */
u32 flags;
};
/**
* struct panthor_gem_backing - GEM memory backing related data
*/
struct panthor_gem_backing {
/** @pages: Pages requested with drm_gem_get_pages() */
struct page **pages;
/** @pin_count: Number of active pin requests on this GEM */
refcount_t pin_count;
};
/**
* struct panthor_gem_cpu_map - GEM CPU mapping related data
*/
struct panthor_gem_cpu_map {
/** @vaddr: Address returned by vmap() */
void *vaddr;
/** @vaddr_use_count: Number of active vmap() requests on this GEM */
refcount_t vaddr_use_count;
/** @mmap_count: Number of active mmap() requests on this GEM */
refcount_t mmap_count;
};
/**
* struct panthor_gem_dev_map - GEM device mapping related data
*/
struct panthor_gem_dev_map {
/** @sgt: Device mapped sg_table for this GEM */
struct sg_table *sgt;
};
/**
* enum panthor_gem_reclaim_state - Reclaim state of a GEM object
*
* This is defined in descending reclaimability order and some part
* of the code depends on that.
*/
enum panthor_gem_reclaim_state {
/**
* @PANTHOR_GEM_UNUSED: GEM is currently unused
*
* This can happen when the GEM was previously vmap-ed, mmap-ed,
* and/or GPU mapped and got unmapped. Because pages are lazily
* returned to the shmem layer, we want to keep a list of such
* BOs, because they should be fairly easy to reclaim (no need
* to wait for GPU to be done, and no need to tear down user
* mappings either).
*/
PANTHOR_GEM_UNUSED,
/**
* @PANTHOR_GEM_MMAPPED: GEM is currently mmap-ed
*
* When a GEM has pages allocated and the mmap_count is > 0, the
* GEM is placed in the mmapped list. This comes right after
* unused because we can relatively easily tear down user mappings.
*/
PANTHOR_GEM_MMAPPED,
/**
* @PANTHOR_GEM_GPU_MAPPED_SINGLE_VM: GEM is GPU mapped to only one VM
*
* When a GEM is mapped to a single VM, reclaim requests have more
* chances to succeed, because we only need to synchronize against
* a single GPU context. This is more annoying than reclaiming
* mmap-ed pages still, because we have to wait for in-flight jobs
* to land, and we might not be able to acquire all necessary locks
* at reclaim time either.
*/
PANTHOR_GEM_GPU_MAPPED_SINGLE_VM,
/**
* @PANTHOR_GEM_GPU_MAPPED_MULTI_VM: GEM is GPU mapped to multiple VMs
*
* Like PANTHOR_GEM_GPU_MAPPED_SINGLE_VM, but the synchronization across
* VMs makes such BOs harder to reclaim.
*/
PANTHOR_GEM_GPU_MAPPED_MULTI_VM,
/**
* @PANTHOR_GEM_UNRECLAIMABLE: GEM can't be reclaimed
*
* Happens when the GEM memory is pinned. It's also the state all GEM
* objects start in, because no memory is allocated until explicitly
* requested by a CPU or GPU map, meaning there's nothing to reclaim
* until such an allocation happens.
*/
PANTHOR_GEM_UNRECLAIMABLE,
};
/**
* struct panthor_gem_object - Driver specific GEM object.
*/
struct panthor_gem_object {
/** @base: Inherit from drm_gem_object. */
struct drm_gem_object base;
/** @backing: Memory backing state */
struct panthor_gem_backing backing;
/** @cmap: CPU mapping state */
struct panthor_gem_cpu_map cmap;
/** @dmap: Device mapping state */
struct panthor_gem_dev_map dmap;
/** @reclaim_state: Cached reclaim state */
enum panthor_gem_reclaim_state reclaim_state;
/**
* @reclaimed_count: How many times object has been evicted to swap.
* The count saturates at %INT_MAX and will never wrap around to 0.
*/
atomic_t reclaimed_count;
/**
* @exclusive_vm_root_gem: Root GEM of the exclusive VM this GEM object
* is attached to.
*
* If @exclusive_vm_root_gem != NULL, any attempt to bind the GEM to a
* different VM will fail.
*
* All FW memory objects have this field set to the root GEM of the MCU
* VM.
*/
struct drm_gem_object *exclusive_vm_root_gem;
/** @flags: Combination of drm_panthor_bo_flags flags. */
u32 flags;
/**
* @label: BO tagging fields. The label can be assigned within the
* driver itself or through a specific IOCTL.
*/
struct {
/**
* @label.str: Pointer to NULL-terminated string,
*/
const char *str;
/** @lock.str: Protects access to the @label.str field. */
struct mutex lock;
} label;
#ifdef CONFIG_DEBUG_FS
struct panthor_gem_debugfs debugfs;
#endif
};
/**
* struct panthor_kernel_bo - Kernel buffer object.
*
* These objects are only manipulated by the kernel driver and not
* directly exposed to the userspace. The GPU address of a kernel
* BO might be passed to userspace though.
*/
struct panthor_kernel_bo {
/**
* @obj: The GEM object backing this kernel buffer object.
*/
struct drm_gem_object *obj;
/**
* @vm: VM this private buffer is attached to.
*/
struct panthor_vm *vm;
/**
* @va_node: VA space allocated to this GEM.
*/
struct drm_mm_node va_node;
/**
* @kmap: Kernel CPU mapping of @gem.
*/
void *kmap;
};
#define to_panthor_bo(obj) container_of_const(obj, struct panthor_gem_object, base)
void panthor_gem_init(struct panthor_device *ptdev);
struct drm_gem_object *
panthor_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt);
int
panthor_gem_create_with_handle(struct drm_file *file,
struct drm_device *ddev,
struct panthor_vm *exclusive_vm,
u64 *size, u32 flags, uint32_t *handle);
struct sg_table *
panthor_gem_get_dev_sgt(struct panthor_gem_object *bo);
int panthor_gem_pin(struct panthor_gem_object *bo);
void panthor_gem_unpin(struct panthor_gem_object *bo);
int panthor_gem_swapin_locked(struct panthor_gem_object *bo);
void panthor_gem_update_reclaim_state_locked(struct panthor_gem_object *bo,
enum panthor_gem_reclaim_state *old_state);
int panthor_gem_shrinker_init(struct panthor_device *ptdev);
void panthor_gem_shrinker_unplug(struct panthor_device *ptdev);
void panthor_gem_bo_set_label(struct drm_gem_object *obj, const char *label);
void panthor_gem_kernel_bo_set_label(struct panthor_kernel_bo *bo, const char *label);
int panthor_gem_sync(struct drm_gem_object *obj,
u32 type, u64 offset, u64 size);
struct drm_gem_object *
panthor_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf);
static inline u64
panthor_kernel_bo_gpuva(struct panthor_kernel_bo *bo)
{
return bo->va_node.start;
}
static inline size_t
panthor_kernel_bo_size(struct panthor_kernel_bo *bo)
{
return bo->obj->size;
}
static inline int
panthor_kernel_bo_vmap(struct panthor_kernel_bo *bo)
{
struct iosys_map map;
int ret;
if (bo->kmap)
return 0;
ret = drm_gem_vmap(bo->obj, &map);
if (ret)
return ret;
bo->kmap = map.vaddr;
return 0;
}
static inline void
panthor_kernel_bo_vunmap(struct panthor_kernel_bo *bo)
{
if (bo->kmap) {
struct iosys_map map = IOSYS_MAP_INIT_VADDR(bo->kmap);
drm_gem_vunmap(bo->obj, &map);
bo->kmap = NULL;
}
}
struct panthor_kernel_bo *
panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
size_t size, u32 bo_flags, u32 vm_map_flags,
u64 gpu_va, const char *name);
void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo);
#ifdef CONFIG_DEBUG_FS
void panthor_gem_debugfs_init(struct drm_minor *minor);
#endif
#endif /* __PANTHOR_GEM_H__ */
@@ -0,0 +1,483 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd., Rob Herring <robh@kernel.org> */
/* Copyright 2019 Collabora ltd. */
#include <linux/bitfield.h>
#include <linux/bitmap.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_drv.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include "panthor_device.h"
#include "panthor_gpu.h"
#include "panthor_gpu_regs.h"
#include "panthor_hw.h"
#define CREATE_TRACE_POINTS
#include "panthor_trace.h"
/**
* struct panthor_gpu - GPU block management data.
*/
struct panthor_gpu {
/** @iomem: CPU mapping of GPU_CONTROL iomem region */
void __iomem *iomem;
/** @irq: GPU irq. */
struct panthor_irq irq;
/** @reqs_lock: Lock protecting access to pending_reqs. */
spinlock_t reqs_lock;
/** @pending_reqs: Pending GPU requests. */
u32 pending_reqs;
/** @reqs_acked: GPU request wait queue. */
wait_queue_head_t reqs_acked;
/** @cache_flush_lock: Lock to serialize cache flushes */
struct mutex cache_flush_lock;
};
#define GPU_INTERRUPTS_MASK \
(GPU_IRQ_FAULT | \
GPU_IRQ_PROTM_FAULT | \
GPU_IRQ_RESET_COMPLETED | \
GPU_IRQ_CLEAN_CACHES_COMPLETED)
#define GPU_POWER_INTERRUPTS_MASK \
(GPU_IRQ_POWER_CHANGED | GPU_IRQ_POWER_CHANGED_ALL)
static void panthor_gpu_coherency_set(struct panthor_device *ptdev)
{
gpu_write(ptdev->gpu->iomem, GPU_COHERENCY_PROTOCOL,
ptdev->gpu_info.selected_coherency);
}
static void panthor_gpu_l2_config_set(struct panthor_device *ptdev)
{
struct panthor_gpu *gpu = ptdev->gpu;
const struct panthor_soc_data *data = ptdev->soc_data;
u32 l2_config;
u32 i;
if (!data || !data->asn_hash_enable)
return;
if (GPU_ARCH_MAJOR(ptdev->gpu_info.gpu_id) < 11) {
drm_err(&ptdev->base, "Custom ASN hash not supported by the device");
return;
}
for (i = 0; i < ARRAY_SIZE(data->asn_hash); i++)
gpu_write(gpu->iomem, GPU_ASN_HASH(i), data->asn_hash[i]);
l2_config = gpu_read(gpu->iomem, GPU_L2_CONFIG);
l2_config |= GPU_L2_CONFIG_ASN_HASH_ENABLE;
gpu_write(gpu->iomem, GPU_L2_CONFIG, l2_config);
}
static void panthor_gpu_irq_handler(struct panthor_device *ptdev, u32 status)
{
struct panthor_gpu *gpu = ptdev->gpu;
gpu_write(gpu->irq.iomem, INT_CLEAR, status);
if (tracepoint_enabled(gpu_power_status) && (status & GPU_POWER_INTERRUPTS_MASK))
trace_gpu_power_status(ptdev->base.dev,
gpu_read64(gpu->iomem, SHADER_READY),
gpu_read64(gpu->iomem, TILER_READY),
gpu_read64(gpu->iomem, L2_READY));
if (status & GPU_IRQ_FAULT) {
u32 fault_status = gpu_read(gpu->iomem, GPU_FAULT_STATUS);
u64 address = gpu_read64(gpu->iomem, GPU_FAULT_ADDR);
drm_warn(&ptdev->base, "GPU Fault 0x%08x (%s) at 0x%016llx\n",
fault_status, panthor_exception_name(ptdev, fault_status & 0xFF),
address);
}
if (status & GPU_IRQ_PROTM_FAULT)
drm_warn(&ptdev->base, "GPU Fault in protected mode\n");
spin_lock(&ptdev->gpu->reqs_lock);
if (status & ptdev->gpu->pending_reqs) {
ptdev->gpu->pending_reqs &= ~status;
wake_up_all(&ptdev->gpu->reqs_acked);
}
spin_unlock(&ptdev->gpu->reqs_lock);
}
PANTHOR_IRQ_HANDLER(gpu, panthor_gpu_irq_handler);
/**
* panthor_gpu_unplug() - Called when the GPU is unplugged.
* @ptdev: Device to unplug.
*/
void panthor_gpu_unplug(struct panthor_device *ptdev)
{
unsigned long flags;
/* Make sure the IRQ handler is not running after that point. */
if (!IS_ENABLED(CONFIG_PM) || pm_runtime_active(ptdev->base.dev))
panthor_gpu_irq_suspend(&ptdev->gpu->irq);
/* Wake-up all waiters. */
spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
ptdev->gpu->pending_reqs = 0;
wake_up_all(&ptdev->gpu->reqs_acked);
spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags);
}
/**
* panthor_gpu_init() - Initialize the GPU block
* @ptdev: Device.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_init(struct panthor_device *ptdev)
{
struct panthor_gpu *gpu;
u32 pa_bits;
int ret, irq;
gpu = drmm_kzalloc(&ptdev->base, sizeof(*gpu), GFP_KERNEL);
if (!gpu)
return -ENOMEM;
gpu->iomem = ptdev->iomem + GPU_CONTROL_BASE;
spin_lock_init(&gpu->reqs_lock);
init_waitqueue_head(&gpu->reqs_acked);
mutex_init(&gpu->cache_flush_lock);
ptdev->gpu = gpu;
dma_set_max_seg_size(ptdev->base.dev, UINT_MAX);
pa_bits = GPU_MMU_FEATURES_PA_BITS(ptdev->gpu_info.mmu_features);
ret = dma_set_mask_and_coherent(ptdev->base.dev, DMA_BIT_MASK(pa_bits));
if (ret)
return ret;
irq = platform_get_irq_byname(to_platform_device(ptdev->base.dev), "gpu");
if (irq < 0)
return irq;
ret = panthor_request_gpu_irq(ptdev, &ptdev->gpu->irq, irq,
ptdev->iomem + GPU_INT_BASE);
if (ret)
return ret;
panthor_gpu_irq_enable_events(&ptdev->gpu->irq, GPU_INTERRUPTS_MASK);
panthor_gpu_irq_resume(&ptdev->gpu->irq);
return 0;
}
int panthor_gpu_power_changed_on(struct panthor_device *ptdev)
{
guard(pm_runtime_active)(ptdev->base.dev);
panthor_gpu_irq_enable_events(&ptdev->gpu->irq, GPU_POWER_INTERRUPTS_MASK);
return 0;
}
void panthor_gpu_power_changed_off(struct panthor_device *ptdev)
{
guard(pm_runtime_active)(ptdev->base.dev);
panthor_gpu_irq_disable_events(&ptdev->gpu->irq, GPU_POWER_INTERRUPTS_MASK);
}
/**
* panthor_gpu_block_power_off() - Power-off a specific block of the GPU
* @ptdev: Device.
* @blk_name: Block name.
* @pwroff_reg: Power-off register for this block.
* @pwrtrans_reg: Power transition register for this block.
* @mask: Sub-elements to power-off.
* @timeout_us: Timeout in microseconds.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_block_power_off(struct panthor_device *ptdev,
const char *blk_name,
u32 pwroff_reg, u32 pwrtrans_reg,
u64 mask, u32 timeout_us)
{
struct panthor_gpu *gpu = ptdev->gpu;
u32 val;
int ret;
ret = gpu_read64_relaxed_poll_timeout(gpu->iomem, pwrtrans_reg, val,
!(mask & val), 100, timeout_us);
if (ret) {
drm_err(&ptdev->base,
"timeout waiting on %s:%llx power transition", blk_name,
mask);
return ret;
}
gpu_write64(gpu->iomem, pwroff_reg, mask);
ret = gpu_read64_relaxed_poll_timeout(gpu->iomem, pwrtrans_reg, val,
!(mask & val), 100, timeout_us);
if (ret) {
drm_err(&ptdev->base,
"timeout waiting on %s:%llx power transition", blk_name,
mask);
return ret;
}
return 0;
}
/**
* panthor_gpu_block_power_on() - Power-on a specific block of the GPU
* @ptdev: Device.
* @blk_name: Block name.
* @pwron_reg: Power-on register for this block.
* @pwrtrans_reg: Power transition register for this block.
* @rdy_reg: Power transition ready register.
* @mask: Sub-elements to power-on.
* @timeout_us: Timeout in microseconds.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_block_power_on(struct panthor_device *ptdev,
const char *blk_name,
u32 pwron_reg, u32 pwrtrans_reg,
u32 rdy_reg, u64 mask, u32 timeout_us)
{
struct panthor_gpu *gpu = ptdev->gpu;
u32 val;
int ret;
ret = gpu_read64_relaxed_poll_timeout(gpu->iomem, pwrtrans_reg, val,
!(mask & val), 100, timeout_us);
if (ret) {
drm_err(&ptdev->base,
"timeout waiting on %s:%llx power transition", blk_name,
mask);
return ret;
}
gpu_write64(gpu->iomem, pwron_reg, mask);
ret = gpu_read64_relaxed_poll_timeout(gpu->iomem, rdy_reg, val,
(mask & val) == val,
100, timeout_us);
if (ret) {
drm_err(&ptdev->base, "timeout waiting on %s:%llx readiness",
blk_name, mask);
return ret;
}
return 0;
}
void panthor_gpu_l2_power_off(struct panthor_device *ptdev)
{
panthor_gpu_power_off(ptdev, L2, ptdev->gpu_info.l2_present, 20000);
}
/**
* panthor_gpu_l2_power_on() - Power-on the L2-cache
* @ptdev: Device.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_l2_power_on(struct panthor_device *ptdev)
{
if (ptdev->gpu_info.l2_present != 1) {
/*
* Only support one core group now.
* ~(l2_present - 1) unsets all bits in l2_present except
* the bottom bit. (l2_present - 2) has all the bits in
* the first core group set. AND them together to generate
* a mask of cores in the first core group.
*/
u64 core_mask = ~(ptdev->gpu_info.l2_present - 1) &
(ptdev->gpu_info.l2_present - 2);
drm_info_once(&ptdev->base, "using only 1st core group (%lu cores from %lu)\n",
hweight64(core_mask),
hweight64(ptdev->gpu_info.shader_present));
}
/* Set the desired coherency mode and L2 config before the power up of L2 */
panthor_gpu_coherency_set(ptdev);
panthor_gpu_l2_config_set(ptdev);
return panthor_gpu_power_on(ptdev, L2, 1, 20000);
}
/**
* panthor_gpu_flush_caches() - Flush caches
* @ptdev: Device.
* @l2: L2 flush type.
* @lsc: LSC flush type.
* @other: Other flush type.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_flush_caches(struct panthor_device *ptdev,
u32 l2, u32 lsc, u32 other)
{
struct panthor_gpu *gpu = ptdev->gpu;
unsigned long flags;
int ret = 0;
/* Serialize cache flush operations. */
guard(mutex)(&ptdev->gpu->cache_flush_lock);
spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
if (!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED)) {
ptdev->gpu->pending_reqs |= GPU_IRQ_CLEAN_CACHES_COMPLETED;
gpu_write(gpu->iomem, GPU_CMD, GPU_FLUSH_CACHES(l2, lsc, other));
} else {
ret = -EIO;
}
spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags);
if (ret)
return ret;
if (!wait_event_timeout(ptdev->gpu->reqs_acked,
!(ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED),
msecs_to_jiffies(100))) {
spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
if ((ptdev->gpu->pending_reqs & GPU_IRQ_CLEAN_CACHES_COMPLETED) != 0 &&
!(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_CLEAN_CACHES_COMPLETED))
ret = -ETIMEDOUT;
else
ptdev->gpu->pending_reqs &= ~GPU_IRQ_CLEAN_CACHES_COMPLETED;
spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags);
}
if (ret) {
panthor_device_schedule_reset(ptdev);
drm_err(&ptdev->base, "Flush caches timeout");
}
return ret;
}
/**
* panthor_gpu_soft_reset() - Issue a soft-reset
* @ptdev: Device.
*
* Return: 0 on success, a negative error code otherwise.
*/
int panthor_gpu_soft_reset(struct panthor_device *ptdev)
{
struct panthor_gpu *gpu = ptdev->gpu;
bool timedout = false;
unsigned long flags;
spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
if (!drm_WARN_ON(&ptdev->base,
ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED)) {
ptdev->gpu->pending_reqs |= GPU_IRQ_RESET_COMPLETED;
gpu_write(gpu->irq.iomem, INT_CLEAR, GPU_IRQ_RESET_COMPLETED);
gpu_write(gpu->iomem, GPU_CMD, GPU_SOFT_RESET);
}
spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags);
if (!wait_event_timeout(ptdev->gpu->reqs_acked,
!(ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED),
msecs_to_jiffies(100))) {
spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
if ((ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED) != 0 &&
!(gpu_read(gpu->irq.iomem, INT_RAWSTAT) & GPU_IRQ_RESET_COMPLETED))
timedout = true;
else
ptdev->gpu->pending_reqs &= ~GPU_IRQ_RESET_COMPLETED;
spin_unlock_irqrestore(&ptdev->gpu->reqs_lock, flags);
}
if (timedout) {
drm_err(&ptdev->base, "Soft reset timeout");
return -ETIMEDOUT;
}
ptdev->gpu->pending_reqs = 0;
return 0;
}
/**
* panthor_gpu_suspend() - Suspend the GPU block.
* @ptdev: Device.
*
* Suspend the GPU irq. This should be called last in the suspend procedure,
* after all other blocks have been suspented.
*/
void panthor_gpu_suspend(struct panthor_device *ptdev)
{
/* On a fast reset, simply power down the L2. */
if (!ptdev->reset.fast)
panthor_hw_soft_reset(ptdev);
else
panthor_hw_l2_power_off(ptdev);
panthor_gpu_irq_suspend(&ptdev->gpu->irq);
}
/**
* panthor_gpu_resume() - Resume the GPU block.
* @ptdev: Device.
*
* Resume the IRQ handler and power-on the L2-cache.
* The FW takes care of powering the other blocks.
*/
void panthor_gpu_resume(struct panthor_device *ptdev)
{
panthor_gpu_irq_resume(&ptdev->gpu->irq);
panthor_hw_l2_power_on(ptdev);
}
u64 panthor_gpu_get_timestamp(struct panthor_device *ptdev)
{
return gpu_read64_counter(ptdev->gpu->iomem, GPU_TIMESTAMP);
}
u64 panthor_gpu_get_timestamp_offset(struct panthor_device *ptdev)
{
return gpu_read64(ptdev->gpu->iomem, GPU_TIMESTAMP_OFFSET);
}
u64 panthor_gpu_get_cycle_count(struct panthor_device *ptdev)
{
return gpu_read64_counter(ptdev->gpu->iomem, GPU_CYCLE_COUNT);
}
int panthor_gpu_coherency_init(struct panthor_device *ptdev)
{
BUILD_BUG_ON(GPU_COHERENCY_NONE != DRM_PANTHOR_GPU_COHERENCY_NONE);
BUILD_BUG_ON(GPU_COHERENCY_ACE_LITE != DRM_PANTHOR_GPU_COHERENCY_ACE_LITE);
BUILD_BUG_ON(GPU_COHERENCY_ACE != DRM_PANTHOR_GPU_COHERENCY_ACE);
/* Start with no coherency, and update it if the device is flagged coherent. */
ptdev->gpu_info.selected_coherency = GPU_COHERENCY_NONE;
ptdev->coherent = device_get_dma_attr(ptdev->base.dev) == DEV_DMA_COHERENT;
if (!ptdev->coherent)
return 0;
/* Check if the ACE-Lite coherency protocol is actually supported by the GPU.
* ACE protocol has never been supported for command stream frontend GPUs.
*/
if ((gpu_read(ptdev->gpu->iomem, GPU_COHERENCY_FEATURES) &
GPU_COHERENCY_PROT_BIT(ACE_LITE))) {
ptdev->gpu_info.selected_coherency = GPU_COHERENCY_ACE_LITE;
return 0;
}
drm_err(&ptdev->base, "Coherency not supported by the device");
return -ENOTSUPP;
}
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Collabora ltd. */
#ifndef __PANTHOR_GPU_H__
#define __PANTHOR_GPU_H__
#include <linux/types.h>
struct panthor_device;
int panthor_gpu_init(struct panthor_device *ptdev);
void panthor_gpu_unplug(struct panthor_device *ptdev);
void panthor_gpu_suspend(struct panthor_device *ptdev);
void panthor_gpu_resume(struct panthor_device *ptdev);
int panthor_gpu_block_power_on(struct panthor_device *ptdev,
const char *blk_name,
u32 pwron_reg, u32 pwrtrans_reg,
u32 rdy_reg, u64 mask, u32 timeout_us);
int panthor_gpu_block_power_off(struct panthor_device *ptdev,
const char *blk_name,
u32 pwroff_reg, u32 pwrtrans_reg,
u64 mask, u32 timeout_us);
/**
* panthor_gpu_power_on() - Power on the GPU block.
*
* Return: 0 on success, a negative error code otherwise.
*/
#define panthor_gpu_power_on(ptdev, type, mask, timeout_us) \
panthor_gpu_block_power_on(ptdev, #type, \
type ## _PWRON, \
type ## _PWRTRANS, \
type ## _READY, \
mask, timeout_us)
/**
* panthor_gpu_power_off() - Power off the GPU block.
*
* Return: 0 on success, a negative error code otherwise.
*/
#define panthor_gpu_power_off(ptdev, type, mask, timeout_us) \
panthor_gpu_block_power_off(ptdev, #type, \
type ## _PWROFF, \
type ## _PWRTRANS, \
mask, timeout_us)
void panthor_gpu_l2_power_off(struct panthor_device *ptdev);
int panthor_gpu_l2_power_on(struct panthor_device *ptdev);
int panthor_gpu_flush_caches(struct panthor_device *ptdev,
u32 l2, u32 lsc, u32 other);
int panthor_gpu_soft_reset(struct panthor_device *ptdev);
void panthor_gpu_power_changed_off(struct panthor_device *ptdev);
int panthor_gpu_power_changed_on(struct panthor_device *ptdev);
u64 panthor_gpu_get_timestamp(struct panthor_device *ptdev);
u64 panthor_gpu_get_timestamp_offset(struct panthor_device *ptdev);
u64 panthor_gpu_get_cycle_count(struct panthor_device *ptdev);
int panthor_gpu_coherency_init(struct panthor_device *ptdev);
#endif
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2026 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_GPU_REGS_H__
#define __PANTHOR_GPU_REGS_H__
#define GPU_CONTROL_BASE 0x0
#define GPU_ID 0x0
#define GPU_ARCH_MAJOR(x) ((x) >> 28)
#define GPU_ARCH_MINOR(x) (((x) & GENMASK(27, 24)) >> 24)
#define GPU_ARCH_REV(x) (((x) & GENMASK(23, 20)) >> 20)
#define GPU_PROD_MAJOR(x) (((x) & GENMASK(19, 16)) >> 16)
#define GPU_VER_MAJOR(x) (((x) & GENMASK(15, 12)) >> 12)
#define GPU_VER_MINOR(x) (((x) & GENMASK(11, 4)) >> 4)
#define GPU_VER_STATUS(x) ((x) & GENMASK(3, 0))
#define GPU_L2_FEATURES 0x4
#define GPU_L2_FEATURES_LINE_SIZE(x) (1 << ((x) & GENMASK(7, 0)))
#define GPU_CORE_FEATURES 0x8
#define GPU_TILER_FEATURES 0xC
#define GPU_MEM_FEATURES 0x10
#define GROUPS_L2_COHERENT BIT(0)
#define GPU_MMU_FEATURES 0x14
#define GPU_MMU_FEATURES_VA_BITS(x) ((x) & GENMASK(7, 0))
#define GPU_MMU_FEATURES_PA_BITS(x) (((x) >> 8) & GENMASK(7, 0))
#define GPU_AS_PRESENT 0x18
#define GPU_CSF_ID 0x1C
#define GPU_INT_BASE 0x20
#define GPU_IRQ_FAULT BIT(0)
#define GPU_IRQ_PROTM_FAULT BIT(1)
#define GPU_IRQ_RESET_COMPLETED BIT(8)
#define GPU_IRQ_POWER_CHANGED BIT(9)
#define GPU_IRQ_POWER_CHANGED_ALL BIT(10)
#define GPU_IRQ_CLEAN_CACHES_COMPLETED BIT(17)
#define GPU_IRQ_DOORBELL_MIRROR BIT(18)
#define GPU_IRQ_MCU_STATUS_CHANGED BIT(19)
#define GPU_CMD 0x30
#define GPU_CMD_DEF(type, payload) ((type) | ((payload) << 8))
#define GPU_SOFT_RESET GPU_CMD_DEF(1, 1)
#define GPU_HARD_RESET GPU_CMD_DEF(1, 2)
#define CACHE_CLEAN BIT(0)
#define CACHE_INV BIT(1)
#define GPU_FLUSH_CACHES(l2, lsc, oth) \
GPU_CMD_DEF(4, ((l2) << 0) | ((lsc) << 4) | ((oth) << 8))
#define GPU_STATUS 0x34
#define GPU_STATUS_ACTIVE BIT(0)
#define GPU_STATUS_PWR_ACTIVE BIT(1)
#define GPU_STATUS_PAGE_FAULT BIT(4)
#define GPU_STATUS_PROTM_ACTIVE BIT(7)
#define GPU_STATUS_DBG_ENABLED BIT(8)
#define GPU_FAULT_STATUS 0x3C
#define GPU_FAULT_ADDR 0x40
#define GPU_L2_CONFIG 0x48
#define GPU_L2_CONFIG_ASN_HASH_ENABLE BIT(24)
#define GPU_PWR_KEY 0x50
#define GPU_PWR_KEY_UNLOCK 0x2968A819
#define GPU_PWR_OVERRIDE0 0x54
#define GPU_PWR_OVERRIDE1 0x58
#define GPU_FEATURES 0x60
#define GPU_FEATURES_RAY_INTERSECTION BIT(2)
#define GPU_FEATURES_RAY_TRAVERSAL BIT(5)
#define GPU_TIMESTAMP_OFFSET 0x88
#define GPU_CYCLE_COUNT 0x90
#define GPU_TIMESTAMP 0x98
#define GPU_THREAD_MAX_THREADS 0xA0
#define GPU_THREAD_MAX_WORKGROUP_SIZE 0xA4
#define GPU_THREAD_MAX_BARRIER_SIZE 0xA8
#define GPU_THREAD_FEATURES 0xAC
#define GPU_TEXTURE_FEATURES(n) (0xB0 + ((n) * 4))
#define GPU_SHADER_PRESENT 0x100
#define GPU_TILER_PRESENT 0x110
#define GPU_L2_PRESENT 0x120
#define SHADER_READY 0x140
#define TILER_READY 0x150
#define L2_READY 0x160
#define SHADER_PWRON 0x180
#define TILER_PWRON 0x190
#define L2_PWRON 0x1A0
#define SHADER_PWROFF 0x1C0
#define TILER_PWROFF 0x1D0
#define L2_PWROFF 0x1E0
#define SHADER_PWRTRANS 0x200
#define TILER_PWRTRANS 0x210
#define L2_PWRTRANS 0x220
#define SHADER_PWRACTIVE 0x240
#define TILER_PWRACTIVE 0x250
#define L2_PWRACTIVE 0x260
#define GPU_REVID 0x280
#define GPU_ASN_HASH(n) (0x2C0 + ((n) * 4))
#define GPU_COHERENCY_FEATURES 0x300
#define GPU_COHERENCY_PROT_BIT(name) BIT(GPU_COHERENCY_ ## name)
#define GPU_COHERENCY_PROTOCOL 0x304
#define GPU_COHERENCY_ACE_LITE 0
#define GPU_COHERENCY_ACE 1
#define GPU_COHERENCY_NONE 31
#endif /* __PANTHOR_GPU_REGS_H__ */
@@ -0,0 +1,632 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2023 Collabora ltd. */
#include <linux/iosys-map.h>
#include <linux/rwsem.h>
#include <drm/drm_print.h>
#include <drm/panthor_drm.h>
#include "panthor_device.h"
#include "panthor_gem.h"
#include "panthor_gpu_regs.h"
#include "panthor_heap.h"
#include "panthor_mmu.h"
/*
* The GPU heap context is an opaque structure used by the GPU to track the
* heap allocations. The driver should only touch it to initialize it (zero all
* fields). Because the CPU and GPU can both access this structure it is
* required to be GPU cache line aligned.
*/
#define HEAP_CONTEXT_SIZE 32
/**
* struct panthor_heap_chunk_header - Heap chunk header
*/
struct panthor_heap_chunk_header {
/**
* @next: Next heap chunk in the list.
*
* This is a GPU VA.
*/
u64 next;
/** @unknown: MBZ. */
u32 unknown[14];
};
/**
* struct panthor_heap_chunk - Structure used to keep track of allocated heap chunks.
*/
struct panthor_heap_chunk {
/** @node: Used to insert the heap chunk in panthor_heap::chunks. */
struct list_head node;
/** @bo: Buffer object backing the heap chunk. */
struct panthor_kernel_bo *bo;
};
/**
* struct panthor_heap - Structure used to manage tiler heap contexts.
*/
struct panthor_heap {
/** @chunks: List containing all heap chunks allocated so far. */
struct list_head chunks;
/** @lock: Lock protecting insertion in the chunks list. */
struct mutex lock;
/** @chunk_size: Size of each chunk. */
u32 chunk_size;
/** @max_chunks: Maximum number of chunks. */
u32 max_chunks;
/**
* @target_in_flight: Number of in-flight render passes after which
* we'd let the FW wait for fragment job to finish instead of allocating new chunks.
*/
u32 target_in_flight;
/** @chunk_count: Number of heap chunks currently allocated. */
u32 chunk_count;
};
#define MAX_HEAPS_PER_POOL 128
/**
* struct panthor_heap_pool - Pool of heap contexts
*
* The pool is attached to a panthor_file and can't be shared across processes.
*/
struct panthor_heap_pool {
/** @refcount: Reference count. */
struct kref refcount;
/** @ptdev: Device. */
struct panthor_device *ptdev;
/** @vm: VM this pool is bound to. */
struct panthor_vm *vm;
/** @lock: Lock protecting access to @xa. */
struct rw_semaphore lock;
/** @xa: Array storing panthor_heap objects. */
struct xarray xa;
/** @gpu_contexts: Buffer object containing the GPU heap contexts. */
struct panthor_kernel_bo *gpu_contexts;
/** @size: Size of all chunks across all heaps in the pool. */
atomic_t size;
};
static int panthor_heap_ctx_stride(struct panthor_device *ptdev)
{
u32 l2_features = ptdev->gpu_info.l2_features;
u32 gpu_cache_line_size = GPU_L2_FEATURES_LINE_SIZE(l2_features);
return ALIGN(HEAP_CONTEXT_SIZE, gpu_cache_line_size);
}
static int panthor_get_heap_ctx_offset(struct panthor_heap_pool *pool, int id)
{
return panthor_heap_ctx_stride(pool->ptdev) * id;
}
static void *panthor_get_heap_ctx(struct panthor_heap_pool *pool, int id)
{
return pool->gpu_contexts->kmap +
panthor_get_heap_ctx_offset(pool, id);
}
static void panthor_free_heap_chunk(struct panthor_heap_pool *pool,
struct panthor_heap *heap,
struct panthor_heap_chunk *chunk)
{
mutex_lock(&heap->lock);
list_del(&chunk->node);
heap->chunk_count--;
mutex_unlock(&heap->lock);
atomic_sub(heap->chunk_size, &pool->size);
panthor_kernel_bo_destroy(chunk->bo);
kfree(chunk);
}
static int panthor_alloc_heap_chunk(struct panthor_heap_pool *pool,
struct panthor_heap *heap,
bool initial_chunk)
{
struct panthor_heap_chunk *chunk;
struct panthor_heap_chunk_header *hdr;
int ret;
chunk = kmalloc_obj(*chunk);
if (!chunk)
return -ENOMEM;
chunk->bo = panthor_kernel_bo_create(pool->ptdev, pool->vm, heap->chunk_size,
DRM_PANTHOR_BO_NO_MMAP,
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC,
PANTHOR_VM_KERNEL_AUTO_VA,
"Tiler heap chunk");
if (IS_ERR(chunk->bo)) {
ret = PTR_ERR(chunk->bo);
goto err_free_chunk;
}
ret = panthor_kernel_bo_vmap(chunk->bo);
if (ret)
goto err_destroy_bo;
hdr = chunk->bo->kmap;
memset(hdr, 0, sizeof(*hdr));
if (initial_chunk && !list_empty(&heap->chunks)) {
struct panthor_heap_chunk *prev_chunk;
u64 prev_gpuva;
prev_chunk = list_first_entry(&heap->chunks,
struct panthor_heap_chunk,
node);
prev_gpuva = panthor_kernel_bo_gpuva(prev_chunk->bo);
hdr->next = (prev_gpuva & GENMASK_ULL(63, 12)) |
(heap->chunk_size >> 12);
}
panthor_kernel_bo_vunmap(chunk->bo);
mutex_lock(&heap->lock);
list_add(&chunk->node, &heap->chunks);
heap->chunk_count++;
mutex_unlock(&heap->lock);
atomic_add(heap->chunk_size, &pool->size);
return 0;
err_destroy_bo:
panthor_kernel_bo_destroy(chunk->bo);
err_free_chunk:
kfree(chunk);
return ret;
}
static void panthor_free_heap_chunks(struct panthor_heap_pool *pool,
struct panthor_heap *heap)
{
struct panthor_heap_chunk *chunk, *tmp;
list_for_each_entry_safe(chunk, tmp, &heap->chunks, node)
panthor_free_heap_chunk(pool, heap, chunk);
}
static int panthor_alloc_heap_chunks(struct panthor_heap_pool *pool,
struct panthor_heap *heap,
u32 chunk_count)
{
int ret;
u32 i;
for (i = 0; i < chunk_count; i++) {
ret = panthor_alloc_heap_chunk(pool, heap, true);
if (ret)
return ret;
}
return 0;
}
static int
panthor_heap_destroy_locked(struct panthor_heap_pool *pool, u32 handle)
{
struct panthor_heap *heap;
heap = xa_erase(&pool->xa, handle);
if (!heap)
return -EINVAL;
panthor_free_heap_chunks(pool, heap);
mutex_destroy(&heap->lock);
kfree(heap);
return 0;
}
/**
* panthor_heap_destroy() - Destroy a heap context
* @pool: Pool this context belongs to.
* @handle: Handle returned by panthor_heap_create().
*/
int panthor_heap_destroy(struct panthor_heap_pool *pool, u32 handle)
{
int ret;
down_write(&pool->lock);
ret = panthor_heap_destroy_locked(pool, handle);
up_write(&pool->lock);
return ret;
}
/**
* panthor_heap_create() - Create a heap context
* @pool: Pool to instantiate the heap context from.
* @initial_chunk_count: Number of chunk allocated at initialization time.
* Must be at least 1.
* @chunk_size: The size of each chunk. Must be page-aligned and lie in the
* [128k:8M] range.
* @max_chunks: Maximum number of chunks that can be allocated.
* @target_in_flight: Maximum number of in-flight render passes.
* @heap_ctx_gpu_va: Pointer holding the GPU address of the allocated heap
* context.
* @first_chunk_gpu_va: Pointer holding the GPU address of the first chunk
* assigned to the heap context.
*
* Return: a positive handle on success, a negative error otherwise.
*/
int panthor_heap_create(struct panthor_heap_pool *pool,
u32 initial_chunk_count,
u32 chunk_size,
u32 max_chunks,
u32 target_in_flight,
u64 *heap_ctx_gpu_va,
u64 *first_chunk_gpu_va)
{
struct panthor_heap *heap;
struct panthor_heap_chunk *first_chunk;
struct panthor_vm *vm;
int ret = 0;
u32 id;
if (initial_chunk_count == 0)
return -EINVAL;
if (initial_chunk_count > max_chunks)
return -EINVAL;
if (!IS_ALIGNED(chunk_size, PAGE_SIZE) ||
chunk_size < SZ_128K || chunk_size > SZ_8M)
return -EINVAL;
down_read(&pool->lock);
vm = panthor_vm_get(pool->vm);
up_read(&pool->lock);
/* The pool has been destroyed, we can't create a new heap. */
if (!vm)
return -EINVAL;
heap = kzalloc_obj(*heap);
if (!heap) {
ret = -ENOMEM;
goto err_put_vm;
}
mutex_init(&heap->lock);
INIT_LIST_HEAD(&heap->chunks);
heap->chunk_size = chunk_size;
heap->max_chunks = max_chunks;
heap->target_in_flight = target_in_flight;
ret = panthor_alloc_heap_chunks(pool, heap, initial_chunk_count);
if (ret)
goto err_free_heap;
first_chunk = list_first_entry(&heap->chunks,
struct panthor_heap_chunk,
node);
*first_chunk_gpu_va = panthor_kernel_bo_gpuva(first_chunk->bo);
down_write(&pool->lock);
/* The pool has been destroyed, we can't create a new heap. */
if (!pool->vm) {
ret = -EINVAL;
} else {
ret = xa_alloc(&pool->xa, &id, heap,
XA_LIMIT(0, MAX_HEAPS_PER_POOL - 1), GFP_KERNEL);
if (!ret) {
void *gpu_ctx = panthor_get_heap_ctx(pool, id);
memset(gpu_ctx, 0, panthor_heap_ctx_stride(pool->ptdev));
*heap_ctx_gpu_va = panthor_kernel_bo_gpuva(pool->gpu_contexts) +
panthor_get_heap_ctx_offset(pool, id);
}
}
up_write(&pool->lock);
if (ret)
goto err_free_heap;
panthor_vm_put(vm);
return id;
err_free_heap:
panthor_free_heap_chunks(pool, heap);
mutex_destroy(&heap->lock);
kfree(heap);
err_put_vm:
panthor_vm_put(vm);
return ret;
}
/**
* panthor_heap_return_chunk() - Return an unused heap chunk
* @pool: The pool this heap belongs to.
* @heap_gpu_va: The GPU address of the heap context.
* @chunk_gpu_va: The chunk VA to return.
*
* This function is used when a chunk allocated with panthor_heap_grow()
* couldn't be linked to the heap context through the FW interface because
* the group requesting the allocation was scheduled out in the meantime.
*/
int panthor_heap_return_chunk(struct panthor_heap_pool *pool,
u64 heap_gpu_va,
u64 chunk_gpu_va)
{
u64 offset = heap_gpu_va - panthor_kernel_bo_gpuva(pool->gpu_contexts);
u32 heap_id = (u32)offset / panthor_heap_ctx_stride(pool->ptdev);
struct panthor_heap_chunk *chunk, *tmp, *removed = NULL;
struct panthor_heap *heap;
int ret;
if (offset > U32_MAX || heap_id >= MAX_HEAPS_PER_POOL)
return -EINVAL;
down_read(&pool->lock);
heap = xa_load(&pool->xa, heap_id);
if (!heap) {
ret = -EINVAL;
goto out_unlock;
}
chunk_gpu_va &= GENMASK_ULL(63, 12);
mutex_lock(&heap->lock);
list_for_each_entry_safe(chunk, tmp, &heap->chunks, node) {
if (panthor_kernel_bo_gpuva(chunk->bo) == chunk_gpu_va) {
removed = chunk;
list_del(&chunk->node);
heap->chunk_count--;
atomic_sub(heap->chunk_size, &pool->size);
break;
}
}
mutex_unlock(&heap->lock);
if (removed) {
panthor_kernel_bo_destroy(chunk->bo);
kfree(chunk);
ret = 0;
} else {
ret = -EINVAL;
}
out_unlock:
up_read(&pool->lock);
return ret;
}
/**
* panthor_heap_grow() - Make a heap context grow.
* @pool: The pool this heap belongs to.
* @heap_gpu_va: The GPU address of the heap context.
* @renderpasses_in_flight: Number of render passes currently in-flight.
* @pending_frag_count: Number of fragment jobs waiting for execution/completion.
* @new_chunk_gpu_va: Pointer used to return the chunk VA.
*
* Return:
* - 0 if a new heap was allocated
* - -ENOMEM if the tiler context reached the maximum number of chunks
* or if too many render passes are in-flight
* or if the allocation failed
* - -EINVAL if any of the arguments passed to panthor_heap_grow() is invalid
*/
int panthor_heap_grow(struct panthor_heap_pool *pool,
u64 heap_gpu_va,
u32 renderpasses_in_flight,
u32 pending_frag_count,
u64 *new_chunk_gpu_va)
{
u64 offset = heap_gpu_va - panthor_kernel_bo_gpuva(pool->gpu_contexts);
u32 heap_id = (u32)offset / panthor_heap_ctx_stride(pool->ptdev);
struct panthor_heap_chunk *chunk;
struct panthor_heap *heap;
int ret;
if (offset > U32_MAX || heap_id >= MAX_HEAPS_PER_POOL)
return -EINVAL;
down_read(&pool->lock);
heap = xa_load(&pool->xa, heap_id);
if (!heap) {
ret = -EINVAL;
goto out_unlock;
}
/* If we reached the target in-flight render passes, or if we
* reached the maximum number of chunks, let the FW figure another way to
* find some memory (wait for render passes to finish, or call the exception
* handler provided by the userspace driver, if any).
*/
if (renderpasses_in_flight > heap->target_in_flight ||
heap->chunk_count >= heap->max_chunks) {
ret = -ENOMEM;
goto out_unlock;
}
/* FIXME: panthor_alloc_heap_chunk() triggers a kernel BO creation,
* which goes through the blocking allocation path. Ultimately, we
* want a non-blocking allocation, so we can immediately report to the
* FW when the system is running out of memory. In that case, the FW
* can call a user-provided exception handler, which might try to free
* some tiler memory by issuing an intermediate fragment job. If the
* exception handler can't do anything, it will flag the queue as
* faulty so the job that triggered this tiler chunk allocation and all
* further jobs in this queue fail immediately instead of having to
* wait for the job timeout.
*/
ret = panthor_alloc_heap_chunk(pool, heap, false);
if (ret)
goto out_unlock;
chunk = list_first_entry(&heap->chunks,
struct panthor_heap_chunk,
node);
*new_chunk_gpu_va = (panthor_kernel_bo_gpuva(chunk->bo) & GENMASK_ULL(63, 12)) |
(heap->chunk_size >> 12);
ret = 0;
out_unlock:
up_read(&pool->lock);
return ret;
}
static void panthor_heap_pool_release(struct kref *refcount)
{
struct panthor_heap_pool *pool =
container_of(refcount, struct panthor_heap_pool, refcount);
xa_destroy(&pool->xa);
kfree(pool);
}
/**
* panthor_heap_pool_put() - Release a heap pool reference
* @pool: Pool to release the reference on. Can be NULL.
*/
void panthor_heap_pool_put(struct panthor_heap_pool *pool)
{
if (pool)
kref_put(&pool->refcount, panthor_heap_pool_release);
}
/**
* panthor_heap_pool_get() - Get a heap pool reference
* @pool: Pool to get the reference on. Can be NULL.
*
* Return: @pool.
*/
struct panthor_heap_pool *
panthor_heap_pool_get(struct panthor_heap_pool *pool)
{
if (pool)
kref_get(&pool->refcount);
return pool;
}
/**
* panthor_heap_pool_create() - Create a heap pool
* @ptdev: Device.
* @vm: The VM this heap pool will be attached to.
*
* Heap pools might contain up to 128 heap contexts, and are per-VM.
*
* Return: A valid pointer on success, a negative error code otherwise.
*/
struct panthor_heap_pool *
panthor_heap_pool_create(struct panthor_device *ptdev, struct panthor_vm *vm)
{
size_t bosize = ALIGN(MAX_HEAPS_PER_POOL *
panthor_heap_ctx_stride(ptdev),
4096);
struct panthor_heap_pool *pool;
int ret = 0;
pool = kzalloc_obj(*pool);
if (!pool)
return ERR_PTR(-ENOMEM);
/* We want a weak ref here: the heap pool belongs to the VM, so we're
* sure that, as long as the heap pool exists, the VM exists too.
*/
pool->vm = vm;
pool->ptdev = ptdev;
init_rwsem(&pool->lock);
xa_init_flags(&pool->xa, XA_FLAGS_ALLOC);
kref_init(&pool->refcount);
pool->gpu_contexts = panthor_kernel_bo_create(ptdev, vm, bosize,
DRM_PANTHOR_BO_NO_MMAP,
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC,
PANTHOR_VM_KERNEL_AUTO_VA,
"Heap pool");
if (IS_ERR(pool->gpu_contexts)) {
ret = PTR_ERR(pool->gpu_contexts);
goto err_destroy_pool;
}
ret = panthor_kernel_bo_vmap(pool->gpu_contexts);
if (ret)
goto err_destroy_pool;
atomic_add(pool->gpu_contexts->obj->size, &pool->size);
return pool;
err_destroy_pool:
panthor_heap_pool_destroy(pool);
return ERR_PTR(ret);
}
/**
* panthor_heap_pool_destroy() - Destroy a heap pool.
* @pool: Pool to destroy.
*
* This function destroys all heap contexts and their resources. Thus
* preventing any use of the heap context or the chunk attached to them
* after that point.
*
* If the GPU still has access to some heap contexts, a fault should be
* triggered, which should flag the command stream groups using these
* context as faulty.
*
* The heap pool object is only released when all references to this pool
* are released.
*/
void panthor_heap_pool_destroy(struct panthor_heap_pool *pool)
{
struct panthor_heap *heap;
unsigned long i;
if (!pool)
return;
down_write(&pool->lock);
xa_for_each(&pool->xa, i, heap)
drm_WARN_ON(&pool->ptdev->base, panthor_heap_destroy_locked(pool, i));
if (!IS_ERR_OR_NULL(pool->gpu_contexts)) {
atomic_sub(pool->gpu_contexts->obj->size, &pool->size);
panthor_kernel_bo_destroy(pool->gpu_contexts);
}
/* Reflects the fact the pool has been destroyed. */
pool->vm = NULL;
up_write(&pool->lock);
panthor_heap_pool_put(pool);
}
/**
* panthor_heap_pool_size() - Get a heap pool's total size
* @pool: Pool whose total chunks size to return
*
* Returns the aggregated size of all chunks for all heaps in the pool
*
*/
size_t panthor_heap_pool_size(struct panthor_heap_pool *pool)
{
if (!pool)
return 0;
return atomic_read(&pool->size);
}
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2023 Collabora ltd. */
#ifndef __PANTHOR_HEAP_H__
#define __PANTHOR_HEAP_H__
#include <linux/types.h>
struct panthor_device;
struct panthor_heap_pool;
struct panthor_vm;
int panthor_heap_create(struct panthor_heap_pool *pool,
u32 initial_chunk_count,
u32 chunk_size,
u32 max_chunks,
u32 target_in_flight,
u64 *heap_ctx_gpu_va,
u64 *first_chunk_gpu_va);
int panthor_heap_destroy(struct panthor_heap_pool *pool, u32 handle);
struct panthor_heap_pool *
panthor_heap_pool_create(struct panthor_device *ptdev, struct panthor_vm *vm);
void panthor_heap_pool_destroy(struct panthor_heap_pool *pool);
struct panthor_heap_pool *
panthor_heap_pool_get(struct panthor_heap_pool *pool);
void panthor_heap_pool_put(struct panthor_heap_pool *pool);
size_t panthor_heap_pool_size(struct panthor_heap_pool *pool);
int panthor_heap_grow(struct panthor_heap_pool *pool,
u64 heap_gpu_va,
u32 renderpasses_in_flight,
u32 pending_frag_count,
u64 *new_chunk_gpu_va);
int panthor_heap_return_chunk(struct panthor_heap_pool *pool,
u64 heap_gpu_va,
u64 chunk_gpu_va);
#endif
@@ -0,0 +1,318 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2025 ARM Limited. All rights reserved. */
#include <linux/nvmem-consumer.h>
#include <linux/platform_device.h>
#include <drm/drm_print.h>
#include "panthor_device.h"
#include "panthor_gpu.h"
#include "panthor_gpu_regs.h"
#include "panthor_hw.h"
#include "panthor_pwr.h"
#include "panthor_pwr_regs.h"
#define GPU_PROD_ID_MAKE(arch_major, prod_major) \
(((arch_major) << 24) | (prod_major))
/** struct panthor_hw_entry - HW arch major to panthor_hw binding entry */
struct panthor_hw_entry {
/** @arch_min: Minimum supported architecture major value (inclusive) */
u8 arch_min;
/** @arch_max: Maximum supported architecture major value (inclusive) */
u8 arch_max;
/** @hwdev: Pointer to panthor_hw structure */
struct panthor_hw *hwdev;
};
static struct panthor_hw panthor_hw_arch_v10 = {
.ops = {
.soft_reset = panthor_gpu_soft_reset,
.l2_power_off = panthor_gpu_l2_power_off,
.l2_power_on = panthor_gpu_l2_power_on,
.power_changed_off = panthor_gpu_power_changed_off,
.power_changed_on = panthor_gpu_power_changed_on,
},
};
static struct panthor_hw panthor_hw_arch_v14 = {
.ops = {
.soft_reset = panthor_pwr_reset_soft,
.l2_power_off = panthor_pwr_l2_power_off,
.l2_power_on = panthor_pwr_l2_power_on,
},
};
static struct panthor_hw_entry panthor_hw_match[] = {
{
.arch_min = 10,
.arch_max = 13,
.hwdev = &panthor_hw_arch_v10,
},
{
.arch_min = 14,
.arch_max = 14,
.hwdev = &panthor_hw_arch_v14,
},
};
static int panthor_hw_set_power_tracing(struct device *dev, void *data)
{
struct panthor_device *ptdev = dev_get_drvdata(dev);
if (!ptdev)
return -ENODEV;
if (!ptdev->hw)
return 0;
if (data) {
if (ptdev->hw->ops.power_changed_on)
return ptdev->hw->ops.power_changed_on(ptdev);
} else {
if (ptdev->hw->ops.power_changed_off)
ptdev->hw->ops.power_changed_off(ptdev);
}
return 0;
}
int panthor_hw_power_status_register(void)
{
struct device_driver *drv;
int ret;
drv = driver_find("panthor", &platform_bus_type);
if (!drv)
return -ENODEV;
ret = driver_for_each_device(drv, NULL, (void *)true,
panthor_hw_set_power_tracing);
return ret;
}
void panthor_hw_power_status_unregister(void)
{
struct device_driver *drv;
int ret;
drv = driver_find("panthor", &platform_bus_type);
if (!drv)
return;
ret = driver_for_each_device(drv, NULL, NULL, panthor_hw_set_power_tracing);
/*
* Ideally, it'd be possible to ask driver_for_each_device to hand us
* another "start" to keep going after the failing device, but it
* doesn't do that. Minor inconvenience in what is probably a bad day
* on the computer already though.
*/
if (ret)
pr_warn("Couldn't mask power IRQ for at least one device: %pe\n",
ERR_PTR(ret));
}
static char *get_gpu_model_name(struct panthor_device *ptdev)
{
const u32 gpu_id = ptdev->gpu_info.gpu_id;
const u32 product_id = GPU_PROD_ID_MAKE(GPU_ARCH_MAJOR(gpu_id),
GPU_PROD_MAJOR(gpu_id));
const bool ray_intersection = !!(ptdev->gpu_info.gpu_features &
GPU_FEATURES_RAY_INTERSECTION);
const u8 shader_core_count = hweight64(ptdev->gpu_info.shader_present);
switch (product_id) {
case GPU_PROD_ID_MAKE(10, 2):
return "Mali-G710";
case GPU_PROD_ID_MAKE(10, 3):
return "Mali-G510";
case GPU_PROD_ID_MAKE(10, 4):
return "Mali-G310";
case GPU_PROD_ID_MAKE(10, 7):
return "Mali-G610";
case GPU_PROD_ID_MAKE(11, 2):
if (shader_core_count > 10 && ray_intersection)
return "Mali-G715-Immortalis";
else if (shader_core_count >= 7)
return "Mali-G715";
fallthrough;
case GPU_PROD_ID_MAKE(11, 3):
return "Mali-G615";
case GPU_PROD_ID_MAKE(12, 0):
if (shader_core_count >= 10 && ray_intersection)
return "Mali-G720-Immortalis";
else if (shader_core_count >= 6)
return "Mali-G720";
fallthrough;
case GPU_PROD_ID_MAKE(12, 1):
return "Mali-G620";
case GPU_PROD_ID_MAKE(13, 0):
if (shader_core_count >= 10 && ray_intersection)
return "Mali-G925-Immortalis";
else if (shader_core_count >= 6)
return "Mali-G725";
fallthrough;
case GPU_PROD_ID_MAKE(13, 1):
return "Mali-G625";
case GPU_PROD_ID_MAKE(14, 0):
return "Mali-G1-Ultra";
case GPU_PROD_ID_MAKE(14, 1):
return "Mali-G1-Premium";
case GPU_PROD_ID_MAKE(14, 3):
return "Mali-G1-Pro";
}
return "(Unknown Mali GPU)";
}
static int overload_shader_present(struct panthor_device *ptdev)
{
u64 contents;
int ret;
ret = nvmem_cell_read_variable_le_u64(ptdev->base.dev, "shader-present",
&contents);
if (!ret)
ptdev->gpu_info.shader_present = contents;
else if (ret == -ENOENT)
return 0;
else
return dev_err_probe(ptdev->base.dev, ret,
"Failed to read shader-present nvmem cell\n");
return 0;
}
static int panthor_gpu_info_init(struct panthor_device *ptdev)
{
unsigned int i;
void __iomem *gpu_iomem = ptdev->iomem + GPU_CONTROL_BASE;
ptdev->gpu_info.csf_id = gpu_read(gpu_iomem, GPU_CSF_ID);
ptdev->gpu_info.gpu_rev = gpu_read(gpu_iomem, GPU_REVID);
ptdev->gpu_info.core_features = gpu_read(gpu_iomem, GPU_CORE_FEATURES);
ptdev->gpu_info.l2_features = gpu_read(gpu_iomem, GPU_L2_FEATURES);
ptdev->gpu_info.tiler_features = gpu_read(gpu_iomem, GPU_TILER_FEATURES);
ptdev->gpu_info.mem_features = gpu_read(gpu_iomem, GPU_MEM_FEATURES);
ptdev->gpu_info.mmu_features = gpu_read(gpu_iomem, GPU_MMU_FEATURES);
ptdev->gpu_info.thread_features = gpu_read(gpu_iomem, GPU_THREAD_FEATURES);
ptdev->gpu_info.max_threads = gpu_read(gpu_iomem, GPU_THREAD_MAX_THREADS);
ptdev->gpu_info.thread_max_workgroup_size =
gpu_read(gpu_iomem, GPU_THREAD_MAX_WORKGROUP_SIZE);
ptdev->gpu_info.thread_max_barrier_size =
gpu_read(gpu_iomem, GPU_THREAD_MAX_BARRIER_SIZE);
ptdev->gpu_info.coherency_features = gpu_read(gpu_iomem, GPU_COHERENCY_FEATURES);
for (i = 0; i < 4; i++)
ptdev->gpu_info.texture_features[i] =
gpu_read(gpu_iomem, GPU_TEXTURE_FEATURES(i));
ptdev->gpu_info.as_present = gpu_read(gpu_iomem, GPU_AS_PRESENT);
/* Introduced in arch 11.x */
ptdev->gpu_info.gpu_features = gpu_read64(gpu_iomem, GPU_FEATURES);
if (panthor_hw_has_pwr_ctrl(ptdev)) {
void __iomem *pwr_iomem = gpu_iomem + PWR_CONTROL_BASE;
/* Introduced in arch 14.x */
ptdev->gpu_info.l2_present = gpu_read64(pwr_iomem, PWR_L2_PRESENT);
ptdev->gpu_info.tiler_present = gpu_read64(pwr_iomem, PWR_TILER_PRESENT);
ptdev->gpu_info.shader_present = gpu_read64(pwr_iomem, PWR_SHADER_PRESENT);
} else {
ptdev->gpu_info.shader_present = gpu_read64(gpu_iomem, GPU_SHADER_PRESENT);
ptdev->gpu_info.tiler_present = gpu_read64(gpu_iomem, GPU_TILER_PRESENT);
ptdev->gpu_info.l2_present = gpu_read64(gpu_iomem, GPU_L2_PRESENT);
}
return overload_shader_present(ptdev);
}
static int panthor_hw_info_init(struct panthor_device *ptdev)
{
u32 major, minor, status;
int ret;
ret = panthor_gpu_info_init(ptdev);
if (ret)
return ret;
major = GPU_VER_MAJOR(ptdev->gpu_info.gpu_id);
minor = GPU_VER_MINOR(ptdev->gpu_info.gpu_id);
status = GPU_VER_STATUS(ptdev->gpu_info.gpu_id);
drm_info(&ptdev->base,
"%s id 0x%x major 0x%x minor 0x%x status 0x%x",
get_gpu_model_name(ptdev), ptdev->gpu_info.gpu_id >> 16,
major, minor, status);
drm_info(&ptdev->base,
"Features: L2:%#x Tiler:%#x Mem:%#x MMU:%#x AS:%#x",
ptdev->gpu_info.l2_features,
ptdev->gpu_info.tiler_features,
ptdev->gpu_info.mem_features,
ptdev->gpu_info.mmu_features,
ptdev->gpu_info.as_present);
drm_info(&ptdev->base,
"shader_present=0x%0llx l2_present=0x%0llx tiler_present=0x%0llx",
ptdev->gpu_info.shader_present, ptdev->gpu_info.l2_present,
ptdev->gpu_info.tiler_present);
return 0;
}
static int panthor_hw_bind_device(struct panthor_device *ptdev)
{
struct panthor_hw *hdev = NULL;
const u32 arch_major = GPU_ARCH_MAJOR(ptdev->gpu_info.gpu_id);
int i = 0;
for (i = 0; i < ARRAY_SIZE(panthor_hw_match); i++) {
struct panthor_hw_entry *entry = &panthor_hw_match[i];
if (arch_major >= entry->arch_min && arch_major <= entry->arch_max) {
hdev = entry->hwdev;
break;
}
}
if (!hdev)
return -EOPNOTSUPP;
ptdev->hw = hdev;
return 0;
}
static int panthor_hw_gpu_id_init(struct panthor_device *ptdev)
{
ptdev->gpu_info.gpu_id = gpu_read(ptdev->iomem, GPU_ID);
if (!ptdev->gpu_info.gpu_id)
return -ENXIO;
return 0;
}
int panthor_hw_init(struct panthor_device *ptdev)
{
int ret = 0;
ret = panthor_hw_gpu_id_init(ptdev);
if (ret)
return ret;
ret = panthor_hw_bind_device(ptdev);
if (ret)
return ret;
return panthor_hw_info_init(ptdev);
}
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_HW_H__
#define __PANTHOR_HW_H__
#include "panthor_device.h"
#include "panthor_gpu_regs.h"
/**
* struct panthor_hw_ops - HW operations that are specific to a GPU
*/
struct panthor_hw_ops {
/** @soft_reset: Soft reset function pointer */
int (*soft_reset)(struct panthor_device *ptdev);
/** @l2_power_off: L2 power off function pointer */
void (*l2_power_off)(struct panthor_device *ptdev);
/** @l2_power_on: L2 power on function pointer */
int (*l2_power_on)(struct panthor_device *ptdev);
/** @power_changed_on: Start listening to power change IRQs */
int (*power_changed_on)(struct panthor_device *ptdev);
/** @power_changed_off: Stop listening to power change IRQs */
void (*power_changed_off)(struct panthor_device *ptdev);
};
/**
* struct panthor_hw - GPU specific register mapping and functions
*/
struct panthor_hw {
/** @features: Bitmap containing panthor_hw_feature */
/** @ops: Panthor HW specific operations */
struct panthor_hw_ops ops;
};
int panthor_hw_init(struct panthor_device *ptdev);
int panthor_hw_power_status_register(void);
void panthor_hw_power_status_unregister(void);
static inline int panthor_hw_soft_reset(struct panthor_device *ptdev)
{
return ptdev->hw->ops.soft_reset(ptdev);
}
static inline int panthor_hw_l2_power_on(struct panthor_device *ptdev)
{
return ptdev->hw->ops.l2_power_on(ptdev);
}
static inline void panthor_hw_l2_power_off(struct panthor_device *ptdev)
{
ptdev->hw->ops.l2_power_off(ptdev);
}
static inline bool panthor_hw_has_pwr_ctrl(struct panthor_device *ptdev)
{
return GPU_ARCH_MAJOR(ptdev->gpu_info.gpu_id) >= 14;
}
#endif /* __PANTHOR_HW_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2023 Collabora ltd. */
/* Copyright 2025 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_MMU_H__
#define __PANTHOR_MMU_H__
#include <linux/dma-resv.h>
struct drm_exec;
struct drm_sched_job;
struct drm_memory_stats;
struct panthor_gem_object;
struct panthor_heap_pool;
struct panthor_vm;
struct panthor_vma;
struct panthor_mmu;
int panthor_mmu_init(struct panthor_device *ptdev);
void panthor_mmu_unplug(struct panthor_device *ptdev);
void panthor_mmu_pre_reset(struct panthor_device *ptdev);
void panthor_mmu_post_reset(struct panthor_device *ptdev);
void panthor_mmu_suspend(struct panthor_device *ptdev);
void panthor_mmu_resume(struct panthor_device *ptdev);
int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo,
u64 offset, u64 size, u64 va, u32 flags);
int panthor_vm_unmap_range(struct panthor_vm *vm, u64 va, u64 size);
struct panthor_gem_object *
panthor_vm_get_bo_for_va(struct panthor_vm *vm, u64 va, u64 *bo_offset);
int panthor_vm_active(struct panthor_vm *vm);
void panthor_vm_idle(struct panthor_vm *vm);
u32 panthor_vm_page_size(struct panthor_vm *vm);
int panthor_vm_as(struct panthor_vm *vm);
struct panthor_heap_pool *
panthor_vm_get_heap_pool(struct panthor_vm *vm, bool create);
void panthor_vm_heaps_sizes(struct panthor_file *pfile, struct drm_memory_stats *stats);
struct panthor_vm *panthor_vm_get(struct panthor_vm *vm);
void panthor_vm_put(struct panthor_vm *vm);
struct panthor_vm *panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
u64 kernel_va_start, u64 kernel_va_size,
u64 kernel_auto_va_start,
u64 kernel_auto_va_size);
void panthor_vm_update_bo_reclaim_lru_locked(struct panthor_gem_object *bo);
int panthor_vm_evict_bo_mappings_locked(struct panthor_gem_object *bo);
unsigned long
panthor_mmu_reclaim_priv_bos(struct panthor_device *ptdev,
unsigned int nr_to_scan, unsigned long *remaining,
bool (*shrink)(struct drm_gem_object *,
struct ww_acquire_ctx *));
int panthor_vm_prepare_mapped_bos_resvs(struct drm_exec *exec,
struct panthor_vm *vm,
u32 slot_count);
int panthor_vm_add_bos_resvs_deps_to_job(struct panthor_vm *vm,
struct drm_sched_job *job);
void panthor_vm_add_job_fence_to_bos_resvs(struct panthor_vm *vm,
struct drm_sched_job *job);
struct dma_resv *panthor_vm_resv(struct panthor_vm *vm);
struct drm_gem_object *panthor_vm_root_gem(struct panthor_vm *vm);
void panthor_vm_pool_destroy(struct panthor_file *pfile);
int panthor_vm_pool_create(struct panthor_file *pfile);
int panthor_vm_pool_create_vm(struct panthor_device *ptdev,
struct panthor_vm_pool *pool,
struct drm_panthor_vm_create *args);
int panthor_vm_pool_destroy_vm(struct panthor_vm_pool *pool, u32 handle);
struct panthor_vm *panthor_vm_pool_get_vm(struct panthor_vm_pool *pool, u32 handle);
bool panthor_vm_has_unhandled_faults(struct panthor_vm *vm);
bool panthor_vm_is_unusable(struct panthor_vm *vm);
/*
* PANTHOR_VM_KERNEL_AUTO_VA: Use this magic address when you want the GEM
* logic to auto-allocate the virtual address in the reserved kernel VA range.
*/
#define PANTHOR_VM_KERNEL_AUTO_VA ~0ull
int panthor_vm_alloc_va(struct panthor_vm *vm, u64 va, u64 size,
struct drm_mm_node *va_node);
void panthor_vm_free_va(struct panthor_vm *vm, struct drm_mm_node *va_node);
int panthor_vm_bind_exec_sync_op(struct drm_file *file,
struct panthor_vm *vm,
struct drm_panthor_vm_bind_op *op);
struct drm_sched_job *
panthor_vm_bind_job_create(struct drm_file *file,
struct panthor_vm *vm,
const struct drm_panthor_vm_bind_op *op);
void panthor_vm_bind_job_put(struct drm_sched_job *job);
int panthor_vm_bind_job_prepare_resvs(struct drm_exec *exec,
struct drm_sched_job *job);
void panthor_vm_bind_job_update_resvs(struct drm_exec *exec, struct drm_sched_job *job);
void panthor_vm_update_resvs(struct panthor_vm *vm, struct drm_exec *exec,
struct dma_fence *fence,
enum dma_resv_usage private_usage,
enum dma_resv_usage extobj_usage);
int panthor_mmu_pt_cache_init(void);
void panthor_mmu_pt_cache_fini(void);
#ifdef CONFIG_DEBUG_FS
void panthor_mmu_debugfs_init(struct drm_minor *minor);
#endif
#endif
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2026 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_MMU_REGS_H__
#define __PANTHOR_MMU_REGS_H__
/* MMU regs */
#define MMU_INT_BASE 0x2000
/* AS_COMMAND register commands */
#define MMU_AS_BASE 0x2400
#define MMU_AS_SHIFT 6
#define MMU_AS(as) ((as) << MMU_AS_SHIFT)
#define AS_TRANSTAB(as) (MMU_AS(as) + 0x0)
#define AS_MEMATTR(as) (MMU_AS(as) + 0x8)
#define AS_MEMATTR_AARCH64_INNER_ALLOC_IMPL (2 << 2)
#define AS_MEMATTR_AARCH64_INNER_ALLOC_EXPL(w, r) ((3 << 2) | \
((w) ? BIT(0) : 0) | \
((r) ? BIT(1) : 0))
#define AS_MEMATTR_AARCH64_SH_MIDGARD_INNER (0 << 4)
#define AS_MEMATTR_AARCH64_SH_CPU_INNER (1 << 4)
#define AS_MEMATTR_AARCH64_SH_CPU_INNER_SHADER_COH (2 << 4)
#define AS_MEMATTR_AARCH64_SHARED (0 << 6)
#define AS_MEMATTR_AARCH64_INNER_OUTER_NC (1 << 6)
#define AS_MEMATTR_AARCH64_INNER_OUTER_WB (2 << 6)
#define AS_MEMATTR_AARCH64_FAULT (3 << 6)
#define AS_LOCKADDR(as) (MMU_AS(as) + 0x10)
#define AS_COMMAND(as) (MMU_AS(as) + 0x18)
#define AS_COMMAND_NOP 0
#define AS_COMMAND_UPDATE 1
#define AS_COMMAND_LOCK 2
#define AS_COMMAND_UNLOCK 3
#define AS_COMMAND_FLUSH_PT 4
#define AS_COMMAND_FLUSH_MEM 5
#define AS_LOCK_REGION_MIN_SIZE (1ULL << 15)
#define AS_FAULTSTATUS(as) (MMU_AS(as) + 0x1C)
#define AS_FAULTSTATUS_ACCESS_TYPE_MASK (0x3 << 8)
#define AS_FAULTSTATUS_ACCESS_TYPE_ATOMIC (0x0 << 8)
#define AS_FAULTSTATUS_ACCESS_TYPE_EX (0x1 << 8)
#define AS_FAULTSTATUS_ACCESS_TYPE_READ (0x2 << 8)
#define AS_FAULTSTATUS_ACCESS_TYPE_WRITE (0x3 << 8)
#define AS_FAULTADDRESS(as) (MMU_AS(as) + 0x20)
#define AS_STATUS(as) (MMU_AS(as) + 0x28)
#define AS_STATUS_AS_ACTIVE BIT(0)
#define AS_TRANSCFG(as) (MMU_AS(as) + 0x30)
#define AS_TRANSCFG_ADRMODE_UNMAPPED (1 << 0)
#define AS_TRANSCFG_ADRMODE_IDENTITY (2 << 0)
#define AS_TRANSCFG_ADRMODE_AARCH64_4K (6 << 0)
#define AS_TRANSCFG_ADRMODE_AARCH64_64K (8 << 0)
#define AS_TRANSCFG_INA_BITS(x) ((x) << 6)
#define AS_TRANSCFG_OUTA_BITS(x) ((x) << 14)
#define AS_TRANSCFG_SL_CONCAT BIT(22)
#define AS_TRANSCFG_PTW_MEMATTR_NC (1 << 24)
#define AS_TRANSCFG_PTW_MEMATTR_WB (2 << 24)
#define AS_TRANSCFG_PTW_SH_NS (0 << 28)
#define AS_TRANSCFG_PTW_SH_OS (2 << 28)
#define AS_TRANSCFG_PTW_SH_IS (3 << 28)
#define AS_TRANSCFG_PTW_RA BIT(30)
#define AS_TRANSCFG_DISABLE_HIER_AP BIT(33)
#define AS_TRANSCFG_DISABLE_AF_FAULT BIT(34)
#define AS_TRANSCFG_WXN BIT(35)
#define AS_TRANSCFG_XREADABLE BIT(36)
#define AS_FAULTEXTRA(as) (MMU_AS(as) + 0x38)
#endif /* __PANTHOR_MMU_REGS_H__ */
@@ -0,0 +1,578 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
/* Copyright 2025 ARM Limited. All rights reserved. */
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/cleanup.h>
#include <linux/iopoll.h>
#include <linux/wait.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include "panthor_device.h"
#include "panthor_gpu_regs.h"
#include "panthor_hw.h"
#include "panthor_pwr.h"
#include "panthor_pwr_regs.h"
#define PWR_INTERRUPTS_MASK \
(PWR_IRQ_POWER_CHANGED_SINGLE | \
PWR_IRQ_POWER_CHANGED_ALL | \
PWR_IRQ_DELEGATION_CHANGED | \
PWR_IRQ_RESET_COMPLETED | \
PWR_IRQ_RETRACT_COMPLETED | \
PWR_IRQ_INSPECT_COMPLETED | \
PWR_IRQ_COMMAND_NOT_ALLOWED | \
PWR_IRQ_COMMAND_INVALID)
#define PWR_ALL_CORES_MASK GENMASK_U64(63, 0)
#define PWR_DOMAIN_MAX_BITS 16
#define PWR_TRANSITION_TIMEOUT_US (2ULL * USEC_PER_SEC)
#define PWR_RETRACT_TIMEOUT_US (2ULL * USEC_PER_MSEC)
#define PWR_RESET_TIMEOUT_MS 500
/**
* struct panthor_pwr - PWR_CONTROL block management data.
*/
struct panthor_pwr {
/** @iomem: CPU mapping of PWR_CONTROL iomem region */
void __iomem *iomem;
/** @irq: PWR irq. */
struct panthor_irq irq;
/** @reqs_lock: Lock protecting access to pending_reqs. */
spinlock_t reqs_lock;
/** @pending_reqs: Pending PWR requests. */
u32 pending_reqs;
/** @reqs_acked: PWR request wait queue. */
wait_queue_head_t reqs_acked;
};
static void panthor_pwr_irq_handler(struct panthor_device *ptdev, u32 status)
{
struct panthor_pwr *pwr = ptdev->pwr;
spin_lock(&ptdev->pwr->reqs_lock);
gpu_write(pwr->irq.iomem, INT_CLEAR, status);
if (unlikely(status & PWR_IRQ_COMMAND_NOT_ALLOWED))
drm_err(&ptdev->base, "PWR_IRQ: COMMAND_NOT_ALLOWED");
if (unlikely(status & PWR_IRQ_COMMAND_INVALID))
drm_err(&ptdev->base, "PWR_IRQ: COMMAND_INVALID");
if (status & ptdev->pwr->pending_reqs) {
ptdev->pwr->pending_reqs &= ~status;
wake_up_all(&ptdev->pwr->reqs_acked);
}
spin_unlock(&ptdev->pwr->reqs_lock);
}
PANTHOR_IRQ_HANDLER(pwr, panthor_pwr_irq_handler);
static void panthor_pwr_write_command(struct panthor_device *ptdev, u32 command, u64 args)
{
struct panthor_pwr *pwr = ptdev->pwr;
if (args)
gpu_write64(pwr->iomem, PWR_CMDARG, args);
gpu_write(pwr->iomem, PWR_COMMAND, command);
}
static bool reset_irq_raised(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr = ptdev->pwr;
return gpu_read(pwr->irq.iomem, INT_RAWSTAT) & PWR_IRQ_RESET_COMPLETED;
}
static bool reset_pending(struct panthor_device *ptdev)
{
return (ptdev->pwr->pending_reqs & PWR_IRQ_RESET_COMPLETED);
}
static int panthor_pwr_reset(struct panthor_device *ptdev, u32 reset_cmd)
{
struct panthor_pwr *pwr = ptdev->pwr;
scoped_guard(spinlock_irqsave, &ptdev->pwr->reqs_lock) {
if (reset_pending(ptdev)) {
drm_WARN(&ptdev->base, 1, "Reset already pending");
} else {
ptdev->pwr->pending_reqs |= PWR_IRQ_RESET_COMPLETED;
gpu_write(pwr->irq.iomem, INT_CLEAR, PWR_IRQ_RESET_COMPLETED);
panthor_pwr_write_command(ptdev, reset_cmd, 0);
}
}
if (!wait_event_timeout(ptdev->pwr->reqs_acked, !reset_pending(ptdev),
msecs_to_jiffies(PWR_RESET_TIMEOUT_MS))) {
guard(spinlock_irqsave)(&ptdev->pwr->reqs_lock);
if (reset_pending(ptdev) && !reset_irq_raised(ptdev)) {
drm_err(&ptdev->base, "RESET timed out (0x%x)", reset_cmd);
return -ETIMEDOUT;
}
ptdev->pwr->pending_reqs &= ~PWR_IRQ_RESET_COMPLETED;
}
return 0;
}
static const char *get_domain_name(u8 domain)
{
switch (domain) {
case PWR_COMMAND_DOMAIN_L2:
return "L2";
case PWR_COMMAND_DOMAIN_TILER:
return "Tiler";
case PWR_COMMAND_DOMAIN_SHADER:
return "Shader";
case PWR_COMMAND_DOMAIN_BASE:
return "Base";
case PWR_COMMAND_DOMAIN_STACK:
return "Stack";
}
return "Unknown";
}
static u32 get_domain_base(u8 domain)
{
switch (domain) {
case PWR_COMMAND_DOMAIN_L2:
return PWR_L2_PRESENT;
case PWR_COMMAND_DOMAIN_TILER:
return PWR_TILER_PRESENT;
case PWR_COMMAND_DOMAIN_SHADER:
return PWR_SHADER_PRESENT;
case PWR_COMMAND_DOMAIN_BASE:
return PWR_BASE_PRESENT;
case PWR_COMMAND_DOMAIN_STACK:
return PWR_STACK_PRESENT;
}
return 0;
}
static u32 get_domain_ready_reg(u32 domain)
{
return get_domain_base(domain) + (PWR_L2_READY - PWR_L2_PRESENT);
}
static u32 get_domain_pwrtrans_reg(u32 domain)
{
return get_domain_base(domain) + (PWR_L2_PWRTRANS - PWR_L2_PRESENT);
}
static bool is_valid_domain(u32 domain)
{
return get_domain_base(domain) != 0;
}
static bool has_rtu(struct panthor_device *ptdev)
{
return ptdev->gpu_info.gpu_features & GPU_FEATURES_RAY_TRAVERSAL;
}
static u8 get_domain_subdomain(struct panthor_device *ptdev, u32 domain)
{
if (domain == PWR_COMMAND_DOMAIN_SHADER && has_rtu(ptdev))
return PWR_COMMAND_SUBDOMAIN_RTU;
return 0;
}
static int panthor_pwr_domain_wait_transition(struct panthor_device *ptdev, u32 domain,
u32 timeout_us)
{
struct panthor_pwr *pwr = ptdev->pwr;
u32 pwrtrans_reg = get_domain_pwrtrans_reg(domain);
u64 val;
int ret = 0;
ret = gpu_read64_poll_timeout(pwr->iomem, pwrtrans_reg, val, !(PWR_ALL_CORES_MASK & val), 100,
timeout_us);
if (ret) {
drm_err(&ptdev->base, "%s domain power in transition, pwrtrans(0x%llx)",
get_domain_name(domain), val);
return ret;
}
return 0;
}
static void panthor_pwr_debug_info_show(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr = ptdev->pwr;
drm_info(&ptdev->base, "GPU_FEATURES: 0x%016llx", ptdev->gpu_info.gpu_features);
drm_info(&ptdev->base, "PWR_STATUS: 0x%016llx", gpu_read64(pwr->iomem, PWR_STATUS));
drm_info(&ptdev->base, "L2_PRESENT: 0x%016llx", gpu_read64(pwr->iomem, PWR_L2_PRESENT));
drm_info(&ptdev->base, "L2_PWRTRANS: 0x%016llx", gpu_read64(pwr->iomem, PWR_L2_PWRTRANS));
drm_info(&ptdev->base, "L2_READY: 0x%016llx", gpu_read64(pwr->iomem, PWR_L2_READY));
drm_info(&ptdev->base, "TILER_PRESENT: 0x%016llx", gpu_read64(pwr->iomem, PWR_TILER_PRESENT));
drm_info(&ptdev->base, "TILER_PWRTRANS: 0x%016llx", gpu_read64(pwr->iomem, PWR_TILER_PWRTRANS));
drm_info(&ptdev->base, "TILER_READY: 0x%016llx", gpu_read64(pwr->iomem, PWR_TILER_READY));
drm_info(&ptdev->base, "SHADER_PRESENT: 0x%016llx", gpu_read64(pwr->iomem, PWR_SHADER_PRESENT));
drm_info(&ptdev->base, "SHADER_PWRTRANS: 0x%016llx", gpu_read64(pwr->iomem, PWR_SHADER_PWRTRANS));
drm_info(&ptdev->base, "SHADER_READY: 0x%016llx", gpu_read64(pwr->iomem, PWR_SHADER_READY));
}
static int panthor_pwr_domain_transition(struct panthor_device *ptdev, u32 cmd, u32 domain,
u64 mask, u32 timeout_us)
{
struct panthor_pwr *pwr = ptdev->pwr;
u32 ready_reg = get_domain_ready_reg(domain);
u32 pwr_cmd = PWR_COMMAND_DEF(cmd, domain, get_domain_subdomain(ptdev, domain));
u64 expected_val = 0;
u64 val;
int ret = 0;
if (drm_WARN_ON(&ptdev->base, !is_valid_domain(domain)))
return -EINVAL;
switch (cmd) {
case PWR_COMMAND_POWER_DOWN:
expected_val = 0;
break;
case PWR_COMMAND_POWER_UP:
expected_val = mask;
break;
default:
drm_err(&ptdev->base, "Invalid power domain transition command (0x%x)", cmd);
return -EINVAL;
}
ret = panthor_pwr_domain_wait_transition(ptdev, domain, timeout_us);
if (ret)
return ret;
/* domain already in target state, return early */
if ((gpu_read64(pwr->iomem, ready_reg) & mask) == expected_val)
return 0;
panthor_pwr_write_command(ptdev, pwr_cmd, mask);
ret = gpu_read64_poll_timeout(pwr->iomem, ready_reg, val, (mask & val) == expected_val,
100, timeout_us);
if (ret) {
drm_err(&ptdev->base,
"timeout waiting on %s power domain transition, cmd(0x%x), arg(0x%llx)",
get_domain_name(domain), pwr_cmd, mask);
panthor_pwr_debug_info_show(ptdev);
return ret;
}
return 0;
}
#define panthor_pwr_domain_power_off(__ptdev, __domain, __mask, __timeout_us) \
panthor_pwr_domain_transition(__ptdev, PWR_COMMAND_POWER_DOWN, __domain, __mask, \
__timeout_us)
#define panthor_pwr_domain_power_on(__ptdev, __domain, __mask, __timeout_us) \
panthor_pwr_domain_transition(__ptdev, PWR_COMMAND_POWER_UP, __domain, __mask, __timeout_us)
/**
* retract_domain() - Retract control of a domain from MCU
* @ptdev: Device.
* @domain: Domain to retract the control
*
* Retracting L2 domain is not expected since it won't be delegated.
*
* Return: 0 on success or retracted already.
* -EPERM if domain is L2.
* A negative error code otherwise.
*/
static int retract_domain(struct panthor_device *ptdev, u32 domain)
{
struct panthor_pwr *pwr = ptdev->pwr;
const u32 pwr_cmd = PWR_COMMAND_DEF(PWR_COMMAND_RETRACT, domain, 0);
const u64 pwr_status = gpu_read64(pwr->iomem, PWR_STATUS);
const u64 delegated_mask = PWR_STATUS_DOMAIN_DELEGATED(domain);
const u64 allow_mask = PWR_STATUS_DOMAIN_ALLOWED(domain);
u64 val;
int ret;
if (drm_WARN_ON(&ptdev->base, domain == PWR_COMMAND_DOMAIN_L2))
return -EPERM;
ret = gpu_read64_poll_timeout(pwr->iomem, PWR_STATUS, val,
!(PWR_STATUS_RETRACT_PENDING & val), 0,
PWR_RETRACT_TIMEOUT_US);
if (ret) {
drm_err(&ptdev->base, "%s domain retract pending", get_domain_name(domain));
return ret;
}
if (!(pwr_status & delegated_mask)) {
drm_dbg(&ptdev->base, "%s domain already retracted", get_domain_name(domain));
return 0;
}
panthor_pwr_write_command(ptdev, pwr_cmd, 0);
/*
* On successful retraction
* allow-flag will be set with delegated-flag being cleared.
*/
ret = gpu_read64_poll_timeout(pwr->iomem, PWR_STATUS, val,
((delegated_mask | allow_mask) & val) == allow_mask, 10,
PWR_TRANSITION_TIMEOUT_US);
if (ret) {
drm_err(&ptdev->base, "Retracting %s domain timeout, cmd(0x%x)",
get_domain_name(domain), pwr_cmd);
return ret;
}
return 0;
}
/**
* delegate_domain() - Delegate control of a domain to MCU
* @ptdev: Device.
* @domain: Domain to delegate the control
*
* Delegating L2 domain is prohibited.
*
* Return:
* * 0 on success or delegated already.
* * -EPERM if domain is L2.
* * A negative error code otherwise.
*/
static int delegate_domain(struct panthor_device *ptdev, u32 domain)
{
struct panthor_pwr *pwr = ptdev->pwr;
const u32 pwr_cmd = PWR_COMMAND_DEF(PWR_COMMAND_DELEGATE, domain, 0);
const u64 pwr_status = gpu_read64(pwr->iomem, PWR_STATUS);
const u64 allow_mask = PWR_STATUS_DOMAIN_ALLOWED(domain);
const u64 delegated_mask = PWR_STATUS_DOMAIN_DELEGATED(domain);
u64 val;
int ret;
if (drm_WARN_ON(&ptdev->base, domain == PWR_COMMAND_DOMAIN_L2))
return -EPERM;
/* Already delegated, exit early */
if (pwr_status & delegated_mask)
return 0;
/* Check if the command is allowed before delegating. */
if (!(pwr_status & allow_mask)) {
drm_warn(&ptdev->base, "Delegating %s domain not allowed", get_domain_name(domain));
return -EPERM;
}
ret = panthor_pwr_domain_wait_transition(ptdev, domain, PWR_TRANSITION_TIMEOUT_US);
if (ret)
return ret;
panthor_pwr_write_command(ptdev, pwr_cmd, 0);
/*
* On successful delegation
* allow-flag will be cleared with delegated-flag being set.
*/
ret = gpu_read64_poll_timeout(pwr->iomem, PWR_STATUS, val,
((delegated_mask | allow_mask) & val) == delegated_mask,
10, PWR_TRANSITION_TIMEOUT_US);
if (ret) {
drm_err(&ptdev->base, "Delegating %s domain timeout, cmd(0x%x)",
get_domain_name(domain), pwr_cmd);
return ret;
}
return 0;
}
static int panthor_pwr_delegate_domains(struct panthor_device *ptdev)
{
int ret;
if (!ptdev->pwr)
return 0;
ret = delegate_domain(ptdev, PWR_COMMAND_DOMAIN_SHADER);
if (ret)
return ret;
ret = delegate_domain(ptdev, PWR_COMMAND_DOMAIN_TILER);
if (ret)
goto err_retract_shader;
return 0;
err_retract_shader:
retract_domain(ptdev, PWR_COMMAND_DOMAIN_SHADER);
return ret;
}
/**
* panthor_pwr_domain_force_off - Forcefully power down a domain.
* @ptdev: Device.
* @domain: Domain to forcefully power down.
*
* This function will attempt to retract and power off the requested power
* domain. However, if retraction fails, the operation is aborted. If power off
* fails, the domain will remain retracted and under the host control.
*
* Return: 0 on success or a negative error code on failure.
*/
static int panthor_pwr_domain_force_off(struct panthor_device *ptdev, u32 domain)
{
struct panthor_pwr *pwr = ptdev->pwr;
const u64 domain_ready = gpu_read64(pwr->iomem, get_domain_ready_reg(domain));
int ret;
/* Domain already powered down, early exit. */
if (!domain_ready)
return 0;
/* Domain has to be in host control to issue power off command. */
ret = retract_domain(ptdev, domain);
if (ret)
return ret;
return panthor_pwr_domain_power_off(ptdev, domain, domain_ready, PWR_TRANSITION_TIMEOUT_US);
}
void panthor_pwr_unplug(struct panthor_device *ptdev)
{
unsigned long flags;
if (!ptdev->pwr)
return;
/* Make sure the IRQ handler is not running after that point. */
if (!IS_ENABLED(CONFIG_PM) || pm_runtime_active(ptdev->base.dev))
panthor_pwr_irq_suspend(&ptdev->pwr->irq);
/* Wake-up all waiters. */
spin_lock_irqsave(&ptdev->pwr->reqs_lock, flags);
ptdev->pwr->pending_reqs = 0;
wake_up_all(&ptdev->pwr->reqs_acked);
spin_unlock_irqrestore(&ptdev->pwr->reqs_lock, flags);
}
int panthor_pwr_init(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr;
int err, irq;
if (!panthor_hw_has_pwr_ctrl(ptdev))
return 0;
pwr = drmm_kzalloc(&ptdev->base, sizeof(*pwr), GFP_KERNEL);
if (!pwr)
return -ENOMEM;
pwr->iomem = ptdev->iomem + PWR_CONTROL_BASE;
spin_lock_init(&pwr->reqs_lock);
init_waitqueue_head(&pwr->reqs_acked);
ptdev->pwr = pwr;
irq = platform_get_irq_byname(to_platform_device(ptdev->base.dev), "gpu");
if (irq < 0)
return irq;
err = panthor_request_pwr_irq(ptdev, &pwr->irq, irq,
pwr->iomem + PWR_INT_BASE);
if (err)
return err;
panthor_pwr_irq_enable_events(&pwr->irq, PWR_INTERRUPTS_MASK);
panthor_pwr_irq_resume(&pwr->irq);
return 0;
}
int panthor_pwr_reset_soft(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr = ptdev->pwr;
if (!(gpu_read64(pwr->iomem, PWR_STATUS) & PWR_STATUS_ALLOW_SOFT_RESET)) {
drm_err(&ptdev->base, "RESET_SOFT not allowed");
return -EOPNOTSUPP;
}
return panthor_pwr_reset(ptdev, PWR_COMMAND_RESET_SOFT);
}
void panthor_pwr_l2_power_off(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr = ptdev->pwr;
const u64 l2_allow_mask = PWR_STATUS_DOMAIN_ALLOWED(PWR_COMMAND_DOMAIN_L2);
const u64 pwr_status = gpu_read64(pwr->iomem, PWR_STATUS);
/* Abort if L2 power off constraints are not satisfied */
if (!(pwr_status & l2_allow_mask)) {
drm_warn(&ptdev->base, "Power off L2 domain not allowed");
return;
}
/* It is expected that when halting the MCU, it would power down its
* delegated domains. However, an unresponsive or hung MCU may not do
* so, which is why we need to check and retract the domains back into
* host control to be powered down in the right order before powering
* down the L2.
*/
if (panthor_pwr_domain_force_off(ptdev, PWR_COMMAND_DOMAIN_TILER))
return;
if (panthor_pwr_domain_force_off(ptdev, PWR_COMMAND_DOMAIN_SHADER))
return;
panthor_pwr_domain_power_off(ptdev, PWR_COMMAND_DOMAIN_L2, ptdev->gpu_info.l2_present,
PWR_TRANSITION_TIMEOUT_US);
}
int panthor_pwr_l2_power_on(struct panthor_device *ptdev)
{
struct panthor_pwr *pwr = ptdev->pwr;
const u32 pwr_status = gpu_read64(pwr->iomem, PWR_STATUS);
const u32 l2_allow_mask = PWR_STATUS_DOMAIN_ALLOWED(PWR_COMMAND_DOMAIN_L2);
int ret;
if ((pwr_status & l2_allow_mask) == 0) {
drm_warn(&ptdev->base, "Power on L2 domain not allowed");
return -EPERM;
}
ret = panthor_pwr_domain_power_on(ptdev, PWR_COMMAND_DOMAIN_L2, ptdev->gpu_info.l2_present,
PWR_TRANSITION_TIMEOUT_US);
if (ret)
return ret;
/* Delegate control of the shader and tiler power domains to the MCU as
* it can better manage which shader/tiler cores need to be powered up
* or can be powered down based on currently running jobs.
*
* If the shader and tiler domains are already delegated to the MCU,
* this call would just return early.
*/
return panthor_pwr_delegate_domains(ptdev);
}
void panthor_pwr_suspend(struct panthor_device *ptdev)
{
if (!ptdev->pwr)
return;
panthor_pwr_irq_suspend(&ptdev->pwr->irq);
}
void panthor_pwr_resume(struct panthor_device *ptdev)
{
if (!ptdev->pwr)
return;
panthor_pwr_irq_resume(&ptdev->pwr->irq);
}
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_PWR_H__
#define __PANTHOR_PWR_H__
struct panthor_device;
void panthor_pwr_unplug(struct panthor_device *ptdev);
int panthor_pwr_init(struct panthor_device *ptdev);
int panthor_pwr_reset_soft(struct panthor_device *ptdev);
void panthor_pwr_l2_power_off(struct panthor_device *ptdev);
int panthor_pwr_l2_power_on(struct panthor_device *ptdev);
void panthor_pwr_suspend(struct panthor_device *ptdev);
void panthor_pwr_resume(struct panthor_device *ptdev);
#endif /* __PANTHOR_PWR_H__ */
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2026 ARM Limited. All rights reserved. */
#ifndef __PANTHOR_PWR_REGS_H__
#define __PANTHOR_PWR_REGS_H__
#define PWR_CONTROL_BASE 0x800
#define PWR_INT_BASE 0x0
#define PWR_IRQ_POWER_CHANGED_SINGLE BIT(0)
#define PWR_IRQ_POWER_CHANGED_ALL BIT(1)
#define PWR_IRQ_DELEGATION_CHANGED BIT(2)
#define PWR_IRQ_RESET_COMPLETED BIT(3)
#define PWR_IRQ_RETRACT_COMPLETED BIT(4)
#define PWR_IRQ_INSPECT_COMPLETED BIT(5)
#define PWR_IRQ_COMMAND_NOT_ALLOWED BIT(30)
#define PWR_IRQ_COMMAND_INVALID BIT(31)
#define PWR_STATUS 0x20
#define PWR_STATUS_ALLOW_L2 BIT_U64(0)
#define PWR_STATUS_ALLOW_TILER BIT_U64(1)
#define PWR_STATUS_ALLOW_SHADER BIT_U64(8)
#define PWR_STATUS_ALLOW_BASE BIT_U64(14)
#define PWR_STATUS_ALLOW_STACK BIT_U64(15)
#define PWR_STATUS_DOMAIN_ALLOWED(x) BIT_U64(x)
#define PWR_STATUS_DELEGATED_L2 BIT_U64(16)
#define PWR_STATUS_DELEGATED_TILER BIT_U64(17)
#define PWR_STATUS_DELEGATED_SHADER BIT_U64(24)
#define PWR_STATUS_DELEGATED_BASE BIT_U64(30)
#define PWR_STATUS_DELEGATED_STACK BIT_U64(31)
#define PWR_STATUS_DELEGATED_SHIFT 16
#define PWR_STATUS_DOMAIN_DELEGATED(x) BIT_U64((x) + PWR_STATUS_DELEGATED_SHIFT)
#define PWR_STATUS_ALLOW_SOFT_RESET BIT_U64(33)
#define PWR_STATUS_ALLOW_FAST_RESET BIT_U64(34)
#define PWR_STATUS_POWER_PENDING BIT_U64(41)
#define PWR_STATUS_RESET_PENDING BIT_U64(42)
#define PWR_STATUS_RETRACT_PENDING BIT_U64(43)
#define PWR_STATUS_INSPECT_PENDING BIT_U64(44)
#define PWR_COMMAND 0x28
#define PWR_COMMAND_POWER_UP 0x10
#define PWR_COMMAND_POWER_DOWN 0x11
#define PWR_COMMAND_DELEGATE 0x20
#define PWR_COMMAND_RETRACT 0x21
#define PWR_COMMAND_RESET_SOFT 0x31
#define PWR_COMMAND_RESET_FAST 0x32
#define PWR_COMMAND_INSPECT 0xF0
#define PWR_COMMAND_DOMAIN_L2 0
#define PWR_COMMAND_DOMAIN_TILER 1
#define PWR_COMMAND_DOMAIN_SHADER 8
#define PWR_COMMAND_DOMAIN_BASE 14
#define PWR_COMMAND_DOMAIN_STACK 15
#define PWR_COMMAND_SUBDOMAIN_RTU BIT(0)
#define PWR_COMMAND_DEF(cmd, domain, subdomain) \
(((subdomain) << 16) | ((domain) << 8) | (cmd))
#define PWR_CMDARG 0x30
#define PWR_L2_PRESENT 0x100
#define PWR_L2_READY 0x108
#define PWR_L2_PWRTRANS 0x110
#define PWR_L2_PWRACTIVE 0x118
#define PWR_TILER_PRESENT 0x140
#define PWR_TILER_READY 0x148
#define PWR_TILER_PWRTRANS 0x150
#define PWR_TILER_PWRACTIVE 0x158
#define PWR_SHADER_PRESENT 0x200
#define PWR_SHADER_READY 0x208
#define PWR_SHADER_PWRTRANS 0x210
#define PWR_SHADER_PWRACTIVE 0x218
#define PWR_BASE_PRESENT 0x380
#define PWR_BASE_READY 0x388
#define PWR_BASE_PWRTRANS 0x390
#define PWR_BASE_PWRACTIVE 0x398
#define PWR_STACK_PRESENT 0x3c0
#define PWR_STACK_READY 0x3c8
#define PWR_STACK_PWRTRANS 0x3d0
#endif /* __PANTHOR_PWR_REGS_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2023 Collabora ltd. */
#ifndef __PANTHOR_SCHED_H__
#define __PANTHOR_SCHED_H__
struct drm_exec;
struct dma_fence;
struct drm_file;
struct drm_gem_object;
struct drm_sched_job;
struct drm_memory_stats;
struct drm_panthor_group_create;
struct drm_panthor_queue_create;
struct drm_panthor_group_get_state;
struct drm_panthor_queue_submit;
struct panthor_device;
struct panthor_file;
struct panthor_group_pool;
struct panthor_job;
int panthor_group_create(struct panthor_file *pfile,
const struct drm_panthor_group_create *group_args,
const struct drm_panthor_queue_create *queue_args,
u64 drm_client_id);
int panthor_group_destroy(struct panthor_file *pfile, u32 group_handle);
int panthor_group_get_state(struct panthor_file *pfile,
struct drm_panthor_group_get_state *get_state);
struct drm_sched_job *
panthor_job_create(struct panthor_file *pfile,
u16 group_handle,
const struct drm_panthor_queue_submit *qsubmit,
u64 drm_client_id);
struct drm_sched_job *panthor_job_get(struct drm_sched_job *job);
struct panthor_vm *panthor_job_vm(struct drm_sched_job *sched_job);
void panthor_job_put(struct drm_sched_job *job);
void panthor_job_update_resvs(struct drm_exec *exec, struct drm_sched_job *job);
int panthor_group_pool_create(struct panthor_file *pfile);
void panthor_group_pool_destroy(struct panthor_file *pfile);
void panthor_fdinfo_gather_group_mem_info(struct panthor_file *pfile,
struct drm_memory_stats *stats);
int panthor_sched_init(struct panthor_device *ptdev);
void panthor_sched_unplug(struct panthor_device *ptdev);
void panthor_sched_pre_reset(struct panthor_device *ptdev);
void panthor_sched_post_reset(struct panthor_device *ptdev, bool reset_failed);
void panthor_sched_suspend(struct panthor_device *ptdev);
void panthor_sched_resume(struct panthor_device *ptdev);
void panthor_sched_report_mmu_fault(struct panthor_device *ptdev);
void panthor_sched_prepare_for_vm_destruction(struct panthor_device *ptdev);
void panthor_sched_report_fw_events(struct panthor_device *ptdev, u32 events);
void panthor_fdinfo_gather_group_samples(struct panthor_file *pfile);
#endif
@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 Collabora ltd. */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM panthor
#if !defined(__PANTHOR_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ)
#define __PANTHOR_TRACE_H__
#include <linux/tracepoint.h>
#include <linux/types.h>
#include "panthor_hw.h"
/**
* gpu_power_status - called whenever parts of GPU hardware are turned on or off
* @dev: pointer to the &struct device, for printing the device name
* @shader_bitmap: bitmap where a high bit indicates the shader core at a given
* bit index is on, and a low bit indicates a shader core is
* either powered off or absent
* @tiler_bitmap: bitmap where a high bit indicates the tiler unit at a given
* bit index is on, and a low bit indicates a tiler unit is
* either powered off or absent
* @l2_bitmap: bitmap where a high bit indicates the L2 cache at a given bit
* index is on, and a low bit indicates the L2 cache is either
* powered off or absent
*/
TRACE_EVENT_FN(gpu_power_status,
TP_PROTO(const struct device *dev, u64 shader_bitmap, u64 tiler_bitmap,
u64 l2_bitmap),
TP_ARGS(dev, shader_bitmap, tiler_bitmap, l2_bitmap),
TP_STRUCT__entry(
__string(dev_name, dev_name(dev))
__field(u64, shader_bitmap)
__field(u64, tiler_bitmap)
__field(u64, l2_bitmap)
),
TP_fast_assign(
__assign_str(dev_name);
__entry->shader_bitmap = shader_bitmap;
__entry->tiler_bitmap = tiler_bitmap;
__entry->l2_bitmap = l2_bitmap;
),
TP_printk("%s: shader_bitmap=0x%llx tiler_bitmap=0x%llx l2_bitmap=0x%llx",
__get_str(dev_name), __entry->shader_bitmap, __entry->tiler_bitmap,
__entry->l2_bitmap
),
panthor_hw_power_status_register, panthor_hw_power_status_unregister
);
/**
* gpu_job_irq - called after a job interrupt from firmware completes
* @dev: pointer to the &struct device, for printing the device name
* @events: bitmask of BIT(CSG id) | BIT(31) for a global event
* @duration_ns: Nanoseconds between job IRQ handler entry and exit
*
* The panthor_job_irq_handler() function instrumented by this tracepoint exits
* once it has queued the firmware interrupts for processing, not when the
* firmware interrupts are fully processed. This tracepoint allows for debugging
* issues with delays in the workqueue's processing of events.
*/
TRACE_EVENT(gpu_job_irq,
TP_PROTO(const struct device *dev, u32 events, u32 duration_ns),
TP_ARGS(dev, events, duration_ns),
TP_STRUCT__entry(
__string(dev_name, dev_name(dev))
__field(u32, events)
__field(u32, duration_ns)
),
TP_fast_assign(
__assign_str(dev_name);
__entry->events = events;
__entry->duration_ns = duration_ns;
),
TP_printk("%s: events=0x%x duration_ns=%d", __get_str(dev_name),
__entry->events, __entry->duration_ns)
);
#endif /* __PANTHOR_TRACE_H__ */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE panthor_trace
#include <trace/define_trace.h>