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,18 @@
# SPDX-License-Identifier: GPL-2.0
config DRM_PANFROST
tristate "Panfrost (DRM support for ARM Mali Midgard/Bifrost GPUs)"
depends on DRM
depends on ARM || ARM64 || COMPILE_TEST
depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_LPAE
depends on MMU
select DRM_SCHED
select IOMMU_SUPPORT
select IOMMU_IO_PGTABLE_LPAE
select DRM_GEM_SHMEM_HELPER
select PM_DEVFREQ
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select WANT_DEV_COREDUMP
help
DRM driver for ARM Mali Midgard (T6xx, T7xx, T8xx) and
Bifrost (G3x, G5x, G7x) GPUs.
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
panfrost-y := \
panfrost_drv.o \
panfrost_device.o \
panfrost_devfreq.o \
panfrost_gem.o \
panfrost_gem_shrinker.o \
panfrost_gpu.o \
panfrost_job.o \
panfrost_mmu.o \
panfrost_perfcnt.o \
panfrost_dump.o
obj-$(CONFIG_DRM_PANFROST) += panfrost.o
+14
View File
@@ -0,0 +1,14 @@
- Thermal support.
- Bifrost support:
- DT bindings (Neil, WIP)
- MMU page table format and address space setup
- Bifrost specific feature and issue handling
- Coherent DMA support
- Support userspace controlled GPU virtual addresses. Needed for Vulkan. (Tomeu)
- Compute job support. So called 'compute only' jobs need to be plumbed up to
userspace.
- Support core dump on job failure
@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Collabora ltd. */
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>
#include <linux/nvmem-consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <drm/drm_print.h>
#include "panfrost_device.h"
#include "panfrost_devfreq.h"
static void panfrost_devfreq_update_utilization(struct panfrost_devfreq *pfdevfreq)
{
ktime_t now, last;
now = ktime_get();
last = pfdevfreq->time_last_update;
if (pfdevfreq->busy_count > 0)
pfdevfreq->busy_time += ktime_sub(now, last);
else
pfdevfreq->idle_time += ktime_sub(now, last);
pfdevfreq->time_last_update = now;
}
static int panfrost_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
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);
if (!err)
pfdev->pfdevfreq.current_frequency = *freq;
return err;
}
static void panfrost_devfreq_reset(struct panfrost_devfreq *pfdevfreq)
{
pfdevfreq->busy_time = 0;
pfdevfreq->idle_time = 0;
pfdevfreq->time_last_update = ktime_get();
}
static int panfrost_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *status)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
unsigned long irqflags;
status->current_frequency = clk_get_rate(pfdev->clock);
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
status->total_time = ktime_to_ns(ktime_add(pfdevfreq->busy_time,
pfdevfreq->idle_time));
status->busy_time = ktime_to_ns(pfdevfreq->busy_time);
panfrost_devfreq_reset(pfdevfreq);
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
dev_dbg(pfdev->base.dev, "busy %lu total %lu %lu %% freq %lu MHz\n",
status->busy_time, status->total_time,
status->busy_time / (status->total_time / 100),
status->current_frequency / 1000 / 1000);
return 0;
}
static struct devfreq_dev_profile panfrost_devfreq_profile = {
.timer = DEVFREQ_TIMER_DELAYED,
.polling_ms = 50, /* ~3 frames */
.target = panfrost_devfreq_target,
.get_dev_status = panfrost_devfreq_get_dev_status,
};
static int panfrost_read_speedbin(struct device *dev)
{
u32 val;
int ret;
ret = nvmem_cell_read_variable_le_u32(dev, "speed-bin", &val);
if (ret) {
/*
* -ENOENT means that this platform doesn't support speedbins
* as it didn't declare any speed-bin nvmem: in this case, we
* keep going without it; any other error means that we are
* supposed to read the bin value, but we failed doing so.
*/
if (ret != -ENOENT && ret != -EOPNOTSUPP) {
DRM_DEV_ERROR(dev, "Cannot read speed-bin (%d).", ret);
return ret;
}
return 0;
}
DRM_DEV_DEBUG(dev, "Using speed-bin = 0x%x\n", val);
return devm_pm_opp_set_supported_hw(dev, &val, 1);
}
int panfrost_devfreq_init(struct panfrost_device *pfdev)
{
int ret;
struct dev_pm_opp *opp;
unsigned long cur_freq;
struct device *dev = pfdev->base.dev;
struct devfreq *devfreq;
struct thermal_cooling_device *cooling;
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
unsigned long freq = ULONG_MAX;
if (pfdev->comp->num_supplies > 1) {
/*
* GPUs with more than 1 supply require platform-specific handling:
* continue without devfreq
*/
DRM_DEV_INFO(dev, "More than 1 supply is not supported yet\n");
return 0;
}
ret = panfrost_read_speedbin(dev);
if (ret)
return ret;
ret = devm_pm_opp_set_regulators(dev, pfdev->comp->supply_names);
if (ret) {
/* Continue if the optional regulator is missing */
if (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) {
/* Optional, continue without devfreq */
if (ret == -ENODEV)
ret = 0;
return ret;
}
pfdevfreq->opp_of_table_added = true;
spin_lock_init(&pfdevfreq->lock);
panfrost_devfreq_reset(pfdevfreq);
cur_freq = clk_get_rate(pfdev->clock);
opp = devfreq_recommended_opp(dev, &cur_freq, 0);
if (IS_ERR(opp))
return PTR_ERR(opp);
panfrost_devfreq_profile.initial_freq = cur_freq;
/*
* We could wait until panfrost_devfreq_target() to set this value, but
* since the simple_ondemand governor works asynchronously, there's a
* chance by the time someone opens the device's fdinfo file, current
* frequency hasn't been updated yet, so let's just do an early set.
*/
pfdevfreq->current_frequency = 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);
pfdevfreq->fast_rate = freq;
dev_pm_opp_put(opp);
/*
* Setup default thresholds for the simple_ondemand governor.
* The values are chosen based on experiments.
*/
pfdevfreq->gov_data.upthreshold = 45;
pfdevfreq->gov_data.downdifferential = 5;
devfreq = devm_devfreq_add_device(dev, &panfrost_devfreq_profile,
DEVFREQ_GOV_SIMPLE_ONDEMAND,
&pfdevfreq->gov_data);
if (IS_ERR(devfreq)) {
DRM_DEV_ERROR(dev, "Couldn't initialize GPU devfreq\n");
return PTR_ERR(devfreq);
}
pfdevfreq->devfreq = devfreq;
cooling = devfreq_cooling_em_register(devfreq, NULL);
if (IS_ERR(cooling))
DRM_DEV_INFO(dev, "Failed to register cooling device\n");
else
pfdevfreq->cooling = cooling;
return 0;
}
void panfrost_devfreq_fini(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (pfdevfreq->cooling) {
devfreq_cooling_unregister(pfdevfreq->cooling);
pfdevfreq->cooling = NULL;
}
}
void panfrost_devfreq_resume(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (!pfdevfreq->devfreq)
return;
panfrost_devfreq_reset(pfdevfreq);
devfreq_resume_device(pfdevfreq->devfreq);
}
void panfrost_devfreq_suspend(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (!pfdevfreq->devfreq)
return;
devfreq_suspend_device(pfdevfreq->devfreq);
}
void panfrost_devfreq_record_busy(struct panfrost_devfreq *pfdevfreq)
{
unsigned long irqflags;
if (!pfdevfreq->devfreq)
return;
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
pfdevfreq->busy_count++;
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
}
void panfrost_devfreq_record_idle(struct panfrost_devfreq *pfdevfreq)
{
unsigned long irqflags;
if (!pfdevfreq->devfreq)
return;
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
WARN_ON(--pfdevfreq->busy_count < 0);
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2019 Collabora ltd. */
#ifndef __PANFROST_DEVFREQ_H__
#define __PANFROST_DEVFREQ_H__
#include <linux/devfreq.h>
#include <linux/spinlock.h>
#include <linux/ktime.h>
struct devfreq;
struct thermal_cooling_device;
struct panfrost_device;
struct panfrost_devfreq {
struct devfreq *devfreq;
struct thermal_cooling_device *cooling;
struct devfreq_simple_ondemand_data gov_data;
bool opp_of_table_added;
unsigned long current_frequency;
unsigned long fast_rate;
ktime_t busy_time;
ktime_t idle_time;
ktime_t time_last_update;
int busy_count;
/*
* Protect busy_time, idle_time, time_last_update and busy_count
* because these can be updated concurrently between multiple jobs.
*/
spinlock_t lock;
};
int panfrost_devfreq_init(struct panfrost_device *pfdev);
void panfrost_devfreq_fini(struct panfrost_device *pfdev);
void panfrost_devfreq_resume(struct panfrost_device *pfdev);
void panfrost_devfreq_suspend(struct panfrost_device *pfdev);
void panfrost_devfreq_record_busy(struct panfrost_devfreq *devfreq);
void panfrost_devfreq_record_idle(struct panfrost_devfreq *devfreq);
#endif /* __PANFROST_DEVFREQ_H__ */
@@ -0,0 +1,566 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include "panfrost_device.h"
#include "panfrost_devfreq.h"
#include "panfrost_features.h"
#include "panfrost_gem.h"
#include "panfrost_issues.h"
#include "panfrost_gpu.h"
#include "panfrost_job.h"
#include "panfrost_mmu.h"
#include "panfrost_perfcnt.h"
static int panfrost_reset_init(struct panfrost_device *pfdev)
{
pfdev->rstc = devm_reset_control_array_get_optional_exclusive(pfdev->base.dev);
if (IS_ERR(pfdev->rstc)) {
dev_err(pfdev->base.dev, "get reset failed %ld\n", PTR_ERR(pfdev->rstc));
return PTR_ERR(pfdev->rstc);
}
return reset_control_deassert(pfdev->rstc);
}
static void panfrost_reset_fini(struct panfrost_device *pfdev)
{
reset_control_assert(pfdev->rstc);
}
static int panfrost_clk_init(struct panfrost_device *pfdev)
{
int err;
unsigned long rate;
pfdev->clock = devm_clk_get(pfdev->base.dev, NULL);
if (IS_ERR(pfdev->clock)) {
dev_err(pfdev->base.dev, "get clock failed %ld\n", PTR_ERR(pfdev->clock));
return PTR_ERR(pfdev->clock);
}
rate = clk_get_rate(pfdev->clock);
dev_info(pfdev->base.dev, "clock rate = %lu\n", rate);
err = clk_prepare_enable(pfdev->clock);
if (err)
return err;
pfdev->bus_clock = devm_clk_get_optional(pfdev->base.dev, "bus");
if (IS_ERR(pfdev->bus_clock)) {
dev_err(pfdev->base.dev, "get bus_clock failed %ld\n",
PTR_ERR(pfdev->bus_clock));
err = PTR_ERR(pfdev->bus_clock);
goto disable_clock;
}
if (pfdev->bus_clock) {
rate = clk_get_rate(pfdev->bus_clock);
dev_info(pfdev->base.dev, "bus_clock rate = %lu\n", rate);
err = clk_prepare_enable(pfdev->bus_clock);
if (err)
goto disable_clock;
}
pfdev->bus_ace_clock = devm_clk_get_optional(pfdev->base.dev, "bus_ace");
if (IS_ERR(pfdev->bus_ace_clock)) {
err = PTR_ERR(pfdev->bus_ace_clock);
dev_err(pfdev->base.dev, "get bus_ace_clock failed %d\n", err);
goto disable_bus_clock;
}
err = clk_prepare_enable(pfdev->bus_ace_clock);
if (err)
goto disable_bus_clock;
return 0;
disable_bus_clock:
clk_disable_unprepare(pfdev->bus_clock);
disable_clock:
clk_disable_unprepare(pfdev->clock);
return err;
}
static void panfrost_clk_fini(struct panfrost_device *pfdev)
{
clk_disable_unprepare(pfdev->bus_ace_clock);
clk_disable_unprepare(pfdev->bus_clock);
clk_disable_unprepare(pfdev->clock);
}
static int panfrost_regulator_init(struct panfrost_device *pfdev)
{
int ret, i;
pfdev->regulators = devm_kcalloc(pfdev->base.dev, pfdev->comp->num_supplies,
sizeof(*pfdev->regulators),
GFP_KERNEL);
if (!pfdev->regulators)
return -ENOMEM;
for (i = 0; i < pfdev->comp->num_supplies; i++)
pfdev->regulators[i].supply = pfdev->comp->supply_names[i];
ret = devm_regulator_bulk_get(pfdev->base.dev,
pfdev->comp->num_supplies,
pfdev->regulators);
if (ret < 0) {
if (ret != -EPROBE_DEFER)
dev_err(pfdev->base.dev, "failed to get regulators: %d\n",
ret);
return ret;
}
ret = regulator_bulk_enable(pfdev->comp->num_supplies,
pfdev->regulators);
if (ret < 0) {
dev_err(pfdev->base.dev, "failed to enable regulators: %d\n", ret);
return ret;
}
return 0;
}
static void panfrost_regulator_fini(struct panfrost_device *pfdev)
{
if (!pfdev->regulators)
return;
regulator_bulk_disable(pfdev->comp->num_supplies, pfdev->regulators);
}
static void panfrost_pm_domain_fini(struct panfrost_device *pfdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(pfdev->pm_domain_devs); i++) {
if (!pfdev->pm_domain_devs[i])
break;
if (pfdev->pm_domain_links[i])
device_link_del(pfdev->pm_domain_links[i]);
dev_pm_domain_detach(pfdev->pm_domain_devs[i], true);
}
}
static int panfrost_pm_domain_init(struct panfrost_device *pfdev)
{
int err;
int i, num_domains;
num_domains = of_count_phandle_with_args(pfdev->base.dev->of_node,
"power-domains",
"#power-domain-cells");
/*
* Single domain is handled by the core, and, if only a single power
* the power domain is requested, the property is optional.
*/
if (num_domains < 2 && pfdev->comp->num_pm_domains < 2)
return 0;
if (num_domains != pfdev->comp->num_pm_domains) {
dev_err(pfdev->base.dev,
"Incorrect number of power domains: %d provided, %d needed\n",
num_domains, pfdev->comp->num_pm_domains);
return -EINVAL;
}
if (WARN(num_domains > ARRAY_SIZE(pfdev->pm_domain_devs),
"Too many supplies in compatible structure.\n"))
return -EINVAL;
for (i = 0; i < num_domains; i++) {
pfdev->pm_domain_devs[i] =
dev_pm_domain_attach_by_name(pfdev->base.dev,
pfdev->comp->pm_domain_names[i]);
if (IS_ERR_OR_NULL(pfdev->pm_domain_devs[i])) {
err = PTR_ERR(pfdev->pm_domain_devs[i]) ? : -ENODATA;
pfdev->pm_domain_devs[i] = NULL;
dev_err(pfdev->base.dev,
"failed to get pm-domain %s(%d): %d\n",
pfdev->comp->pm_domain_names[i], i, err);
goto err;
}
pfdev->pm_domain_links[i] =
device_link_add(pfdev->base.dev,
pfdev->pm_domain_devs[i], DL_FLAG_PM_RUNTIME |
DL_FLAG_STATELESS | DL_FLAG_RPM_ACTIVE);
if (!pfdev->pm_domain_links[i]) {
dev_err(pfdev->pm_domain_devs[i],
"adding device link failed!\n");
err = -ENODEV;
goto err;
}
}
return 0;
err:
panfrost_pm_domain_fini(pfdev);
return err;
}
int panfrost_device_init(struct panfrost_device *pfdev)
{
int err;
mutex_init(&pfdev->sched_lock);
INIT_LIST_HEAD(&pfdev->scheduled_jobs);
INIT_LIST_HEAD(&pfdev->as_lru_list);
spin_lock_init(&pfdev->as_lock);
spin_lock_init(&pfdev->cycle_counter.lock);
#ifdef CONFIG_DEBUG_FS
mutex_init(&pfdev->debugfs.gems_lock);
INIT_LIST_HEAD(&pfdev->debugfs.gems_list);
#endif
err = panfrost_pm_domain_init(pfdev);
if (err)
return err;
err = panfrost_reset_init(pfdev);
if (err) {
dev_err(pfdev->base.dev, "reset init failed %d\n", err);
goto out_pm_domain;
}
err = panfrost_clk_init(pfdev);
if (err) {
dev_err(pfdev->base.dev, "clk init failed %d\n", err);
goto out_reset;
}
err = panfrost_devfreq_init(pfdev);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(pfdev->base.dev, "devfreq init failed %d\n", err);
goto out_clk;
}
/* OPP will handle regulators */
if (!pfdev->pfdevfreq.opp_of_table_added) {
err = panfrost_regulator_init(pfdev);
if (err)
goto out_devfreq;
}
pfdev->iomem = devm_platform_ioremap_resource(to_platform_device(pfdev->base.dev), 0);
if (IS_ERR(pfdev->iomem)) {
err = PTR_ERR(pfdev->iomem);
goto out_regulator;
}
err = panfrost_gpu_init(pfdev);
if (err)
goto out_regulator;
err = panfrost_mmu_init(pfdev);
if (err)
goto out_gpu;
err = panfrost_jm_init(pfdev);
if (err)
goto out_mmu;
err = panfrost_perfcnt_init(pfdev);
if (err)
goto out_job;
panfrost_gem_init(pfdev);
return 0;
out_job:
panfrost_jm_fini(pfdev);
out_mmu:
panfrost_mmu_fini(pfdev);
out_gpu:
panfrost_gpu_fini(pfdev);
out_regulator:
panfrost_regulator_fini(pfdev);
out_devfreq:
panfrost_devfreq_fini(pfdev);
out_clk:
panfrost_clk_fini(pfdev);
out_reset:
panfrost_reset_fini(pfdev);
out_pm_domain:
panfrost_pm_domain_fini(pfdev);
return err;
}
void panfrost_device_fini(struct panfrost_device *pfdev)
{
panfrost_perfcnt_fini(pfdev);
panfrost_jm_fini(pfdev);
panfrost_mmu_fini(pfdev);
panfrost_gpu_fini(pfdev);
panfrost_devfreq_fini(pfdev);
panfrost_regulator_fini(pfdev);
panfrost_clk_fini(pfdev);
panfrost_reset_fini(pfdev);
panfrost_pm_domain_fini(pfdev);
}
#define PANFROST_EXCEPTION(id) \
[DRM_PANFROST_EXCEPTION_ ## id] = { \
.name = #id, \
}
struct panfrost_exception_info {
const char *name;
};
static const struct panfrost_exception_info panfrost_exception_infos[] = {
PANFROST_EXCEPTION(OK),
PANFROST_EXCEPTION(DONE),
PANFROST_EXCEPTION(INTERRUPTED),
PANFROST_EXCEPTION(STOPPED),
PANFROST_EXCEPTION(TERMINATED),
PANFROST_EXCEPTION(KABOOM),
PANFROST_EXCEPTION(EUREKA),
PANFROST_EXCEPTION(ACTIVE),
PANFROST_EXCEPTION(JOB_CONFIG_FAULT),
PANFROST_EXCEPTION(JOB_POWER_FAULT),
PANFROST_EXCEPTION(JOB_READ_FAULT),
PANFROST_EXCEPTION(JOB_WRITE_FAULT),
PANFROST_EXCEPTION(JOB_AFFINITY_FAULT),
PANFROST_EXCEPTION(JOB_BUS_FAULT),
PANFROST_EXCEPTION(INSTR_INVALID_PC),
PANFROST_EXCEPTION(INSTR_INVALID_ENC),
PANFROST_EXCEPTION(INSTR_TYPE_MISMATCH),
PANFROST_EXCEPTION(INSTR_OPERAND_FAULT),
PANFROST_EXCEPTION(INSTR_TLS_FAULT),
PANFROST_EXCEPTION(INSTR_BARRIER_FAULT),
PANFROST_EXCEPTION(INSTR_ALIGN_FAULT),
PANFROST_EXCEPTION(DATA_INVALID_FAULT),
PANFROST_EXCEPTION(TILE_RANGE_FAULT),
PANFROST_EXCEPTION(ADDR_RANGE_FAULT),
PANFROST_EXCEPTION(IMPRECISE_FAULT),
PANFROST_EXCEPTION(OOM),
PANFROST_EXCEPTION(OOM_AFBC),
PANFROST_EXCEPTION(UNKNOWN),
PANFROST_EXCEPTION(DELAYED_BUS_FAULT),
PANFROST_EXCEPTION(GPU_SHAREABILITY_FAULT),
PANFROST_EXCEPTION(SYS_SHAREABILITY_FAULT),
PANFROST_EXCEPTION(GPU_CACHEABILITY_FAULT),
PANFROST_EXCEPTION(TRANSLATION_FAULT_0),
PANFROST_EXCEPTION(TRANSLATION_FAULT_1),
PANFROST_EXCEPTION(TRANSLATION_FAULT_2),
PANFROST_EXCEPTION(TRANSLATION_FAULT_3),
PANFROST_EXCEPTION(TRANSLATION_FAULT_4),
PANFROST_EXCEPTION(TRANSLATION_FAULT_IDENTITY),
PANFROST_EXCEPTION(PERM_FAULT_0),
PANFROST_EXCEPTION(PERM_FAULT_1),
PANFROST_EXCEPTION(PERM_FAULT_2),
PANFROST_EXCEPTION(PERM_FAULT_3),
PANFROST_EXCEPTION(TRANSTAB_BUS_FAULT_0),
PANFROST_EXCEPTION(TRANSTAB_BUS_FAULT_1),
PANFROST_EXCEPTION(TRANSTAB_BUS_FAULT_2),
PANFROST_EXCEPTION(TRANSTAB_BUS_FAULT_3),
PANFROST_EXCEPTION(ACCESS_FLAG_0),
PANFROST_EXCEPTION(ACCESS_FLAG_1),
PANFROST_EXCEPTION(ACCESS_FLAG_2),
PANFROST_EXCEPTION(ACCESS_FLAG_3),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_IN0),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_IN1),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_IN2),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_IN3),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_OUT0),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_OUT1),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_OUT2),
PANFROST_EXCEPTION(ADDR_SIZE_FAULT_OUT3),
PANFROST_EXCEPTION(MEM_ATTR_FAULT_0),
PANFROST_EXCEPTION(MEM_ATTR_FAULT_1),
PANFROST_EXCEPTION(MEM_ATTR_FAULT_2),
PANFROST_EXCEPTION(MEM_ATTR_FAULT_3),
PANFROST_EXCEPTION(MEM_ATTR_NONCACHE_0),
PANFROST_EXCEPTION(MEM_ATTR_NONCACHE_1),
PANFROST_EXCEPTION(MEM_ATTR_NONCACHE_2),
PANFROST_EXCEPTION(MEM_ATTR_NONCACHE_3),
};
const char *panfrost_exception_name(u32 exception_code)
{
if (WARN_ON(exception_code >= ARRAY_SIZE(panfrost_exception_infos) ||
!panfrost_exception_infos[exception_code].name))
return "Unknown exception type";
return panfrost_exception_infos[exception_code].name;
}
bool panfrost_exception_needs_reset(const struct panfrost_device *pfdev,
u32 exception_code)
{
/* If an occlusion query write causes a bus fault on affected GPUs,
* future fragment jobs may hang. Reset to workaround.
*/
if (exception_code == DRM_PANFROST_EXCEPTION_JOB_BUS_FAULT)
return panfrost_has_hw_issue(pfdev, HW_ISSUE_TTRX_3076);
/* No other GPUs we support need a reset */
return false;
}
void panfrost_device_reset(struct panfrost_device *pfdev, bool enable_job_int)
{
panfrost_gpu_soft_reset(pfdev);
panfrost_gpu_power_on(pfdev);
panfrost_mmu_reset(pfdev);
panfrost_jm_reset_interrupts(pfdev);
if (enable_job_int)
panfrost_jm_enable_interrupts(pfdev);
}
static int panfrost_device_runtime_resume(struct device *dev)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
int ret;
if (pfdev->comp->pm_features & BIT(GPU_PM_RT)) {
ret = reset_control_deassert(pfdev->rstc);
if (ret)
return ret;
ret = clk_enable(pfdev->clock);
if (ret)
goto err_clk;
ret = clk_enable(pfdev->bus_clock);
if (ret)
goto err_bus_clk;
ret = clk_enable(pfdev->bus_ace_clock);
if (ret)
goto err_bus_ace_clk;
}
panfrost_device_reset(pfdev, true);
panfrost_devfreq_resume(pfdev);
return 0;
err_bus_ace_clk:
if (pfdev->comp->pm_features & BIT(GPU_PM_RT))
clk_disable(pfdev->bus_clock);
err_bus_clk:
if (pfdev->comp->pm_features & BIT(GPU_PM_RT))
clk_disable(pfdev->clock);
err_clk:
if (pfdev->comp->pm_features & BIT(GPU_PM_RT))
reset_control_assert(pfdev->rstc);
return ret;
}
static int panfrost_device_runtime_suspend(struct device *dev)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
if (!panfrost_jm_is_idle(pfdev))
return -EBUSY;
panfrost_devfreq_suspend(pfdev);
panfrost_jm_suspend_irq(pfdev);
panfrost_mmu_suspend_irq(pfdev);
panfrost_gpu_suspend_irq(pfdev);
panfrost_gpu_power_off(pfdev);
if (pfdev->comp->pm_features & BIT(GPU_PM_RT)) {
clk_disable(pfdev->bus_ace_clock);
clk_disable(pfdev->bus_clock);
clk_disable(pfdev->clock);
reset_control_assert(pfdev->rstc);
}
return 0;
}
static int panfrost_device_resume(struct device *dev)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
int ret;
if (pfdev->comp->pm_features & BIT(GPU_PM_VREG_OFF)) {
unsigned long freq = pfdev->pfdevfreq.fast_rate;
struct dev_pm_opp *opp;
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
if (IS_ERR(opp))
return PTR_ERR(opp);
dev_pm_opp_set_opp(dev, opp);
dev_pm_opp_put(opp);
}
if (pfdev->comp->pm_features & BIT(GPU_PM_CLK_DIS)) {
ret = clk_enable(pfdev->clock);
if (ret)
goto err_clk;
if (pfdev->bus_clock) {
ret = clk_enable(pfdev->bus_clock);
if (ret)
goto err_bus_clk;
}
}
ret = pm_runtime_force_resume(dev);
if (ret)
goto err_resume;
return 0;
err_resume:
if (pfdev->comp->pm_features & BIT(GPU_PM_CLK_DIS) && pfdev->bus_clock)
clk_disable(pfdev->bus_clock);
err_bus_clk:
if (pfdev->comp->pm_features & BIT(GPU_PM_CLK_DIS))
clk_disable(pfdev->clock);
err_clk:
if (pfdev->comp->pm_features & BIT(GPU_PM_VREG_OFF))
dev_pm_opp_set_opp(dev, NULL);
return ret;
}
static int panfrost_device_suspend(struct device *dev)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
int ret;
ret = pm_runtime_force_suspend(dev);
if (ret)
return ret;
if (pfdev->comp->pm_features & BIT(GPU_PM_CLK_DIS)) {
if (pfdev->bus_clock)
clk_disable(pfdev->bus_clock);
clk_disable(pfdev->clock);
}
if (pfdev->comp->pm_features & BIT(GPU_PM_VREG_OFF))
dev_pm_opp_set_opp(dev, NULL);
return 0;
}
EXPORT_GPL_DEV_PM_OPS(panfrost_pm_ops) = {
RUNTIME_PM_OPS(panfrost_device_runtime_suspend, panfrost_device_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(panfrost_device_suspend, panfrost_device_resume)
};
@@ -0,0 +1,345 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
#ifndef __PANFROST_DEVICE_H__
#define __PANFROST_DEVICE_H__
#include <linux/atomic.h>
#include <linux/io-pgtable.h>
#include <linux/pm.h>
#include <linux/regulator/consumer.h>
#include <linux/spinlock.h>
#include <drm/drm_auth.h>
#include <drm/drm_device.h>
#include <drm/drm_mm.h>
#include <drm/gpu_scheduler.h>
#include "panfrost_devfreq.h"
#include "panfrost_job.h"
struct panfrost_device;
struct panfrost_mmu;
struct panfrost_job_slot;
struct panfrost_job;
struct panfrost_perfcnt;
#define MAX_PM_DOMAINS 5
#define ALL_JS_INT_MASK \
(GENMASK(16 + NUM_JOB_SLOTS - 1, 16) | \
GENMASK(NUM_JOB_SLOTS - 1, 0))
enum panfrost_drv_comp_bits {
PANFROST_COMP_BIT_GPU,
PANFROST_COMP_BIT_JOB,
PANFROST_COMP_BIT_MMU,
PANFROST_COMP_BIT_MAX
};
/**
* enum panfrost_gpu_pm - Supported kernel power management features
* @GPU_PM_CLK_DIS: Allow disabling clocks during system suspend
* @GPU_PM_VREG_OFF: Allow turning off regulators during system suspend
* @GPU_PM_RT: Allow disabling clocks and asserting the reset control during
* system runtime suspend
*/
enum panfrost_gpu_pm {
GPU_PM_CLK_DIS,
GPU_PM_VREG_OFF,
GPU_PM_RT
};
/**
* enum panfrost_gpu_quirks - GPU optional quirks
* @GPU_QUIRK_FORCE_AARCH64_PGTABLE: Use AARCH64_4K page table format
*/
enum panfrost_gpu_quirks {
GPU_QUIRK_FORCE_AARCH64_PGTABLE,
};
struct panfrost_features {
u16 id;
u16 revision;
u64 shader_present;
u64 tiler_present;
u64 l2_present;
u64 stack_present;
u32 as_present;
u32 js_present;
u32 l2_features;
u32 core_features;
u32 tiler_features;
u32 mem_features;
u32 mmu_features;
u32 thread_features;
u32 max_threads;
u32 thread_max_workgroup_sz;
u32 thread_max_barrier_sz;
u32 coherency_features;
u32 selected_coherency;
u32 afbc_features;
u32 texture_features[4];
u32 js_features[16];
u32 nr_core_groups;
u32 thread_tls_alloc;
unsigned long hw_features[64 / BITS_PER_LONG];
unsigned long hw_issues[64 / BITS_PER_LONG];
};
/*
* Features that cannot be automatically detected and need matching using the
* compatible string, typically SoC-specific.
*/
struct panfrost_compatible {
/* Supplies count and names. */
int num_supplies;
const char * const *supply_names;
/*
* Number of power domains required, note that values 0 and 1 are
* handled identically, as only values > 1 need special handling.
*/
int num_pm_domains;
/* Only required if num_pm_domains > 1. */
const char * const *pm_domain_names;
/* Vendor implementation quirks callback */
void (*vendor_quirk)(struct panfrost_device *pfdev);
/* Allowed PM features */
u8 pm_features;
/* GPU configuration quirks */
u8 gpu_quirks;
};
/**
* struct panfrost_device_debugfs - Device-wide DebugFS tracking structures
*/
struct panfrost_device_debugfs {
/** @gems_list: Device-wide list of GEM objects owned by at least one file. */
struct list_head gems_list;
/** @gems_lock: Serializes access to the device-wide list of GEM objects. */
struct mutex gems_lock;
};
struct panfrost_device {
struct drm_device base;
int gpu_irq;
int mmu_irq;
void __iomem *iomem;
struct clk *clock;
struct clk *bus_clock;
struct clk *bus_ace_clock;
struct regulator_bulk_data *regulators;
struct reset_control *rstc;
/* pm_domains for devices with more than one. */
struct device *pm_domain_devs[MAX_PM_DOMAINS];
struct device_link *pm_domain_links[MAX_PM_DOMAINS];
bool coherent;
struct panfrost_features features;
const struct panfrost_compatible *comp;
DECLARE_BITMAP(is_suspended, PANFROST_COMP_BIT_MAX);
spinlock_t as_lock;
unsigned long as_alloc_mask;
unsigned long as_faulty_mask;
struct list_head as_lru_list;
struct panfrost_job_slot *js;
struct panfrost_job *jobs[NUM_JOB_SLOTS][2];
struct list_head scheduled_jobs;
struct panfrost_perfcnt *perfcnt;
bool profile_mode;
struct mutex sched_lock;
struct {
struct workqueue_struct *wq;
struct work_struct work;
atomic_t pending;
} reset;
struct mutex shrinker_lock;
struct list_head shrinker_list;
struct shrinker *shrinker;
struct panfrost_devfreq pfdevfreq;
struct {
atomic_t use_count;
spinlock_t lock;
} cycle_counter;
#ifdef CONFIG_DEBUG_FS
struct panfrost_device_debugfs debugfs;
#endif
};
struct panfrost_mmu {
struct panfrost_device *pfdev;
struct kref refcount;
struct io_pgtable_cfg pgtbl_cfg;
struct io_pgtable_ops *pgtbl_ops;
struct drm_mm mm;
spinlock_t mm_lock;
int as;
atomic_t as_count;
struct list_head list;
struct {
u64 transtab;
u64 memattr;
u64 transcfg;
} cfg;
};
struct panfrost_engine_usage {
unsigned long long elapsed_ns[NUM_JOB_SLOTS];
unsigned long long cycles[NUM_JOB_SLOTS];
};
struct panfrost_file_priv {
struct panfrost_device *pfdev;
struct xarray jm_ctxs;
struct panfrost_mmu *mmu;
struct panfrost_engine_usage engine_usage;
};
static inline bool panfrost_high_prio_allowed(struct drm_file *file)
{
/* Higher priorities require CAP_SYS_NICE or DRM_MASTER */
return (capable(CAP_SYS_NICE) || drm_is_current_master(file));
}
static inline struct panfrost_device *to_panfrost_device(struct drm_device *ddev)
{
return container_of(ddev, struct panfrost_device, base);
}
static inline int panfrost_model_cmp(struct panfrost_device *pfdev, s32 id)
{
s32 match_id = pfdev->features.id;
if (match_id & 0xf000)
match_id &= 0xf00f;
return match_id - id;
}
static inline bool panfrost_model_is_bifrost(struct panfrost_device *pfdev)
{
return panfrost_model_cmp(pfdev, 0x1000) >= 0;
}
static inline bool panfrost_model_eq(struct panfrost_device *pfdev, s32 id)
{
return !panfrost_model_cmp(pfdev, id);
}
int panfrost_unstable_ioctl_check(void);
int panfrost_device_init(struct panfrost_device *pfdev);
void panfrost_device_fini(struct panfrost_device *pfdev);
void panfrost_device_reset(struct panfrost_device *pfdev, bool enable_job_int);
extern const struct dev_pm_ops panfrost_pm_ops;
enum drm_panfrost_exception_type {
DRM_PANFROST_EXCEPTION_OK = 0x00,
DRM_PANFROST_EXCEPTION_DONE = 0x01,
DRM_PANFROST_EXCEPTION_INTERRUPTED = 0x02,
DRM_PANFROST_EXCEPTION_STOPPED = 0x03,
DRM_PANFROST_EXCEPTION_TERMINATED = 0x04,
DRM_PANFROST_EXCEPTION_KABOOM = 0x05,
DRM_PANFROST_EXCEPTION_EUREKA = 0x06,
DRM_PANFROST_EXCEPTION_ACTIVE = 0x08,
DRM_PANFROST_EXCEPTION_MAX_NON_FAULT = 0x3f,
DRM_PANFROST_EXCEPTION_JOB_CONFIG_FAULT = 0x40,
DRM_PANFROST_EXCEPTION_JOB_POWER_FAULT = 0x41,
DRM_PANFROST_EXCEPTION_JOB_READ_FAULT = 0x42,
DRM_PANFROST_EXCEPTION_JOB_WRITE_FAULT = 0x43,
DRM_PANFROST_EXCEPTION_JOB_AFFINITY_FAULT = 0x44,
DRM_PANFROST_EXCEPTION_JOB_BUS_FAULT = 0x48,
DRM_PANFROST_EXCEPTION_INSTR_INVALID_PC = 0x50,
DRM_PANFROST_EXCEPTION_INSTR_INVALID_ENC = 0x51,
DRM_PANFROST_EXCEPTION_INSTR_TYPE_MISMATCH = 0x52,
DRM_PANFROST_EXCEPTION_INSTR_OPERAND_FAULT = 0x53,
DRM_PANFROST_EXCEPTION_INSTR_TLS_FAULT = 0x54,
DRM_PANFROST_EXCEPTION_INSTR_BARRIER_FAULT = 0x55,
DRM_PANFROST_EXCEPTION_INSTR_ALIGN_FAULT = 0x56,
DRM_PANFROST_EXCEPTION_DATA_INVALID_FAULT = 0x58,
DRM_PANFROST_EXCEPTION_TILE_RANGE_FAULT = 0x59,
DRM_PANFROST_EXCEPTION_ADDR_RANGE_FAULT = 0x5a,
DRM_PANFROST_EXCEPTION_IMPRECISE_FAULT = 0x5b,
DRM_PANFROST_EXCEPTION_OOM = 0x60,
DRM_PANFROST_EXCEPTION_OOM_AFBC = 0x61,
DRM_PANFROST_EXCEPTION_UNKNOWN = 0x7f,
DRM_PANFROST_EXCEPTION_DELAYED_BUS_FAULT = 0x80,
DRM_PANFROST_EXCEPTION_GPU_SHAREABILITY_FAULT = 0x88,
DRM_PANFROST_EXCEPTION_SYS_SHAREABILITY_FAULT = 0x89,
DRM_PANFROST_EXCEPTION_GPU_CACHEABILITY_FAULT = 0x8a,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_0 = 0xc0,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_1 = 0xc1,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_2 = 0xc2,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_3 = 0xc3,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_4 = 0xc4,
DRM_PANFROST_EXCEPTION_TRANSLATION_FAULT_IDENTITY = 0xc7,
DRM_PANFROST_EXCEPTION_PERM_FAULT_0 = 0xc8,
DRM_PANFROST_EXCEPTION_PERM_FAULT_1 = 0xc9,
DRM_PANFROST_EXCEPTION_PERM_FAULT_2 = 0xca,
DRM_PANFROST_EXCEPTION_PERM_FAULT_3 = 0xcb,
DRM_PANFROST_EXCEPTION_TRANSTAB_BUS_FAULT_0 = 0xd0,
DRM_PANFROST_EXCEPTION_TRANSTAB_BUS_FAULT_1 = 0xd1,
DRM_PANFROST_EXCEPTION_TRANSTAB_BUS_FAULT_2 = 0xd2,
DRM_PANFROST_EXCEPTION_TRANSTAB_BUS_FAULT_3 = 0xd3,
DRM_PANFROST_EXCEPTION_ACCESS_FLAG_0 = 0xd8,
DRM_PANFROST_EXCEPTION_ACCESS_FLAG_1 = 0xd9,
DRM_PANFROST_EXCEPTION_ACCESS_FLAG_2 = 0xda,
DRM_PANFROST_EXCEPTION_ACCESS_FLAG_3 = 0xdb,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_IN0 = 0xe0,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_IN1 = 0xe1,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_IN2 = 0xe2,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_IN3 = 0xe3,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_OUT0 = 0xe4,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_OUT1 = 0xe5,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_OUT2 = 0xe6,
DRM_PANFROST_EXCEPTION_ADDR_SIZE_FAULT_OUT3 = 0xe7,
DRM_PANFROST_EXCEPTION_MEM_ATTR_FAULT_0 = 0xe8,
DRM_PANFROST_EXCEPTION_MEM_ATTR_FAULT_1 = 0xe9,
DRM_PANFROST_EXCEPTION_MEM_ATTR_FAULT_2 = 0xea,
DRM_PANFROST_EXCEPTION_MEM_ATTR_FAULT_3 = 0xeb,
DRM_PANFROST_EXCEPTION_MEM_ATTR_NONCACHE_0 = 0xec,
DRM_PANFROST_EXCEPTION_MEM_ATTR_NONCACHE_1 = 0xed,
DRM_PANFROST_EXCEPTION_MEM_ATTR_NONCACHE_2 = 0xee,
DRM_PANFROST_EXCEPTION_MEM_ATTR_NONCACHE_3 = 0xef,
};
static inline bool
panfrost_exception_is_fault(u32 exception_code)
{
return exception_code > DRM_PANFROST_EXCEPTION_MAX_NON_FAULT;
}
const char *panfrost_exception_name(u32 exception_code);
bool panfrost_exception_needs_reset(const struct panfrost_device *pfdev,
u32 exception_code);
static inline void
panfrost_device_schedule_reset(struct panfrost_device *pfdev)
{
atomic_set(&pfdev->reset.pending, 1);
queue_work(pfdev->reset.wq, &pfdev->reset.work);
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 or MIT */
/* Copyright 2025 Amazon.com, Inc. or its affiliates */
#ifndef __PANFROST_DRV_H__
#define __PANFROST_DRV_H__
extern bool panfrost_transparent_hugepage;
#endif
@@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2021 Collabora ltd. */
#include <linux/err.h>
#include <linux/device.h>
#include <linux/devcoredump.h>
#include <linux/moduleparam.h>
#include <linux/iosys-map.h>
#include <drm/panfrost_drm.h>
#include <drm/drm_device.h>
#include "panfrost_job.h"
#include "panfrost_gem.h"
#include "panfrost_regs.h"
#include "panfrost_dump.h"
#include "panfrost_device.h"
static bool panfrost_dump_core = true;
module_param_named(dump_core, panfrost_dump_core, bool, 0600);
struct panfrost_dump_iterator {
void *start;
struct panfrost_dump_object_header *hdr;
void *data;
};
static const unsigned short panfrost_dump_registers[] = {
SHADER_READY_LO,
SHADER_READY_HI,
TILER_READY_LO,
TILER_READY_HI,
L2_READY_LO,
L2_READY_HI,
JOB_INT_MASK,
JOB_INT_STAT,
JS_HEAD_LO(0),
JS_HEAD_HI(0),
JS_TAIL_LO(0),
JS_TAIL_HI(0),
JS_AFFINITY_LO(0),
JS_AFFINITY_HI(0),
JS_CONFIG(0),
JS_STATUS(0),
JS_HEAD_NEXT_LO(0),
JS_HEAD_NEXT_HI(0),
JS_AFFINITY_NEXT_LO(0),
JS_AFFINITY_NEXT_HI(0),
JS_CONFIG_NEXT(0),
MMU_INT_MASK,
MMU_INT_STAT,
AS_TRANSTAB_LO(0),
AS_TRANSTAB_HI(0),
AS_MEMATTR_LO(0),
AS_MEMATTR_HI(0),
AS_FAULTSTATUS(0),
AS_FAULTADDRESS_LO(0),
AS_FAULTADDRESS_HI(0),
AS_STATUS(0),
};
static void panfrost_core_dump_header(struct panfrost_dump_iterator *iter,
u32 type, void *data_end)
{
struct panfrost_dump_object_header *hdr = iter->hdr;
hdr->magic = PANFROSTDUMP_MAGIC;
hdr->type = type;
hdr->file_offset = iter->data - iter->start;
hdr->file_size = data_end - iter->data;
iter->hdr++;
iter->data += hdr->file_size;
}
static void
panfrost_core_dump_registers(struct panfrost_dump_iterator *iter,
struct panfrost_device *pfdev,
u32 as_nr, int slot)
{
struct panfrost_dump_registers *dumpreg = iter->data;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(panfrost_dump_registers); i++, dumpreg++) {
unsigned int js_as_offset = 0;
unsigned int reg;
if (panfrost_dump_registers[i] >= JS_BASE &&
panfrost_dump_registers[i] <= JS_BASE + JS_SLOT_STRIDE)
js_as_offset = slot * JS_SLOT_STRIDE;
else if (panfrost_dump_registers[i] >= MMU_BASE &&
panfrost_dump_registers[i] <= MMU_BASE + MMU_AS_STRIDE)
js_as_offset = (as_nr << MMU_AS_SHIFT);
reg = panfrost_dump_registers[i] + js_as_offset;
dumpreg->reg = reg;
dumpreg->value = gpu_read(pfdev, reg);
}
panfrost_core_dump_header(iter, PANFROSTDUMP_BUF_REG, dumpreg);
}
void panfrost_core_dump(struct panfrost_job *job)
{
struct panfrost_device *pfdev = job->pfdev;
struct panfrost_dump_iterator iter;
struct drm_gem_object *dbo;
unsigned int n_obj, n_bomap_pages;
u64 *bomap, *bomap_start;
size_t file_size;
u32 as_nr;
int slot;
int ret, i;
as_nr = job->mmu->as;
slot = panfrost_job_get_slot(job);
/* Only catch the first event, or when manually re-armed */
if (!panfrost_dump_core)
return;
panfrost_dump_core = false;
/* At least, we dump registers and end marker */
n_obj = 2;
n_bomap_pages = 0;
file_size = ARRAY_SIZE(panfrost_dump_registers) *
sizeof(struct panfrost_dump_registers);
/* Add in the active buffer objects */
for (i = 0; i < job->bo_count; i++) {
/*
* Even though the CPU could be configured to use 16K or 64K pages, this
* is a very unusual situation for most kernel setups on SoCs that have
* a Panfrost device. Also many places across the driver make the somewhat
* arbitrary assumption that Panfrost's MMU page size is the same as the CPU's,
* so let's have a sanity check to ensure that's always the case
*/
dbo = job->bos[i];
WARN_ON(!IS_ALIGNED(dbo->size, PAGE_SIZE));
file_size += dbo->size;
n_bomap_pages += dbo->size >> PAGE_SHIFT;
n_obj++;
}
/* If we have any buffer objects, add a bomap object */
if (n_bomap_pages) {
file_size += n_bomap_pages * sizeof(*bomap);
n_obj++;
}
/* Add the size of the headers */
file_size += sizeof(*iter.hdr) * n_obj;
/*
* Allocate the file in vmalloc memory, it's likely to be big.
* The reason behind these GFP flags is that we don't want to trigger the
* OOM killer in the event that not enough memory could be found for our
* dump file. We also don't want the allocator to do any error reporting,
* as the right behaviour is failing gracefully if a big enough buffer
* could not be allocated.
*/
iter.start = __vmalloc(file_size, GFP_KERNEL | __GFP_NOWARN |
__GFP_NORETRY);
if (!iter.start) {
dev_warn(pfdev->base.dev, "failed to allocate devcoredump file\n");
return;
}
/* Point the data member after the headers */
iter.hdr = iter.start;
iter.data = &iter.hdr[n_obj];
memset(iter.hdr, 0, iter.data - iter.start);
/*
* For now, we write the job identifier in the register dump header,
* so that we can decode the entire dump later with pandecode
*/
iter.hdr->reghdr.jc = job->jc;
iter.hdr->reghdr.major = PANFROSTDUMP_MAJOR;
iter.hdr->reghdr.minor = PANFROSTDUMP_MINOR;
iter.hdr->reghdr.gpu_id = pfdev->features.id;
iter.hdr->reghdr.nbos = job->bo_count;
panfrost_core_dump_registers(&iter, pfdev, as_nr, slot);
/* Reserve space for the bomap */
if (job->bo_count) {
bomap_start = bomap = iter.data;
memset(bomap, 0, sizeof(*bomap) * n_bomap_pages);
panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_BOMAP,
bomap + n_bomap_pages);
}
for (i = 0; i < job->bo_count; i++) {
struct iosys_map map;
struct panfrost_gem_mapping *mapping;
struct panfrost_gem_object *bo;
struct sg_page_iter page_iter;
void *vaddr;
bo = to_panfrost_bo(job->bos[i]);
mapping = job->mappings[i];
if (!bo->base.sgt) {
dev_err(pfdev->base.dev, "Panfrost Dump: BO has no sgt, cannot dump\n");
iter.hdr->bomap.valid = 0;
goto dump_header;
}
ret = drm_gem_vmap(&bo->base.base, &map);
if (ret) {
dev_err(pfdev->base.dev, "Panfrost Dump: couldn't map Buffer Object\n");
iter.hdr->bomap.valid = 0;
goto dump_header;
}
WARN_ON(!mapping->active);
iter.hdr->bomap.data[0] = bomap - bomap_start;
for_each_sgtable_page(bo->base.sgt, &page_iter, 0)
*bomap++ = page_to_phys(sg_page_iter_page(&page_iter));
iter.hdr->bomap.iova = mapping->mmnode.start << PAGE_SHIFT;
vaddr = map.vaddr;
memcpy(iter.data, vaddr, bo->base.base.size);
drm_gem_vunmap(&bo->base.base, &map);
iter.hdr->bomap.valid = 1;
dump_header: panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_BO, iter.data +
bo->base.base.size);
}
panfrost_core_dump_header(&iter, PANFROSTDUMP_BUF_TRAILER, iter.data);
dev_coredumpv(pfdev->base.dev, iter.start, iter.data - iter.start, GFP_KERNEL);
}
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2021 Collabora ltd.
*/
#ifndef PANFROST_DUMP_H
#define PANFROST_DUMP_H
struct panfrost_job;
void panfrost_core_dump(struct panfrost_job *job);
#endif
@@ -0,0 +1,130 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* (C) COPYRIGHT 2014-2018 ARM Limited. All rights reserved. */
/* Copyright 2019 Linaro, Ltd., Rob Herring <robh@kernel.org> */
#ifndef __PANFROST_FEATURES_H__
#define __PANFROST_FEATURES_H__
#include <linux/bitops.h>
#include "panfrost_device.h"
enum panfrost_hw_feature {
HW_FEATURE_JOBCHAIN_DISAMBIGUATION,
HW_FEATURE_PWRON_DURING_PWROFF_TRANS,
HW_FEATURE_XAFFINITY,
HW_FEATURE_V4,
HW_FEATURE_FLUSH_REDUCTION,
HW_FEATURE_PROTECTED_MODE,
HW_FEATURE_COHERENCY_REG,
HW_FEATURE_PROTECTED_DEBUG_MODE,
HW_FEATURE_AARCH64_MMU,
HW_FEATURE_TLS_HASHING,
HW_FEATURE_THREAD_GROUP_SPLIT,
HW_FEATURE_IDVS_GROUP_SIZE,
HW_FEATURE_CLEAN_ONLY_SAFE,
HW_FEATURE_3BIT_EXT_RW_L2_MMU_CONFIG,
};
#define hw_features_t600 (\
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_V4))
#define hw_features_t620 hw_features_t600
#define hw_features_t720 hw_features_t600
#define hw_features_t760 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT))
#define hw_features_t860 hw_features_t760
#define hw_features_t880 hw_features_t760
#define hw_features_t830 hw_features_t760
#define hw_features_t820 hw_features_t760
#define hw_features_g71 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG))
#define hw_features_g72 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG))
#define hw_features_g51 hw_features_g72
#define hw_features_g52 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \
BIT_ULL(HW_FEATURE_IDVS_GROUP_SIZE) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG))
#define hw_features_g76 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_TLS_HASHING) | \
BIT_ULL(HW_FEATURE_IDVS_GROUP_SIZE) | \
BIT_ULL(HW_FEATURE_3BIT_EXT_RW_L2_MMU_CONFIG))
#define hw_features_g31 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_THREAD_GROUP_SPLIT) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_TLS_HASHING) | \
BIT_ULL(HW_FEATURE_3BIT_EXT_RW_L2_MMU_CONFIG))
#define hw_features_g57 (\
BIT_ULL(HW_FEATURE_JOBCHAIN_DISAMBIGUATION) | \
BIT_ULL(HW_FEATURE_PWRON_DURING_PWROFF_TRANS) | \
BIT_ULL(HW_FEATURE_XAFFINITY) | \
BIT_ULL(HW_FEATURE_FLUSH_REDUCTION) | \
BIT_ULL(HW_FEATURE_PROTECTED_MODE) | \
BIT_ULL(HW_FEATURE_PROTECTED_DEBUG_MODE) | \
BIT_ULL(HW_FEATURE_COHERENCY_REG) | \
BIT_ULL(HW_FEATURE_AARCH64_MMU) | \
BIT_ULL(HW_FEATURE_IDVS_GROUP_SIZE) | \
BIT_ULL(HW_FEATURE_CLEAN_ONLY_SAFE))
static inline bool panfrost_has_hw_feature(struct panfrost_device *pfdev,
enum panfrost_hw_feature feat)
{
return test_bit(feat, pfdev->features.hw_features);
}
#endif
@@ -0,0 +1,748 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/* Copyright 2025 Amazon.com, Inc. or its affiliates */
#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <drm/panfrost_drm.h>
#include <drm/drm_print.h>
#include "panfrost_device.h"
#include "panfrost_drv.h"
#include "panfrost_gem.h"
#include "panfrost_mmu.h"
void panfrost_gem_init(struct panfrost_device *pfdev)
{
int err;
if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
!panfrost_transparent_hugepage)
return;
err = drm_gem_huge_mnt_create(&pfdev->base, "within_size");
if (drm_gem_get_huge_mnt(&pfdev->base))
drm_info(&pfdev->base, "Using Transparent Hugepage\n");
else if (err)
drm_warn(&pfdev->base, "Can't use Transparent Hugepage (%d)\n",
err);
}
#ifdef CONFIG_DEBUG_FS
static void panfrost_gem_debugfs_bo_add(struct panfrost_device *pfdev,
struct panfrost_gem_object *bo)
{
bo->debugfs.creator.tgid = current->tgid;
get_task_comm(bo->debugfs.creator.process_name, current->group_leader);
mutex_lock(&pfdev->debugfs.gems_lock);
list_add_tail(&bo->debugfs.node, &pfdev->debugfs.gems_list);
mutex_unlock(&pfdev->debugfs.gems_lock);
}
static void panfrost_gem_debugfs_bo_rm(struct panfrost_gem_object *bo)
{
struct panfrost_device *pfdev = to_panfrost_device(bo->base.base.dev);
if (list_empty(&bo->debugfs.node))
return;
mutex_lock(&pfdev->debugfs.gems_lock);
list_del_init(&bo->debugfs.node);
mutex_unlock(&pfdev->debugfs.gems_lock);
}
#else
static void panfrost_gem_debugfs_bo_add(struct panfrost_device *pfdev,
struct panfrost_gem_object *bo)
{}
static void panfrost_gem_debugfs_bo_rm(struct panfrost_gem_object *bo) {}
#endif
/* Called DRM core on the last userspace/kernel unreference of the
* BO.
*/
static void panfrost_gem_free_object(struct drm_gem_object *obj)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
struct panfrost_device *pfdev = to_panfrost_device(obj->dev);
/*
* Make sure the BO is no longer inserted in the shrinker list before
* taking care of the destruction itself. If we don't do that we have a
* race condition between this function and what's done in
* panfrost_gem_shrinker_scan().
*/
mutex_lock(&pfdev->shrinker_lock);
list_del_init(&bo->base.madv_list);
mutex_unlock(&pfdev->shrinker_lock);
/*
* If we still have mappings attached to the BO, there's a problem in
* our refcounting.
*/
WARN_ON_ONCE(!list_empty(&bo->mappings.list));
kfree_const(bo->label.str);
panfrost_gem_debugfs_bo_rm(bo);
mutex_destroy(&bo->label.lock);
if (bo->sgts) {
int i;
int n_sgt = bo->base.base.size / SZ_2M;
for (i = 0; i < n_sgt; i++) {
if (bo->sgts[i].sgl) {
dma_unmap_sgtable(pfdev->base.dev, &bo->sgts[i],
DMA_BIDIRECTIONAL, 0);
sg_free_table(&bo->sgts[i]);
}
}
kvfree(bo->sgts);
}
drm_gem_shmem_free(&bo->base);
}
struct panfrost_gem_mapping *
panfrost_gem_mapping_get(struct panfrost_gem_object *bo,
struct panfrost_file_priv *priv)
{
struct panfrost_gem_mapping *iter, *mapping = NULL;
mutex_lock(&bo->mappings.lock);
list_for_each_entry(iter, &bo->mappings.list, node) {
if (iter->mmu == priv->mmu) {
kref_get(&iter->refcount);
mapping = iter;
break;
}
}
mutex_unlock(&bo->mappings.lock);
return mapping;
}
static void
panfrost_gem_teardown_mapping(struct panfrost_gem_mapping *mapping)
{
if (mapping->active)
panfrost_mmu_unmap(mapping);
spin_lock(&mapping->mmu->mm_lock);
if (drm_mm_node_allocated(&mapping->mmnode))
drm_mm_remove_node(&mapping->mmnode);
spin_unlock(&mapping->mmu->mm_lock);
}
static void panfrost_gem_mapping_release(struct kref *kref)
{
struct panfrost_gem_mapping *mapping;
mapping = container_of(kref, struct panfrost_gem_mapping, refcount);
panfrost_gem_teardown_mapping(mapping);
drm_gem_object_put(&mapping->obj->base.base);
panfrost_mmu_ctx_put(mapping->mmu);
kfree(mapping);
}
void panfrost_gem_mapping_put(struct panfrost_gem_mapping *mapping)
{
if (!mapping)
return;
kref_put(&mapping->refcount, panfrost_gem_mapping_release);
}
void panfrost_gem_teardown_mappings_locked(struct panfrost_gem_object *bo)
{
struct panfrost_gem_mapping *mapping;
list_for_each_entry(mapping, &bo->mappings.list, node)
panfrost_gem_teardown_mapping(mapping);
}
int panfrost_gem_open(struct drm_gem_object *obj, struct drm_file *file_priv)
{
int ret;
size_t size = obj->size;
u64 align;
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
unsigned long color = bo->noexec ? PANFROST_BO_NOEXEC : 0;
struct panfrost_file_priv *priv = file_priv->driver_priv;
struct panfrost_gem_mapping *mapping;
mapping = kzalloc_obj(*mapping);
if (!mapping)
return -ENOMEM;
INIT_LIST_HEAD(&mapping->node);
kref_init(&mapping->refcount);
drm_gem_object_get(obj);
mapping->obj = bo;
/*
* Executable buffers cannot cross a 16MB boundary as the program
* counter is 24-bits. We assume executable buffers will be less than
* 16MB and aligning executable buffers to their size will avoid
* crossing a 16MB boundary.
*/
if (!bo->noexec)
align = size >> PAGE_SHIFT;
else
align = size >= SZ_2M ? SZ_2M >> PAGE_SHIFT : 0;
mapping->mmu = panfrost_mmu_ctx_get(priv->mmu);
spin_lock(&mapping->mmu->mm_lock);
ret = drm_mm_insert_node_generic(&mapping->mmu->mm, &mapping->mmnode,
size >> PAGE_SHIFT, align, color, 0);
spin_unlock(&mapping->mmu->mm_lock);
if (ret)
goto err;
if (!bo->is_heap) {
ret = panfrost_mmu_map(mapping);
if (ret)
goto err;
}
mutex_lock(&bo->mappings.lock);
WARN_ON(bo->base.madv != PANFROST_MADV_WILLNEED);
list_add_tail(&mapping->node, &bo->mappings.list);
mutex_unlock(&bo->mappings.lock);
err:
if (ret)
panfrost_gem_mapping_put(mapping);
return ret;
}
void panfrost_gem_close(struct drm_gem_object *obj, struct drm_file *file_priv)
{
struct panfrost_file_priv *priv = file_priv->driver_priv;
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
struct panfrost_gem_mapping *mapping = NULL, *iter;
mutex_lock(&bo->mappings.lock);
list_for_each_entry(iter, &bo->mappings.list, node) {
if (iter->mmu == priv->mmu) {
mapping = iter;
list_del(&iter->node);
break;
}
}
mutex_unlock(&bo->mappings.lock);
panfrost_gem_mapping_put(mapping);
}
static int panfrost_gem_pin(struct drm_gem_object *obj)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
if (bo->is_heap)
return -EINVAL;
return drm_gem_shmem_pin_locked(&bo->base);
}
static enum drm_gem_object_status panfrost_gem_status(struct drm_gem_object *obj)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
enum drm_gem_object_status res = 0;
if (drm_gem_is_imported(&bo->base.base) || bo->base.pages)
res |= DRM_GEM_OBJECT_RESIDENT;
if (bo->base.madv == PANFROST_MADV_DONTNEED)
res |= DRM_GEM_OBJECT_PURGEABLE;
return res;
}
static size_t panfrost_gem_rss(struct drm_gem_object *obj)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
if (bo->is_heap) {
return bo->heap_rss_size;
} else if (bo->base.pages) {
WARN_ON(bo->heap_rss_size);
return bo->base.base.size;
}
return 0;
}
static struct sg_table *
panfrost_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
enum dma_data_direction dir)
{
struct sg_table *sgt = drm_gem_map_dma_buf(attach, dir);
if (!IS_ERR(sgt))
attach->priv = sgt;
return sgt;
}
static void
panfrost_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach,
struct sg_table *sgt,
enum dma_data_direction dir)
{
attach->priv = NULL;
drm_gem_unmap_dma_buf(attach, sgt, dir);
}
static int
panfrost_gem_prime_begin_cpu_access(struct dma_buf *dma_buf,
enum dma_data_direction dir)
{
struct drm_gem_object *obj = dma_buf->priv;
struct drm_device *dev = obj->dev;
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
struct dma_buf_attachment *attach;
dma_resv_lock(obj->resv, NULL);
if (shmem->sgt)
dma_sync_sgtable_for_cpu(dev->dev, shmem->sgt, dir);
if (shmem->vaddr)
invalidate_kernel_vmap_range(shmem->vaddr, shmem->base.size);
list_for_each_entry(attach, &dma_buf->attachments, node) {
struct sg_table *sgt = attach->priv;
if (sgt)
dma_sync_sgtable_for_cpu(attach->dev, sgt, dir);
}
dma_resv_unlock(obj->resv);
return 0;
}
static int
panfrost_gem_prime_end_cpu_access(struct dma_buf *dma_buf,
enum dma_data_direction dir)
{
struct drm_gem_object *obj = dma_buf->priv;
struct drm_device *dev = obj->dev;
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
struct dma_buf_attachment *attach;
dma_resv_lock(obj->resv, NULL);
list_for_each_entry(attach, &dma_buf->attachments, node) {
struct sg_table *sgt = attach->priv;
if (sgt)
dma_sync_sgtable_for_device(attach->dev, sgt, dir);
}
if (shmem->vaddr)
flush_kernel_vmap_range(shmem->vaddr, shmem->base.size);
if (shmem->sgt)
dma_sync_sgtable_for_device(dev->dev, shmem->sgt, dir);
dma_resv_unlock(obj->resv);
return 0;
}
static const struct dma_buf_ops panfrost_dma_buf_ops = {
.attach = drm_gem_map_attach,
.detach = drm_gem_map_detach,
.map_dma_buf = panfrost_gem_prime_map_dma_buf,
.unmap_dma_buf = panfrost_gem_prime_unmap_dma_buf,
.release = drm_gem_dmabuf_release,
.mmap = drm_gem_dmabuf_mmap,
.vmap = drm_gem_dmabuf_vmap,
.vunmap = drm_gem_dmabuf_vunmap,
.begin_cpu_access = panfrost_gem_prime_begin_cpu_access,
.end_cpu_access = panfrost_gem_prime_end_cpu_access,
};
static struct dma_buf *
panfrost_gem_prime_export(struct drm_gem_object *obj, int flags)
{
struct drm_device *dev = obj->dev;
struct dma_buf_export_info exp_info = {
.exp_name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.ops = &panfrost_dma_buf_ops,
.size = obj->size,
.flags = flags,
.priv = obj,
.resv = obj->resv,
};
return drm_gem_dmabuf_export(dev, &exp_info);
}
struct drm_gem_object *
panfrost_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf)
{
struct drm_gem_object *obj = dma_buf->priv;
if (dma_buf->ops == &panfrost_dma_buf_ops && obj->dev == dev) {
/* Importing dmabuf exported from our own gem increases
* refcount on gem itself instead of f_count of dmabuf.
*/
drm_gem_object_get(obj);
return obj;
}
return drm_gem_prime_import(dev, dma_buf);
}
static const struct drm_gem_object_funcs panfrost_gem_funcs = {
.free = panfrost_gem_free_object,
.open = panfrost_gem_open,
.close = panfrost_gem_close,
.print_info = drm_gem_shmem_object_print_info,
.pin = panfrost_gem_pin,
.unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_object_get_sg_table,
.export = panfrost_gem_prime_export,
.vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_object_vunmap,
.mmap = drm_gem_shmem_object_mmap,
.status = panfrost_gem_status,
.rss = panfrost_gem_rss,
.vm_ops = &drm_gem_shmem_vm_ops,
};
/**
* panfrost_gem_create_object - Implementation of driver->gem_create_object.
* @dev: DRM device
* @size: Size in bytes of the memory the object will reference
*
* This lets the GEM helpers allocate object structs for us, and keep
* our BO stats correct.
*/
struct drm_gem_object *panfrost_gem_create_object(struct drm_device *dev, size_t size)
{
struct panfrost_device *pfdev = to_panfrost_device(dev);
struct panfrost_gem_object *obj;
obj = kzalloc_obj(*obj);
if (!obj)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&obj->mappings.list);
mutex_init(&obj->mappings.lock);
obj->base.base.funcs = &panfrost_gem_funcs;
obj->base.map_wc = !pfdev->coherent;
mutex_init(&obj->label.lock);
panfrost_gem_debugfs_bo_add(pfdev, obj);
return &obj->base.base;
}
static bool
should_map_wc(struct panfrost_gem_object *bo)
{
struct panfrost_device *pfdev = to_panfrost_device(bo->base.base.dev);
/* We can't do uncached mappings if the device is coherent,
* because the zeroing done by the shmem layer at page allocation
* time happens on a cached mapping which isn't CPU-flushed (at least
* not on Arm64 where the flush is deferred to PTE setup time, and
* only done conditionally based on the mapping permissions). We can't
* rely on dma_map_sgtable()/dma_sync_sgtable_for_xxx() either to flush
* those, because they are NOPed if dma_dev_coherent() returns true.
*/
if (pfdev->coherent)
return false;
/* Cached mappings are explicitly requested, so no write-combine. */
if (bo->wb_mmap)
return false;
/* The default is write-combine. */
return true;
}
struct panfrost_gem_object *
panfrost_gem_create(struct drm_device *dev, size_t size, u32 flags)
{
struct drm_gem_shmem_object *shmem;
struct panfrost_gem_object *bo;
/* The heap buffer is not supposed to be CPU-visible, so don't allow
* WB_MMAP on those.
*/
if ((flags & PANFROST_BO_HEAP) && (flags & PANFROST_BO_WB_MMAP))
return ERR_PTR(-EINVAL);
/* Round up heap allocations to 2MB to keep fault handling simple */
if (flags & PANFROST_BO_HEAP)
size = roundup(size, SZ_2M);
shmem = drm_gem_shmem_create(dev, size);
if (IS_ERR(shmem))
return ERR_CAST(shmem);
bo = to_panfrost_bo(&shmem->base);
bo->noexec = !!(flags & PANFROST_BO_NOEXEC);
bo->is_heap = !!(flags & PANFROST_BO_HEAP);
bo->wb_mmap = !!(flags & PANFROST_BO_WB_MMAP);
bo->base.map_wc = should_map_wc(bo);
return bo;
}
struct drm_gem_object *
panfrost_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt)
{
struct drm_gem_object *obj;
struct panfrost_gem_object *bo;
obj = drm_gem_shmem_prime_import_sg_table(dev, attach, sgt);
if (IS_ERR(obj))
return ERR_CAST(obj);
bo = to_panfrost_bo(obj);
bo->noexec = true;
/*
* We assign this generic label because this function cannot
* be reached through any of the Panfrost UM driver-specific
* code paths, unless one is given by explicitly calling the
* SET_LABEL_BO ioctl. It is therefore preferable to have a
* blanket BO tag that tells us the object was imported from
* another driver than nothing at all.
*/
panfrost_gem_internal_set_label(obj, "GEM PRIME buffer");
return obj;
}
void
panfrost_gem_set_label(struct drm_gem_object *obj, const char *label)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
const char *old_label;
scoped_guard(mutex, &bo->label.lock) {
old_label = bo->label.str;
bo->label.str = label;
}
kfree_const(old_label);
}
int
panfrost_gem_sync(struct drm_gem_object *obj, u32 type, u32 offset, u32 size)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
struct drm_gem_shmem_object *shmem = &bo->base;
const struct drm_device *dev = shmem->base.dev;
struct sg_table *sgt;
struct scatterlist *sgl;
unsigned int count;
/* Make sure the range is in bounds. */
if (offset + size < offset || offset + size > shmem->base.size)
return -EINVAL;
/* Disallow CPU-cache maintenance on imported buffers. */
if (drm_gem_is_imported(&shmem->base))
return -EINVAL;
switch (type) {
case PANFROST_BO_SYNC_CPU_CACHE_FLUSH:
case PANFROST_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE:
break;
default:
return -EINVAL;
}
/* Don't bother if it's WC-mapped */
if (shmem->map_wc)
return 0;
/* Nothing to do if the size is zero. */
if (size == 0)
return 0;
sgt = drm_gem_shmem_get_pages_sgt(shmem);
if (IS_ERR(sgt))
return PTR_ERR(sgt);
for_each_sgtable_dma_sg(sgt, sgl, count) {
if (size == 0)
break;
dma_addr_t paddr = sg_dma_address(sgl);
size_t len = sg_dma_len(sgl);
if (len <= offset) {
offset -= len;
continue;
}
paddr += offset;
len -= offset;
len = min_t(size_t, len, size);
size -= len;
offset = 0;
/* It's unclear whether dma_sync_xxx() is the right API to do CPU
* cache maintenance given an IOMMU can register their own
* implementation doing more than just CPU cache flushes/invalidation,
* and what we really care about here is CPU caches only, but that's
* the best we have that is both arch-agnostic and does at least the
* CPU cache maintenance on a <page,offset,size> tuple.
*
* Also, I wish we could do a single
*
* dma_sync_single_for_device(BIDIR)
*
* and get a flush+invalidate, but that's not how it's implemented
* in practice (at least on arm64), so we have to make it
*
* dma_sync_single_for_device(TO_DEVICE)
* dma_sync_single_for_cpu(FROM_DEVICE)
*
* for the flush+invalidate case.
*/
dma_sync_single_for_device(dev->dev, paddr, len, DMA_TO_DEVICE);
if (type == PANFROST_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE)
dma_sync_single_for_cpu(dev->dev, paddr, len, DMA_FROM_DEVICE);
}
return 0;
}
void
panfrost_gem_internal_set_label(struct drm_gem_object *obj, const char *label)
{
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
const char *str;
/* We should never attempt labelling a UM-exposed GEM object */
if (drm_WARN_ON(bo->base.base.dev, bo->base.base.handle_count > 0))
return;
if (!label)
return;
str = kstrdup_const(label, GFP_KERNEL);
if (!str) {
/* Failing to allocate memory for a label isn't a fatal condition */
drm_warn(bo->base.base.dev, "Not enough memory to allocate BO label");
return;
}
panfrost_gem_set_label(obj, str);
}
#ifdef CONFIG_DEBUG_FS
struct gem_size_totals {
size_t size;
size_t resident;
size_t reclaimable;
};
struct flag_def {
u32 flag;
const char *name;
};
static void panfrost_gem_debugfs_print_flag_names(struct seq_file *m)
{
int len;
int i;
static const struct flag_def gem_state_flags_names[] = {
{PANFROST_DEBUGFS_GEM_STATE_FLAG_IMPORTED, "imported"},
{PANFROST_DEBUGFS_GEM_STATE_FLAG_EXPORTED, "exported"},
{PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGED, "purged"},
{PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGEABLE, "purgeable"},
};
seq_puts(m, "GEM state flags: ");
for (i = 0, len = ARRAY_SIZE(gem_state_flags_names); i < len; i++) {
seq_printf(m, "%s (0x%x)%s", gem_state_flags_names[i].name,
gem_state_flags_names[i].flag, (i < len - 1) ? ", " : "\n\n");
}
}
static void panfrost_gem_debugfs_bo_print(struct panfrost_gem_object *bo,
struct seq_file *m,
struct gem_size_totals *totals)
{
unsigned int refcount = kref_read(&bo->base.base.refcount);
char creator_info[32] = {};
size_t resident_size;
u32 gem_state_flags = 0;
/* Skip BOs being destroyed. */
if (!refcount)
return;
resident_size = panfrost_gem_rss(&bo->base.base);
snprintf(creator_info, sizeof(creator_info),
"%s/%d", bo->debugfs.creator.process_name, bo->debugfs.creator.tgid);
seq_printf(m, "%-32s%-16d%-16d%-16zd%-16zd0x%-16lx",
creator_info,
bo->base.base.name,
refcount,
bo->base.base.size,
resident_size,
drm_vma_node_start(&bo->base.base.vma_node));
if (drm_gem_is_imported(&bo->base.base))
gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_IMPORTED;
if (bo->base.base.dma_buf)
gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_EXPORTED;
if (bo->base.madv < 0)
gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGED;
else if (bo->base.madv > 0)
gem_state_flags |= PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGEABLE;
seq_printf(m, "0x%-10x", gem_state_flags);
scoped_guard(mutex, &bo->label.lock) {
seq_printf(m, "%s\n", bo->label.str ? : "");
}
totals->size += bo->base.base.size;
totals->resident += resident_size;
if (bo->base.madv > 0)
totals->reclaimable += resident_size;
}
void panfrost_gem_debugfs_print_bos(struct panfrost_device *pfdev,
struct seq_file *m)
{
struct gem_size_totals totals = {0};
struct panfrost_gem_object *bo;
panfrost_gem_debugfs_print_flag_names(m);
seq_puts(m, "created-by global-name refcount size resident-size file-offset state label\n");
seq_puts(m, "-----------------------------------------------------------------------------------------------------------------------------------\n");
scoped_guard(mutex, &pfdev->debugfs.gems_lock) {
list_for_each_entry(bo, &pfdev->debugfs.gems_list, debugfs.node) {
panfrost_gem_debugfs_bo_print(bo, m, &totals);
}
}
seq_puts(m, "===================================================================================================================================\n");
seq_printf(m, "Total size: %zd, Total resident: %zd, Total reclaimable: %zd\n",
totals.size, totals.resident, totals.reclaimable);
}
#endif
@@ -0,0 +1,170 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
#ifndef __PANFROST_GEM_H__
#define __PANFROST_GEM_H__
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_mm.h>
struct panfrost_mmu;
struct panfrost_device;
#define PANFROST_BO_LABEL_MAXLEN 4096
enum panfrost_debugfs_gem_state_flags {
/** @PANFROST_DEBUGFS_GEM_STATE_FLAG_IMPORTED: GEM BO is PRIME imported. */
PANFROST_DEBUGFS_GEM_STATE_FLAG_IMPORTED = BIT(0),
/** @PANFROST_DEBUGFS_GEM_STATE_FLAG_EXPORTED: GEM BO is PRIME exported. */
PANFROST_DEBUGFS_GEM_STATE_FLAG_EXPORTED = BIT(1),
/** @PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGED: GEM BO was reclaimed by the shrinker. */
PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGED = BIT(2),
/**
* @PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGEABLE: GEM BO pages were marked as no longer
* needed by UM and can be reclaimed by the shrinker.
*/
PANFROST_DEBUGFS_GEM_STATE_FLAG_PURGEABLE = BIT(3),
};
/**
* struct panfrost_gem_debugfs - GEM object's DebugFS list information
*/
struct panfrost_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;
};
struct panfrost_gem_object {
struct drm_gem_shmem_object base;
struct sg_table *sgts;
/*
* Use a list for now. If searching a mapping ever becomes the
* bottleneck, we should consider using an RB-tree, or even better,
* let the core store drm_gem_object_mapping entries (where we
* could place driver specific data) instead of drm_gem_object ones
* in its drm_file->object_idr table.
*
* struct drm_gem_object_mapping {
* struct drm_gem_object *obj;
* void *driver_priv;
* };
*/
struct {
struct list_head list;
struct mutex lock;
} mappings;
/*
* Count the number of jobs referencing this BO so we don't let the
* shrinker reclaim this object prematurely.
*/
atomic_t gpu_usecount;
/*
* Object chunk size currently mapped onto physical memory
*/
size_t heap_rss_size;
/**
* @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;
bool noexec :1;
bool is_heap :1;
/* On coherent devices, this reflects the creation flags, not the true
* cacheability attribute of the mapping.
*/
bool wb_mmap :1;
#ifdef CONFIG_DEBUG_FS
struct panfrost_gem_debugfs debugfs;
#endif
};
struct panfrost_gem_mapping {
struct list_head node;
struct kref refcount;
struct panfrost_gem_object *obj;
struct drm_mm_node mmnode;
struct panfrost_mmu *mmu;
bool active :1;
};
static inline
struct panfrost_gem_object *to_panfrost_bo(struct drm_gem_object *obj)
{
return container_of(to_drm_gem_shmem_obj(obj), struct panfrost_gem_object, base);
}
static inline struct panfrost_gem_mapping *
drm_mm_node_to_panfrost_mapping(struct drm_mm_node *node)
{
return container_of(node, struct panfrost_gem_mapping, mmnode);
}
void panfrost_gem_init(struct panfrost_device *pfdev);
struct drm_gem_object *panfrost_gem_create_object(struct drm_device *dev, size_t size);
struct drm_gem_object *
panfrost_gem_prime_import_sg_table(struct drm_device *dev,
struct dma_buf_attachment *attach,
struct sg_table *sgt);
struct drm_gem_object *
panfrost_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf);
struct panfrost_gem_object *
panfrost_gem_create(struct drm_device *dev, size_t size, u32 flags);
int panfrost_gem_open(struct drm_gem_object *obj, struct drm_file *file_priv);
void panfrost_gem_close(struct drm_gem_object *obj,
struct drm_file *file_priv);
struct panfrost_gem_mapping *
panfrost_gem_mapping_get(struct panfrost_gem_object *bo,
struct panfrost_file_priv *priv);
void panfrost_gem_mapping_put(struct panfrost_gem_mapping *mapping);
void panfrost_gem_teardown_mappings_locked(struct panfrost_gem_object *bo);
int panfrost_gem_shrinker_init(struct drm_device *dev);
void panfrost_gem_shrinker_cleanup(struct drm_device *dev);
void panfrost_gem_set_label(struct drm_gem_object *obj, const char *label);
int panfrost_gem_sync(struct drm_gem_object *obj, u32 type,
u32 offset, u32 size);
void panfrost_gem_internal_set_label(struct drm_gem_object *obj, const char *label);
#ifdef CONFIG_DEBUG_FS
void panfrost_gem_debugfs_print_bos(struct panfrost_device *pfdev,
struct seq_file *m);
#endif
#endif /* __PANFROST_GEM_H__ */
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2019 Arm Ltd.
*
* Based on msm_gem_freedreno.c:
* Copyright (C) 2016 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/list.h>
#include <drm/drm_device.h>
#include <drm/drm_gem_shmem_helper.h>
#include "panfrost_device.h"
#include "panfrost_gem.h"
#include "panfrost_mmu.h"
static unsigned long
panfrost_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
{
struct panfrost_device *pfdev = shrinker->private_data;
struct drm_gem_shmem_object *shmem;
unsigned long count = 0;
if (!mutex_trylock(&pfdev->shrinker_lock))
return 0;
list_for_each_entry(shmem, &pfdev->shrinker_list, madv_list) {
if (drm_gem_shmem_is_purgeable(shmem))
count += shmem->base.size >> PAGE_SHIFT;
}
mutex_unlock(&pfdev->shrinker_lock);
return count;
}
static bool panfrost_gem_purge(struct drm_gem_object *obj)
{
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
struct panfrost_gem_object *bo = to_panfrost_bo(obj);
bool ret = false;
if (atomic_read(&bo->gpu_usecount))
return false;
if (!mutex_trylock(&bo->mappings.lock))
return false;
if (!dma_resv_trylock(shmem->base.resv))
goto unlock_mappings;
panfrost_gem_teardown_mappings_locked(bo);
drm_gem_shmem_purge_locked(&bo->base);
ret = true;
dma_resv_unlock(shmem->base.resv);
unlock_mappings:
mutex_unlock(&bo->mappings.lock);
return ret;
}
static unsigned long
panfrost_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
{
struct panfrost_device *pfdev = shrinker->private_data;
struct drm_gem_shmem_object *shmem, *tmp;
unsigned long freed = 0;
if (!mutex_trylock(&pfdev->shrinker_lock))
return SHRINK_STOP;
list_for_each_entry_safe(shmem, tmp, &pfdev->shrinker_list, madv_list) {
if (freed >= sc->nr_to_scan)
break;
if (drm_gem_shmem_is_purgeable(shmem) &&
panfrost_gem_purge(&shmem->base)) {
freed += shmem->base.size >> PAGE_SHIFT;
list_del_init(&shmem->madv_list);
}
}
mutex_unlock(&pfdev->shrinker_lock);
if (freed > 0)
pr_info_ratelimited("Purging %lu bytes\n", freed << PAGE_SHIFT);
return freed;
}
/**
* panfrost_gem_shrinker_init - Initialize panfrost shrinker
* @dev: DRM device
*
* This function registers and sets up the panfrost shrinker.
*/
int panfrost_gem_shrinker_init(struct drm_device *dev)
{
struct panfrost_device *pfdev = to_panfrost_device(dev);
pfdev->shrinker = shrinker_alloc(0, "drm-panfrost");
if (!pfdev->shrinker)
return -ENOMEM;
pfdev->shrinker->count_objects = panfrost_gem_shrinker_count;
pfdev->shrinker->scan_objects = panfrost_gem_shrinker_scan;
pfdev->shrinker->private_data = pfdev;
shrinker_register(pfdev->shrinker);
return 0;
}
/**
* panfrost_gem_shrinker_cleanup - Clean up panfrost shrinker
* @dev: DRM device
*
* This function unregisters the panfrost shrinker.
*/
void panfrost_gem_shrinker_cleanup(struct drm_device *dev)
{
struct panfrost_device *pfdev = to_panfrost_device(dev);
if (pfdev->shrinker)
shrinker_free(pfdev->shrinker);
}
@@ -0,0 +1,570 @@
// SPDX-License-Identifier: GPL-2.0
/* 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_print.h>
#include "panfrost_device.h"
#include "panfrost_features.h"
#include "panfrost_issues.h"
#include "panfrost_gpu.h"
#include "panfrost_perfcnt.h"
#include "panfrost_regs.h"
static irqreturn_t panfrost_gpu_irq_handler(int irq, void *data)
{
struct panfrost_device *pfdev = data;
u32 fault_status, state;
if (test_bit(PANFROST_COMP_BIT_GPU, pfdev->is_suspended))
return IRQ_NONE;
fault_status = gpu_read(pfdev, GPU_FAULT_STATUS);
state = gpu_read(pfdev, GPU_INT_STAT);
if (!state)
return IRQ_NONE;
if (state & GPU_IRQ_MASK_ERROR) {
u64 address = (u64) gpu_read(pfdev, GPU_FAULT_ADDRESS_HI) << 32;
address |= gpu_read(pfdev, GPU_FAULT_ADDRESS_LO);
dev_warn(pfdev->base.dev, "GPU Fault 0x%08x (%s) at 0x%016llx\n",
fault_status, panfrost_exception_name(fault_status & 0xFF),
address);
if (state & GPU_IRQ_MULTIPLE_FAULT)
dev_warn(pfdev->base.dev, "There were multiple GPU faults - some have not been reported\n");
gpu_write(pfdev, GPU_INT_MASK, 0);
}
if (state & GPU_IRQ_PERFCNT_SAMPLE_COMPLETED)
panfrost_perfcnt_sample_done(pfdev);
if (state & GPU_IRQ_CLEAN_CACHES_COMPLETED)
panfrost_perfcnt_clean_cache_done(pfdev);
gpu_write(pfdev, GPU_INT_CLEAR, state);
return IRQ_HANDLED;
}
int panfrost_gpu_soft_reset(struct panfrost_device *pfdev)
{
int ret;
u32 val;
gpu_write(pfdev, GPU_INT_MASK, 0);
gpu_write(pfdev, GPU_INT_CLEAR, GPU_IRQ_RESET_COMPLETED);
clear_bit(PANFROST_COMP_BIT_GPU, pfdev->is_suspended);
gpu_write(pfdev, GPU_CMD, GPU_CMD_SOFT_RESET);
ret = readl_relaxed_poll_timeout(pfdev->iomem + GPU_INT_RAWSTAT,
val, val & GPU_IRQ_RESET_COMPLETED, 10, 10000);
if (ret) {
dev_err(pfdev->base.dev, "gpu soft reset timed out, attempting hard reset\n");
gpu_write(pfdev, GPU_CMD, GPU_CMD_HARD_RESET);
ret = readl_relaxed_poll_timeout(pfdev->iomem + GPU_INT_RAWSTAT, val,
val & GPU_IRQ_RESET_COMPLETED, 100, 10000);
if (ret) {
dev_err(pfdev->base.dev, "gpu hard reset timed out\n");
return ret;
}
}
gpu_write(pfdev, GPU_INT_CLEAR, GPU_IRQ_MASK_ALL);
/* Only enable the interrupts we care about */
gpu_write(pfdev, GPU_INT_MASK,
GPU_IRQ_MASK_ERROR |
GPU_IRQ_PERFCNT_SAMPLE_COMPLETED |
GPU_IRQ_CLEAN_CACHES_COMPLETED);
/*
* All in-flight jobs should have released their cycle
* counter references upon reset, but let us make sure
*/
if (drm_WARN_ON(&pfdev->base, atomic_read(&pfdev->cycle_counter.use_count) != 0))
atomic_set(&pfdev->cycle_counter.use_count, 0);
return 0;
}
void panfrost_gpu_amlogic_quirk(struct panfrost_device *pfdev)
{
/*
* The Amlogic integrated Mali-T820, Mali-G31 & Mali-G52 needs
* these undocumented bits in GPU_PWR_OVERRIDE1 to be set in order
* to operate correctly.
*/
gpu_write(pfdev, GPU_PWR_KEY, GPU_PWR_KEY_UNLOCK);
gpu_write(pfdev, GPU_PWR_OVERRIDE1, 0xfff | (0x20 << 16));
}
static void panfrost_gpu_init_quirks(struct panfrost_device *pfdev)
{
u32 quirks = 0;
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_8443) ||
panfrost_has_hw_issue(pfdev, HW_ISSUE_11035))
quirks |= SC_LS_PAUSEBUFFER_DISABLE;
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_10327))
quirks |= SC_SDC_DISABLE_OQ_DISCARD;
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_10797))
quirks |= SC_ENABLE_TEXGRD_FLAGS;
if (!panfrost_has_hw_issue(pfdev, GPUCORE_1619)) {
if (panfrost_model_cmp(pfdev, 0x750) < 0) /* T60x, T62x, T72x */
quirks |= SC_LS_ATTR_CHECK_DISABLE;
else if (panfrost_model_cmp(pfdev, 0x880) <= 0) /* T76x, T8xx */
quirks |= SC_LS_ALLOW_ATTR_TYPES;
}
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_TTRX_2968_TTRX_3162))
quirks |= SC_VAR_ALGORITHM;
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_TLS_HASHING))
quirks |= SC_TLS_HASH_ENABLE;
if (quirks)
gpu_write(pfdev, GPU_SHADER_CONFIG, quirks);
quirks = gpu_read(pfdev, GPU_TILER_CONFIG);
/* Set tiler clock gate override if required */
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_T76X_3953))
quirks |= TC_CLOCK_GATE_OVERRIDE;
gpu_write(pfdev, GPU_TILER_CONFIG, quirks);
quirks = 0;
if ((panfrost_model_eq(pfdev, 0x860) || panfrost_model_eq(pfdev, 0x880)) &&
pfdev->features.revision >= 0x2000)
quirks |= JM_MAX_JOB_THROTTLE_LIMIT << JM_JOB_THROTTLE_LIMIT_SHIFT;
else if (panfrost_model_eq(pfdev, 0x6000) &&
pfdev->features.coherency_features == BIT(COHERENCY_ACE))
quirks |= (BIT(COHERENCY_ACE_LITE) | BIT(COHERENCY_ACE)) <<
JM_FORCE_COHERENCY_FEATURES_SHIFT;
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_IDVS_GROUP_SIZE))
quirks |= JM_DEFAULT_IDVS_GROUP_SIZE << JM_IDVS_GROUP_SIZE_SHIFT;
if (quirks)
gpu_write(pfdev, GPU_JM_CONFIG, quirks);
/* Here goes platform specific quirks */
if (pfdev->comp->vendor_quirk)
pfdev->comp->vendor_quirk(pfdev);
}
#define MAX_HW_REVS 6
struct panfrost_model {
const char *name;
u32 id;
u64 features;
u64 issues;
struct {
u32 revision;
u64 issues;
} revs[MAX_HW_REVS];
};
#define GPU_MODEL(_name, _id, ...) \
{\
.name = __stringify(_name), \
.id = _id, \
.features = hw_features_##_name, \
.issues = hw_issues_##_name, \
.revs = { __VA_ARGS__ }, \
}
#define GPU_REV_EXT(name, _rev, _p, _s, stat) \
{\
.revision = (_rev) << 12 | (_p) << 4 | (_s), \
.issues = hw_issues_##name##_r##_rev##p##_p##stat, \
}
#define GPU_REV(name, r, p) GPU_REV_EXT(name, r, p, 0, )
static const struct panfrost_model gpu_models[] = {
/* T60x has an oddball version */
GPU_MODEL(t600, 0x600,
GPU_REV_EXT(t600, 0, 0, 1, _15dev0)),
GPU_MODEL(t620, 0x620,
GPU_REV(t620, 0, 1), GPU_REV(t620, 1, 0)),
GPU_MODEL(t720, 0x720),
GPU_MODEL(t760, 0x750,
GPU_REV(t760, 0, 0), GPU_REV(t760, 0, 1),
GPU_REV_EXT(t760, 0, 1, 0, _50rel0),
GPU_REV(t760, 0, 2), GPU_REV(t760, 0, 3)),
GPU_MODEL(t820, 0x820),
GPU_MODEL(t830, 0x830),
GPU_MODEL(t860, 0x860),
GPU_MODEL(t880, 0x880),
GPU_MODEL(g71, 0x6000,
GPU_REV_EXT(g71, 0, 0, 1, _05dev0)),
GPU_MODEL(g72, 0x6001),
GPU_MODEL(g51, 0x7000),
GPU_MODEL(g76, 0x7001),
GPU_MODEL(g52, 0x7002),
GPU_MODEL(g31, 0x7003,
GPU_REV(g31, 1, 0)),
GPU_MODEL(g57, 0x9001,
GPU_REV(g57, 0, 0)),
/* MediaTek MT8192 has a Mali-G57 with a different GPU ID from the
* standard. Arm's driver does not appear to handle this model.
* ChromeOS has a hack downstream for it. Treat it as equivalent to
* standard Mali-G57 for now.
*/
GPU_MODEL(g57, 0x9003,
GPU_REV(g57, 0, 0)),
/* MediaTek MT8188 Mali-G57 MC3 */
GPU_MODEL(g57, 0x9093,
GPU_REV(g57, 0, 0)),
{0},
};
static int panfrost_gpu_init_features(struct panfrost_device *pfdev)
{
u32 gpu_id, num_js, major, minor, status, rev;
const char *name = "unknown";
u64 hw_feat = 0;
u64 hw_issues = hw_issues_all;
const struct panfrost_model *model;
int i;
pfdev->features.l2_features = gpu_read(pfdev, GPU_L2_FEATURES);
pfdev->features.core_features = gpu_read(pfdev, GPU_CORE_FEATURES);
pfdev->features.tiler_features = gpu_read(pfdev, GPU_TILER_FEATURES);
pfdev->features.mem_features = gpu_read(pfdev, GPU_MEM_FEATURES);
pfdev->features.mmu_features = gpu_read(pfdev, GPU_MMU_FEATURES);
pfdev->features.thread_features = gpu_read(pfdev, GPU_THREAD_FEATURES);
pfdev->features.max_threads = gpu_read(pfdev, GPU_THREAD_MAX_THREADS);
pfdev->features.thread_max_workgroup_sz = gpu_read(pfdev, GPU_THREAD_MAX_WORKGROUP_SIZE);
pfdev->features.thread_max_barrier_sz = gpu_read(pfdev, GPU_THREAD_MAX_BARRIER_SIZE);
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_COHERENCY_REG))
pfdev->features.coherency_features = gpu_read(pfdev, GPU_COHERENCY_FEATURES);
else
pfdev->features.coherency_features = BIT(COHERENCY_ACE_LITE);
BUILD_BUG_ON(COHERENCY_ACE_LITE != DRM_PANFROST_GPU_COHERENCY_ACE_LITE);
BUILD_BUG_ON(COHERENCY_ACE != DRM_PANFROST_GPU_COHERENCY_ACE);
BUILD_BUG_ON(COHERENCY_NONE != DRM_PANFROST_GPU_COHERENCY_NONE);
if (!pfdev->coherent) {
pfdev->features.selected_coherency = COHERENCY_NONE;
} else if (pfdev->features.coherency_features & BIT(COHERENCY_ACE)) {
pfdev->features.selected_coherency = COHERENCY_ACE;
} else if (pfdev->features.coherency_features & BIT(COHERENCY_ACE_LITE)) {
pfdev->features.selected_coherency = COHERENCY_ACE_LITE;
} else {
drm_WARN(&pfdev->base, true, "No known coherency protocol supported");
pfdev->features.selected_coherency = COHERENCY_NONE;
}
pfdev->features.afbc_features = gpu_read(pfdev, GPU_AFBC_FEATURES);
for (i = 0; i < 4; i++)
pfdev->features.texture_features[i] = gpu_read(pfdev, GPU_TEXTURE_FEATURES(i));
pfdev->features.as_present = gpu_read(pfdev, GPU_AS_PRESENT);
pfdev->features.js_present = gpu_read(pfdev, GPU_JS_PRESENT);
num_js = hweight32(pfdev->features.js_present);
for (i = 0; i < num_js; i++)
pfdev->features.js_features[i] = gpu_read(pfdev, GPU_JS_FEATURES(i));
pfdev->features.shader_present = gpu_read(pfdev, GPU_SHADER_PRESENT_LO);
pfdev->features.shader_present |= (u64)gpu_read(pfdev, GPU_SHADER_PRESENT_HI) << 32;
pfdev->features.tiler_present = gpu_read(pfdev, GPU_TILER_PRESENT_LO);
pfdev->features.tiler_present |= (u64)gpu_read(pfdev, GPU_TILER_PRESENT_HI) << 32;
pfdev->features.l2_present = gpu_read(pfdev, GPU_L2_PRESENT_LO);
pfdev->features.l2_present |= (u64)gpu_read(pfdev, GPU_L2_PRESENT_HI) << 32;
pfdev->features.nr_core_groups = hweight64(pfdev->features.l2_present);
pfdev->features.stack_present = gpu_read(pfdev, GPU_STACK_PRESENT_LO);
pfdev->features.stack_present |= (u64)gpu_read(pfdev, GPU_STACK_PRESENT_HI) << 32;
pfdev->features.thread_tls_alloc = gpu_read(pfdev, GPU_THREAD_TLS_ALLOC);
gpu_id = gpu_read(pfdev, GPU_ID);
pfdev->features.revision = gpu_id & 0xffff;
pfdev->features.id = gpu_id >> 16;
/* The T60x has an oddball ID value. Fix it up to the standard Midgard
* format so we (and userspace) don't have to special case it.
*/
if (pfdev->features.id == 0x6956)
pfdev->features.id = 0x0600;
major = (pfdev->features.revision >> 12) & 0xf;
minor = (pfdev->features.revision >> 4) & 0xff;
status = pfdev->features.revision & 0xf;
rev = pfdev->features.revision;
gpu_id = pfdev->features.id;
for (model = gpu_models; model->name; model++) {
int best = -1;
if (!panfrost_model_eq(pfdev, model->id))
continue;
name = model->name;
hw_feat = model->features;
hw_issues |= model->issues;
for (i = 0; i < MAX_HW_REVS; i++) {
if (model->revs[i].revision == rev) {
best = i;
break;
} else if (model->revs[i].revision == (rev & ~0xf))
best = i;
}
if (best >= 0)
hw_issues |= model->revs[best].issues;
break;
}
if (!model->name) {
dev_err(pfdev->base.dev, "GPU model not found: mali-%s id rev %#x %#x\n",
name, gpu_id, rev);
return -ENODEV;
}
bitmap_from_u64(pfdev->features.hw_features, hw_feat);
bitmap_from_u64(pfdev->features.hw_issues, hw_issues);
dev_info(pfdev->base.dev, "mali-%s id 0x%x major 0x%x minor 0x%x status 0x%x",
name, gpu_id, major, minor, status);
dev_info(pfdev->base.dev, "features: %64pb, issues: %64pb",
pfdev->features.hw_features,
pfdev->features.hw_issues);
dev_info(pfdev->base.dev, "Features: L2:0x%08x Shader:0x%08x Tiler:0x%08x Mem:0x%0x MMU:0x%08x AS:0x%x JS:0x%x",
pfdev->features.l2_features,
pfdev->features.core_features,
pfdev->features.tiler_features,
pfdev->features.mem_features,
pfdev->features.mmu_features,
pfdev->features.as_present,
pfdev->features.js_present);
dev_info(pfdev->base.dev, "shader_present=0x%0llx l2_present=0x%0llx",
pfdev->features.shader_present, pfdev->features.l2_present);
return 0;
}
void panfrost_cycle_counter_get(struct panfrost_device *pfdev)
{
if (atomic_inc_not_zero(&pfdev->cycle_counter.use_count))
return;
spin_lock(&pfdev->cycle_counter.lock);
if (atomic_inc_return(&pfdev->cycle_counter.use_count) == 1)
gpu_write(pfdev, GPU_CMD, GPU_CMD_CYCLE_COUNT_START);
spin_unlock(&pfdev->cycle_counter.lock);
}
void panfrost_cycle_counter_put(struct panfrost_device *pfdev)
{
if (atomic_add_unless(&pfdev->cycle_counter.use_count, -1, 1))
return;
spin_lock(&pfdev->cycle_counter.lock);
if (atomic_dec_return(&pfdev->cycle_counter.use_count) == 0)
gpu_write(pfdev, GPU_CMD, GPU_CMD_CYCLE_COUNT_STOP);
spin_unlock(&pfdev->cycle_counter.lock);
}
unsigned long long panfrost_cycle_counter_read(struct panfrost_device *pfdev)
{
u32 hi, lo;
do {
hi = gpu_read(pfdev, GPU_CYCLE_COUNT_HI);
lo = gpu_read(pfdev, GPU_CYCLE_COUNT_LO);
} while (hi != gpu_read(pfdev, GPU_CYCLE_COUNT_HI));
return ((u64)hi << 32) | lo;
}
unsigned long long panfrost_timestamp_read(struct panfrost_device *pfdev)
{
u32 hi, lo;
do {
hi = gpu_read(pfdev, GPU_TIMESTAMP_HI);
lo = gpu_read(pfdev, GPU_TIMESTAMP_LO);
} while (hi != gpu_read(pfdev, GPU_TIMESTAMP_HI));
return ((u64)hi << 32) | lo;
}
static u64 panfrost_get_core_mask(struct panfrost_device *pfdev)
{
u64 core_mask;
if (pfdev->features.l2_present == 1)
return U64_MAX;
/*
* 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.
*/
core_mask = ~(pfdev->features.l2_present - 1) &
(pfdev->features.l2_present - 2);
dev_info_once(pfdev->base.dev, "using only 1st core group (%lu cores from %lu)\n",
hweight64(core_mask),
hweight64(pfdev->features.shader_present));
return core_mask;
}
void panfrost_gpu_power_on(struct panfrost_device *pfdev)
{
int ret;
u32 val;
u64 core_mask;
panfrost_gpu_init_quirks(pfdev);
core_mask = panfrost_get_core_mask(pfdev);
gpu_write(pfdev, L2_PWRON_LO, pfdev->features.l2_present & core_mask);
ret = readl_relaxed_poll_timeout(pfdev->iomem + L2_READY_LO,
val, val == (pfdev->features.l2_present & core_mask),
10, 20000);
if (ret)
dev_err(pfdev->base.dev, "error powering up gpu L2");
gpu_write(pfdev, SHADER_PWRON_LO,
pfdev->features.shader_present & core_mask);
ret = readl_relaxed_poll_timeout(pfdev->iomem + SHADER_READY_LO,
val, val == (pfdev->features.shader_present & core_mask),
10, 20000);
if (ret)
dev_err(pfdev->base.dev, "error powering up gpu shader");
gpu_write(pfdev, TILER_PWRON_LO, pfdev->features.tiler_present);
ret = readl_relaxed_poll_timeout(pfdev->iomem + TILER_READY_LO,
val, val == pfdev->features.tiler_present, 10, 1000);
if (ret)
dev_err(pfdev->base.dev, "error powering up gpu tiler");
}
void panfrost_gpu_power_off(struct panfrost_device *pfdev)
{
int ret;
u32 val;
gpu_write(pfdev, SHADER_PWROFF_LO, pfdev->features.shader_present);
ret = readl_relaxed_poll_timeout(pfdev->iomem + SHADER_PWRTRANS_LO,
val, !val, 1, 2000);
if (ret)
dev_err(pfdev->base.dev, "shader power transition timeout");
gpu_write(pfdev, TILER_PWROFF_LO, pfdev->features.tiler_present);
ret = readl_relaxed_poll_timeout(pfdev->iomem + TILER_PWRTRANS_LO,
val, !val, 1, 2000);
if (ret)
dev_err(pfdev->base.dev, "tiler power transition timeout");
gpu_write(pfdev, L2_PWROFF_LO, pfdev->features.l2_present);
ret = readl_poll_timeout(pfdev->iomem + L2_PWRTRANS_LO,
val, !val, 0, 2000);
if (ret)
dev_err(pfdev->base.dev, "l2 power transition timeout");
}
void panfrost_gpu_suspend_irq(struct panfrost_device *pfdev)
{
set_bit(PANFROST_COMP_BIT_GPU, pfdev->is_suspended);
gpu_write(pfdev, GPU_INT_MASK, 0);
synchronize_irq(pfdev->gpu_irq);
}
int panfrost_gpu_init(struct panfrost_device *pfdev)
{
int err;
err = panfrost_gpu_soft_reset(pfdev);
if (err)
return err;
err = panfrost_gpu_init_features(pfdev);
if (err)
return err;
err = dma_set_mask_and_coherent(pfdev->base.dev,
DMA_BIT_MASK(FIELD_GET(0xff00,
pfdev->features.mmu_features)));
if (err)
return err;
dma_set_max_seg_size(pfdev->base.dev, UINT_MAX);
pfdev->gpu_irq = platform_get_irq_byname(to_platform_device(pfdev->base.dev), "gpu");
if (pfdev->gpu_irq < 0)
return pfdev->gpu_irq;
err = devm_request_irq(pfdev->base.dev, pfdev->gpu_irq, panfrost_gpu_irq_handler,
IRQF_SHARED, KBUILD_MODNAME "-gpu", pfdev);
if (err) {
dev_err(pfdev->base.dev, "failed to request gpu irq");
return err;
}
panfrost_gpu_power_on(pfdev);
return 0;
}
void panfrost_gpu_fini(struct panfrost_device *pfdev)
{
panfrost_gpu_power_off(pfdev);
}
u32 panfrost_gpu_get_latest_flush_id(struct panfrost_device *pfdev)
{
u32 flush_id;
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_FLUSH_REDUCTION)) {
/* Flush reduction only makes sense when the GPU is kept powered on between jobs */
if (pm_runtime_get_if_in_use(pfdev->base.dev)) {
flush_id = gpu_read(pfdev, GPU_LATEST_FLUSH_ID);
pm_runtime_put(pfdev->base.dev);
return flush_id;
}
}
return 0;
}
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Collabora ltd. */
#ifndef __PANFROST_GPU_H__
#define __PANFROST_GPU_H__
struct panfrost_device;
int panfrost_gpu_init(struct panfrost_device *pfdev);
void panfrost_gpu_fini(struct panfrost_device *pfdev);
u32 panfrost_gpu_get_latest_flush_id(struct panfrost_device *pfdev);
int panfrost_gpu_soft_reset(struct panfrost_device *pfdev);
void panfrost_gpu_power_on(struct panfrost_device *pfdev);
void panfrost_gpu_power_off(struct panfrost_device *pfdev);
void panfrost_gpu_suspend_irq(struct panfrost_device *pfdev);
void panfrost_cycle_counter_get(struct panfrost_device *pfdev);
void panfrost_cycle_counter_put(struct panfrost_device *pfdev);
unsigned long long panfrost_cycle_counter_read(struct panfrost_device *pfdev);
unsigned long long panfrost_timestamp_read(struct panfrost_device *pfdev);
void panfrost_gpu_amlogic_quirk(struct panfrost_device *pfdev);
#endif
@@ -0,0 +1,274 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* (C) COPYRIGHT 2014-2018 ARM Limited. All rights reserved. */
/* Copyright 2019 Linaro, Ltd., Rob Herring <robh@kernel.org> */
#ifndef __PANFROST_ISSUES_H__
#define __PANFROST_ISSUES_H__
#include <linux/bitops.h>
#include "panfrost_device.h"
/*
* This is not a complete list of issues, but only the ones the driver needs
* to care about.
*/
enum panfrost_hw_issue {
/* Need way to guarantee that all previously-translated memory accesses
* are committed */
HW_ISSUE_6367,
/* On job complete with non-done the cache is not flushed */
HW_ISSUE_6787,
/* Write of PRFCNT_CONFIG_MODE_MANUAL to PRFCNT_CONFIG causes a
* instrumentation dump if PRFCNT_TILER_EN is enabled */
HW_ISSUE_8186,
/* TIB: Reports faults from a vtile which has not yet been allocated */
HW_ISSUE_8245,
/* uTLB deadlock could occur when writing to an invalid page at the
* same time as access to a valid page in the same uTLB cache line ( ==
* 4 PTEs == 16K block of mapping) */
HW_ISSUE_8316,
/* HT: TERMINATE for RUN command ignored if previous LOAD_DESCRIPTOR is
* still executing */
HW_ISSUE_8394,
/* CSE: Sends a TERMINATED response for a task that should not be
* terminated */
HW_ISSUE_8401,
/* Repeatedly Soft-stopping a job chain consisting of (Vertex Shader,
* Cache Flush, Tiler) jobs causes DATA_INVALID_FAULT on tiler job. */
HW_ISSUE_8408,
/* Disable the Pause Buffer in the LS pipe. */
HW_ISSUE_8443,
/* Change in RMUs in use causes problems related with the core's SDC */
HW_ISSUE_8987,
/* Compute endpoint has a 4-deep queue of tasks, meaning a soft stop
* won't complete until all 4 tasks have completed */
HW_ISSUE_9435,
/* HT: Tiler returns TERMINATED for non-terminated command */
HW_ISSUE_9510,
/* Occasionally the GPU will issue multiple page faults for the same
* address before the MMU page table has been read by the GPU */
HW_ISSUE_9630,
/* RA DCD load request to SDC returns invalid load ignore causing
* colour buffer mismatch */
HW_ISSUE_10327,
/* MMU TLB invalidation hazards */
HW_ISSUE_10649,
/* Missing cache flush in multi core-group configuration */
HW_ISSUE_10676,
/* Chicken bit on T72X for a hardware workaround in compiler */
HW_ISSUE_10797,
/* Soft-stopping fragment jobs might fail with TILE_RANGE_FAULT */
HW_ISSUE_10817,
/* Intermittent missing interrupt on job completion */
HW_ISSUE_10883,
/* Soft-stopping fragment jobs might fail with TILE_RANGE_ERROR
* (similar to issue 10817) and can use #10817 workaround */
HW_ISSUE_10959,
/* Soft-stopped fragment shader job can restart with out-of-bound
* restart index */
HW_ISSUE_10969,
/* Race condition can cause tile list corruption */
HW_ISSUE_11020,
/* Write buffer can cause tile list corruption */
HW_ISSUE_11024,
/* Pause buffer can cause a fragment job hang */
HW_ISSUE_11035,
/* Dynamic Core Scaling not supported due to errata */
HW_ISSUE_11056,
/* Clear encoder state for a hard stopped fragment job which is AFBC
* encoded by soft resetting the GPU. Only for T76X r0p0, r0p1 and
* r0p1_50rel0 */
HW_ISSUE_T76X_3542,
/* Keep tiler module clock on to prevent GPU stall */
HW_ISSUE_T76X_3953,
/* Must ensure L2 is not transitioning when we reset. Workaround with a
* busy wait until L2 completes transition; ensure there is a maximum
* loop count as she may never complete her transition. (On chips
* without this errata, it's totally okay if L2 transitions.) */
HW_ISSUE_TMIX_8463,
/* Don't set SC_LS_ATTR_CHECK_DISABLE/SC_LS_ALLOW_ATTR_TYPES */
GPUCORE_1619,
/* When a hard-stop follows close after a soft-stop, the completion
* code for the terminated job may be incorrectly set to STOPPED */
HW_ISSUE_TMIX_8438,
/* "Protected mode" is buggy on Mali-G31 some Bifrost chips, so the
* kernel must fiddle with L2 caches to prevent data leakage */
HW_ISSUE_TGOX_R1_1234,
/* Must set SC_VAR_ALGORITHM */
HW_ISSUE_TTRX_2968_TTRX_3162,
/* Bus fault from occlusion query write may cause future fragment jobs
* to hang */
HW_ISSUE_TTRX_3076,
/* Must issue a dummy job before starting real work to prevent hangs */
HW_ISSUE_TTRX_3485,
HW_ISSUE_END
};
#define hw_issues_all (\
BIT_ULL(HW_ISSUE_9435))
#define hw_issues_t600 (\
BIT_ULL(HW_ISSUE_6367) | \
BIT_ULL(HW_ISSUE_6787) | \
BIT_ULL(HW_ISSUE_8408) | \
BIT_ULL(HW_ISSUE_9510) | \
BIT_ULL(HW_ISSUE_10649) | \
BIT_ULL(HW_ISSUE_10676) | \
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11035) | \
BIT_ULL(HW_ISSUE_11056) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t600_r0p0_15dev0 (\
BIT_ULL(HW_ISSUE_8186) | \
BIT_ULL(HW_ISSUE_8245) | \
BIT_ULL(HW_ISSUE_8316) | \
BIT_ULL(HW_ISSUE_8394) | \
BIT_ULL(HW_ISSUE_8401) | \
BIT_ULL(HW_ISSUE_8443) | \
BIT_ULL(HW_ISSUE_8987) | \
BIT_ULL(HW_ISSUE_9630) | \
BIT_ULL(HW_ISSUE_10969) | \
BIT_ULL(GPUCORE_1619))
#define hw_issues_t620 (\
BIT_ULL(HW_ISSUE_10649) | \
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_10959) | \
BIT_ULL(HW_ISSUE_11056) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t620_r0p1 (\
BIT_ULL(HW_ISSUE_10327) | \
BIT_ULL(HW_ISSUE_10676) | \
BIT_ULL(HW_ISSUE_10817) | \
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11024) | \
BIT_ULL(HW_ISSUE_11035))
#define hw_issues_t620_r1p0 (\
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11024))
#define hw_issues_t720 (\
BIT_ULL(HW_ISSUE_10649) | \
BIT_ULL(HW_ISSUE_10797) | \
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_11056) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t760 (\
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_T76X_3953) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t760_r0p0 (\
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11024) | \
BIT_ULL(HW_ISSUE_T76X_3542))
#define hw_issues_t760_r0p1 (\
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11024) | \
BIT_ULL(HW_ISSUE_T76X_3542))
#define hw_issues_t760_r0p1_50rel0 (\
BIT_ULL(HW_ISSUE_T76X_3542))
#define hw_issues_t760_r0p2 (\
BIT_ULL(HW_ISSUE_11020) | \
BIT_ULL(HW_ISSUE_11024) | \
BIT_ULL(HW_ISSUE_T76X_3542))
#define hw_issues_t760_r0p3 (\
BIT_ULL(HW_ISSUE_T76X_3542))
#define hw_issues_t820 (\
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_T76X_3953) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t830 (\
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_T76X_3953) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t860 (\
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_T76X_3953) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_t880 (\
BIT_ULL(HW_ISSUE_10883) | \
BIT_ULL(HW_ISSUE_T76X_3953) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_g31 0
#define hw_issues_g31_r1p0 (\
BIT_ULL(HW_ISSUE_TGOX_R1_1234))
#define hw_issues_g51 0
#define hw_issues_g52 0
#define hw_issues_g71 (\
BIT_ULL(HW_ISSUE_TMIX_8463) | \
BIT_ULL(HW_ISSUE_TMIX_8438))
#define hw_issues_g71_r0p0_05dev0 (\
BIT_ULL(HW_ISSUE_T76X_3953))
#define hw_issues_g72 0
#define hw_issues_g76 0
#define hw_issues_g57 (\
BIT_ULL(HW_ISSUE_TTRX_2968_TTRX_3162) | \
BIT_ULL(HW_ISSUE_TTRX_3076))
#define hw_issues_g57_r0p0 (\
BIT_ULL(HW_ISSUE_TTRX_3485))
static inline bool panfrost_has_hw_issue(const struct panfrost_device *pfdev,
enum panfrost_hw_issue issue)
{
return test_bit(issue, pfdev->features.hw_issues);
}
#endif /* __PANFROST_ISSUES_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2019 Collabora ltd. */
#ifndef __PANFROST_JOB_H__
#define __PANFROST_JOB_H__
#include <uapi/drm/panfrost_drm.h>
#include <drm/gpu_scheduler.h>
struct panfrost_device;
struct panfrost_gem_object;
struct panfrost_file_priv;
struct panfrost_job {
struct drm_sched_job base;
struct kref refcount;
struct panfrost_device *pfdev;
struct panfrost_mmu *mmu;
struct panfrost_jm_ctx *ctx;
/* Fence to be signaled by IRQ handler when the job is complete. */
struct dma_fence *done_fence;
__u64 jc;
__u32 requirements;
__u32 flush_id;
struct panfrost_gem_mapping **mappings;
struct drm_gem_object **bos;
u32 bo_count;
/* Fence to be signaled by drm-sched once its done with the job */
struct dma_fence *render_done_fence;
struct panfrost_engine_usage *engine_usage;
bool is_profiled;
ktime_t start_time;
u64 start_cycles;
};
struct panfrost_js_ctx {
struct drm_sched_entity sched_entity;
bool enabled;
};
#define NUM_JOB_SLOTS 3
struct panfrost_jm_ctx {
struct kref refcnt;
bool destroyed;
struct drm_sched_entity slot_entity[NUM_JOB_SLOTS];
};
extern const char * const panfrost_engine_names[];
int panfrost_jm_ctx_create(struct drm_file *file,
struct drm_panfrost_jm_ctx_create *args);
int panfrost_jm_ctx_destroy(struct drm_file *file, u32 handle);
void panfrost_jm_ctx_put(struct panfrost_jm_ctx *jm_ctx);
struct panfrost_jm_ctx *panfrost_jm_ctx_get(struct panfrost_jm_ctx *jm_ctx);
struct panfrost_jm_ctx *panfrost_jm_ctx_from_handle(struct drm_file *file, u32 handle);
int panfrost_jm_init(struct panfrost_device *pfdev);
void panfrost_jm_fini(struct panfrost_device *pfdev);
int panfrost_jm_open(struct drm_file *file);
void panfrost_jm_close(struct drm_file *file);
void panfrost_jm_reset_interrupts(struct panfrost_device *pfdev);
void panfrost_jm_enable_interrupts(struct panfrost_device *pfdev);
void panfrost_jm_suspend_irq(struct panfrost_device *pfdev);
int panfrost_jm_is_idle(struct panfrost_device *pfdev);
int panfrost_job_get_slot(struct panfrost_job *job);
int panfrost_job_push(struct panfrost_job *job);
void panfrost_job_put(struct panfrost_job *job);
#endif
@@ -0,0 +1,987 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
#include <drm/panfrost_drm.h>
#include <drm/drm_print.h>
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/io-pgtable.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/shmem_fs.h>
#include <linux/sizes.h>
#include "panfrost_device.h"
#include "panfrost_mmu.h"
#include "panfrost_gem.h"
#include "panfrost_features.h"
#include "panfrost_regs.h"
#define mmu_write(dev, reg, data) writel(data, dev->iomem + reg)
#define mmu_read(dev, reg) readl(dev->iomem + reg)
static u64 mair_to_memattr(u64 mair, bool coherent)
{
u64 memattr = 0;
u32 i;
for (i = 0; i < 8; i++) {
u8 in_attr = mair >> (8 * i), out_attr;
u8 outer = in_attr >> 4, inner = in_attr & 0xf;
/* For caching to be enabled, inner and outer caching policy
* have to be both write-back, if one of them is write-through
* or non-cacheable, we just choose non-cacheable. Device
* memory is also translated to non-cacheable.
*/
if (!(outer & 3) || !(outer & 4) || !(inner & 4)) {
out_attr = AS_MEMATTR_AARCH64_INNER_OUTER_NC |
AS_MEMATTR_AARCH64_SH_MIDGARD_INNER |
AS_MEMATTR_AARCH64_INNER_ALLOC_EXPL(false, false);
} else {
out_attr = AS_MEMATTR_AARCH64_INNER_OUTER_WB |
AS_MEMATTR_AARCH64_INNER_ALLOC_EXPL(inner & 1, inner & 2);
/* Use SH_MIDGARD_INNER mode when device isn't coherent,
* so SH_IS, which is used when IOMMU_CACHE is set, maps
* to Mali's internal-shareable mode. As per the Mali
* Spec, inner and outer-shareable modes aren't allowed
* for WB memory when coherency is disabled.
* Use SH_CPU_INNER mode when coherency is enabled, so
* that SH_IS actually maps to the standard definition of
* inner-shareable.
*/
if (!coherent)
out_attr |= AS_MEMATTR_AARCH64_SH_MIDGARD_INNER;
else
out_attr |= AS_MEMATTR_AARCH64_SH_CPU_INNER;
}
memattr |= (u64)out_attr << (8 * i);
}
return memattr;
}
static int wait_ready(struct panfrost_device *pfdev, u32 as_nr)
{
int ret;
u32 val;
/* Wait for the MMU status to indicate there is no active command, in
* case one is pending. */
ret = readl_relaxed_poll_timeout_atomic(pfdev->iomem + AS_STATUS(as_nr),
val, !(val & AS_STATUS_AS_ACTIVE), 10, 100000);
if (ret) {
/* The GPU hung, let's trigger a reset */
panfrost_device_schedule_reset(pfdev);
dev_err(pfdev->base.dev, "AS_ACTIVE bit stuck\n");
}
return ret;
}
static int write_cmd(struct panfrost_device *pfdev, u32 as_nr, u32 cmd)
{
int status;
/* write AS_COMMAND when MMU is ready to accept another command */
status = wait_ready(pfdev, as_nr);
if (!status)
mmu_write(pfdev, AS_COMMAND(as_nr), cmd);
return status;
}
static void lock_region(struct panfrost_device *pfdev, u32 as_nr,
u64 region_start, u64 size)
{
u8 region_width;
u64 region;
u64 region_end = region_start + size;
if (!size)
return;
/*
* The locked region is a naturally aligned power of 2 block encoded as
* log2 minus(1).
* Calculate the desired start/end and look for the highest bit which
* differs. The smallest naturally aligned block must include this bit
* change, the desired region starts with this bit (and subsequent bits)
* zeroed and ends with the bit (and subsequent bits) set to one.
*/
region_width = max(fls64(region_start ^ (region_end - 1)),
const_ilog2(AS_LOCK_REGION_MIN_SIZE)) - 1;
/*
* Mask off the low bits of region_start (which would be ignored by
* the hardware anyway)
*/
region_start &= GENMASK_ULL(63, region_width);
region = region_width | region_start;
/* Lock the region that needs to be updated */
mmu_write(pfdev, AS_LOCKADDR_LO(as_nr), lower_32_bits(region));
mmu_write(pfdev, AS_LOCKADDR_HI(as_nr), upper_32_bits(region));
write_cmd(pfdev, as_nr, AS_COMMAND_LOCK);
}
static int mmu_hw_do_operation_locked(struct panfrost_device *pfdev, int as_nr,
u64 iova, u64 size, u32 op)
{
if (as_nr < 0)
return 0;
if (op != AS_COMMAND_UNLOCK)
lock_region(pfdev, as_nr, iova, size);
/* Run the MMU operation */
write_cmd(pfdev, as_nr, op);
/* Wait for the flush to complete */
return wait_ready(pfdev, as_nr);
}
static int mmu_hw_do_operation(struct panfrost_device *pfdev,
struct panfrost_mmu *mmu,
u64 iova, u64 size, u32 op)
{
int ret;
spin_lock(&pfdev->as_lock);
ret = mmu_hw_do_operation_locked(pfdev, mmu->as, iova, size, op);
spin_unlock(&pfdev->as_lock);
return ret;
}
static void panfrost_mmu_enable(struct panfrost_device *pfdev, struct panfrost_mmu *mmu)
{
int as_nr = mmu->as;
u64 transtab = mmu->cfg.transtab;
u64 memattr = mmu->cfg.memattr;
u64 transcfg = mmu->cfg.transcfg;
mmu_hw_do_operation_locked(pfdev, as_nr, 0, ~0ULL, AS_COMMAND_FLUSH_MEM);
mmu_write(pfdev, AS_TRANSTAB_LO(as_nr), lower_32_bits(transtab));
mmu_write(pfdev, AS_TRANSTAB_HI(as_nr), upper_32_bits(transtab));
/* Need to revisit mem attrs.
* NC is the default, Mali driver is inner WT.
*/
mmu_write(pfdev, AS_MEMATTR_LO(as_nr), lower_32_bits(memattr));
mmu_write(pfdev, AS_MEMATTR_HI(as_nr), upper_32_bits(memattr));
mmu_write(pfdev, AS_TRANSCFG_LO(as_nr), lower_32_bits(transcfg));
mmu_write(pfdev, AS_TRANSCFG_HI(as_nr), upper_32_bits(transcfg));
write_cmd(pfdev, as_nr, AS_COMMAND_UPDATE);
}
static void panfrost_mmu_disable(struct panfrost_device *pfdev, u32 as_nr)
{
mmu_hw_do_operation_locked(pfdev, as_nr, 0, ~0ULL, AS_COMMAND_FLUSH_MEM);
mmu_write(pfdev, AS_TRANSTAB_LO(as_nr), 0);
mmu_write(pfdev, AS_TRANSTAB_HI(as_nr), 0);
mmu_write(pfdev, AS_MEMATTR_LO(as_nr), 0);
mmu_write(pfdev, AS_MEMATTR_HI(as_nr), 0);
mmu_write(pfdev, AS_TRANSCFG_LO(as_nr), AS_TRANSCFG_ADRMODE_UNMAPPED);
mmu_write(pfdev, AS_TRANSCFG_HI(as_nr), 0);
write_cmd(pfdev, as_nr, AS_COMMAND_UPDATE);
}
static int mmu_cfg_init_mali_lpae(struct panfrost_mmu *mmu)
{
struct io_pgtable_cfg *pgtbl_cfg = &mmu->pgtbl_cfg;
/* TODO: The following fields are duplicated between the MMU and Page
* Table config structs. Ideally, should be kept in one place.
*/
mmu->cfg.transtab = pgtbl_cfg->arm_mali_lpae_cfg.transtab;
mmu->cfg.memattr = pgtbl_cfg->arm_mali_lpae_cfg.memattr;
mmu->cfg.transcfg = AS_TRANSCFG_ADRMODE_LEGACY;
return 0;
}
static int mmu_cfg_init_aarch64_4k(struct panfrost_mmu *mmu)
{
struct io_pgtable_cfg *pgtbl_cfg = &mmu->pgtbl_cfg;
struct panfrost_device *pfdev = mmu->pfdev;
if (drm_WARN_ON(&pfdev->base, pgtbl_cfg->arm_lpae_s1_cfg.ttbr &
~AS_TRANSTAB_AARCH64_4K_ADDR_MASK))
return -EINVAL;
mmu->cfg.transtab = pgtbl_cfg->arm_lpae_s1_cfg.ttbr;
mmu->cfg.memattr = mair_to_memattr(pgtbl_cfg->arm_lpae_s1_cfg.mair,
pgtbl_cfg->coherent_walk);
mmu->cfg.transcfg = AS_TRANSCFG_PTW_MEMATTR_WB |
AS_TRANSCFG_PTW_RA |
AS_TRANSCFG_ADRMODE_AARCH64_4K |
AS_TRANSCFG_INA_BITS(55 - pgtbl_cfg->ias);
if (pgtbl_cfg->coherent_walk)
mmu->cfg.transcfg |= AS_TRANSCFG_PTW_SH_OS;
return 0;
}
static int panfrost_mmu_cfg_init(struct panfrost_mmu *mmu,
enum io_pgtable_fmt fmt)
{
struct panfrost_device *pfdev = mmu->pfdev;
switch (fmt) {
case ARM_64_LPAE_S1:
return mmu_cfg_init_aarch64_4k(mmu);
case ARM_MALI_LPAE:
return mmu_cfg_init_mali_lpae(mmu);
default:
/* This should never happen */
drm_WARN(&pfdev->base, 1, "Invalid pgtable format");
return -EINVAL;
}
}
int panfrost_mmu_as_get(struct panfrost_device *pfdev, struct panfrost_mmu *mmu)
{
int as;
spin_lock(&pfdev->as_lock);
as = mmu->as;
if (as >= 0) {
int en = atomic_inc_return(&mmu->as_count);
u32 mask = BIT(as) | BIT(16 + as);
/*
* AS can be retained by active jobs or a perfcnt context,
* hence the '+ 1' here.
*/
WARN_ON(en >= (NUM_JOB_SLOTS + 1));
list_move(&mmu->list, &pfdev->as_lru_list);
if (pfdev->as_faulty_mask & mask) {
/* Unhandled pagefault on this AS, the MMU was
* disabled. We need to re-enable the MMU after
* clearing+unmasking the AS interrupts.
*/
mmu_write(pfdev, MMU_INT_CLEAR, mask);
mmu_write(pfdev, MMU_INT_MASK, ~pfdev->as_faulty_mask);
pfdev->as_faulty_mask &= ~mask;
panfrost_mmu_enable(pfdev, mmu);
}
goto out;
}
/* Check for a free AS */
as = ffz(pfdev->as_alloc_mask);
if (!(BIT(as) & pfdev->features.as_present)) {
struct panfrost_mmu *lru_mmu;
list_for_each_entry_reverse(lru_mmu, &pfdev->as_lru_list, list) {
if (!atomic_read(&lru_mmu->as_count))
break;
}
if (WARN_ON(&lru_mmu->list == &pfdev->as_lru_list)) {
as = -EBUSY;
goto out;
}
list_del_init(&lru_mmu->list);
as = lru_mmu->as;
WARN_ON(as < 0);
lru_mmu->as = -1;
}
/* Assign the free or reclaimed AS to the FD */
mmu->as = as;
set_bit(as, &pfdev->as_alloc_mask);
atomic_set(&mmu->as_count, 1);
list_add(&mmu->list, &pfdev->as_lru_list);
dev_dbg(pfdev->base.dev,
"Assigned AS%d to mmu %p, alloc_mask=%lx",
as, mmu, pfdev->as_alloc_mask);
panfrost_mmu_enable(pfdev, mmu);
out:
spin_unlock(&pfdev->as_lock);
return as;
}
void panfrost_mmu_as_put(struct panfrost_device *pfdev, struct panfrost_mmu *mmu)
{
atomic_dec(&mmu->as_count);
WARN_ON(atomic_read(&mmu->as_count) < 0);
}
void panfrost_mmu_reset(struct panfrost_device *pfdev)
{
struct panfrost_mmu *mmu, *mmu_tmp;
clear_bit(PANFROST_COMP_BIT_MMU, pfdev->is_suspended);
spin_lock(&pfdev->as_lock);
pfdev->as_alloc_mask = 0;
pfdev->as_faulty_mask = 0;
list_for_each_entry_safe(mmu, mmu_tmp, &pfdev->as_lru_list, list) {
mmu->as = -1;
atomic_set(&mmu->as_count, 0);
list_del_init(&mmu->list);
}
spin_unlock(&pfdev->as_lock);
mmu_write(pfdev, MMU_INT_CLEAR, ~0);
mmu_write(pfdev, MMU_INT_MASK, ~0);
}
static size_t get_pgsize(u64 addr, size_t size, size_t *count)
{
/*
* io-pgtable only operates on multiple pages within a single table
* entry, so we need to split at boundaries of the table size, i.e.
* the next block size up. The distance from address A to the next
* boundary of block size B is logically B - A % B, but in unsigned
* two's complement where B is a power of two we get the equivalence
* B - A % B == (B - A) % B == (n * B - A) % B, and choose n = 0 :)
*/
size_t blk_offset = -addr % SZ_2M;
if (blk_offset || size < SZ_2M) {
*count = min_not_zero(blk_offset, size) / SZ_4K;
return SZ_4K;
}
blk_offset = -addr % SZ_1G ?: SZ_1G;
*count = min(blk_offset, size) / SZ_2M;
return SZ_2M;
}
static void panfrost_mmu_flush_range(struct panfrost_device *pfdev,
struct panfrost_mmu *mmu,
u64 iova, u64 size)
{
if (mmu->as < 0)
return;
pm_runtime_get_noresume(pfdev->base.dev);
/* Flush the PTs only if we're already awake */
if (pm_runtime_active(pfdev->base.dev))
mmu_hw_do_operation(pfdev, mmu, iova, size, AS_COMMAND_FLUSH_PT);
pm_runtime_put_autosuspend(pfdev->base.dev);
}
static void mmu_unmap_range(struct panfrost_mmu *mmu, u64 iova, size_t len)
{
struct io_pgtable_ops *ops = mmu->pgtbl_ops;
size_t pgsize, unmapped_len = 0;
size_t unmapped_page, pgcount;
while (unmapped_len < len) {
pgsize = get_pgsize(iova, len - unmapped_len, &pgcount);
unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
WARN_ON(unmapped_page != pgsize * pgcount);
iova += pgsize * pgcount;
unmapped_len += pgsize * pgcount;
}
}
static int mmu_map_sg(struct panfrost_device *pfdev, struct panfrost_mmu *mmu,
u64 iova, int prot, struct sg_table *sgt)
{
unsigned int count;
struct scatterlist *sgl;
struct io_pgtable_ops *ops = mmu->pgtbl_ops;
size_t total_mapped = 0;
u64 start_iova = iova;
int ret;
for_each_sgtable_dma_sg(sgt, sgl, count) {
unsigned long paddr = sg_dma_address(sgl);
size_t len = sg_dma_len(sgl);
dev_dbg(pfdev->base.dev,
"map: as=%d, iova=%llx, paddr=%lx, len=%zx",
mmu->as, iova, paddr, len);
while (len) {
size_t pgcount, mapped = 0;
size_t pgsize = get_pgsize(iova | paddr, len, &pgcount);
ret = ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot,
GFP_KERNEL, &mapped);
if (ret)
goto err_unmap_pages;
/* Don't get stuck if things have gone wrong */
mapped = max(mapped, pgsize);
total_mapped += mapped;
iova += mapped;
paddr += mapped;
len -= mapped;
}
}
panfrost_mmu_flush_range(pfdev, mmu, start_iova, iova - start_iova);
return 0;
err_unmap_pages:
mmu_unmap_range(mmu, start_iova, total_mapped);
return ret;
}
int panfrost_mmu_map(struct panfrost_gem_mapping *mapping)
{
struct panfrost_gem_object *bo = mapping->obj;
struct drm_gem_shmem_object *shmem = &bo->base;
struct drm_gem_object *obj = &shmem->base;
struct panfrost_device *pfdev = to_panfrost_device(obj->dev);
struct sg_table *sgt;
int prot = IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE;
int ret;
if (WARN_ON(mapping->active))
return 0;
if (bo->noexec)
prot |= IOMMU_NOEXEC;
sgt = drm_gem_shmem_get_pages_sgt(shmem);
if (WARN_ON(IS_ERR(sgt)))
return PTR_ERR(sgt);
ret = mmu_map_sg(pfdev, mapping->mmu, mapping->mmnode.start << PAGE_SHIFT,
prot, sgt);
if (ret)
goto err_put_pages;
mapping->active = true;
return 0;
err_put_pages:
drm_gem_shmem_put_pages_locked(shmem);
return ret;
}
void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping)
{
struct panfrost_gem_object *bo = mapping->obj;
struct drm_gem_object *obj = &bo->base.base;
struct panfrost_device *pfdev = to_panfrost_device(obj->dev);
struct io_pgtable_ops *ops = mapping->mmu->pgtbl_ops;
u64 iova = mapping->mmnode.start << PAGE_SHIFT;
size_t len = mapping->mmnode.size << PAGE_SHIFT;
size_t unmapped_len = 0;
if (WARN_ON(!mapping->active))
return;
dev_dbg(pfdev->base.dev, "unmap: as=%d, iova=%llx, len=%zx",
mapping->mmu->as, iova, len);
while (unmapped_len < len) {
size_t unmapped_page, pgcount;
size_t pgsize = get_pgsize(iova, len - unmapped_len, &pgcount);
if (bo->is_heap)
pgcount = 1;
if (!bo->is_heap || ops->iova_to_phys(ops, iova)) {
unmapped_page = ops->unmap_pages(ops, iova, pgsize, pgcount, NULL);
WARN_ON(unmapped_page != pgsize * pgcount);
}
iova += pgsize * pgcount;
unmapped_len += pgsize * pgcount;
}
panfrost_mmu_flush_range(pfdev, mapping->mmu,
mapping->mmnode.start << PAGE_SHIFT, len);
mapping->active = false;
}
static void mmu_tlb_inv_context_s1(void *cookie)
{}
static void mmu_tlb_sync_context(void *cookie)
{
//struct panfrost_mmu *mmu = cookie;
// TODO: Wait 1000 GPU cycles for HW_ISSUE_6367/T60X
}
static void mmu_tlb_flush_walk(unsigned long iova, size_t size, size_t granule,
void *cookie)
{
mmu_tlb_sync_context(cookie);
}
static const struct iommu_flush_ops mmu_tlb_ops = {
.tlb_flush_all = mmu_tlb_inv_context_s1,
.tlb_flush_walk = mmu_tlb_flush_walk,
};
static struct panfrost_gem_mapping *
addr_to_mapping(struct panfrost_device *pfdev, int as, u64 addr)
{
struct panfrost_gem_mapping *mapping = NULL;
struct drm_mm_node *node;
u64 offset = addr >> PAGE_SHIFT;
struct panfrost_mmu *mmu;
spin_lock(&pfdev->as_lock);
list_for_each_entry(mmu, &pfdev->as_lru_list, list) {
if (as == mmu->as)
goto found_mmu;
}
goto out;
found_mmu:
spin_lock(&mmu->mm_lock);
drm_mm_for_each_node(node, &mmu->mm) {
if (offset >= node->start &&
offset < (node->start + node->size)) {
mapping = drm_mm_node_to_panfrost_mapping(node);
kref_get(&mapping->refcount);
break;
}
}
spin_unlock(&mmu->mm_lock);
out:
spin_unlock(&pfdev->as_lock);
return mapping;
}
#define NUM_FAULT_PAGES (SZ_2M / PAGE_SIZE)
static int panfrost_mmu_map_fault_addr(struct panfrost_device *pfdev, int as,
u64 addr)
{
int ret;
struct panfrost_gem_mapping *bomapping;
struct panfrost_gem_object *bo;
struct address_space *mapping;
struct drm_gem_object *obj;
pgoff_t page_offset, nr_pages;
struct sg_table *sgt;
struct page **pages;
bomapping = addr_to_mapping(pfdev, as, addr);
if (!bomapping)
return -ENOENT;
bo = bomapping->obj;
if (!bo->is_heap) {
dev_WARN(pfdev->base.dev, "matching BO is not heap type (GPU VA = %llx)",
bomapping->mmnode.start << PAGE_SHIFT);
ret = -EINVAL;
goto err_bo;
}
WARN_ON(bomapping->mmu->as != as);
/* Assume 2MB alignment and size multiple */
addr &= ~((u64)SZ_2M - 1);
page_offset = addr >> PAGE_SHIFT;
page_offset -= bomapping->mmnode.start;
nr_pages = bo->base.base.size >> PAGE_SHIFT;
obj = &bo->base.base;
dma_resv_lock(obj->resv, NULL);
if (!bo->base.pages) {
bo->sgts = kvmalloc_objs(struct sg_table,
bo->base.base.size / SZ_2M,
GFP_KERNEL | __GFP_ZERO);
if (!bo->sgts) {
ret = -ENOMEM;
goto err_unlock;
}
pages = kvmalloc_objs(struct page *, nr_pages,
GFP_KERNEL | __GFP_ZERO);
if (!pages) {
kvfree(bo->sgts);
bo->sgts = NULL;
ret = -ENOMEM;
goto err_unlock;
}
bo->base.pages = pages;
refcount_set(&bo->base.pages_use_count, 1);
} else {
pages = bo->base.pages;
}
sgt = &bo->sgts[page_offset / (SZ_2M / PAGE_SIZE)];
if (sgt->sgl) {
/* Pages are already mapped, bail out. */
goto out;
}
mapping = bo->base.base.filp->f_mapping;
mapping_set_unevictable(mapping);
for (pgoff_t pg = page_offset; pg < page_offset + NUM_FAULT_PAGES;) {
bool already_owned = false;
struct folio *folio;
folio = shmem_read_folio(mapping, pg);
if (IS_ERR(folio)) {
ret = PTR_ERR(folio);
goto err_unlock;
}
pg &= ~(folio_nr_pages(folio) - 1);
for (u32 i = 0; i < folio_nr_pages(folio) && pg < nr_pages; i++) {
if (pages[pg])
already_owned = true;
pages[pg++] = folio_page(folio, i);
}
/* We always fill the page array at a folio granularity so
* there's no valid reason for a folio range to be partially
* populated.
*/
if (drm_WARN_ON(&pfdev->base, already_owned))
folio_put(folio);
}
ret = sg_alloc_table_from_pages(sgt, pages + page_offset,
NUM_FAULT_PAGES, 0, SZ_2M, GFP_KERNEL);
if (ret)
goto err_unlock;
ret = dma_map_sgtable(pfdev->base.dev, sgt, DMA_BIDIRECTIONAL, 0);
if (ret)
goto err_map;
ret = mmu_map_sg(pfdev, bomapping->mmu, addr,
IOMMU_WRITE | IOMMU_READ | IOMMU_CACHE | IOMMU_NOEXEC, sgt);
if (ret)
goto err_mmu_map_sg;
bomapping->active = true;
bo->heap_rss_size += SZ_2M;
dev_dbg(pfdev->base.dev, "mapped page fault @ AS%d %llx", as, addr);
out:
dma_resv_unlock(obj->resv);
panfrost_gem_mapping_put(bomapping);
return 0;
err_mmu_map_sg:
dma_unmap_sgtable(pfdev->base.dev, sgt, DMA_BIDIRECTIONAL, 0);
err_map:
sg_free_table(sgt);
err_unlock:
dma_resv_unlock(obj->resv);
err_bo:
panfrost_gem_mapping_put(bomapping);
return ret;
}
static void panfrost_mmu_release_ctx(struct kref *kref)
{
struct panfrost_mmu *mmu = container_of(kref, struct panfrost_mmu,
refcount);
struct panfrost_device *pfdev = mmu->pfdev;
spin_lock(&pfdev->as_lock);
if (mmu->as >= 0) {
pm_runtime_get_noresume(pfdev->base.dev);
if (pm_runtime_active(pfdev->base.dev))
panfrost_mmu_disable(pfdev, mmu->as);
pm_runtime_put_autosuspend(pfdev->base.dev);
clear_bit(mmu->as, &pfdev->as_alloc_mask);
list_del(&mmu->list);
}
spin_unlock(&pfdev->as_lock);
free_io_pgtable_ops(mmu->pgtbl_ops);
drm_mm_takedown(&mmu->mm);
kfree(mmu);
}
void panfrost_mmu_ctx_put(struct panfrost_mmu *mmu)
{
kref_put(&mmu->refcount, panfrost_mmu_release_ctx);
}
struct panfrost_mmu *panfrost_mmu_ctx_get(struct panfrost_mmu *mmu)
{
kref_get(&mmu->refcount);
return mmu;
}
#define PFN_4G (SZ_4G >> PAGE_SHIFT)
#define PFN_4G_MASK (PFN_4G - 1)
#define PFN_16M (SZ_16M >> PAGE_SHIFT)
static void panfrost_drm_mm_color_adjust(const struct drm_mm_node *node,
unsigned long color,
u64 *start, u64 *end)
{
/* Executable buffers can't start or end on a 4GB boundary */
if (!(color & PANFROST_BO_NOEXEC)) {
u64 next_seg;
if ((*start & PFN_4G_MASK) == 0)
(*start)++;
if ((*end & PFN_4G_MASK) == 0)
(*end)--;
next_seg = ALIGN(*start, PFN_4G);
if (next_seg - *start <= PFN_16M)
*start = next_seg + 1;
*end = min(*end, ALIGN(*start, PFN_4G) - 1);
}
}
struct panfrost_mmu *panfrost_mmu_ctx_create(struct panfrost_device *pfdev)
{
u32 va_bits = GPU_MMU_FEATURES_VA_BITS(pfdev->features.mmu_features);
u32 pa_bits = GPU_MMU_FEATURES_PA_BITS(pfdev->features.mmu_features);
struct panfrost_mmu *mmu;
enum io_pgtable_fmt fmt;
int ret;
if (pfdev->comp->gpu_quirks & BIT(GPU_QUIRK_FORCE_AARCH64_PGTABLE)) {
if (!panfrost_has_hw_feature(pfdev, HW_FEATURE_AARCH64_MMU)) {
dev_err_once(pfdev->base.dev,
"AARCH64_4K page table not supported\n");
return ERR_PTR(-EINVAL);
}
fmt = ARM_64_LPAE_S1;
} else {
fmt = ARM_MALI_LPAE;
}
mmu = kzalloc_obj(*mmu);
if (!mmu)
return ERR_PTR(-ENOMEM);
mmu->pfdev = pfdev;
spin_lock_init(&mmu->mm_lock);
/* 4G enough for now. can be 48-bit */
drm_mm_init(&mmu->mm, SZ_32M >> PAGE_SHIFT, (SZ_4G - SZ_32M) >> PAGE_SHIFT);
mmu->mm.color_adjust = panfrost_drm_mm_color_adjust;
INIT_LIST_HEAD(&mmu->list);
mmu->as = -1;
mmu->pgtbl_cfg = (struct io_pgtable_cfg) {
.pgsize_bitmap = SZ_4K | SZ_2M,
.ias = va_bits,
.oas = pa_bits,
.coherent_walk = pfdev->coherent,
.tlb = &mmu_tlb_ops,
.iommu_dev = pfdev->base.dev,
};
mmu->pgtbl_ops = alloc_io_pgtable_ops(fmt, &mmu->pgtbl_cfg, mmu);
if (!mmu->pgtbl_ops) {
ret = -EINVAL;
goto err_free_mmu;
}
ret = panfrost_mmu_cfg_init(mmu, fmt);
if (ret)
goto err_free_io_pgtable;
kref_init(&mmu->refcount);
return mmu;
err_free_io_pgtable:
free_io_pgtable_ops(mmu->pgtbl_ops);
err_free_mmu:
kfree(mmu);
return ERR_PTR(ret);
}
static const char *access_type_name(struct panfrost_device *pfdev,
u32 fault_status)
{
switch (fault_status & AS_FAULTSTATUS_ACCESS_TYPE_MASK) {
case AS_FAULTSTATUS_ACCESS_TYPE_ATOMIC:
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_AARCH64_MMU))
return "ATOMIC";
else
return "UNKNOWN";
case AS_FAULTSTATUS_ACCESS_TYPE_READ:
return "READ";
case AS_FAULTSTATUS_ACCESS_TYPE_WRITE:
return "WRITE";
case AS_FAULTSTATUS_ACCESS_TYPE_EX:
return "EXECUTE";
default:
WARN_ON(1);
return NULL;
}
}
static irqreturn_t panfrost_mmu_irq_handler(int irq, void *data)
{
struct panfrost_device *pfdev = data;
if (test_bit(PANFROST_COMP_BIT_MMU, pfdev->is_suspended))
return IRQ_NONE;
if (!mmu_read(pfdev, MMU_INT_STAT))
return IRQ_NONE;
mmu_write(pfdev, MMU_INT_MASK, 0);
return IRQ_WAKE_THREAD;
}
static irqreturn_t panfrost_mmu_irq_handler_thread(int irq, void *data)
{
struct panfrost_device *pfdev = data;
u32 status = mmu_read(pfdev, MMU_INT_RAWSTAT);
int ret;
while (status) {
u32 as = ffs(status | (status >> 16)) - 1;
u32 mask = BIT(as) | BIT(as + 16);
u64 addr;
u32 fault_status;
u32 exception_type;
u32 access_type;
u32 source_id;
fault_status = mmu_read(pfdev, AS_FAULTSTATUS(as));
addr = mmu_read(pfdev, AS_FAULTADDRESS_LO(as));
addr |= (u64)mmu_read(pfdev, AS_FAULTADDRESS_HI(as)) << 32;
/* decode the fault status */
exception_type = fault_status & 0xFF;
access_type = (fault_status >> 8) & 0x3;
source_id = (fault_status >> 16);
mmu_write(pfdev, MMU_INT_CLEAR, mask);
/* Page fault only */
ret = -1;
if ((status & mask) == BIT(as) && (exception_type & 0xF8) == 0xC0)
ret = panfrost_mmu_map_fault_addr(pfdev, as, addr);
if (ret) {
/* terminal fault, print info about the fault */
dev_err(pfdev->base.dev,
"Unhandled Page fault in AS%d at VA 0x%016llX\n"
"Reason: %s\n"
"raw fault status: 0x%X\n"
"decoded fault status: %s\n"
"exception type 0x%X: %s\n"
"access type 0x%X: %s\n"
"source id 0x%X\n",
as, addr,
"TODO",
fault_status,
(fault_status & (1 << 10) ? "DECODER FAULT" : "SLAVE FAULT"),
exception_type, panfrost_exception_name(exception_type),
access_type, access_type_name(pfdev, fault_status),
source_id);
spin_lock(&pfdev->as_lock);
/* Ignore MMU interrupts on this AS until it's been
* re-enabled.
*/
pfdev->as_faulty_mask |= mask;
/* Disable the MMU to kill jobs on this AS. */
panfrost_mmu_disable(pfdev, as);
spin_unlock(&pfdev->as_lock);
}
status &= ~mask;
/* If we received new MMU interrupts, process them before returning. */
if (!status)
status = mmu_read(pfdev, MMU_INT_RAWSTAT) & ~pfdev->as_faulty_mask;
}
/* Enable interrupts only if we're not about to get suspended */
if (!test_bit(PANFROST_COMP_BIT_MMU, pfdev->is_suspended)) {
spin_lock(&pfdev->as_lock);
mmu_write(pfdev, MMU_INT_MASK, ~pfdev->as_faulty_mask);
spin_unlock(&pfdev->as_lock);
}
return IRQ_HANDLED;
};
int panfrost_mmu_init(struct panfrost_device *pfdev)
{
int err;
pfdev->mmu_irq = platform_get_irq_byname(to_platform_device(pfdev->base.dev), "mmu");
if (pfdev->mmu_irq < 0)
return pfdev->mmu_irq;
err = devm_request_threaded_irq(pfdev->base.dev, pfdev->mmu_irq,
panfrost_mmu_irq_handler,
panfrost_mmu_irq_handler_thread,
IRQF_SHARED, KBUILD_MODNAME "-mmu",
pfdev);
if (err) {
dev_err(pfdev->base.dev, "failed to request mmu irq");
return err;
}
return 0;
}
void panfrost_mmu_fini(struct panfrost_device *pfdev)
{
mmu_write(pfdev, MMU_INT_MASK, 0);
}
void panfrost_mmu_suspend_irq(struct panfrost_device *pfdev)
{
set_bit(PANFROST_COMP_BIT_MMU, pfdev->is_suspended);
mmu_write(pfdev, MMU_INT_MASK, 0);
synchronize_irq(pfdev->mmu_irq);
}
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
#ifndef __PANFROST_MMU_H__
#define __PANFROST_MMU_H__
struct panfrost_device;
struct panfrost_gem_mapping;
struct panfrost_file_priv;
struct panfrost_mmu;
int panfrost_mmu_map(struct panfrost_gem_mapping *mapping);
void panfrost_mmu_unmap(struct panfrost_gem_mapping *mapping);
int panfrost_mmu_init(struct panfrost_device *pfdev);
void panfrost_mmu_fini(struct panfrost_device *pfdev);
void panfrost_mmu_reset(struct panfrost_device *pfdev);
void panfrost_mmu_suspend_irq(struct panfrost_device *pfdev);
int panfrost_mmu_as_get(struct panfrost_device *pfdev, struct panfrost_mmu *mmu);
void panfrost_mmu_as_put(struct panfrost_device *pfdev, struct panfrost_mmu *mmu);
struct panfrost_mmu *panfrost_mmu_ctx_get(struct panfrost_mmu *mmu);
void panfrost_mmu_ctx_put(struct panfrost_mmu *mmu);
struct panfrost_mmu *panfrost_mmu_ctx_create(struct panfrost_device *pfdev);
#endif
@@ -0,0 +1,353 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Collabora Ltd */
#include <linux/completion.h>
#include <linux/iopoll.h>
#include <linux/iosys-map.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <drm/drm_file.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/panfrost_drm.h>
#include "panfrost_device.h"
#include "panfrost_features.h"
#include "panfrost_gem.h"
#include "panfrost_issues.h"
#include "panfrost_job.h"
#include "panfrost_mmu.h"
#include "panfrost_perfcnt.h"
#include "panfrost_regs.h"
#define COUNTERS_PER_BLOCK 64
#define BYTES_PER_COUNTER 4
#define BLOCKS_PER_COREGROUP 8
#define V4_SHADERS_PER_COREGROUP 4
struct panfrost_perfcnt {
struct panfrost_gem_mapping *mapping;
size_t bosize;
void *buf;
struct panfrost_file_priv *user;
struct mutex lock;
struct completion dump_comp;
};
void panfrost_perfcnt_clean_cache_done(struct panfrost_device *pfdev)
{
complete(&pfdev->perfcnt->dump_comp);
}
void panfrost_perfcnt_sample_done(struct panfrost_device *pfdev)
{
gpu_write(pfdev, GPU_CMD, GPU_CMD_CLEAN_CACHES);
}
static int panfrost_perfcnt_dump_locked(struct panfrost_device *pfdev)
{
u64 gpuva;
int ret;
reinit_completion(&pfdev->perfcnt->dump_comp);
gpuva = pfdev->perfcnt->mapping->mmnode.start << PAGE_SHIFT;
gpu_write(pfdev, GPU_PERFCNT_BASE_LO, lower_32_bits(gpuva));
gpu_write(pfdev, GPU_PERFCNT_BASE_HI, upper_32_bits(gpuva));
gpu_write(pfdev, GPU_INT_CLEAR,
GPU_IRQ_CLEAN_CACHES_COMPLETED |
GPU_IRQ_PERFCNT_SAMPLE_COMPLETED);
gpu_write(pfdev, GPU_CMD, GPU_CMD_PERFCNT_SAMPLE);
ret = wait_for_completion_interruptible_timeout(&pfdev->perfcnt->dump_comp,
msecs_to_jiffies(1000));
if (!ret)
ret = -ETIMEDOUT;
else if (ret > 0)
ret = 0;
return ret;
}
static int panfrost_perfcnt_enable_locked(struct panfrost_device *pfdev,
struct drm_file *file_priv,
unsigned int counterset)
{
struct panfrost_file_priv *user = file_priv->driver_priv;
struct panfrost_perfcnt *perfcnt = pfdev->perfcnt;
struct iosys_map map;
struct drm_gem_shmem_object *bo;
u32 cfg, as;
int ret;
if (user == perfcnt->user)
return 0;
else if (perfcnt->user)
return -EBUSY;
ret = pm_runtime_get_sync(pfdev->base.dev);
if (ret < 0)
goto err_put_pm;
bo = drm_gem_shmem_create(&pfdev->base, perfcnt->bosize);
if (IS_ERR(bo)) {
ret = PTR_ERR(bo);
goto err_put_pm;
}
/* Map the perfcnt buf in the address space attached to file_priv. */
ret = panfrost_gem_open(&bo->base, file_priv);
if (ret)
goto err_put_bo;
perfcnt->mapping = panfrost_gem_mapping_get(to_panfrost_bo(&bo->base),
user);
if (!perfcnt->mapping) {
ret = -EINVAL;
goto err_close_bo;
}
ret = drm_gem_vmap(&bo->base, &map);
if (ret)
goto err_put_mapping;
perfcnt->buf = map.vaddr;
panfrost_gem_internal_set_label(&bo->base, "Perfcnt sample buffer");
/*
* Invalidate the cache and clear the counters to start from a fresh
* state.
*/
reinit_completion(&pfdev->perfcnt->dump_comp);
gpu_write(pfdev, GPU_INT_CLEAR,
GPU_IRQ_CLEAN_CACHES_COMPLETED |
GPU_IRQ_PERFCNT_SAMPLE_COMPLETED);
gpu_write(pfdev, GPU_CMD, GPU_CMD_PERFCNT_CLEAR);
gpu_write(pfdev, GPU_CMD, GPU_CMD_CLEAN_INV_CACHES);
ret = wait_for_completion_timeout(&pfdev->perfcnt->dump_comp,
msecs_to_jiffies(1000));
if (!ret) {
ret = -ETIMEDOUT;
goto err_vunmap;
}
ret = panfrost_mmu_as_get(pfdev, perfcnt->mapping->mmu);
if (ret < 0)
goto err_vunmap;
as = ret;
cfg = GPU_PERFCNT_CFG_AS(as) |
GPU_PERFCNT_CFG_MODE(GPU_PERFCNT_CFG_MODE_MANUAL);
/*
* Bifrost GPUs have 2 set of counters, but we're only interested by
* the first one for now.
*/
if (panfrost_model_is_bifrost(pfdev))
cfg |= GPU_PERFCNT_CFG_SETSEL(counterset);
gpu_write(pfdev, GPU_PRFCNT_JM_EN, 0xffffffff);
gpu_write(pfdev, GPU_PRFCNT_SHADER_EN, 0xffffffff);
gpu_write(pfdev, GPU_PRFCNT_MMU_L2_EN, 0xffffffff);
/*
* Due to PRLAM-8186 we need to disable the Tiler before we enable HW
* counters.
*/
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_8186))
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0);
else
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0xffffffff);
gpu_write(pfdev, GPU_PERFCNT_CFG, cfg);
if (panfrost_has_hw_issue(pfdev, HW_ISSUE_8186))
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0xffffffff);
/* The BO ref is retained by the mapping. */
drm_gem_object_put(&bo->base);
perfcnt->user = user;
return 0;
err_vunmap:
drm_gem_vunmap(&bo->base, &map);
err_put_mapping:
panfrost_gem_mapping_put(perfcnt->mapping);
err_close_bo:
panfrost_gem_close(&bo->base, file_priv);
err_put_bo:
drm_gem_object_put(&bo->base);
err_put_pm:
pm_runtime_put(pfdev->base.dev);
return ret;
}
static int panfrost_perfcnt_disable_locked(struct panfrost_device *pfdev,
struct drm_file *file_priv)
{
struct panfrost_file_priv *user = file_priv->driver_priv;
struct panfrost_perfcnt *perfcnt = pfdev->perfcnt;
struct iosys_map map = IOSYS_MAP_INIT_VADDR(perfcnt->buf);
if (user != perfcnt->user)
return -EINVAL;
gpu_write(pfdev, GPU_PRFCNT_JM_EN, 0x0);
gpu_write(pfdev, GPU_PRFCNT_SHADER_EN, 0x0);
gpu_write(pfdev, GPU_PRFCNT_MMU_L2_EN, 0x0);
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0);
gpu_write(pfdev, GPU_PERFCNT_CFG,
GPU_PERFCNT_CFG_MODE(GPU_PERFCNT_CFG_MODE_OFF));
perfcnt->user = NULL;
drm_gem_vunmap(&perfcnt->mapping->obj->base.base, &map);
perfcnt->buf = NULL;
panfrost_gem_close(&perfcnt->mapping->obj->base.base, file_priv);
panfrost_mmu_as_put(pfdev, perfcnt->mapping->mmu);
panfrost_gem_mapping_put(perfcnt->mapping);
perfcnt->mapping = NULL;
pm_runtime_put_autosuspend(pfdev->base.dev);
return 0;
}
int panfrost_ioctl_perfcnt_enable(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct panfrost_device *pfdev = to_panfrost_device(dev);
struct panfrost_perfcnt *perfcnt = pfdev->perfcnt;
struct drm_panfrost_perfcnt_enable *req = data;
int ret;
ret = panfrost_unstable_ioctl_check();
if (ret)
return ret;
/* Only Bifrost GPUs have 2 set of counters. */
if (req->counterset > (panfrost_model_is_bifrost(pfdev) ? 1 : 0))
return -EINVAL;
mutex_lock(&perfcnt->lock);
if (req->enable)
ret = panfrost_perfcnt_enable_locked(pfdev, file_priv,
req->counterset);
else
ret = panfrost_perfcnt_disable_locked(pfdev, file_priv);
mutex_unlock(&perfcnt->lock);
return ret;
}
int panfrost_ioctl_perfcnt_dump(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct panfrost_device *pfdev = to_panfrost_device(dev);
struct panfrost_perfcnt *perfcnt = pfdev->perfcnt;
struct drm_panfrost_perfcnt_dump *req = data;
void __user *user_ptr = (void __user *)(uintptr_t)req->buf_ptr;
int ret;
ret = panfrost_unstable_ioctl_check();
if (ret)
return ret;
mutex_lock(&perfcnt->lock);
if (perfcnt->user != file_priv->driver_priv) {
ret = -EINVAL;
goto out;
}
ret = panfrost_perfcnt_dump_locked(pfdev);
if (ret)
goto out;
if (copy_to_user(user_ptr, perfcnt->buf, perfcnt->bosize))
ret = -EFAULT;
out:
mutex_unlock(&perfcnt->lock);
return ret;
}
void panfrost_perfcnt_close(struct drm_file *file_priv)
{
struct panfrost_file_priv *pfile = file_priv->driver_priv;
struct panfrost_device *pfdev = pfile->pfdev;
struct panfrost_perfcnt *perfcnt = pfdev->perfcnt;
pm_runtime_get_sync(pfdev->base.dev);
mutex_lock(&perfcnt->lock);
if (perfcnt->user == pfile)
panfrost_perfcnt_disable_locked(pfdev, file_priv);
mutex_unlock(&perfcnt->lock);
pm_runtime_put_autosuspend(pfdev->base.dev);
}
int panfrost_perfcnt_init(struct panfrost_device *pfdev)
{
struct panfrost_perfcnt *perfcnt;
size_t size;
if (panfrost_has_hw_feature(pfdev, HW_FEATURE_V4)) {
unsigned int ncoregroups;
ncoregroups = hweight64(pfdev->features.l2_present);
size = ncoregroups * BLOCKS_PER_COREGROUP *
COUNTERS_PER_BLOCK * BYTES_PER_COUNTER;
} else {
unsigned int nl2c, ncores;
/*
* TODO: define a macro to extract the number of l2 caches from
* mem_features.
*/
nl2c = ((pfdev->features.mem_features >> 8) & GENMASK(3, 0)) + 1;
/*
* shader_present might be sparse, but the counters layout
* forces to dump unused regions too, hence the fls64() call
* instead of hweight64().
*/
ncores = fls64(pfdev->features.shader_present);
/*
* There's always one JM and one Tiler block, hence the '+ 2'
* here.
*/
size = (nl2c + ncores + 2) *
COUNTERS_PER_BLOCK * BYTES_PER_COUNTER;
}
perfcnt = devm_kzalloc(pfdev->base.dev, sizeof(*perfcnt), GFP_KERNEL);
if (!perfcnt)
return -ENOMEM;
perfcnt->bosize = size;
/* Start with everything disabled. */
gpu_write(pfdev, GPU_PERFCNT_CFG,
GPU_PERFCNT_CFG_MODE(GPU_PERFCNT_CFG_MODE_OFF));
gpu_write(pfdev, GPU_PRFCNT_JM_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_SHADER_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_MMU_L2_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0);
init_completion(&perfcnt->dump_comp);
mutex_init(&perfcnt->lock);
pfdev->perfcnt = perfcnt;
return 0;
}
void panfrost_perfcnt_fini(struct panfrost_device *pfdev)
{
/* Disable everything before leaving. */
gpu_write(pfdev, GPU_PERFCNT_CFG,
GPU_PERFCNT_CFG_MODE(GPU_PERFCNT_CFG_MODE_OFF));
gpu_write(pfdev, GPU_PRFCNT_JM_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_SHADER_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_MMU_L2_EN, 0);
gpu_write(pfdev, GPU_PRFCNT_TILER_EN, 0);
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2019 Collabora Ltd */
#ifndef __PANFROST_PERFCNT_H__
#define __PANFROST_PERFCNT_H__
#include "panfrost_device.h"
void panfrost_perfcnt_sample_done(struct panfrost_device *pfdev);
void panfrost_perfcnt_clean_cache_done(struct panfrost_device *pfdev);
int panfrost_perfcnt_init(struct panfrost_device *pfdev);
void panfrost_perfcnt_fini(struct panfrost_device *pfdev);
void panfrost_perfcnt_close(struct drm_file *file_priv);
int panfrost_ioctl_perfcnt_enable(struct drm_device *dev, void *data,
struct drm_file *file_priv);
int panfrost_ioctl_perfcnt_dump(struct drm_device *dev, void *data,
struct drm_file *file_priv);
#endif
@@ -0,0 +1,382 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2018 Marty E. Plummer <hanetzer@startmail.com> */
/* Copyright 2019 Linaro, Ltd, Rob Herring <robh@kernel.org> */
/*
* Register definitions based on mali_midg_regmap.h
* (C) COPYRIGHT 2010-2018 ARM Limited. All rights reserved.
*/
#ifndef __PANFROST_REGS_H__
#define __PANFROST_REGS_H__
#define GPU_ID 0x00
#define GPU_L2_FEATURES 0x004 /* (RO) Level 2 cache features */
#define GPU_CORE_FEATURES 0x008 /* (RO) Shader Core Features */
#define GPU_TILER_FEATURES 0x00C /* (RO) Tiler Features */
#define GPU_MEM_FEATURES 0x010 /* (RO) Memory system features */
#define GROUPS_L2_COHERENT BIT(0) /* Cores groups are l2 coherent */
#define GPU_MMU_FEATURES 0x014 /* (RO) MMU features */
#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 0x018 /* (RO) Address space slots present */
#define GPU_JS_PRESENT 0x01C /* (RO) Job slots present */
#define GPU_INT_RAWSTAT 0x20
#define GPU_INT_CLEAR 0x24
#define GPU_INT_MASK 0x28
#define GPU_INT_STAT 0x2c
#define GPU_IRQ_FAULT BIT(0)
#define GPU_IRQ_MULTIPLE_FAULT BIT(7)
#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_PERFCNT_SAMPLE_COMPLETED BIT(16)
#define GPU_IRQ_CLEAN_CACHES_COMPLETED BIT(17)
#define GPU_IRQ_MASK_ALL \
(GPU_IRQ_FAULT |\
GPU_IRQ_MULTIPLE_FAULT |\
GPU_IRQ_RESET_COMPLETED |\
GPU_IRQ_POWER_CHANGED |\
GPU_IRQ_POWER_CHANGED_ALL |\
GPU_IRQ_PERFCNT_SAMPLE_COMPLETED |\
GPU_IRQ_CLEAN_CACHES_COMPLETED)
#define GPU_IRQ_MASK_ERROR \
( \
GPU_IRQ_FAULT |\
GPU_IRQ_MULTIPLE_FAULT)
#define GPU_CMD 0x30
#define GPU_CMD_SOFT_RESET 0x01
#define GPU_CMD_HARD_RESET 0x02
#define GPU_CMD_PERFCNT_CLEAR 0x03
#define GPU_CMD_PERFCNT_SAMPLE 0x04
#define GPU_CMD_CYCLE_COUNT_START 0x05
#define GPU_CMD_CYCLE_COUNT_STOP 0x06
#define GPU_CMD_CLEAN_CACHES 0x07
#define GPU_CMD_CLEAN_INV_CACHES 0x08
#define GPU_STATUS 0x34
#define GPU_STATUS_PRFCNT_ACTIVE BIT(2)
#define GPU_LATEST_FLUSH_ID 0x38
#define GPU_PWR_KEY 0x50 /* (WO) Power manager key register */
#define GPU_PWR_KEY_UNLOCK 0x2968A819
#define GPU_PWR_OVERRIDE0 0x54 /* (RW) Power manager override settings */
#define GPU_PWR_OVERRIDE1 0x58 /* (RW) Power manager override settings */
#define GPU_FAULT_STATUS 0x3C
#define GPU_FAULT_ADDRESS_LO 0x40
#define GPU_FAULT_ADDRESS_HI 0x44
#define GPU_PERFCNT_BASE_LO 0x60
#define GPU_PERFCNT_BASE_HI 0x64
#define GPU_PERFCNT_CFG 0x68
#define GPU_PERFCNT_CFG_MODE(x) (x)
#define GPU_PERFCNT_CFG_MODE_OFF 0
#define GPU_PERFCNT_CFG_MODE_MANUAL 1
#define GPU_PERFCNT_CFG_MODE_TILE 2
#define GPU_PERFCNT_CFG_AS(x) ((x) << 4)
#define GPU_PERFCNT_CFG_SETSEL(x) ((x) << 8)
#define GPU_PRFCNT_JM_EN 0x6c
#define GPU_PRFCNT_SHADER_EN 0x70
#define GPU_PRFCNT_TILER_EN 0x74
#define GPU_PRFCNT_MMU_L2_EN 0x7c
#define GPU_CYCLE_COUNT_LO 0x90
#define GPU_CYCLE_COUNT_HI 0x94
#define GPU_TIMESTAMP_LO 0x98
#define GPU_TIMESTAMP_HI 0x9C
#define GPU_THREAD_MAX_THREADS 0x0A0 /* (RO) Maximum number of threads per core */
#define GPU_THREAD_MAX_WORKGROUP_SIZE 0x0A4 /* (RO) Maximum workgroup size */
#define GPU_THREAD_MAX_BARRIER_SIZE 0x0A8 /* (RO) Maximum threads waiting at a barrier */
#define GPU_THREAD_FEATURES 0x0AC /* (RO) Thread features */
#define GPU_THREAD_TLS_ALLOC 0x310 /* (RO) Number of threads per core that
* TLS must be allocated for */
#define GPU_TEXTURE_FEATURES(n) (0x0B0 + ((n) * 4))
#define GPU_JS_FEATURES(n) (0x0C0 + ((n) * 4))
#define GPU_AFBC_FEATURES (0x4C) /* (RO) AFBC support on Bifrost */
#define GPU_SHADER_PRESENT_LO 0x100 /* (RO) Shader core present bitmap, low word */
#define GPU_SHADER_PRESENT_HI 0x104 /* (RO) Shader core present bitmap, high word */
#define GPU_TILER_PRESENT_LO 0x110 /* (RO) Tiler core present bitmap, low word */
#define GPU_TILER_PRESENT_HI 0x114 /* (RO) Tiler core present bitmap, high word */
#define GPU_L2_PRESENT_LO 0x120 /* (RO) Level 2 cache present bitmap, low word */
#define GPU_L2_PRESENT_HI 0x124 /* (RO) Level 2 cache present bitmap, high word */
/* GPU_COHERENCY_FEATURES is a bitmask of BIT(COHERENCY_xxx) values encoding the
* set of supported coherency protocols. GPU_COHERENCY_ENABLE is passed a
* COHERENCY_xxx value.
*/
#define GPU_COHERENCY_FEATURES 0x300 /* (RO) Coherency features present */
#define GPU_COHERENCY_ENABLE 0x304 /* (RW) Coherency protocol selection */
#define COHERENCY_ACE_LITE 0
#define COHERENCY_ACE 1
#define COHERENCY_NONE 31
#define GPU_STACK_PRESENT_LO 0xE00 /* (RO) Core stack present bitmap, low word */
#define GPU_STACK_PRESENT_HI 0xE04 /* (RO) Core stack present bitmap, high word */
#define SHADER_READY_LO 0x140 /* (RO) Shader core ready bitmap, low word */
#define SHADER_READY_HI 0x144 /* (RO) Shader core ready bitmap, high word */
#define TILER_READY_LO 0x150 /* (RO) Tiler core ready bitmap, low word */
#define TILER_READY_HI 0x154 /* (RO) Tiler core ready bitmap, high word */
#define L2_READY_LO 0x160 /* (RO) Level 2 cache ready bitmap, low word */
#define L2_READY_HI 0x164 /* (RO) Level 2 cache ready bitmap, high word */
#define STACK_READY_LO 0xE10 /* (RO) Core stack ready bitmap, low word */
#define STACK_READY_HI 0xE14 /* (RO) Core stack ready bitmap, high word */
#define SHADER_PWRON_LO 0x180 /* (WO) Shader core power on bitmap, low word */
#define SHADER_PWRON_HI 0x184 /* (WO) Shader core power on bitmap, high word */
#define TILER_PWRON_LO 0x190 /* (WO) Tiler core power on bitmap, low word */
#define TILER_PWRON_HI 0x194 /* (WO) Tiler core power on bitmap, high word */
#define L2_PWRON_LO 0x1A0 /* (WO) Level 2 cache power on bitmap, low word */
#define L2_PWRON_HI 0x1A4 /* (WO) Level 2 cache power on bitmap, high word */
#define STACK_PWRON_LO 0xE20 /* (RO) Core stack power on bitmap, low word */
#define STACK_PWRON_HI 0xE24 /* (RO) Core stack power on bitmap, high word */
#define SHADER_PWROFF_LO 0x1C0 /* (WO) Shader core power off bitmap, low word */
#define SHADER_PWROFF_HI 0x1C4 /* (WO) Shader core power off bitmap, high word */
#define TILER_PWROFF_LO 0x1D0 /* (WO) Tiler core power off bitmap, low word */
#define TILER_PWROFF_HI 0x1D4 /* (WO) Tiler core power off bitmap, high word */
#define L2_PWROFF_LO 0x1E0 /* (WO) Level 2 cache power off bitmap, low word */
#define L2_PWROFF_HI 0x1E4 /* (WO) Level 2 cache power off bitmap, high word */
#define STACK_PWROFF_LO 0xE30 /* (RO) Core stack power off bitmap, low word */
#define STACK_PWROFF_HI 0xE34 /* (RO) Core stack power off bitmap, high word */
#define SHADER_PWRTRANS_LO 0x200 /* (RO) Shader core power transition bitmap, low word */
#define SHADER_PWRTRANS_HI 0x204 /* (RO) Shader core power transition bitmap, high word */
#define TILER_PWRTRANS_LO 0x210 /* (RO) Tiler core power transition bitmap, low word */
#define TILER_PWRTRANS_HI 0x214 /* (RO) Tiler core power transition bitmap, high word */
#define L2_PWRTRANS_LO 0x220 /* (RO) Level 2 cache power transition bitmap, low word */
#define L2_PWRTRANS_HI 0x224 /* (RO) Level 2 cache power transition bitmap, high word */
#define STACK_PWRTRANS_LO 0xE40 /* (RO) Core stack power transition bitmap, low word */
#define STACK_PWRTRANS_HI 0xE44 /* (RO) Core stack power transition bitmap, high word */
#define SHADER_PWRACTIVE_LO 0x240 /* (RO) Shader core active bitmap, low word */
#define SHADER_PWRACTIVE_HI 0x244 /* (RO) Shader core active bitmap, high word */
#define TILER_PWRACTIVE_LO 0x250 /* (RO) Tiler core active bitmap, low word */
#define TILER_PWRACTIVE_HI 0x254 /* (RO) Tiler core active bitmap, high word */
#define L2_PWRACTIVE_LO 0x260 /* (RO) Level 2 cache active bitmap, low word */
#define L2_PWRACTIVE_HI 0x264 /* (RO) Level 2 cache active bitmap, high word */
#define GPU_JM_CONFIG 0xF00 /* (RW) Job Manager configuration register (Implementation specific register) */
#define GPU_SHADER_CONFIG 0xF04 /* (RW) Shader core configuration settings (Implementation specific register) */
#define GPU_TILER_CONFIG 0xF08 /* (RW) Tiler core configuration settings (Implementation specific register) */
#define GPU_L2_MMU_CONFIG 0xF0C /* (RW) Configuration of the L2 cache and MMU (Implementation specific register) */
/* L2_MMU_CONFIG register */
#define L2_MMU_CONFIG_ALLOW_SNOOP_DISPARITY_SHIFT 23
#define L2_MMU_CONFIG_ALLOW_SNOOP_DISPARITY (0x1 << L2_MMU_CONFIG_ALLOW_SNOOP_DISPARITY_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT 24
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_READS (0x3 << L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_OCTANT (0x1 << L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_QUARTER (0x2 << L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_HALF (0x3 << L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT 26
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES (0x3 << L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_OCTANT (0x1 << L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_QUARTER (0x2 << L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT)
#define L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_HALF (0x3 << L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT)
#define L2_MMU_CONFIG_3BIT_LIMIT_EXTERNAL_READS_SHIFT 12
#define L2_MMU_CONFIG_3BIT_LIMIT_EXTERNAL_READS (0x7 << L2_MMU_CONFIG_LIMIT_EXTERNAL_READS_SHIFT)
#define L2_MMU_CONFIG_3BIT_LIMIT_EXTERNAL_WRITES_SHIFT 15
#define L2_MMU_CONFIG_3BIT_LIMIT_EXTERNAL_WRITES (0x7 << L2_MMU_CONFIG_LIMIT_EXTERNAL_WRITES_SHIFT)
/* SHADER_CONFIG register */
#define SC_ALT_COUNTERS BIT(3)
#define SC_OVERRIDE_FWD_PIXEL_KILL BIT(4)
#define SC_SDC_DISABLE_OQ_DISCARD BIT(6)
#define SC_LS_ALLOW_ATTR_TYPES BIT(16)
#define SC_LS_PAUSEBUFFER_DISABLE BIT(16)
#define SC_TLS_HASH_ENABLE BIT(17)
#define SC_LS_ATTR_CHECK_DISABLE BIT(18)
#define SC_ENABLE_TEXGRD_FLAGS BIT(25)
#define SC_VAR_ALGORITHM BIT(29)
/* End SHADER_CONFIG register */
/* TILER_CONFIG register */
#define TC_CLOCK_GATE_OVERRIDE BIT(0)
/* JM_CONFIG register */
#define JM_TIMESTAMP_OVERRIDE BIT(0)
#define JM_CLOCK_GATE_OVERRIDE BIT(1)
#define JM_JOB_THROTTLE_ENABLE BIT(2)
#define JM_JOB_THROTTLE_LIMIT_SHIFT 3
#define JM_MAX_JOB_THROTTLE_LIMIT 0x3F
#define JM_FORCE_COHERENCY_FEATURES_SHIFT 2
#define JM_IDVS_GROUP_SIZE_SHIFT 16
#define JM_DEFAULT_IDVS_GROUP_SIZE 0xF
#define JM_MAX_IDVS_GROUP_SIZE 0x3F
/* Job Control regs */
#define JOB_INT_RAWSTAT 0x1000
#define JOB_INT_CLEAR 0x1004
#define JOB_INT_MASK 0x1008
#define JOB_INT_STAT 0x100c
#define JOB_INT_JS_STATE 0x1010
#define JOB_INT_THROTTLE 0x1014
#define MK_JS_MASK(j) (0x10001 << (j))
#define JOB_INT_MASK_ERR(j) BIT((j) + 16)
#define JOB_INT_MASK_DONE(j) BIT(j)
#define JS_BASE 0x1800
#define JS_SLOT_STRIDE 0x80
#define JS_HEAD_LO(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x00)
#define JS_HEAD_HI(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x04)
#define JS_TAIL_LO(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x08)
#define JS_TAIL_HI(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x0c)
#define JS_AFFINITY_LO(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x10)
#define JS_AFFINITY_HI(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x14)
#define JS_CONFIG(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x18)
#define JS_XAFFINITY(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x1c)
#define JS_COMMAND(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x20)
#define JS_STATUS(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x24)
#define JS_HEAD_NEXT_LO(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x40)
#define JS_HEAD_NEXT_HI(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x44)
#define JS_AFFINITY_NEXT_LO(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x50)
#define JS_AFFINITY_NEXT_HI(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x54)
#define JS_CONFIG_NEXT(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x58)
#define JS_COMMAND_NEXT(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x60)
#define JS_FLUSH_ID_NEXT(n) (JS_BASE + ((n) * JS_SLOT_STRIDE) + 0x70)
/* Possible values of JS_CONFIG and JS_CONFIG_NEXT registers */
#define JS_CONFIG_START_FLUSH_CLEAN BIT(8)
#define JS_CONFIG_START_FLUSH_CLEAN_INVALIDATE (3u << 8)
#define JS_CONFIG_START_MMU BIT(10)
#define JS_CONFIG_JOB_CHAIN_FLAG BIT(11)
#define JS_CONFIG_END_FLUSH_CLEAN BIT(12)
#define JS_CONFIG_END_FLUSH_CLEAN_INVALIDATE (3u << 12)
#define JS_CONFIG_ENABLE_FLUSH_REDUCTION BIT(14)
#define JS_CONFIG_DISABLE_DESCRIPTOR_WR_BK BIT(15)
#define JS_CONFIG_THREAD_PRI(n) ((n) << 16)
#define JS_COMMAND_NOP 0x00
#define JS_COMMAND_START 0x01
#define JS_COMMAND_SOFT_STOP 0x02 /* Gently stop processing a job chain */
#define JS_COMMAND_HARD_STOP 0x03 /* Rudely stop processing a job chain */
#define JS_COMMAND_SOFT_STOP_0 0x04 /* Execute SOFT_STOP if JOB_CHAIN_FLAG is 0 */
#define JS_COMMAND_HARD_STOP_0 0x05 /* Execute HARD_STOP if JOB_CHAIN_FLAG is 0 */
#define JS_COMMAND_SOFT_STOP_1 0x06 /* Execute SOFT_STOP if JOB_CHAIN_FLAG is 1 */
#define JS_COMMAND_HARD_STOP_1 0x07 /* Execute HARD_STOP if JOB_CHAIN_FLAG is 1 */
/* MMU regs */
#define MMU_INT_RAWSTAT 0x2000
#define MMU_INT_CLEAR 0x2004
#define MMU_INT_MASK 0x2008
#define MMU_INT_STAT 0x200c
/* AS_COMMAND register commands */
#define AS_COMMAND_NOP 0x00 /* NOP Operation */
#define AS_COMMAND_UPDATE 0x01 /* Broadcasts the values in AS_TRANSTAB and ASn_MEMATTR to all MMUs */
#define AS_COMMAND_LOCK 0x02 /* Issue a lock region command to all MMUs */
#define AS_COMMAND_UNLOCK 0x03 /* Issue a flush region command to all MMUs */
#define AS_COMMAND_FLUSH 0x04 /* Flush all L2 caches then issue a flush region command to all MMUs
(deprecated - only for use with T60x) */
#define AS_COMMAND_FLUSH_PT 0x04 /* Flush all L2 caches then issue a flush region command to all MMUs */
#define AS_COMMAND_FLUSH_MEM 0x05 /* Wait for memory accesses to complete, flush all the L1s cache then
flush all L2 caches then issue a flush region command to all MMUs */
#define MMU_BASE 0x2400
#define MMU_AS_SHIFT 0x06
#define MMU_AS(as) (MMU_BASE + ((as) << MMU_AS_SHIFT))
#define AS_TRANSTAB_LO(as) (MMU_AS(as) + 0x00) /* (RW) Translation Table Base Address for address space n, low word */
#define AS_TRANSTAB_HI(as) (MMU_AS(as) + 0x04) /* (RW) Translation Table Base Address for address space n, high word */
#define AS_MEMATTR_LO(as) (MMU_AS(as) + 0x08) /* (RW) Memory attributes for address space n, low word. */
#define AS_MEMATTR_HI(as) (MMU_AS(as) + 0x0C) /* (RW) Memory attributes for address space n, high word. */
#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_LO(as) (MMU_AS(as) + 0x10) /* (RW) Lock region address for address space n, low word */
#define AS_LOCKADDR_HI(as) (MMU_AS(as) + 0x14) /* (RW) Lock region address for address space n, high word */
#define AS_COMMAND(as) (MMU_AS(as) + 0x18) /* (WO) MMU command register for address space n */
#define AS_FAULTSTATUS(as) (MMU_AS(as) + 0x1C) /* (RO) MMU fault status register for address space n */
#define AS_FAULTADDRESS_LO(as) (MMU_AS(as) + 0x20) /* (RO) Fault Address for address space n, low word */
#define AS_FAULTADDRESS_HI(as) (MMU_AS(as) + 0x24) /* (RO) Fault Address for address space n, high word */
#define AS_STATUS(as) (MMU_AS(as) + 0x28) /* (RO) Status flags for address space n */
/* Additional Bifrost AS registers */
#define AS_TRANSCFG_LO(as) (MMU_AS(as) + 0x30) /* (RW) Translation table configuration for address space n, low word */
#define AS_TRANSCFG_HI(as) (MMU_AS(as) + 0x34) /* (RW) Translation table configuration for address space n, high word */
#define AS_TRANSCFG_ADRMODE_LEGACY (0 << 0)
#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_LO(as) (MMU_AS(as) + 0x38) /* (RO) Secondary fault address for address space n, low word */
#define AS_FAULTEXTRA_HI(as) (MMU_AS(as) + 0x3C) /* (RO) Secondary fault address for address space n, high word */
#define MMU_AS_STRIDE (1 << MMU_AS_SHIFT)
/*
* Begin LPAE MMU TRANSTAB register values
*/
#define AS_TRANSTAB_LPAE_ADDR_SPACE_MASK 0xfffffffffffff000
#define AS_TRANSTAB_LPAE_ADRMODE_IDENTITY 0x2
#define AS_TRANSTAB_LPAE_ADRMODE_TABLE 0x3
#define AS_TRANSTAB_LPAE_ADRMODE_MASK 0x3
#define AS_TRANSTAB_LPAE_READ_INNER BIT(2)
#define AS_TRANSTAB_LPAE_SHARE_OUTER BIT(4)
/*
* Begin AARCH64_4K MMU TRANSTAB register values
*/
#define AS_TRANSTAB_AARCH64_4K_ADDR_MASK 0xfffffffffffffff0
#define AS_STATUS_AS_ACTIVE 0x01
#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_LOCK_REGION_MIN_SIZE (1ULL << 15)
#define gpu_write(dev, reg, data) writel(data, dev->iomem + reg)
#define gpu_read(dev, reg) readl(dev->iomem + reg)
#endif