drivers: gpu: mali: port r51p0

This commit is contained in:
Mustafa Gökmen 2024-09-25 17:54:55 +03:00
parent cada736b53
commit 1589ebebbd
No known key found for this signature in database
GPG key ID: 3204D8100CFF21ED
453 changed files with 182826 additions and 51 deletions

View file

@ -3278,7 +3278,8 @@ CONFIG_MALI_THEX=y
# CONFIG_MALI_BIFROST_R19P0_Q is not set
# CONFIG_MALI_BIFROST_R32P1 is not set
# CONFIG_MALI_BIFROST_R38P1 is not set
CONFIG_MALI_BIFROST_R38P2=y
# CONFIG_MALI_BIFROST_R38P2 is not set
CONFIG_MALI_BIFROST_R51P0=y
# CONFIG_MALI_2MB_ALLOC is not set
# CONFIG_MALI_JOB_DUMP is not set
CONFIG_MALI_REAL_HW=y

View file

@ -51,4 +51,7 @@ ifeq ($(CONFIG_MALI_THEX),y)
ifeq ($(CONFIG_MALI_BIFROST_R38P2),y)
obj-y += bv_r38p2/
endif
ifeq ($(CONFIG_MALI_BIFROST_R51P0),y)
obj-y += bv_r51p0/
endif
endif

View file

@ -0,0 +1,246 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2012-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
# make $(src) as absolute path if it is not already, by prefixing $(srctree)
# This is to prevent any build issue due to wrong path.
src:=$(if $(patsubst /%,,$(src)),$(srctree)/$(src),$(src))
#
# Prevent misuse when Kernel configurations are not present by default
# in out-of-tree builds
#
ifneq ($(CONFIG_ANDROID),n)
ifeq ($(CONFIG_GPU_TRACEPOINTS),n)
$(error CONFIG_GPU_TRACEPOINTS must be set in Kernel configuration)
endif
endif
ifeq ($(CONFIG_DMA_SHARED_BUFFER),n)
$(error CONFIG_DMA_SHARED_BUFFER must be set in Kernel configuration)
endif
ifeq ($(CONFIG_PM_DEVFREQ),n)
$(error CONFIG_PM_DEVFREQ must be set in Kernel configuration)
endif
ifeq ($(CONFIG_DEVFREQ_THERMAL),n)
$(error CONFIG_DEVFREQ_THERMAL must be set in Kernel configuration)
endif
ifeq ($(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND),n)
$(error CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND must be set in Kernel configuration)
endif
ifeq ($(CONFIG_FW_LOADER), n)
$(error CONFIG_FW_LOADER must be set in Kernel configuration)
endif
ifeq ($(CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS), y)
ifneq ($(CONFIG_DEBUG_FS), y)
$(error CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS depends on CONFIG_DEBUG_FS to be set in Kernel configuration)
endif
endif
ifeq ($(CONFIG_MALI_FENCE_DEBUG), y)
ifneq ($(CONFIG_SYNC_FILE), y)
$(error CONFIG_MALI_FENCE_DEBUG depends on CONFIG_SYNC_FILE to be set in Kernel configuration)
endif
endif
#
# Configurations
#
# Driver version string which is returned to userspace via an ioctl
MALI_RELEASE_NAME ?= '"r51p0-00eac0"'
# Set up defaults if not defined by build system
ifeq ($(CONFIG_MALI_DEBUG), y)
MALI_UNIT_TEST = 1
MALI_CUSTOMER_RELEASE ?= 0
else
MALI_UNIT_TEST ?= 0
MALI_CUSTOMER_RELEASE ?= 1
endif
MALI_COVERAGE ?= 0
CONFIG_MALI_PLATFORM_NAME ?= "exynos"
# Kconfig passes in the name with quotes for in-tree builds - remove them.
MALI_PLATFORM_DIR := $(shell echo $(CONFIG_MALI_PLATFORM_NAME))
ifeq ($(CONFIG_MALI_CSF_SUPPORT),y)
MALI_JIT_PRESSURE_LIMIT_BASE = 0
MALI_USE_CSF = 1
else
MALI_JIT_PRESSURE_LIMIT_BASE ?= 1
MALI_USE_CSF ?= 0
endif
ifneq ($(CONFIG_MALI_KUTF), n)
MALI_KERNEL_TEST_API ?= 1
else
MALI_KERNEL_TEST_API ?= 0
endif
# Experimental features (corresponding -D definition should be appended to
# ccflags-y below, e.g. for MALI_EXPERIMENTAL_FEATURE,
# -DMALI_EXPERIMENTAL_FEATURE=$(MALI_EXPERIMENTAL_FEATURE) should be appended)
#
# Experimental features must default to disabled, e.g.:
# MALI_EXPERIMENTAL_FEATURE ?= 0
#
# ccflags
#
ccflags-y = \
-DMALI_CUSTOMER_RELEASE=$(MALI_CUSTOMER_RELEASE) \
-DMALI_USE_CSF=$(MALI_USE_CSF) \
-DMALI_KERNEL_TEST_API=$(MALI_KERNEL_TEST_API) \
-DMALI_UNIT_TEST=$(MALI_UNIT_TEST) \
-DMALI_COVERAGE=$(MALI_COVERAGE) \
-DMALI_RELEASE_NAME=$(MALI_RELEASE_NAME) \
-DMALI_JIT_PRESSURE_LIMIT_BASE=$(MALI_JIT_PRESSURE_LIMIT_BASE) \
-DMALI_PLATFORM_DIR=$(MALI_PLATFORM_DIR)
ifeq ($(KBUILD_EXTMOD),)
# in-tree
ccflags-y +=-DMALI_KBASE_PLATFORM_PATH=../../$(src)/../$(CONFIG_MALI_PLATFORM_NAME)
else
# out-of-tree
ccflags-y +=-DMALI_KBASE_PLATFORM_PATH=$(src)/../$(CONFIG_MALI_PLATFORM_NAME)
endif
ccflags-y += \
-I$(srctree)/include/linux \
-I$(srctree)/drivers/staging/android \
-I$(src) \
-I$(src)/../$(MALI_PLATFORM_DIR) \
-I$(src)/../../../base \
-I$(src)/../../../../include
subdir-ccflags-y += $(ccflags-y)
#
# Kernel Modules
#
obj-$(CONFIG_MALI_MIDGARD) += mali_kbase.o
#obj-$(CONFIG_MALI_ARBITRATION) += ../arbitration/
obj-$(CONFIG_MALI_KUTF) += tests/
mali_kbase-y := \
mali_kbase_cache_policy.o \
mali_kbase_ccswe.o \
mali_kbase_mem.o \
mali_kbase_reg_track.o \
mali_kbase_mem_migrate.o \
mali_kbase_mem_pool_group.o \
mali_kbase_native_mgm.o \
mali_kbase_ctx_sched.o \
mali_kbase_gpuprops.o \
mali_kbase_pm.o \
mali_kbase_config.o \
mali_kbase_kinstr_prfcnt.o \
mali_kbase_softjobs.o \
mali_kbase_hw.o \
mali_kbase_debug.o \
mali_kbase_gpu_memory_debugfs.o \
mali_kbase_mem_linux.o \
mali_kbase_core_linux.o \
mali_kbase_mem_profile_debugfs.o \
mali_kbase_disjoint_events.o \
mali_kbase_debug_mem_view.o \
mali_kbase_debug_mem_zones.o \
mali_kbase_debug_mem_allocs.o \
mali_kbase_smc.o \
mali_kbase_mem_pool.o \
mali_kbase_mem_pool_debugfs.o \
mali_kbase_debugfs_helper.o \
mali_kbase_as_fault_debugfs.o \
mali_kbase_regs_history_debugfs.o \
mali_kbase_dvfs_debugfs.o \
mali_power_gpu_frequency_trace.o \
mali_kbase_trace_gpu_mem.o \
mali_kbase_pbha.o
mali_kbase-$(CONFIG_DEBUG_FS) += mali_kbase_pbha_debugfs.o
mali_kbase-$(CONFIG_MALI_CINSTR_GWT) += mali_kbase_gwt.o
mali_kbase-$(CONFIG_SYNC_FILE) += \
mali_kbase_fence_ops.o \
mali_kbase_sync_file.o \
mali_kbase_sync_common.o
mali_kbase-$(CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD) += \
mali_power_gpu_work_period_trace.o \
mali_kbase_gpu_metrics.o
ifneq ($(CONFIG_MALI_CSF_SUPPORT),y)
mali_kbase-y += \
mali_kbase_jm.o \
mali_kbase_dummy_job_wa.o \
mali_kbase_debug_job_fault.o \
mali_kbase_event.o \
mali_kbase_jd.o \
mali_kbase_jd_debugfs.o \
mali_kbase_js.o \
mali_kbase_js_ctx_attr.o \
mali_kbase_kinstr_jm.o
mali_kbase-$(CONFIG_SYNC_FILE) += \
mali_kbase_fence_ops.o \
mali_kbase_fence.o
endif
INCLUDE_SUBDIR = \
$(src)/arbiter/Kbuild \
$(src)/context/Kbuild \
$(src)/debug/Kbuild \
$(src)/device/Kbuild \
$(src)/backend/gpu/Kbuild \
$(src)/mmu/Kbuild \
$(src)/tl/Kbuild \
$(src)/hwcnt/Kbuild \
$(src)/gpu/Kbuild \
$(src)/hw_access/Kbuild \
$(src)/thirdparty/Kbuild \
$(src)/../$(MALI_PLATFORM_DIR)/Kbuild
ifeq ($(CONFIG_MALI_CSF_SUPPORT),y)
INCLUDE_SUBDIR += $(src)/csf/Kbuild
endif
ifeq ($(CONFIG_MALI_DEVFREQ),y)
ifeq ($(CONFIG_DEVFREQ_THERMAL),y)
INCLUDE_SUBDIR += $(src)/ipa/Kbuild
endif
endif
ifeq ($(KBUILD_EXTMOD),)
# in-tree
-include $(INCLUDE_SUBDIR)
else
# out-of-tree
include $(INCLUDE_SUBDIR)
endif

View file

@ -0,0 +1,400 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2012-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
menuconfig MALI_MIDGARD
tristate "Mali Midgard series support"
select GPU_TRACEPOINTS if ANDROID
select DMA_SHARED_BUFFER
select PM_DEVFREQ
select DEVFREQ_THERMAL
select FW_LOADER
default m if SOC_EXYNOS2100
default y if SOC_EXYNOS9830
default y
help
Enable this option to build support for a ARM Mali Midgard GPU.
To compile this driver as a module, choose M here:
this will generate a single module, called mali_kbase.
if MALI_MIDGARD
config MALI_PLATFORM_NAME
depends on MALI_MIDGARD
string "Platform name"
default "exynos"
help
Enter the name of the desired platform configuration directory to
include in the build. 'platform/$(MALI_PLATFORM_NAME)/Kbuild' must
exist.
config MALI_REAL_HW
bool "Enable build of Mali kernel driver for real HW"
depends on MALI_MIDGARD
default y
help
This is the default HW backend.
config MALI_NO_MALI
bool "Enable build of Mali kernel driver for No Mali"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
This can be used to test the driver in a simulated environment
whereby the hardware is not physically present. If the hardware is physically
present it will not be used. This can be used to test the majority of the
driver without needing actual hardware or for software benchmarking.
All calls to the simulated hardware will complete immediately as if the hardware
completed the task.
config MALI_NO_MALI_DEFAULT_GPU
string "Default GPU for No Mali"
depends on MALI_NO_MALI
default "tMIx"
help
This option sets the default GPU to identify as for No Mali builds.
config MALI_IS_FPGA
bool "Enable build of Mali kernel driver for FPGA"
depends on MALI_MIDGARD
default n
help
This is the default HW backend.
menu "Platform specific options"
source "drivers/gpu/arm/exynos/Kconfig"
endmenu
config MALI_CSF_SUPPORT
bool "Enable Mali CSF based GPU support"
depends on MALI_MIDGARD
default n
help
Enables support for CSF based GPUs.
config MALI_DEVFREQ
bool "Enable devfreq support for Mali"
depends on MALI_MIDGARD && PM_DEVFREQ
select DEVFREQ_GOV_SIMPLE_ONDEMAND
default n if SOC_EXYNOS2100
default n if SOC_EXYNOS9830
default n
help
Support devfreq for Mali.
Using the devfreq framework and, by default, the simple on-demand
governor, the frequency of Mali will be dynamically selected from the
available OPPs.
config MALI_MIDGARD_DVFS
bool "Enable legacy DVFS"
depends on MALI_MIDGARD && !MALI_DEVFREQ
default n
help
Choose this option to enable legacy DVFS in the Mali Midgard DDK.
config MALI_GATOR_SUPPORT
bool "Enable Streamline tracing support"
depends on MALI_MIDGARD
default y
help
Enables kbase tracing used by the Arm Streamline Performance Analyzer.
The tracepoints are used to derive GPU activity charts in Streamline.
config MALI_MIDGARD_ENABLE_TRACE
bool "Enable kbase tracing"
depends on MALI_MIDGARD
default y if MALI_DEBUG
default y
help
Enables tracing in kbase. Trace log available through
the "mali_trace" debugfs file, when the CONFIG_DEBUG_FS is enabled
config MALI_ARBITER_SUPPORT
bool "Enable arbiter support for Mali"
depends on MALI_MIDGARD
default n
help
Enable support for the arbiter interface in the driver.
This allows an external arbiter to manage driver access
to GPU hardware in a virtualized environment
If unsure, say N.
config MALI_DMA_BUF_MAP_ON_DEMAND
bool "Enable map imported dma-bufs on demand"
depends on MALI_MIDGARD
default n
help
This option will cause kbase to set up the GPU mapping of imported
dma-buf when needed to run atoms. This is the legacy behavior.
This is intended for testing and the option will get removed in the
future.
config MALI_DMA_BUF_LEGACY_COMPAT
bool "Enable legacy compatibility cache flush on dma-buf map"
depends on MALI_MIDGARD && !MALI_DMA_BUF_MAP_ON_DEMAND
default y
help
This option enables compatibility with legacy dma-buf mapping
behavior, then the dma-buf is mapped on import, by adding cache
maintenance where MALI_DMA_BUF_MAP_ON_DEMAND would do the mapping,
including a cache flush.
This option might work-around issues related to missing cache
flushes in other drivers. This only has an effect for clients using
UK 11.18 or older. For later UK versions it is not possible.
config MALI_CORESIGHT
depends on MALI_MIDGARD && MALI_CSF_SUPPORT && !MALI_NO_MALI
bool "Enable Kbase CoreSight tracing support"
default n
menuconfig MALI_EXPERT
depends on MALI_MIDGARD
bool "Enable Expert Settings"
default y if SOC_EXYNOS2100
default y if SOC_EXYNOS9830
default y
help
Enabling this option and modifying the default settings may produce
a driver with performance or other limitations.
if MALI_EXPERT
config LARGE_PAGE_SUPPORT
bool "Support for 2MB page allocations"
depends on MALI_MIDGARD && MALI_EXPERT
default y
help
Rather than allocating all GPU memory page-by-page, allow the system
to decide whether to attempt to allocate 2MB pages from the kernel.
This reduces TLB pressure.
Note that this option only enables the support for the module parameter
and does not necessarily mean that 2MB pages will be used automatically.
This depends on GPU support.
If in doubt, say Y.
config PAGE_MIGRATION_SUPPORT
bool "Enable support for page migration"
depends on MALI_MIDGARD && MALI_EXPERT
default y
default n if ANDROID
help
Compile in support for page migration.
If set to disabled ('n') then page migration cannot
be enabled at all, and related symbols are not compiled in.
If not set, page migration is compiled in by default, and
if not explicitly enabled or disabled with the insmod parameter,
page migration becomes automatically enabled with large pages.
If in doubt, say Y. To strip out page migration symbols and support,
say N.
config MALI_CORESTACK
bool "Enable support of GPU core stack power control"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Enabling this feature on supported GPUs will let the driver powering
on/off the GPU core stack independently without involving the Power
Domain Controller. This should only be enabled on platforms which
integration of the PDC to the Mali GPU is known to be problematic.
This feature is currently only supported on t-Six and t-HEx GPUs.
If unsure, say N.
comment "Debug options"
depends on MALI_MIDGARD && MALI_EXPERT
config MALI_DEBUG
bool "Enable debug build"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Select this option for increased checking and reporting of errors.
config MALI_FENCE_DEBUG
bool "Enable debug sync fence usage"
depends on MALI_MIDGARD && MALI_EXPERT && SYNC_FILE
default y if MALI_DEBUG
help
Select this option to enable additional checking and reporting on the
use of sync fences in the Mali driver.
This will add a 3s timeout to all sync fence waits in the Mali
driver, so that when work for Mali has been waiting on a sync fence
for a long time a debug message will be printed, detailing what fence
is causing the block, and which dependent Mali atoms are blocked as a
result of this.
The timeout can be changed at runtime through the js_soft_timeout
device attribute, where the timeout is specified in milliseconds.
config MALI_SYSTEM_TRACE
bool "Enable system event tracing support"
depends on MALI_MIDGARD && MALI_EXPERT
default y if MALI_DEBUG
default n
help
Choose this option to enable system trace events for each
kbase event. This is typically used for debugging but has
minimal overhead when not in use. Enable only if you know what
you are doing.
comment "Instrumentation options"
depends on MALI_MIDGARD && MALI_EXPERT
# MALI_SEC_INTEGRATION: Don't set MALI_PRFCNT_SET_ options as choice
# If it's set as a choice, it can cause problems during 9810 build which contains
# older Mali kbase code which contains the same option name as these.
config MALI_PRFCNT_SET_PRIMARY
bool "Primary"
depends on MALI_MIDGARD && MALI_EXPERT
default y
help
Select this option to use primary set of performance counters.
config MALI_PRFCNT_SET_SECONDARY
bool "Secondary"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Select this option to use secondary set of performance counters. Kernel
features that depend on an access to the primary set of counters may
become unavailable. Enabling this option will prevent power management
from working optimally and may cause instrumentation tools to return
bogus results.
If unsure, use MALI_PRFCNT_SET_PRIMARY.
config MALI_PRFCNT_SET_TERTIARY
bool "Tertiary"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Select this option to use tertiary set of performance counters. Kernel
features that depend on an access to the primary set of counters may
become unavailable. Enabling this option will prevent power management
from working optimally and may cause instrumentation tools to return
bogus results.
If unsure, use MALI_PRFCNT_SET_PRIMARY.
config MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS
bool "Enable runtime selection of performance counters set via debugfs"
depends on MALI_MIDGARD && MALI_EXPERT && DEBUG_FS && !MALI_CSF_SUPPORT
default n
help
Select this option to make the secondary set of performance counters
available at runtime via debugfs. Kernel features that depend on an
access to the primary set of counters may become unavailable.
If no runtime debugfs option is set, the build time counter set
choice will be used.
This feature is unsupported and unstable, and may break at any time.
Enabling this option will prevent power management from working
optimally and may cause instrumentation tools to return bogus results.
No validation is done on the debugfs input. Invalid input could cause
performance counter errors. Valid inputs are the values accepted by
the SET_SELECT bits of the PRFCNT_CONFIG register as defined in the
architecture specification.
If unsure, say N.
config MALI_JOB_DUMP
bool "Enable system level support needed for job dumping"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Choose this option to enable system level support needed for
job dumping. This is typically used for instrumentation but has
minimal overhead when not in use. Enable only if you know what
you are doing.
comment "Workarounds"
depends on MALI_MIDGARD && MALI_EXPERT
config MALI_PWRSOFT_765
bool "Enable workaround for PWRSOFT-765"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
PWRSOFT-765 fixes devfreq cooling devices issues. The fix was merged
in kernel v4.10, however if backported into the kernel then this
option must be manually selected.
If using kernel >= v4.10 then say N, otherwise if devfreq cooling
changes have been backported say Y to avoid compilation errors.
config MALI_HW_ERRATA_1485982_NOT_AFFECTED
bool "Disable workaround for KBASE_HW_ISSUE_GPU2017_1336"
depends on MALI_MIDGARD && MALI_EXPERT
default y if SOC_EXYNOS2100
default y if SOC_EXYNOS9830
default y
help
This option disables the default workaround for GPU2017-1336. The
workaround keeps the L2 cache powered up except for powerdown and reset.
The workaround introduces a limitation that will prevent the running of
protected mode content on fully coherent platforms, as the switch to IO
coherency mode requires the L2 to be turned off.
config MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE
bool "Use alternative workaround for KBASE_HW_ISSUE_GPU2017_1336"
depends on MALI_MIDGARD && MALI_EXPERT && !MALI_HW_ERRATA_1485982_NOT_AFFECTED
default n
help
This option uses an alternative workaround for GPU2017-1336. Lowering
the GPU clock to a, platform specific, known good frequency before
powering down the L2 cache. The clock can be specified in the device
tree using the property, opp-mali-errata-1485982. Otherwise the
slowest clock will be selected.
endif
config MALI_ARBITRATION
boolean "Enable Virtualization reference code"
depends on MALI_MIDGARD
default n
help
Enables the build of several reference modules used in the reference
virtualization setup for Mali
If unsure, say N.
config MALI_TRACE_POWER_GPU_WORK_PERIOD
bool "Enable per-application GPU metrics tracepoints"
depends on MALI_MIDGARD
default y
help
This option enables per-application GPU metrics tracepoints.
If unsure, say N.
# source "drivers/gpu/arm/bv_r51p0/tests/Kconfig"
endif

View file

@ -0,0 +1,305 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2010-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
KDIR ?= $(KERNEL_SRC)
M ?= $(shell pwd)
ifeq ($(KDIR),)
$(error Must specify KDIR to point to the kernel to target))
endif
#
# Default configuration values
#
# Dependency resolution is done through statements as Kconfig
# is not supported for out-of-tree builds.
#
CONFIGS :=
ifeq (,)
CONFIG_MALI_MIDGARD ?= m
ifeq ($(CONFIG_MALI_MIDGARD),m)
CONFIG_MALI_PLATFORM_NAME ?= "devicetree"
CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD ?= y
CONFIG_MALI_GATOR_SUPPORT ?= y
CONFIG_MALI_ARBITRATION ?= n
CONFIG_MALI_PARTITION_MANAGER ?= n
ifneq ($(CONFIG_MALI_NO_MALI),y)
# Prevent misuse when CONFIG_MALI_NO_MALI!=y
CONFIG_MALI_REAL_HW ?= y
else
CONFIG_MALI_CORESIGHT = n
endif
ifeq ($(CONFIG_MALI_MIDGARD_DVFS),y)
# Prevent misuse when CONFIG_MALI_MIDGARD_DVFS=y
CONFIG_MALI_DEVFREQ ?= n
else
CONFIG_MALI_DEVFREQ ?= y
endif
ifeq ($(CONFIG_MALI_DMA_BUF_MAP_ON_DEMAND), y)
# Prevent misuse when CONFIG_MALI_DMA_BUF_MAP_ON_DEMAND=y
CONFIG_MALI_DMA_BUF_LEGACY_COMPAT = n
endif
ifeq ($(CONFIG_MALI_CSF_SUPPORT), y)
CONFIG_MALI_CORESIGHT ?= n
endif
#
# Expert/Debug/Test released configurations
#
ifeq ($(CONFIG_MALI_EXPERT), y)
ifeq ($(CONFIG_MALI_NO_MALI), y)
CONFIG_MALI_REAL_HW = n
CONFIG_MALI_NO_MALI_DEFAULT_GPU ?= "tMIx"
else
# Prevent misuse when CONFIG_MALI_NO_MALI=n
CONFIG_MALI_REAL_HW = y
endif
ifneq ($(CONFIG_ANDROID), y)
CONFIG_PAGE_MIGRATION_SUPPORT ?= y
endif
ifeq ($(CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED), y)
# Prevent misuse when CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED=y
CONFIG_MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE = n
endif
ifeq ($(CONFIG_MALI_DEBUG), y)
CONFIG_MALI_MIDGARD_ENABLE_TRACE ?= y
CONFIG_MALI_SYSTEM_TRACE ?= y
ifeq ($(CONFIG_SYNC_FILE), y)
CONFIG_MALI_FENCE_DEBUG ?= y
else
CONFIG_MALI_FENCE_DEBUG = n
endif
else
# Prevent misuse when CONFIG_MALI_DEBUG=n
CONFIG_MALI_MIDGARD_ENABLE_TRACE = n
CONFIG_MALI_SYSTEM_TRACE = n
CONFIG_MALI_FENCE_DEBUG = n
endif
else
# Prevent misuse when CONFIG_MALI_EXPERT=n
CONFIG_MALI_CORESTACK = n
CONFIG_LARGE_PAGE_SUPPORT = y
CONFIG_MALI_PWRSOFT_765 = n
CONFIG_MALI_JOB_DUMP = n
CONFIG_MALI_NO_MALI = n
CONFIG_MALI_REAL_HW = y
CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED = n
CONFIG_MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE = n
CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS = n
CONFIG_MALI_DEBUG = n
CONFIG_MALI_MIDGARD_ENABLE_TRACE = n
CONFIG_MALI_SYSTEM_TRACE = n
CONFIG_MALI_FENCE_DEBUG = n
endif
ifeq ($(CONFIG_MALI_DEBUG), y)
CONFIG_MALI_KUTF ?= y
ifeq ($(CONFIG_MALI_KUTF), y)
CONFIG_MALI_KUTF_IRQ_TEST ?= y
CONFIG_MALI_KUTF_CLK_RATE_TRACE ?= y
CONFIG_MALI_KUTF_MGM_INTEGRATION_TEST ?= y
ifeq ($(CONFIG_MALI_DEVFREQ), y)
ifeq ($(CONFIG_MALI_NO_MALI), y)
CONFIG_MALI_KUTF_IPA_UNIT_TEST ?= y
endif
endif
else
# Prevent misuse when CONFIG_MALI_KUTF=n
CONFIG_MALI_KUTF_IRQ_TEST = n
CONFIG_MALI_KUTF_CLK_RATE_TRACE = n
CONFIG_MALI_KUTF_MGM_INTEGRATION_TEST = n
endif
else
# Prevent misuse when CONFIG_MALI_DEBUG=n
CONFIG_MALI_KUTF = n
CONFIG_MALI_KUTF_IRQ_TEST = n
CONFIG_MALI_KUTF_CLK_RATE_TRACE = n
CONFIG_MALI_KUTF_MGM_INTEGRATION_TEST = n
endif
else
# Prevent misuse when CONFIG_MALI_MIDGARD=n
CONFIG_MALI_ARBITRATION = n
CONFIG_MALI_KUTF = n
CONFIG_MALI_KUTF_IRQ_TEST = n
CONFIG_MALI_KUTF_CLK_RATE_TRACE = n
CONFIG_MALI_KUTF_MGM_INTEGRATION_TEST = n
endif
# All Mali CONFIG should be listed here
CONFIGS += \
CONFIG_MALI_MIDGARD \
CONFIG_MALI_CSF_SUPPORT \
CONFIG_MALI_GATOR_SUPPORT \
CONFIG_MALI_ARBITRATION \
CONFIG_MALI_PARTITION_MANAGER \
CONFIG_MALI_REAL_HW \
CONFIG_MALI_DEVFREQ \
CONFIG_MALI_MIDGARD_DVFS \
CONFIG_MALI_DMA_BUF_MAP_ON_DEMAND \
CONFIG_MALI_DMA_BUF_LEGACY_COMPAT \
CONFIG_MALI_EXPERT \
CONFIG_MALI_CORESTACK \
CONFIG_PAGE_MIGRATION_SUPPORT \
CONFIG_LARGE_PAGE_SUPPORT \
CONFIG_MALI_PWRSOFT_765 \
CONFIG_MALI_JOB_DUMP \
CONFIG_MALI_NO_MALI \
CONFIG_MALI_IS_FPGA \
CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED \
CONFIG_MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE \
CONFIG_MALI_PRFCNT_SET_PRIMARY \
CONFIG_MALI_PRFCNT_SET_SECONDARY \
CONFIG_MALI_PRFCNT_SET_TERTIARY \
CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS \
CONFIG_MALI_DEBUG \
CONFIG_MALI_MIDGARD_ENABLE_TRACE \
CONFIG_MALI_SYSTEM_TRACE \
CONFIG_MALI_FENCE_DEBUG \
CONFIG_MALI_KUTF \
CONFIG_MALI_KUTF_IRQ_TEST \
CONFIG_MALI_KUTF_CLK_RATE_TRACE \
CONFIG_MALI_KUTF_MGM_INTEGRATION_TEST \
CONFIG_MALI_XEN \
CONFIG_MALI_CORESIGHT \
CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD
endif
THIS_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
-include $(THIS_DIR)/../arbitration/Makefile
# MAKE_ARGS to pass the custom CONFIGs on out-of-tree build
#
# Generate the list of CONFIGs and values.
# $(value config) is the name of the CONFIG option.
# $(value $(value config)) is its value (y, m).
# When the CONFIG is not set to y or m, it defaults to n.
MAKE_ARGS := $(foreach config,$(CONFIGS), \
$(if $(filter y m,$(value $(value config))), \
$(value config)=$(value $(value config)), \
$(value config)=n))
ifeq (,)
MAKE_ARGS += CONFIG_MALI_PLATFORM_NAME=$(CONFIG_MALI_PLATFORM_NAME)
endif
#
# EXTRA_CFLAGS to define the custom CONFIGs on out-of-tree build
#
# Generate the list of CONFIGs defines with values from CONFIGS.
# $(value config) is the name of the CONFIG option.
# When set to y or m, the CONFIG gets defined to 1.
EXTRA_CFLAGS := $(foreach config,$(CONFIGS), \
$(if $(filter y m,$(value $(value config))), \
-D$(value config)=1))
ifeq (,)
EXTRA_CFLAGS += -DCONFIG_MALI_PLATFORM_NAME='\"$(CONFIG_MALI_PLATFORM_NAME)\"'
EXTRA_CFLAGS += -DCONFIG_MALI_NO_MALI_DEFAULT_GPU='\"$(CONFIG_MALI_NO_MALI_DEFAULT_GPU)\"'
endif
#
# KBUILD_EXTRA_SYMBOLS to prevent warnings about unknown functions
#
BASE_SYMBOLS =
EXTRA_SYMBOLS += \
$(BASE_SYMBOLS)
CFLAGS_MODULE += -Wall -Werror
# The following were added to align with W=1 in scripts/Makefile.extrawarn
# from the Linux source tree (v5.18.14)
CFLAGS_MODULE += -Wextra -Wunused -Wno-unused-parameter
CFLAGS_MODULE += -Wmissing-declarations
CFLAGS_MODULE += -Wmissing-format-attribute
CFLAGS_MODULE += -Wmissing-prototypes
CFLAGS_MODULE += -Wold-style-definition
# The -Wmissing-include-dirs cannot be enabled as the path to some of the
# included directories change depending on whether it is an in-tree or
# out-of-tree build.
CFLAGS_MODULE += $(call cc-option, -Wunused-but-set-variable)
CFLAGS_MODULE += $(call cc-option, -Wunused-const-variable)
CFLAGS_MODULE += $(call cc-option, -Wpacked-not-aligned)
CFLAGS_MODULE += $(call cc-option, -Wstringop-truncation)
# The following turn off the warnings enabled by -Wextra
CFLAGS_MODULE += -Wno-sign-compare
CFLAGS_MODULE += -Wno-shift-negative-value
# This flag is needed to avoid build errors on older kernels
CFLAGS_MODULE += $(call cc-option, -Wno-cast-function-type)
KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN1
# The following were added to align with W=2 in scripts/Makefile.extrawarn
# from the Linux source tree (v5.18.14)
CFLAGS_MODULE += -Wdisabled-optimization
# The -Wshadow flag cannot be enabled unless upstream kernels are
# patched to fix redefinitions of certain built-in functions and
# global variables.
CFLAGS_MODULE += $(call cc-option, -Wlogical-op)
CFLAGS_MODULE += -Wmissing-field-initializers
# -Wtype-limits must be disabled due to build failures on kernel 5.x
CFLAGS_MODULE += -Wno-type-limits
CFLAGS_MODULE += $(call cc-option, -Wmaybe-uninitialized)
CFLAGS_MODULE += $(call cc-option, -Wunused-macros)
# The following ensures the stack frame does not get larger than a page
CFLAGS_MODULE += -Wframe-larger-than=4096
KBUILD_CPPFLAGS += -DKBUILD_EXTRA_WARN2
# This warning is disabled to avoid build failures in some kernel versions
CFLAGS_MODULE += -Wno-ignored-qualifiers
ifeq ($(CONFIG_GCOV_KERNEL),y)
CFLAGS_MODULE += $(call cc-option, -ftest-coverage)
CFLAGS_MODULE += $(call cc-option, -fprofile-arcs)
EXTRA_CFLAGS += -DGCOV_PROFILE=1
endif
ifeq ($(CONFIG_MALI_KCOV),y)
CFLAGS_MODULE += $(call cc-option, -fsanitize-coverage=trace-cmp)
EXTRA_CFLAGS += -DKCOV=1
EXTRA_CFLAGS += -DKCOV_ENABLE_COMPARISONS=1
endif
all:
$(MAKE) -C $(KDIR) M=$(M) $(MAKE_ARGS) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" modules
modules_install:
$(MAKE) -C $(KDIR) M=$(M) $(MAKE_ARGS) modules_install
clean:
$(MAKE) -C $(KDIR) M=$(M) $(MAKE_ARGS) clean

View file

@ -0,0 +1,364 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2012-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
menuconfig MALI_MIDGARD
bool "Mali Midgard series support"
default y
help
Enable this option to build support for a ARM Mali Midgard GPU.
To compile this driver as a module, choose M here:
this will generate a single module, called mali_kbase.
config MALI_PLATFORM_NAME
depends on MALI_MIDGARD
string "Platform name"
default "hisilicon" if PLATFORM_HIKEY960
default "hisilicon" if PLATFORM_HIKEY970
default "devicetree"
help
Enter the name of the desired platform configuration directory to
include in the build. 'platform/$(MALI_PLATFORM_NAME)/Kbuild' must
exist.
When PLATFORM_CUSTOM is set, this needs to be set manually to
pick up the desired platform files.
choice
prompt "Mali HW backend"
depends on MALI_MIDGARD
default MALI_NO_MALI if NO_MALI
default MALI_REAL_HW
config MALI_REAL_HW
bool "Enable build of Mali kernel driver for real HW"
depends on MALI_MIDGARD
help
This is the default HW backend.
config MALI_NO_MALI
bool "Enable build of Mali kernel driver for No Mali"
depends on MALI_MIDGARD && MALI_EXPERT
help
This can be used to test the driver in a simulated environment
whereby the hardware is not physically present. If the hardware is physically
present it will not be used. This can be used to test the majority of the
driver without needing actual hardware or for software benchmarking.
All calls to the simulated hardware will complete immediately as if the hardware
completed the task.
endchoice
config MALI_IS_FPGA
bool "Enable build of Mali kernel driver for FPGA"
depends on MALI_MIDGARD
default n
default y if PLATFORM_IS_FPGA
help
This is the default HW backend.
config MALI_CSF_SUPPORT
bool "Enable Mali CSF based GPU support"
depends on MALI_MIDGARD
default y if GPU_HAS_CSF
help
Enables support for CSF based GPUs.
config MALI_DEVFREQ
bool "Enable devfreq support for Mali"
depends on MALI_MIDGARD
default y
help
Support devfreq for Mali.
Using the devfreq framework and, by default, the simple on-demand
governor, the frequency of Mali will be dynamically selected from the
available OPPs.
config MALI_MIDGARD_DVFS
bool "Enable legacy DVFS"
depends on MALI_MIDGARD && !MALI_DEVFREQ
default n
help
Choose this option to enable legacy DVFS in the Mali Midgard DDK.
config MALI_GATOR_SUPPORT
bool "Enable Streamline tracing support"
depends on MALI_MIDGARD && !BACKEND_USER
default y
help
Enables kbase tracing used by the Arm Streamline Performance Analyzer.
The tracepoints are used to derive GPU activity charts in Streamline.
config MALI_MIDGARD_ENABLE_TRACE
bool "Enable kbase tracing"
depends on MALI_MIDGARD
default y if MALI_DEBUG
default n
help
Enables tracing in kbase. Trace log available through
the "mali_trace" debugfs file, when the CONFIG_DEBUG_FS is enabled
config MALI_ARBITER_SUPPORT
bool "Enable arbiter support for Mali"
depends on MALI_MIDGARD
default n
help
Enable support for the arbiter interface in the driver.
This allows an external arbiter to manage driver access
to GPU hardware in a virtualized environment
If unsure, say N.
config DMA_BUF_SYNC_IOCTL_SUPPORTED
bool "Enable Kernel DMA buffers support DMA_BUF_IOCTL_SYNC"
depends on MALI_MIDGARD && BACKEND_KERNEL
default y
config MALI_DMA_BUF_MAP_ON_DEMAND
bool "Enable map imported dma-bufs on demand"
depends on MALI_MIDGARD
default n
default y if !DMA_BUF_SYNC_IOCTL_SUPPORTED
help
This option will cause kbase to set up the GPU mapping of imported
dma-buf when needed to run atoms. This is the legacy behavior.
This is intended for testing and the option will get removed in the
future.
config MALI_DMA_BUF_LEGACY_COMPAT
bool "Enable legacy compatibility cache flush on dma-buf map"
depends on MALI_MIDGARD && !MALI_DMA_BUF_MAP_ON_DEMAND
default n
help
This option enables compatibility with legacy dma-buf mapping
behavior, then the dma-buf is mapped on import, by adding cache
maintenance where MALI_DMA_BUF_MAP_ON_DEMAND would do the mapping,
including a cache flush.
This option might work-around issues related to missing cache
flushes in other drivers. This only has an effect for clients using
UK 11.18 or older. For later UK versions it is not possible.
config MALI_CORESIGHT
depends on MALI_MIDGARD && MALI_CSF_SUPPORT && !NO_MALI
select CSFFW_DEBUG_FW_AS_RW
bool "Enable Kbase CoreSight tracing support"
default n
menuconfig MALI_EXPERT
depends on MALI_MIDGARD
bool "Enable Expert Settings"
default y
help
Enabling this option and modifying the default settings may produce
a driver with performance or other limitations.
config MALI_CORESTACK
bool "Enable support of GPU core stack power control"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Enabling this feature on supported GPUs will let the driver powering
on/off the GPU core stack independently without involving the Power
Domain Controller. This should only be enabled on platforms which
integration of the PDC to the Mali GPU is known to be problematic.
This feature is currently only supported on t-Six and t-HEx GPUs.
If unsure, say N.
config PAGE_MIGRATION_SUPPORT
bool "Compile with page migration support"
depends on MALI_MIDGARD && MALI_EXPERT
default y
default n if ANDROID
help
Compile in support for page migration.
If set to disabled ('n') then page migration cannot
be enabled at all. If set to enabled, then page migration
support is explicitly compiled in. This has no effect when
PAGE_MIGRATION_OVERRIDE is disabled.
config LARGE_PAGE_SUPPORT
bool "Support for 2MB page allocations"
depends on BACKEND_KERNEL
default y
help
Rather than allocating all GPU memory page-by-page, allow the system
to decide whether to attempt to allocate 2MB pages from the kernel.
This reduces TLB pressure and helps to prevent memory fragmentation.
Note that this option only enables the support for the module parameter
and does not necessarily mean that 2MB pages will be used automatically.
This depends on GPU support.
If in doubt, say Y.
config MALI_DEBUG
bool "Enable debug build"
depends on MALI_MIDGARD && MALI_EXPERT
default y if DEBUG
default n
help
Select this option for increased checking and reporting of errors.
config MALI_GCOV_KERNEL
bool "Enable branch coverage via gcov"
depends on MALI_MIDGARD && MALI_DEBUG
default n
help
Choose this option to enable building kbase with branch
coverage information. When built against a supporting kernel,
the coverage information will be available via debugfs.
config MALI_KCOV
bool "Enable kcov coverage to support fuzzers"
depends on MALI_MIDGARD && MALI_DEBUG
default n
help
Choose this option to enable building with fuzzing-oriented
coverage, to improve the random test cases that are generated.
config MALI_FENCE_DEBUG
bool "Enable debug sync fence usage"
depends on MALI_MIDGARD && MALI_EXPERT
default y if MALI_DEBUG
help
Select this option to enable additional checking and reporting on the
use of sync fences in the Mali driver.
This will add a 3s timeout to all sync fence waits in the Mali
driver, so that when work for Mali has been waiting on a sync fence
for a long time a debug message will be printed, detailing what fence
is causing the block, and which dependent Mali atoms are blocked as a
result of this.
The timeout can be changed at runtime through the js_soft_timeout
device attribute, where the timeout is specified in milliseconds.
config MALI_SYSTEM_TRACE
bool "Enable system event tracing support"
depends on MALI_MIDGARD && MALI_EXPERT
default y if MALI_DEBUG
default n
help
Choose this option to enable system trace events for each
kbase event. This is typically used for debugging but has
minimal overhead when not in use. Enable only if you know what
you are doing.
# Instrumentation options.
# config MALI_PRFCNT_SET_PRIMARY exists in the Kernel Kconfig but is configured using CINSTR_PRIMARY_HWC in Mconfig.
# config MALI_PRFCNT_SET_SECONDARY exists in the Kernel Kconfig but is configured using CINSTR_SECONDARY_HWC in Mconfig.
# config MALI_PRFCNT_SET_TERTIARY exists in the Kernel Kconfig but is configured using CINSTR_TERTIARY_HWC in Mconfig.
# config MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS exists in the Kernel Kconfig but is configured using CINSTR_HWC_SET_SELECT_VIA_DEBUG_FS in Mconfig.
config MALI_JOB_DUMP
bool "Enable system level support needed for job dumping"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
Choose this option to enable system level support needed for
job dumping. This is typically used for instrumentation but has
minimal overhead when not in use. Enable only if you know what
you are doing.
config MALI_PWRSOFT_765
bool "Enable workaround for PWRSOFT-765"
depends on MALI_MIDGARD && MALI_EXPERT
default n
help
PWRSOFT-765 fixes devfreq cooling devices issues. The fix was merged
in kernel v4.10, however if backported into the kernel then this
option must be manually selected.
If using kernel >= v4.10 then say N, otherwise if devfreq cooling
changes have been backported say Y to avoid compilation errors.
config MALI_HW_ERRATA_1485982_NOT_AFFECTED
bool "Disable workaround for KBASE_HW_ISSUE_GPU2017_1336"
depends on MALI_MIDGARD && MALI_EXPERT
default n
default y if PLATFORM_JUNO
help
This option disables the default workaround for GPU2017-1336. The
workaround keeps the L2 cache powered up except for powerdown and reset.
The workaround introduces a limitation that will prevent the running of
protected mode content on fully coherent platforms, as the switch to IO
coherency mode requires the L2 to be turned off.
config MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE
bool "Use alternative workaround for KBASE_HW_ISSUE_GPU2017_1336"
depends on MALI_MIDGARD && MALI_EXPERT && !MALI_HW_ERRATA_1485982_NOT_AFFECTED
default n
help
This option uses an alternative workaround for GPU2017-1336. Lowering
the GPU clock to a, platform specific, known good frequeuncy before
powering down the L2 cache. The clock can be specified in the device
tree using the property, opp-mali-errata-1485982. Otherwise the
slowest clock will be selected.
config MALI_TRACE_POWER_GPU_WORK_PERIOD
bool "Enable per-application GPU metrics tracepoints"
depends on MALI_MIDGARD
default y
help
This option enables per-application GPU metrics tracepoints.
If unsure, say N.
choice
prompt "CSF Firmware trace mode"
depends on MALI_MIDGARD
default MALI_FW_TRACE_MODE_MANUAL
help
CSF Firmware log operating mode.
config MALI_FW_TRACE_MODE_MANUAL
bool "manual mode"
depends on MALI_MIDGARD
help
firmware log can be read manually by the userspace (and it will
also be dumped automatically into dmesg on GPU reset).
config MALI_FW_TRACE_MODE_AUTO_PRINT
bool "automatic printing mode"
depends on MALI_MIDGARD
help
firmware log will be periodically emptied into dmesg, manual
reading through debugfs is disabled.
config MALI_FW_TRACE_MODE_AUTO_DISCARD
bool "automatic discarding mode"
depends on MALI_MIDGARD
help
firmware log will be periodically discarded, the remaining log can be
read manually by the userspace (and it will also be dumped
automatically into dmesg on GPU reset).
endchoice
source "kernel/drivers/gpu/arm/arbitration/Mconfig"
source "kernel/drivers/gpu/arm/bv_r51p0/tests/Mconfig"

View file

@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
mali_kbase-y += \
arbiter/mali_kbase_arbif.o \
arbiter/mali_kbase_arbiter_pm.o

View file

@ -0,0 +1,395 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/**
* DOC: Mali arbiter interface APIs to share GPU between Virtual Machines
*/
#include <mali_kbase.h>
#include "mali_kbase_arbif.h"
#include <tl/mali_kbase_tracepoints.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include "linux/mali_arbiter_interface.h"
/* Arbiter interface version against which was implemented this module */
#define MALI_REQUIRED_KBASE_ARBITER_INTERFACE_VERSION 5
#if MALI_REQUIRED_KBASE_ARBITER_INTERFACE_VERSION != MALI_ARBITER_INTERFACE_VERSION
#error "Unsupported Mali Arbiter interface version."
#endif
static void on_max_config(struct device *dev, uint32_t max_l2_slices, uint32_t max_core_mask)
{
struct kbase_device *kbdev;
if (!dev) {
pr_err("%s(): dev is NULL", __func__);
return;
}
kbdev = dev_get_drvdata(dev);
if (!kbdev) {
dev_err(dev, "%s(): kbdev is NULL", __func__);
return;
}
if (!max_l2_slices || !max_core_mask) {
dev_dbg(dev, "%s(): max_config ignored as one of the fields is zero", __func__);
return;
}
/* set the max config info in the kbase device */
kbase_arbiter_set_max_config(kbdev, max_l2_slices, max_core_mask);
}
/**
* on_update_freq() - Updates GPU clock frequency
* @dev: arbiter interface device handle
* @freq: GPU clock frequency value reported from arbiter
*
* call back function to update GPU clock frequency with
* new value from arbiter
*/
static void on_update_freq(struct device *dev, uint32_t freq)
{
struct kbase_device *kbdev;
if (!dev) {
pr_err("%s(): dev is NULL", __func__);
return;
}
kbdev = dev_get_drvdata(dev);
if (!kbdev) {
dev_err(dev, "%s(): kbdev is NULL", __func__);
return;
}
kbase_arbiter_pm_update_gpu_freq(&kbdev->arb.arb_freq, freq);
}
/**
* on_gpu_stop() - sends KBASE_VM_GPU_STOP_EVT event on VM stop
* @dev: arbiter interface device handle
*
* call back function to signal a GPU STOP event from arbiter interface
*/
static void on_gpu_stop(struct device *dev)
{
struct kbase_device *kbdev;
if (!dev) {
pr_err("%s(): dev is NULL", __func__);
return;
}
kbdev = dev_get_drvdata(dev);
if (!kbdev) {
dev_err(dev, "%s(): kbdev is NULL", __func__);
return;
}
KBASE_TLSTREAM_TL_ARBITER_STOP_REQUESTED(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_STOP_REQUESTED, NULL, 0);
kbase_arbiter_pm_vm_event(kbdev, KBASE_VM_GPU_STOP_EVT);
}
/**
* on_gpu_granted() - sends KBASE_VM_GPU_GRANTED_EVT event on GPU granted
* @dev: arbiter interface device handle
*
* call back function to signal a GPU GRANT event from arbiter interface
*/
static void on_gpu_granted(struct device *dev)
{
struct kbase_device *kbdev;
if (!dev) {
pr_err("%s(): dev is NULL", __func__);
return;
}
kbdev = dev_get_drvdata(dev);
if (!kbdev) {
dev_err(dev, "%s(): kbdev is NULL", __func__);
return;
}
KBASE_TLSTREAM_TL_ARBITER_GRANTED(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_GRANTED, NULL, 0);
kbase_arbiter_pm_vm_event(kbdev, KBASE_VM_GPU_GRANTED_EVT);
}
/**
* on_gpu_lost() - sends KBASE_VM_GPU_LOST_EVT event on GPU granted
* @dev: arbiter interface device handle
*
* call back function to signal a GPU LOST event from arbiter interface
*/
static void on_gpu_lost(struct device *dev)
{
struct kbase_device *kbdev;
if (!dev) {
pr_err("%s(): dev is NULL", __func__);
return;
}
kbdev = dev_get_drvdata(dev);
if (!kbdev) {
dev_err(dev, "%s(): kbdev is NULL", __func__);
return;
}
KBASE_TLSTREAM_TL_ARBITER_LOST(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_LOST, NULL, 0);
kbase_arbiter_pm_vm_event(kbdev, KBASE_VM_GPU_LOST_EVT);
}
static int kbase_arbif_of_init(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if;
struct device_node *arbiter_if_node;
struct platform_device *pdev;
if (!IS_ENABLED(CONFIG_OF)) {
/*
* Return -ENODEV in the event CONFIG_OF is not available and let the
* internal AW check for suitability for arbitration.
*/
return -ENODEV;
}
arbiter_if_node = of_parse_phandle(kbdev->dev->of_node, "arbiter-if", 0);
if (!arbiter_if_node)
arbiter_if_node = of_parse_phandle(kbdev->dev->of_node, "arbiter_if", 0);
if (!arbiter_if_node) {
dev_dbg(kbdev->dev, "No arbiter_if in Device Tree");
/* no arbiter interface defined in device tree */
kbdev->arb.arb_dev = NULL;
kbdev->arb.arb_if = NULL;
return -ENODEV;
}
pdev = of_find_device_by_node(arbiter_if_node);
if (!pdev) {
dev_err(kbdev->dev, "Failed to find arbiter_if device");
return -EPROBE_DEFER;
}
if (!pdev->dev.driver || !try_module_get(pdev->dev.driver->owner)) {
dev_err(kbdev->dev, "arbiter_if driver not available");
put_device(&pdev->dev);
return -EPROBE_DEFER;
}
kbdev->arb.arb_dev = &pdev->dev;
arb_if = platform_get_drvdata(pdev);
if (!arb_if) {
dev_err(kbdev->dev, "arbiter_if driver not ready");
module_put(pdev->dev.driver->owner);
put_device(&pdev->dev);
return -EPROBE_DEFER;
}
kbdev->arb.arb_if = arb_if;
return 0;
}
static void kbase_arbif_of_term(struct kbase_device *kbdev)
{
if (!IS_ENABLED(CONFIG_OF))
return;
if (kbdev->arb.arb_dev) {
module_put(kbdev->arb.arb_dev->driver->owner);
put_device(kbdev->arb.arb_dev);
}
kbdev->arb.arb_dev = NULL;
}
/**
* kbase_arbif_init() - Kbase Arbiter interface initialisation.
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Initialise Kbase Arbiter interface and assign callback functions.
*
* Return:
* * 0 - the interface was initialized or was not specified
* * in the device tree.
* * -EFAULT - the interface was specified but failed to initialize.
* * -EPROBE_DEFER - module dependencies are not yet available.
*/
int kbase_arbif_init(struct kbase_device *kbdev)
{
struct arbiter_if_arb_vm_ops ops;
struct arbiter_if_dev *arb_if;
int err = 0;
/* Tries to init with 'arbiter-if' if present in devicetree */
err = kbase_arbif_of_init(kbdev);
if (err == -ENODEV) {
/* devicetree does not support arbitration */
return -EPERM;
}
if (err)
return err;
ops.arb_vm_gpu_stop = on_gpu_stop;
ops.arb_vm_gpu_granted = on_gpu_granted;
ops.arb_vm_gpu_lost = on_gpu_lost;
ops.arb_vm_max_config = on_max_config;
ops.arb_vm_update_freq = on_update_freq;
kbdev->arb.arb_freq.arb_freq = 0;
kbdev->arb.arb_freq.freq_updated = false;
mutex_init(&kbdev->arb.arb_freq.arb_freq_lock);
arb_if = kbdev->arb.arb_if;
if (arb_if == NULL) {
dev_err(kbdev->dev, "No arbiter interface present");
goto failure_term;
}
if (!arb_if->vm_ops.vm_arb_register_dev) {
dev_err(kbdev->dev, "arbiter_if registration callback not present");
goto failure_term;
}
/* register kbase arbiter_if callbacks */
err = arb_if->vm_ops.vm_arb_register_dev(arb_if, kbdev->dev, &ops);
if (err) {
dev_err(kbdev->dev, "Failed to register with arbiter. (err = %d)", err);
goto failure_term;
}
return 0;
failure_term:
{
kbase_arbif_of_term(kbdev);
}
if (err != -EPROBE_DEFER)
err = -EFAULT;
return err;
}
/**
* kbase_arbif_destroy() - De-init Kbase arbiter interface
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* De-initialise Kbase arbiter interface
*/
void kbase_arbif_destroy(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_unregister_dev)
arb_if->vm_ops.vm_arb_unregister_dev(kbdev->arb.arb_if);
{
kbase_arbif_of_term(kbdev);
}
kbdev->arb.arb_if = NULL;
}
/**
* kbase_arbif_get_max_config() - Request max config info
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* call back function from arb interface to arbiter requesting max config info
*/
void kbase_arbif_get_max_config(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_get_max_config)
arb_if->vm_ops.vm_arb_get_max_config(arb_if);
}
/**
* kbase_arbif_gpu_request() - Request GPU from
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* call back function from arb interface to arbiter requesting GPU for VM
*/
void kbase_arbif_gpu_request(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_gpu_request) {
KBASE_TLSTREAM_TL_ARBITER_REQUESTED(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_REQUESTED, NULL, 0);
arb_if->vm_ops.vm_arb_gpu_request(arb_if);
}
}
/**
* kbase_arbif_gpu_stopped() - send GPU stopped message to the arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
* @gpu_required: GPU request flag
*
*/
void kbase_arbif_gpu_stopped(struct kbase_device *kbdev, u8 gpu_required)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_gpu_stopped) {
KBASE_TLSTREAM_TL_ARBITER_STOPPED(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_STOPPED, NULL, 0);
if (gpu_required) {
KBASE_TLSTREAM_TL_ARBITER_REQUESTED(kbdev, kbdev);
KBASE_KTRACE_ADD(kbdev, ARB_GPU_REQUESTED, NULL, 0);
}
arb_if->vm_ops.vm_arb_gpu_stopped(arb_if, gpu_required);
}
}
/**
* kbase_arbif_gpu_active() - Sends a GPU_ACTIVE message to the Arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Informs the arbiter VM is active
*/
void kbase_arbif_gpu_active(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_gpu_active)
arb_if->vm_ops.vm_arb_gpu_active(arb_if);
}
/**
* kbase_arbif_gpu_idle() - Inform the arbiter that the VM has gone idle
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Informs the arbiter VM is idle
*/
void kbase_arbif_gpu_idle(struct kbase_device *kbdev)
{
struct arbiter_if_dev *arb_if = kbdev->arb.arb_if;
if (arb_if && arb_if->vm_ops.vm_arb_gpu_idle)
arb_if->vm_ops.vm_arb_gpu_idle(arb_if);
}

View file

@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/**
* DOC: Mali arbiter interface APIs to share GPU between Virtual Machines
*/
#ifndef _MALI_KBASE_ARBIF_H_
#define _MALI_KBASE_ARBIF_H_
/**
* enum kbase_arbif_evt - Internal Arbiter event.
*
* @KBASE_VM_GPU_INITIALIZED_EVT: KBase has finished initializing
* and can be stopped
* @KBASE_VM_GPU_STOP_EVT: Stop message received from Arbiter
* @KBASE_VM_GPU_GRANTED_EVT: Grant message received from Arbiter
* @KBASE_VM_GPU_LOST_EVT: Lost message received from Arbiter
* @KBASE_VM_GPU_IDLE_EVENT: KBase has transitioned into an inactive state.
* @KBASE_VM_REF_EVENT: KBase has transitioned into an active state.
* @KBASE_VM_OS_SUSPEND_EVENT: KBase is suspending
* @KBASE_VM_OS_RESUME_EVENT: Kbase is resuming
*/
enum kbase_arbif_evt {
KBASE_VM_GPU_INITIALIZED_EVT = 1,
KBASE_VM_GPU_STOP_EVT,
KBASE_VM_GPU_GRANTED_EVT,
KBASE_VM_GPU_LOST_EVT,
KBASE_VM_GPU_IDLE_EVENT,
KBASE_VM_REF_EVENT,
KBASE_VM_OS_SUSPEND_EVENT,
KBASE_VM_OS_RESUME_EVENT,
};
/**
* kbase_arbif_init() - Initialize the arbiter interface functionality.
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Initialize the arbiter interface and also determines
* if Arbiter functionality is required.
*
* Return:
* * 0 - the interface was initialized or was not specified
* * in the device tree.
* * -EFAULT - the interface was specified but failed to initialize.
* * -EPROBE_DEFER - module dependencies are not yet available.
*/
int kbase_arbif_init(struct kbase_device *kbdev);
/**
* kbase_arbif_destroy() - Cleanups the arbiter interface functionality.
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Cleans up the arbiter interface functionality and resets the reference count
* of the arbif module used
*/
void kbase_arbif_destroy(struct kbase_device *kbdev);
/**
* kbase_arbif_get_max_config() - Request max config info
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* call back function from arb interface to arbiter requesting max config info
*/
void kbase_arbif_get_max_config(struct kbase_device *kbdev);
/**
* kbase_arbif_gpu_request() - Send GPU request message to the arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Sends a message to Arbiter to request GPU access.
*/
void kbase_arbif_gpu_request(struct kbase_device *kbdev);
/**
* kbase_arbif_gpu_stopped() - Send GPU stopped message to the arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
* @gpu_required: true if GPU access is still required
* (Arbiter will automatically send another grant message)
*
* Sends a message to Arbiter to notify that the GPU has stopped.
* @note Once this call has been made, KBase must not attempt to access the GPU
* until the #KBASE_VM_GPU_GRANTED_EVT event has been received.
*/
void kbase_arbif_gpu_stopped(struct kbase_device *kbdev, u8 gpu_required);
/**
* kbase_arbif_gpu_active() - Send a GPU active message to the arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Sends a message to Arbiter to report that KBase has gone active.
*/
void kbase_arbif_gpu_active(struct kbase_device *kbdev);
/**
* kbase_arbif_gpu_idle() - Send a GPU idle message to the arbiter
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Sends a message to Arbiter to report that KBase has gone idle.
*/
void kbase_arbif_gpu_idle(struct kbase_device *kbdev);
#endif /* _MALI_KBASE_ARBIF_H_ */

View file

@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2022 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/**
* DOC: Mali structures define to support arbitration feature
*/
#ifndef _MALI_KBASE_ARBITER_DEFS_H_
#define _MALI_KBASE_ARBITER_DEFS_H_
#include "mali_kbase_arbiter_pm.h"
/**
* struct kbase_arbiter_vm_state - Struct representing the state and containing the
* data of pm work
* @kbdev: Pointer to kbase device structure (must be a valid pointer)
* @vm_state_lock: The lock protecting the VM state when arbiter is used.
* This lock must also be held whenever the VM state is being
* transitioned
* @vm_state_wait: Wait queue set when GPU is granted
* @vm_state: Current state of VM
* @vm_arb_wq: Work queue for resuming or stopping work on the GPU for use
* with the Arbiter
* @vm_suspend_work: Work item for vm_arb_wq to stop current work on GPU
* @vm_resume_work: Work item for vm_arb_wq to resume current work on GPU
* @vm_arb_starting: Work queue resume in progress
* @vm_arb_stopping: Work queue suspend in progress
* @interrupts_installed: Flag set when interrupts are installed
* @vm_request_timer: Timer to monitor GPU request
*/
struct kbase_arbiter_vm_state {
struct kbase_device *kbdev;
struct mutex vm_state_lock;
wait_queue_head_t vm_state_wait;
enum kbase_vm_state vm_state;
struct workqueue_struct *vm_arb_wq;
struct work_struct vm_suspend_work;
struct work_struct vm_resume_work;
bool vm_arb_starting;
bool vm_arb_stopping;
bool interrupts_installed;
struct hrtimer vm_request_timer;
};
/**
* struct kbase_arbiter_device - Representing an instance of arbiter device,
* allocated from the probe method of Mali driver
* @arb_if: Pointer to the arbiter interface device
* @arb_dev: Pointer to the arbiter device
* @arb_freq: GPU clock frequency retrieved from arbiter.
*/
struct kbase_arbiter_device {
struct arbiter_if_dev *arb_if;
struct device *arb_dev;
struct kbase_arbiter_freq arb_freq;
};
#endif /* _MALI_KBASE_ARBITER_DEFS_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,195 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/**
* DOC: Mali arbiter power manager state machine and APIs
*/
#ifndef _MALI_KBASE_ARBITER_PM_H_
#define _MALI_KBASE_ARBITER_PM_H_
#include "mali_kbase_arbif.h"
/**
* enum kbase_vm_state - Current PM Arbitration state.
*
* @KBASE_VM_STATE_INITIALIZING: Special state before arbiter is initialized.
* @KBASE_VM_STATE_INITIALIZING_WITH_GPU: Initialization after GPU
* has been granted.
* @KBASE_VM_STATE_SUSPENDED: KBase is suspended by OS and GPU is not assigned.
* @KBASE_VM_STATE_STOPPED: GPU is not assigned to KBase and is not required.
* @KBASE_VM_STATE_STOPPED_GPU_REQUESTED: GPU is not assigned to KBase
* but a request has been made.
* @KBASE_VM_STATE_STARTING: GPU is assigned and KBase is getting ready to run.
* @KBASE_VM_STATE_IDLE: GPU is assigned but KBase has no work to do
* @KBASE_VM_STATE_ACTIVE: GPU is assigned and KBase is busy using it
* @KBASE_VM_STATE_SUSPEND_PENDING: OS is going into suspend mode.
* @KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT: OS is going into suspend mode but GPU
* has already been requested.
* In this situation we must wait for
* the Arbiter to send a GRANTED message
* and respond immediately with
* a STOPPED message before entering
* the suspend mode.
* @KBASE_VM_STATE_STOPPING_IDLE: Arbiter has sent a stopped message and there
* is currently no work to do on the GPU.
* @KBASE_VM_STATE_STOPPING_ACTIVE: Arbiter has sent a stopped message when
* KBase has work to do.
*/
enum kbase_vm_state {
KBASE_VM_STATE_INITIALIZING,
KBASE_VM_STATE_INITIALIZING_WITH_GPU,
KBASE_VM_STATE_SUSPENDED,
KBASE_VM_STATE_STOPPED,
KBASE_VM_STATE_STOPPED_GPU_REQUESTED,
KBASE_VM_STATE_STARTING,
KBASE_VM_STATE_IDLE,
KBASE_VM_STATE_ACTIVE,
KBASE_VM_STATE_SUSPEND_PENDING,
KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT,
KBASE_VM_STATE_STOPPING_IDLE,
KBASE_VM_STATE_STOPPING_ACTIVE
};
/**
* kbase_arbiter_pm_early_init() - Initialize arbiter for VM Paravirtualized use
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Initialize the arbiter and other required resources during the runtime
* and request the GPU for the VM for the first time.
*
* Return: 0 if successful, otherwise a standard Linux error code
*/
int kbase_arbiter_pm_early_init(struct kbase_device *kbdev);
/**
* kbase_arbiter_pm_early_term() - Shutdown arbiter and free resources.
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Clean up all the resources
*/
void kbase_arbiter_pm_early_term(struct kbase_device *kbdev);
/**
* kbase_arbiter_pm_release_interrupts() - Release the GPU interrupts
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Releases interrupts and set the interrupt flag to false
*/
void kbase_arbiter_pm_release_interrupts(struct kbase_device *kbdev);
/**
* kbase_arbiter_pm_install_interrupts() - Install the GPU interrupts
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Install interrupts and set the interrupt_install flag to true.
*
* Return: 0 if success or already installed. Otherwise a Linux error code
*/
int kbase_arbiter_pm_install_interrupts(struct kbase_device *kbdev);
/**
* kbase_arbiter_pm_vm_event() - Dispatch VM event to the state machine
* @kbdev: The kbase device structure for the device (must be a valid pointer)
* @event: The event to dispatch
*
* The state machine function. Receives events and transitions states
* according the event received and the current state
*/
void kbase_arbiter_pm_vm_event(struct kbase_device *kbdev, enum kbase_arbif_evt event);
/**
* kbase_arbiter_pm_ctx_active_handle_suspend() - Handle suspend operation for
* arbitration mode
* @kbdev: The kbase device structure for the device (must be a valid pointer)
* @suspend_handler: The handler code for how to handle a suspend
* that might occur
* @sched_lock_held: Flag variable that tells whether the caller grabs the
* scheduler lock or not
*
* This function handles a suspend event from the driver,
* communicating with the arbiter and waiting synchronously for the GPU
* to be granted again depending on the VM state.
*
* Return: 0 if success, 1 if failure due to system suspending/suspended
*/
int kbase_arbiter_pm_ctx_active_handle_suspend(struct kbase_device *kbdev,
enum kbase_pm_suspend_handler suspend_handler,
bool sched_lock_held);
/**
* kbase_arbiter_pm_vm_stopped() - Handle stop event for the VM
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* This function handles a stop event for the VM.
* It will update the VM state and forward the stop event to the driver.
*/
void kbase_arbiter_pm_vm_stopped(struct kbase_device *kbdev);
/**
* kbase_arbiter_set_max_config() - Set the max config data in kbase device.
* @kbdev: The kbase device structure for the device (must be a valid pointer).
* @max_l2_slices: The maximum number of L2 slices.
* @max_core_mask: The largest core mask.
*
* This function handles a stop event for the VM.
* It will update the VM state and forward the stop event to the driver.
*/
void kbase_arbiter_set_max_config(struct kbase_device *kbdev, uint32_t max_l2_slices,
uint32_t max_core_mask);
/**
* kbase_arbiter_pm_gpu_assigned() - Determine if this VM has access to the GPU
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Return: 0 if the VM does not have access, 1 if it does, and a negative number
* if an error occurred
*/
int kbase_arbiter_pm_gpu_assigned(struct kbase_device *kbdev);
extern struct kbase_clk_rate_trace_op_conf arb_clk_rate_trace_ops;
/**
* struct kbase_arbiter_freq - Holding the GPU clock frequency data retrieved
* from arbiter
* @arb_freq: GPU clock frequency value
* @arb_freq_lock: Mutex protecting access to arbfreq value
* @nb: Notifier block to receive rate change callbacks
* @freq_updated: Flag to indicate whether a frequency changed has just been
* communicated to avoid "GPU_GRANTED when not expected" warning
*/
struct kbase_arbiter_freq {
uint32_t arb_freq;
struct mutex arb_freq_lock;
struct notifier_block *nb;
bool freq_updated;
};
/**
* kbase_arbiter_pm_update_gpu_freq() - Update GPU frequency
* @arb_freq: Pointer to GPU clock frequency data
* @freq: The new frequency
*
* Updates the GPU frequency and triggers any notifications
*/
void kbase_arbiter_pm_update_gpu_freq(struct kbase_arbiter_freq *arb_freq, uint32_t freq);
#endif /*_MALI_KBASE_ARBITER_PM_H_ */

View file

@ -0,0 +1,53 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
mali_kbase-y += \
backend/gpu/mali_kbase_cache_policy_backend.o \
backend/gpu/mali_kbase_gpuprops_backend.o \
backend/gpu/mali_kbase_irq_linux.o \
backend/gpu/mali_kbase_pm_backend.o \
backend/gpu/mali_kbase_pm_driver.o \
backend/gpu/mali_kbase_pm_metrics.o \
backend/gpu/mali_kbase_pm_ca.o \
backend/gpu/mali_kbase_pm_always_on.o \
backend/gpu/mali_kbase_pm_coarse_demand.o \
backend/gpu/mali_kbase_pm_policy.o \
backend/gpu/mali_kbase_time.o \
backend/gpu/mali_kbase_l2_mmu_config.o \
backend/gpu/mali_kbase_clk_rate_trace_mgr.o
ifeq ($(MALI_USE_CSF),0)
mali_kbase-y += \
backend/gpu/mali_kbase_instr_backend.o \
backend/gpu/mali_kbase_jm_as.o \
backend/gpu/mali_kbase_debug_job_fault_backend.o \
backend/gpu/mali_kbase_jm_hw.o \
backend/gpu/mali_kbase_jm_rb.o \
backend/gpu/mali_kbase_js_backend.o
endif
mali_kbase-$(CONFIG_MALI_DEVFREQ) += \
backend/gpu/mali_kbase_devfreq.o
mali_kbase-$(CONFIG_MALI_NO_MALI) += backend/gpu/mali_kbase_model_linux.o
# NO_MALI Dummy model interface
mali_kbase-$(CONFIG_MALI_NO_MALI) += backend/gpu/mali_kbase_model_dummy.o

View file

@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "backend/gpu/mali_kbase_cache_policy_backend.h"
#include <device/mali_kbase_device.h>
#include <mali_exynos_kbase_entrypoint.h>
void kbase_cache_set_coherency_mode(struct kbase_device *kbdev, u32 mode)
{
mali_exynos_coherency_set_coherency_feature();
kbdev->current_gpu_coherency_mode = mode;
#if MALI_USE_CSF
if (kbdev->gpu_props.gpu_id.arch_id >= GPU_ID_ARCH_MAKE(12, 0, 1)) {
/* AMBA_ENABLE present from 12.0.1 */
u32 val = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(AMBA_ENABLE));
val = AMBA_ENABLE_COHERENCY_PROTOCOL_SET(val, mode);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(AMBA_ENABLE), val);
} else {
/* Fallback to COHERENCY_ENABLE for older versions */
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(COHERENCY_ENABLE), mode);
}
#else /* MALI_USE_CSF */
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(COHERENCY_ENABLE), mode);
#endif /* MALI_USE_CSF */
mali_exynos_llc_set_awuser();
mali_exynos_llc_set_aruser();
}
void kbase_amba_set_shareable_cache_support(struct kbase_device *kbdev)
{
#if MALI_USE_CSF
/* AMBA registers only present from 12.0.1 */
if (kbdev->gpu_props.gpu_id.arch_id < GPU_ID_ARCH_MAKE(12, 0, 1))
return;
if (kbdev->system_coherency != COHERENCY_NONE) {
u32 val = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(AMBA_FEATURES));
if (AMBA_FEATURES_SHAREABLE_CACHE_SUPPORT_GET(val)) {
val = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(AMBA_ENABLE));
val = AMBA_ENABLE_SHAREABLE_CACHE_SUPPORT_SET(val, 1);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(AMBA_ENABLE), val);
}
}
#endif /* MALI_USE_CSF */
}

View file

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CACHE_POLICY_BACKEND_H_
#define _KBASE_CACHE_POLICY_BACKEND_H_
#include <linux/types.h>
struct kbase_device;
/**
* kbase_cache_set_coherency_mode() - Sets the system coherency mode
* in the GPU.
* @kbdev: Device pointer
* @mode: Coherency mode. COHERENCY_ACE/ACE_LITE
*/
void kbase_cache_set_coherency_mode(struct kbase_device *kbdev, u32 mode);
/**
* kbase_amba_set_shareable_cache_support() - Sets AMBA shareable cache support
* in the GPU.
* @kbdev: Device pointer
*
* Note: Only for arch version 12.x.1 onwards.
*/
void kbase_amba_set_shareable_cache_support(struct kbase_device *kbdev);
#endif /* _KBASE_CACHE_POLICY_BACKEND_H_ */

View file

@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Implementation of the GPU clock rate trace manager.
*/
#include <mali_kbase.h>
#include <mali_kbase_config_defaults.h>
#include <linux/clk.h>
#include <linux/pm_opp.h>
#include <asm/div64.h>
#include "backend/gpu/mali_kbase_clk_rate_trace_mgr.h"
#ifdef CONFIG_TRACE_POWER_GPU_FREQUENCY
#include <trace/events/power_gpu_frequency.h>
#else
#include "mali_power_gpu_frequency_trace.h"
#endif
#ifndef CLK_RATE_TRACE_OPS
#define CLK_RATE_TRACE_OPS (NULL)
#endif
/**
* get_clk_rate_trace_callbacks() - Returns pointer to clk trace ops.
* @kbdev: Pointer to kbase device, used to check if arbitration is enabled
* when compiled with arbiter support.
* Return: Pointer to clk trace ops if supported or NULL.
*/
static struct kbase_clk_rate_trace_op_conf *
get_clk_rate_trace_callbacks(__maybe_unused struct kbase_device *kbdev)
{
/* base case */
const void *arbiter_if_node;
struct kbase_clk_rate_trace_op_conf *callbacks =
(struct kbase_clk_rate_trace_op_conf *)CLK_RATE_TRACE_OPS;
/* Nothing left to do here if there is no Arbiter/virtualization or if
* CONFIG_OF is not enabled.
*/
if (!IS_ENABLED(CONFIG_OF))
return callbacks;
if (WARN_ON(!kbdev) || WARN_ON(!kbdev->dev))
return callbacks;
if (!kbase_has_arbiter(kbdev))
return callbacks;
arbiter_if_node = of_get_property(kbdev->dev->of_node, "arbiter-if", NULL);
if (!arbiter_if_node)
arbiter_if_node = of_get_property(kbdev->dev->of_node, "arbiter_if", NULL);
/* Arbitration enabled, override the callback pointer.*/
if (arbiter_if_node)
callbacks = &arb_clk_rate_trace_ops;
else
dev_dbg(kbdev->dev,
"Arbitration supported but disabled by platform. Leaving clk rate callbacks as default.\n");
return callbacks;
}
static int gpu_clk_rate_change_notifier(struct notifier_block *nb, unsigned long event, void *data)
{
struct kbase_gpu_clk_notifier_data *ndata = data;
struct kbase_clk_data *clk_data =
container_of(nb, struct kbase_clk_data, clk_rate_change_nb);
struct kbase_clk_rate_trace_manager *clk_rtm = clk_data->clk_rtm;
unsigned long flags;
if (WARN_ON_ONCE(clk_data->gpu_clk_handle != ndata->gpu_clk_handle))
return NOTIFY_BAD;
spin_lock_irqsave(&clk_rtm->lock, flags);
if (event == POST_RATE_CHANGE) {
if (!clk_rtm->gpu_idle && (clk_data->clock_val != ndata->new_rate)) {
kbase_clk_rate_trace_manager_notify_all(clk_rtm, clk_data->index,
ndata->new_rate);
}
clk_data->clock_val = ndata->new_rate;
}
spin_unlock_irqrestore(&clk_rtm->lock, flags);
return NOTIFY_DONE;
}
static int gpu_clk_data_init(struct kbase_device *kbdev, void *gpu_clk_handle, unsigned int index)
{
struct kbase_clk_rate_trace_op_conf *callbacks;
struct kbase_clk_data *clk_data;
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
int ret = 0;
callbacks = get_clk_rate_trace_callbacks(kbdev);
if (WARN_ON(!callbacks) || WARN_ON(!gpu_clk_handle) ||
WARN_ON(index >= BASE_MAX_NR_CLOCKS_REGULATORS))
return -EINVAL;
clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
if (!clk_data) {
dev_err(kbdev->dev, "Failed to allocate data for clock enumerated at index %u",
index);
return -ENOMEM;
}
clk_data->index = (u8)index;
clk_data->gpu_clk_handle = gpu_clk_handle;
/* Store the initial value of clock */
clk_data->clock_val = callbacks->get_gpu_clk_rate(kbdev, gpu_clk_handle);
{
/* At the initialization time, GPU is powered off. */
unsigned long flags;
spin_lock_irqsave(&clk_rtm->lock, flags);
kbase_clk_rate_trace_manager_notify_all(clk_rtm, clk_data->index, 0);
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
clk_data->clk_rtm = clk_rtm;
clk_rtm->clks[index] = clk_data;
clk_data->clk_rate_change_nb.notifier_call = gpu_clk_rate_change_notifier;
if (callbacks->gpu_clk_notifier_register)
ret = callbacks->gpu_clk_notifier_register(kbdev, gpu_clk_handle,
&clk_data->clk_rate_change_nb);
if (ret) {
dev_err(kbdev->dev, "Failed to register notifier for clock enumerated at index %u",
index);
kfree(clk_data);
}
return ret;
}
int kbase_clk_rate_trace_manager_init(struct kbase_device *kbdev)
{
struct kbase_clk_rate_trace_op_conf *callbacks;
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
unsigned int i;
int ret = 0;
callbacks = get_clk_rate_trace_callbacks(kbdev);
spin_lock_init(&clk_rtm->lock);
INIT_LIST_HEAD(&clk_rtm->listeners);
/* Return early if no callbacks provided for clock rate tracing */
if (!callbacks) {
WRITE_ONCE(clk_rtm->clk_rate_trace_ops, NULL);
return 0;
}
clk_rtm->gpu_idle = true;
for (i = 0; i < BASE_MAX_NR_CLOCKS_REGULATORS; i++) {
void *gpu_clk_handle = callbacks->enumerate_gpu_clk(kbdev, i);
if (!gpu_clk_handle)
break;
ret = gpu_clk_data_init(kbdev, gpu_clk_handle, i);
if (ret)
goto error;
}
/* Activate clock rate trace manager if at least one GPU clock was
* enumerated.
*/
if (i) {
WRITE_ONCE(clk_rtm->clk_rate_trace_ops, callbacks);
} else {
dev_info(kbdev->dev, "No clock(s) available for rate tracing");
WRITE_ONCE(clk_rtm->clk_rate_trace_ops, NULL);
}
return 0;
error:
while (i--) {
clk_rtm->clk_rate_trace_ops->gpu_clk_notifier_unregister(
kbdev, clk_rtm->clks[i]->gpu_clk_handle,
&clk_rtm->clks[i]->clk_rate_change_nb);
kfree(clk_rtm->clks[i]);
}
return ret;
}
void kbase_clk_rate_trace_manager_term(struct kbase_device *kbdev)
{
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
unsigned int i;
WARN_ON(!list_empty(&clk_rtm->listeners));
if (!clk_rtm->clk_rate_trace_ops)
return;
for (i = 0; i < BASE_MAX_NR_CLOCKS_REGULATORS; i++) {
if (!clk_rtm->clks[i])
break;
if (clk_rtm->clk_rate_trace_ops->gpu_clk_notifier_unregister)
clk_rtm->clk_rate_trace_ops->gpu_clk_notifier_unregister(
kbdev, clk_rtm->clks[i]->gpu_clk_handle,
&clk_rtm->clks[i]->clk_rate_change_nb);
kfree(clk_rtm->clks[i]);
}
WRITE_ONCE(clk_rtm->clk_rate_trace_ops, NULL);
}
void kbase_clk_rate_trace_manager_gpu_active(struct kbase_device *kbdev)
{
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
unsigned int i;
unsigned long flags;
if (!clk_rtm->clk_rate_trace_ops)
return;
spin_lock_irqsave(&clk_rtm->lock, flags);
for (i = 0; i < BASE_MAX_NR_CLOCKS_REGULATORS; i++) {
struct kbase_clk_data *clk_data = clk_rtm->clks[i];
if (!clk_data)
break;
if (unlikely(!clk_data->clock_val))
continue;
kbase_clk_rate_trace_manager_notify_all(clk_rtm, clk_data->index,
clk_data->clock_val);
}
clk_rtm->gpu_idle = false;
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
void kbase_clk_rate_trace_manager_gpu_idle(struct kbase_device *kbdev)
{
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
unsigned int i;
unsigned long flags;
if (!clk_rtm->clk_rate_trace_ops)
return;
spin_lock_irqsave(&clk_rtm->lock, flags);
for (i = 0; i < BASE_MAX_NR_CLOCKS_REGULATORS; i++) {
struct kbase_clk_data *clk_data = clk_rtm->clks[i];
if (!clk_data)
break;
if (unlikely(!clk_data->clock_val))
continue;
kbase_clk_rate_trace_manager_notify_all(clk_rtm, clk_data->index, 0);
}
clk_rtm->gpu_idle = true;
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
void kbase_clk_rate_trace_manager_notify_all(struct kbase_clk_rate_trace_manager *clk_rtm,
u32 clk_index, unsigned long new_rate)
{
struct kbase_clk_rate_listener *pos;
struct kbase_device *kbdev;
lockdep_assert_held(&clk_rtm->lock);
kbdev = container_of(clk_rtm, struct kbase_device, pm.clk_rtm);
dev_dbg(kbdev->dev, "%s - GPU clock %u rate changed to %lu, pid: %d", __func__, clk_index,
new_rate, current->pid);
/* Raise standard `power/gpu_frequency` ftrace event */
{
unsigned long new_rate_khz = new_rate;
#if BITS_PER_LONG == 64
do_div(new_rate_khz, 1000);
#elif BITS_PER_LONG == 32
new_rate_khz /= 1000;
#else
#error "unsigned long division is not supported for this architecture"
#endif
trace_gpu_frequency(new_rate_khz, clk_index);
}
/* Notify the listeners. */
list_for_each_entry(pos, &clk_rtm->listeners, node) {
pos->notify(pos, clk_index, new_rate);
}
}
KBASE_EXPORT_TEST_API(kbase_clk_rate_trace_manager_notify_all);

View file

@ -0,0 +1,150 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CLK_RATE_TRACE_MGR_
#define _KBASE_CLK_RATE_TRACE_MGR_
/* The index of top clock domain in kbase_clk_rate_trace_manager:clks. */
#define KBASE_CLOCK_DOMAIN_TOP (0)
/* The index of shader-cores clock domain in
* kbase_clk_rate_trace_manager:clks.
*/
#define KBASE_CLOCK_DOMAIN_SHADER_CORES (1)
/**
* struct kbase_clk_data - Data stored per enumerated GPU clock.
*
* @clk_rtm: Pointer to clock rate trace manager object.
* @gpu_clk_handle: Handle unique to the enumerated GPU clock.
* @plat_private: Private data for the platform to store into
* @clk_rate_change_nb: notifier block containing the pointer to callback
* function that is invoked whenever the rate of
* enumerated GPU clock changes.
* @clock_val: Current rate of the enumerated GPU clock.
* @index: Index at which the GPU clock was enumerated.
*/
struct kbase_clk_data {
struct kbase_clk_rate_trace_manager *clk_rtm;
void *gpu_clk_handle;
void *plat_private;
struct notifier_block clk_rate_change_nb;
unsigned long clock_val;
u8 index;
};
/**
* kbase_clk_rate_trace_manager_init - Initialize GPU clock rate trace manager.
*
* @kbdev: Device pointer
*
* Return: 0 if success, or an error code on failure.
*/
int kbase_clk_rate_trace_manager_init(struct kbase_device *kbdev);
/**
* kbase_clk_rate_trace_manager_term - Terminate GPU clock rate trace manager.
*
* @kbdev: Device pointer
*/
void kbase_clk_rate_trace_manager_term(struct kbase_device *kbdev);
/**
* kbase_clk_rate_trace_manager_gpu_active - Inform GPU clock rate trace
* manager of GPU becoming active.
*
* @kbdev: Device pointer
*/
void kbase_clk_rate_trace_manager_gpu_active(struct kbase_device *kbdev);
/**
* kbase_clk_rate_trace_manager_gpu_idle - Inform GPU clock rate trace
* manager of GPU becoming idle.
* @kbdev: Device pointer
*/
void kbase_clk_rate_trace_manager_gpu_idle(struct kbase_device *kbdev);
/**
* kbase_clk_rate_trace_manager_subscribe_no_lock() - Add freq change listener.
*
* @clk_rtm: Clock rate manager instance.
* @listener: Listener handle
*
* kbase_clk_rate_trace_manager:lock must be held by the caller.
*/
static inline void
kbase_clk_rate_trace_manager_subscribe_no_lock(struct kbase_clk_rate_trace_manager *clk_rtm,
struct kbase_clk_rate_listener *listener)
{
lockdep_assert_held(&clk_rtm->lock);
list_add(&listener->node, &clk_rtm->listeners);
}
/**
* kbase_clk_rate_trace_manager_subscribe() - Add freq change listener.
*
* @clk_rtm: Clock rate manager instance.
* @listener: Listener handle
*/
static inline void
kbase_clk_rate_trace_manager_subscribe(struct kbase_clk_rate_trace_manager *clk_rtm,
struct kbase_clk_rate_listener *listener)
{
unsigned long flags;
spin_lock_irqsave(&clk_rtm->lock, flags);
kbase_clk_rate_trace_manager_subscribe_no_lock(clk_rtm, listener);
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
/**
* kbase_clk_rate_trace_manager_unsubscribe() - Remove freq change listener.
*
* @clk_rtm: Clock rate manager instance.
* @listener: Listener handle
*/
static inline void
kbase_clk_rate_trace_manager_unsubscribe(struct kbase_clk_rate_trace_manager *clk_rtm,
struct kbase_clk_rate_listener *listener)
{
unsigned long flags;
spin_lock_irqsave(&clk_rtm->lock, flags);
list_del(&listener->node);
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
/**
* kbase_clk_rate_trace_manager_notify_all() - Notify all clock \
* rate listeners.
*
* @clk_rtm: Clock rate manager instance.
* @clock_index: Clock index.
* @new_rate: New clock frequency(Hz)
*
* kbase_clk_rate_trace_manager:lock must be locked.
* This function is exported to be used by clock rate trace test
* portal.
*/
void kbase_clk_rate_trace_manager_notify_all(struct kbase_clk_rate_trace_manager *clk_rtm,
u32 clock_index, unsigned long new_rate);
#endif /* _KBASE_CLK_RATE_TRACE_MGR_ */

View file

@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2012-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <device/mali_kbase_device.h>
#include <hw_access/mali_kbase_hw_access.h>
#include "mali_kbase_debug_job_fault.h"
#if IS_ENABLED(CONFIG_DEBUG_FS)
/*GPU_CONTROL_REG(r)*/
static unsigned int gpu_control_reg_snapshot[] = { GPU_CONTROL_ENUM(GPU_ID),
GPU_CONTROL_ENUM(SHADER_READY),
GPU_CONTROL_ENUM(TILER_READY),
GPU_CONTROL_ENUM(L2_READY) };
/* JOB_CONTROL_REG(r) */
static unsigned int job_control_reg_snapshot[] = { JOB_CONTROL_ENUM(JOB_IRQ_MASK),
JOB_CONTROL_ENUM(JOB_IRQ_STATUS) };
/* JOB_SLOT_REG(n,r) */
static unsigned int job_slot_reg_snapshot[] = {
JOB_SLOT_ENUM(0, HEAD) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, TAIL) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, AFFINITY) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, CONFIG) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, STATUS) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, HEAD_NEXT) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, AFFINITY_NEXT) - JOB_SLOT_BASE_ENUM(0),
JOB_SLOT_ENUM(0, CONFIG_NEXT) - JOB_SLOT_BASE_ENUM(0)
};
/*MMU_CONTROL_REG(r)*/
static unsigned int mmu_reg_snapshot[] = { MMU_CONTROL_ENUM(IRQ_MASK),
MMU_CONTROL_ENUM(IRQ_STATUS) };
/* MMU_AS_REG(n,r) */
static unsigned int as_reg_snapshot[] = { MMU_AS_ENUM(0, TRANSTAB) - MMU_AS_BASE_ENUM(0),
MMU_AS_ENUM(0, TRANSCFG) - MMU_AS_BASE_ENUM(0),
MMU_AS_ENUM(0, MEMATTR) - MMU_AS_BASE_ENUM(0),
MMU_AS_ENUM(0, FAULTSTATUS) - MMU_AS_BASE_ENUM(0),
MMU_AS_ENUM(0, FAULTADDRESS) - MMU_AS_BASE_ENUM(0),
MMU_AS_ENUM(0, STATUS) - MMU_AS_BASE_ENUM(0) };
bool kbase_debug_job_fault_reg_snapshot_init(struct kbase_context *kctx, int reg_range)
{
uint i, j;
int offset = 0;
uint slot_number;
uint as_number;
if (kctx->reg_dump == NULL)
return false;
slot_number = kctx->kbdev->gpu_props.num_job_slots;
as_number = kctx->kbdev->gpu_props.num_address_spaces;
/* get the GPU control registers*/
for (i = 0; i < ARRAY_SIZE(gpu_control_reg_snapshot); i++) {
kctx->reg_dump[offset] = gpu_control_reg_snapshot[i];
if (kbase_reg_is_size64(kctx->kbdev, kctx->reg_dump[offset]))
offset += 4;
else
offset += 2;
}
/* get the Job control registers*/
for (i = 0; i < ARRAY_SIZE(job_control_reg_snapshot); i++) {
kctx->reg_dump[offset] = job_control_reg_snapshot[i];
if (kbase_reg_is_size64(kctx->kbdev, kctx->reg_dump[offset]))
offset += 4;
else
offset += 2;
}
/* get the Job Slot registers*/
for (j = 0; j < slot_number; j++) {
for (i = 0; i < ARRAY_SIZE(job_slot_reg_snapshot); i++) {
kctx->reg_dump[offset] = JOB_SLOT_BASE_OFFSET(j) + job_slot_reg_snapshot[i];
if (kbase_reg_is_size64(kctx->kbdev, kctx->reg_dump[offset]))
offset += 4;
else
offset += 2;
}
}
/* get the MMU registers*/
for (i = 0; i < ARRAY_SIZE(mmu_reg_snapshot); i++) {
kctx->reg_dump[offset] = mmu_reg_snapshot[i];
if (kbase_reg_is_size64(kctx->kbdev, kctx->reg_dump[offset]))
offset += 4;
else
offset += 2;
}
/* get the Address space registers*/
for (j = 0; j < as_number; j++) {
for (i = 0; i < ARRAY_SIZE(as_reg_snapshot); i++) {
kctx->reg_dump[offset] = MMU_AS_BASE_OFFSET(j) + as_reg_snapshot[i];
if (kbase_reg_is_size64(kctx->kbdev, kctx->reg_dump[offset]))
offset += 4;
else
offset += 2;
}
}
WARN_ON(offset >= (reg_range * 2 / 4));
/* set the termination flag*/
kctx->reg_dump[offset] = REGISTER_DUMP_TERMINATION_FLAG;
kctx->reg_dump[offset + 1] = REGISTER_DUMP_TERMINATION_FLAG;
dev_dbg(kctx->kbdev->dev, "kbase_job_fault_reg_snapshot_init:%d\n", offset);
return true;
}
bool kbase_job_fault_get_reg_snapshot(struct kbase_context *kctx)
{
int offset = 0;
u32 reg_enum;
u64 val64;
if (kctx->reg_dump == NULL)
return false;
while (kctx->reg_dump[offset] != REGISTER_DUMP_TERMINATION_FLAG) {
reg_enum = kctx->reg_dump[offset];
/* Get register offset from enum */
kbase_reg_get_offset(kctx->kbdev, reg_enum, &kctx->reg_dump[offset]);
if (kbase_reg_is_size64(kctx->kbdev, reg_enum)) {
val64 = kbase_reg_read64(kctx->kbdev, reg_enum);
/* offset computed offset to get _HI offset */
kctx->reg_dump[offset + 2] = kctx->reg_dump[offset] + 4;
kctx->reg_dump[offset + 1] = (u32)(val64 & 0xFFFFFFFF);
kctx->reg_dump[offset + 3] = (u32)(val64 >> 32);
offset += 4;
} else {
kctx->reg_dump[offset + 1] = kbase_reg_read32(kctx->kbdev, reg_enum);
offset += 2;
}
}
return true;
}
#endif

View file

@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Register-based HW access backend specific definitions
*/
#ifndef _KBASE_HWACCESS_GPU_DEFS_H_
#define _KBASE_HWACCESS_GPU_DEFS_H_
/* SLOT_RB_SIZE must be < 256 */
#define SLOT_RB_SIZE 2
#define SLOT_RB_MASK (SLOT_RB_SIZE - 1)
/**
* struct rb_entry - Ringbuffer entry
* @katom: Atom associated with this entry
*/
struct rb_entry {
struct kbase_jd_atom *katom;
};
/* SLOT_RB_TAG_PURGED assumes a value that is different from
* NULL (SLOT_RB_NULL_TAG_VAL) and will not be the result of
* any valid pointer via macro translation: SLOT_RB_TAG_KCTX(x).
*/
#define SLOT_RB_TAG_PURGED ((u64)(1 << 1))
#define SLOT_RB_NULL_TAG_VAL ((u64)0)
/**
* SLOT_RB_TAG_KCTX() - a function-like macro for converting a pointer to a
* u64 for serving as tagged value.
* @kctx: Pointer to kbase context.
*/
#define SLOT_RB_TAG_KCTX(kctx) ((u64)(uintptr_t)(kctx))
/**
* struct slot_rb - Slot ringbuffer
* @entries: Ringbuffer entries
* @last_kctx_tagged: The last context that submitted a job to the slot's
* HEAD_NEXT register. The value is a tagged variant so
* must not be dereferenced. It is used in operation to
* track when shader core L1 caches might contain a
* previous context's data, and so must only be set to
* SLOT_RB_NULL_TAG_VAL after reset/powerdown of the
* cores. In slot job submission, if there is a kctx
* change, and the relevant katom is configured with
* BASE_JD_REQ_SKIP_CACHE_START, a L1 read only cache
* maintenace operation is enforced.
* @read_idx: Current read index of buffer
* @write_idx: Current write index of buffer
* @job_chain_flag: Flag used to implement jobchain disambiguation
*/
struct slot_rb {
struct rb_entry entries[SLOT_RB_SIZE];
u64 last_kctx_tagged;
u8 read_idx;
u8 write_idx;
u8 job_chain_flag;
};
/**
* struct kbase_backend_data - GPU backend specific data for HW access layer
* @slot_rb: Slot ringbuffers
* @scheduling_timer: The timer tick used for rescheduling jobs
* @timer_running: Is the timer running? The runpool_mutex must be
* held whilst modifying this.
* @suspend_timer: Is the timer suspended? Set when a suspend
* occurs and cleared on resume. The runpool_mutex
* must be held whilst modifying this.
* @reset_gpu: Set to a KBASE_RESET_xxx value (see comments)
* @reset_workq: Work queue for performing the reset
* @reset_work: Work item for performing the reset
* @reset_wait: Wait event signalled when the reset is complete
* @reset_timer: Timeout for soft-stops before the reset
* @timeouts_updated: Have timeout values just been updated?
*
* The hwaccess_lock (a spinlock) must be held when accessing this structure
*/
struct kbase_backend_data {
#if !MALI_USE_CSF
struct slot_rb slot_rb[BASE_JM_MAX_NR_SLOTS];
struct hrtimer scheduling_timer;
bool timer_running;
#endif
bool suspend_timer;
atomic_t reset_gpu;
/* The GPU reset isn't pending */
#define KBASE_RESET_GPU_NOT_PENDING 0
/* kbase_prepare_to_reset_gpu has been called */
#define KBASE_RESET_GPU_PREPARED 1
/* kbase_reset_gpu has been called - the reset will now definitely happen
* within the timeout period
*/
#define KBASE_RESET_GPU_COMMITTED 2
/* The GPU reset process is currently occurring (timeout has expired or
* kbasep_try_reset_gpu_early was called)
*/
#define KBASE_RESET_GPU_HAPPENING 3
/* Reset the GPU silently, used when resetting the GPU as part of normal
* behavior (e.g. when exiting protected mode).
*/
#define KBASE_RESET_GPU_SILENT 4
struct workqueue_struct *reset_workq;
struct work_struct reset_work;
wait_queue_head_t reset_wait;
struct hrtimer reset_timer;
bool timeouts_updated;
};
#endif /* _KBASE_HWACCESS_GPU_DEFS_H_ */

View file

@ -0,0 +1,745 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <tl/mali_kbase_tracepoints.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/devfreq.h>
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
#include <linux/devfreq_cooling.h>
#endif
#include <linux/version.h>
#include <linux/pm_opp.h>
#include "mali_kbase_devfreq.h"
/**
* get_voltage() - Get the voltage value corresponding to the nominal frequency
* used by devfreq.
* @kbdev: Device pointer
* @freq: Nominal frequency in Hz passed by devfreq.
*
* This function will be called only when the opp table which is compatible with
* "operating-points-v2-mali", is not present in the devicetree for GPU device.
*
* Return: Voltage value in micro volts, 0 in case of error.
*/
static unsigned long get_voltage(struct kbase_device *kbdev, unsigned long freq)
{
struct dev_pm_opp *opp;
unsigned long voltage = 0;
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_lock();
#endif
opp = dev_pm_opp_find_freq_exact(kbdev->dev, freq, true);
if (IS_ERR_OR_NULL(opp))
dev_err(kbdev->dev, "Failed to get opp (%d)\n", PTR_ERR_OR_ZERO(opp));
else {
voltage = dev_pm_opp_get_voltage(opp);
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
dev_pm_opp_put(opp);
#endif
}
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_unlock();
#endif
/* Return the voltage in micro volts */
return voltage;
}
void kbase_devfreq_opp_translate(struct kbase_device *kbdev, unsigned long freq, u64 *core_mask,
unsigned long *freqs, unsigned long *volts)
{
unsigned int i;
for (i = 0; i < kbdev->num_opps; i++) {
if (kbdev->devfreq_table[i].opp_freq == freq) {
unsigned int j;
*core_mask = kbdev->devfreq_table[i].core_mask;
for (j = 0; j < kbdev->nr_clocks; j++) {
freqs[j] = kbdev->devfreq_table[i].real_freqs[j];
volts[j] = kbdev->devfreq_table[i].opp_volts[j];
}
break;
}
}
/* If failed to find OPP, return all cores enabled
* and nominal frequency and the corresponding voltage.
*/
if (i == kbdev->num_opps) {
unsigned long voltage = get_voltage(kbdev, freq);
*core_mask = kbdev->gpu_props.shader_present;
for (i = 0; i < kbdev->nr_clocks; i++) {
freqs[i] = freq;
volts[i] = voltage;
}
}
}
static int kbase_devfreq_target(struct device *dev, unsigned long *target_freq, u32 flags)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
struct dev_pm_opp *opp;
unsigned long nominal_freq;
unsigned long freqs[BASE_MAX_NR_CLOCKS_REGULATORS] = { 0 };
#if IS_ENABLED(CONFIG_REGULATOR)
unsigned long original_freqs[BASE_MAX_NR_CLOCKS_REGULATORS] = { 0 };
#endif
unsigned long volts[BASE_MAX_NR_CLOCKS_REGULATORS] = { 0 };
unsigned int i;
int err;
u64 core_mask;
nominal_freq = *target_freq;
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_lock();
#endif
opp = devfreq_recommended_opp(dev, &nominal_freq, flags);
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_unlock();
#endif
if (IS_ERR_OR_NULL(opp)) {
dev_err(dev, "Failed to get opp (%d)\n", PTR_ERR_OR_ZERO(opp));
return IS_ERR(opp) ? PTR_ERR(opp) : -ENODEV;
}
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
dev_pm_opp_put(opp);
#endif
/*
* Only update if there is a change of frequency
*/
if (kbdev->current_nominal_freq == nominal_freq) {
*target_freq = nominal_freq;
return 0;
}
kbase_devfreq_opp_translate(kbdev, nominal_freq, &core_mask, freqs, volts);
#if IS_ENABLED(CONFIG_REGULATOR)
/* Regulators and clocks work in pairs: every clock has a regulator,
* and we never expect to have more regulators than clocks.
*
* We always need to increase the voltage before increasing the number
* of shader cores and the frequency of a regulator/clock pair,
* otherwise the clock wouldn't have enough power to perform
* the transition.
*
* It's always safer to decrease the number of shader cores and
* the frequency before decreasing voltage of a regulator/clock pair,
* otherwise the clock could have problematic operation if it is
* deprived of the necessary power to sustain its current frequency
* (even if that happens for a short transition interval).
*/
for (i = 0; i < kbdev->nr_clocks; i++) {
if (kbdev->regulators[i] && kbdev->current_voltages[i] != volts[i] &&
kbdev->current_freqs[i] < freqs[i]) {
err = regulator_set_voltage(kbdev->regulators[i], volts[i], volts[i]);
if (!err) {
kbdev->current_voltages[i] = volts[i];
} else {
dev_err(dev, "Failed to increase voltage (%d) (target %lu)\n", err,
volts[i]);
return err;
}
}
}
#endif
for (i = 0; i < kbdev->nr_clocks; i++) {
if (kbdev->clocks[i]) {
err = clk_set_rate(kbdev->clocks[i], freqs[i]);
if (!err) {
#if IS_ENABLED(CONFIG_REGULATOR)
original_freqs[i] = kbdev->current_freqs[i];
#endif
kbdev->current_freqs[i] = freqs[i];
} else {
dev_err(dev, "Failed to set clock %lu (target %lu)\n", freqs[i],
*target_freq);
return err;
}
}
}
kbase_devfreq_set_core_mask(kbdev, core_mask);
#if IS_ENABLED(CONFIG_REGULATOR)
for (i = 0; i < kbdev->nr_clocks; i++) {
if (kbdev->regulators[i] && kbdev->current_voltages[i] != volts[i] &&
original_freqs[i] > freqs[i]) {
err = regulator_set_voltage(kbdev->regulators[i], volts[i], volts[i]);
if (!err) {
kbdev->current_voltages[i] = volts[i];
} else {
dev_err(dev, "Failed to decrease voltage (%d) (target %lu)\n", err,
volts[i]);
return err;
}
}
}
#endif
*target_freq = nominal_freq;
kbdev->current_nominal_freq = nominal_freq;
kbdev->current_core_mask = core_mask;
KBASE_TLSTREAM_AUX_DEVFREQ_TARGET(kbdev, (u64)nominal_freq);
return 0;
}
void kbase_devfreq_force_freq(struct kbase_device *kbdev, unsigned long freq)
{
unsigned long target_freq = freq;
kbase_devfreq_target(kbdev->dev, &target_freq, 0);
}
static int kbase_devfreq_cur_freq(struct device *dev, unsigned long *freq)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
*freq = kbdev->current_nominal_freq;
return 0;
}
static int kbase_devfreq_status(struct device *dev, struct devfreq_dev_status *stat)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
struct kbasep_pm_metrics diff;
kbase_pm_get_dvfs_metrics(kbdev, &kbdev->last_devfreq_metrics, &diff);
stat->busy_time = diff.time_busy;
stat->total_time = diff.time_busy + diff.time_idle;
stat->current_frequency = kbdev->current_nominal_freq;
stat->private_data = NULL;
#if MALI_USE_CSF && defined CONFIG_DEVFREQ_THERMAL
kbase_ipa_reset_data(kbdev);
#endif
return 0;
}
static int kbase_devfreq_init_freq_table(struct kbase_device *kbdev, struct devfreq_dev_profile *dp)
{
int count;
unsigned int i = 0;
unsigned long freq;
struct dev_pm_opp *opp;
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_lock();
#endif
count = dev_pm_opp_get_opp_count(kbdev->dev);
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_unlock();
#endif
if (count < 0)
return count;
dp->freq_table = kmalloc_array((size_t)count, sizeof(dp->freq_table[0]), GFP_KERNEL);
if (!dp->freq_table)
return -ENOMEM;
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_lock();
#endif
for (i = 0, freq = ULONG_MAX; i < (unsigned int)count; i++, freq--) {
opp = dev_pm_opp_find_freq_floor(kbdev->dev, &freq);
if (IS_ERR(opp))
break;
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
dev_pm_opp_put(opp);
#endif /* KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE */
dp->freq_table[i] = freq;
}
#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE
rcu_read_unlock();
#endif
if ((unsigned int)count != i)
dev_warn(kbdev->dev, "Unable to enumerate all OPPs (%d!=%u\n", count, i);
dp->max_state = i;
/* Have the lowest clock as suspend clock.
* It may be overridden by 'opp-mali-errata-1485982'.
*/
if (kbdev->pm.backend.gpu_clock_slow_down_wa) {
freq = 0;
opp = dev_pm_opp_find_freq_ceil(kbdev->dev, &freq);
if (IS_ERR(opp)) {
dev_err(kbdev->dev, "failed to find slowest clock");
return 0;
}
dev_info(kbdev->dev, "suspend clock %lu from slowest", freq);
kbdev->pm.backend.gpu_clock_suspend_freq = freq;
}
return 0;
}
static void kbase_devfreq_term_freq_table(struct kbase_device *kbdev)
{
struct devfreq_dev_profile *dp = &kbdev->devfreq_profile;
kfree(dp->freq_table);
dp->freq_table = NULL;
}
static void kbase_devfreq_term_core_mask_table(struct kbase_device *kbdev)
{
kfree(kbdev->devfreq_table);
kbdev->devfreq_table = NULL;
}
static void kbase_devfreq_exit(struct device *dev)
{
struct kbase_device *kbdev = dev_get_drvdata(dev);
if (kbdev)
kbase_devfreq_term_freq_table(kbdev);
}
static void kbasep_devfreq_read_suspend_clock(struct kbase_device *kbdev, struct device_node *node)
{
u64 freq = 0;
int err = 0;
/* Check if this node is the opp entry having 'opp-mali-errata-1485982'
* to get the suspend clock, otherwise skip it.
*/
if (!of_property_read_bool(node, "opp-mali-errata-1485982"))
return;
/* In kbase DevFreq, the clock will be read from 'opp-hz'
* and translated into the actual clock by opp_translate.
*
* In customer DVFS, the clock will be read from 'opp-hz-real'
* for clk driver. If 'opp-hz-real' does not exist,
* read from 'opp-hz'.
*/
if (IS_ENABLED(CONFIG_MALI_DEVFREQ))
err = of_property_read_u64(node, "opp-hz", &freq);
else {
if (of_property_read_u64(node, "opp-hz-real", &freq))
err = of_property_read_u64(node, "opp-hz", &freq);
}
if (WARN_ON(err || !freq))
return;
kbdev->pm.backend.gpu_clock_suspend_freq = freq;
dev_info(kbdev->dev, "suspend clock %llu by opp-mali-errata-1485982", freq);
}
static int kbase_devfreq_init_core_mask_table(struct kbase_device *kbdev)
{
#ifndef CONFIG_OF
/* OPP table initialization requires at least the capability to get
* regulators and clocks from the device tree, as well as parsing
* arrays of unsigned integer values.
*
* The whole initialization process shall simply be skipped if the
* minimum capability is not available.
*/
return 0;
#else
struct device_node *opp_node =
of_parse_phandle(kbdev->dev->of_node, "operating-points-v2", 0);
struct device_node *node;
unsigned int i = 0;
int count;
u64 shader_present = kbdev->gpu_props.shader_present;
if (!opp_node)
return 0;
if (!of_device_is_compatible(opp_node, "operating-points-v2-mali"))
return 0;
count = dev_pm_opp_get_opp_count(kbdev->dev);
kbdev->devfreq_table =
kmalloc_array((size_t)count, sizeof(struct kbase_devfreq_opp), GFP_KERNEL);
if (!kbdev->devfreq_table)
return -ENOMEM;
for_each_available_child_of_node(opp_node, node) {
const void *core_count_p;
u64 core_mask, opp_freq, real_freqs[BASE_MAX_NR_CLOCKS_REGULATORS];
int err;
#if IS_ENABLED(CONFIG_REGULATOR)
u32 opp_volts[BASE_MAX_NR_CLOCKS_REGULATORS];
#endif
/* Read suspend clock from opp table */
if (kbdev->pm.backend.gpu_clock_slow_down_wa)
kbasep_devfreq_read_suspend_clock(kbdev, node);
err = of_property_read_u64(node, "opp-hz", &opp_freq);
if (err) {
dev_warn(kbdev->dev, "Failed to read opp-hz property with error %d\n", err);
continue;
}
#if BASE_MAX_NR_CLOCKS_REGULATORS > 1
err = of_property_read_u64_array(node, "opp-hz-real", real_freqs, kbdev->nr_clocks);
#else
WARN_ON(kbdev->nr_clocks != 1);
err = of_property_read_u64(node, "opp-hz-real", real_freqs);
#endif
if (err < 0) {
dev_warn(kbdev->dev, "Failed to read opp-hz-real property with error %d",
err);
continue;
}
#if IS_ENABLED(CONFIG_REGULATOR)
err = of_property_read_u32_array(node, "opp-microvolt", opp_volts,
kbdev->nr_regulators);
if (err < 0) {
dev_warn(kbdev->dev, "Failed to read opp-microvolt property with error %d",
err);
continue;
}
#endif
if (of_property_read_u64(node, "opp-core-mask", &core_mask))
core_mask = shader_present;
if (core_mask != shader_present && corestack_driver_control) {
dev_warn(
kbdev->dev,
"Ignoring OPP %llu - Dynamic Core Scaling not supported on this GPU",
opp_freq);
continue;
}
core_count_p = of_get_property(node, "opp-core-count", NULL);
if (core_count_p) {
u64 remaining_core_mask = kbdev->gpu_props.shader_present;
int core_count = be32_to_cpup(core_count_p);
core_mask = 0;
for (; core_count > 0; core_count--) {
int core = ffs(remaining_core_mask);
if (!core) {
dev_err(kbdev->dev, "OPP has more cores than GPU\n");
return -ENODEV;
}
core_mask |= (1ull << (core - 1));
remaining_core_mask &= ~(1ull << (core - 1));
}
}
if (!core_mask) {
dev_err(kbdev->dev, "OPP has invalid core mask of 0\n");
return -ENODEV;
}
kbdev->devfreq_table[i].opp_freq = opp_freq;
kbdev->devfreq_table[i].core_mask = core_mask;
if (kbdev->nr_clocks > 0) {
unsigned int j;
for (j = 0; j < kbdev->nr_clocks; j++)
kbdev->devfreq_table[i].real_freqs[j] = real_freqs[j];
}
#if IS_ENABLED(CONFIG_REGULATOR)
if (kbdev->nr_regulators > 0) {
unsigned int j;
for (j = 0; j < kbdev->nr_regulators; j++)
kbdev->devfreq_table[i].opp_volts[j] = opp_volts[j];
}
#endif
dev_info(kbdev->dev, "OPP %d : opp_freq=%llu core_mask=%llx\n", i, opp_freq,
core_mask);
i++;
}
kbdev->num_opps = i;
return 0;
#endif /* CONFIG_OF */
}
static const char *kbase_devfreq_req_type_name(enum kbase_devfreq_work_type type)
{
const char *p;
switch (type) {
case DEVFREQ_WORK_NONE:
p = "devfreq_none";
break;
case DEVFREQ_WORK_SUSPEND:
p = "devfreq_suspend";
break;
case DEVFREQ_WORK_RESUME:
p = "devfreq_resume";
break;
default:
p = "Unknown devfreq_type";
}
return p;
}
static void kbase_devfreq_suspend_resume_worker(struct work_struct *work)
{
struct kbase_devfreq_queue_info *info =
container_of(work, struct kbase_devfreq_queue_info, work);
struct kbase_device *kbdev = container_of(info, struct kbase_device, devfreq_queue);
unsigned long flags;
enum kbase_devfreq_work_type type, acted_type;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
type = kbdev->devfreq_queue.req_type;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
acted_type = kbdev->devfreq_queue.acted_type;
dev_dbg(kbdev->dev, "Worker handles queued req: %s (acted: %s)\n",
kbase_devfreq_req_type_name(type), kbase_devfreq_req_type_name(acted_type));
switch (type) {
case DEVFREQ_WORK_SUSPEND:
case DEVFREQ_WORK_RESUME:
if (type != acted_type) {
if (type == DEVFREQ_WORK_RESUME)
devfreq_resume_device(kbdev->devfreq);
else
devfreq_suspend_device(kbdev->devfreq);
dev_dbg(kbdev->dev, "Devfreq transition occured: %s => %s\n",
kbase_devfreq_req_type_name(acted_type),
kbase_devfreq_req_type_name(type));
kbdev->devfreq_queue.acted_type = type;
}
break;
default:
WARN_ON(1);
}
}
void kbase_devfreq_enqueue_work(struct kbase_device *kbdev, enum kbase_devfreq_work_type work_type)
{
unsigned long flags;
WARN_ON(work_type == DEVFREQ_WORK_NONE);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
/* Skip enqueuing a work if workqueue has already been terminated. */
if (likely(kbdev->devfreq_queue.workq)) {
kbdev->devfreq_queue.req_type = work_type;
queue_work(kbdev->devfreq_queue.workq, &kbdev->devfreq_queue.work);
}
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
dev_dbg(kbdev->dev, "Enqueuing devfreq req: %s\n", kbase_devfreq_req_type_name(work_type));
}
static int kbase_devfreq_work_init(struct kbase_device *kbdev)
{
kbdev->devfreq_queue.req_type = DEVFREQ_WORK_NONE;
kbdev->devfreq_queue.acted_type = DEVFREQ_WORK_RESUME;
kbdev->devfreq_queue.workq = alloc_ordered_workqueue("devfreq_workq", 0);
if (!kbdev->devfreq_queue.workq)
return -ENOMEM;
INIT_WORK(&kbdev->devfreq_queue.work, kbase_devfreq_suspend_resume_worker);
return 0;
}
static void kbase_devfreq_work_term(struct kbase_device *kbdev)
{
unsigned long flags;
struct workqueue_struct *workq;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
workq = kbdev->devfreq_queue.workq;
kbdev->devfreq_queue.workq = NULL;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
destroy_workqueue(workq);
}
int kbase_devfreq_init(struct kbase_device *kbdev)
{
struct devfreq_dev_profile *dp;
int err;
unsigned int i;
bool free_devfreq_freq_table = true;
if (kbdev->nr_clocks == 0) {
dev_err(kbdev->dev, "Clock not available for devfreq\n");
return -ENODEV;
}
for (i = 0; i < kbdev->nr_clocks; i++) {
if (kbdev->clocks[i])
kbdev->current_freqs[i] = clk_get_rate(kbdev->clocks[i]);
}
kbdev->current_nominal_freq = kbdev->current_freqs[0];
dp = &kbdev->devfreq_profile;
dp->initial_freq = kbdev->current_freqs[0];
dp->polling_ms = 100;
dp->target = kbase_devfreq_target;
dp->get_dev_status = kbase_devfreq_status;
dp->get_cur_freq = kbase_devfreq_cur_freq;
dp->exit = kbase_devfreq_exit;
if (kbase_devfreq_init_freq_table(kbdev, dp))
return -EFAULT;
if (dp->max_state > 0) {
/* Record the maximum frequency possible */
kbdev->gpu_props.gpu_freq_khz_max = dp->freq_table[0] / 1000;
}
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
err = kbase_ipa_init(kbdev);
if (err) {
dev_err(kbdev->dev, "IPA initialization failed");
goto ipa_init_failed;
}
#endif
err = kbase_devfreq_init_core_mask_table(kbdev);
if (err)
goto init_core_mask_table_failed;
kbdev->devfreq = devfreq_add_device(kbdev->dev, dp, "simple_ondemand", NULL);
if (IS_ERR(kbdev->devfreq)) {
err = PTR_ERR(kbdev->devfreq);
kbdev->devfreq = NULL;
dev_err(kbdev->dev, "Fail to add devfreq device(%d)", err);
goto devfreq_add_dev_failed;
}
/* Explicit free of freq table isn't needed after devfreq_add_device() */
free_devfreq_freq_table = false;
/* Initialize devfreq suspend/resume workqueue */
err = kbase_devfreq_work_init(kbdev);
if (err) {
dev_err(kbdev->dev, "Fail to init devfreq workqueue");
goto devfreq_work_init_failed;
}
/* devfreq_add_device only copies a few of kbdev->dev's fields, so
* set drvdata explicitly so IPA models can access kbdev.
*/
dev_set_drvdata(&kbdev->devfreq->dev, kbdev);
err = devfreq_register_opp_notifier(kbdev->dev, kbdev->devfreq);
if (err) {
dev_err(kbdev->dev, "Failed to register OPP notifier (%d)", err);
goto opp_notifier_failed;
}
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
kbdev->devfreq_cooling = of_devfreq_cooling_register_power(
kbdev->dev->of_node, kbdev->devfreq, &kbase_ipa_power_model_ops);
if (IS_ERR_OR_NULL(kbdev->devfreq_cooling)) {
err = PTR_ERR_OR_ZERO(kbdev->devfreq_cooling);
dev_err(kbdev->dev, "Failed to register cooling device (%d)", err);
err = err == 0 ? -ENODEV : err;
goto cooling_reg_failed;
}
#endif
return 0;
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
cooling_reg_failed:
devfreq_unregister_opp_notifier(kbdev->dev, kbdev->devfreq);
#endif /* CONFIG_DEVFREQ_THERMAL */
opp_notifier_failed:
kbase_devfreq_work_term(kbdev);
devfreq_work_init_failed:
if (devfreq_remove_device(kbdev->devfreq))
dev_err(kbdev->dev, "Failed to terminate devfreq (%d)", err);
kbdev->devfreq = NULL;
devfreq_add_dev_failed:
kbase_devfreq_term_core_mask_table(kbdev);
init_core_mask_table_failed:
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
kbase_ipa_term(kbdev);
ipa_init_failed:
#endif
if (free_devfreq_freq_table)
kbase_devfreq_term_freq_table(kbdev);
return err;
}
void kbase_devfreq_term(struct kbase_device *kbdev)
{
int err;
dev_dbg(kbdev->dev, "Term Mali devfreq\n");
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
if (kbdev->devfreq_cooling)
devfreq_cooling_unregister(kbdev->devfreq_cooling);
#endif
devfreq_unregister_opp_notifier(kbdev->dev, kbdev->devfreq);
kbase_devfreq_work_term(kbdev);
err = devfreq_remove_device(kbdev->devfreq);
if (err)
dev_err(kbdev->dev, "Failed to terminate devfreq (%d)\n", err);
else
kbdev->devfreq = NULL;
kbase_devfreq_term_core_mask_table(kbdev);
#if IS_ENABLED(CONFIG_DEVFREQ_THERMAL)
kbase_ipa_term(kbdev);
#endif
}

View file

@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _BASE_DEVFREQ_H_
#define _BASE_DEVFREQ_H_
/**
* kbase_devfreq_init - Initialize kbase device for DevFreq.
* @kbdev: Device pointer
*
* This function must be called only when a kbase device is initialized.
*
* Return: 0 on success.
*/
int kbase_devfreq_init(struct kbase_device *kbdev);
void kbase_devfreq_term(struct kbase_device *kbdev);
/**
* kbase_devfreq_force_freq - Set GPU frequency on L2 power on/off.
* @kbdev: Device pointer
* @freq: GPU frequency in HZ to be set when
* MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE is enabled
*/
void kbase_devfreq_force_freq(struct kbase_device *kbdev, unsigned long freq);
/**
* kbase_devfreq_enqueue_work - Enqueue a work item for suspend/resume devfreq.
* @kbdev: Device pointer
* @work_type: The type of the devfreq work item, i.e. suspend or resume
*/
void kbase_devfreq_enqueue_work(struct kbase_device *kbdev, enum kbase_devfreq_work_type work_type);
/**
* kbase_devfreq_opp_translate - Translate nominal OPP frequency from devicetree
* into real frequency & voltage pair, along with
* core mask
* @kbdev: Device pointer
* @freq: Nominal frequency
* @core_mask: Pointer to u64 to store core mask to
* @freqs: Pointer to array of frequencies
* @volts: Pointer to array of voltages
*
* This function will only perform translation if an operating-points-v2-mali
* table is present in devicetree. If one is not present then it will return an
* untranslated frequency (and corresponding voltage) and all cores enabled.
* The voltages returned are in micro Volts (uV).
*/
void kbase_devfreq_opp_translate(struct kbase_device *kbdev, unsigned long freq, u64 *core_mask,
unsigned long *freqs, unsigned long *volts);
#endif /* _BASE_DEVFREQ_H_ */

View file

@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel property query backend APIs
*/
#include <mali_kbase.h>
#include <device/mali_kbase_device.h>
#include <mali_kbase_hwaccess_gpuprops.h>
#include <mali_kbase_gpuprops_private_types.h>
#include <mali_exynos_kbase_entrypoint.h>
int kbase_backend_gpuprops_get(struct kbase_device *kbdev, struct kbasep_gpuprops_regdump *regdump)
{
uint i;
/* regdump is zero intiialized, individual entries do not need to be explicitly set */
regdump->gpu_id = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(GPU_ID));
regdump->shader_present = kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(SHADER_PRESENT));
regdump->tiler_present = kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(TILER_PRESENT));
regdump->l2_present = kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(L2_PRESENT));
if (kbase_reg_is_valid(kbdev, GPU_CONTROL_ENUM(AS_PRESENT)))
regdump->as_present = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(AS_PRESENT));
if (kbase_reg_is_valid(kbdev, GPU_CONTROL_ENUM(STACK_PRESENT)))
regdump->stack_present = kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(STACK_PRESENT));
#if !MALI_USE_CSF
regdump->js_present = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(JS_PRESENT));
/* Not a valid register on TMIX */
/* TGOx specific register */
if (kbase_hw_has_feature(kbdev, KBASE_HW_FEATURE_THREAD_TLS_ALLOC))
regdump->thread_tls_alloc =
kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(THREAD_TLS_ALLOC));
#endif /* !MALI_USE_CSF */
regdump->thread_max_threads = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(THREAD_MAX_THREADS));
if (kbase_reg_is_valid(kbdev, GPU_CONTROL_ENUM(THREAD_MAX_WORKGROUP_SIZE)))
regdump->thread_max_workgroup_size =
kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(THREAD_MAX_WORKGROUP_SIZE));
#if MALI_USE_CSF
#endif /* MALI_USE_CSF */
regdump->thread_max_barrier_size =
kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(THREAD_MAX_BARRIER_SIZE));
regdump->thread_features = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(THREAD_FEATURES));
/* Feature Registers */
/* AMBA_FEATURES enum is mapped to COHERENCY_FEATURES enum */
regdump->coherency_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(COHERENCY_FEATURES));
if (kbase_hw_has_feature(kbdev, KBASE_HW_FEATURE_CORE_FEATURES))
regdump->core_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(CORE_FEATURES));
#if MALI_USE_CSF
if (kbase_reg_is_valid(kbdev, GPU_CONTROL_ENUM(GPU_FEATURES)))
regdump->gpu_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(GPU_FEATURES));
#endif /* MALI_USE_CSF */
regdump->tiler_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(TILER_FEATURES));
regdump->l2_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(L2_FEATURES));
regdump->mem_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(MEM_FEATURES));
regdump->mmu_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(MMU_FEATURES));
#if !MALI_USE_CSF
for (i = 0; i < GPU_MAX_JOB_SLOTS; i++)
regdump->js_features[i] = kbase_reg_read32(kbdev, GPU_JS_FEATURES_OFFSET(i));
#endif /* !MALI_USE_CSF */
#if MALI_USE_CSF
#endif /* MALI_USE_CSF */
{
for (i = 0; i < BASE_GPU_NUM_TEXTURE_FEATURES_REGISTERS; i++)
regdump->texture_features[i] =
kbase_reg_read32(kbdev, GPU_TEXTURE_FEATURES_OFFSET(i));
}
/* EXYNOS TODO: determine if needed by userspace */
mali_exynos_coherency_set_coherency_feature();
regdump->coherency_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(COHERENCY_FEATURES));
if (kbase_is_gpu_removed(kbdev))
return -EIO;
return 0;
}
int kbase_backend_gpuprops_get_curr_config(struct kbase_device *kbdev,
struct kbase_current_config_regdump *curr_config_regdump)
{
if (WARN_ON(!kbdev) || WARN_ON(!curr_config_regdump))
return -EINVAL;
curr_config_regdump->mem_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(MEM_FEATURES));
curr_config_regdump->l2_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(L2_FEATURES));
curr_config_regdump->shader_present =
kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(SHADER_PRESENT));
curr_config_regdump->l2_present = kbase_reg_read64(kbdev, GPU_CONTROL_ENUM(L2_PRESENT));
if (kbase_is_gpu_removed(kbdev))
return -EIO;
return 0;
}
int kbase_backend_gpuprops_get_l2_features(struct kbase_device *kbdev,
struct kbasep_gpuprops_regdump *regdump)
{
if (kbase_hw_has_feature(kbdev, KBASE_HW_FEATURE_L2_CONFIG)) {
regdump->l2_features = KBASE_REG_READ(kbdev, GPU_CONTROL_ENUM(L2_FEATURES));
regdump->l2_config = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(L2_CONFIG));
#if MALI_USE_CSF
if (kbase_hw_has_l2_slice_hash_feature(kbdev)) {
uint i;
for (i = 0; i < GPU_L2_SLICE_HASH_COUNT; i++)
regdump->l2_slice_hash[i] =
kbase_reg_read32(kbdev, GPU_L2_SLICE_HASH_OFFSET(i));
}
#endif /* MALI_USE_CSF */
if (kbase_is_gpu_removed(kbdev))
return -EIO;
}
return 0;
}

View file

@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* GPU backend instrumentation APIs.
*/
#include <mali_kbase.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <mali_kbase_hwaccess_instr.h>
#include <device/mali_kbase_device.h>
#include <backend/gpu/mali_kbase_instr_internal.h>
#define WAIT_FOR_DUMP_TIMEOUT_MS 5000
static int wait_prfcnt_ready(struct kbase_device *kbdev)
{
u32 val;
const u32 timeout_us =
kbase_get_timeout_ms(kbdev, KBASE_PRFCNT_ACTIVE_TIMEOUT) * USEC_PER_MSEC;
const int err = kbase_reg_poll32_timeout(kbdev, GPU_CONTROL_ENUM(GPU_STATUS), val,
!(val & GPU_STATUS_PRFCNT_ACTIVE), 0, timeout_us,
false);
if (err) {
dev_err(kbdev->dev, "PRFCNT_ACTIVE bit stuck\n");
return -EBUSY;
}
return 0;
}
int kbase_instr_hwcnt_enable_internal(struct kbase_device *kbdev, struct kbase_context *kctx,
struct kbase_instr_hwcnt_enable *enable)
{
unsigned long flags;
int err = -EINVAL;
u32 irq_mask;
u32 prfcnt_config;
lockdep_assert_held(&kbdev->hwaccess_lock);
/* alignment failure */
if ((enable->dump_buffer == 0ULL) || (enable->dump_buffer & (2048 - 1)))
return err;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
if (kbdev->hwcnt.backend.state != KBASE_INSTR_STATE_DISABLED) {
/* Instrumentation is already enabled */
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return err;
}
if (kbase_is_gpu_removed(kbdev)) {
/* GPU has been removed by Arbiter */
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return err;
}
/* Enable interrupt */
irq_mask = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(GPU_IRQ_MASK));
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(GPU_IRQ_MASK),
irq_mask | PRFCNT_SAMPLE_COMPLETED);
/* In use, this context is the owner */
kbdev->hwcnt.kctx = kctx;
/* Remember the dump address so we can reprogram it later */
kbdev->hwcnt.addr = enable->dump_buffer;
kbdev->hwcnt.addr_bytes = enable->dump_buffer_bytes;
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
/* Configure */
prfcnt_config = (u32)kctx->as_nr << PRFCNT_CONFIG_AS_SHIFT;
#ifdef CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS
prfcnt_config |= (u32)kbdev->hwcnt.backend.override_counter_set
<< PRFCNT_CONFIG_SETSELECT_SHIFT;
#else
prfcnt_config |= (u32)enable->counter_set << PRFCNT_CONFIG_SETSELECT_SHIFT;
#endif
/* Wait until prfcnt config register can be written */
err = wait_prfcnt_ready(kbdev);
if (err)
return err;
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_CONFIG),
prfcnt_config | PRFCNT_CONFIG_MODE_OFF);
/* Wait until prfcnt is disabled before writing configuration registers */
err = wait_prfcnt_ready(kbdev);
if (err)
return err;
kbase_reg_write64(kbdev, GPU_CONTROL_ENUM(PRFCNT_BASE), enable->dump_buffer);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_JM_EN), enable->fe_bm);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_SHADER_EN), enable->shader_bm);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_MMU_L2_EN), enable->mmu_l2_bm);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_TILER_EN), enable->tiler_bm);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_CONFIG),
prfcnt_config | PRFCNT_CONFIG_MODE_MANUAL);
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_IDLE;
kbdev->hwcnt.backend.triggered = 1;
wake_up(&kbdev->hwcnt.backend.wait);
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
dev_dbg(kbdev->dev, "HW counters dumping set-up for context %pK", kctx);
return 0;
}
static void kbasep_instr_hwc_disable_hw_prfcnt(struct kbase_device *kbdev)
{
u32 irq_mask;
lockdep_assert_held(&kbdev->hwaccess_lock);
lockdep_assert_held(&kbdev->hwcnt.lock);
if (kbase_is_gpu_removed(kbdev))
/* GPU has been removed by Arbiter */
return;
/* Disable interrupt */
irq_mask = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(GPU_IRQ_MASK));
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(GPU_IRQ_MASK),
irq_mask & ~PRFCNT_SAMPLE_COMPLETED);
/* Wait until prfcnt config register can be written, then disable the counters.
* Return value is ignored as we are disabling anyway.
*/
wait_prfcnt_ready(kbdev);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(PRFCNT_CONFIG), 0);
kbdev->hwcnt.kctx = NULL;
kbdev->hwcnt.addr = 0ULL;
kbdev->hwcnt.addr_bytes = 0ULL;
}
int kbase_instr_hwcnt_disable_internal(struct kbase_context *kctx)
{
unsigned long flags, pm_flags;
struct kbase_device *kbdev = kctx->kbdev;
const unsigned long timeout = msecs_to_jiffies(WAIT_FOR_DUMP_TIMEOUT_MS);
unsigned int remaining;
while (1) {
spin_lock_irqsave(&kbdev->hwaccess_lock, pm_flags);
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_UNRECOVERABLE_ERROR) {
/* Instrumentation is in unrecoverable error state,
* there is nothing for us to do.
*/
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, pm_flags);
/* Already disabled, return no error. */
return 0;
}
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_DISABLED) {
/* Instrumentation is not enabled */
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, pm_flags);
return -EINVAL;
}
if (kbdev->hwcnt.kctx != kctx) {
/* Instrumentation has been setup for another context */
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, pm_flags);
return -EINVAL;
}
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_IDLE)
break;
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, pm_flags);
/* Ongoing dump/setup - wait for its completion */
remaining = wait_event_timeout(kbdev->hwcnt.backend.wait,
kbdev->hwcnt.backend.triggered != 0, timeout);
if (remaining == 0)
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_UNRECOVERABLE_ERROR;
}
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_DISABLED;
kbdev->hwcnt.backend.triggered = 0;
kbasep_instr_hwc_disable_hw_prfcnt(kbdev);
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, pm_flags);
dev_dbg(kbdev->dev, "HW counters dumping disabled for context %pK", kctx);
return 0;
}
int kbase_instr_hwcnt_request_dump(struct kbase_context *kctx)
{
unsigned long flags;
int err = -EINVAL;
struct kbase_device *kbdev = kctx->kbdev;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
if (kbdev->hwcnt.kctx != kctx) {
/* The instrumentation has been setup for another context */
goto unlock;
}
if (kbdev->hwcnt.backend.state != KBASE_INSTR_STATE_IDLE) {
/* HW counters are disabled or another dump is ongoing, or we're
* resetting, or we are in unrecoverable error state.
*/
goto unlock;
}
if (kbase_is_gpu_removed(kbdev)) {
/* GPU has been removed by Arbiter */
goto unlock;
}
kbdev->hwcnt.backend.triggered = 0;
/* Mark that we're dumping - the PF handler can signal that we faulted
*/
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_DUMPING;
/* Wait until prfcnt is ready to request dump */
err = wait_prfcnt_ready(kbdev);
if (err)
goto unlock;
/* Reconfigure the dump address */
kbase_reg_write64(kbdev, GPU_CONTROL_ENUM(PRFCNT_BASE), kbdev->hwcnt.addr);
/* Start dumping */
KBASE_KTRACE_ADD(kbdev, CORE_GPU_PRFCNT_SAMPLE, NULL, kbdev->hwcnt.addr);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(GPU_COMMAND), GPU_COMMAND_PRFCNT_SAMPLE);
dev_dbg(kbdev->dev, "HW counters dumping done for context %pK", kctx);
unlock:
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return err;
}
KBASE_EXPORT_SYMBOL(kbase_instr_hwcnt_request_dump);
bool kbase_instr_hwcnt_dump_complete(struct kbase_context *kctx, bool *const success)
{
unsigned long flags;
bool complete = false;
struct kbase_device *kbdev = kctx->kbdev;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_IDLE) {
*success = true;
complete = true;
} else if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_FAULT) {
*success = false;
complete = true;
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_IDLE;
}
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return complete;
}
KBASE_EXPORT_SYMBOL(kbase_instr_hwcnt_dump_complete);
void kbase_instr_hwcnt_sample_done(struct kbase_device *kbdev)
{
unsigned long flags;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
/* If the state is in unrecoverable error, we already wake_up the waiter
* and don't need to do any action when sample is done.
*/
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_FAULT) {
kbdev->hwcnt.backend.triggered = 1;
wake_up(&kbdev->hwcnt.backend.wait);
} else if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_DUMPING) {
/* All finished and idle */
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_IDLE;
kbdev->hwcnt.backend.triggered = 1;
wake_up(&kbdev->hwcnt.backend.wait);
}
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
}
int kbase_instr_hwcnt_wait_for_dump(struct kbase_context *kctx)
{
struct kbase_device *kbdev = kctx->kbdev;
unsigned long flags;
int err;
unsigned long remaining;
const unsigned long timeout = msecs_to_jiffies(WAIT_FOR_DUMP_TIMEOUT_MS);
/* Wait for dump & cache clean to complete */
remaining = wait_event_timeout(kbdev->hwcnt.backend.wait,
kbdev->hwcnt.backend.triggered != 0, timeout);
if (remaining == 0) {
err = -ETIME;
/* Set the backend state so it's clear things have gone bad (could be a HW issue)
*/
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_UNRECOVERABLE_ERROR;
goto timed_out;
}
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_FAULT) {
err = -EINVAL;
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_IDLE;
} else if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_UNRECOVERABLE_ERROR) {
err = -EIO;
} else {
/* Dump done */
KBASE_DEBUG_ASSERT(kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_IDLE);
err = 0;
}
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
timed_out:
return err;
}
int kbase_instr_hwcnt_clear(struct kbase_context *kctx)
{
unsigned long flags;
int err = -EINVAL;
struct kbase_device *kbdev = kctx->kbdev;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
/* Check it's the context previously set up and we're not in IDLE
* state.
*/
if (kbdev->hwcnt.kctx != kctx || kbdev->hwcnt.backend.state != KBASE_INSTR_STATE_IDLE)
goto unlock;
if (kbase_is_gpu_removed(kbdev)) {
/* GPU has been removed by Arbiter */
goto unlock;
}
/* Wait until prfcnt is ready to clear */
err = wait_prfcnt_ready(kbdev);
if (err)
goto unlock;
/* Clear the counters */
KBASE_KTRACE_ADD(kbdev, CORE_GPU_PRFCNT_CLEAR, NULL, 0);
kbase_reg_write32(kbdev, GPU_CONTROL_ENUM(GPU_COMMAND), GPU_COMMAND_PRFCNT_CLEAR);
unlock:
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return err;
}
KBASE_EXPORT_SYMBOL(kbase_instr_hwcnt_clear);
void kbase_instr_hwcnt_on_unrecoverable_error(struct kbase_device *kbdev)
{
unsigned long flags;
lockdep_assert_held(&kbdev->hwaccess_lock);
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
/* If we already in unrecoverable error state, early return. */
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_UNRECOVERABLE_ERROR) {
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
return;
}
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_UNRECOVERABLE_ERROR;
/* Need to disable HW if it's not disabled yet. */
if (kbdev->hwcnt.backend.state != KBASE_INSTR_STATE_DISABLED)
kbasep_instr_hwc_disable_hw_prfcnt(kbdev);
/* Wake up any waiters. */
kbdev->hwcnt.backend.triggered = 1;
wake_up(&kbdev->hwcnt.backend.wait);
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
}
KBASE_EXPORT_SYMBOL(kbase_instr_hwcnt_on_unrecoverable_error);
void kbase_instr_hwcnt_on_before_reset(struct kbase_device *kbdev)
{
unsigned long flags;
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
/* A reset is the only way to exit the unrecoverable error state */
if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_UNRECOVERABLE_ERROR)
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_DISABLED;
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
}
KBASE_EXPORT_SYMBOL(kbase_instr_hwcnt_on_before_reset);
int kbase_instr_backend_init(struct kbase_device *kbdev)
{
spin_lock_init(&kbdev->hwcnt.lock);
kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_DISABLED;
init_waitqueue_head(&kbdev->hwcnt.backend.wait);
#ifdef CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS
/* Use the build time option for the override default. */
#if defined(CONFIG_MALI_PRFCNT_SET_SECONDARY)
kbdev->hwcnt.backend.override_counter_set = KBASE_HWCNT_PHYSICAL_SET_SECONDARY;
#elif defined(CONFIG_MALI_PRFCNT_SET_TERTIARY)
kbdev->hwcnt.backend.override_counter_set = KBASE_HWCNT_PHYSICAL_SET_TERTIARY;
#else
/* Default to primary */
kbdev->hwcnt.backend.override_counter_set = KBASE_HWCNT_PHYSICAL_SET_PRIMARY;
#endif
#endif
return 0;
}
void kbase_instr_backend_term(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
#ifdef CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS
void kbase_instr_backend_debugfs_init(struct kbase_device *kbdev)
{
/* No validation is done on the debugfs input. Invalid input could cause
* performance counter errors. This is acceptable since this is a debug
* only feature and users should know what they are doing.
*
* Valid inputs are the values accepted bythe SET_SELECT bits of the
* PRFCNT_CONFIG register as defined in the architecture specification.
*/
debugfs_create_u8("hwcnt_set_select", 0644, kbdev->mali_debugfs_directory,
(u8 *)&kbdev->hwcnt.backend.override_counter_set);
}
#endif

View file

@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014, 2016, 2018-2022 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific instrumentation definitions
*/
#ifndef _KBASE_INSTR_DEFS_H_
#define _KBASE_INSTR_DEFS_H_
#include <hwcnt/mali_kbase_hwcnt_gpu.h>
/*
* Instrumentation State Machine States
*/
enum kbase_instr_state {
/* State where instrumentation is not active */
KBASE_INSTR_STATE_DISABLED = 0,
/* State machine is active and ready for a command. */
KBASE_INSTR_STATE_IDLE,
/* Hardware is currently dumping a frame. */
KBASE_INSTR_STATE_DUMPING,
/* An error has occurred during DUMPING (page fault). */
KBASE_INSTR_STATE_FAULT,
/* An unrecoverable error has occurred, a reset is the only way to exit
* from unrecoverable error state.
*/
KBASE_INSTR_STATE_UNRECOVERABLE_ERROR,
};
/* Structure used for instrumentation and HW counters dumping */
struct kbase_instr_backend {
wait_queue_head_t wait;
int triggered;
#ifdef CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS
enum kbase_hwcnt_physical_set override_counter_set;
#endif
enum kbase_instr_state state;
};
#endif /* _KBASE_INSTR_DEFS_H_ */

View file

@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014, 2018, 2020-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific HW access instrumentation APIs
*/
#ifndef _KBASE_INSTR_INTERNAL_H_
#define _KBASE_INSTR_INTERNAL_H_
/**
* kbasep_cache_clean_worker() - Workqueue for handling cache cleaning
* @data: a &struct work_struct
*/
void kbasep_cache_clean_worker(struct work_struct *data);
/**
* kbase_instr_hwcnt_sample_done() - Dump complete interrupt received
* @kbdev: Kbase device
*/
void kbase_instr_hwcnt_sample_done(struct kbase_device *kbdev);
#endif /* _KBASE_INSTR_INTERNAL_H_ */

View file

@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend specific IRQ APIs
*/
#ifndef _KBASE_IRQ_INTERNAL_H_
#define _KBASE_IRQ_INTERNAL_H_
/* GPU IRQ Tags */
#define JOB_IRQ_TAG 0
#define MMU_IRQ_TAG 1
#define GPU_IRQ_TAG 2
/**
* kbase_install_interrupts - Install IRQs handlers.
*
* @kbdev: The kbase device
*
* This function must be called once only when a kbase device is initialized.
*
* Return: 0 on success. Error code (negative) on failure.
*/
int kbase_install_interrupts(struct kbase_device *kbdev);
/**
* kbase_release_interrupts - Uninstall IRQs handlers.
*
* @kbdev: The kbase device
*
* This function needs to be called when a kbase device is terminated.
*/
void kbase_release_interrupts(struct kbase_device *kbdev);
/**
* kbase_synchronize_irqs - Ensure that all IRQ handlers have completed
* execution
* @kbdev: The kbase device
*/
void kbase_synchronize_irqs(struct kbase_device *kbdev);
#ifdef CONFIG_MALI_DEBUG
#if IS_ENABLED(CONFIG_MALI_REAL_HW)
/**
* kbase_validate_interrupts - Validate interrupts
*
* @kbdev: The kbase device
*
* This function will be called once when a kbase device is initialized
* to check whether interrupt handlers are configured appropriately.
* If interrupt numbers and/or flags defined in the device tree are
* incorrect, then the validation might fail.
* The whold device initialization will fail if it returns error code.
*
* Return: 0 on success. Error code (negative) on failure.
*/
int kbase_validate_interrupts(struct kbase_device *const kbdev);
#endif /* IS_ENABLED(CONFIG_MALI_REAL_HW) */
#endif /* CONFIG_MALI_DEBUG */
/**
* kbase_get_interrupt_handler - Return default interrupt handler
* @kbdev: Kbase device
* @irq_tag: Tag to choose the handler
*
* If single interrupt line is used the combined interrupt handler
* will be returned regardless of irq_tag. Otherwise the corresponding
* interrupt handler will be returned.
*
* Return: Interrupt handler corresponding to the tag. NULL on failure.
*/
irq_handler_t kbase_get_interrupt_handler(struct kbase_device *kbdev, u32 irq_tag);
/**
* kbase_set_custom_irq_handler - Set a custom IRQ handler
*
* @kbdev: The kbase device for which the handler is to be registered
* @custom_handler: Handler to be registered
* @irq_tag: Interrupt tag
*
* Register given interrupt handler for requested interrupt tag
* In the case where irq handler is not specified, the default handler shall be
* registered
*
* Return: 0 case success, error code otherwise
*/
int kbase_set_custom_irq_handler(struct kbase_device *kbdev, irq_handler_t custom_handler,
u32 irq_tag);
#endif /* _KBASE_IRQ_INTERNAL_H_ */

View file

@ -0,0 +1,494 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <device/mali_kbase_device.h>
#include <backend/gpu/mali_kbase_irq_internal.h>
#include <linux/interrupt.h>
#if IS_ENABLED(CONFIG_MALI_REAL_HW)
static void *kbase_tag(void *ptr, u32 tag)
{
return (void *)(((uintptr_t)ptr) | tag);
}
#endif
static void *kbase_untag(void *ptr)
{
return (void *)(((uintptr_t)ptr) & ~(uintptr_t)3);
}
static irqreturn_t kbase_job_irq_handler(int irq, void *data)
{
unsigned long flags;
struct kbase_device *kbdev = kbase_untag(data);
u32 val;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_powered) {
/* GPU is turned off - IRQ is not for us */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
val = kbase_reg_read32(kbdev, JOB_CONTROL_ENUM(JOB_IRQ_STATUS));
if (!val) {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
dev_dbg(kbdev->dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val);
#if MALI_USE_CSF
/* call the csf interrupt handler */
kbase_csf_interrupt(kbdev, val);
#else
kbase_job_done(kbdev, val);
#endif
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_HANDLED;
}
static irqreturn_t kbase_mmu_irq_handler(int irq, void *data)
{
unsigned long flags;
struct kbase_device *kbdev = kbase_untag(data);
u32 val;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_powered) {
/* GPU is turned off - IRQ is not for us */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
atomic_inc(&kbdev->faults_pending);
val = kbase_reg_read32(kbdev, MMU_CONTROL_ENUM(IRQ_STATUS));
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (!val) {
atomic_dec(&kbdev->faults_pending);
return IRQ_NONE;
}
dev_dbg(kbdev->dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val);
kbase_mmu_interrupt(kbdev, val);
atomic_dec(&kbdev->faults_pending);
return IRQ_HANDLED;
}
static irqreturn_t kbase_gpuonly_irq_handler(int irq, void *data)
{
unsigned long flags;
struct kbase_device *kbdev = kbase_untag(data);
u32 gpu_irq_status;
irqreturn_t irq_state = IRQ_NONE;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_powered) {
/* GPU is turned off - IRQ is not for us */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
gpu_irq_status = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(GPU_IRQ_STATUS));
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (gpu_irq_status) {
dev_dbg(kbdev->dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, gpu_irq_status);
kbase_gpu_interrupt(kbdev, gpu_irq_status);
irq_state = IRQ_HANDLED;
}
return irq_state;
}
/**
* kbase_gpu_irq_handler - GPU interrupt handler
* @irq: IRQ number
* @data: Data associated with this IRQ (i.e. kbdev)
*
* Return: IRQ_HANDLED if any interrupt request has been successfully handled.
* IRQ_NONE otherwise.
*/
static irqreturn_t kbase_gpu_irq_handler(int irq, void *data)
{
irqreturn_t irq_state = kbase_gpuonly_irq_handler(irq, data);
return irq_state;
}
/**
* kbase_combined_irq_handler - Combined interrupt handler for all interrupts
* @irq: IRQ number
* @data: Data associated with this IRQ (i.e. kbdev)
*
* This handler will be used for the GPU with single interrupt line.
*
* Return: IRQ_HANDLED if any interrupt request has been successfully handled.
* IRQ_NONE otherwise.
*/
static irqreturn_t kbase_combined_irq_handler(int irq, void *data)
{
irqreturn_t irq_state = IRQ_NONE;
irq_state |= kbase_job_irq_handler(irq, data);
irq_state |= kbase_mmu_irq_handler(irq, data);
irq_state |= kbase_gpu_irq_handler(irq, data);
return irq_state;
}
static irq_handler_t kbase_handler_table[] = {
[JOB_IRQ_TAG] = kbase_job_irq_handler,
[MMU_IRQ_TAG] = kbase_mmu_irq_handler,
[GPU_IRQ_TAG] = kbase_gpu_irq_handler,
};
irq_handler_t kbase_get_interrupt_handler(struct kbase_device *kbdev, u32 irq_tag)
{
if (kbdev->nr_irqs == 1)
return kbase_combined_irq_handler;
else if (irq_tag < ARRAY_SIZE(kbase_handler_table))
return kbase_handler_table[irq_tag];
else
return NULL;
}
#if IS_ENABLED(CONFIG_MALI_REAL_HW)
#ifdef CONFIG_MALI_DEBUG
int kbase_set_custom_irq_handler(struct kbase_device *kbdev, irq_handler_t custom_handler,
u32 irq_tag)
{
int result = 0;
irq_handler_t handler = custom_handler;
const int irq = (kbdev->nr_irqs == 1) ? 0 : irq_tag;
if (unlikely(!((irq_tag >= JOB_IRQ_TAG) && (irq_tag <= GPU_IRQ_TAG)))) {
dev_err(kbdev->dev, "Invalid irq_tag (%d)\n", irq_tag);
return -EINVAL;
}
/* Release previous handler */
if (kbdev->irqs[irq].irq)
free_irq(kbdev->irqs[irq].irq, kbase_tag(kbdev, irq));
/* If a custom handler isn't provided use the default handler */
if (!handler)
handler = kbase_get_interrupt_handler(kbdev, irq_tag);
if (request_irq(kbdev->irqs[irq].irq, handler, kbdev->irqs[irq].flags | IRQF_SHARED,
dev_name(kbdev->dev), kbase_tag(kbdev, irq)) != 0) {
result = -EINVAL;
dev_err(kbdev->dev, "Can't request interrupt %u (index %u)\n", kbdev->irqs[irq].irq,
irq_tag);
if (IS_ENABLED(CONFIG_SPARSE_IRQ))
dev_err(kbdev->dev,
"CONFIG_SPARSE_IRQ enabled - is the interrupt number correct for this config?\n");
}
return result;
}
KBASE_EXPORT_TEST_API(kbase_set_custom_irq_handler);
/* test correct interrupt assigment and reception by cpu */
struct kbasep_irq_test {
struct hrtimer timer;
wait_queue_head_t wait;
int triggered;
u32 timeout;
};
static struct kbasep_irq_test kbasep_irq_test_data;
#define IRQ_TEST_TIMEOUT 500
static irqreturn_t kbase_job_irq_test_handler(int irq, void *data)
{
unsigned long flags;
struct kbase_device *kbdev = kbase_untag(data);
u32 val;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_powered) {
/* GPU is turned off - IRQ is not for us */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
val = kbase_reg_read32(kbdev, JOB_CONTROL_ENUM(JOB_IRQ_STATUS));
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (!val)
return IRQ_NONE;
dev_dbg(kbdev->dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val);
kbasep_irq_test_data.triggered = 1;
wake_up(&kbasep_irq_test_data.wait);
kbase_reg_write32(kbdev, JOB_CONTROL_ENUM(JOB_IRQ_CLEAR), val);
return IRQ_HANDLED;
}
static irqreturn_t kbase_mmu_irq_test_handler(int irq, void *data)
{
unsigned long flags;
struct kbase_device *kbdev = kbase_untag(data);
u32 val;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_powered) {
/* GPU is turned off - IRQ is not for us */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return IRQ_NONE;
}
val = kbase_reg_read32(kbdev, MMU_CONTROL_ENUM(IRQ_STATUS));
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (!val)
return IRQ_NONE;
dev_dbg(kbdev->dev, "%s: irq %d irqstatus 0x%x\n", __func__, irq, val);
kbasep_irq_test_data.triggered = 1;
wake_up(&kbasep_irq_test_data.wait);
kbase_reg_write32(kbdev, MMU_CONTROL_ENUM(IRQ_CLEAR), val);
return IRQ_HANDLED;
}
static enum hrtimer_restart kbasep_test_interrupt_timeout(struct hrtimer *timer)
{
struct kbasep_irq_test *test_data = container_of(timer, struct kbasep_irq_test, timer);
test_data->timeout = 1;
test_data->triggered = 1;
wake_up(&test_data->wait);
return HRTIMER_NORESTART;
}
/**
* validate_interrupt - Validate an interrupt
* @kbdev: Kbase device
* @tag: Tag to choose the interrupt
*
* To validate the settings for the interrupt, write a value on RAWSTAT
* register to trigger interrupt. Then with custom interrupt handler
* check whether the interrupt happens within reasonable time.
*
* Return: 0 if validating interrupt succeeds.
*/
static int validate_interrupt(struct kbase_device *const kbdev, u32 tag)
{
int err = 0;
irq_handler_t handler;
const int irq = (kbdev->nr_irqs == 1) ? 0 : tag;
u32 old_mask_val;
u16 mask_offset;
u16 rawstat_offset;
switch (tag) {
case JOB_IRQ_TAG:
handler = kbase_job_irq_test_handler;
rawstat_offset = JOB_CONTROL_ENUM(JOB_IRQ_RAWSTAT);
mask_offset = JOB_CONTROL_ENUM(JOB_IRQ_MASK);
break;
case MMU_IRQ_TAG:
handler = kbase_mmu_irq_test_handler;
rawstat_offset = MMU_CONTROL_ENUM(IRQ_RAWSTAT);
mask_offset = MMU_CONTROL_ENUM(IRQ_MASK);
break;
case GPU_IRQ_TAG:
/* already tested by pm_driver - bail out */
return 0;
default:
dev_err(kbdev->dev, "Invalid tag (%d)\n", tag);
return -EINVAL;
}
/* store old mask */
old_mask_val = kbase_reg_read32(kbdev, mask_offset);
/* mask interrupts */
kbase_reg_write32(kbdev, mask_offset, 0x0);
if (kbdev->irqs[irq].irq) {
/* release original handler and install test handler */
if (kbase_set_custom_irq_handler(kbdev, handler, tag) != 0) {
err = -EINVAL;
} else {
kbasep_irq_test_data.timeout = 0;
hrtimer_init(&kbasep_irq_test_data.timer, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
kbasep_irq_test_data.timer.function = kbasep_test_interrupt_timeout;
/* trigger interrupt */
kbase_reg_write32(kbdev, mask_offset, 0x1);
kbase_reg_write32(kbdev, rawstat_offset, 0x1);
hrtimer_start(&kbasep_irq_test_data.timer,
HR_TIMER_DELAY_MSEC(IRQ_TEST_TIMEOUT), HRTIMER_MODE_REL);
wait_event(kbasep_irq_test_data.wait, kbasep_irq_test_data.triggered != 0);
if (kbasep_irq_test_data.timeout != 0) {
dev_err(kbdev->dev, "Interrupt %u (index %u) didn't reach CPU.\n",
kbdev->irqs[irq].irq, irq);
err = -EINVAL;
} else {
dev_dbg(kbdev->dev, "Interrupt %u (index %u) reached CPU.\n",
kbdev->irqs[irq].irq, irq);
}
hrtimer_cancel(&kbasep_irq_test_data.timer);
kbasep_irq_test_data.triggered = 0;
/* mask interrupts */
kbase_reg_write32(kbdev, mask_offset, 0x0);
/* release test handler */
free_irq(kbdev->irqs[irq].irq, kbase_tag(kbdev, irq));
}
/* restore original interrupt */
if (request_irq(kbdev->irqs[irq].irq, kbase_get_interrupt_handler(kbdev, tag),
kbdev->irqs[irq].flags | IRQF_SHARED, dev_name(kbdev->dev),
kbase_tag(kbdev, irq))) {
dev_err(kbdev->dev, "Can't restore original interrupt %u (index %u)\n",
kbdev->irqs[irq].irq, tag);
err = -EINVAL;
}
}
/* restore old mask */
kbase_reg_write32(kbdev, mask_offset, old_mask_val);
return err;
}
#if IS_ENABLED(CONFIG_MALI_REAL_HW)
int kbase_validate_interrupts(struct kbase_device *const kbdev)
{
int err;
init_waitqueue_head(&kbasep_irq_test_data.wait);
kbasep_irq_test_data.triggered = 0;
/* A suspend won't happen during startup/insmod */
kbase_pm_context_active(kbdev);
err = validate_interrupt(kbdev, JOB_IRQ_TAG);
if (err) {
dev_err(kbdev->dev,
"Interrupt JOB_IRQ didn't reach CPU. Check interrupt assignments.\n");
goto out;
}
err = validate_interrupt(kbdev, MMU_IRQ_TAG);
if (err) {
dev_err(kbdev->dev,
"Interrupt MMU_IRQ didn't reach CPU. Check interrupt assignments.\n");
goto out;
}
dev_dbg(kbdev->dev, "Interrupts are correctly assigned.\n");
out:
kbase_pm_context_idle(kbdev);
return err;
}
#endif /* CONFIG_MALI_REAL_HW */
#endif /* CONFIG_MALI_DEBUG */
int kbase_install_interrupts(struct kbase_device *kbdev)
{
u32 i;
for (i = 0; i < kbdev->nr_irqs; i++) {
const int result = request_irq(kbdev->irqs[i].irq,
kbase_get_interrupt_handler(kbdev, i),
kbdev->irqs[i].flags | IRQF_SHARED,
dev_name(kbdev->dev), kbase_tag(kbdev, i));
if (result) {
dev_err(kbdev->dev, "Can't request interrupt %u (index %u)\n",
kbdev->irqs[i].irq, i);
goto release;
}
}
return 0;
release:
if (IS_ENABLED(CONFIG_SPARSE_IRQ))
dev_err(kbdev->dev,
"CONFIG_SPARSE_IRQ enabled - is the interrupt number correct for this config?\n");
while (i-- > 0)
free_irq(kbdev->irqs[i].irq, kbase_tag(kbdev, i));
return -EINVAL;
}
void kbase_release_interrupts(struct kbase_device *kbdev)
{
u32 i;
for (i = 0; i < kbdev->nr_irqs; i++) {
if (kbdev->irqs[i].irq)
free_irq(kbdev->irqs[i].irq, kbase_tag(kbdev, i));
}
}
void kbase_synchronize_irqs(struct kbase_device *kbdev)
{
u32 i;
for (i = 0; i < kbdev->nr_irqs; i++) {
if (kbdev->irqs[i].irq)
synchronize_irq(kbdev->irqs[i].irq);
}
}
KBASE_EXPORT_TEST_API(kbase_synchronize_irqs);
#endif /* IS_ENABLED(CONFIG_MALI_REAL_HW) */

View file

@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Register backend context / address space management
*/
#include <mali_kbase.h>
#include <mali_kbase_hwaccess_jm.h>
#include <mali_kbase_ctx_sched.h>
/**
* assign_and_activate_kctx_addr_space - Assign an AS to a context
* @kbdev: Kbase device
* @kctx: Kbase context
* @current_as: Address Space to assign
*
* Assign an Address Space (AS) to a context, and add the context to the Policy.
*
* This includes
* setting up the global runpool_irq structure and the context on the AS,
* Activating the MMU on the AS,
* Allowing jobs to be submitted on the AS.
*
* Context:
* kbasep_js_kctx_info.jsctx_mutex held,
* kbasep_js_device_data.runpool_mutex held,
* AS transaction mutex held,
* Runpool IRQ lock held
*/
static void assign_and_activate_kctx_addr_space(struct kbase_device *kbdev,
struct kbase_context *kctx,
struct kbase_as *current_as)
{
struct kbasep_js_device_data *js_devdata = &kbdev->js_data;
CSTD_UNUSED(current_as);
lockdep_assert_held(&kctx->jctx.sched_info.ctx.jsctx_mutex);
lockdep_assert_held(&js_devdata->runpool_mutex);
lockdep_assert_held(&kbdev->hwaccess_lock);
#if !MALI_USE_CSF
/* Attribute handling */
kbasep_js_ctx_attr_runpool_retain_ctx(kbdev, kctx);
#endif
/* Allow it to run jobs */
kbasep_js_set_submit_allowed(js_devdata, kctx);
kbase_js_runpool_inc_context_count(kbdev, kctx);
}
bool kbase_backend_use_ctx_sched(struct kbase_device *kbdev, struct kbase_context *kctx,
unsigned int js)
{
int i;
if (kbdev->hwaccess.active_kctx[js] == kctx) {
/* Context is already active */
return true;
}
for (i = 0; i < kbdev->nr_hw_address_spaces; i++) {
if (kbdev->as_to_kctx[i] == kctx) {
/* Context already has ASID - mark as active */
return true;
}
}
/* Context does not have address space assigned */
return false;
}
void kbase_backend_release_ctx_irq(struct kbase_device *kbdev, struct kbase_context *kctx)
{
int as_nr = kctx->as_nr;
if (as_nr == KBASEP_AS_NR_INVALID) {
WARN(1, "Attempting to release context without ASID\n");
return;
}
lockdep_assert_held(&kbdev->hwaccess_lock);
if (atomic_read(&kctx->refcount) != 1) {
WARN(1, "Attempting to release active ASID\n");
return;
}
kbasep_js_clear_submit_allowed(&kbdev->js_data, kctx);
kbase_ctx_sched_release_ctx(kctx);
kbase_js_runpool_dec_context_count(kbdev, kctx);
}
void kbase_backend_release_ctx_noirq(struct kbase_device *kbdev, struct kbase_context *kctx)
{
CSTD_UNUSED(kbdev);
CSTD_UNUSED(kctx);
}
int kbase_backend_find_and_release_free_address_space(struct kbase_device *kbdev,
struct kbase_context *kctx)
{
struct kbasep_js_device_data *js_devdata;
struct kbasep_js_kctx_info *js_kctx_info;
unsigned long flags;
int i;
js_devdata = &kbdev->js_data;
js_kctx_info = &kctx->jctx.sched_info;
mutex_lock(&js_kctx_info->ctx.jsctx_mutex);
mutex_lock(&js_devdata->runpool_mutex);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
for (i = 0; i < kbdev->nr_hw_address_spaces; i++) {
struct kbasep_js_kctx_info *as_js_kctx_info;
struct kbase_context *as_kctx;
as_kctx = kbdev->as_to_kctx[i];
as_js_kctx_info = &as_kctx->jctx.sched_info;
/* Don't release privileged or active contexts, or contexts with
* jobs running.
* Note that a context will have at least 1 reference (which
* was previously taken by kbasep_js_schedule_ctx()) until
* descheduled.
*/
if (as_kctx && !kbase_ctx_flag(as_kctx, KCTX_PRIVILEGED) &&
atomic_read(&as_kctx->refcount) == 1) {
if (!kbase_ctx_sched_inc_refcount_nolock(as_kctx)) {
WARN(1, "Failed to retain active context\n");
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
mutex_unlock(&js_devdata->runpool_mutex);
mutex_unlock(&js_kctx_info->ctx.jsctx_mutex);
return KBASEP_AS_NR_INVALID;
}
kbasep_js_clear_submit_allowed(js_devdata, as_kctx);
/* Drop and retake locks to take the jsctx_mutex on the
* context we're about to release without violating lock
* ordering
*/
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
mutex_unlock(&js_devdata->runpool_mutex);
mutex_unlock(&js_kctx_info->ctx.jsctx_mutex);
/* Release context from address space */
mutex_lock(&as_js_kctx_info->ctx.jsctx_mutex);
mutex_lock(&js_devdata->runpool_mutex);
kbasep_js_runpool_release_ctx_nolock(kbdev, as_kctx);
if (!kbase_ctx_flag(as_kctx, KCTX_SCHEDULED)) {
kbasep_js_runpool_requeue_or_kill_ctx(kbdev, as_kctx, true);
mutex_unlock(&js_devdata->runpool_mutex);
mutex_unlock(&as_js_kctx_info->ctx.jsctx_mutex);
return i;
}
/* Context was retained while locks were dropped,
* continue looking for free AS
*/
mutex_unlock(&js_devdata->runpool_mutex);
mutex_unlock(&as_js_kctx_info->ctx.jsctx_mutex);
mutex_lock(&js_kctx_info->ctx.jsctx_mutex);
mutex_lock(&js_devdata->runpool_mutex);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
}
}
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
mutex_unlock(&js_devdata->runpool_mutex);
mutex_unlock(&js_kctx_info->ctx.jsctx_mutex);
return KBASEP_AS_NR_INVALID;
}
bool kbase_backend_use_ctx(struct kbase_device *kbdev, struct kbase_context *kctx, int as_nr)
{
struct kbasep_js_device_data *js_devdata;
struct kbase_as *new_address_space = NULL;
int js;
js_devdata = &kbdev->js_data;
for (js = 0; js < BASE_JM_MAX_NR_SLOTS; js++) {
if (kbdev->hwaccess.active_kctx[js] == kctx) {
WARN(1, "Context is already scheduled in\n");
return false;
}
}
new_address_space = &kbdev->as[as_nr];
lockdep_assert_held(&js_devdata->runpool_mutex);
lockdep_assert_held(&kbdev->mmu_hw_mutex);
lockdep_assert_held(&kbdev->hwaccess_lock);
assign_and_activate_kctx_addr_space(kbdev, kctx, new_address_space);
if (kbase_ctx_flag(kctx, KCTX_PRIVILEGED)) {
/* We need to retain it to keep the corresponding address space
*/
kbase_ctx_sched_retain_ctx_refcount(kctx);
}
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2011-2016, 2018-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Job Manager backend-specific low-level APIs.
*/
#ifndef _KBASE_JM_HWACCESS_H_
#define _KBASE_JM_HWACCESS_H_
#include <mali_kbase_hw.h>
#include <mali_kbase_debug.h>
#include <linux/atomic.h>
#include <backend/gpu/mali_kbase_jm_rb.h>
#include <device/mali_kbase_device.h>
/**
* kbase_job_done_slot() - Complete the head job on a particular job-slot
* @kbdev: Device pointer
* @s: Job slot
* @completion_code: Completion code of job reported by GPU
* @job_tail: Job tail address reported by GPU
* @end_timestamp: Timestamp of job completion
*/
void kbase_job_done_slot(struct kbase_device *kbdev, int s, u32 completion_code, u64 job_tail,
ktime_t *end_timestamp);
#if IS_ENABLED(CONFIG_GPU_TRACEPOINTS)
static inline char *kbasep_make_job_slot_string(unsigned int js, char *js_string, size_t js_size)
{
(void)scnprintf(js_string, js_size, "job_slot_%u", js);
return js_string;
}
#endif
/**
* kbase_job_hw_submit() - Submit a job to the GPU
* @kbdev: Device pointer
* @katom: Atom to submit
* @js: Job slot to submit on
*
* The caller must check kbasep_jm_is_submit_slots_free() != false before
* calling this.
*
* The following locking conditions are made on the caller:
* - it must hold the hwaccess_lock
*
* Return: 0 if the job was successfully submitted to hardware, an error otherwise.
*/
int kbase_job_hw_submit(struct kbase_device *kbdev, struct kbase_jd_atom *katom, unsigned int js);
#if !MALI_USE_CSF
/**
* kbasep_job_slot_soft_or_hard_stop_do_action() - Perform a soft or hard stop
* on the specified atom
* @kbdev: Device pointer
* @js: Job slot to stop on
* @action: The action to perform, either JS_COMMAND_HARD_STOP or
* JS_COMMAND_SOFT_STOP
* @core_reqs: Core requirements of atom to stop
* @target_katom: Atom to stop
*
* The following locking conditions are made on the caller:
* - it must hold the hwaccess_lock
*/
void kbasep_job_slot_soft_or_hard_stop_do_action(struct kbase_device *kbdev, unsigned int js,
u32 action, base_jd_core_req core_reqs,
struct kbase_jd_atom *target_katom);
#endif /* !MALI_USE_CSF */
/**
* kbase_backend_soft_hard_stop_slot() - Soft or hard stop jobs on a given job
* slot belonging to a given context.
* @kbdev: Device pointer
* @kctx: Context pointer. May be NULL
* @katom: Specific atom to stop. May be NULL
* @js: Job slot to hard stop
* @action: The action to perform, either JS_COMMAND_HARD_STOP or
* JS_COMMAND_SOFT_STOP
*
* If no context is provided then all jobs on the slot will be soft or hard
* stopped.
*
* If a katom is provided then only that specific atom will be stopped. In this
* case the kctx parameter is ignored.
*
* Jobs that are on the slot but are not yet on the GPU will be unpulled and
* returned to the job scheduler.
*
* Return: true if an atom was stopped, false otherwise
*/
bool kbase_backend_soft_hard_stop_slot(struct kbase_device *kbdev, struct kbase_context *kctx,
unsigned int js, struct kbase_jd_atom *katom, u32 action);
/**
* kbase_job_slot_init - Initialise job slot framework
* @kbdev: Device pointer
*
* Called on driver initialisation
*
* Return: 0 on success
*/
int kbase_job_slot_init(struct kbase_device *kbdev);
/**
* kbase_job_slot_halt - Halt the job slot framework
* @kbdev: Device pointer
*
* Should prevent any further job slot processing
*/
void kbase_job_slot_halt(struct kbase_device *kbdev);
/**
* kbase_job_slot_term - Terminate job slot framework
* @kbdev: Device pointer
*
* Called on driver termination
*/
void kbase_job_slot_term(struct kbase_device *kbdev);
/**
* kbase_gpu_cache_clean - Cause a GPU cache clean & flush
* @kbdev: Device pointer
*
* Caller must not be in IRQ context
*/
void kbase_gpu_cache_clean(struct kbase_device *kbdev);
#endif /* _KBASE_JM_HWACCESS_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2018, 2020-2022 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Register-based HW access backend specific APIs
*/
#ifndef _KBASE_HWACCESS_GPU_H_
#define _KBASE_HWACCESS_GPU_H_
#include <backend/gpu/mali_kbase_pm_internal.h>
/**
* kbase_gpu_irq_evict - Evict an atom from a NEXT slot
*
* @kbdev: Device pointer
* @js: Job slot to evict from
* @completion_code: Event code from job that was run.
*
* Evict the atom in the NEXT slot for the specified job slot. This function is
* called from the job complete IRQ handler when the previous job has failed.
*
* Return: true if job evicted from NEXT registers, false otherwise
*/
bool kbase_gpu_irq_evict(struct kbase_device *kbdev, unsigned int js, u32 completion_code);
/**
* kbase_gpu_complete_hw - Complete an atom on job slot js
*
* @kbdev: Device pointer
* @js: Job slot that has completed
* @completion_code: Event code from job that has completed
* @job_tail: The tail address from the hardware if the job has partially
* completed
* @end_timestamp: Time of completion
*/
void kbase_gpu_complete_hw(struct kbase_device *kbdev, unsigned int js, u32 completion_code,
u64 job_tail, ktime_t *end_timestamp);
/**
* kbase_gpu_inspect - Inspect the contents of the HW access ringbuffer
*
* @kbdev: Device pointer
* @js: Job slot to inspect
* @idx: Index into ringbuffer. 0 is the job currently running on
* the slot, 1 is the job waiting, all other values are invalid.
* Return: The atom at that position in the ringbuffer
* or NULL if no atom present
*/
struct kbase_jd_atom *kbase_gpu_inspect(struct kbase_device *kbdev, unsigned int js, int idx);
/**
* kbase_gpu_dump_slots - Print the contents of the slot ringbuffers
*
* @kbdev: Device pointer
*/
void kbase_gpu_dump_slots(struct kbase_device *kbdev);
#endif /* _KBASE_HWACCESS_GPU_H_ */

View file

@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Register-based HW access backend specific job scheduler APIs
*/
#include <mali_kbase.h>
#include <mali_kbase_hwaccess_jm.h>
#include <mali_kbase_reset_gpu.h>
#include <backend/gpu/mali_kbase_jm_internal.h>
#include <backend/gpu/mali_kbase_js_internal.h>
#if IS_ENABLED(CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD)
#include <mali_kbase_gpu_metrics.h>
#endif
/*
* Hold the runpool_mutex for this
*/
static inline bool timer_callback_should_run(struct kbase_device *kbdev, int nr_running_ctxs)
{
lockdep_assert_held(&kbdev->js_data.runpool_mutex);
#ifdef CONFIG_MALI_DEBUG
if (kbdev->js_data.softstop_always) {
/* Debug support for allowing soft-stop on a single context */
return true;
}
#endif /* CONFIG_MALI_DEBUG */
if (kbase_hw_has_issue(kbdev, KBASE_HW_ISSUE_9435)) {
/* Timeouts would have to be 4x longer (due to micro-
* architectural design) to support OpenCL conformance tests, so
* only run the timer when there's:
* - 2 or more CL contexts
* - 1 or more GLES contexts
*
* NOTE: We will treat a context that has both Compute and Non-
* Compute jobs will be treated as an OpenCL context (hence, we
* don't check KBASEP_JS_CTX_ATTR_NON_COMPUTE).
*/
{
int nr_compute_ctxs = kbasep_js_ctx_attr_count_on_runpool(
kbdev, KBASEP_JS_CTX_ATTR_COMPUTE);
int nr_noncompute_ctxs = nr_running_ctxs - nr_compute_ctxs;
return (bool)(nr_compute_ctxs >= 2 || nr_noncompute_ctxs > 0);
}
} else {
/* Run the timer callback whenever you have at least 1 context
*/
return (bool)(nr_running_ctxs > 0);
}
}
static enum hrtimer_restart timer_callback(struct hrtimer *timer)
{
unsigned long flags;
struct kbase_device *kbdev;
struct kbasep_js_device_data *js_devdata;
struct kbase_backend_data *backend;
unsigned int s;
bool reset_needed = false;
KBASE_DEBUG_ASSERT(timer != NULL);
backend = container_of(timer, struct kbase_backend_data, scheduling_timer);
kbdev = container_of(backend, struct kbase_device, hwaccess.backend);
js_devdata = &kbdev->js_data;
/* Loop through the slots */
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
for (s = 0; s < kbdev->gpu_props.num_job_slots; s++) {
struct kbase_jd_atom *atom = NULL;
if (kbase_backend_nr_atoms_on_slot(kbdev, s) > 0) {
atom = kbase_gpu_inspect(kbdev, s, 0);
KBASE_DEBUG_ASSERT(atom != NULL);
}
if (atom != NULL) {
/* The current version of the model doesn't support
* Soft-Stop
*/
if (!kbase_hw_has_issue(kbdev, KBASE_HW_ISSUE_5736)) {
u32 ticks = atom->ticks++;
#if !defined(CONFIG_MALI_JOB_DUMP) && !defined(CONFIG_MALI_VECTOR_DUMP)
u32 soft_stop_ticks, hard_stop_ticks, gpu_reset_ticks;
if (atom->core_req & BASE_JD_REQ_ONLY_COMPUTE) {
soft_stop_ticks = js_devdata->soft_stop_ticks_cl;
hard_stop_ticks = js_devdata->hard_stop_ticks_cl;
gpu_reset_ticks = js_devdata->gpu_reset_ticks_cl;
} else {
soft_stop_ticks = js_devdata->soft_stop_ticks;
hard_stop_ticks = js_devdata->hard_stop_ticks_ss;
gpu_reset_ticks = js_devdata->gpu_reset_ticks_ss;
}
/* If timeouts have been changed then ensure
* that atom tick count is not greater than the
* new soft_stop timeout. This ensures that
* atoms do not miss any of the timeouts due to
* races between this worker and the thread
* changing the timeouts.
*/
if (backend->timeouts_updated && ticks > soft_stop_ticks)
ticks = atom->ticks = soft_stop_ticks;
/* Job is Soft-Stoppable */
if (ticks == soft_stop_ticks) {
/* Job has been scheduled for at least
* js_devdata->soft_stop_ticks ticks.
* Soft stop the slot so we can run
* other jobs.
*/
#if !KBASE_DISABLE_SCHEDULING_SOFT_STOPS
int disjoint_threshold =
KBASE_DISJOINT_STATE_INTERLEAVED_CONTEXT_COUNT_THRESHOLD;
u32 softstop_flags = 0u;
dev_dbg(kbdev->dev, "Soft-stop");
/* nr_user_contexts_running is updated
* with the runpool_mutex, but we can't
* take that here.
*
* However, if it's about to be
* increased then the new context can't
* run any jobs until they take the
* hwaccess_lock, so it's OK to observe
* the older value.
*
* Similarly, if it's about to be
* decreased, the last job from another
* context has already finished, so
* it's not too bad that we observe the
* older value and register a disjoint
* event when we try soft-stopping
*/
if (js_devdata->nr_user_contexts_running >=
disjoint_threshold)
softstop_flags |= JS_COMMAND_SW_CAUSES_DISJOINT;
kbase_job_slot_softstop_swflags(kbdev, s, atom,
softstop_flags);
#endif
} else if (ticks == hard_stop_ticks) {
/* Job has been scheduled for at least
* js_devdata->hard_stop_ticks_ss ticks.
* It should have been soft-stopped by
* now. Hard stop the slot.
*/
#if !KBASE_DISABLE_SCHEDULING_HARD_STOPS
u32 ms = js_devdata->scheduling_period_ns / 1000000u;
dev_warn(
kbdev->dev,
"JS: Job Hard-Stopped (took more than %u ticks at %u ms/tick)",
ticks, ms);
kbase_job_slot_hardstop(atom->kctx, s, atom);
#endif
} else if (ticks == gpu_reset_ticks) {
/* Job has been scheduled for at least
* js_devdata->gpu_reset_ticks_ss ticks.
* It should have left the GPU by now.
* Signal that the GPU needs to be
* reset.
*/
reset_needed = true;
}
#else /* !CONFIG_MALI_JOB_DUMP */
/* NOTE: During CONFIG_MALI_JOB_DUMP, we use
* the alternate timeouts, which makes the hard-
* stop and GPU reset timeout much longer. We
* also ensure that we don't soft-stop at all.
*/
if (ticks == js_devdata->soft_stop_ticks) {
/* Job has been scheduled for at least
* js_devdata->soft_stop_ticks. We do
* not soft-stop during
* CONFIG_MALI_JOB_DUMP, however.
*/
dev_dbg(kbdev->dev, "Soft-stop");
} else if (ticks == js_devdata->hard_stop_ticks_dumping) {
/* Job has been scheduled for at least
* js_devdata->hard_stop_ticks_dumping
* ticks. Hard stop the slot.
*/
#if !KBASE_DISABLE_SCHEDULING_HARD_STOPS
u32 ms = js_devdata->scheduling_period_ns / 1000000u;
dev_warn(
kbdev->dev,
"JS: Job Hard-Stopped (took more than %u ticks at %u ms/tick)",
ticks, ms);
kbase_job_slot_hardstop(atom->kctx, s, atom);
#endif
} else if (ticks == js_devdata->gpu_reset_ticks_dumping) {
/* Job has been scheduled for at least
* js_devdata->gpu_reset_ticks_dumping
* ticks. It should have left the GPU by
* now. Signal that the GPU needs to be
* reset.
*/
reset_needed = true;
}
#endif /* !CONFIG_MALI_JOB_DUMP */
}
}
}
if (reset_needed) {
dev_err(kbdev->dev,
"JS: Job has been on the GPU for too long (JS_RESET_TICKS_SS/DUMPING timeout hit). Issuing GPU soft-reset to resolve.");
if (kbase_prepare_to_reset_gpu_locked(kbdev, RESET_FLAGS_NONE))
kbase_reset_gpu_locked(kbdev);
}
/* the timer is re-issued if there is contexts in the run-pool */
if (backend->timer_running)
hrtimer_start(&backend->scheduling_timer,
HR_TIMER_DELAY_NSEC(js_devdata->scheduling_period_ns),
HRTIMER_MODE_REL);
backend->timeouts_updated = false;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return HRTIMER_NORESTART;
}
void kbase_backend_ctx_count_changed(struct kbase_device *kbdev)
{
struct kbasep_js_device_data *js_devdata = &kbdev->js_data;
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
unsigned long flags;
/* Timer must stop if we are suspending */
const bool suspend_timer = backend->suspend_timer;
const int nr_running_ctxs = atomic_read(&kbdev->js_data.nr_contexts_runnable);
lockdep_assert_held(&js_devdata->runpool_mutex);
if (suspend_timer || !timer_callback_should_run(kbdev, nr_running_ctxs)) {
/* Take spinlock to force synchronisation with timer */
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
backend->timer_running = false;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
/* From now on, return value of timer_callback_should_run()
* will also cause the timer to not requeue itself. Its return
* value cannot change, because it depends on variables updated
* with the runpool_mutex held, which the caller of this must
* also hold
*/
hrtimer_cancel(&backend->scheduling_timer);
}
if (!suspend_timer && timer_callback_should_run(kbdev, nr_running_ctxs) &&
!backend->timer_running) {
/* Take spinlock to force synchronisation with timer */
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
backend->timer_running = true;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
hrtimer_start(&backend->scheduling_timer,
HR_TIMER_DELAY_NSEC(js_devdata->scheduling_period_ns),
HRTIMER_MODE_REL);
KBASE_KTRACE_ADD_JM(kbdev, JS_POLICY_TIMER_START, NULL, NULL, 0u, 0u);
}
#if IS_ENABLED(CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD)
if (unlikely(suspend_timer)) {
js_devdata->gpu_metrics_timer_needed = false;
/* Cancel the timer as System suspend is happening */
hrtimer_cancel(&js_devdata->gpu_metrics_timer);
js_devdata->gpu_metrics_timer_running = false;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
/* Explicitly emit the tracepoint on System suspend */
kbase_gpu_metrics_emit_tracepoint(kbdev, ktime_get_raw_ns());
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return;
}
if (!nr_running_ctxs) {
/* Just set the flag to not restart the timer on expiry */
js_devdata->gpu_metrics_timer_needed = false;
return;
}
/* There are runnable contexts so the timer is needed */
if (!js_devdata->gpu_metrics_timer_needed) {
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
js_devdata->gpu_metrics_timer_needed = true;
/* No need to restart the timer if it is already running. */
if (!js_devdata->gpu_metrics_timer_running) {
hrtimer_start(&js_devdata->gpu_metrics_timer,
HR_TIMER_DELAY_NSEC(kbase_gpu_metrics_get_tp_emit_interval()),
HRTIMER_MODE_REL);
js_devdata->gpu_metrics_timer_running = true;
}
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
#endif
}
int kbase_backend_timer_init(struct kbase_device *kbdev)
{
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
hrtimer_init(&backend->scheduling_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
backend->scheduling_timer.function = timer_callback;
backend->timer_running = false;
return 0;
}
void kbase_backend_timer_term(struct kbase_device *kbdev)
{
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
hrtimer_cancel(&backend->scheduling_timer);
}
void kbase_backend_timer_suspend(struct kbase_device *kbdev)
{
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
backend->suspend_timer = true;
kbase_backend_ctx_count_changed(kbdev);
}
void kbase_backend_timer_resume(struct kbase_device *kbdev)
{
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
backend->suspend_timer = false;
kbase_backend_ctx_count_changed(kbdev);
}
void kbase_backend_timeouts_changed(struct kbase_device *kbdev)
{
struct kbase_backend_data *backend = &kbdev->hwaccess.backend;
backend->timeouts_updated = true;
}

View file

@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2015, 2020-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Register-based HW access backend specific job scheduler APIs
*/
#ifndef _KBASE_JS_BACKEND_H_
#define _KBASE_JS_BACKEND_H_
/**
* kbase_backend_timer_init() - Initialise the JS scheduling timer
* @kbdev: Device pointer
*
* This function should be called at driver initialisation
*
* Return: 0 on success
*/
int kbase_backend_timer_init(struct kbase_device *kbdev);
/**
* kbase_backend_timer_term() - Terminate the JS scheduling timer
* @kbdev: Device pointer
*
* This function should be called at driver termination
*/
void kbase_backend_timer_term(struct kbase_device *kbdev);
/**
* kbase_backend_timer_suspend - Suspend is happening, stop the JS scheduling
* timer
* @kbdev: Device pointer
*
* This function should be called on suspend, after the active count has reached
* zero. This is required as the timer may have been started on job submission
* to the job scheduler, but before jobs are submitted to the GPU.
*
* Caller must hold runpool_mutex.
*/
void kbase_backend_timer_suspend(struct kbase_device *kbdev);
/**
* kbase_backend_timer_resume - Resume is happening, re-evaluate the JS
* scheduling timer
* @kbdev: Device pointer
*
* This function should be called on resume. Note that is not guaranteed to
* re-start the timer, only evalute whether it should be re-started.
*
* Caller must hold runpool_mutex.
*/
void kbase_backend_timer_resume(struct kbase_device *kbdev);
#endif /* _KBASE_JS_BACKEND_H_ */

View file

@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <linux/version_compat_defs.h>
#include <mali_kbase.h>
#include <mali_kbase_config_defaults.h>
#include <device/mali_kbase_device.h>
#include "mali_kbase_l2_mmu_config.h"
/**
* struct l2_mmu_config_limit_region - L2 MMU limit field
*
* @value: The default value to load into the L2_MMU_CONFIG register
* @mask: The shifted mask of the field in the L2_MMU_CONFIG register
* @shift: The shift of where the field starts in the L2_MMU_CONFIG register
* This should be the same value as the smaller of the two mask
* values
*/
struct l2_mmu_config_limit_region {
u32 value, mask, shift;
};
/**
* struct l2_mmu_config_limit - L2 MMU read and write limit
*
* @product_model: The GPU for which this entry applies
* @read: Values for the read limit field
* @write: Values for the write limit field
*/
struct l2_mmu_config_limit {
u32 product_model;
struct l2_mmu_config_limit_region read;
struct l2_mmu_config_limit_region write;
};
/*
* Zero represents no limit
*
* For LBEX TBEX TBAX TTRX and TNAX:
* The value represents the number of outstanding reads (6 bits) or writes (5 bits)
*
* For all other GPUS it is a fraction see: mali_kbase_config_defaults.h
*/
static const struct l2_mmu_config_limit limits[] = {
/* GPU, read, write */
{ GPU_ID_PRODUCT_LBEX, { 0, GENMASK(10, 5), 5 }, { 0, GENMASK(16, 12), 12 } },
{ GPU_ID_PRODUCT_TBEX, { 0, GENMASK(10, 5), 5 }, { 0, GENMASK(16, 12), 12 } },
{ GPU_ID_PRODUCT_TBAX, { 0, GENMASK(10, 5), 5 }, { 0, GENMASK(16, 12), 12 } },
{ GPU_ID_PRODUCT_TTRX, { 0, GENMASK(12, 7), 7 }, { 0, GENMASK(17, 13), 13 } },
{ GPU_ID_PRODUCT_TNAX, { 0, GENMASK(12, 7), 7 }, { 0, GENMASK(17, 13), 13 } },
{ GPU_ID_PRODUCT_TGOX,
{ KBASE_3BIT_AID_32, GENMASK(14, 12), 12 },
{ KBASE_3BIT_AID_32, GENMASK(17, 15), 15 } },
{ GPU_ID_PRODUCT_TNOX,
{ KBASE_3BIT_AID_32, GENMASK(14, 12), 12 },
{ KBASE_3BIT_AID_32, GENMASK(17, 15), 15 } },
};
int kbase_set_mmu_quirks(struct kbase_device *kbdev)
{
/* All older GPUs had 2 bits for both fields, this is a default */
struct l2_mmu_config_limit limit = { 0, /* Any GPU not in the limits array defined above */
{ KBASE_AID_32, GENMASK(25, 24), 24 },
{ KBASE_AID_32, GENMASK(27, 26), 26 } };
u32 product_model;
u32 mmu_config = 0;
unsigned int i;
product_model = kbdev->gpu_props.gpu_id.product_model;
/* Limit the GPU bus bandwidth if the platform needs this. */
for (i = 0; i < ARRAY_SIZE(limits); i++) {
if (product_model == limits[i].product_model) {
limit = limits[i];
break;
}
}
if (kbase_reg_is_valid(kbdev, GPU_CONTROL_ENUM(L2_MMU_CONFIG)))
mmu_config = kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(L2_MMU_CONFIG));
if (kbase_is_gpu_removed(kbdev))
return -EIO;
mmu_config &= ~(limit.read.mask | limit.write.mask);
/* Can't use FIELD_PREP() macro here as the mask isn't constant */
mmu_config |= (limit.read.value << limit.read.shift) |
(limit.write.value << limit.write.shift);
kbdev->hw_quirks_mmu = mmu_config;
if (kbdev->system_coherency == COHERENCY_ACE) {
/* Allow memory configuration disparity to be ignored,
* we optimize the use of shared memory and thus we
* expect some disparity in the memory configuration.
*/
kbdev->hw_quirks_mmu |= L2_MMU_CONFIG_ALLOW_SNOOP_DISPARITY;
}
return 0;
}

View file

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_L2_MMU_CONFIG_H_
#define _KBASE_L2_MMU_CONFIG_H_
/**
* kbase_set_mmu_quirks - Set the hw_quirks_mmu field of kbdev
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Use this function to initialise the hw_quirks_mmu field, for instance to set
* the MAX_READS and MAX_WRITES to sane defaults for each GPU.
*
* Return: Zero for succeess or a Linux error code
*/
int kbase_set_mmu_quirks(struct kbase_device *kbdev);
#endif /* _KBASE_L2_MMU_CONFIG_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,224 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Dummy Model interface
*
* Support for NO_MALI dummy Model interface.
*
* +-----------------------------------+
* | Kbase read/write/IRQ |
* +-----------------------------------+
* | Model Linux Framework |
* +-----------------------------------+
* | Model Dummy interface definitions |
* +-----------------+-----------------+
* | Fake R/W | Fake IRQ |
* +-----------------+-----------------+
*/
#ifndef _KBASE_MODEL_DUMMY_H_
#define _KBASE_MODEL_DUMMY_H_
#include <uapi/gpu/arm/bv_r51p0/backend/gpu/mali_kbase_model_linux.h>
#include <uapi/gpu/arm/bv_r51p0/backend/gpu/mali_kbase_model_dummy.h>
#define model_error_log(module, ...) pr_err(__VA_ARGS__)
#define NUM_SLOTS 4 /*number of job slots */
/*Errors Mask Codes*/
/* each bit of errors_mask is associated to a specific error:
* NON FAULT STATUS CODES: only the following are implemented since the others
* represent normal working statuses
*/
#define KBASE_JOB_INTERRUPTED (1 << 0)
#define KBASE_JOB_STOPPED (1 << 1)
#define KBASE_JOB_TERMINATED (1 << 2)
/* JOB EXCEPTIONS: */
#define KBASE_JOB_CONFIG_FAULT (1 << 3)
#define KBASE_JOB_POWER_FAULT (1 << 4)
#define KBASE_JOB_READ_FAULT (1 << 5)
#define KBASE_JOB_WRITE_FAULT (1 << 6)
#define KBASE_JOB_AFFINITY_FAULT (1 << 7)
#define KBASE_JOB_BUS_FAULT (1 << 8)
#define KBASE_INSTR_INVALID_PC (1 << 9)
#define KBASE_INSTR_INVALID_ENC (1 << 10)
#define KBASE_INSTR_TYPE_MISMATCH (1 << 11)
#define KBASE_INSTR_OPERAND_FAULT (1 << 12)
#define KBASE_INSTR_TLS_FAULT (1 << 13)
#define KBASE_INSTR_BARRIER_FAULT (1 << 14)
#define KBASE_INSTR_ALIGN_FAULT (1 << 15)
#define KBASE_DATA_INVALID_FAULT (1 << 16)
#define KBASE_TILE_RANGE_FAULT (1 << 17)
#define KBASE_ADDR_RANGE_FAULT (1 << 18)
#define KBASE_OUT_OF_MEMORY (1 << 19)
#define KBASE_UNKNOWN (1 << 20)
/* GPU EXCEPTIONS:*/
#define KBASE_DELAYED_BUS_FAULT (1 << 21)
#define KBASE_SHAREABILITY_FAULT (1 << 22)
/* MMU EXCEPTIONS:*/
#define KBASE_TRANSLATION_FAULT (1 << 23)
#define KBASE_PERMISSION_FAULT (1 << 24)
#define KBASE_TRANSTAB_BUS_FAULT (1 << 25)
#define KBASE_ACCESS_FLAG (1 << 26)
/* generic useful bitmasks */
#define IS_A_JOB_ERROR ((KBASE_UNKNOWN << 1) - KBASE_JOB_INTERRUPTED)
#define IS_A_MMU_ERROR ((KBASE_ACCESS_FLAG << 1) - KBASE_TRANSLATION_FAULT)
#define IS_A_GPU_ERROR (KBASE_DELAYED_BUS_FAULT | KBASE_SHAREABILITY_FAULT)
/* number of possible MMU address spaces */
#define NUM_MMU_AS \
16 /* total number of MMU address spaces as in
* MMU_IRQ_RAWSTAT register
*/
/* Forward declaration */
struct kbase_device;
/*
* the function below is used to trigger the simulation of a faulty
* HW condition for a specific job chain atom
*/
struct kbase_error_params {
u64 jc;
u32 errors_mask;
u32 mmu_table_level;
u16 faulty_mmu_as;
u16 padding[3];
};
enum kbase_model_control_command {
/* Disable/Enable job completion in the dummy model */
KBASE_MC_DISABLE_JOBS
};
/* struct to control dummy model behavior */
struct kbase_model_control_params {
s32 command;
s32 value;
};
/* struct to track faulty atoms */
struct kbase_error_atom {
struct kbase_error_params params;
struct kbase_error_atom *next;
};
/*struct to track the system error state*/
struct error_status_t {
spinlock_t access_lock;
u32 errors_mask;
u32 mmu_table_level;
u32 faulty_mmu_as;
u64 current_jc;
u32 current_job_slot;
u32 job_irq_rawstat;
u32 job_irq_status;
u32 js_status[NUM_SLOTS];
u32 mmu_irq_mask;
u32 mmu_irq_rawstat;
u32 gpu_error_irq;
u32 gpu_fault_status;
u32 as_faultstatus[NUM_MMU_AS];
u32 as_command[NUM_MMU_AS];
u64 as_transtab[NUM_MMU_AS];
};
/**
* struct gpu_model_prfcnt_en - Performance counter enable masks
* @fe: Enable mask for front-end block
* @tiler: Enable mask for tiler block
* @l2: Enable mask for L2/Memory system blocks
* @shader: Enable mask for shader core blocks
*/
struct gpu_model_prfcnt_en {
u32 fe;
u32 tiler;
u32 l2;
u32 shader;
};
void bv_r51p0_set_error(u32 job_slot);
int job_atom_inject_error(struct kbase_error_params *params);
int gpu_model_control(void *h, struct kbase_model_control_params *params);
/**
* gpu_model_set_dummy_prfcnt_user_sample() - Set performance counter values
* @data: Userspace pointer to array of counter values
* @size: Size of counter value array
*
* Counter values set by this function will be used for one sample dump only
* after which counters will be cleared back to zero.
*
* Return: 0 on success, else error code.
*/
int gpu_model_set_dummy_prfcnt_user_sample(u32 __user *data, u32 size);
/**
* gpu_model_set_dummy_prfcnt_kernel_sample() - Set performance counter values
* @data: Pointer to array of counter values
* @size: Size of counter value array
*
* Counter values set by this function will be used for one sample dump only
* after which counters will be cleared back to zero.
*/
void gpu_model_set_dummy_prfcnt_kernel_sample(u64 *data, u32 size);
void gpu_model_get_dummy_prfcnt_cores(struct kbase_device *kbdev, u64 *l2_present,
u64 *shader_present);
void gpu_model_set_dummy_prfcnt_cores(struct kbase_device *kbdev, u64 l2_present,
u64 shader_present);
/* Clear the counter values array maintained by the dummy model */
void gpu_model_clear_prfcnt_values(void);
#if MALI_USE_CSF
/**
* gpu_model_prfcnt_dump_request() - Request performance counter sample dump.
* @sample_buf: Pointer to KBASE_DUMMY_MODEL_MAX_VALUES_PER_SAMPLE sized array
* in which to store dumped performance counter values.
* @enable_maps: Physical enable maps for performance counter blocks.
*/
void gpu_model_prfcnt_dump_request(uint32_t *sample_buf, struct gpu_model_prfcnt_en enable_maps);
/**
* gpu_model_glb_request_job_irq() - Trigger job interrupt with global request
* flag set.
* @model: Model pointer returned by bv_r51p0_model_create().
*/
void gpu_model_glb_request_job_irq(void *model);
#endif /* MALI_USE_CSF */
extern struct error_status_t hw_error_status;
#endif

View file

@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2010-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Model Linux Framework interfaces.
*/
#include <mali_kbase.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include "backend/gpu/mali_kbase_model_linux.h"
#include "device/mali_kbase_device.h"
#include "mali_kbase_irq_internal.h"
#include <linux/kthread.h>
struct model_irq_data {
struct kbase_device *kbdev;
struct work_struct work;
};
#define DEFINE_SERVE_IRQ(irq_handler) \
static void serve_##irq_handler(struct work_struct *work) \
{ \
struct model_irq_data *data = container_of(work, struct model_irq_data, work); \
struct kbase_device *kbdev = data->kbdev; \
irq_handler(kbdev); \
kmem_cache_free(kbdev->irq_slab, data); \
}
static void job_irq(struct kbase_device *kbdev)
{
/* Make sure no worker is already serving this IRQ */
while (atomic_cmpxchg(&kbdev->serving_job_irq, 1, 0) == 1)
kbase_get_interrupt_handler(kbdev, JOB_IRQ_TAG)(0, kbdev);
}
DEFINE_SERVE_IRQ(job_irq)
static void gpu_irq(struct kbase_device *kbdev)
{
/* Make sure no worker is already serving this IRQ */
while (atomic_cmpxchg(&kbdev->serving_gpu_irq, 1, 0) == 1)
kbase_get_interrupt_handler(kbdev, GPU_IRQ_TAG)(0, kbdev);
}
DEFINE_SERVE_IRQ(gpu_irq)
static void mmu_irq(struct kbase_device *kbdev)
{
/* Make sure no worker is already serving this IRQ */
while (atomic_cmpxchg(&kbdev->serving_mmu_irq, 1, 0) == 1)
kbase_get_interrupt_handler(kbdev, MMU_IRQ_TAG)(0, kbdev);
}
DEFINE_SERVE_IRQ(mmu_irq)
void gpu_device_raise_irq(void *model, u32 irq)
{
struct model_irq_data *data;
struct kbase_device *kbdev = gpu_device_get_data(model);
KBASE_DEBUG_ASSERT(kbdev);
data = kmem_cache_alloc(kbdev->irq_slab, GFP_ATOMIC);
if (data == NULL)
return;
data->kbdev = kbdev;
switch (irq) {
case MODEL_LINUX_JOB_IRQ:
INIT_WORK(&data->work, serve_job_irq);
atomic_set(&kbdev->serving_job_irq, 1);
break;
case MODEL_LINUX_GPU_IRQ:
INIT_WORK(&data->work, serve_gpu_irq);
atomic_set(&kbdev->serving_gpu_irq, 1);
break;
case MODEL_LINUX_MMU_IRQ:
INIT_WORK(&data->work, serve_mmu_irq);
atomic_set(&kbdev->serving_mmu_irq, 1);
break;
default:
dev_warn(kbdev->dev, "Unknown IRQ");
kmem_cache_free(kbdev->irq_slab, data);
data = NULL;
break;
}
if (data != NULL)
queue_work(kbdev->irq_workq, &data->work);
}
int kbase_install_interrupts(struct kbase_device *kbdev)
{
KBASE_DEBUG_ASSERT(kbdev);
atomic_set(&kbdev->serving_job_irq, 0);
atomic_set(&kbdev->serving_gpu_irq, 0);
atomic_set(&kbdev->serving_mmu_irq, 0);
kbdev->irq_workq = alloc_ordered_workqueue("dummy irq queue", 0);
if (kbdev->irq_workq == NULL)
return -ENOMEM;
kbdev->irq_slab =
kmem_cache_create("dummy_irq_slab", sizeof(struct model_irq_data), 0, 0, NULL);
if (kbdev->irq_slab == NULL) {
destroy_workqueue(kbdev->irq_workq);
return -ENOMEM;
}
kbdev->nr_irqs = 3;
return 0;
}
void kbase_release_interrupts(struct kbase_device *kbdev)
{
KBASE_DEBUG_ASSERT(kbdev);
destroy_workqueue(kbdev->irq_workq);
kmem_cache_destroy(kbdev->irq_slab);
}
void kbase_synchronize_irqs(struct kbase_device *kbdev)
{
KBASE_DEBUG_ASSERT(kbdev);
flush_workqueue(kbdev->irq_workq);
}
KBASE_EXPORT_TEST_API(kbase_synchronize_irqs);
int kbase_set_custom_irq_handler(struct kbase_device *kbdev, irq_handler_t custom_handler,
u32 irq_tag)
{
return 0;
}
KBASE_EXPORT_TEST_API(kbase_set_custom_irq_handler);
int kbase_gpu_device_create(struct kbase_device *kbdev)
{
kbdev->model = bv_r51p0_model_create(kbdev);
if (kbdev->model == NULL)
return -ENOMEM;
spin_lock_init(&kbdev->reg_op_lock);
return 0;
}
/**
* kbase_gpu_device_destroy - Destroy GPU device
*
* @kbdev: kbase device
*/
void kbase_gpu_device_destroy(struct kbase_device *kbdev)
{
bv_r51p0_model_destroy(kbdev->model);
}

View file

@ -0,0 +1,146 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Model Linux Framework interfaces.
*
* This framework is used to provide generic Kbase Models interfaces.
* Note: Backends cannot be used together; the selection is done at build time.
*
* - Without Model Linux Framework:
* +-----------------------------+
* | Kbase read/write/IRQ |
* +-----------------------------+
* | HW interface definitions |
* +-----------------------------+
*
* - With Model Linux Framework:
* +-----------------------------+
* | Kbase read/write/IRQ |
* +-----------------------------+
* | Model Linux Framework |
* +-----------------------------+
* | Model interface definitions |
* +-----------------------------+
*/
#ifndef _KBASE_MODEL_LINUX_H_
#define _KBASE_MODEL_LINUX_H_
/*
* Include Model definitions
*/
#include <backend/gpu/mali_kbase_model_dummy.h>
/**
* kbase_gpu_device_create() - Generic create function.
*
* @kbdev: Kbase device.
*
* Specific model hook is implemented by bv_r51p0_model_create()
*
* Return: 0 on success, error code otherwise.
*/
int kbase_gpu_device_create(struct kbase_device *kbdev);
/**
* kbase_gpu_device_destroy() - Generic create function.
*
* @kbdev: Kbase device.
*
* Specific model hook is implemented by bv_r51p0_model_destroy()
*/
void kbase_gpu_device_destroy(struct kbase_device *kbdev);
/**
* bv_r51p0_model_create() - Private create function.
*
* @kbdev: Kbase device.
*
* This hook is specific to the model built in Kbase.
*
* Return: Model handle.
*/
void *bv_r51p0_model_create(struct kbase_device *kbdev);
/**
* bv_r51p0_model_destroy() - Private destroy function.
*
* @h: Model handle.
*
* This hook is specific to the model built in Kbase.
*/
void bv_r51p0_model_destroy(void *h);
/**
* bv_r51p0_model_write_reg() - Private model write function.
*
* @h: Model handle.
* @addr: Address at which to write.
* @value: value to write.
*
* This hook is specific to the model built in Kbase.
*/
void bv_r51p0_model_write_reg(void *h, u32 addr, u32 value);
/**
* bv_r51p0_model_read_reg() - Private model read function.
*
* @h: Model handle.
* @addr: Address from which to read.
* @value: Pointer where to store the read value.
*
* This hook is specific to the model built in Kbase.
*/
void bv_r51p0_model_read_reg(void *h, u32 addr, u32 *const value);
/**
* gpu_device_raise_irq() - Private IRQ raise function.
*
* @model: Model handle.
* @irq: IRQ type to raise.
*
* This hook is global to the model Linux framework.
*/
void gpu_device_raise_irq(void *model, u32 irq);
/**
* gpu_device_set_data() - Private model set data function.
*
* @model: Model handle.
* @data: Data carried by model.
*
* This hook is global to the model Linux framework.
*/
void gpu_device_set_data(void *model, void *data);
/**
* gpu_device_get_data() - Private model get data function.
*
* @model: Model handle.
*
* This hook is global to the model Linux framework.
*
* Return: Pointer to the data carried by model.
*/
void *gpu_device_get_data(void *model);
#endif /* _KBASE_MODEL_LINUX_H_ */

View file

@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2010-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* "Always on" power management policy
*/
#include <mali_kbase.h>
#include <mali_kbase_pm.h>
static bool always_on_shaders_needed(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
return true;
}
static bool always_on_get_core_active(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
return true;
}
static void always_on_init(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
/**
* always_on_term - Term callback function for always-on power policy
*
* @kbdev: kbase device
*/
static void always_on_term(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
/*
* The struct kbase_pm_policy structure for the demand power policy.
*
* This is the static structure that defines the demand power policy's callback
* and name.
*/
const struct kbase_pm_policy kbase_pm_always_on_policy_ops = {
"always_on", /* name */
always_on_init, /* init */
always_on_term, /* term */
always_on_shaders_needed, /* shaders_needed */
always_on_get_core_active, /* get_core_active */
NULL, /* handle_event */
KBASE_PM_POLICY_ID_ALWAYS_ON, /* id */
#if MALI_USE_CSF
ALWAYS_ON_PM_SCHED_FLAGS, /* pm_sched_flags */
#endif
};
KBASE_EXPORT_TEST_API(kbase_pm_always_on_policy_ops);

View file

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2011-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* "Always on" power management policy
*/
#ifndef MALI_KBASE_PM_ALWAYS_ON_H
#define MALI_KBASE_PM_ALWAYS_ON_H
/**
* DOC:
* The "Always on" power management policy has the following
* characteristics:
*
* - When KBase indicates that the GPU will be powered up, but we don't yet
* know which Job Chains are to be run:
* Shader Cores are powered up, regardless of whether or not they will be
* needed later.
*
* - When KBase indicates that Shader Cores are needed to submit the currently
* queued Job Chains:
* Shader Cores are kept powered, regardless of whether or not they will be
* needed
*
* - When KBase indicates that the GPU need not be powered:
* The Shader Cores are kept powered, regardless of whether or not they will
* be needed. The GPU itself is also kept powered, even though it is not
* needed.
*
* This policy is automatically overridden during system suspend: the desired
* core state is ignored, and the cores are forced off regardless of what the
* policy requests. After resuming from suspend, new changes to the desired
* core state made by the policy are honored.
*
* Note:
*
* - KBase indicates the GPU will be powered up when it has a User Process that
* has just started to submit Job Chains.
*
* - KBase indicates the GPU need not be powered when all the Job Chains from
* User Processes have finished, and it is waiting for a User Process to
* submit some more Job Chains.
*/
/**
* struct kbasep_pm_policy_always_on - Private struct for policy instance data
* @dummy: unused dummy variable
*
* This contains data that is private to the particular power policy that is
* active.
*/
struct kbasep_pm_policy_always_on {
int dummy;
};
extern const struct kbase_pm_policy kbase_pm_always_on_policy_ops;
#endif /* MALI_KBASE_PM_ALWAYS_ON_H */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2013-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel core availability APIs
*/
#include <mali_kbase.h>
#include <mali_kbase_pm.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <backend/gpu/mali_kbase_model_linux.h>
#include <mali_kbase_dummy_job_wa.h>
int kbase_pm_ca_init(struct kbase_device *kbdev)
{
#ifdef CONFIG_MALI_DEVFREQ
struct kbase_pm_backend_data *pm_backend = &kbdev->pm.backend;
if (kbdev->current_core_mask)
pm_backend->ca_cores_enabled = kbdev->current_core_mask;
else
pm_backend->ca_cores_enabled = kbdev->gpu_props.shader_present;
#endif
return 0;
}
void kbase_pm_ca_term(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
#ifdef CONFIG_MALI_DEVFREQ
void kbase_devfreq_set_core_mask(struct kbase_device *kbdev, u64 core_mask)
{
struct kbase_pm_backend_data *pm_backend = &kbdev->pm.backend;
unsigned long flags;
#if MALI_USE_CSF
u64 old_core_mask = 0;
bool mmu_sync_needed = false;
if (!IS_ENABLED(CONFIG_MALI_NO_MALI) &&
kbase_hw_has_issue(kbdev, KBASE_HW_ISSUE_GPU2019_3901)) {
mmu_sync_needed = true;
down_write(&kbdev->csf.mmu_sync_sem);
}
#endif
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
#if MALI_USE_CSF
if (!(core_mask & kbdev->pm.debug_core_mask)) {
dev_err(kbdev->dev,
"OPP core mask 0x%llX does not intersect with debug mask 0x%llX\n",
core_mask, kbdev->pm.debug_core_mask);
goto unlock;
}
old_core_mask = pm_backend->ca_cores_enabled;
#else
if (!(core_mask & kbdev->pm.debug_core_mask_all)) {
dev_err(kbdev->dev,
"OPP core mask 0x%llX does not intersect with debug mask 0x%llX\n",
core_mask, kbdev->pm.debug_core_mask_all);
goto unlock;
}
if (kbase_dummy_job_wa_enabled(kbdev)) {
dev_err_once(kbdev->dev,
"Dynamic core scaling not supported as dummy job WA is enabled");
goto unlock;
}
#endif /* MALI_USE_CSF */
pm_backend->ca_cores_enabled = core_mask;
kbase_pm_update_state(kbdev);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
#if MALI_USE_CSF
/* Check if old_core_mask contained the undesired cores and wait
* for those cores to get powered down
*/
if ((core_mask & old_core_mask) != old_core_mask) {
if (kbase_pm_wait_for_cores_down_scale(kbdev)) {
dev_warn(kbdev->dev,
"Wait for update of core_mask from %llx to %llx failed",
old_core_mask, core_mask);
}
}
if (mmu_sync_needed)
up_write(&kbdev->csf.mmu_sync_sem);
#endif
dev_dbg(kbdev->dev, "Devfreq policy : new core mask=%llX\n", pm_backend->ca_cores_enabled);
return;
unlock:
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
#if MALI_USE_CSF
if (mmu_sync_needed)
up_write(&kbdev->csf.mmu_sync_sem);
#endif
}
KBASE_EXPORT_TEST_API(kbase_devfreq_set_core_mask);
#endif
u64 kbase_pm_ca_get_debug_core_mask(struct kbase_device *kbdev)
{
#if MALI_USE_CSF
return kbdev->pm.debug_core_mask;
#else
return kbdev->pm.debug_core_mask_all;
#endif
}
KBASE_EXPORT_TEST_API(kbase_pm_ca_get_debug_core_mask);
u64 kbase_pm_ca_get_core_mask(struct kbase_device *kbdev)
{
u64 debug_core_mask = kbase_pm_ca_get_debug_core_mask(kbdev);
lockdep_assert_held(&kbdev->hwaccess_lock);
#ifdef CONFIG_MALI_DEVFREQ
/*
* Although in the init we let the pm_backend->ca_cores_enabled to be
* the max config (it uses the base_gpu_props), at this function we need
* to limit it to be a subgroup of the curr config, otherwise the
* shaders state machine on the PM does not evolve.
*/
return kbdev->gpu_props.curr_config.shader_present & kbdev->pm.backend.ca_cores_enabled &
debug_core_mask;
#else
return kbdev->gpu_props.curr_config.shader_present & debug_core_mask;
#endif
}
KBASE_EXPORT_TEST_API(kbase_pm_ca_get_core_mask);
u64 kbase_pm_ca_get_instr_core_mask(struct kbase_device *kbdev)
{
lockdep_assert_held(&kbdev->hwaccess_lock);
#if IS_ENABLED(CONFIG_MALI_NO_MALI)
return (((1ull) << KBASE_DUMMY_MODEL_MAX_SHADER_CORES) - 1);
#elif MALI_USE_CSF
return kbase_pm_get_ready_cores(kbdev, KBASE_PM_CORE_SHADER);
#else
return kbdev->pm.backend.pm_shaders_core_mask;
#endif
}

View file

@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2011-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel core availability APIs
*/
#ifndef _KBASE_PM_CA_H_
#define _KBASE_PM_CA_H_
/**
* kbase_pm_ca_init - Initialize core availability framework
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Must be called before calling any other core availability function
*
* Return: 0 if the core availability framework was successfully initialized,
* -errno otherwise
*/
int kbase_pm_ca_init(struct kbase_device *kbdev);
/**
* kbase_pm_ca_term - Terminate core availability framework
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*/
void kbase_pm_ca_term(struct kbase_device *kbdev);
/**
* kbase_pm_ca_get_core_mask - Get currently available shaders core mask
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Returns a mask of the currently available shader cores.
* Calls into the core availability policy
*
* Return: The bit mask of available cores
*/
u64 kbase_pm_ca_get_core_mask(struct kbase_device *kbdev);
/**
* kbase_pm_ca_get_debug_core_mask - Get debug core mask.
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Returns a mask of the currently selected shader cores.
*
* Return: The bit mask of user-selected cores
*/
u64 kbase_pm_ca_get_debug_core_mask(struct kbase_device *kbdev);
/**
* kbase_pm_ca_update_core_status - Update core status
*
* @kbdev: The kbase device structure for the device (must be
* a valid pointer)
* @cores_ready: The bit mask of cores ready for job submission
* @cores_transitioning: The bit mask of cores that are transitioning power
* state
*
* Update core availability policy with current core power status
*
* Calls into the core availability policy
*/
void kbase_pm_ca_update_core_status(struct kbase_device *kbdev, u64 cores_ready,
u64 cores_transitioning);
/**
* kbase_pm_ca_get_instr_core_mask - Get the PM state sync-ed shaders core mask
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Returns a mask of the PM state synchronised shader cores for arranging
* HW performance counter dumps
*
* Return: The bit mask of PM state synchronised cores
*/
u64 kbase_pm_ca_get_instr_core_mask(struct kbase_device *kbdev);
#endif /* _KBASE_PM_CA_H_ */

View file

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2017-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* A core availability policy for use with devfreq, where core masks are
* associated with OPPs.
*/
#ifndef MALI_KBASE_PM_CA_DEVFREQ_H
#define MALI_KBASE_PM_CA_DEVFREQ_H
/**
* struct kbasep_pm_ca_policy_devfreq - Private structure for devfreq ca policy
*
* @cores_desired: Cores that the policy wants to be available
* @cores_enabled: Cores that the policy is currently returning as available
* @cores_used: Cores currently powered or transitioning
*
* This contains data that is private to the devfreq core availability
* policy.
*/
struct kbasep_pm_ca_policy_devfreq {
u64 cores_desired;
u64 cores_enabled;
u64 cores_used;
};
extern const struct kbase_pm_ca_policy kbase_pm_ca_devfreq_policy_ops;
/**
* kbase_devfreq_set_core_mask - Set core mask for policy to use
* @kbdev: Device pointer
* @core_mask: New core mask
*
* The new core mask will have immediate effect if the GPU is powered, or will
* take effect when it is next powered on.
*/
void kbase_devfreq_set_core_mask(struct kbase_device *kbdev, u64 core_mask);
#endif /* MALI_KBASE_PM_CA_DEVFREQ_H */

View file

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2012-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* "Coarse Demand" power management policy
*/
#include <mali_kbase.h>
#include <mali_kbase_pm.h>
static bool coarse_demand_shaders_needed(struct kbase_device *kbdev)
{
return kbase_pm_is_active(kbdev);
}
static bool coarse_demand_get_core_active(struct kbase_device *kbdev)
{
return kbase_pm_is_active(kbdev);
}
static void coarse_demand_init(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
static void coarse_demand_term(struct kbase_device *kbdev)
{
CSTD_UNUSED(kbdev);
}
/* The struct kbase_pm_policy structure for the demand power policy.
*
* This is the static structure that defines the demand power policy's callback
* and name.
*/
const struct kbase_pm_policy kbase_pm_coarse_demand_policy_ops = {
"coarse_demand", /* name */
coarse_demand_init, /* init */
coarse_demand_term, /* term */
coarse_demand_shaders_needed, /* shaders_needed */
coarse_demand_get_core_active, /* get_core_active */
NULL, /* handle_event */
KBASE_PM_POLICY_ID_COARSE_DEMAND, /* id */
#if MALI_USE_CSF
COARSE_ON_DEMAND_PM_SCHED_FLAGS, /* pm_sched_flags */
#endif
};
KBASE_EXPORT_TEST_API(kbase_pm_coarse_demand_policy_ops);

View file

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2012-2015, 2018, 2020-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* "Coarse Demand" power management policy
*/
#ifndef MALI_KBASE_PM_COARSE_DEMAND_H
#define MALI_KBASE_PM_COARSE_DEMAND_H
/**
* DOC:
* The "Coarse" demand power management policy has the following
* characteristics:
* - When KBase indicates that the GPU will be powered up, but we don't yet
* know which Job Chains are to be run:
* - Shader Cores are powered up, regardless of whether or not they will be
* needed later.
* - When KBase indicates that Shader Cores are needed to submit the currently
* queued Job Chains:
* - Shader Cores are kept powered, regardless of whether or not they will
* be needed
* - When KBase indicates that the GPU need not be powered:
* - The Shader Cores are powered off, and the GPU itself is powered off too.
*
* @note:
* - KBase indicates the GPU will be powered up when it has a User Process that
* has just started to submit Job Chains.
* - KBase indicates the GPU need not be powered when all the Job Chains from
* User Processes have finished, and it is waiting for a User Process to
* submit some more Job Chains.
*/
/**
* struct kbasep_pm_policy_coarse_demand - Private structure for coarse demand
* policy
* @dummy: Dummy member - no state needed
* This contains data that is private to the coarse demand power policy.
*/
struct kbasep_pm_policy_coarse_demand {
int dummy;
};
extern const struct kbase_pm_policy kbase_pm_coarse_demand_policy_ops;
#endif /* MALI_KBASE_PM_COARSE_DEMAND_H */

View file

@ -0,0 +1,708 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific Power Manager definitions
*/
#ifndef _KBASE_PM_HWACCESS_DEFS_H_
#define _KBASE_PM_HWACCESS_DEFS_H_
#include "mali_kbase_pm_always_on.h"
#include "mali_kbase_pm_coarse_demand.h"
#include <hw_access/mali_kbase_hw_access_regmap.h>
#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_PM)
#define KBASE_PM_RUNTIME 1
#endif
/* Forward definition - see mali_kbase.h */
struct kbase_device;
struct kbase_jd_atom;
/**
* enum kbase_pm_core_type - The types of core in a GPU.
*
* @KBASE_PM_CORE_L2: The L2 cache
* @KBASE_PM_CORE_SHADER: Shader cores
* @KBASE_PM_CORE_TILER: Tiler cores
* @KBASE_PM_CORE_STACK: Core stacks
*
* These enumerated values are used in calls to
* - kbase_pm_get_present_cores()
* - kbase_pm_get_active_cores()
* - kbase_pm_get_trans_cores()
* - kbase_pm_get_ready_cores()
* - kbase_pm_get_state()
* - core_type_to_reg()
* - pwr_cmd_constructor()
* - valid_to_power_up()
* - valid_to_power_down()
* - kbase_pm_invoke()
*
* They specify which type of core should be acted on.
*/
enum kbase_pm_core_type {
KBASE_PM_CORE_L2 = GPU_CONTROL_ENUM(L2_PRESENT),
KBASE_PM_CORE_SHADER = GPU_CONTROL_ENUM(SHADER_PRESENT),
KBASE_PM_CORE_TILER = GPU_CONTROL_ENUM(TILER_PRESENT),
KBASE_PM_CORE_STACK = GPU_CONTROL_ENUM(STACK_PRESENT)
};
/*
* enum kbase_l2_core_state - The states used for the L2 cache & tiler power
* state machine.
*/
enum kbase_l2_core_state {
#define KBASEP_L2_STATE(n) KBASE_L2_##n,
#include "mali_kbase_pm_l2_states.h"
#undef KBASEP_L2_STATE
};
#if MALI_USE_CSF
/*
* enum kbase_mcu_state - The states used for the MCU state machine.
*/
enum kbase_mcu_state {
#define KBASEP_MCU_STATE(n) KBASE_MCU_##n,
#include "mali_kbase_pm_mcu_states.h"
#undef KBASEP_MCU_STATE
};
#endif
/*
* enum kbase_shader_core_state - The states used for the shaders' state machine.
*/
enum kbase_shader_core_state {
#define KBASEP_SHADER_STATE(n) KBASE_SHADERS_##n,
#include "mali_kbase_pm_shader_states.h"
#undef KBASEP_SHADER_STATE
};
/**
* enum kbase_pm_runtime_suspend_abort_reason - Reason why runtime suspend was aborted
* after the wake up of MCU.
*
* @ABORT_REASON_NONE: Not aborted
* @ABORT_REASON_DB_MIRROR_IRQ: Runtime suspend was aborted due to DB_MIRROR irq.
* @ABORT_REASON_NON_IDLE_CGS: Runtime suspend was aborted as CSGs were detected as non-idle after
* their suspension.
*/
enum kbase_pm_runtime_suspend_abort_reason {
ABORT_REASON_NONE,
ABORT_REASON_DB_MIRROR_IRQ,
ABORT_REASON_NON_IDLE_CGS
};
/* The following indices point to the corresponding bits stored in
* &kbase_pm_backend_data.gpu_sleep_allowed. They denote the conditions that
* would be checked against to determine the level of support for GPU sleep
* and firmware sleep-on-idle.
*/
#define KBASE_GPU_SUPPORTS_GPU_SLEEP ((uint8_t)0)
#define KBASE_GPU_SUPPORTS_FW_SLEEP_ON_IDLE ((uint8_t)1)
#define KBASE_GPU_PERF_COUNTERS_COLLECTION_ENABLED ((uint8_t)2)
#define KBASE_GPU_IGNORE_IDLE_EVENT ((uint8_t)3)
#define KBASE_GPU_NON_IDLE_OFF_SLOT_GROUPS_AVAILABLE ((uint8_t)4)
/* FW sleep-on-idle could be enabled if
* &kbase_pm_backend_data.gpu_sleep_allowed is equal to this value.
*/
#define KBASE_GPU_FW_SLEEP_ON_IDLE_ALLOWED \
((uint8_t)((1 << KBASE_GPU_SUPPORTS_GPU_SLEEP) | \
(1 << KBASE_GPU_SUPPORTS_FW_SLEEP_ON_IDLE) | \
(0 << KBASE_GPU_PERF_COUNTERS_COLLECTION_ENABLED) | \
(0 << KBASE_GPU_IGNORE_IDLE_EVENT) | \
(0 << KBASE_GPU_NON_IDLE_OFF_SLOT_GROUPS_AVAILABLE)))
/**
* struct kbasep_pm_metrics - Metrics data collected for use by the power
* management framework.
*
* @time_busy: the amount of time the GPU was busy executing jobs since the
* @time_period_start timestamp, in units of 256ns. This also includes
* time_in_protm, the time spent in protected mode, since it's assumed
* the GPU was busy 100% during this period.
* @time_idle: the amount of time the GPU was not executing jobs since the
* time_period_start timestamp, measured in units of 256ns.
* @time_in_protm: The amount of time the GPU has spent in protected mode since
* the time_period_start timestamp, measured in units of 256ns.
* @busy_cl: the amount of time the GPU was busy executing CL jobs. Note that
* if two CL jobs were active for 256ns, this value would be updated
* with 2 (2x256ns).
* @busy_gl: the amount of time the GPU was busy executing GL jobs. Note that
* if two GL jobs were active for 256ns, this value would be updated
* with 2 (2x256ns).
*/
struct kbasep_pm_metrics {
u32 time_busy;
u32 time_idle;
#if MALI_USE_CSF
u32 time_in_protm;
#else
u32 busy_cl[2];
u32 busy_gl;
#endif
};
/**
* struct kbasep_pm_metrics_state - State required to collect the metrics in
* struct kbasep_pm_metrics
* @time_period_start: time at which busy/idle measurements started
* @ipa_control_client: Handle returned on registering DVFS as a
* kbase_ipa_control client
* @skip_gpu_active_sanity_check: Decide whether to skip GPU_ACTIVE sanity
* check in DVFS utilisation calculation
* @gpu_active: true when the GPU is executing jobs. false when
* not. Updated when the job scheduler informs us a job in submitted
* or removed from a GPU slot.
* @active_cl_ctx: number of CL jobs active on the GPU. Array is per-device.
* @active_gl_ctx: number of GL jobs active on the GPU. Array is per-slot.
* @lock: spinlock protecting the kbasep_pm_metrics_state structure
* @platform_data: pointer to data controlled by platform specific code
* @kbdev: pointer to kbase device for which metrics are collected
* @values: The current values of the power management metrics. The
* kbase_pm_get_dvfs_metrics() function is used to compare these
* current values with the saved values from a previous invocation.
* @initialized: tracks whether metrics_state has been initialized or not.
* @timer: timer to regularly make DVFS decisions based on the power
* management metrics.
* @timer_state: atomic indicating current @timer state, on, off, or stopped.
* @dvfs_last: values of the PM metrics from the last DVFS tick
* @dvfs_diff: different between the current and previous PM metrics.
*/
struct kbasep_pm_metrics_state {
ktime_t time_period_start;
#if MALI_USE_CSF
void *ipa_control_client;
bool skip_gpu_active_sanity_check;
#else
bool gpu_active;
u32 active_cl_ctx[2];
u32 active_gl_ctx[3];
#endif
spinlock_t lock;
void *platform_data;
struct kbase_device *kbdev;
struct kbasep_pm_metrics values;
#ifdef CONFIG_MALI_MIDGARD_DVFS
bool initialized;
struct hrtimer timer;
atomic_t timer_state;
struct kbasep_pm_metrics dvfs_last;
struct kbasep_pm_metrics dvfs_diff;
#endif
};
/**
* struct kbasep_pm_tick_timer_state - State for the shader hysteresis timer
* @wq: Work queue to wait for the timer to stopped
* @work: Work item which cancels the timer
* @timer: Timer for powering off the shader cores
* @configured_interval: Period of GPU poweroff timer
* @default_ticks: User-configured number of ticks to wait after the shader
* power down request is received before turning off the cores
* @configured_ticks: Power-policy configured number of ticks to wait after the
* shader power down request is received before turning off
* the cores. For simple power policies, this is equivalent
* to @default_ticks.
* @remaining_ticks: Number of remaining timer ticks until shaders are powered off
* @cancel_queued: True if the cancellation work item has been queued. This is
* required to ensure that it is not queued twice, e.g. after
* a reset, which could cause the timer to be incorrectly
* cancelled later by a delayed workitem.
* @needed: Whether the timer should restart itself
*/
struct kbasep_pm_tick_timer_state {
struct workqueue_struct *wq;
struct work_struct work;
struct hrtimer timer;
ktime_t configured_interval;
unsigned int default_ticks;
unsigned int configured_ticks;
unsigned int remaining_ticks;
bool cancel_queued;
bool needed;
};
union kbase_pm_policy_data {
struct kbasep_pm_policy_always_on always_on;
struct kbasep_pm_policy_coarse_demand coarse_demand;
};
/**
* struct kbase_pm_backend_data - Data stored per device for power management.
*
* @pm_current_policy: The policy that is currently actively controlling the
* power state.
* @pm_policy_data: Private data for current PM policy. This is automatically
* zeroed when a policy change occurs.
* @reset_done: Flag when a reset is complete
* @reset_done_wait: Wait queue to wait for changes to @reset_done
* @gpu_cycle_counter_requests: The reference count of active gpu cycle counter
* users
* @gpu_cycle_counter_requests_lock: Lock to protect @gpu_cycle_counter_requests
* @gpu_in_desired_state_wait: Wait queue set when the GPU is in the desired
* state according to the L2 and shader power state
* machines
* @gpu_powered: Set to true when the GPU is powered and register
* accesses are possible, false otherwise. Access to this
* variable should be protected by: both the hwaccess_lock
* spinlock and the pm.lock mutex for writes; or at least
* one of either lock for reads.
* @gpu_ready: Indicates whether the GPU is in a state in which it is
* safe to perform PM changes. When false, the PM state
* machine needs to wait before making changes to the GPU
* power policy, DevFreq or core_mask, so as to avoid these
* changing while implicit GPU resets are ongoing.
* @pm_shaders_core_mask: Shader PM state synchronised shaders core mask. It
* holds the cores enabled in a hardware counters dump,
* and may differ from @shaders_avail when under different
* states and transitions.
* @cg1_disabled: Set if the policy wants to keep the second core group
* powered off
* @metrics: Structure to hold metrics for the GPU
* @shader_tick_timer: Structure to hold the shader poweroff tick timer state
* @poweroff_wait_in_progress: true if a wait for GPU power off is in progress.
* hwaccess_lock must be held when accessing
* @invoke_poweroff_wait_wq_when_l2_off: flag indicating that the L2 power state
* machine should invoke the poweroff
* worker after the L2 has turned off.
* @poweron_required: true if a GPU power on is required. Should only be set
* when poweroff_wait_in_progress is true, and therefore the
* GPU can not immediately be powered on. pm.lock must be
* held when accessing
* @gpu_poweroff_wait_wq: workqueue for waiting for GPU to power off
* @gpu_poweroff_wait_work: work item for use with @gpu_poweroff_wait_wq
* @poweroff_wait: waitqueue for waiting for @gpu_poweroff_wait_work to complete
* @callback_power_on: Callback when the GPU needs to be turned on. See
* &struct kbase_pm_callback_conf
* @callback_power_off: Callback when the GPU may be turned off. See
* &struct kbase_pm_callback_conf
* @callback_power_suspend: Callback when a suspend occurs and the GPU needs to
* be turned off. See &struct kbase_pm_callback_conf
* @callback_power_resume: Callback when a resume occurs and the GPU needs to
* be turned on. See &struct kbase_pm_callback_conf
* @callback_power_runtime_on: Callback when the GPU needs to be turned on. See
* &struct kbase_pm_callback_conf
* @callback_power_runtime_off: Callback when the GPU may be turned off. See
* &struct kbase_pm_callback_conf
* @callback_power_runtime_idle: Optional callback invoked by runtime PM core
* when the GPU may be idle. See
* &struct kbase_pm_callback_conf
* @callback_soft_reset: Optional callback to software reset the GPU. See
* &struct kbase_pm_callback_conf
* @callback_power_runtime_gpu_idle: Callback invoked by Kbase when GPU has
* become idle.
* See &struct kbase_pm_callback_conf.
* @callback_power_runtime_gpu_active: Callback when GPU has become active and
* @callback_power_runtime_gpu_idle was
* called previously.
* See &struct kbase_pm_callback_conf.
* @ca_cores_enabled: Cores that are currently available
* @apply_hw_issue_TITANHW_2938_wa: Indicates if the workaround for KBASE_HW_ISSUE_TITANHW_2938
* needs to be applied when unmapping memory from GPU.
* @mcu_state: The current state of the micro-control unit, only applicable
* to GPUs that have such a component
* @l2_state: The current state of the L2 cache state machine. See
* &enum kbase_l2_core_state
* @l2_desired: True if the L2 cache should be powered on by the L2 cache state
* machine
* @l2_always_on: If true, disable powering down of l2 cache.
* @shaders_state: The current state of the shader state machine.
* @shaders_avail: This is updated by the state machine when it is in a state
* where it can write to the SHADER_PWRON or PWROFF registers
* to have the same set of available cores as specified by
* @shaders_desired_mask. So would precisely indicate the cores
* that are currently available. This is internal to shader
* state machine of JM GPUs and should *not* be modified
* elsewhere.
* @shaders_desired_mask: This is updated by the state machine when it is in
* a state where it can handle changes to the core
* availability (either by DVFS or sysfs). This is
* internal to the shader state machine and should
* *not* be modified elsewhere.
* @shaders_desired: True if the PM active count or power policy requires the
* shader cores to be on. This is used as an input to the
* shader power state machine. The current state of the
* cores may be different, but there should be transitions in
* progress that will eventually achieve this state (assuming
* that the policy doesn't change its mind in the mean time).
* @mcu_desired: True if the micro-control unit should be powered on by the MCU state
* machine. Updated as per the value of @mcu_poweron_required.
* @mcu_poweron_required: Boolean flag updated mainly by the CSF Scheduler code,
* before updating the PM active count, to indicate to the
* PM code that micro-control unit needs to be powered up/down.
* @policy_change_clamp_state_to_off: Signaling the backend is in PM policy
* change transition, needs the mcu/L2 to be brought back to the
* off state and remain in that state until the flag is cleared.
* @csf_pm_sched_flags: CSF Dynamic PM control flags in accordance to the
* current active PM policy. This field is updated whenever a
* new policy is activated.
* @policy_change_lock: Used to serialize the policy change calls. In CSF case,
* the change of policy may involve the scheduler to
* suspend running CSGs and then reconfigure the MCU.
* @core_idle_wq: Workqueue for executing the @core_idle_work.
* @core_idle_work: Work item used to wait for undesired cores to become inactive.
* The work item is enqueued when Host controls the power for
* shader cores and down scaling of cores is performed.
* @gpu_sleep_allowed: Bitmask to indicate the conditions that would be
* used to determine what support for GPU sleep is
* available.
* @gpu_sleep_mode_active: Flag to indicate that the GPU needs to be in sleep
* mode. It is set when the GPU idle notification is
* received and is cleared when HW state has been
* saved in the runtime suspend callback function or
* when the GPU power down is aborted if GPU became
* active whilst it was in sleep mode. The flag is
* guarded with hwaccess_lock spinlock.
* @exit_gpu_sleep_mode: Flag to indicate the GPU can now exit the sleep
* mode due to the submission of work from Userspace.
* The flag is guarded with hwaccess_lock spinlock.
* The @gpu_sleep_mode_active flag is not immediately
* reset when this flag is set, this is to ensure that
* MCU doesn't gets disabled undesirably without the
* suspend of CSGs. That could happen when
* scheduler_pm_active() and scheduler_pm_idle() gets
* called before the Scheduler gets reactivated.
* @gpu_idled: Flag to ensure that the gpu_idle & gpu_active callbacks are
* always called in pair. The flag is guarded with pm.lock mutex.
* @gpu_wakeup_override: Flag to force the power up of L2 cache & reactivation
* of MCU. This is set during the runtime suspend
* callback function, when GPU needs to exit the sleep
* mode for the saving the HW state before power down.
* @db_mirror_interrupt_enabled: Flag tracking if the Doorbell mirror interrupt
* is enabled or not.
* @runtime_suspend_abort_reason: Tracks if the runtime suspend was aborted,
* after the wake up of MCU, due to the DB_MIRROR irq
* or non-idle CSGs. Tracking is done to avoid
* redundant transition of MCU to sleep state after the
* abort of runtime suspend and before the resumption
* of scheduling.
* @l2_force_off_after_mcu_halt: Flag to indicate that L2 cache power down is
* must after performing the MCU halt. Flag is set
* immediately after the MCU halt and cleared
* after the L2 cache power down. MCU can't be
* re-enabled whilst the flag is set.
* @in_reset: True if a GPU is resetting and normal power manager operation is
* suspended
* @partial_shaderoff: True if we want to partial power off shader cores,
* it indicates a partial shader core off case,
* do some special operation for such case like flush
* L2 cache because of GPU2017-861
* @protected_entry_transition_override : True if GPU reset is being used
* before entering the protected mode and so
* the reset handling behaviour is being
* overridden.
* @protected_transition_override : True if a protected mode transition is in
* progress and is overriding power manager
* behaviour.
* @protected_l2_override : Non-zero if the L2 cache is required during a
* protected mode transition. Has no effect if not
* transitioning.
* @hwcnt_desired: True if we want GPU hardware counters to be enabled.
* @hwcnt_disabled: True if GPU hardware counters are not enabled.
* @hwcnt_disable_work: Work item to disable GPU hardware counters, used if
* atomic disable is not possible.
* @gpu_clock_suspend_freq: 'opp-mali-errata-1485982' clock in opp table
* for safe L2 power cycle.
* If no opp-mali-errata-1485982 specified,
* the slowest clock will be taken.
* @gpu_clock_slow_down_wa: If true, slow down GPU clock during L2 power cycle.
* @gpu_clock_slow_down_desired: True if we want lower GPU clock
* for safe L2 power cycle. False if want GPU clock
* to back to normalized one. This is updated only
* in L2 state machine, kbase_pm_l2_update_state.
* @gpu_clock_slowed_down: During L2 power cycle,
* True if gpu clock is set at lower frequency
* for safe L2 power down, False if gpu clock gets
* restored to previous speed. This is updated only in
* work function, kbase_pm_gpu_clock_control_worker.
* @gpu_clock_control_work: work item to set GPU clock during L2 power cycle
* using gpu_clock_control
* @reset_in_progress: Set if reset is ongoing, otherwise set to 0
*
* This structure contains data for the power management framework. There is one
* instance of this structure per device in the system.
*
* Note:
* During an IRQ, @pm_current_policy can be NULL when the policy is being
* changed with kbase_pm_set_policy(). The change is protected under
* kbase_device.pm.pcower_change_lock. Direct access to this from IRQ context
* must therefore check for NULL. If NULL, then kbase_pm_set_policy() will
* re-issue the policy functions that would have been done under IRQ.
*/
struct kbase_pm_backend_data {
const struct kbase_pm_policy *pm_current_policy;
union kbase_pm_policy_data pm_policy_data;
bool reset_done;
wait_queue_head_t reset_done_wait;
int gpu_cycle_counter_requests;
spinlock_t gpu_cycle_counter_requests_lock;
wait_queue_head_t gpu_in_desired_state_wait;
bool gpu_powered;
bool gpu_ready;
u64 pm_shaders_core_mask;
bool cg1_disabled;
struct kbasep_pm_metrics_state metrics;
struct kbasep_pm_tick_timer_state shader_tick_timer;
bool poweroff_wait_in_progress;
bool invoke_poweroff_wait_wq_when_l2_off;
bool poweron_required;
struct workqueue_struct *gpu_poweroff_wait_wq;
struct work_struct gpu_poweroff_wait_work;
wait_queue_head_t poweroff_wait;
int (*callback_power_on)(struct kbase_device *kbdev);
void (*callback_power_off)(struct kbase_device *kbdev);
void (*callback_power_suspend)(struct kbase_device *kbdev);
void (*callback_power_resume)(struct kbase_device *kbdev);
int (*callback_power_runtime_on)(struct kbase_device *kbdev);
void (*callback_power_runtime_off)(struct kbase_device *kbdev);
int (*callback_power_runtime_idle)(struct kbase_device *kbdev);
int (*callback_soft_reset)(struct kbase_device *kbdev);
void (*callback_power_runtime_gpu_idle)(struct kbase_device *kbdev);
void (*callback_power_runtime_gpu_active)(struct kbase_device *kbdev);
u64 ca_cores_enabled;
#if MALI_USE_CSF
bool apply_hw_issue_TITANHW_2938_wa;
enum kbase_mcu_state mcu_state;
#endif
enum kbase_l2_core_state l2_state;
enum kbase_shader_core_state shaders_state;
u64 shaders_avail;
u64 shaders_desired_mask;
#if MALI_USE_CSF
bool mcu_desired;
bool mcu_poweron_required;
bool policy_change_clamp_state_to_off;
unsigned int csf_pm_sched_flags;
struct mutex policy_change_lock;
struct workqueue_struct *core_idle_wq;
struct work_struct core_idle_work;
#ifdef KBASE_PM_RUNTIME
unsigned long gpu_sleep_allowed;
bool gpu_sleep_mode_active;
bool exit_gpu_sleep_mode;
bool gpu_idled;
bool gpu_wakeup_override;
bool db_mirror_interrupt_enabled;
enum kbase_pm_runtime_suspend_abort_reason runtime_suspend_abort_reason;
#endif
bool l2_force_off_after_mcu_halt;
#endif
bool l2_desired;
bool l2_always_on;
bool shaders_desired;
bool in_reset;
#if !MALI_USE_CSF
bool partial_shaderoff;
bool protected_entry_transition_override;
bool protected_transition_override;
int protected_l2_override;
#endif
bool hwcnt_desired;
bool hwcnt_disabled;
struct work_struct hwcnt_disable_work;
u64 gpu_clock_suspend_freq;
bool gpu_clock_slow_down_wa;
bool gpu_clock_slow_down_desired;
bool gpu_clock_slowed_down;
struct work_struct gpu_clock_control_work;
atomic_t reset_in_progress;
};
#if MALI_USE_CSF
/* CSF PM flag, signaling that the MCU shader Core should be kept on */
#define CSF_DYNAMIC_PM_CORE_KEEP_ON (1 << 0)
/* CSF PM flag, signaling no scheduler suspension on idle groups */
#define CSF_DYNAMIC_PM_SCHED_IGNORE_IDLE (1 << 1)
/* CSF PM flag, signaling no scheduler suspension on no runnable groups */
#define CSF_DYNAMIC_PM_SCHED_NO_SUSPEND (1 << 2)
/* The following flags corresponds to existing defined PM policies */
#define ALWAYS_ON_PM_SCHED_FLAGS \
(CSF_DYNAMIC_PM_CORE_KEEP_ON | CSF_DYNAMIC_PM_SCHED_IGNORE_IDLE | \
CSF_DYNAMIC_PM_SCHED_NO_SUSPEND)
#define COARSE_ON_DEMAND_PM_SCHED_FLAGS (0)
#if !MALI_CUSTOMER_RELEASE
#define ALWAYS_ON_DEMAND_PM_SCHED_FLAGS (CSF_DYNAMIC_PM_SCHED_IGNORE_IDLE)
#endif
#endif
/* List of policy IDs */
enum kbase_pm_policy_id {
KBASE_PM_POLICY_ID_COARSE_DEMAND,
#if !MALI_CUSTOMER_RELEASE
KBASE_PM_POLICY_ID_ALWAYS_ON_DEMAND,
#endif
KBASE_PM_POLICY_ID_ALWAYS_ON
};
/**
* enum kbase_pm_policy_event - PM Policy event ID
*/
enum kbase_pm_policy_event {
/**
* @KBASE_PM_POLICY_EVENT_IDLE: Indicates that the GPU power state
* model has determined that the GPU has gone idle.
*/
KBASE_PM_POLICY_EVENT_IDLE,
/**
* @KBASE_PM_POLICY_EVENT_POWER_ON: Indicates that the GPU state model
* is preparing to power on the GPU.
*/
KBASE_PM_POLICY_EVENT_POWER_ON,
/**
* @KBASE_PM_POLICY_EVENT_TIMER_HIT: Indicates that the GPU became
* active while the Shader Tick Timer was holding the GPU in a powered
* on state.
*/
KBASE_PM_POLICY_EVENT_TIMER_HIT,
/**
* @KBASE_PM_POLICY_EVENT_TIMER_MISS: Indicates that the GPU did not
* become active before the Shader Tick Timer timeout occurred.
*/
KBASE_PM_POLICY_EVENT_TIMER_MISS
};
/**
* struct kbase_pm_policy - Power policy structure.
*
* @name: The name of this policy
* @init: Function called when the policy is selected
* @term: Function called when the policy is unselected
* @shaders_needed: Function called to find out if shader cores are needed
* @get_core_active: Function called to get the current overall GPU power
* state
* @handle_event: Function called when a PM policy event occurs. Should be
* set to NULL if the power policy doesn't require any
* event notifications.
* @id: Field indicating an ID for this policy. This is not
* necessarily the same as its index in the list returned
* by kbase_pm_list_policies().
* It is used purely for debugging.
* @pm_sched_flags: Policy associated with CSF PM scheduling operational flags.
* Pre-defined required flags exist for each of the
* ARM released policies, such as 'always_on', 'coarse_demand'
* and etc.
* Each power policy exposes a (static) instance of this structure which
* contains function pointers to the policy's methods.
*/
struct kbase_pm_policy {
char *name;
/*
* Function called when the policy is selected
*
* This should initialize the kbdev->pm.pm_policy_data structure. It
* should not attempt to make any changes to hardware state.
*
* It is undefined what state the cores are in when the function is
* called.
*
* @kbdev: The kbase device structure for the device (must be a
* valid pointer)
*/
void (*init)(struct kbase_device *kbdev);
/*
* Function called when the policy is unselected.
*
* @kbdev: The kbase device structure for the device (must be a
* valid pointer)
*/
void (*term)(struct kbase_device *kbdev);
/*
* Function called to find out if shader cores are needed
*
* This needs to at least satisfy kbdev->pm.backend.shaders_desired,
* and so must never return false when shaders_desired is true.
*
* @kbdev: The kbase device structure for the device (must be a
* valid pointer)
*
* Return: true if shader cores are needed, false otherwise
*/
bool (*shaders_needed)(struct kbase_device *kbdev);
/*
* Function called to get the current overall GPU power state
*
* This function must meet or exceed the requirements for power
* indicated by kbase_pm_is_active().
*
* @kbdev: The kbase device structure for the device (must be a
* valid pointer)
*
* Return: true if the GPU should be powered, false otherwise
*/
bool (*get_core_active)(struct kbase_device *kbdev);
/*
* Function called when a power event occurs
*
* @kbdev: The kbase device structure for the device (must be a
* valid pointer)
* @event: The id of the power event that has occurred
*/
void (*handle_event)(struct kbase_device *kbdev, enum kbase_pm_policy_event event);
enum kbase_pm_policy_id id;
#if MALI_USE_CSF
/* Policy associated with CSF PM scheduling operational flags.
* There are pre-defined required flags exist for each of the
* ARM released policies, such as 'always_on', 'coarse_demand'
* and etc.
*/
unsigned int pm_sched_flags;
#endif
};
#endif /* _KBASE_PM_HWACCESS_DEFS_H_ */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2018-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific Power Manager level 2 cache state definitions.
* The function-like macro KBASEP_L2_STATE() must be defined before including
* this header file. This header file can be included multiple times in the
* same compilation unit with different definitions of KBASEP_L2_STATE().
*
* @OFF: The L2 cache and tiler are off
* @PEND_ON: The L2 cache and tiler are powering on
* @RESTORE_CLOCKS: The GPU clock is restored. Conditionally used.
* @ON_HWCNT_ENABLE: The L2 cache and tiler are on, and hwcnt is being enabled
* @ON: The L2 cache and tiler are on, and hwcnt is enabled
* @ON_HWCNT_DISABLE: The L2 cache and tiler are on, and hwcnt is being disabled
* @SLOW_DOWN_CLOCKS: The GPU clock is set to appropriate or lowest clock.
* Conditionally used.
* @POWER_DOWN: The L2 cache and tiler are about to be powered off
* @PEND_OFF: The L2 cache and tiler are powering off
* @RESET_WAIT: The GPU is resetting, L2 cache and tiler power state are
* unknown
*/
KBASEP_L2_STATE(OFF)
KBASEP_L2_STATE(PEND_ON)
KBASEP_L2_STATE(RESTORE_CLOCKS)
KBASEP_L2_STATE(ON_HWCNT_ENABLE)
KBASEP_L2_STATE(ON)
KBASEP_L2_STATE(ON_HWCNT_DISABLE)
KBASEP_L2_STATE(SLOW_DOWN_CLOCKS)
KBASEP_L2_STATE(POWER_DOWN)
KBASEP_L2_STATE(PEND_OFF)
KBASEP_L2_STATE(RESET_WAIT)

View file

@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2020-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific Power Manager MCU state definitions.
* The function-like macro KBASEP_MCU_STATE() must be defined before including
* this header file. This header file can be included multiple times in the
* same compilation unit with different definitions of KBASEP_MCU_STATE().
*
* @OFF: The MCU is powered off.
* @PEND_ON_RELOAD: The warm boot of MCU or cold boot of MCU (with
* firmware reloading) is in progress.
* @ON_GLB_REINIT_PEND: The MCU is enabled and Global configuration
* requests have been sent to the firmware.
* @ON_HWCNT_ENABLE: The Global requests have completed and MCU is now
* ready for use and hwcnt is being enabled.
* @ON: The MCU is active and hwcnt has been enabled.
* @ON_CORE_ATTR_UPDATE_PEND: The MCU is active and mask of enabled shader cores
* is being updated.
* @ON_HWCNT_DISABLE: The MCU is on and hwcnt is being disabled.
* @ON_HALT: The MCU is on and hwcnt has been disabled, MCU
* halt would be triggered.
* @ON_PEND_HALT: MCU halt in progress, confirmation pending.
* @POWER_DOWN: MCU halted operations, pending being disabled.
* @PEND_OFF: MCU is being disabled, pending on powering off.
* @RESET_WAIT: The GPU is resetting, MCU state is unknown.
* @HCTL_SHADERS_PEND_ON: Global configuration requests sent to the firmware
* have completed and shaders have been requested to
* power on.
* @HCTL_CORES_NOTIFY_PEND: Shader cores have powered up and firmware is being
* notified of the mask of enabled shader cores.
* @HCTL_MCU_ON_RECHECK: MCU is on and hwcnt disabling is triggered
* and checks are done to update the number of
* enabled cores.
* @HCTL_SHADERS_READY_OFF: MCU has halted and cores need to be powered down
* @HCTL_SHADERS_PEND_OFF: Cores are transitioning to power down.
* @HCTL_CORES_DOWN_SCALE_NOTIFY_PEND: Firmware has been informed to stop using
* specific cores, due to core_mask change request.
* After the ACK from FW, the wait will be done for
* undesired cores to become inactive.
* @HCTL_CORE_INACTIVE_PEND: Waiting for specific cores to become inactive.
* Once the cores become inactive their power down
* will be initiated.
* @HCTL_SHADERS_CORE_OFF_PEND: Waiting for specific cores to complete the
* transition to power down. Once powered down,
* HW counters will be re-enabled.
* @ON_SLEEP_INITIATE: MCU is on and hwcnt has been disabled and MCU
* is being put to sleep.
* @ON_PEND_SLEEP: MCU sleep is in progress.
* @IN_SLEEP: Sleep request is completed and MCU has halted.
* @ON_PMODE_ENTER_CORESIGHT_DISABLE: The MCU is on, protected mode enter is about to
* be requested, Coresight is being disabled.
* @ON_PMODE_EXIT_CORESIGHT_ENABLE : The MCU is on, protected mode exit has happened
* Coresight is being enabled.
* @CORESIGHT_DISABLE: The MCU is on and Coresight is being disabled.
* @CORESIGHT_ENABLE: The MCU is on, host does not have control and
* Coresight is being enabled.
*/
KBASEP_MCU_STATE(OFF)
KBASEP_MCU_STATE(PEND_ON_RELOAD)
KBASEP_MCU_STATE(ON_GLB_REINIT_PEND)
KBASEP_MCU_STATE(ON_HWCNT_ENABLE)
KBASEP_MCU_STATE(ON)
KBASEP_MCU_STATE(ON_CORE_ATTR_UPDATE_PEND)
KBASEP_MCU_STATE(ON_HWCNT_DISABLE)
KBASEP_MCU_STATE(ON_HALT)
KBASEP_MCU_STATE(ON_PEND_HALT)
KBASEP_MCU_STATE(POWER_DOWN)
KBASEP_MCU_STATE(PEND_OFF)
KBASEP_MCU_STATE(RESET_WAIT)
/* Additional MCU states with HOST_CONTROL_SHADERS */
KBASEP_MCU_STATE(HCTL_SHADERS_PEND_ON)
KBASEP_MCU_STATE(HCTL_CORES_NOTIFY_PEND)
KBASEP_MCU_STATE(HCTL_MCU_ON_RECHECK)
KBASEP_MCU_STATE(HCTL_SHADERS_READY_OFF)
KBASEP_MCU_STATE(HCTL_SHADERS_PEND_OFF)
KBASEP_MCU_STATE(HCTL_CORES_DOWN_SCALE_NOTIFY_PEND)
KBASEP_MCU_STATE(HCTL_CORE_INACTIVE_PEND)
KBASEP_MCU_STATE(HCTL_SHADERS_CORE_OFF_PEND)
/* Additional MCU states to support GPU sleep feature */
KBASEP_MCU_STATE(ON_SLEEP_INITIATE)
KBASEP_MCU_STATE(ON_PEND_SLEEP)
KBASEP_MCU_STATE(ON_PEND_SOI_SLEEP)
KBASEP_MCU_STATE(IN_SLEEP)
#if IS_ENABLED(CONFIG_MALI_CORESIGHT)
/* Additional MCU states for Coresight */
KBASEP_MCU_STATE(ON_PMODE_ENTER_CORESIGHT_DISABLE)
KBASEP_MCU_STATE(ON_PMODE_EXIT_CORESIGHT_ENABLE)
KBASEP_MCU_STATE(CORESIGHT_DISABLE)
KBASEP_MCU_STATE(CORESIGHT_ENABLE)
#endif /* IS_ENABLED(CONFIG_MALI_CORESIGHT) */

View file

@ -0,0 +1,508 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2011-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Metrics for power management
*/
#include <mali_kbase.h>
#include <mali_kbase_config_defaults.h>
#include <mali_kbase_pm.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#if MALI_USE_CSF
#include "backend/gpu/mali_kbase_clk_rate_trace_mgr.h"
#include <csf/ipa_control/mali_kbase_csf_ipa_control.h>
#else
#include <backend/gpu/mali_kbase_jm_rb.h>
#endif /* !MALI_USE_CSF */
#include <backend/gpu/mali_kbase_pm_defs.h>
#include <mali_linux_trace.h>
#include <mali_exynos_kbase_entrypoint.h>
#if defined(CONFIG_MALI_DEVFREQ) || defined(CONFIG_MALI_MIDGARD_DVFS) || !MALI_USE_CSF
/* Shift used for kbasep_pm_metrics_data.time_busy/idle - units of (1 << 8) ns
* This gives a maximum period between samples of 2^(32+8)/100 ns = slightly
* under 11s. Exceeding this will cause overflow
*/
#define KBASE_PM_TIME_SHIFT 8
#endif
#if MALI_USE_CSF
/* To get the GPU_ACTIVE value in nano seconds unit */
#define GPU_ACTIVE_SCALING_FACTOR ((u64)1E9)
#endif
/*
* Possible state transitions
* ON -> ON | OFF | STOPPED
* STOPPED -> ON | OFF
* OFF -> ON
*
*
* ef
* v v
* ON a> STOPPED b> OFF
* ^^
* c
*
* d
*
* Transition effects:
* a. None
* b. Timer expires without restart
* c. Timer is not stopped, timer period is unaffected
* d. Timer must be restarted
* e. Callback is executed and the timer is restarted
* f. Timer is cancelled, or the callback is waited on if currently executing. This is called during
* tear-down and should not be subject to a race from an OFF->ON transition
*/
enum dvfs_metric_timer_state { TIMER_OFF, TIMER_STOPPED, TIMER_ON };
#ifdef CONFIG_MALI_MIDGARD_DVFS
static enum hrtimer_restart dvfs_callback(struct hrtimer *timer)
{
struct kbasep_pm_metrics_state *metrics;
if (WARN_ON(!timer))
return HRTIMER_NORESTART;
metrics = container_of(timer, struct kbasep_pm_metrics_state, timer);
/* Transition (b) to fully off if timer was stopped, don't restart the timer in this case */
if (atomic_cmpxchg(&metrics->timer_state, TIMER_STOPPED, TIMER_OFF) != TIMER_ON)
return HRTIMER_NORESTART;
kbase_pm_get_dvfs_action(metrics->kbdev);
/* Set the new expiration time and restart (transition e) */
hrtimer_forward_now(timer, HR_TIMER_DELAY_MSEC(metrics->kbdev->pm.dvfs_period));
return HRTIMER_RESTART;
}
#endif /* CONFIG_MALI_MIDGARD_DVFS */
int kbasep_pm_metrics_init(struct kbase_device *kbdev)
{
#if MALI_USE_CSF
struct kbase_ipa_control_perf_counter perf_counter;
int err;
/* One counter group */
const size_t NUM_PERF_COUNTERS = 1;
KBASE_DEBUG_ASSERT(kbdev != NULL);
kbdev->pm.backend.metrics.kbdev = kbdev;
kbdev->pm.backend.metrics.time_period_start = ktime_get_raw();
perf_counter.scaling_factor = GPU_ACTIVE_SCALING_FACTOR;
/* Normalize values by GPU frequency */
perf_counter.gpu_norm = true;
/* We need the GPU_ACTIVE counter, which is in the CSHW group */
perf_counter.type = KBASE_IPA_CORE_TYPE_CSHW;
/* We need the GPU_ACTIVE counter */
perf_counter.idx = GPU_ACTIVE_CNT_IDX;
err = kbase_ipa_control_register(kbdev, &perf_counter, NUM_PERF_COUNTERS,
&kbdev->pm.backend.metrics.ipa_control_client);
if (err) {
dev_err(kbdev->dev, "Failed to register IPA with kbase_ipa_control: err=%d", err);
return -1;
}
#else
KBASE_DEBUG_ASSERT(kbdev != NULL);
kbdev->pm.backend.metrics.kbdev = kbdev;
kbdev->pm.backend.metrics.time_period_start = ktime_get_raw();
#endif
spin_lock_init(&kbdev->pm.backend.metrics.lock);
#ifdef CONFIG_MALI_MIDGARD_DVFS
hrtimer_init(&kbdev->pm.backend.metrics.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
kbdev->pm.backend.metrics.timer.function = dvfs_callback;
kbdev->pm.backend.metrics.initialized = true;
atomic_set(&kbdev->pm.backend.metrics.timer_state, TIMER_OFF);
kbase_pm_metrics_start(kbdev);
#endif /* CONFIG_MALI_MIDGARD_DVFS */
#if MALI_USE_CSF
/* The sanity check on the GPU_ACTIVE performance counter
* is skipped for Juno platforms that have timing problems.
*/
kbdev->pm.backend.metrics.skip_gpu_active_sanity_check =
of_machine_is_compatible("arm,juno");
#endif
return 0;
}
KBASE_EXPORT_TEST_API(kbasep_pm_metrics_init);
void kbasep_pm_metrics_term(struct kbase_device *kbdev)
{
#ifdef CONFIG_MALI_MIDGARD_DVFS
KBASE_DEBUG_ASSERT(kbdev != NULL);
/* Cancel the timer, and block if the callback is currently executing (transition f) */
kbdev->pm.backend.metrics.initialized = false;
atomic_set(&kbdev->pm.backend.metrics.timer_state, TIMER_OFF);
hrtimer_cancel(&kbdev->pm.backend.metrics.timer);
#endif /* CONFIG_MALI_MIDGARD_DVFS */
#if MALI_USE_CSF
kbase_ipa_control_unregister(kbdev, kbdev->pm.backend.metrics.ipa_control_client);
#else
CSTD_UNUSED(kbdev);
#endif
}
KBASE_EXPORT_TEST_API(kbasep_pm_metrics_term);
/* caller needs to hold kbdev->pm.backend.metrics.lock before calling this
* function
*/
#if MALI_USE_CSF
#if defined(CONFIG_MALI_DEVFREQ) || defined(CONFIG_MALI_MIDGARD_DVFS)
static void kbase_pm_get_dvfs_utilisation_calc(struct kbase_device *kbdev)
{
int err;
u64 gpu_active_counter;
u64 protected_time;
ktime_t now;
lockdep_assert_held(&kbdev->pm.backend.metrics.lock);
/* Query IPA_CONTROL for the latest GPU-active and protected-time
* info.
*/
err = kbase_ipa_control_query(kbdev, kbdev->pm.backend.metrics.ipa_control_client,
&gpu_active_counter, 1, &protected_time);
/* Read the timestamp after reading the GPU_ACTIVE counter value.
* This ensures the time gap between the 2 reads is consistent for
* a meaningful comparison between the increment of GPU_ACTIVE and
* elapsed time. The lock taken inside kbase_ipa_control_query()
* function can cause lot of variation.
*/
now = ktime_get_raw();
if (err) {
dev_err(kbdev->dev, "Failed to query the increment of GPU_ACTIVE counter: err=%d",
err);
} else {
u64 diff_ns;
s64 diff_ns_signed;
u32 ns_time;
ktime_t diff = ktime_sub(now, kbdev->pm.backend.metrics.time_period_start);
diff_ns_signed = ktime_to_ns(diff);
if (diff_ns_signed < 0)
return;
diff_ns = (u64)diff_ns_signed;
#if !IS_ENABLED(CONFIG_MALI_NO_MALI)
/* The GPU_ACTIVE counter shouldn't clock-up more time than has
* actually elapsed - but still some margin needs to be given
* when doing the comparison. There could be some drift between
* the CPU and GPU clock.
*
* Can do the check only in a real driver build, as an arbitrary
* value for GPU_ACTIVE can be fed into dummy model in no_mali
* configuration which may not correspond to the real elapsed
* time.
*/
if (!kbdev->pm.backend.metrics.skip_gpu_active_sanity_check) {
/* The margin is scaled to allow for the worst-case
* scenario where the samples are maximally separated,
* plus a small offset for sampling errors.
*/
u64 const MARGIN_NS =
IPA_CONTROL_TIMER_DEFAULT_VALUE_MS * NSEC_PER_MSEC * 3 / 2;
if (gpu_active_counter > (diff_ns + MARGIN_NS)) {
dev_info(
kbdev->dev,
"GPU activity takes longer than time interval: %llu ns > %llu ns",
(unsigned long long)gpu_active_counter,
(unsigned long long)diff_ns);
}
}
#endif
/* Calculate time difference in units of 256ns */
ns_time = (u32)(diff_ns >> KBASE_PM_TIME_SHIFT);
/* Add protected_time to gpu_active_counter so that time in
* protected mode is included in the apparent GPU active time,
* then convert it from units of 1ns to units of 256ns, to
* match what JM GPUs use. The assumption is made here that the
* GPU is 100% busy while in protected mode, so we should add
* this since the GPU can't (and thus won't) update these
* counters while it's actually in protected mode.
*
* Perform the add after dividing each value down, to reduce
* the chances of overflows.
*/
protected_time >>= KBASE_PM_TIME_SHIFT;
gpu_active_counter >>= KBASE_PM_TIME_SHIFT;
gpu_active_counter += protected_time;
/* Ensure the following equations don't go wrong if ns_time is
* slightly larger than gpu_active_counter somehow
*/
gpu_active_counter = MIN(gpu_active_counter, ns_time);
kbdev->pm.backend.metrics.values.time_busy += gpu_active_counter;
kbdev->pm.backend.metrics.values.time_idle += ns_time - gpu_active_counter;
/* Also make time in protected mode available explicitly,
* so users of this data have this info, too.
*/
kbdev->pm.backend.metrics.values.time_in_protm += protected_time;
}
kbdev->pm.backend.metrics.time_period_start = now;
}
#endif /* defined(CONFIG_MALI_DEVFREQ) || defined(CONFIG_MALI_MIDGARD_DVFS) */
#else
static void kbase_pm_get_dvfs_utilisation_calc(struct kbase_device *kbdev, ktime_t now)
{
ktime_t diff;
lockdep_assert_held(&kbdev->pm.backend.metrics.lock);
diff = ktime_sub(now, kbdev->pm.backend.metrics.time_period_start);
if (ktime_to_ns(diff) < 0)
return;
if (kbdev->pm.backend.metrics.gpu_active) {
u32 ns_time = (u32)(ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT);
kbdev->pm.backend.metrics.values.time_busy += ns_time;
if (kbdev->pm.backend.metrics.active_cl_ctx[0])
kbdev->pm.backend.metrics.values.busy_cl[0] += ns_time;
if (kbdev->pm.backend.metrics.active_cl_ctx[1])
kbdev->pm.backend.metrics.values.busy_cl[1] += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[0])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[1])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
if (kbdev->pm.backend.metrics.active_gl_ctx[2])
kbdev->pm.backend.metrics.values.busy_gl += ns_time;
} else {
kbdev->pm.backend.metrics.values.time_idle +=
(u32)(ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT);
}
mali_exynos_update_jobslot_util(0, kbdev->pm.backend.metrics.gpu_active,
(u32)ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT);
kbdev->pm.backend.metrics.time_period_start = now;
}
#endif /* MALI_USE_CSF */
#if defined(CONFIG_MALI_DEVFREQ) || defined(CONFIG_MALI_MIDGARD_DVFS)
void kbase_pm_get_dvfs_metrics(struct kbase_device *kbdev, struct kbasep_pm_metrics *last,
struct kbasep_pm_metrics *diff)
{
struct kbasep_pm_metrics *cur = &kbdev->pm.backend.metrics.values;
unsigned long flags;
spin_lock_irqsave(&kbdev->pm.backend.metrics.lock, flags);
#if MALI_USE_CSF
kbase_pm_get_dvfs_utilisation_calc(kbdev);
#else
kbase_pm_get_dvfs_utilisation_calc(kbdev, ktime_get_raw());
#endif
memset(diff, 0, sizeof(*diff));
diff->time_busy = cur->time_busy - last->time_busy;
diff->time_idle = cur->time_idle - last->time_idle;
#if MALI_USE_CSF
diff->time_in_protm = cur->time_in_protm - last->time_in_protm;
#else
diff->busy_cl[0] = cur->busy_cl[0] - last->busy_cl[0];
diff->busy_cl[1] = cur->busy_cl[1] - last->busy_cl[1];
diff->busy_gl = cur->busy_gl - last->busy_gl;
#endif
*last = *cur;
spin_unlock_irqrestore(&kbdev->pm.backend.metrics.lock, flags);
}
KBASE_EXPORT_TEST_API(kbase_pm_get_dvfs_metrics);
#endif
#ifdef CONFIG_MALI_MIDGARD_DVFS
void kbase_pm_get_dvfs_action(struct kbase_device *kbdev)
{
int utilisation;
struct kbasep_pm_metrics *diff;
#if !MALI_USE_CSF
int busy;
int util_gl_share;
int util_cl_share[2];
#endif
KBASE_DEBUG_ASSERT(kbdev != NULL);
diff = &kbdev->pm.backend.metrics.dvfs_diff;
kbase_pm_get_dvfs_metrics(kbdev, &kbdev->pm.backend.metrics.dvfs_last, diff);
utilisation = (100 * diff->time_busy) / max(diff->time_busy + diff->time_idle, 1u);
#if !MALI_USE_CSF
busy = max(diff->busy_gl + diff->busy_cl[0] + diff->busy_cl[1], 1u);
util_gl_share = (100 * diff->busy_gl) / busy;
util_cl_share[0] = (100 * diff->busy_cl[0]) / busy;
util_cl_share[1] = (100 * diff->busy_cl[1]) / busy;
kbase_platform_dvfs_event(kbdev, utilisation, util_gl_share, util_cl_share);
#else
/* Note that, at present, we don't pass protected-mode time to the
* platform here. It's unlikely to be useful, however, as the platform
* probably just cares whether the GPU is busy or not; time in
* protected mode is already added to busy-time at this point, though,
* so we should be good.
*/
kbase_platform_dvfs_event(kbdev, utilisation);
#endif
}
bool kbase_pm_metrics_is_active(struct kbase_device *kbdev)
{
KBASE_DEBUG_ASSERT(kbdev != NULL);
return atomic_read(&kbdev->pm.backend.metrics.timer_state) == TIMER_ON;
}
KBASE_EXPORT_TEST_API(kbase_pm_metrics_is_active);
void kbase_pm_metrics_start(struct kbase_device *kbdev)
{
struct kbasep_pm_metrics_state *metrics = &kbdev->pm.backend.metrics;
if (unlikely(!metrics->initialized))
return;
/* Transition to ON, from a stopped state (transition c) */
if (atomic_xchg(&metrics->timer_state, TIMER_ON) == TIMER_OFF)
/* Start the timer only if it's been fully stopped (transition d)*/
hrtimer_start(&metrics->timer, HR_TIMER_DELAY_MSEC(kbdev->pm.dvfs_period),
HRTIMER_MODE_REL);
}
void kbase_pm_metrics_stop(struct kbase_device *kbdev)
{
if (unlikely(!kbdev->pm.backend.metrics.initialized))
return;
/* Timer is Stopped if its currently on (transition a) */
atomic_cmpxchg(&kbdev->pm.backend.metrics.timer_state, TIMER_ON, TIMER_STOPPED);
}
#endif /* CONFIG_MALI_MIDGARD_DVFS */
#if !MALI_USE_CSF
/**
* kbase_pm_metrics_active_calc - Update PM active counts based on currently
* running atoms
* @kbdev: Device pointer
*
* The caller must hold kbdev->pm.backend.metrics.lock
*/
static void kbase_pm_metrics_active_calc(struct kbase_device *kbdev)
{
unsigned int js;
lockdep_assert_held(&kbdev->pm.backend.metrics.lock);
kbdev->pm.backend.metrics.active_gl_ctx[0] = 0;
kbdev->pm.backend.metrics.active_gl_ctx[1] = 0;
kbdev->pm.backend.metrics.active_gl_ctx[2] = 0;
kbdev->pm.backend.metrics.active_cl_ctx[0] = 0;
kbdev->pm.backend.metrics.active_cl_ctx[1] = 0;
kbdev->pm.backend.metrics.gpu_active = false;
mali_exynos_set_jobslot_status(0, false);
for (js = 0; js < BASE_JM_MAX_NR_SLOTS; js++) {
struct kbase_jd_atom *katom = kbase_gpu_inspect(kbdev, js, 0);
/* Head atom may have just completed, so if it isn't running
* then try the next atom
*/
if (katom && katom->gpu_rb_state != KBASE_ATOM_GPU_RB_SUBMITTED)
katom = kbase_gpu_inspect(kbdev, js, 1);
if (katom && katom->gpu_rb_state == KBASE_ATOM_GPU_RB_SUBMITTED) {
if (katom->core_req & BASE_JD_REQ_ONLY_COMPUTE) {
u32 device_nr =
(katom->core_req & BASE_JD_REQ_SPECIFIC_COHERENT_GROUP) ?
katom->device_nr :
0;
if (!WARN_ON(device_nr >= 2))
kbdev->pm.backend.metrics.active_cl_ctx[device_nr] = 1;
} else {
kbdev->pm.backend.metrics.active_gl_ctx[js] = 1;
trace_sysgraph(SGR_ACTIVE, 0, js);
}
kbdev->pm.backend.metrics.gpu_active = true;
mali_exynos_set_jobslot_status(js, true);
} else {
trace_sysgraph(SGR_INACTIVE, 0, js);
}
}
}
/* called when job is submitted to or removed from a GPU slot */
void kbase_pm_metrics_update(struct kbase_device *kbdev, ktime_t *timestamp)
{
unsigned long flags;
ktime_t now;
lockdep_assert_held(&kbdev->hwaccess_lock);
spin_lock_irqsave(&kbdev->pm.backend.metrics.lock, flags);
if (!timestamp) {
now = ktime_get_raw();
timestamp = &now;
}
/* Track how much of time has been spent busy or idle. For JM GPUs,
* this also evaluates how long CL and/or GL jobs have been busy for.
*/
kbase_pm_get_dvfs_utilisation_calc(kbdev, *timestamp);
kbase_pm_metrics_active_calc(kbdev);
spin_unlock_irqrestore(&kbdev->pm.backend.metrics.lock, flags);
}
#endif /* !MALI_USE_CSF */

View file

@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2010-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Power policy API implementations
*/
#include <mali_kbase.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <mali_kbase_pm.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <mali_kbase_reset_gpu.h>
#if MALI_USE_CSF && defined CONFIG_MALI_DEBUG
#include <csf/mali_kbase_csf_firmware.h>
#endif
#include <linux/of.h>
static const struct kbase_pm_policy *const all_policy_list[] = {
#if IS_ENABLED(CONFIG_MALI_NO_MALI)
&kbase_pm_always_on_policy_ops,
&kbase_pm_coarse_demand_policy_ops,
#else /* CONFIG_MALI_NO_MALI */
&kbase_pm_coarse_demand_policy_ops,
&kbase_pm_always_on_policy_ops,
#endif /* CONFIG_MALI_NO_MALI */
};
void kbase_pm_policy_init(struct kbase_device *kbdev)
{
const struct kbase_pm_policy *default_policy = all_policy_list[0];
struct device_node *np = kbdev->dev->of_node;
const char *power_policy_name;
unsigned long flags;
unsigned int i;
/* Read "power-policy" property and fallback to "power_policy" if not found */
if ((of_property_read_string(np, "power-policy", &power_policy_name) == 0) ||
(of_property_read_string(np, "power_policy", &power_policy_name) == 0)) {
for (i = 0; i < ARRAY_SIZE(all_policy_list); i++)
if (sysfs_streq(all_policy_list[i]->name, power_policy_name)) {
default_policy = all_policy_list[i];
break;
}
}
#if MALI_USE_CSF && defined(CONFIG_MALI_DEBUG)
/* Use always_on policy if module param fw_debug=1 is
* passed, to aid firmware debugging.
*/
if (fw_debug)
default_policy = &kbase_pm_always_on_policy_ops;
#endif
default_policy->init(kbdev);
#if MALI_USE_CSF
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
kbdev->pm.backend.pm_current_policy = default_policy;
kbdev->pm.backend.csf_pm_sched_flags = default_policy->pm_sched_flags;
#ifdef KBASE_PM_RUNTIME
if (kbase_pm_idle_groups_sched_suspendable(kbdev))
clear_bit(KBASE_GPU_IGNORE_IDLE_EVENT, &kbdev->pm.backend.gpu_sleep_allowed);
else
set_bit(KBASE_GPU_IGNORE_IDLE_EVENT, &kbdev->pm.backend.gpu_sleep_allowed);
#endif /* KBASE_PM_RUNTIME */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
#else
CSTD_UNUSED(flags);
kbdev->pm.backend.pm_current_policy = default_policy;
#endif
}
void kbase_pm_policy_term(struct kbase_device *kbdev)
{
kbdev->pm.backend.pm_current_policy->term(kbdev);
}
void kbase_pm_update_active(struct kbase_device *kbdev)
{
struct kbase_pm_device_data *pm = &kbdev->pm;
struct kbase_pm_backend_data *backend = &pm->backend;
unsigned long flags;
bool active;
lockdep_assert_held(&pm->lock);
/* pm_current_policy will never be NULL while pm.lock is held */
KBASE_DEBUG_ASSERT(backend->pm_current_policy);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
active = backend->pm_current_policy->get_core_active(kbdev);
WARN((kbase_pm_is_active(kbdev) && !active),
"GPU is active but policy '%s' is indicating that it can be powered off",
kbdev->pm.backend.pm_current_policy->name);
if (active) {
/* Power on the GPU and any cores requested by the policy */
if (!pm->backend.invoke_poweroff_wait_wq_when_l2_off &&
pm->backend.poweroff_wait_in_progress) {
KBASE_DEBUG_ASSERT(kbdev->pm.backend.gpu_powered);
pm->backend.poweron_required = true;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
} else {
/* Cancel the invocation of
* kbase_pm_gpu_poweroff_wait_wq() from the L2 state
* machine. This is safe - it
* invoke_poweroff_wait_wq_when_l2_off is true, then
* the poweroff work hasn't even been queued yet,
* meaning we can go straight to powering on.
*/
pm->backend.invoke_poweroff_wait_wq_when_l2_off = false;
pm->backend.poweroff_wait_in_progress = false;
pm->backend.l2_desired = true;
#if MALI_USE_CSF
pm->backend.mcu_desired = pm->backend.mcu_poweron_required;
#endif
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
kbase_pm_do_poweron(kbdev, false);
}
} else {
/* It is an error for the power policy to power off the GPU
* when there are contexts active
*/
KBASE_DEBUG_ASSERT(pm->active_count == 0);
pm->backend.poweron_required = false;
/* Request power off */
if (pm->backend.gpu_powered) {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
/* Power off the GPU immediately */
kbase_pm_do_poweroff(kbdev);
} else {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
}
}
void kbase_pm_update_dynamic_cores_onoff(struct kbase_device *kbdev)
{
bool shaders_desired;
lockdep_assert_held(&kbdev->hwaccess_lock);
lockdep_assert_held(&kbdev->pm.lock);
if (kbdev->pm.backend.pm_current_policy == NULL)
return;
if (kbdev->pm.backend.poweroff_wait_in_progress)
return;
#if MALI_USE_CSF
CSTD_UNUSED(shaders_desired);
/* Invoke the MCU state machine to send a request to FW for updating
* the mask of shader cores that can be used for allocation of
* endpoints requested by CSGs.
*/
if (kbase_pm_is_mcu_desired(kbdev))
kbase_pm_update_state(kbdev);
#else
/* In protected transition, don't allow outside shader core request
* affect transition, return directly
*/
if (kbdev->pm.backend.protected_transition_override)
return;
shaders_desired = kbdev->pm.backend.pm_current_policy->shaders_needed(kbdev);
if (shaders_desired && kbase_pm_is_l2_desired(kbdev))
kbase_pm_update_state(kbdev);
#endif
}
void kbase_pm_update_cores_state_nolock(struct kbase_device *kbdev)
{
bool shaders_desired = false;
lockdep_assert_held(&kbdev->hwaccess_lock);
if (kbdev->pm.backend.pm_current_policy == NULL)
return;
if (kbdev->pm.backend.poweroff_wait_in_progress)
return;
#if !MALI_USE_CSF
if (kbdev->pm.backend.protected_transition_override)
/* We are trying to change in/out of protected mode - force all
* cores off so that the L2 powers down
*/
shaders_desired = false;
else
shaders_desired = kbdev->pm.backend.pm_current_policy->shaders_needed(kbdev);
#endif
if (kbdev->pm.backend.shaders_desired != shaders_desired) {
KBASE_KTRACE_ADD(kbdev, PM_CORES_CHANGE_DESIRED, NULL,
kbdev->pm.backend.shaders_desired);
kbdev->pm.backend.shaders_desired = shaders_desired;
kbase_pm_update_state(kbdev);
}
}
void kbase_pm_update_cores_state(struct kbase_device *kbdev)
{
unsigned long flags;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
kbase_pm_update_cores_state_nolock(kbdev);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
size_t kbase_pm_list_policies(struct kbase_device *kbdev,
const struct kbase_pm_policy *const **list)
{
CSTD_UNUSED(kbdev);
if (list)
*list = all_policy_list;
return ARRAY_SIZE(all_policy_list);
}
KBASE_EXPORT_TEST_API(kbase_pm_list_policies);
const struct kbase_pm_policy *kbase_pm_get_policy(struct kbase_device *kbdev)
{
KBASE_DEBUG_ASSERT(kbdev != NULL);
return kbdev->pm.backend.pm_current_policy;
}
KBASE_EXPORT_TEST_API(kbase_pm_get_policy);
#if MALI_USE_CSF
static int policy_change_wait_for_L2_off(struct kbase_device *kbdev)
{
long remaining;
long timeout = kbase_csf_timeout_in_jiffies(kbase_get_timeout_ms(kbdev, CSF_PM_TIMEOUT));
int err = 0;
/* Wait for L2 becoming off, by which the MCU is also implicitly off
* since the L2 state machine would only start its power-down
* sequence when the MCU is in off state. The L2 off is required
* as the tiler may need to be power cycled for MCU reconfiguration
* for host control of shader cores.
*/
#if KERNEL_VERSION(4, 13, 1) <= LINUX_VERSION_CODE
remaining = wait_event_killable_timeout(kbdev->pm.backend.gpu_in_desired_state_wait,
kbdev->pm.backend.l2_state == KBASE_L2_OFF,
timeout);
#else
remaining = wait_event_timeout(kbdev->pm.backend.gpu_in_desired_state_wait,
kbdev->pm.backend.l2_state == KBASE_L2_OFF, timeout);
#endif
if (!remaining) {
err = -ETIMEDOUT;
} else if (remaining < 0) {
dev_info(kbdev->dev, "Wait for L2_off got interrupted");
err = (int)remaining;
}
dev_dbg(kbdev->dev, "%s: err=%d mcu_state=%d, L2_state=%d\n", __func__, err,
kbdev->pm.backend.mcu_state, kbdev->pm.backend.l2_state);
return err;
}
#endif
void kbase_pm_set_policy(struct kbase_device *kbdev, const struct kbase_pm_policy *new_policy)
{
const struct kbase_pm_policy *old_policy;
unsigned long flags;
#if MALI_USE_CSF
unsigned int new_policy_csf_pm_sched_flags;
bool sched_suspend;
bool reset_gpu = false;
bool reset_op_prevented = true;
struct kbase_csf_scheduler *scheduler = NULL;
u64 pwroff_ns;
bool switching_to_always_on;
#endif
KBASE_DEBUG_ASSERT(kbdev != NULL);
KBASE_DEBUG_ASSERT(new_policy != NULL);
KBASE_KTRACE_ADD(kbdev, PM_SET_POLICY, NULL, new_policy->id);
#if MALI_USE_CSF
pwroff_ns = kbase_csf_firmware_get_mcu_core_pwroff_time(kbdev);
switching_to_always_on = new_policy == &kbase_pm_always_on_policy_ops;
if (pwroff_ns == 0 && !switching_to_always_on) {
dev_warn(
kbdev->dev,
"power_policy: cannot switch away from always_on with mcu_shader_pwroff_timeout set to 0\n");
dev_warn(
kbdev->dev,
"power_policy: resetting mcu_shader_pwroff_timeout to default value to switch policy from always_on\n");
kbase_csf_firmware_reset_mcu_core_pwroff_time(kbdev);
}
scheduler = &kbdev->csf.scheduler;
KBASE_DEBUG_ASSERT(scheduler != NULL);
/* Serialize calls on kbase_pm_set_policy() */
mutex_lock(&kbdev->pm.backend.policy_change_lock);
if (kbase_reset_gpu_prevent_and_wait(kbdev)) {
dev_warn(kbdev->dev, "Set PM policy failing to prevent gpu reset");
reset_op_prevented = false;
}
/* In case of CSF, the scheduler may be invoked to suspend. In that
* case, there is a risk that the L2 may be turned on by the time we
* check it here. So we hold the scheduler lock to avoid other operations
* interfering with the policy change and vice versa.
*/
mutex_lock(&scheduler->lock);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
/* policy_change_clamp_state_to_off, when needed, is set/cleared in
* this function, a very limited temporal scope for covering the
* change transition.
*/
WARN_ON(kbdev->pm.backend.policy_change_clamp_state_to_off);
new_policy_csf_pm_sched_flags = new_policy->pm_sched_flags;
/* Requiring the scheduler PM suspend operation when changes involving
* the always_on policy, reflected by the CSF_DYNAMIC_PM_CORE_KEEP_ON
* flag bit.
*/
sched_suspend = reset_op_prevented &&
(CSF_DYNAMIC_PM_CORE_KEEP_ON &
(new_policy_csf_pm_sched_flags | kbdev->pm.backend.csf_pm_sched_flags));
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (sched_suspend) {
/* Update the suspend flag to reflect actually suspend being done ! */
sched_suspend = !kbase_csf_scheduler_pm_suspend_no_lock(kbdev);
/* Set the reset recovery flag if the required suspend failed */
reset_gpu = !sched_suspend;
}
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
kbdev->pm.backend.policy_change_clamp_state_to_off = sched_suspend;
kbase_pm_update_state(kbdev);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (sched_suspend)
reset_gpu = policy_change_wait_for_L2_off(kbdev);
#endif
kbase_pm_lock(kbdev);
/* During a policy change we pretend the GPU is active */
/* A suspend won't happen here, because we're in a syscall from a
* userspace thread
*/
if (kbase_pm_context_active_handle_suspend_locked(kbdev,
KBASE_PM_SUSPEND_HANDLER_NOT_POSSIBLE))
dev_warn_once(
kbdev->dev,
"Error shouldn't be returned with SUSPEND_HANDLER_NOT_POSSIBLE flag.");
/* Remove the policy to prevent IRQ handlers from working on it */
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
old_policy = kbdev->pm.backend.pm_current_policy;
kbdev->pm.backend.pm_current_policy = NULL;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
KBASE_KTRACE_ADD(kbdev, PM_CURRENT_POLICY_TERM, NULL, old_policy->id);
if (old_policy->term)
old_policy->term(kbdev);
memset(&kbdev->pm.backend.pm_policy_data, 0, sizeof(union kbase_pm_policy_data));
KBASE_KTRACE_ADD(kbdev, PM_CURRENT_POLICY_INIT, NULL, new_policy->id);
if (new_policy->init)
new_policy->init(kbdev);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
kbdev->pm.backend.pm_current_policy = new_policy;
#if MALI_USE_CSF
kbdev->pm.backend.csf_pm_sched_flags = new_policy_csf_pm_sched_flags;
/* New policy in place, release the clamping on mcu/L2 off state */
kbdev->pm.backend.policy_change_clamp_state_to_off = false;
kbase_pm_update_state(kbdev);
#ifdef KBASE_PM_RUNTIME
if (kbase_pm_idle_groups_sched_suspendable(kbdev))
clear_bit(KBASE_GPU_IGNORE_IDLE_EVENT, &kbdev->pm.backend.gpu_sleep_allowed);
else
set_bit(KBASE_GPU_IGNORE_IDLE_EVENT, &kbdev->pm.backend.gpu_sleep_allowed);
#endif /* KBASE_PM_RUNTIME */
#endif
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
/* If any core power state changes were previously attempted, but
* couldn't be made because the policy was changing (current_policy was
* NULL), then re-try them here.
*/
kbase_pm_update_active(kbdev);
kbase_pm_update_cores_state(kbdev);
/* Now the policy change is finished, we release our fake context active
* reference
*/
kbase_pm_context_idle_locked(kbdev);
kbase_pm_unlock(kbdev);
#if MALI_USE_CSF
/* Reverse the suspension done */
if (sched_suspend)
kbase_csf_scheduler_pm_resume_no_lock(kbdev);
mutex_unlock(&scheduler->lock);
if (reset_op_prevented)
kbase_reset_gpu_allow(kbdev);
if (reset_gpu) {
dev_warn(kbdev->dev, "Resorting to GPU reset for policy change\n");
if (kbase_prepare_to_reset_gpu(kbdev, RESET_FLAGS_NONE))
kbase_reset_gpu(kbdev);
kbase_reset_gpu_wait(kbdev);
}
mutex_unlock(&kbdev->pm.backend.policy_change_lock);
#endif
}
KBASE_EXPORT_TEST_API(kbase_pm_set_policy);

View file

@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2010-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Power policy API definitions
*/
#ifndef _KBASE_PM_POLICY_H_
#define _KBASE_PM_POLICY_H_
/**
* kbase_pm_policy_init - Initialize power policy framework
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Must be called before calling any other policy function
*/
void kbase_pm_policy_init(struct kbase_device *kbdev);
/**
* kbase_pm_policy_term - Terminate power policy framework
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*/
void kbase_pm_policy_term(struct kbase_device *kbdev);
/**
* kbase_pm_update_active - Update the active power state of the GPU
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Calls into the current power policy
*/
void kbase_pm_update_active(struct kbase_device *kbdev);
/**
* kbase_pm_update_cores - Update the desired core state of the GPU
*
* @kbdev: The kbase device structure for the device (must be a valid pointer)
*
* Calls into the current power policy
*/
void kbase_pm_update_cores(struct kbase_device *kbdev);
/**
* kbase_pm_cores_requested - Check that a power request has been locked into
* the HW.
* @kbdev: Kbase device
* @shader_required: true if shaders are required
*
* Called by the scheduler to check if a power on request has been locked into
* the HW.
*
* Note that there is no guarantee that the cores are actually ready, however
* when the request has been locked into the HW, then it is safe to submit work
* since the HW will wait for the transition to ready.
*
* A reference must first be taken prior to making this call.
*
* Caller must hold the hwaccess_lock.
*
* Return: true if the request to the HW was successfully made else false if the
* request is still pending.
*/
static inline bool kbase_pm_cores_requested(struct kbase_device *kbdev, bool shader_required)
{
lockdep_assert_held(&kbdev->hwaccess_lock);
/* If the L2 & tiler are not on or pending, then the tiler is not yet
* available, and shaders are definitely not powered.
*/
if (kbdev->pm.backend.l2_state != KBASE_L2_PEND_ON &&
kbdev->pm.backend.l2_state != KBASE_L2_ON &&
kbdev->pm.backend.l2_state != KBASE_L2_ON_HWCNT_ENABLE)
return false;
if (shader_required &&
kbdev->pm.backend.shaders_state != KBASE_SHADERS_PEND_ON_CORESTACK_ON &&
kbdev->pm.backend.shaders_state != KBASE_SHADERS_ON_CORESTACK_ON &&
kbdev->pm.backend.shaders_state != KBASE_SHADERS_ON_CORESTACK_ON_RECHECK)
return false;
return true;
}
#endif /* _KBASE_PM_POLICY_H_ */

View file

@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2018-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Backend-specific Power Manager shader core state definitions.
* The function-like macro KBASEP_SHADER_STATE() must be defined before
* including this header file. This header file can be included multiple
* times in the same compilation unit with different definitions of
* KBASEP_SHADER_STATE().
*
* @OFF_CORESTACK_OFF: The shaders and core stacks are off
* @OFF_CORESTACK_PEND_ON: The shaders are off, core stacks have been
* requested to power on and hwcnt is being
* disabled
* @PEND_ON_CORESTACK_ON: Core stacks are on, shaders have been
* requested to power on. Or after doing
* partial shader on/off, checking whether
* it's the desired state.
* @ON_CORESTACK_ON: The shaders and core stacks are on, and
* hwcnt already enabled.
* @ON_CORESTACK_ON_RECHECK: The shaders and core stacks are on, hwcnt
* disabled, and checks to powering down or
* re-enabling hwcnt.
* @WAIT_OFF_CORESTACK_ON: The shaders have been requested to power
* off, but they remain on for the duration
* of the hysteresis timer
* @WAIT_GPU_IDLE: The shaders partial poweroff needs to
* reach a state where jobs on the GPU are
* finished including jobs currently running
* and in the GPU queue because of
* GPU2017-861
* @WAIT_FINISHED_CORESTACK_ON: The hysteresis timer has expired
* @L2_FLUSHING_CORESTACK_ON: The core stacks are on and the level 2
* cache is being flushed.
* @READY_OFF_CORESTACK_ON: The core stacks are on and the shaders are
* ready to be powered off.
* @PEND_OFF_CORESTACK_ON: The core stacks are on, and the shaders
* have been requested to power off
* @OFF_CORESTACK_PEND_OFF: The shaders are off, and the core stacks
* have been requested to power off
* @OFF_CORESTACK_OFF_TIMER_PEND_OFF: Shaders and corestacks are off, but the
* tick timer cancellation is still pending.
* @RESET_WAIT: The GPU is resetting, shader and core
* stack power states are unknown
*/
KBASEP_SHADER_STATE(OFF_CORESTACK_OFF)
KBASEP_SHADER_STATE(OFF_CORESTACK_PEND_ON)
KBASEP_SHADER_STATE(PEND_ON_CORESTACK_ON)
KBASEP_SHADER_STATE(ON_CORESTACK_ON)
KBASEP_SHADER_STATE(ON_CORESTACK_ON_RECHECK)
KBASEP_SHADER_STATE(WAIT_OFF_CORESTACK_ON)
#if !MALI_USE_CSF
KBASEP_SHADER_STATE(WAIT_GPU_IDLE)
#endif /* !MALI_USE_CSF */
KBASEP_SHADER_STATE(WAIT_FINISHED_CORESTACK_ON)
KBASEP_SHADER_STATE(L2_FLUSHING_CORESTACK_ON)
KBASEP_SHADER_STATE(READY_OFF_CORESTACK_ON)
KBASEP_SHADER_STATE(PEND_OFF_CORESTACK_ON)
KBASEP_SHADER_STATE(OFF_CORESTACK_PEND_OFF)
KBASEP_SHADER_STATE(OFF_CORESTACK_OFF_TIMER_PEND_OFF)
KBASEP_SHADER_STATE(RESET_WAIT)

View file

@ -0,0 +1,418 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2014-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <mali_kbase_hwaccess_time.h>
#if MALI_USE_CSF
#include <linux/gcd.h>
#include <csf/mali_kbase_csf_timeout.h>
#endif
#include <device/mali_kbase_device.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <mali_kbase_config_defaults.h>
#include <linux/version_compat_defs.h>
#include <asm/arch_timer.h>
#include <linux/mali_hw_access.h>
struct kbase_timeout_info {
char *selector_str;
u64 timeout_cycles;
};
#if MALI_USE_CSF
#define GPU_TIMESTAMP_OFFSET_INVALID S64_MAX
static struct kbase_timeout_info timeout_info[KBASE_TIMEOUT_SELECTOR_COUNT] = {
[CSF_FIRMWARE_TIMEOUT] = { "CSF_FIRMWARE_TIMEOUT", MIN(CSF_FIRMWARE_TIMEOUT_CYCLES,
CSF_FIRMWARE_PING_TIMEOUT_CYCLES) },
[CSF_PM_TIMEOUT] = { "CSF_PM_TIMEOUT", CSF_PM_TIMEOUT_CYCLES },
[CSF_GPU_RESET_TIMEOUT] = { "CSF_GPU_RESET_TIMEOUT", CSF_GPU_RESET_TIMEOUT_CYCLES },
[CSF_CSG_TERM_TIMEOUT] = { "CSF_CSG_TERM_TIMEOUT", CSF_CSG_TERM_TIMEOUT_CYCLES },
[CSF_FIRMWARE_BOOT_TIMEOUT] = { "CSF_FIRMWARE_BOOT_TIMEOUT",
CSF_FIRMWARE_BOOT_TIMEOUT_CYCLES },
[CSF_FIRMWARE_WAKE_UP_TIMEOUT] = { "CSF_FIRMWARE_WAKE_UP_TIMEOUT",
CSF_FIRMWARE_WAKE_UP_TIMEOUT_CYCLES },
[CSF_FIRMWARE_SOI_HALT_TIMEOUT] = { "CSF_FIRMWARE_SOI_HALT_TIMEOUT",
CSF_FIRMWARE_SOI_HALT_TIMEOUT_CYCLES },
[CSF_FIRMWARE_PING_TIMEOUT] = { "CSF_FIRMWARE_PING_TIMEOUT",
CSF_FIRMWARE_PING_TIMEOUT_CYCLES },
[CSF_SCHED_PROTM_PROGRESS_TIMEOUT] = { "CSF_SCHED_PROTM_PROGRESS_TIMEOUT",
DEFAULT_PROGRESS_TIMEOUT_CYCLES },
[MMU_AS_INACTIVE_WAIT_TIMEOUT] = { "MMU_AS_INACTIVE_WAIT_TIMEOUT",
MMU_AS_INACTIVE_WAIT_TIMEOUT_CYCLES },
[KCPU_FENCE_SIGNAL_TIMEOUT] = { "KCPU_FENCE_SIGNAL_TIMEOUT",
KCPU_FENCE_SIGNAL_TIMEOUT_CYCLES },
[KBASE_PRFCNT_ACTIVE_TIMEOUT] = { "KBASE_PRFCNT_ACTIVE_TIMEOUT",
KBASE_PRFCNT_ACTIVE_TIMEOUT_CYCLES },
[KBASE_CLEAN_CACHE_TIMEOUT] = { "KBASE_CLEAN_CACHE_TIMEOUT",
KBASE_CLEAN_CACHE_TIMEOUT_CYCLES },
[KBASE_AS_INACTIVE_TIMEOUT] = { "KBASE_AS_INACTIVE_TIMEOUT",
KBASE_AS_INACTIVE_TIMEOUT_CYCLES },
[IPA_INACTIVE_TIMEOUT] = { "IPA_INACTIVE_TIMEOUT", IPA_INACTIVE_TIMEOUT_CYCLES },
[CSF_FIRMWARE_STOP_TIMEOUT] = { "CSF_FIRMWARE_STOP_TIMEOUT",
CSF_FIRMWARE_STOP_TIMEOUT_CYCLES },
};
#else
static struct kbase_timeout_info timeout_info[KBASE_TIMEOUT_SELECTOR_COUNT] = {
[MMU_AS_INACTIVE_WAIT_TIMEOUT] = { "MMU_AS_INACTIVE_WAIT_TIMEOUT",
MMU_AS_INACTIVE_WAIT_TIMEOUT_CYCLES },
[JM_DEFAULT_JS_FREE_TIMEOUT] = { "JM_DEFAULT_JS_FREE_TIMEOUT",
JM_DEFAULT_JS_FREE_TIMEOUT_CYCLES },
[KBASE_PRFCNT_ACTIVE_TIMEOUT] = { "KBASE_PRFCNT_ACTIVE_TIMEOUT",
KBASE_PRFCNT_ACTIVE_TIMEOUT_CYCLES },
[KBASE_CLEAN_CACHE_TIMEOUT] = { "KBASE_CLEAN_CACHE_TIMEOUT",
KBASE_CLEAN_CACHE_TIMEOUT_CYCLES },
[KBASE_AS_INACTIVE_TIMEOUT] = { "KBASE_AS_INACTIVE_TIMEOUT",
KBASE_AS_INACTIVE_TIMEOUT_CYCLES },
};
#endif
#if MALI_USE_CSF
void kbase_backend_invalidate_gpu_timestamp_offset(struct kbase_device *kbdev)
{
kbdev->backend_time.gpu_timestamp_offset = GPU_TIMESTAMP_OFFSET_INVALID;
}
KBASE_EXPORT_TEST_API(kbase_backend_invalidate_gpu_timestamp_offset);
/**
* kbase_backend_compute_gpu_ts_offset() - Compute GPU TS offset.
*
* @kbdev: Kbase device.
*
* This function compute the value of GPU and CPU TS offset:
* - set to zero current TIMESTAMP_OFFSET register
* - read CPU TS and convert it to ticks
* - read GPU TS
* - calculate diff between CPU and GPU ticks
* - cache the diff as the GPU TS offset
*
* To reduce delays, preemption must be disabled during reads of both CPU and GPU TS
* this function require access to GPU register to be enabled
*/
static inline void kbase_backend_compute_gpu_ts_offset(struct kbase_device *kbdev)
{
s64 cpu_ts_ticks = 0;
s64 gpu_ts_ticks = 0;
if (kbdev->backend_time.gpu_timestamp_offset != GPU_TIMESTAMP_OFFSET_INVALID)
return;
kbase_reg_write64(kbdev, GPU_CONTROL_ENUM(TIMESTAMP_OFFSET), 0);
gpu_ts_ticks = kbase_reg_read64_coherent(kbdev, GPU_CONTROL_ENUM(TIMESTAMP));
cpu_ts_ticks = ktime_get_raw_ns();
cpu_ts_ticks = div64_u64(cpu_ts_ticks * kbdev->backend_time.divisor,
kbdev->backend_time.multiplier);
kbdev->backend_time.gpu_timestamp_offset = cpu_ts_ticks - gpu_ts_ticks;
}
void kbase_backend_update_gpu_timestamp_offset(struct kbase_device *kbdev)
{
lockdep_assert_held(&kbdev->pm.lock);
kbase_backend_compute_gpu_ts_offset(kbdev);
dev_dbg(kbdev->dev, "Setting GPU timestamp offset register to %lld (%lld ns)",
kbdev->backend_time.gpu_timestamp_offset,
div64_s64(kbdev->backend_time.gpu_timestamp_offset *
(s64)kbdev->backend_time.multiplier,
(s64)kbdev->backend_time.divisor));
kbase_reg_write64(kbdev, GPU_CONTROL_ENUM(TIMESTAMP_OFFSET),
kbdev->backend_time.gpu_timestamp_offset);
}
#if MALI_UNIT_TEST
u64 kbase_backend_read_gpu_timestamp_offset_reg(struct kbase_device *kbdev)
{
return kbase_reg_read64_coherent(kbdev, GPU_CONTROL_ENUM(TIMESTAMP_OFFSET));
}
KBASE_EXPORT_TEST_API(kbase_backend_read_gpu_timestamp_offset_reg);
#endif
#endif
void kbase_backend_get_gpu_time_norequest(struct kbase_device *kbdev, u64 *cycle_counter,
u64 *system_time, struct timespec64 *ts)
{
if (cycle_counter)
*cycle_counter = kbase_backend_get_cycle_cnt(kbdev);
if (system_time) {
*system_time = kbase_reg_read64_coherent(kbdev, GPU_CONTROL_ENUM(TIMESTAMP));
}
/* Record the CPU's idea of current time */
if (ts != NULL)
#if (KERNEL_VERSION(4, 17, 0) > LINUX_VERSION_CODE)
*ts = ktime_to_timespec64(ktime_get_raw());
#else
ktime_get_raw_ts64(ts);
#endif
}
KBASE_EXPORT_TEST_API(kbase_backend_get_gpu_time_norequest);
#if !MALI_USE_CSF
/**
* timedwait_cycle_count_active() - Timed wait till CYCLE_COUNT_ACTIVE is active
*
* @kbdev: Kbase device
*
* Return: true if CYCLE_COUNT_ACTIVE is active within the timeout.
*/
static bool timedwait_cycle_count_active(struct kbase_device *kbdev)
{
#if IS_ENABLED(CONFIG_MALI_NO_MALI)
return true;
#else
bool success = false;
const unsigned int timeout = 100;
const unsigned long remaining = jiffies + msecs_to_jiffies(timeout);
while (time_is_after_jiffies(remaining)) {
if ((kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(GPU_STATUS)) &
GPU_STATUS_CYCLE_COUNT_ACTIVE)) {
success = true;
break;
}
}
return success;
#endif
}
#endif
void kbase_backend_get_gpu_time(struct kbase_device *kbdev, u64 *cycle_counter, u64 *system_time,
struct timespec64 *ts)
{
#if !MALI_USE_CSF
kbase_pm_request_gpu_cycle_counter(kbdev);
WARN_ONCE(kbdev->pm.backend.l2_state != KBASE_L2_ON, "L2 not powered up");
WARN_ONCE((!timedwait_cycle_count_active(kbdev)), "Timed out on CYCLE_COUNT_ACTIVE");
#endif
kbase_backend_get_gpu_time_norequest(kbdev, cycle_counter, system_time, ts);
#if !MALI_USE_CSF
kbase_pm_release_gpu_cycle_counter(kbdev);
#endif
}
KBASE_EXPORT_TEST_API(kbase_backend_get_gpu_time);
static u64 kbase_device_get_scaling_frequency(struct kbase_device *kbdev)
{
u64 freq_khz = kbdev->lowest_gpu_freq_khz;
if (!freq_khz) {
dev_dbg(kbdev->dev,
"Lowest frequency uninitialized! Using reference frequency for scaling");
return DEFAULT_REF_TIMEOUT_FREQ_KHZ;
}
return freq_khz;
}
void kbase_device_set_timeout_ms(struct kbase_device *kbdev, enum kbase_timeout_selector selector,
unsigned int timeout_ms)
{
char *selector_str;
if (unlikely(selector >= KBASE_TIMEOUT_SELECTOR_COUNT)) {
selector = KBASE_DEFAULT_TIMEOUT;
selector_str = timeout_info[selector].selector_str;
dev_warn(kbdev->dev,
"Unknown timeout selector passed, falling back to default: %s\n",
timeout_info[selector].selector_str);
}
selector_str = timeout_info[selector].selector_str;
#if MALI_USE_CSF
if (IS_ENABLED(CONFIG_MALI_REAL_HW) && !IS_ENABLED(CONFIG_MALI_IS_FPGA) &&
unlikely(timeout_ms >= MAX_TIMEOUT_MS)) {
dev_warn(kbdev->dev, "%s is capped from %dms to %dms\n",
timeout_info[selector].selector_str, timeout_ms, MAX_TIMEOUT_MS);
timeout_ms = MAX_TIMEOUT_MS;
}
#endif
kbdev->backend_time.device_scaled_timeouts[selector] = timeout_ms;
dev_dbg(kbdev->dev, "\t%-35s: %ums\n", selector_str, timeout_ms);
}
void kbase_device_set_timeout(struct kbase_device *kbdev, enum kbase_timeout_selector selector,
u64 timeout_cycles, u32 cycle_multiplier)
{
u64 final_cycles;
u64 timeout;
u64 freq_khz = kbase_device_get_scaling_frequency(kbdev);
if (unlikely(selector >= KBASE_TIMEOUT_SELECTOR_COUNT)) {
selector = KBASE_DEFAULT_TIMEOUT;
dev_warn(kbdev->dev,
"Unknown timeout selector passed, falling back to default: %s\n",
timeout_info[selector].selector_str);
}
/* If the multiplication overflows, we will have unsigned wrap-around, and so might
* end up with a shorter timeout. In those cases, we then want to have the largest
* timeout possible that will not run into these issues. Note that this will not
* wait for U64_MAX/frequency ms, as it will be clamped to a max of UINT_MAX
* milliseconds by subsequent steps.
*/
if (check_mul_overflow(timeout_cycles, (u64)cycle_multiplier, &final_cycles))
final_cycles = U64_MAX;
/* Timeout calculation:
* dividing number of cycles by freq in KHz automatically gives value
* in milliseconds. nr_cycles will have to be multiplied by 1e3 to
* get result in microseconds, and 1e6 to get result in nanoseconds.
*/
timeout = div_u64(final_cycles, freq_khz);
if (unlikely(timeout > UINT_MAX)) {
dev_dbg(kbdev->dev,
"Capping excessive timeout %llums for %s at freq %llukHz to UINT_MAX ms",
timeout, timeout_info[selector].selector_str,
kbase_device_get_scaling_frequency(kbdev));
timeout = UINT_MAX;
}
kbase_device_set_timeout_ms(kbdev, selector, (unsigned int)timeout);
}
/**
* kbase_timeout_scaling_init - Initialize the table of scaled timeout
* values associated with a @kbase_device.
*
* @kbdev: KBase device pointer.
*
* Return: 0 on success, negative error code otherwise.
*/
static int kbase_timeout_scaling_init(struct kbase_device *kbdev)
{
int err;
enum kbase_timeout_selector selector;
/* First, we initialize the minimum and maximum device frequencies, which
* are used to compute the timeouts.
*/
err = kbase_pm_gpu_freq_init(kbdev);
if (unlikely(err < 0)) {
dev_dbg(kbdev->dev, "Could not initialize GPU frequency\n");
return err;
}
dev_dbg(kbdev->dev, "Scaling kbase timeouts:\n");
for (selector = 0; selector < KBASE_TIMEOUT_SELECTOR_COUNT; selector++) {
u32 cycle_multiplier = 1;
u64 nr_cycles = timeout_info[selector].timeout_cycles;
#if MALI_USE_CSF
/* Special case: the scheduler progress timeout can be set manually,
* and does not have a canonical length defined in the headers. Hence,
* we query it once upon startup to get a baseline, and change it upon
* every invocation of the appropriate functions
*/
if (selector == CSF_SCHED_PROTM_PROGRESS_TIMEOUT)
nr_cycles = kbase_csf_timeout_get(kbdev);
#endif
/* Since we are in control of the iteration bounds for the selector,
* we don't have to worry about bounds checking when setting the timeout.
*/
kbase_device_set_timeout(kbdev, selector, nr_cycles, cycle_multiplier);
}
return 0;
}
unsigned int kbase_get_timeout_ms(struct kbase_device *kbdev, enum kbase_timeout_selector selector)
{
if (unlikely(selector >= KBASE_TIMEOUT_SELECTOR_COUNT)) {
dev_warn(kbdev->dev, "Querying wrong selector, falling back to default\n");
selector = KBASE_DEFAULT_TIMEOUT;
}
return kbdev->backend_time.device_scaled_timeouts[selector];
}
KBASE_EXPORT_TEST_API(kbase_get_timeout_ms);
u64 kbase_backend_get_cycle_cnt(struct kbase_device *kbdev)
{
return kbase_reg_read64_coherent(kbdev, GPU_CONTROL_ENUM(CYCLE_COUNT));
}
#if MALI_USE_CSF
u64 __maybe_unused kbase_backend_time_convert_gpu_to_cpu(struct kbase_device *kbdev, u64 gpu_ts)
{
if (WARN_ON(!kbdev))
return 0;
return div64_u64(gpu_ts * kbdev->backend_time.multiplier, kbdev->backend_time.divisor);
}
KBASE_EXPORT_TEST_API(kbase_backend_time_convert_gpu_to_cpu);
#endif
u64 kbase_arch_timer_get_cntfrq(struct kbase_device *kbdev)
{
u64 freq = mali_arch_timer_get_cntfrq();
dev_dbg(kbdev->dev, "System Timer Freq = %lluHz", freq);
return freq;
}
int kbase_backend_time_init(struct kbase_device *kbdev)
{
int err = 0;
#if MALI_USE_CSF
u64 freq;
u64 common_factor;
kbase_pm_register_access_enable(kbdev);
freq = kbase_arch_timer_get_cntfrq(kbdev);
if (!freq) {
dev_warn(kbdev->dev, "arch_timer_get_rate() is zero!");
err = -EINVAL;
goto disable_registers;
}
common_factor = gcd(NSEC_PER_SEC, freq);
kbdev->backend_time.multiplier = div64_u64(NSEC_PER_SEC, common_factor);
kbdev->backend_time.divisor = div64_u64(freq, common_factor);
if (!kbdev->backend_time.divisor) {
dev_warn(kbdev->dev, "CPU to GPU divisor is zero!");
err = -EINVAL;
goto disable_registers;
}
kbase_backend_invalidate_gpu_timestamp_offset(
kbdev); /* force computation of GPU Timestamp offset */
#endif
if (kbase_timeout_scaling_init(kbdev)) {
dev_warn(kbdev->dev, "Could not initialize timeout scaling");
err = -EINVAL;
}
#if MALI_USE_CSF
disable_registers:
kbase_pm_register_access_disable(kbdev);
#endif
return err;
}

View file

@ -0,0 +1,266 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2017-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/* Kernel-side tests may include mali_kbase's headers. Therefore any config
* options which affect the sizes of any structs (e.g. adding extra members)
* must be included in these defaults, so that the structs are consistent in
* both mali_kbase and the test modules. */
bob_defaults {
name: "mali_kbase_shared_config_defaults",
defaults: [
"kernel_defaults",
],
mali_no_mali: {
kbuild_options: [
"CONFIG_MALI_NO_MALI=y",
"CONFIG_MALI_NO_MALI_DEFAULT_GPU={{.gpu}}",
"CONFIG_GPU_HWVER={{.hwver}}",
],
},
gpu_has_csf: {
kbuild_options: ["CONFIG_MALI_CSF_SUPPORT=y"],
},
mali_devfreq: {
kbuild_options: ["CONFIG_MALI_DEVFREQ=y"],
},
mali_bv_r51p0_dvfs: {
kbuild_options: ["CONFIG_MALI_MIDGARD_DVFS=y"],
},
mali_gator_support: {
kbuild_options: ["CONFIG_MALI_GATOR_SUPPORT=y"],
},
mali_bv_r51p0_enable_trace: {
kbuild_options: ["CONFIG_MALI_MIDGARD_ENABLE_TRACE=y"],
},
mali_arbiter_support: {
kbuild_options: ["CONFIG_MALI_ARBITER_SUPPORT=y"],
},
mali_dma_buf_map_on_demand: {
kbuild_options: ["CONFIG_MALI_DMA_BUF_MAP_ON_DEMAND=y"],
},
mali_dma_buf_legacy_compat: {
kbuild_options: ["CONFIG_MALI_DMA_BUF_LEGACY_COMPAT=y"],
},
page_migration_support: {
kbuild_options: ["CONFIG_PAGE_MIGRATION_SUPPORT=y"],
},
large_page_support: {
kbuild_options: ["CONFIG_LARGE_PAGE_SUPPORT=y"],
},
mali_corestack: {
kbuild_options: ["CONFIG_MALI_CORESTACK=y"],
},
mali_real_hw: {
kbuild_options: ["CONFIG_MALI_REAL_HW=y"],
},
mali_debug: {
kbuild_options: [
"CONFIG_MALI_DEBUG=y",
"MALI_KERNEL_TEST_API={{.debug}}",
],
},
mali_fence_debug: {
kbuild_options: ["CONFIG_MALI_FENCE_DEBUG=y"],
},
mali_system_trace: {
kbuild_options: ["CONFIG_MALI_SYSTEM_TRACE=y"],
},
cinstr_vector_dump: {
kbuild_options: ["CONFIG_MALI_VECTOR_DUMP=y"],
},
cinstr_gwt: {
kbuild_options: ["CONFIG_MALI_CINSTR_GWT=y"],
},
cinstr_primary_hwc: {
kbuild_options: ["CONFIG_MALI_PRFCNT_SET_PRIMARY=y"],
},
cinstr_secondary_hwc: {
kbuild_options: ["CONFIG_MALI_PRFCNT_SET_SECONDARY=y"],
},
cinstr_tertiary_hwc: {
kbuild_options: ["CONFIG_MALI_PRFCNT_SET_TERTIARY=y"],
},
cinstr_hwc_set_select_via_debug_fs: {
kbuild_options: ["CONFIG_MALI_PRFCNT_SET_SELECT_VIA_DEBUG_FS=y"],
},
mali_job_dump: {
kbuild_options: ["CONFIG_MALI_JOB_DUMP"],
},
mali_pwrsoft_765: {
kbuild_options: ["CONFIG_MALI_PWRSOFT_765=y"],
},
mali_hw_errata_1485982_not_affected: {
kbuild_options: ["CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED=y"],
},
mali_hw_errata_1485982_use_clock_alternative: {
kbuild_options: ["CONFIG_MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE=y"],
},
mali_is_fpga: {
kbuild_options: ["CONFIG_MALI_IS_FPGA=y"],
},
mali_coresight: {
kbuild_options: ["CONFIG_MALI_CORESIGHT=y"],
},
mali_fw_trace_mode_manual: {
kbuild_options: ["CONFIG_MALI_FW_TRACE_MODE_MANUAL=y"],
},
mali_fw_trace_mode_auto_print: {
kbuild_options: ["CONFIG_MALI_FW_TRACE_MODE_AUTO_PRINT=y"],
},
mali_fw_trace_mode_auto_discard: {
kbuild_options: ["CONFIG_MALI_FW_TRACE_MODE_AUTO_DISCARD=y"],
},
kbuild_options: [
"CONFIG_MALI_PLATFORM_NAME={{.mali_platform_name}}",
"MALI_CUSTOMER_RELEASE={{.release}}",
"MALI_UNIT_TEST={{.unit_test_code}}",
"MALI_USE_CSF={{.gpu_has_csf}}",
"MALI_JIT_PRESSURE_LIMIT_BASE={{.jit_pressure_limit_base}}",
// Start of CS experimental features definitions.
// If there is nothing below, definition should be added as follows:
// "MALI_EXPERIMENTAL_FEATURE={{.experimental_feature}}"
// experimental_feature above comes from Mconfig in
// <ddk_root>/product/base/
// However, in Mconfig, experimental_feature should be looked up (for
// similar explanation to this one) as ALLCAPS, i.e.
// EXPERIMENTAL_FEATURE.
//
// IMPORTANT: MALI_CS_EXPERIMENTAL should NEVER be defined below as it
// is an umbrella feature that would be open for inappropriate use
// (catch-all for experimental CS code without separating it into
// different features).
"MALI_BASE_CSF_PERFORMANCE_TESTS={{.base_csf_performance_tests}}",
],
}
bob_kernel_module {
name: "mali_kbase",
defaults: [
"mali_kbase_shared_config_defaults",
],
srcs: [
"*.c",
"*.h",
"Kbuild",
"arbiter/*.c",
"arbiter/*.h",
"arbiter/Kbuild",
"backend/gpu/*.c",
"backend/gpu/*.h",
"backend/gpu/Kbuild",
"context/*.c",
"context/*.h",
"context/Kbuild",
"hwcnt/*.c",
"hwcnt/*.h",
"hwcnt/backend/*.h",
"hwcnt/Kbuild",
"ipa/*.c",
"ipa/*.h",
"ipa/Kbuild",
"platform/*.h",
"platform/*/*.c",
"platform/*/*.h",
"platform/*/Kbuild",
"platform/*/*/*.c",
"platform/*/*/*.h",
"platform/*/*/Kbuild",
"platform/*/*/*.c",
"platform/*/*/*.h",
"platform/*/*/Kbuild",
"platform/*/*/*/*.c",
"platform/*/*/*/*.h",
"platform/*/*/*/Kbuild",
"thirdparty/*.c",
"thirdparty/*.h",
"thirdparty/Kbuild",
"debug/*.c",
"debug/*.h",
"debug/Kbuild",
"device/*.c",
"device/*.h",
"device/Kbuild",
"gpu/*.c",
"gpu/*.h",
"gpu/Kbuild",
"hw_access/*.c",
"hw_access/*.h",
"hw_access/*/*.c",
"hw_access/*/*.h",
"hw_access/Kbuild",
"tl/*.c",
"tl/*.h",
"tl/Kbuild",
"mmu/*.c",
"mmu/*.h",
"mmu/Kbuild",
],
gpu_has_job_manager: {
srcs: [
"context/backend/*_jm.c",
"debug/backend/*_jm.c",
"debug/backend/*_jm.h",
"device/backend/*_jm.c",
"gpu/backend/*_jm.c",
"gpu/backend/*_jm.h",
"hwcnt/backend/*_jm.c",
"hwcnt/backend/*_jm.h",
"hwcnt/backend/*_jm_*.c",
"hwcnt/backend/*_jm_*.h",
"jm/*.h",
"tl/backend/*_jm.c",
"mmu/backend/*_jm.c",
"mmu/backend/*_jm.h",
"ipa/backend/*_jm.c",
"ipa/backend/*_jm.h",
],
},
gpu_has_csf: {
srcs: [
"context/backend/*_csf.c",
"csf/*.c",
"csf/*.h",
"csf/Kbuild",
"csf/ipa_control/*.c",
"csf/ipa_control/*.h",
"csf/ipa_control/Kbuild",
"debug/backend/*_csf.c",
"debug/backend/*_csf.h",
"device/backend/*_csf.c",
"gpu/backend/*_csf.c",
"gpu/backend/*_csf.h",
"hwcnt/backend/*_csf.c",
"hwcnt/backend/*_csf.h",
"hwcnt/backend/*_csf_*.c",
"hwcnt/backend/*_csf_*.h",
"tl/backend/*_csf.c",
"mmu/backend/*_csf.c",
"mmu/backend/*_csf.h",
"ipa/backend/*_csf.c",
"ipa/backend/*_csf.h",
],
},
kbuild_options: [
"CONFIG_MALI_MIDGARD=m",
"CONFIG_MALI_KUTF=n",
],
}

View file

@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2012-2013, 2016-2017, 2020-2021 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
mali_kbase-y += context/mali_kbase_context.o
ifeq ($(CONFIG_MALI_CSF_SUPPORT),y)
mali_kbase-y += context/backend/mali_kbase_context_csf.o
else
mali_kbase-y += context/backend/mali_kbase_context_jm.o
endif

View file

@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel context APIs for CSF GPUs
*/
#include <context/mali_kbase_context_internal.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <mali_kbase.h>
#include <mali_kbase_mem_linux.h>
#include <mali_kbase_mem_pool_group.h>
#include <mmu/mali_kbase_mmu.h>
#include <tl/mali_kbase_timeline.h>
#include <mali_kbase_ctx_sched.h>
#if IS_ENABLED(CONFIG_DEBUG_FS)
#include <csf/mali_kbase_csf_csg_debugfs.h>
#include <csf/mali_kbase_csf_kcpu_debugfs.h>
#include <csf/mali_kbase_csf_sync_debugfs.h>
#include <csf/mali_kbase_csf_tiler_heap_debugfs.h>
#include <csf/mali_kbase_csf_cpu_queue_debugfs.h>
#include <mali_kbase_debug_mem_view.h>
#include <mali_kbase_debug_mem_zones.h>
#include <mali_kbase_debug_mem_allocs.h>
#include <mali_kbase_mem_pool_debugfs.h>
void kbase_context_debugfs_init(struct kbase_context *const kctx)
{
kbase_debug_mem_view_init(kctx);
kbase_debug_mem_zones_init(kctx);
kbase_debug_mem_allocs_init(kctx);
kbase_mem_pool_debugfs_init(kctx->kctx_dentry, kctx);
kbase_jit_debugfs_init(kctx);
kbase_csf_queue_group_debugfs_init(kctx);
kbase_csf_kcpu_debugfs_init(kctx);
kbase_csf_sync_debugfs_init(kctx);
kbase_csf_tiler_heap_debugfs_init(kctx);
kbase_csf_tiler_heap_total_debugfs_init(kctx);
kbase_csf_cpu_queue_debugfs_init(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_init);
void kbase_context_debugfs_term(struct kbase_context *const kctx)
{
debugfs_remove_recursive(kctx->kctx_dentry);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_term);
#else
void kbase_context_debugfs_init(struct kbase_context *const kctx)
{
CSTD_UNUSED(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_init);
void kbase_context_debugfs_term(struct kbase_context *const kctx)
{
CSTD_UNUSED(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_term);
#endif /* CONFIG_DEBUG_FS */
static void kbase_context_free(struct kbase_context *kctx)
{
kbase_timeline_post_kbase_context_destroy(kctx);
vfree(kctx);
}
static const struct kbase_context_init context_init[] = {
{ NULL, kbase_context_free, NULL },
{ kbase_context_common_init, kbase_context_common_term,
"Common context initialization failed" },
{ kbase_context_mem_pool_group_init, kbase_context_mem_pool_group_term,
"Memory pool group initialization failed" },
{ kbase_mem_evictable_init, kbase_mem_evictable_deinit,
"Memory evictable initialization failed" },
{ kbase_ctx_sched_init_ctx, NULL, NULL },
{ kbase_context_mmu_init, kbase_context_mmu_term, "MMU initialization failed" },
{ kbase_context_mem_alloc_page, kbase_context_mem_pool_free, "Memory alloc page failed" },
{ kbase_region_tracker_init, kbase_region_tracker_term,
"Region tracker initialization failed" },
{ kbase_sticky_resource_init, kbase_context_sticky_resource_term,
"Sticky resource initialization failed" },
{ kbase_jit_init, kbase_jit_term, "JIT initialization failed" },
{ kbase_csf_ctx_init, kbase_csf_ctx_term, "CSF context initialization failed" },
{ kbase_context_add_to_dev_list, kbase_context_remove_from_dev_list,
"Adding kctx to device failed" },
};
static void kbase_context_term_partial(struct kbase_context *kctx, unsigned int i)
{
while (i-- > 0) {
if (context_init[i].term)
context_init[i].term(kctx);
}
}
struct kbase_context *kbase_create_context(struct kbase_device *kbdev, bool is_compat,
base_context_create_flags const flags,
unsigned long const api_version, struct file *const filp)
{
struct kbase_context *kctx;
unsigned int i = 0;
if (WARN_ON(!kbdev))
return NULL;
/* Validate flags */
if (WARN_ON(flags != (flags & BASEP_CONTEXT_CREATE_KERNEL_FLAGS)))
return NULL;
/* zero-inited as lot of code assume it's zero'ed out on create */
kctx = vzalloc(sizeof(*kctx));
if (WARN_ON(!kctx))
return NULL;
kctx->kbdev = kbdev;
kctx->api_version = api_version;
kctx->filp = filp;
kctx->create_flags = flags;
memcpy(kctx->comm, current->comm, sizeof(current->comm));
if (is_compat)
kbase_ctx_flag_set(kctx, KCTX_COMPAT);
#if defined(CONFIG_64BIT)
else
kbase_ctx_flag_set(kctx, KCTX_FORCE_SAME_VA);
#endif /* defined(CONFIG_64BIT) */
for (i = 0; i < ARRAY_SIZE(context_init); i++) {
int err = 0;
if (context_init[i].init)
err = context_init[i].init(kctx);
if (err) {
dev_err(kbdev->dev, "%s error = %d\n", context_init[i].err_mes, err);
/* kctx should be freed by kbase_context_free().
* Otherwise it will result in memory leak.
*/
WARN_ON(i == 0);
kbase_context_term_partial(kctx, i);
return NULL;
}
}
return kctx;
}
KBASE_EXPORT_SYMBOL(kbase_create_context);
void kbase_destroy_context(struct kbase_context *kctx)
{
struct kbase_device *kbdev;
if (WARN_ON(!kctx))
return;
kbdev = kctx->kbdev;
if (WARN_ON(!kbdev))
return;
/* Context termination could happen whilst the system suspend of
* the GPU device is ongoing or has completed. It has been seen on
* Customer side that a hang could occur if context termination is
* not blocked until the resume of GPU device.
*/
if (kbase_has_arbiter(kbdev))
atomic_inc(&kbdev->pm.gpu_users_waiting);
while (kbase_pm_context_active_handle_suspend(kbdev,
KBASE_PM_SUSPEND_HANDLER_DONT_INCREASE)) {
dev_dbg(kbdev->dev, "Suspend in progress when destroying context");
wait_event(kbdev->pm.resume_wait, !kbase_pm_is_suspending(kbdev));
}
if (kbase_has_arbiter(kbdev))
atomic_dec(&kbdev->pm.gpu_users_waiting);
/* Have synchronized against the System suspend and incremented the
* pm.active_count. So any subsequent invocation of System suspend
* callback would get blocked.
* If System suspend callback was already in progress then the above loop
* would have waited till the System resume callback has begun.
* So wait for the System resume callback to also complete as we want to
* avoid context termination during System resume also.
*/
wait_event(kbdev->pm.resume_wait, !kbase_pm_is_resuming(kbdev));
kbase_mem_pool_group_mark_dying(&kctx->mem_pools);
kbase_context_term_partial(kctx, ARRAY_SIZE(context_init));
kbase_pm_context_idle(kbdev);
}
KBASE_EXPORT_SYMBOL(kbase_destroy_context);

View file

@ -0,0 +1,266 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel context APIs for Job Manager GPUs
*/
#include <context/mali_kbase_context_internal.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <mali_kbase.h>
#include <mali_kbase_ctx_sched.h>
#include <mali_kbase_kinstr_jm.h>
#include <mali_kbase_mem_linux.h>
#include <mali_kbase_mem_pool_group.h>
#include <mmu/mali_kbase_mmu.h>
#include <tl/mali_kbase_timeline.h>
#if IS_ENABLED(CONFIG_DEBUG_FS)
#include <mali_kbase_debug_mem_view.h>
#include <mali_kbase_debug_mem_zones.h>
#include <mali_kbase_debug_mem_allocs.h>
#include <mali_kbase_mem_pool_debugfs.h>
void kbase_context_debugfs_init(struct kbase_context *const kctx)
{
kbase_debug_mem_view_init(kctx);
kbase_debug_mem_zones_init(kctx);
kbase_debug_mem_allocs_init(kctx);
kbase_mem_pool_debugfs_init(kctx->kctx_dentry, kctx);
kbase_jit_debugfs_init(kctx);
kbasep_jd_debugfs_ctx_init(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_init);
void kbase_context_debugfs_term(struct kbase_context *const kctx)
{
debugfs_remove_recursive(kctx->kctx_dentry);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_term);
#else
void kbase_context_debugfs_init(struct kbase_context *const kctx)
{
CSTD_UNUSED(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_init);
void kbase_context_debugfs_term(struct kbase_context *const kctx)
{
CSTD_UNUSED(kctx);
}
KBASE_EXPORT_SYMBOL(kbase_context_debugfs_term);
#endif /* CONFIG_DEBUG_FS */
static int kbase_context_kbase_kinstr_jm_init(struct kbase_context *kctx)
{
return kbase_kinstr_jm_init(&kctx->kinstr_jm);
}
static void kbase_context_kbase_kinstr_jm_term(struct kbase_context *kctx)
{
kbase_kinstr_jm_term(kctx->kinstr_jm);
}
static int kbase_context_kbase_timer_setup(struct kbase_context *kctx)
{
kbase_timer_setup(&kctx->soft_job_timeout, kbasep_soft_job_timeout_worker);
return 0;
}
static int kbase_context_submit_check(struct kbase_context *kctx)
{
struct kbasep_js_kctx_info *js_kctx_info = &kctx->jctx.sched_info;
unsigned long irq_flags = 0;
base_context_create_flags const flags = kctx->create_flags;
mutex_lock(&js_kctx_info->ctx.jsctx_mutex);
spin_lock_irqsave(&kctx->kbdev->hwaccess_lock, irq_flags);
/* Translate the flags */
if ((flags & BASE_CONTEXT_SYSTEM_MONITOR_SUBMIT_DISABLED) == 0)
kbase_ctx_flag_clear(kctx, KCTX_SUBMIT_DISABLED);
spin_unlock_irqrestore(&kctx->kbdev->hwaccess_lock, irq_flags);
mutex_unlock(&js_kctx_info->ctx.jsctx_mutex);
return 0;
}
static void kbase_context_flush_jobs(struct kbase_context *kctx)
{
kbase_jd_zap_context(kctx);
flush_workqueue(kctx->jctx.job_done_wq);
}
/**
* kbase_context_free - Free kcontext at its destruction
*
* @kctx: kcontext to be freed
*/
static void kbase_context_free(struct kbase_context *kctx)
{
kbase_timeline_post_kbase_context_destroy(kctx);
vfree(kctx);
}
static const struct kbase_context_init context_init[] = {
{ NULL, kbase_context_free, NULL },
{ kbase_context_common_init, kbase_context_common_term,
"Common context initialization failed" },
{ kbase_context_mem_pool_group_init, kbase_context_mem_pool_group_term,
"Memory pool group initialization failed" },
{ kbase_mem_evictable_init, kbase_mem_evictable_deinit,
"Memory evictable initialization failed" },
{ kbase_ctx_sched_init_ctx, NULL, NULL },
{ kbase_context_mmu_init, kbase_context_mmu_term, "MMU initialization failed" },
{ kbase_context_mem_alloc_page, kbase_context_mem_pool_free, "Memory alloc page failed" },
{ kbase_region_tracker_init, kbase_region_tracker_term,
"Region tracker initialization failed" },
{ kbase_sticky_resource_init, kbase_context_sticky_resource_term,
"Sticky resource initialization failed" },
{ kbase_jit_init, kbase_jit_term, "JIT initialization failed" },
{ kbase_context_kbase_kinstr_jm_init, kbase_context_kbase_kinstr_jm_term,
"JM instrumentation initialization failed" },
{ kbase_context_kbase_timer_setup, NULL, "Timers initialization failed" },
{ kbase_event_init, kbase_event_cleanup, "Event initialization failed" },
{ kbasep_js_kctx_init, kbasep_js_kctx_term, "JS kctx initialization failed" },
{ kbase_jd_init, kbase_jd_exit, "JD initialization failed" },
{ kbase_context_submit_check, NULL, "Enabling job submission failed" },
#if IS_ENABLED(CONFIG_DEBUG_FS)
{ kbase_debug_job_fault_context_init, kbase_debug_job_fault_context_term,
"Job fault context initialization failed" },
#endif
{ kbasep_platform_context_init, kbasep_platform_context_term,
"Platform callback for kctx initialization failed" },
{ NULL, kbase_context_flush_jobs, NULL },
{ kbase_context_add_to_dev_list, kbase_context_remove_from_dev_list,
"Adding kctx to device failed" },
};
static void kbase_context_term_partial(struct kbase_context *kctx, unsigned int i)
{
while (i-- > 0) {
if (context_init[i].term)
context_init[i].term(kctx);
}
}
struct kbase_context *kbase_create_context(struct kbase_device *kbdev, bool is_compat,
base_context_create_flags const flags,
unsigned long const api_version, struct file *const filp)
{
struct kbase_context *kctx;
unsigned int i = 0;
if (WARN_ON(!kbdev))
return NULL;
/* Validate flags */
if (WARN_ON(flags != (flags & BASEP_CONTEXT_CREATE_KERNEL_FLAGS)))
return NULL;
/* zero-inited as lot of code assume it's zero'ed out on create */
kctx = vzalloc(sizeof(*kctx));
if (WARN_ON(!kctx))
return NULL;
kctx->kbdev = kbdev;
kctx->api_version = api_version;
kctx->filp = filp;
kctx->create_flags = flags;
if (is_compat)
kbase_ctx_flag_set(kctx, KCTX_COMPAT);
#if defined(CONFIG_64BIT)
else
kbase_ctx_flag_set(kctx, KCTX_FORCE_SAME_VA);
#endif /* defined(CONFIG_64BIT) */
for (i = 0; i < ARRAY_SIZE(context_init); i++) {
int err = 0;
if (context_init[i].init)
err = context_init[i].init(kctx);
if (err) {
dev_err(kbdev->dev, "%s error = %d\n", context_init[i].err_mes, err);
/* kctx should be freed by kbase_context_free().
* Otherwise it will result in memory leak.
*/
WARN_ON(i == 0);
kbase_context_term_partial(kctx, i);
return NULL;
}
}
return kctx;
}
KBASE_EXPORT_SYMBOL(kbase_create_context);
void kbase_destroy_context(struct kbase_context *kctx)
{
struct kbase_device *kbdev;
if (WARN_ON(!kctx))
return;
kbdev = kctx->kbdev;
if (WARN_ON(!kbdev))
return;
/* Context termination could happen whilst the system suspend of
* the GPU device is ongoing or has completed. It has been seen on
* Customer side that a hang could occur if context termination is
* not blocked until the resume of GPU device.
*/
if (kbase_has_arbiter(kbdev))
atomic_inc(&kbdev->pm.gpu_users_waiting);
while (kbase_pm_context_active_handle_suspend(kbdev,
KBASE_PM_SUSPEND_HANDLER_DONT_INCREASE)) {
dev_dbg(kbdev->dev, "Suspend in progress when destroying context");
wait_event(kbdev->pm.resume_wait, !kbase_pm_is_suspending(kbdev));
}
/* Have synchronized against the System suspend and incremented the
* pm.active_count. So any subsequent invocation of System suspend
* callback would get blocked.
* If System suspend callback was already in progress then the above loop
* would have waited till the System resume callback has begun.
* So wait for the System resume callback to also complete as we want to
* avoid context termination during System resume also.
*/
wait_event(kbdev->pm.resume_wait, !kbase_pm_is_resuming(kbdev));
if (kbase_has_arbiter(kbdev))
atomic_dec(&kbdev->pm.gpu_users_waiting);
kbase_mem_pool_group_mark_dying(&kctx->mem_pools);
kbase_context_term_partial(kctx, ARRAY_SIZE(context_init));
kbase_pm_context_idle(kbdev);
}
KBASE_EXPORT_SYMBOL(kbase_destroy_context);

View file

@ -0,0 +1,372 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
/*
* Base kernel context APIs
*/
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/task.h>
#endif
#if KERNEL_VERSION(4, 19, 0) <= LINUX_VERSION_CODE
#include <linux/sched/signal.h>
#else
#include <linux/sched.h>
#endif
#include <mali_kbase.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <mali_kbase_mem_linux.h>
#include <mali_kbase_ctx_sched.h>
#include <mali_kbase_mem_pool_group.h>
#include <tl/mali_kbase_timeline.h>
#include <mmu/mali_kbase_mmu.h>
#include <context/mali_kbase_context_internal.h>
/**
* find_process_node - Used to traverse the process rb_tree to find if
* process exists already in process rb_tree.
*
* @node: Pointer to root node to start search.
* @tgid: Thread group PID to search for.
*
* Return: Pointer to kbase_process if exists otherwise NULL.
*/
static struct kbase_process *find_process_node(struct rb_node *node, pid_t tgid)
{
struct kbase_process *kprcs = NULL;
/* Check if the kctx creation request is from a existing process.*/
while (node) {
struct kbase_process *prcs_node = rb_entry(node, struct kbase_process, kprcs_node);
if (prcs_node->tgid == tgid) {
kprcs = prcs_node;
break;
}
if (tgid < prcs_node->tgid)
node = node->rb_left;
else
node = node->rb_right;
}
return kprcs;
}
/**
* kbase_insert_kctx_to_process - Initialise kbase process context.
*
* @kctx: Pointer to kbase context.
*
* Here we initialise per process rb_tree managed by kbase_device.
* We maintain a rb_tree of each unique process that gets created.
* and Each process maintains a list of kbase context.
* This setup is currently used by kernel trace functionality
* to trace and visualise gpu memory consumption.
*
* Return: 0 on success and error number on failure.
*/
static int kbase_insert_kctx_to_process(struct kbase_context *kctx)
{
struct rb_root *const prcs_root = &kctx->kbdev->process_root;
const pid_t tgid = kctx->tgid;
struct kbase_process *kprcs = NULL;
lockdep_assert_held(&kctx->kbdev->kctx_list_lock);
kprcs = find_process_node(prcs_root->rb_node, tgid);
/* if the kctx is from new process then create a new kbase_process
* and add it to the &kbase_device->rb_tree
*/
if (!kprcs) {
struct rb_node **new = &prcs_root->rb_node, *parent = NULL;
kprcs = kzalloc(sizeof(*kprcs), GFP_KERNEL);
if (kprcs == NULL)
return -ENOMEM;
kprcs->tgid = tgid;
INIT_LIST_HEAD(&kprcs->kctx_list);
kprcs->dma_buf_root = RB_ROOT;
kprcs->total_gpu_pages = 0;
while (*new) {
struct kbase_process *prcs_node;
parent = *new;
prcs_node = rb_entry(parent, struct kbase_process, kprcs_node);
if (tgid < prcs_node->tgid)
new = &(*new)->rb_left;
else
new = &(*new)->rb_right;
}
rb_link_node(&kprcs->kprcs_node, parent, new);
rb_insert_color(&kprcs->kprcs_node, prcs_root);
}
kctx->kprcs = kprcs;
list_add(&kctx->kprcs_link, &kprcs->kctx_list);
return 0;
}
int kbase_context_common_init(struct kbase_context *kctx)
{
const unsigned long cookies_mask = KBASE_COOKIE_MASK;
int err = 0;
/* creating a context is considered a disjoint event */
kbase_disjoint_event(kctx->kbdev);
kctx->tgid = current->tgid;
kctx->pid = current->pid;
/* Check if this is a Userspace created context */
if (likely(kctx->filp)) {
struct pid *pid_struct;
rcu_read_lock();
pid_struct = get_pid(task_tgid(current));
if (likely(pid_struct)) {
struct task_struct *task = pid_task(pid_struct, PIDTYPE_PID);
if (likely(task)) {
/* Take a reference on the task to avoid slow lookup
* later on from the page allocation loop.
*/
get_task_struct(task);
kctx->task = task;
} else {
dev_err(kctx->kbdev->dev, "Failed to get task pointer for %s/%d",
current->comm, current->pid);
err = -ESRCH;
}
put_pid(pid_struct);
} else {
dev_err(kctx->kbdev->dev, "Failed to get pid pointer for %s/%d",
current->comm, current->pid);
err = -ESRCH;
}
rcu_read_unlock();
if (unlikely(err))
return err;
kbase_mem_mmgrab();
kctx->process_mm = current->mm;
}
mutex_init(&kctx->reg_lock);
spin_lock_init(&kctx->mem_partials_lock);
INIT_LIST_HEAD(&kctx->mem_partials);
spin_lock_init(&kctx->waiting_soft_jobs_lock);
INIT_LIST_HEAD(&kctx->waiting_soft_jobs);
init_waitqueue_head(&kctx->event_queue);
kbase_gpu_vm_lock(kctx);
bitmap_copy(kctx->cookies, &cookies_mask, BITS_PER_LONG);
kbase_gpu_vm_unlock(kctx);
kctx->id = (u32)atomic_add_return(1, &(kctx->kbdev->ctx_num)) - 1;
mutex_lock(&kctx->kbdev->kctx_list_lock);
err = kbase_insert_kctx_to_process(kctx);
mutex_unlock(&kctx->kbdev->kctx_list_lock);
if (err) {
dev_err(kctx->kbdev->dev, "(err:%d) failed to insert kctx to kbase_process", err);
if (likely(kctx->filp)) {
mmdrop(kctx->process_mm);
put_task_struct(kctx->task);
}
}
return err;
}
int kbase_context_add_to_dev_list(struct kbase_context *kctx)
{
if (WARN_ON(!kctx))
return -EINVAL;
if (WARN_ON(!kctx->kbdev))
return -EINVAL;
mutex_lock(&kctx->kbdev->kctx_list_lock);
list_add(&kctx->kctx_list_link, &kctx->kbdev->kctx_list);
mutex_unlock(&kctx->kbdev->kctx_list_lock);
kbase_timeline_post_kbase_context_create(kctx);
return 0;
}
void kbase_context_remove_from_dev_list(struct kbase_context *kctx)
{
if (WARN_ON(!kctx))
return;
if (WARN_ON(!kctx->kbdev))
return;
kbase_timeline_pre_kbase_context_destroy(kctx);
mutex_lock(&kctx->kbdev->kctx_list_lock);
list_del_init(&kctx->kctx_list_link);
mutex_unlock(&kctx->kbdev->kctx_list_lock);
}
/**
* kbase_remove_kctx_from_process - remove a terminating context from
* the process list.
*
* @kctx: Pointer to kbase context.
*
* Remove the tracking of context from the list of contexts maintained under
* kbase process and if the list if empty then there no outstanding contexts
* we can remove the process node as well.
*/
static void kbase_remove_kctx_from_process(struct kbase_context *kctx)
{
struct kbase_process *kprcs = kctx->kprcs;
lockdep_assert_held(&kctx->kbdev->kctx_list_lock);
list_del(&kctx->kprcs_link);
/* if there are no outstanding contexts in current process node,
* we can remove it from the process rb_tree.
*/
if (list_empty(&kprcs->kctx_list)) {
rb_erase(&kprcs->kprcs_node, &kctx->kbdev->process_root);
/* Add checks, so that the terminating process Should not
* hold any gpu_memory.
*/
spin_lock(&kctx->kbdev->gpu_mem_usage_lock);
WARN_ON(kprcs->total_gpu_pages);
spin_unlock(&kctx->kbdev->gpu_mem_usage_lock);
WARN_ON(!RB_EMPTY_ROOT(&kprcs->dma_buf_root));
kfree(kprcs);
}
}
void kbase_context_common_term(struct kbase_context *kctx)
{
int pages;
pages = atomic_read(&kctx->used_pages);
if (pages != 0)
dev_warn(kctx->kbdev->dev, "%s: %d pages in use!\n", __func__, pages);
WARN_ON(atomic_read(&kctx->nonmapped_pages) != 0);
mutex_lock(&kctx->kbdev->kctx_list_lock);
kbase_remove_kctx_from_process(kctx);
mutex_unlock(&kctx->kbdev->kctx_list_lock);
if (likely(kctx->filp)) {
mmdrop(kctx->process_mm);
put_task_struct(kctx->task);
}
KBASE_KTRACE_ADD(kctx->kbdev, CORE_CTX_DESTROY, kctx, 0u);
}
int kbase_context_mem_pool_group_init(struct kbase_context *kctx)
{
return kbase_mem_pool_group_init(&kctx->mem_pools, kctx->kbdev,
&kctx->kbdev->mem_pool_defaults, &kctx->kbdev->mem_pools);
}
void kbase_context_mem_pool_group_term(struct kbase_context *kctx)
{
kbase_mem_pool_group_term(&kctx->mem_pools);
}
int kbase_context_mmu_init(struct kbase_context *kctx)
{
return kbase_mmu_init(kctx->kbdev, &kctx->mmu, kctx,
kbase_context_mmu_group_id_get(kctx->create_flags));
}
void kbase_context_mmu_term(struct kbase_context *kctx)
{
kbase_mmu_term(kctx->kbdev, &kctx->mmu);
}
int kbase_context_mem_alloc_page(struct kbase_context *kctx)
{
struct page *p;
p = kbase_mem_alloc_page(&kctx->mem_pools.small[KBASE_MEM_GROUP_SINK], false);
if (!p)
return -ENOMEM;
kctx->aliasing_sink_page = as_tagged(page_to_phys(p));
return 0;
}
void kbase_context_mem_pool_free(struct kbase_context *kctx)
{
/* drop the aliasing sink page now that it can't be mapped anymore */
kbase_mem_pool_free(&kctx->mem_pools.small[KBASE_MEM_GROUP_SINK],
as_page(kctx->aliasing_sink_page), false);
}
void kbase_context_sticky_resource_term(struct kbase_context *kctx)
{
unsigned long pending_regions_to_clean;
kbase_gpu_vm_lock(kctx);
kbase_sticky_resource_term(kctx);
/* free pending region setups */
pending_regions_to_clean = KBASE_COOKIE_MASK;
bitmap_andnot(&pending_regions_to_clean, &pending_regions_to_clean, kctx->cookies,
BITS_PER_LONG);
while (pending_regions_to_clean) {
unsigned int cookie = find_first_bit(&pending_regions_to_clean, BITS_PER_LONG);
if (!WARN_ON(!kctx->pending_regions[cookie])) {
dev_dbg(kctx->kbdev->dev, "Freeing pending unmapped region\n");
kbase_mem_phy_alloc_put(kctx->pending_regions[cookie]->cpu_alloc);
kbase_mem_phy_alloc_put(kctx->pending_regions[cookie]->gpu_alloc);
kfree(kctx->pending_regions[cookie]);
kctx->pending_regions[cookie] = NULL;
}
bitmap_clear(&pending_regions_to_clean, cookie, 1);
}
kbase_gpu_vm_unlock(kctx);
}
bool kbase_ctx_compat_mode(struct kbase_context *kctx)
{
return !IS_ENABLED(CONFIG_64BIT) ||
(IS_ENABLED(CONFIG_64BIT) && kbase_ctx_flag(kctx, KCTX_COMPAT));
}
KBASE_EXPORT_TEST_API(kbase_ctx_compat_mode);

View file

@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2011-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CONTEXT_H_
#define _KBASE_CONTEXT_H_
#include <linux/atomic.h>
/**
* kbase_context_debugfs_init - Initialize the kctx platform
* specific debugfs
*
* @kctx: kbase context
*
* This initializes some debugfs interfaces specific to the platform the source
* is compiled for.
*/
void kbase_context_debugfs_init(struct kbase_context *const kctx);
/**
* kbase_context_debugfs_term - Terminate the kctx platform
* specific debugfs
*
* @kctx: kbase context
*
* This terminates some debugfs interfaces specific to the platform the source
* is compiled for.
*/
void kbase_context_debugfs_term(struct kbase_context *const kctx);
/**
* kbase_create_context() - Create a kernel base context.
*
* @kbdev: Object representing an instance of GPU platform device,
* allocated from the probe method of the Mali driver.
* @is_compat: Force creation of a 32-bit context
* @flags: Flags to set, which shall be any combination of
* BASEP_CONTEXT_CREATE_KERNEL_FLAGS.
* @api_version: Application program interface version, as encoded in
* a single integer by the KBASE_API_VERSION macro.
* @filp: Pointer to the struct file corresponding to device file
* /dev/malixx instance, passed to the file's open method.
* Shall be passed as NULL for internally created contexts.
*
* Up to one context can be created for each client that opens the device file
* /dev/malixx. Context creation is deferred until a special ioctl() system call
* is made on the device file. Each context has its own GPU address space.
*
* Return: new kbase context or NULL on failure
*/
struct kbase_context *kbase_create_context(struct kbase_device *kbdev, bool is_compat,
base_context_create_flags const flags,
unsigned long api_version, struct file *filp);
/**
* kbase_destroy_context - Destroy a kernel base context.
* @kctx: Context to destroy
*
* Will release all outstanding regions.
*/
void kbase_destroy_context(struct kbase_context *kctx);
/**
* kbase_ctx_flag - Check if @flag is set on @kctx
* @kctx: Pointer to kbase context to check
* @flag: Flag to check
*
* Return: true if @flag is set on @kctx, false if not.
*/
static inline bool kbase_ctx_flag(struct kbase_context *kctx, enum kbase_context_flags flag)
{
return atomic_read(&kctx->flags) & (int)flag;
}
/**
* kbase_ctx_compat_mode - Indicate whether a kbase context needs to operate
* in compatibility mode for 32-bit userspace.
* @kctx: kbase context
*
* Return: True if needs to maintain compatibility, False otherwise.
*/
bool kbase_ctx_compat_mode(struct kbase_context *kctx);
/**
* kbase_ctx_flag_clear - Clear @flag on @kctx
* @kctx: Pointer to kbase context
* @flag: Flag to clear
*
* Clear the @flag on @kctx. This is done atomically, so other flags being
* cleared or set at the same time will be safe.
*
* Some flags have locking requirements, check the documentation for the
* respective flags.
*/
static inline void kbase_ctx_flag_clear(struct kbase_context *kctx, enum kbase_context_flags flag)
{
atomic_andnot(flag, &kctx->flags);
}
/**
* kbase_ctx_flag_set - Set @flag on @kctx
* @kctx: Pointer to kbase context
* @flag: Flag to set
*
* Set the @flag on @kctx. This is done atomically, so other flags being
* cleared or set at the same time will be safe.
*
* Some flags have locking requirements, check the documentation for the
* respective flags.
*/
static inline void kbase_ctx_flag_set(struct kbase_context *kctx, enum kbase_context_flags flag)
{
atomic_or(flag, &kctx->flags);
}
#endif /* _KBASE_CONTEXT_H_ */

View file

@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
typedef int kbase_context_init_method(struct kbase_context *kctx);
typedef void kbase_context_term_method(struct kbase_context *kctx);
/**
* struct kbase_context_init - Device init/term methods.
* @init: Function pointer to a initialise method.
* @term: Function pointer to a terminate method.
* @err_mes: Error message to be printed when init method fails.
*/
struct kbase_context_init {
kbase_context_init_method *init;
kbase_context_term_method *term;
char *err_mes;
};
/**
* kbase_context_common_init() - Initialize kbase context
* @kctx: Pointer to the kbase context to be initialized.
*
* This function must be called only when a kbase context is instantiated.
*
* Return: 0 on success.
*/
int kbase_context_common_init(struct kbase_context *kctx);
void kbase_context_common_term(struct kbase_context *kctx);
int kbase_context_mem_pool_group_init(struct kbase_context *kctx);
void kbase_context_mem_pool_group_term(struct kbase_context *kctx);
int kbase_context_mmu_init(struct kbase_context *kctx);
void kbase_context_mmu_term(struct kbase_context *kctx);
int kbase_context_mem_alloc_page(struct kbase_context *kctx);
void kbase_context_mem_pool_free(struct kbase_context *kctx);
void kbase_context_sticky_resource_term(struct kbase_context *kctx);
int kbase_context_add_to_dev_list(struct kbase_context *kctx);
void kbase_context_remove_from_dev_list(struct kbase_context *kctx);

View file

@ -0,0 +1,66 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2018-2024 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
mali_kbase-y += \
csf/mali_kbase_csf_util.o \
csf/mali_kbase_csf_firmware_cfg.o \
csf/mali_kbase_csf_trace_buffer.o \
csf/mali_kbase_csf.o \
csf/mali_kbase_csf_scheduler.o \
csf/mali_kbase_csf_kcpu.o \
csf/mali_kbase_csf_tiler_heap.o \
csf/mali_kbase_csf_timeout.o \
csf/mali_kbase_csf_tl_reader.o \
csf/mali_kbase_csf_heap_context_alloc.o \
csf/mali_kbase_csf_reset_gpu.o \
csf/mali_kbase_csf_csg.o \
csf/mali_kbase_csf_csg_debugfs.o \
csf/mali_kbase_csf_kcpu_debugfs.o \
csf/mali_kbase_csf_sync.o \
csf/mali_kbase_csf_sync_debugfs.o \
csf/mali_kbase_csf_kcpu_fence_debugfs.o \
csf/mali_kbase_csf_protected_memory.o \
csf/mali_kbase_csf_tiler_heap_debugfs.o \
csf/mali_kbase_csf_cpu_queue.o \
csf/mali_kbase_csf_cpu_queue_debugfs.o \
csf/mali_kbase_csf_event.o \
csf/mali_kbase_csf_firmware_log.o \
csf/mali_kbase_csf_firmware_core_dump.o \
csf/mali_kbase_csf_tiler_heap_reclaim.o \
csf/mali_kbase_csf_mcu_shared_reg.o
ifeq ($(CONFIG_MALI_NO_MALI),y)
mali_kbase-y += csf/mali_kbase_csf_firmware_no_mali.o
mali_kbase-y += csf/mali_kbase_csf_fw_io_no_mali.o
else
mali_kbase-y += csf/mali_kbase_csf_firmware.o
mali_kbase-y += csf/mali_kbase_csf_fw_io.o
endif
mali_kbase-$(CONFIG_DEBUG_FS) += csf/mali_kbase_debug_csf_fault.o
ifeq ($(KBUILD_EXTMOD),)
# in-tree
-include $(src)/csf/ipa_control/Kbuild
else
# out-of-tree
include $(src)/csf/ipa_control/Kbuild
endif

View file

@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
#
# (C) COPYRIGHT 2020-2021 ARM Limited. All rights reserved.
#
# This program is free software and is provided to you under the terms of the
# GNU General Public License version 2 as published by the Free Software
# Foundation, and any use by you of this program is subject to the terms
# of such GNU license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can access it online at
# http://www.gnu.org/licenses/gpl-2.0.html.
#
#
mali_kbase-y += \
csf/ipa_control/mali_kbase_csf_ipa_control.o

View file

@ -0,0 +1,986 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <mali_kbase_config_defaults.h>
#include "backend/gpu/mali_kbase_clk_rate_trace_mgr.h"
#include "mali_kbase_csf_ipa_control.h"
/*
* Status flags from the STATUS register of the IPA Control interface.
*/
#define STATUS_COMMAND_ACTIVE ((u32)1 << 0)
#define STATUS_PROTECTED_MODE ((u32)1 << 8)
#define STATUS_RESET ((u32)1 << 9)
#define STATUS_TIMER_ENABLED ((u32)1 << 31)
/*
* Commands for the COMMAND register of the IPA Control interface.
*/
#define COMMAND_APPLY ((u32)1)
#define COMMAND_SAMPLE ((u32)3)
#define COMMAND_PROTECTED_ACK ((u32)4)
#define COMMAND_RESET_ACK ((u32)5)
/*
* Number of timer events per second.
*/
#define TIMER_EVENTS_PER_SECOND ((u32)1000 / IPA_CONTROL_TIMER_DEFAULT_VALUE_MS)
/*
* Number of bits used to configure a performance counter in SELECT registers.
*/
#define IPA_CONTROL_SELECT_BITS_PER_CNT ((u64)8)
/*
* Maximum value of a performance counter.
*/
#define MAX_PRFCNT_VALUE (((u64)1 << 48) - 1)
/**
* struct kbase_ipa_control_listener_data - Data for the GPU clock frequency
* listener
*
* @listener: GPU clock frequency listener.
* @kbdev: Pointer to kbase device.
* @clk_chg_wq: Dedicated workqueue to process the work item corresponding to
* a clock rate notification.
* @clk_chg_work: Work item to process the clock rate change
* @rate: The latest notified rate change, in unit of Hz
*/
struct kbase_ipa_control_listener_data {
struct kbase_clk_rate_listener listener;
struct kbase_device *kbdev;
struct workqueue_struct *clk_chg_wq;
struct work_struct clk_chg_work;
atomic_t rate;
};
static u32 timer_value(u32 gpu_rate)
{
return gpu_rate / TIMER_EVENTS_PER_SECOND;
}
static int wait_status(struct kbase_device *kbdev, u32 flags)
{
u32 val;
const u32 timeout_us = kbase_get_timeout_ms(kbdev, IPA_INACTIVE_TIMEOUT) * USEC_PER_MSEC;
/*
* Wait for the STATUS register to indicate that flags have been
* cleared, in case a transition is pending.
*/
const int err = kbase_reg_poll32_timeout(kbdev, IPA_CONTROL_ENUM(STATUS), val,
!(val & flags), 0, timeout_us, false);
if (err) {
dev_err(kbdev->dev, "IPA_CONTROL STATUS register stuck");
return -EBUSY;
}
return 0;
}
static int apply_select_config(struct kbase_device *kbdev, u64 *select)
{
int ret;
kbase_reg_write64(kbdev, IPA_CONTROL_ENUM(SELECT_CSHW), select[KBASE_IPA_CORE_TYPE_CSHW]);
kbase_reg_write64(kbdev, IPA_CONTROL_ENUM(SELECT_MEMSYS),
select[KBASE_IPA_CORE_TYPE_MEMSYS]);
kbase_reg_write64(kbdev, IPA_CONTROL_ENUM(SELECT_TILER), select[KBASE_IPA_CORE_TYPE_TILER]);
kbase_reg_write64(kbdev, IPA_CONTROL_ENUM(SELECT_SHADER),
select[KBASE_IPA_CORE_TYPE_SHADER]);
ret = wait_status(kbdev, STATUS_COMMAND_ACTIVE);
if (!ret) {
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(COMMAND), COMMAND_APPLY);
ret = wait_status(kbdev, STATUS_COMMAND_ACTIVE);
} else {
dev_err(kbdev->dev, "Wait for the pending command failed");
}
return ret;
}
static u64 read_value_cnt(struct kbase_device *kbdev, u8 type, u8 select_idx)
{
switch (type) {
case KBASE_IPA_CORE_TYPE_CSHW:
return kbase_reg_read64(kbdev, IPA_VALUE_CSHW_OFFSET(select_idx));
case KBASE_IPA_CORE_TYPE_MEMSYS:
return kbase_reg_read64(kbdev, IPA_VALUE_MEMSYS_OFFSET(select_idx));
case KBASE_IPA_CORE_TYPE_TILER:
return kbase_reg_read64(kbdev, IPA_VALUE_TILER_OFFSET(select_idx));
case KBASE_IPA_CORE_TYPE_SHADER:
return kbase_reg_read64(kbdev, IPA_VALUE_SHADER_OFFSET(select_idx));
default:
WARN(1, "Unknown core type: %u\n", type);
return 0;
}
}
static void build_select_config(struct kbase_ipa_control *ipa_ctrl, u64 *select_config)
{
size_t i;
for (i = 0; i < KBASE_IPA_CORE_TYPE_NUM; i++) {
size_t j;
select_config[i] = 0ULL;
for (j = 0; j < KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS; j++) {
struct kbase_ipa_control_prfcnt_config *prfcnt_config =
&ipa_ctrl->blocks[i].select[j];
select_config[i] |=
((u64)prfcnt_config->idx << (IPA_CONTROL_SELECT_BITS_PER_CNT * j));
}
}
}
static int update_select_registers(struct kbase_device *kbdev)
{
u64 select_config[KBASE_IPA_CORE_TYPE_NUM];
lockdep_assert_held(&kbdev->csf.ipa_control.lock);
build_select_config(&kbdev->csf.ipa_control, select_config);
return apply_select_config(kbdev, select_config);
}
static inline void calc_prfcnt_delta(struct kbase_device *kbdev,
struct kbase_ipa_control_prfcnt *prfcnt, bool gpu_ready)
{
u64 delta_value, raw_value;
if (gpu_ready)
raw_value = read_value_cnt(kbdev, (u8)prfcnt->type, prfcnt->select_idx);
else
raw_value = prfcnt->latest_raw_value;
if (raw_value < prfcnt->latest_raw_value) {
delta_value = (MAX_PRFCNT_VALUE - prfcnt->latest_raw_value) + raw_value;
} else {
delta_value = raw_value - prfcnt->latest_raw_value;
}
delta_value *= prfcnt->scaling_factor;
if (kbdev->csf.ipa_control.cur_gpu_rate == 0) {
static bool warned;
if (!warned) {
dev_warn(kbdev->dev, "%s: GPU freq is unexpectedly 0", __func__);
warned = true;
}
} else if (prfcnt->gpu_norm)
delta_value = div_u64(delta_value, kbdev->csf.ipa_control.cur_gpu_rate);
prfcnt->latest_raw_value = raw_value;
/* Accumulate the difference */
prfcnt->accumulated_diff += delta_value;
}
/**
* kbase_ipa_control_rate_change_notify - GPU frequency change callback
*
* @listener: Clock frequency change listener.
* @clk_index: Index of the clock for which the change has occurred.
* @clk_rate_hz: Clock frequency(Hz).
*
* This callback notifies kbase_ipa_control about GPU frequency changes.
* Only top-level clock changes are meaningful. GPU frequency updates
* affect all performance counters which require GPU normalization
* in every session.
*/
static void kbase_ipa_control_rate_change_notify(struct kbase_clk_rate_listener *listener,
u32 clk_index, u32 clk_rate_hz)
{
if ((clk_index == KBASE_CLOCK_DOMAIN_TOP) && (clk_rate_hz != 0)) {
struct kbase_ipa_control_listener_data *listener_data =
container_of(listener, struct kbase_ipa_control_listener_data, listener);
/* Save the rate and delegate the job to a work item */
atomic_set(&listener_data->rate, clk_rate_hz);
queue_work(listener_data->clk_chg_wq, &listener_data->clk_chg_work);
}
}
static void kbase_ipa_ctrl_rate_change_worker(struct work_struct *data)
{
struct kbase_ipa_control_listener_data *listener_data =
container_of(data, struct kbase_ipa_control_listener_data, clk_chg_work);
struct kbase_device *kbdev = listener_data->kbdev;
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
unsigned long flags;
u32 rate;
size_t i;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (!kbdev->pm.backend.gpu_ready) {
dev_err(kbdev->dev, "%s: GPU frequency cannot change while GPU is off", __func__);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return;
}
spin_lock(&ipa_ctrl->lock);
/* Picking up the latest notified rate */
rate = (u32)atomic_read(&listener_data->rate);
for (i = 0; i < KBASE_IPA_CONTROL_MAX_SESSIONS; i++) {
struct kbase_ipa_control_session *session = &ipa_ctrl->sessions[i];
if (session->active) {
size_t j;
for (j = 0; j < session->num_prfcnts; j++) {
struct kbase_ipa_control_prfcnt *prfcnt = &session->prfcnts[j];
if (prfcnt->gpu_norm)
calc_prfcnt_delta(kbdev, prfcnt, true);
}
}
}
ipa_ctrl->cur_gpu_rate = rate;
/* Update the timer for automatic sampling if active sessions
* are present. Counters have already been manually sampled.
*/
if (ipa_ctrl->num_active_sessions > 0)
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(TIMER), timer_value(rate));
spin_unlock(&ipa_ctrl->lock);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
void kbase_ipa_control_init(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
struct kbase_ipa_control_listener_data *listener_data;
size_t i;
unsigned long flags;
for (i = 0; i < KBASE_IPA_CORE_TYPE_NUM; i++) {
ipa_ctrl->blocks[i].num_available_counters = KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS;
}
spin_lock_init(&ipa_ctrl->lock);
listener_data = kmalloc(sizeof(struct kbase_ipa_control_listener_data), GFP_KERNEL);
if (listener_data) {
listener_data->clk_chg_wq =
alloc_workqueue("ipa_ctrl_wq", WQ_HIGHPRI | WQ_UNBOUND, 1);
if (listener_data->clk_chg_wq) {
INIT_WORK(&listener_data->clk_chg_work, kbase_ipa_ctrl_rate_change_worker);
listener_data->listener.notify = kbase_ipa_control_rate_change_notify;
listener_data->kbdev = kbdev;
ipa_ctrl->rtm_listener_data = listener_data;
/* Initialise to 0, which is out of normal notified rates */
atomic_set(&listener_data->rate, 0);
} else {
dev_warn(kbdev->dev,
"%s: failed to allocate workqueue, clock rate update disabled",
__func__);
kfree(listener_data);
listener_data = NULL;
}
} else
dev_warn(kbdev->dev,
"%s: failed to allocate memory, IPA control clock rate update disabled",
__func__);
spin_lock_irqsave(&clk_rtm->lock, flags);
if (clk_rtm->clks[KBASE_CLOCK_DOMAIN_TOP])
ipa_ctrl->cur_gpu_rate = clk_rtm->clks[KBASE_CLOCK_DOMAIN_TOP]->clock_val;
if (listener_data)
kbase_clk_rate_trace_manager_subscribe_no_lock(clk_rtm, &listener_data->listener);
spin_unlock_irqrestore(&clk_rtm->lock, flags);
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_init);
void kbase_ipa_control_term(struct kbase_device *kbdev)
{
unsigned long flags;
struct kbase_clk_rate_trace_manager *clk_rtm = &kbdev->pm.clk_rtm;
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
struct kbase_ipa_control_listener_data *listener_data = ipa_ctrl->rtm_listener_data;
WARN_ON(ipa_ctrl->num_active_sessions);
if (listener_data) {
kbase_clk_rate_trace_manager_unsubscribe(clk_rtm, &listener_data->listener);
destroy_workqueue(listener_data->clk_chg_wq);
}
kfree(ipa_ctrl->rtm_listener_data);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
if (kbdev->pm.backend.gpu_powered)
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(TIMER), 0);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_term);
/** session_read_raw_values - Read latest raw values for a sessions
* @kbdev: Pointer to kbase device.
* @session: Pointer to the session whose performance counters shall be read.
*
* Read and update the latest raw values of all the performance counters
* belonging to a given session.
*/
static void session_read_raw_values(struct kbase_device *kbdev,
struct kbase_ipa_control_session *session)
{
size_t i;
lockdep_assert_held(&kbdev->csf.ipa_control.lock);
for (i = 0; i < session->num_prfcnts; i++) {
struct kbase_ipa_control_prfcnt *prfcnt = &session->prfcnts[i];
u64 raw_value = read_value_cnt(kbdev, (u8)prfcnt->type, prfcnt->select_idx);
prfcnt->latest_raw_value = raw_value;
}
}
/** session_gpu_start - Start one or all sessions
* @kbdev: Pointer to kbase device.
* @ipa_ctrl: Pointer to IPA_CONTROL descriptor.
* @session: Pointer to the session to initialize, or NULL to initialize
* all sessions.
*
* This function starts one or all sessions by capturing a manual sample,
* reading the latest raw value of performance counters and possibly enabling
* the timer for automatic sampling if necessary.
*
* If a single session is given, it is assumed to be active, regardless of
* the number of active sessions. The number of performance counters belonging
* to the session shall be set in advance.
*
* If no session is given, the function shall start all sessions.
* The function does nothing if there are no active sessions.
*
* Return: 0 on success, or error code on failure.
*/
static int session_gpu_start(struct kbase_device *kbdev, struct kbase_ipa_control *ipa_ctrl,
struct kbase_ipa_control_session *session)
{
bool first_start = (session != NULL) && (ipa_ctrl->num_active_sessions == 0);
int ret = 0;
lockdep_assert_held(&kbdev->csf.ipa_control.lock);
/*
* Exit immediately if the caller intends to start all sessions
* but there are no active sessions. It's important that no operation
* is done on the IPA_CONTROL interface in that case.
*/
if (!session && ipa_ctrl->num_active_sessions == 0)
return ret;
/*
* Take a manual sample unconditionally if the caller intends
* to start all sessions. Otherwise, only take a manual sample
* if this is the first session to be initialized, for accumulator
* registers are empty and no timer has been configured for automatic
* sampling.
*/
if (!session || first_start) {
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(COMMAND), COMMAND_SAMPLE);
ret = wait_status(kbdev, STATUS_COMMAND_ACTIVE);
if (ret)
dev_err(kbdev->dev, "%s: failed to sample new counters", __func__);
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(TIMER),
timer_value(ipa_ctrl->cur_gpu_rate));
}
/*
* Read current raw value to start the session.
* This is necessary to put the first query in condition
* to generate a correct value by calculating the difference
* from the beginning of the session. This consideration
* is true regardless of the number of sessions the caller
* intends to start.
*/
if (!ret) {
if (session) {
/* On starting a session, value read is required for
* IPA power model's calculation initialization.
*/
session_read_raw_values(kbdev, session);
} else {
size_t session_idx;
for (session_idx = 0; session_idx < KBASE_IPA_CONTROL_MAX_SESSIONS;
session_idx++) {
struct kbase_ipa_control_session *session_to_check =
&ipa_ctrl->sessions[session_idx];
if (session_to_check->active)
session_read_raw_values(kbdev, session_to_check);
}
}
}
return ret;
}
int kbase_ipa_control_register(struct kbase_device *kbdev,
const struct kbase_ipa_control_perf_counter *perf_counters,
size_t num_counters, void **client)
{
int ret = 0;
size_t i, session_idx, req_counters[KBASE_IPA_CORE_TYPE_NUM];
bool already_configured[KBASE_IPA_CONTROL_MAX_COUNTERS];
bool new_config = false;
struct kbase_ipa_control *ipa_ctrl;
struct kbase_ipa_control_session *session = NULL;
unsigned long flags;
if (WARN_ON(unlikely(kbdev == NULL)))
return -ENODEV;
if (WARN_ON(perf_counters == NULL) || WARN_ON(client == NULL) ||
WARN_ON(num_counters > KBASE_IPA_CONTROL_MAX_COUNTERS)) {
dev_err(kbdev->dev, "%s: wrong input arguments", __func__);
return -EINVAL;
}
kbase_pm_context_active(kbdev);
ipa_ctrl = &kbdev->csf.ipa_control;
spin_lock_irqsave(&ipa_ctrl->lock, flags);
if (ipa_ctrl->num_active_sessions == KBASE_IPA_CONTROL_MAX_SESSIONS) {
dev_err(kbdev->dev, "%s: too many sessions", __func__);
ret = -EBUSY;
goto exit;
}
for (i = 0; i < KBASE_IPA_CORE_TYPE_NUM; i++)
req_counters[i] = 0;
/*
* Count how many counters would need to be configured in order to
* satisfy the request. Requested counters which happen to be already
* configured can be skipped.
*/
for (i = 0; i < num_counters; i++) {
size_t j;
enum kbase_ipa_core_type type = perf_counters[i].type;
u8 idx = perf_counters[i].idx;
if ((type >= KBASE_IPA_CORE_TYPE_NUM) || (idx >= KBASE_IPA_CONTROL_CNT_MAX_IDX)) {
dev_err(kbdev->dev, "%s: invalid requested type %u and/or index %u",
__func__, type, idx);
ret = -EINVAL;
goto exit;
}
for (j = 0; j < KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS; j++) {
struct kbase_ipa_control_prfcnt_config *prfcnt_config =
&ipa_ctrl->blocks[type].select[j];
if (prfcnt_config->refcount > 0) {
if (prfcnt_config->idx == idx) {
already_configured[i] = true;
break;
}
}
}
if (j == KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS) {
already_configured[i] = false;
req_counters[type]++;
new_config = true;
}
}
for (i = 0; i < KBASE_IPA_CORE_TYPE_NUM; i++)
if (req_counters[i] > ipa_ctrl->blocks[i].num_available_counters) {
dev_err(kbdev->dev,
"%s: more counters (%zu) than available (%zu) have been requested for type %zu",
__func__, req_counters[i],
ipa_ctrl->blocks[i].num_available_counters, i);
ret = -EINVAL;
goto exit;
}
/*
* The request has been validated.
* Firstly, find an available session and then set up the initial state
* of the session and update the configuration of performance counters
* in the internal state of kbase_ipa_control.
*/
for (session_idx = 0; session_idx < KBASE_IPA_CONTROL_MAX_SESSIONS; session_idx++) {
if (!ipa_ctrl->sessions[session_idx].active) {
session = &ipa_ctrl->sessions[session_idx];
break;
}
}
if (!session) {
dev_err(kbdev->dev, "%s: wrong or corrupt session state", __func__);
ret = -EBUSY;
goto exit;
}
for (i = 0; i < num_counters; i++) {
struct kbase_ipa_control_prfcnt_config *prfcnt_config;
size_t j;
u8 type = perf_counters[i].type;
u8 idx = perf_counters[i].idx;
for (j = 0; j < KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS; j++) {
prfcnt_config = &ipa_ctrl->blocks[type].select[j];
if (already_configured[i]) {
if ((prfcnt_config->refcount > 0) && (prfcnt_config->idx == idx)) {
break;
}
} else {
if (prfcnt_config->refcount == 0)
break;
}
}
if (WARN_ON((prfcnt_config->refcount > 0 && prfcnt_config->idx != idx) ||
(j == KBASE_IPA_CONTROL_NUM_BLOCK_COUNTERS))) {
dev_err(kbdev->dev,
"%s: invalid internal state: counter already configured or no counter available to configure",
__func__);
ret = -EBUSY;
goto exit;
}
if (prfcnt_config->refcount == 0) {
prfcnt_config->idx = idx;
ipa_ctrl->blocks[type].num_available_counters--;
}
session->prfcnts[i].accumulated_diff = 0;
session->prfcnts[i].type = type;
session->prfcnts[i].select_idx = j;
session->prfcnts[i].scaling_factor = perf_counters[i].scaling_factor;
session->prfcnts[i].gpu_norm = perf_counters[i].gpu_norm;
/* Reports to this client for GPU time spent in protected mode
* should begin from the point of registration.
*/
session->last_query_time = ktime_get_raw_ns();
/* Initially, no time has been spent in protected mode */
session->protm_time = 0;
prfcnt_config->refcount++;
}
/*
* Apply new configuration, if necessary.
* As a temporary solution, make sure that the GPU is on
* before applying the new configuration.
*/
if (new_config) {
ret = update_select_registers(kbdev);
if (ret)
dev_err(kbdev->dev, "%s: failed to apply new SELECT configuration",
__func__);
}
if (!ret) {
session->num_prfcnts = num_counters;
ret = session_gpu_start(kbdev, ipa_ctrl, session);
}
if (!ret) {
session->active = true;
ipa_ctrl->num_active_sessions++;
*client = session;
}
exit:
spin_unlock_irqrestore(&ipa_ctrl->lock, flags);
kbase_pm_context_idle(kbdev);
return ret;
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_register);
int kbase_ipa_control_unregister(struct kbase_device *kbdev, const void *client)
{
struct kbase_ipa_control *ipa_ctrl;
struct kbase_ipa_control_session *session;
int ret = 0;
size_t i;
unsigned long flags;
bool new_config = false, valid_session = false;
if (WARN_ON(unlikely(kbdev == NULL)))
return -ENODEV;
if (WARN_ON(client == NULL)) {
dev_err(kbdev->dev, "%s: wrong input arguments", __func__);
return -EINVAL;
}
kbase_pm_context_active(kbdev);
ipa_ctrl = &kbdev->csf.ipa_control;
session = (struct kbase_ipa_control_session *)client;
spin_lock_irqsave(&ipa_ctrl->lock, flags);
for (i = 0; i < KBASE_IPA_CONTROL_MAX_SESSIONS; i++) {
if (session == &ipa_ctrl->sessions[i]) {
valid_session = true;
break;
}
}
if (!valid_session) {
dev_err(kbdev->dev, "%s: invalid session handle", __func__);
ret = -EINVAL;
goto exit;
}
if (ipa_ctrl->num_active_sessions == 0) {
dev_err(kbdev->dev, "%s: no active sessions found", __func__);
ret = -EINVAL;
goto exit;
}
if (!session->active) {
dev_err(kbdev->dev, "%s: session is already inactive", __func__);
ret = -EINVAL;
goto exit;
}
for (i = 0; i < session->num_prfcnts; i++) {
struct kbase_ipa_control_prfcnt_config *prfcnt_config;
u8 type = session->prfcnts[i].type;
u8 idx = session->prfcnts[i].select_idx;
prfcnt_config = &ipa_ctrl->blocks[type].select[idx];
if (!WARN_ON(prfcnt_config->refcount == 0)) {
prfcnt_config->refcount--;
if (prfcnt_config->refcount == 0) {
new_config = true;
ipa_ctrl->blocks[type].num_available_counters++;
}
}
}
if (new_config) {
ret = update_select_registers(kbdev);
if (ret)
dev_err(kbdev->dev, "%s: failed to apply SELECT configuration", __func__);
}
session->num_prfcnts = 0;
session->active = false;
ipa_ctrl->num_active_sessions--;
exit:
spin_unlock_irqrestore(&ipa_ctrl->lock, flags);
kbase_pm_context_idle(kbdev);
return ret;
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_unregister);
int kbase_ipa_control_query(struct kbase_device *kbdev, const void *client, u64 *values,
size_t num_values, u64 *protected_time)
{
struct kbase_ipa_control *ipa_ctrl;
struct kbase_ipa_control_session *session;
size_t i;
unsigned long flags;
bool gpu_ready;
if (WARN_ON(unlikely(kbdev == NULL)))
return -ENODEV;
if (WARN_ON(client == NULL) || WARN_ON(values == NULL)) {
dev_err(kbdev->dev, "%s: wrong input arguments", __func__);
return -EINVAL;
}
ipa_ctrl = &kbdev->csf.ipa_control;
session = (struct kbase_ipa_control_session *)client;
if (!session->active) {
dev_err(kbdev->dev, "%s: attempt to query inactive session", __func__);
return -EINVAL;
}
if (WARN_ON(num_values < session->num_prfcnts)) {
dev_err(kbdev->dev, "%s: not enough space (%zu) to return all counter values (%zu)",
__func__, num_values, session->num_prfcnts);
return -EINVAL;
}
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
gpu_ready = kbdev->pm.backend.gpu_ready;
for (i = 0; i < session->num_prfcnts; i++) {
struct kbase_ipa_control_prfcnt *prfcnt = &session->prfcnts[i];
calc_prfcnt_delta(kbdev, prfcnt, gpu_ready);
/* Return all the accumulated difference */
values[i] = prfcnt->accumulated_diff;
prfcnt->accumulated_diff = 0;
}
if (protected_time) {
u64 time_now = ktime_get_raw_ns();
/* This is the amount of protected-mode time spent prior to
* the current protm period.
*/
*protected_time = session->protm_time;
if (kbdev->protected_mode) {
*protected_time +=
time_now - MAX(session->last_query_time, ipa_ctrl->protm_start);
}
session->last_query_time = time_now;
session->protm_time = 0;
}
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
for (i = session->num_prfcnts; i < num_values; i++)
values[i] = 0;
return 0;
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_query);
void kbase_ipa_control_handle_gpu_power_off(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
size_t session_idx;
int ret;
lockdep_assert_held(&kbdev->hwaccess_lock);
/* GPU should still be ready for use when this function gets called */
WARN_ON(!kbdev->pm.backend.gpu_ready);
/* Interrupts are already disabled and interrupt state is also saved */
spin_lock(&ipa_ctrl->lock);
/* First disable the automatic sampling through TIMER */
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(TIMER), 0);
ret = wait_status(kbdev, STATUS_TIMER_ENABLED);
if (ret) {
dev_err(kbdev->dev, "Wait for disabling of IPA control timer failed: %d", ret);
}
/* Now issue the manual SAMPLE command */
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(COMMAND), COMMAND_SAMPLE);
ret = wait_status(kbdev, STATUS_COMMAND_ACTIVE);
if (ret) {
dev_err(kbdev->dev, "Wait for the completion of manual sample failed: %d", ret);
}
for (session_idx = 0; session_idx < KBASE_IPA_CONTROL_MAX_SESSIONS; session_idx++) {
struct kbase_ipa_control_session *session = &ipa_ctrl->sessions[session_idx];
if (session->active) {
size_t i;
for (i = 0; i < session->num_prfcnts; i++) {
struct kbase_ipa_control_prfcnt *prfcnt = &session->prfcnts[i];
calc_prfcnt_delta(kbdev, prfcnt, true);
}
}
}
spin_unlock(&ipa_ctrl->lock);
}
void kbase_ipa_control_handle_gpu_power_on(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
int ret;
lockdep_assert_held(&kbdev->hwaccess_lock);
/* GPU should have become ready for use when this function gets called */
WARN_ON(!kbdev->pm.backend.gpu_ready);
/* Interrupts are already disabled and interrupt state is also saved */
spin_lock(&ipa_ctrl->lock);
ret = update_select_registers(kbdev);
if (ret) {
dev_err(kbdev->dev, "Failed to reconfigure the select registers: %d", ret);
}
/* Accumulator registers would not contain any sample after GPU power
* cycle if the timer has not been enabled first. Initialize all sessions.
*/
ret = session_gpu_start(kbdev, ipa_ctrl, NULL);
spin_unlock(&ipa_ctrl->lock);
}
void kbase_ipa_control_handle_gpu_reset_pre(struct kbase_device *kbdev)
{
/* A soft reset is treated as a power down */
kbase_ipa_control_handle_gpu_power_off(kbdev);
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_handle_gpu_reset_pre);
void kbase_ipa_control_handle_gpu_reset_post(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
int ret;
u32 status;
lockdep_assert_held(&kbdev->hwaccess_lock);
/* GPU should have become ready for use when this function gets called */
WARN_ON(!kbdev->pm.backend.gpu_ready);
/* Interrupts are already disabled and interrupt state is also saved */
spin_lock(&ipa_ctrl->lock);
/* Check the status reset bit is set before acknowledging it */
status = kbase_reg_read32(kbdev, IPA_CONTROL_ENUM(STATUS));
if (status & STATUS_RESET) {
/* Acknowledge the reset command */
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(COMMAND), COMMAND_RESET_ACK);
ret = wait_status(kbdev, STATUS_RESET);
if (ret) {
dev_err(kbdev->dev, "Wait for the reset ack command failed: %d", ret);
}
}
spin_unlock(&ipa_ctrl->lock);
kbase_ipa_control_handle_gpu_power_on(kbdev);
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_handle_gpu_reset_post);
#ifdef KBASE_PM_RUNTIME
void kbase_ipa_control_handle_gpu_sleep_enter(struct kbase_device *kbdev)
{
lockdep_assert_held(&kbdev->hwaccess_lock);
if (kbdev->pm.backend.mcu_state == KBASE_MCU_IN_SLEEP) {
/* GPU Sleep is treated as a power down */
kbase_ipa_control_handle_gpu_power_off(kbdev);
/* SELECT_CSHW register needs to be cleared to prevent any
* IPA control message to be sent to the top level GPU HWCNT.
*/
kbase_reg_write64(kbdev, IPA_CONTROL_ENUM(SELECT_CSHW), 0);
/* No need to issue the APPLY command here */
}
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_handle_gpu_sleep_enter);
void kbase_ipa_control_handle_gpu_sleep_exit(struct kbase_device *kbdev)
{
lockdep_assert_held(&kbdev->hwaccess_lock);
if (kbdev->pm.backend.mcu_state == KBASE_MCU_IN_SLEEP) {
/* To keep things simple, currently exit from
* GPU Sleep is treated as a power on event where
* all 4 SELECT registers are reconfigured.
* On exit from sleep, reconfiguration is needed
* only for the SELECT_CSHW register.
*/
kbase_ipa_control_handle_gpu_power_on(kbdev);
}
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_handle_gpu_sleep_exit);
#endif
#if MALI_UNIT_TEST
void kbase_ipa_control_rate_change_notify_test(struct kbase_device *kbdev, u32 clk_index,
u32 clk_rate_hz)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
struct kbase_ipa_control_listener_data *listener_data = ipa_ctrl->rtm_listener_data;
kbase_ipa_control_rate_change_notify(&listener_data->listener, clk_index, clk_rate_hz);
/* Ensure the callback has taken effect before returning back to the test caller */
flush_work(&listener_data->clk_chg_work);
}
KBASE_EXPORT_TEST_API(kbase_ipa_control_rate_change_notify_test);
#endif
void kbase_ipa_control_protm_entered(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
lockdep_assert_held(&kbdev->hwaccess_lock);
ipa_ctrl->protm_start = ktime_get_raw_ns();
}
void kbase_ipa_control_protm_exited(struct kbase_device *kbdev)
{
struct kbase_ipa_control *ipa_ctrl = &kbdev->csf.ipa_control;
size_t i;
u64 time_now = ktime_get_raw_ns();
u32 status;
lockdep_assert_held(&kbdev->hwaccess_lock);
for (i = 0; i < KBASE_IPA_CONTROL_MAX_SESSIONS; i++) {
struct kbase_ipa_control_session *session = &ipa_ctrl->sessions[i];
if (session->active) {
u64 protm_time =
time_now - MAX(session->last_query_time, ipa_ctrl->protm_start);
session->protm_time += protm_time;
}
}
/* Acknowledge the protected_mode bit in the IPA_CONTROL STATUS
* register
*/
status = kbase_reg_read32(kbdev, IPA_CONTROL_ENUM(STATUS));
if (status & STATUS_PROTECTED_MODE) {
int ret;
/* Acknowledge the protm command */
kbase_reg_write32(kbdev, IPA_CONTROL_ENUM(COMMAND), COMMAND_PROTECTED_ACK);
ret = wait_status(kbdev, STATUS_PROTECTED_MODE);
if (ret) {
dev_err(kbdev->dev, "Wait for the protm ack command failed: %d", ret);
}
}
}

View file

@ -0,0 +1,270 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_IPA_CONTROL_H_
#define _KBASE_CSF_IPA_CONTROL_H_
#include <mali_kbase.h>
/*
* Maximum index accepted to configure an IPA Control performance counter.
*/
#define KBASE_IPA_CONTROL_CNT_MAX_IDX ((u8)64 * 3)
/**
* struct kbase_ipa_control_perf_counter - Performance counter description
*
* @scaling_factor: Scaling factor by which the counter's value shall be
* multiplied. A scaling factor of 1 corresponds to units
* of 1 second if values are normalised by GPU frequency.
* @gpu_norm: Indicating whether counter values shall be normalized by
* GPU frequency. If true, returned values represent
* an interval of time expressed in seconds (when the scaling
* factor is set to 1).
* @type: Type of counter block for performance counter.
* @idx: Index of the performance counter inside the block.
* It may be dependent on GPU architecture.
* It cannot be greater than KBASE_IPA_CONTROL_CNT_MAX_IDX.
*
* This structure is used by clients of the IPA Control component to describe
* a performance counter that they intend to read. The counter is identified
* by block and index. In addition to that, the client also specifies how
* values shall be represented. Raw values are a number of GPU cycles;
* if normalized, they are divided by GPU frequency and become an interval
* of time expressed in seconds, since the GPU frequency is given in Hz.
* The client may specify a scaling factor to multiply counter values before
* they are divided by frequency, in case the unit of time of 1 second is
* too low in resolution. For instance: a scaling factor of 1000 implies
* that the returned value is a time expressed in milliseconds; a scaling
* factor of 1000 * 1000 implies that the returned value is a time expressed
* in microseconds.
*/
struct kbase_ipa_control_perf_counter {
u64 scaling_factor;
bool gpu_norm;
enum kbase_ipa_core_type type;
u8 idx;
};
/**
* kbase_ipa_control_init - Initialize the IPA Control component
*
* @kbdev: Pointer to Kbase device.
*
* This function must be called only when a kbase device is initialized.
*/
void kbase_ipa_control_init(struct kbase_device *kbdev);
/**
* kbase_ipa_control_term - Terminate the IPA Control component
*
* @kbdev: Pointer to Kbase device.
*/
void kbase_ipa_control_term(struct kbase_device *kbdev);
/**
* kbase_ipa_control_register - Register a client to the IPA Control component
*
* @kbdev: Pointer to Kbase device.
* @perf_counters: Array of performance counters the client intends to read.
* For each counter the client specifies block, index,
* scaling factor and whether it must be normalized by GPU
* frequency.
* @num_counters: Number of performance counters. It cannot exceed the total
* number of counters that exist on the IPA Control interface.
* @client: Handle to an opaque structure set by IPA Control if
* the registration is successful. This handle identifies
* a client's session and shall be provided in its future
* queries.
*
* A client needs to subscribe to the IPA Control component by declaring which
* performance counters it intends to read, and specifying a scaling factor
* and whether normalization is requested for each performance counter.
* The function shall configure the IPA Control interface accordingly and start
* a session for the client that made the request. A unique handle is returned
* if registration is successful in order to identify the client's session
* and be used for future queries.
*
* Return: 0 on success, negative -errno on error
*/
int kbase_ipa_control_register(struct kbase_device *kbdev,
const struct kbase_ipa_control_perf_counter *perf_counters,
size_t num_counters, void **client);
/**
* kbase_ipa_control_unregister - Unregister a client from IPA Control
*
* @kbdev: Pointer to kbase device.
* @client: Handle to an opaque structure that identifies the client session
* to terminate, as returned by kbase_ipa_control_register.
*
* Return: 0 on success, negative -errno on error
*/
int kbase_ipa_control_unregister(struct kbase_device *kbdev, const void *client);
/**
* kbase_ipa_control_query - Query performance counters
*
* @kbdev: Pointer to kbase device.
* @client: Handle to an opaque structure that identifies the client
* session, as returned by kbase_ipa_control_register.
* @values: Array of values queried from performance counters, whose
* length depends on the number of counters requested at
* the time of registration. Values are scaled and normalized
* and represent the difference since the last query.
* @num_values: Number of entries in the array of values that has been
* passed by the caller. It must be at least equal to the
* number of performance counters the client registered itself
* to read.
* @protected_time: Time spent in protected mode since last query,
* expressed in nanoseconds. This pointer may be NULL if the
* client doesn't want to know about this.
*
* A client that has already opened a session by registering itself to read
* some performance counters may use this function to query the values of
* those counters. The values returned are normalized by GPU frequency if
* requested and then multiplied by the scaling factor provided at the time
* of registration. Values always represent a difference since the last query.
*
* Performance counters are not updated while the GPU operates in protected
* mode. For this reason, returned values may be unreliable if the GPU has
* been in protected mode since the last query. The function returns success
* in that case, but it also gives a measure of how much time has been spent
* in protected mode.
*
* Return: 0 on success, negative -errno on error
*/
int kbase_ipa_control_query(struct kbase_device *kbdev, const void *client, u64 *values,
size_t num_values, u64 *protected_time);
/**
* kbase_ipa_control_handle_gpu_power_on - Handle the GPU power on event
*
* @kbdev: Pointer to kbase device.
*
* This function is called after GPU has been powered and is ready for use.
* After the GPU power on, IPA Control component needs to ensure that the
* counters start incrementing again.
*/
void kbase_ipa_control_handle_gpu_power_on(struct kbase_device *kbdev);
/**
* kbase_ipa_control_handle_gpu_power_off - Handle the GPU power off event
*
* @kbdev: Pointer to kbase device.
*
* This function is called just before the GPU is powered off when it is still
* ready for use.
* IPA Control component needs to be aware of the GPU power off so that it can
* handle the query from Clients appropriately and return meaningful values
* to them.
*/
void kbase_ipa_control_handle_gpu_power_off(struct kbase_device *kbdev);
/**
* kbase_ipa_control_handle_gpu_reset_pre - Handle the pre GPU reset event
*
* @kbdev: Pointer to kbase device.
*
* This function is called when the GPU is about to be reset.
*/
void kbase_ipa_control_handle_gpu_reset_pre(struct kbase_device *kbdev);
/**
* kbase_ipa_control_handle_gpu_reset_post - Handle the post GPU reset event
*
* @kbdev: Pointer to kbase device.
*
* This function is called after the GPU has been reset.
*/
void kbase_ipa_control_handle_gpu_reset_post(struct kbase_device *kbdev);
#ifdef KBASE_PM_RUNTIME
/**
* kbase_ipa_control_handle_gpu_sleep_enter - Handle the pre GPU Sleep event
*
* @kbdev: Pointer to kbase device.
*
* This function is called after MCU has been put to sleep state & L2 cache has
* been powered down. The top level part of GPU is still powered up when this
* function is called.
*/
void kbase_ipa_control_handle_gpu_sleep_enter(struct kbase_device *kbdev);
/**
* kbase_ipa_control_handle_gpu_sleep_exit - Handle the post GPU Sleep event
*
* @kbdev: Pointer to kbase device.
*
* This function is called when L2 needs to be powered up and MCU can exit the
* sleep state. The top level part of GPU is powered up when this function is
* called.
*
* This function must be called only if kbase_ipa_control_handle_gpu_sleep_enter()
* was called previously.
*/
void kbase_ipa_control_handle_gpu_sleep_exit(struct kbase_device *kbdev);
#endif
#if MALI_UNIT_TEST
/**
* kbase_ipa_control_rate_change_notify_test - Notify GPU rate change
* (only for testing)
*
* @kbdev: Pointer to kbase device.
* @clk_index: Index of the clock for which the change has occurred.
* @clk_rate_hz: Clock frequency(Hz).
*
* Notify the IPA Control component about a GPU rate change.
*/
void kbase_ipa_control_rate_change_notify_test(struct kbase_device *kbdev, u32 clk_index,
u32 clk_rate_hz);
#endif /* MALI_UNIT_TEST */
/**
* kbase_ipa_control_protm_entered - Tell IPA_CONTROL that protected mode
* has been entered.
*
* @kbdev: Pointer to kbase device.
*
* This function provides a means through which IPA_CONTROL can be informed
* that the GPU has entered protected mode. Since the GPU cannot access
* performance counters while in this mode, this information is useful as
* it implies (a) the values of these registers cannot change, so theres no
* point trying to read them, and (b) IPA_CONTROL has a means through which
* to record the duration of time the GPU is in protected mode, which can
* then be forwarded on to clients, who may wish, for example, to assume
* that the GPU was busy 100% of the time while in this mode.
*/
void kbase_ipa_control_protm_entered(struct kbase_device *kbdev);
/**
* kbase_ipa_control_protm_exited - Tell IPA_CONTROL that protected mode
* has been exited.
*
* @kbdev: Pointer to kbase device
*
* This function provides a means through which IPA_CONTROL can be informed
* that the GPU has exited from protected mode.
*/
void kbase_ipa_control_protm_exited(struct kbase_device *kbdev);
#endif /* _KBASE_CSF_IPA_CONTROL_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,584 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2018-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_H_
#define _KBASE_CSF_H_
#include "mali_kbase_csf_kcpu.h"
#include "mali_kbase_csf_scheduler.h"
#include "mali_kbase_csf_firmware.h"
#include "mali_kbase_csf_protected_memory.h"
#include "mali_kbase_hwaccess_time.h"
/* Indicate invalid CS h/w interface
*/
#define KBASEP_IF_NR_INVALID ((s8)-1)
/* Indicate invalid CSG number for a GPU command queue group
*/
#define KBASEP_CSG_NR_INVALID ((s8)-1)
/* Indicate invalid user doorbell number for a GPU command queue
*/
#define KBASEP_USER_DB_NR_INVALID ((s8)-1)
/* Number of pages used for GPU command queue's User input & output data */
#define KBASEP_NUM_CS_USER_IO_PAGES (2)
/* Indicates an invalid value for the scan out sequence number, used to
* signify there is no group that has protected mode execution pending.
*/
#define KBASEP_TICK_PROTM_PEND_SCAN_SEQ_NR_INVALID (U32_MAX)
#define FIRMWARE_IDLE_HYSTERESIS_TIME_NS (10 * 1000 * 1000) /* Default 10 milliseconds */
/* Idle hysteresis time can be scaled down when GPU sleep feature is used */
#define FIRMWARE_IDLE_HYSTERESIS_GPU_SLEEP_SCALER (5)
/**
* kbase_csf_ctx_init - Initialize the CSF interface for a GPU address space.
*
* @kctx: Pointer to the kbase context which is being initialized.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_ctx_init(struct kbase_context *kctx);
/**
* kbase_csf_ctx_handle_fault - Terminate queue groups & notify fault upon
* GPU bus fault, MMU page fault or similar.
*
* @kctx: Pointer to faulty kbase context.
* @fault: Pointer to the fault.
* @fw_unresponsive: Whether or not the FW is deemed unresponsive
*
* This function terminates all GPU command queue groups in the context and
* notifies the event notification thread of the fault. If the FW is deemed
* unresponsive, e.g. when recovering from a GLB_FATAL, it will not wait
* for the groups to be terminated by the MCU, since in this case it will
* time-out anyway.
*/
void kbase_csf_ctx_handle_fault(struct kbase_context *kctx, struct kbase_fault *fault,
bool fw_unresponsive);
/**
* kbase_csf_ctx_report_page_fault_for_active_groups - Notify Userspace about GPU page fault
* for active groups of the faulty context.
*
* @kctx: Pointer to faulty kbase context.
* @fault: Pointer to the fault.
*
* This function notifies the event notification thread of the GPU page fault.
*/
void kbase_csf_ctx_report_page_fault_for_active_groups(struct kbase_context *kctx,
struct kbase_fault *fault);
/**
* kbase_csf_ctx_term - Terminate the CSF interface for a GPU address space.
*
* @kctx: Pointer to the kbase context which is being terminated.
*
* This function terminates any remaining CSGs and CSs which weren't destroyed
* before context termination.
*/
void kbase_csf_ctx_term(struct kbase_context *kctx);
/**
* kbase_csf_queue_register - Register a GPU command queue.
*
* @kctx: Pointer to the kbase context within which the
* queue is to be registered.
* @reg: Pointer to the structure which contains details of the
* queue to be registered within the provided
* context.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_register(struct kbase_context *kctx, struct kbase_ioctl_cs_queue_register *reg);
/**
* kbase_csf_queue_register_ex - Register a GPU command queue with
* extended format.
*
* @kctx: Pointer to the kbase context within which the
* queue is to be registered.
* @reg: Pointer to the structure which contains details of the
* queue to be registered within the provided
* context, together with the extended parameter fields
* for supporting cs trace command.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_register_ex(struct kbase_context *kctx,
struct kbase_ioctl_cs_queue_register_ex *reg);
/**
* kbase_csf_queue_terminate - Terminate a GPU command queue.
*
* @kctx: Pointer to the kbase context within which the
* queue is to be terminated.
* @term: Pointer to the structure which identifies which
* queue is to be terminated.
*/
void kbase_csf_queue_terminate(struct kbase_context *kctx,
struct kbase_ioctl_cs_queue_terminate *term);
/**
* kbase_csf_free_command_stream_user_pages() - Free the resources allocated
* for a queue at the time of bind.
*
* @kctx: Address of the kbase context within which the queue was created.
* @queue: Pointer to the queue to be unlinked.
*
* This function will free the pair of physical pages allocated for a GPU
* command queue, and also release the hardware doorbell page, that were mapped
* into the process address space to enable direct submission of commands to
* the hardware. Also releases the reference taken on the queue when the mapping
* was created.
*
* If an explicit or implicit unbind was missed by the userspace then the
* mapping will persist. On process exit kernel itself will remove the mapping.
*/
void kbase_csf_free_command_stream_user_pages(struct kbase_context *kctx,
struct kbase_queue *queue);
/**
* kbase_csf_alloc_command_stream_user_pages - Allocate resources for a
* GPU command queue.
*
* @kctx: Pointer to the kbase context within which the resources
* for the queue are being allocated.
* @queue: Pointer to the queue for which to allocate resources.
*
* This function allocates a pair of User mode input/output pages for a
* GPU command queue and maps them in the shared interface segment of MCU
* firmware address space. Also reserves a hardware doorbell page for the queue.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_alloc_command_stream_user_pages(struct kbase_context *kctx,
struct kbase_queue *queue);
/**
* kbase_csf_queue_bind - Bind a GPU command queue to a queue group.
*
* @kctx: The kbase context.
* @bind: Pointer to the union which specifies a queue group and a
* queue to be bound to that group.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_bind(struct kbase_context *kctx, union kbase_ioctl_cs_queue_bind *bind);
/**
* kbase_csf_queue_unbind - Unbind a GPU command queue from a queue group
* to which it has been bound and free
* resources allocated for this queue if there
* are any.
*
* @queue: Pointer to queue to be unbound.
* @process_exit: Flag to indicate if process exit is happening.
*/
void kbase_csf_queue_unbind(struct kbase_queue *queue, bool process_exit);
/**
* kbase_csf_queue_unbind_stopped - Unbind a GPU command queue in the case
* where it was never started.
* @queue: Pointer to queue to be unbound.
*
* Variant of kbase_csf_queue_unbind() for use on error paths for cleaning up
* queues that failed to fully bind.
*/
void kbase_csf_queue_unbind_stopped(struct kbase_queue *queue);
/**
* kbase_csf_queue_kick - Schedule a GPU command queue on the firmware
*
* @kctx: The kbase context.
* @kick: Pointer to the struct which specifies the queue
* that needs to be scheduled.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_kick(struct kbase_context *kctx, struct kbase_ioctl_cs_queue_kick *kick);
/**
* kbase_csf_find_queue_group - Find the queue group corresponding
* to the indicated handle.
*
* @kctx: The kbase context under which the queue group exists.
* @group_handle: Handle for the group which uniquely identifies it within
* the context with which it was created.
*
* This function is used to find the queue group when passed a handle.
*
* Return: Pointer to a queue group on success, NULL on failure
*/
struct kbase_queue_group *kbase_csf_find_queue_group(struct kbase_context *kctx, u8 group_handle);
/**
* kbase_csf_queue_group_handle_is_valid - Find if the given queue group handle
* is valid.
*
* @kctx: The kbase context under which the queue group exists.
* @group_handle: Handle for the group which uniquely identifies it within
* the context with which it was created.
*
* This function is used to determine if the queue group handle is valid.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_group_handle_is_valid(struct kbase_context *kctx, u8 group_handle);
/**
* kbase_csf_queue_group_clear_faults - Re-enable CS Fault reporting.
*
* @kctx: Pointer to the kbase context within which the
* CS Faults for the queues has to be re-enabled.
* @clear_faults: Pointer to the structure which contains details of the
* queues for which the CS Fault reporting has to be re-enabled.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_group_clear_faults(struct kbase_context *kctx,
struct kbase_ioctl_queue_group_clear_faults *clear_faults);
/**
* kbase_csf_queue_group_create - Create a GPU command queue group.
*
* @kctx: Pointer to the kbase context within which the
* queue group is to be created.
* @create: Pointer to the structure which contains details of the
* queue group which is to be created within the
* provided kbase context.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_queue_group_create(struct kbase_context *kctx,
union kbase_ioctl_cs_queue_group_create *create);
/**
* kbase_csf_queue_group_terminate - Terminate a GPU command queue group.
*
* @kctx: Pointer to the kbase context within which the
* queue group is to be terminated.
* @group_handle: Pointer to the structure which identifies the queue
* group which is to be terminated.
*/
void kbase_csf_queue_group_terminate(struct kbase_context *kctx, u8 group_handle);
/**
* kbase_csf_term_descheduled_queue_group - Terminate a GPU command queue
* group that is not operational
* inside the scheduler.
*
* @group: Pointer to the structure which identifies the queue
* group to be terminated. The function assumes that the caller
* is sure that the given group is not operational inside the
* scheduler. If in doubt, use its alternative:
* @ref kbase_csf_queue_group_terminate().
*/
void kbase_csf_term_descheduled_queue_group(struct kbase_queue_group *group);
#if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST
/**
* kbase_csf_queue_group_suspend - Suspend a GPU command queue group
*
* @kctx: The kbase context for which the queue group is to be
* suspended.
* @sus_buf: Pointer to the structure which contains details of the
* user buffer and its kernel pinned pages.
* @group_handle: Handle for the group which uniquely identifies it within
* the context within which it was created.
*
* This function is used to suspend a queue group and copy the suspend buffer.
*
* Return: 0 on success or negative value if failed to suspend
* queue group and copy suspend buffer contents.
*/
int kbase_csf_queue_group_suspend(struct kbase_context *kctx,
struct kbase_suspend_copy_buffer *sus_buf, u8 group_handle);
#endif
/**
* kbase_csf_add_group_fatal_error - Report a fatal group error to userspace
*
* @group: GPU command queue group.
* @err_payload: Error payload to report.
*/
void kbase_csf_add_group_fatal_error(struct kbase_queue_group *const group,
struct base_gpu_queue_group_error const *const err_payload);
/**
* kbase_csf_interrupt - Handle interrupts issued by CSF firmware.
*
* @kbdev: The kbase device to handle an IRQ for
* @val: The value of JOB IRQ status register which triggered the interrupt
*/
void kbase_csf_interrupt(struct kbase_device *kbdev, u32 val);
/**
* kbase_csf_handle_csg_sync_update - Handle SYNC_UPDATE notification for the group.
*
* @kbdev: The kbase device to handle the SYNC_UPDATE interrupt.
* @group_id: CSG index.
* @group: Pointer to the GPU command queue group.
* @req: CSG_REQ register value corresponding to @group.
* @ack: CSG_ACK register value corresponding to @group.
*/
void kbase_csf_handle_csg_sync_update(struct kbase_device *const kbdev, u32 group_id,
struct kbase_queue_group *group, u32 req, u32 ack);
/**
* kbase_csf_doorbell_mapping_init - Initialize the fields that facilitates
* the update of userspace mapping of HW
* doorbell page.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* The function creates a file and allocates a dummy page to facilitate the
* update of userspace mapping to point to the dummy page instead of the real
* HW doorbell page after the suspend of queue group.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_doorbell_mapping_init(struct kbase_device *kbdev);
/**
* kbase_csf_doorbell_mapping_term - Free the dummy page & close the file used
* to update the userspace mapping of HW doorbell page
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_doorbell_mapping_term(struct kbase_device *kbdev);
/**
* kbase_csf_setup_dummy_user_reg_page - Setup the dummy page that is accessed
* instead of the User register page after
* the GPU power down.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* The function allocates a dummy page which is used to replace the User
* register page in the userspace mapping after the power down of GPU.
* On the power up of GPU, the mapping is updated to point to the real
* User register page. The mapping is used to allow access to LATEST_FLUSH
* register from userspace.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_setup_dummy_user_reg_page(struct kbase_device *kbdev);
/**
* kbase_csf_free_dummy_user_reg_page - Free the dummy page that was used
* to replace the User register page
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_free_dummy_user_reg_page(struct kbase_device *kbdev);
/**
* kbase_csf_pending_gpuq_kick_queues_init - Initialize the data used for handling
* GPU queue kicks.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_pending_gpuq_kick_queues_init(struct kbase_device *kbdev);
/**
* kbase_csf_pending_gpuq_kick_queues_term - De-initialize the data used for handling
* GPU queue kicks.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_pending_gpuq_kick_queues_term(struct kbase_device *kbdev);
/**
* kbase_csf_ring_csg_doorbell - ring the doorbell for a CSG interface.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @slot: Index of CSG interface for ringing the door-bell.
*
* The function kicks a notification on the CSG interface to firmware.
*/
void kbase_csf_ring_csg_doorbell(struct kbase_device *kbdev, int slot);
/**
* kbase_csf_ring_csg_slots_doorbell - ring the doorbell for a set of CSG
* interfaces.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @slot_bitmap: bitmap for the given slots, slot-0 on bit-0, etc.
*
* The function kicks a notification on a set of CSG interfaces to firmware.
*/
void kbase_csf_ring_csg_slots_doorbell(struct kbase_device *kbdev, u32 slot_bitmap);
/**
* kbase_csf_ring_cs_kernel_doorbell - ring the kernel doorbell for a CSI
* assigned to a GPU queue
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @csi_index: ID of the CSI assigned to the GPU queue.
* @csg_nr: Index of the CSG slot assigned to the queue
* group to which the GPU queue is bound.
* @ring_csg_doorbell: Flag to indicate if the CSG doorbell needs to be rung
* after updating the CSG_DB_REQ. So if this flag is false
* the doorbell interrupt will not be sent to FW.
* The flag is supposed be false only when the input page
* for bound GPU queues is programmed at the time of
* starting/resuming the group on a CSG slot.
*
* The function sends a doorbell interrupt notification to the firmware for
* a CSI assigned to a GPU queue.
*/
void kbase_csf_ring_cs_kernel_doorbell(struct kbase_device *kbdev, int csi_index, int csg_nr,
bool ring_csg_doorbell);
/**
* kbase_csf_ring_cs_user_doorbell - ring the user doorbell allocated for a
* queue.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @queue: Pointer to the queue for ringing the door-bell.
*
* The function kicks a notification to the firmware on the doorbell assigned
* to the queue.
*/
void kbase_csf_ring_cs_user_doorbell(struct kbase_device *kbdev, struct kbase_queue *queue);
/**
* kbase_csf_active_queue_groups_reset - Reset the state of all active GPU
* command queue groups associated with the context.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @kctx: The kbase context.
*
* This function will iterate through all the active/scheduled GPU command
* queue groups associated with the context, deschedule and mark them as
* terminated (which will then lead to unbinding of all the queues bound to
* them) and also no more work would be allowed to execute for them.
*
* This is similar to the action taken in response to an unexpected OoM event.
*/
void kbase_csf_active_queue_groups_reset(struct kbase_device *kbdev, struct kbase_context *kctx);
/**
* kbase_csf_priority_check - Check the priority requested
*
* @kbdev: Device pointer
* @req_priority: Requested priority
*
* This will determine whether the requested priority can be satisfied.
*
* Return: The same or lower priority than requested.
*/
u8 kbase_csf_priority_check(struct kbase_device *kbdev, u8 req_priority);
extern const u8 kbasep_csf_queue_group_priority_to_relative[BASE_QUEUE_GROUP_PRIORITY_COUNT];
extern const u8 kbasep_csf_relative_to_queue_group_priority[KBASE_QUEUE_GROUP_PRIORITY_COUNT];
/**
* kbase_csf_priority_relative_to_queue_group_priority - Convert relative to base priority
*
* @priority: kbase relative priority
*
* This will convert the monotonically increasing realtive priority to the
* fixed base priority list.
*
* Return: base_queue_group_priority priority.
*/
static inline u8 kbase_csf_priority_relative_to_queue_group_priority(u8 priority)
{
if (priority >= KBASE_QUEUE_GROUP_PRIORITY_COUNT)
priority = KBASE_QUEUE_GROUP_PRIORITY_LOW;
return kbasep_csf_relative_to_queue_group_priority[priority];
}
/**
* kbase_csf_priority_queue_group_priority_to_relative - Convert base priority to relative
*
* @priority: base_queue_group_priority priority
*
* This will convert the fixed base priority list to monotonically increasing realtive priority.
*
* Return: kbase relative priority.
*/
static inline u8 kbase_csf_priority_queue_group_priority_to_relative(u8 priority)
{
/* Apply low priority in case of invalid priority */
if (priority >= BASE_QUEUE_GROUP_PRIORITY_COUNT)
priority = BASE_QUEUE_GROUP_PRIORITY_LOW;
return kbasep_csf_queue_group_priority_to_relative[priority];
}
/**
* kbase_csf_ktrace_gpu_cycle_cnt - Wrapper to retrieve the GPU cycle counter
* value for Ktrace purpose.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* This function is just a wrapper to retrieve the GPU cycle counter value, to
* avoid any overhead on Release builds where Ktrace is disabled by default.
*
* Return: Snapshot of the GPU cycle count register.
*/
static inline u64 kbase_csf_ktrace_gpu_cycle_cnt(struct kbase_device *kbdev)
{
#if KBASE_KTRACE_ENABLE
return kbase_backend_get_cycle_cnt(kbdev);
#else
CSTD_UNUSED(kbdev);
return 0;
#endif
}
/**
* kbase_csf_process_queue_kick() - Process a pending kicked GPU command queue.
*
* @queue: Pointer to the queue to process.
*
* This function starts the pending queue, for which the work
* was previously submitted via ioctl call from application thread.
* If the queue is already scheduled and resident, it will be started
* right away, otherwise once the group is made resident.
*/
void kbase_csf_process_queue_kick(struct kbase_queue *queue);
/**
* kbase_csf_process_protm_event_request - Handle protected mode switch request
*
* @group: The group to handle protected mode request
*
* Request to switch to protected mode.
*/
void kbase_csf_process_protm_event_request(struct kbase_queue_group *group);
/**
* kbase_csf_glb_fatal_worker - Worker function for handling GLB FATAL error
*
* @data: Pointer to a work_struct embedded in kbase device.
*
* Handle the GLB fatal error
*/
void kbase_csf_glb_fatal_worker(struct work_struct *const data);
#endif /* _KBASE_CSF_H_ */

View file

@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_csf_cpu_queue.h"
#include "mali_kbase_csf_util.h"
#include <mali_kbase.h>
#include <asm/atomic.h>
void kbase_csf_cpu_queue_init(struct kbase_context *kctx)
{
if (WARN_ON(!kctx))
return;
kctx->csf.cpu_queue.buffer = NULL;
kctx->csf.cpu_queue.buffer_size = 0;
atomic_set(&kctx->csf.cpu_queue.dump_req_status, BASE_CSF_CPU_QUEUE_DUMP_COMPLETE);
}
bool kbase_csf_cpu_queue_read_dump_req(struct kbase_context *kctx,
struct base_csf_notification *req)
{
if (atomic_cmpxchg(&kctx->csf.cpu_queue.dump_req_status, BASE_CSF_CPU_QUEUE_DUMP_ISSUED,
BASE_CSF_CPU_QUEUE_DUMP_PENDING) != BASE_CSF_CPU_QUEUE_DUMP_ISSUED) {
return false;
}
req->type = BASE_CSF_NOTIFICATION_CPU_QUEUE_DUMP;
return true;
}
bool kbase_csf_cpu_queue_dump_needed(struct kbase_context *kctx)
{
return (atomic_read(&kctx->csf.cpu_queue.dump_req_status) ==
BASE_CSF_CPU_QUEUE_DUMP_ISSUED);
}
int kbase_csf_cpu_queue_dump_buffer(struct kbase_context *kctx, u64 buffer, size_t buf_size)
{
size_t alloc_size = buf_size;
char *dump_buffer;
if (!buffer || !buf_size)
return 0;
if (alloc_size > KBASE_MEM_ALLOC_MAX_SIZE)
return -EINVAL;
alloc_size = (alloc_size + PAGE_SIZE) & ~(PAGE_SIZE - 1);
dump_buffer = kzalloc(alloc_size, GFP_KERNEL);
if (!dump_buffer)
return -ENOMEM;
WARN_ON(kctx->csf.cpu_queue.buffer != NULL);
if (copy_from_user(dump_buffer, u64_to_user_ptr(buffer), buf_size)) {
kfree(dump_buffer);
return -EFAULT;
}
mutex_lock(&kctx->csf.lock);
kfree(kctx->csf.cpu_queue.buffer);
if (atomic_read(&kctx->csf.cpu_queue.dump_req_status) == BASE_CSF_CPU_QUEUE_DUMP_PENDING) {
kctx->csf.cpu_queue.buffer = dump_buffer;
kctx->csf.cpu_queue.buffer_size = buf_size;
complete_all(&kctx->csf.cpu_queue.dump_cmp);
} else
kfree(dump_buffer);
mutex_unlock(&kctx->csf.lock);
return 0;
}
int kbasep_csf_cpu_queue_dump_print(struct kbase_context *kctx, struct kbasep_printer *kbpr)
{
mutex_lock(&kctx->csf.lock);
if (atomic_read(&kctx->csf.cpu_queue.dump_req_status) != BASE_CSF_CPU_QUEUE_DUMP_COMPLETE) {
kbasep_print(kbpr, "Dump request already started! (try again)\n");
mutex_unlock(&kctx->csf.lock);
return -EBUSY;
}
atomic_set(&kctx->csf.cpu_queue.dump_req_status, BASE_CSF_CPU_QUEUE_DUMP_ISSUED);
init_completion(&kctx->csf.cpu_queue.dump_cmp);
kbase_event_wakeup(kctx);
mutex_unlock(&kctx->csf.lock);
kbasep_print(kbpr, "CPU Queues table (version:v" __stringify(
MALI_CSF_CPU_QUEUE_DUMP_VERSION) "):\n");
wait_for_completion_timeout(&kctx->csf.cpu_queue.dump_cmp, msecs_to_jiffies(3000));
mutex_lock(&kctx->csf.lock);
if (kctx->csf.cpu_queue.buffer) {
WARN_ON(atomic_read(&kctx->csf.cpu_queue.dump_req_status) !=
BASE_CSF_CPU_QUEUE_DUMP_PENDING);
/* The CPU queue dump is returned as a single formatted string */
kbasep_puts(kbpr, kctx->csf.cpu_queue.buffer);
kbasep_puts(kbpr, "\n");
kfree(kctx->csf.cpu_queue.buffer);
kctx->csf.cpu_queue.buffer = NULL;
kctx->csf.cpu_queue.buffer_size = 0;
} else
kbasep_print(kbpr, "Dump error! (time out)\n");
atomic_set(&kctx->csf.cpu_queue.dump_req_status, BASE_CSF_CPU_QUEUE_DUMP_COMPLETE);
mutex_unlock(&kctx->csf.lock);
return 0;
}

View file

@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_CPU_QUEUE_H_
#define _KBASE_CSF_CPU_QUEUE_H_
#include <linux/types.h>
/* Forward declaration */
struct base_csf_notification;
struct kbase_context;
struct kbasep_printer;
#define MALI_CSF_CPU_QUEUE_DUMP_VERSION 0
/* CPU queue dump status */
/* Dumping is done or no dumping is in progress. */
#define BASE_CSF_CPU_QUEUE_DUMP_COMPLETE 0
/* Dumping request is pending. */
#define BASE_CSF_CPU_QUEUE_DUMP_PENDING 1
/* Dumping request is issued to Userspace */
#define BASE_CSF_CPU_QUEUE_DUMP_ISSUED 2
/**
* kbase_csf_cpu_queue_init() - Initialise cpu queue handling per context cpu queue(s)
*
* @kctx: The kbase_context
*/
void kbase_csf_cpu_queue_init(struct kbase_context *kctx);
/**
* kbase_csf_cpu_queue_read_dump_req() - Read cpu queue dump request event
*
* @kctx: The kbase_context which cpu queue dumped belongs to.
* @req: Notification with cpu queue dump request.
*
* Return: true if needs CPU queue dump, or false otherwise.
*/
bool kbase_csf_cpu_queue_read_dump_req(struct kbase_context *kctx,
struct base_csf_notification *req);
/**
* kbase_csf_cpu_queue_dump_needed() - Check the requirement for cpu queue dump
*
* @kctx: The kbase_context which cpu queue dumped belongs to.
*
* Return: true if it needs cpu queue dump, or false otherwise.
*/
bool kbase_csf_cpu_queue_dump_needed(struct kbase_context *kctx);
/**
* kbase_csf_cpu_queue_dump_buffer() - dump buffer containing cpu queue information
*
* @kctx: The kbase_context which cpu queue dumped belongs to.
* @buffer: Buffer containing the cpu queue information.
* @buf_size: Buffer size.
*
* Return: Return 0 for dump successfully, or error code.
*/
int kbase_csf_cpu_queue_dump_buffer(struct kbase_context *kctx, u64 buffer, size_t buf_size);
/**
* kbasep_csf_cpu_queue_dump_print() - Dump cpu queue information to file
*
* @kctx: The kbase_context which cpu queue dumped belongs to.
* @kbpr: Pointer to printer instance.
*
* Return: Return 0 for dump successfully, or error code.
*/
int kbasep_csf_cpu_queue_dump_print(struct kbase_context *kctx, struct kbasep_printer *kbpr);
#endif /* _KBASE_CSF_CPU_QUEUE_H_ */

View file

@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_csf_cpu_queue_debugfs.h"
#if IS_ENABLED(CONFIG_DEBUG_FS)
#include "mali_kbase_csf_cpu_queue.h"
#include "mali_kbase_csf_util.h"
#include <mali_kbase.h>
#include <linux/seq_file.h>
/**
* kbasep_csf_cpu_queue_debugfs_show() - Print cpu queue information for per context
*
* @file: The seq_file for printing to
* @data: The debugfs dentry private data, a pointer to kbase_context
*
* Return: Negative error code or 0 on success.
*/
static int kbasep_csf_cpu_queue_debugfs_show(struct seq_file *file, void *data)
{
struct kbasep_printer *kbpr;
struct kbase_context *const kctx = file->private;
int ret = -EINVAL;
CSTD_UNUSED(data);
kbpr = kbasep_printer_file_init(file);
if (kbpr != NULL) {
ret = kbasep_csf_cpu_queue_dump_print(kctx, kbpr);
kbasep_printer_term(kbpr);
}
return ret;
}
static int kbasep_csf_cpu_queue_debugfs_open(struct inode *in, struct file *file)
{
return single_open(file, kbasep_csf_cpu_queue_debugfs_show, in->i_private);
}
static const struct file_operations kbasep_csf_cpu_queue_debugfs_fops = {
.open = kbasep_csf_cpu_queue_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void kbase_csf_cpu_queue_debugfs_init(struct kbase_context *kctx)
{
struct dentry *file;
if (WARN_ON(!kctx || IS_ERR_OR_NULL(kctx->kctx_dentry)))
return;
file = debugfs_create_file("cpu_queue", 0444, kctx->kctx_dentry, kctx,
&kbasep_csf_cpu_queue_debugfs_fops);
if (IS_ERR_OR_NULL(file)) {
dev_warn(kctx->kbdev->dev, "Unable to create cpu queue debugfs entry");
}
}
#else
/*
* Stub functions for when debugfs is disabled
*/
void kbase_csf_cpu_queue_debugfs_init(struct kbase_context *kctx)
{
}
#endif /* CONFIG_DEBUG_FS */

View file

@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_CPU_QUEUE_DEBUGFS_H_
#define _KBASE_CSF_CPU_QUEUE_DEBUGFS_H_
/* Forward declaration */
struct kbase_context;
/**
* kbase_csf_cpu_queue_debugfs_init() - Create a debugfs entry for per context cpu queue(s)
*
* @kctx: The kbase_context for which to create the debugfs entry
*/
void kbase_csf_cpu_queue_debugfs_init(struct kbase_context *kctx);
#endif /* _KBASE_CSF_CPU_QUEUE_DEBUGFS_H_ */

View file

@ -0,0 +1,671 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2023-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_csf_csg.h"
#include "mali_kbase_csf_scheduler.h"
#include "mali_kbase_csf_util.h"
#include <mali_kbase.h>
#include <linux/delay.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
/* Wait time to be used cumulatively for all the CSG slots.
* Since scheduler lock is held when STATUS_UPDATE request is sent, there won't be
* any other Host request pending on the FW side and usually FW would be responsive
* to the Doorbell IRQs as it won't do any polling for a long time and also it won't
* have to wait for any HW state transition to complete for publishing the status.
* So it is reasonable to expect that handling of STATUS_UPDATE request would be
* relatively very quick.
*/
#define STATUS_UPDATE_WAIT_TIMEOUT_NS 500
/* Number of nearby commands around the "cmd_ptr" of GPU queues.
*
* [cmd_ptr - MAX_NR_NEARBY_INSTR, cmd_ptr + MAX_NR_NEARBY_INSTR].
*/
#define MAX_NR_NEARBY_INSTR 32
/* The bitmask of CSG slots for which the STATUS_UPDATE request completed.
* The access to it is serialized with scheduler lock, so at a time it would
* get used either for "active_groups" or per context "groups".
*/
static DECLARE_BITMAP(csg_slots_status_updated, MAX_SUPPORTED_CSGS);
/* String header for dumping cs user I/O status information */
#define KBASEP_CSF_CSG_DUMP_CS_HEADER_USER_IO \
"Bind Idx, Ringbuf addr, Size, Prio, Insert offset, Extract offset, Active, Doorbell\n"
/* String representation of WAITING */
#define WAITING "Waiting"
/* String representation of NOT_WAITING */
#define NOT_WAITING "Not waiting"
/**
* csg_slot_status_update_finish() - Complete STATUS_UPDATE request for a group slot.
*
* @kbdev: Pointer to kbase device.
* @csg_nr: The group slot number.
*
* Return: True if completed, false otherwise.
*/
static bool csg_slot_status_update_finish(struct kbase_device *kbdev, u32 csg_nr)
{
u32 csg_req;
u32 csg_ack;
csg_req = kbase_csf_fw_io_group_input_read(&kbdev->csf.fw_io, csg_nr, CSG_REQ);
csg_ack = kbase_csf_fw_io_group_read(&kbdev->csf.fw_io, csg_nr, CSG_ACK);
return !((csg_req ^ csg_ack) & CSG_REQ_STATUS_UPDATE_MASK);
}
/**
* csg_slots_status_update_finish() - Complete STATUS_UPDATE requests for all group slots.
*
* @kbdev: Pointer to kbase device.
* @slots_mask: The group slots mask.
*
* Return: True if completed, false otherwise.
*/
static bool csg_slots_status_update_finish(struct kbase_device *kbdev,
const unsigned long *slots_mask)
{
const u32 max_csg_slots = kbdev->csf.global_iface.group_num;
bool changed = false;
u32 csg_nr;
lockdep_assert_held(&kbdev->csf.scheduler.lock);
for_each_set_bit(csg_nr, slots_mask, max_csg_slots) {
if (csg_slot_status_update_finish(kbdev, csg_nr)) {
set_bit(csg_nr, csg_slots_status_updated);
changed = true;
}
}
return changed;
}
/**
* wait_csg_slots_status_update_finish() - Wait completion of STATUS_UPDATE requests for all
* group slots.
*
* @kbdev: Pointer to kbase device.
* @slots_mask: The group slots mask.
*/
static void wait_csg_slots_status_update_finish(struct kbase_device *kbdev,
unsigned long *slots_mask)
{
const u32 max_csg_slots = kbdev->csf.global_iface.group_num;
long remaining = kbase_csf_timeout_in_jiffies(STATUS_UPDATE_WAIT_TIMEOUT_NS);
lockdep_assert_held(&kbdev->csf.scheduler.lock);
bitmap_zero(csg_slots_status_updated, max_csg_slots);
while (!bitmap_empty(slots_mask, max_csg_slots) && remaining) {
remaining = kbase_csf_fw_io_wait_event_timeout(
&kbdev->csf.fw_io, kbdev->csf.event_wait,
csg_slots_status_update_finish(kbdev, slots_mask), remaining);
if (likely(remaining > 0)) {
bitmap_andnot(slots_mask, slots_mask, csg_slots_status_updated,
max_csg_slots);
} else if (!remaining) {
dev_warn(kbdev->dev, "STATUS_UPDATE request timed out for slots 0x%lx",
slots_mask[0]);
}
}
}
/**
* blocked_reason_to_string() - Convert blocking reason id to a string
*
* @reason_id: blocked_reason
*
* Return: Suitable string
*/
static const char *blocked_reason_to_string(u32 reason_id)
{
/* possible blocking reasons of a cs */
static const char *const cs_blocked_reason[] = {
[CS_STATUS_BLOCKED_REASON_REASON_UNBLOCKED] = "UNBLOCKED",
[CS_STATUS_BLOCKED_REASON_REASON_WAIT] = "WAIT",
[CS_STATUS_BLOCKED_REASON_REASON_PROGRESS_WAIT] = "PROGRESS_WAIT",
[CS_STATUS_BLOCKED_REASON_REASON_SYNC_WAIT] = "SYNC_WAIT",
[CS_STATUS_BLOCKED_REASON_REASON_DEFERRED] = "DEFERRED",
[CS_STATUS_BLOCKED_REASON_REASON_RESOURCE] = "RESOURCE",
[CS_STATUS_BLOCKED_REASON_REASON_FLUSH] = "FLUSH"
};
if (WARN_ON(reason_id >= ARRAY_SIZE(cs_blocked_reason)))
return "UNKNOWN_BLOCKED_REASON_ID";
return cs_blocked_reason[reason_id];
}
/**
* sb_source_supported() - Check SB_SOURCE GLB version support
*
* @glb_version: The GLB version
*
* Return: False or true on success.
*/
static bool sb_source_supported(u32 glb_version)
{
bool supported = false;
if (((GLB_VERSION_MAJOR_GET(glb_version) == 3) &&
(GLB_VERSION_MINOR_GET(glb_version) >= 5)) ||
((GLB_VERSION_MAJOR_GET(glb_version) == 2) &&
(GLB_VERSION_MINOR_GET(glb_version) >= 6)) ||
((GLB_VERSION_MAJOR_GET(glb_version) == 1) &&
(GLB_VERSION_MINOR_GET(glb_version) >= 3)))
supported = true;
return supported;
}
/**
* kbasep_csf_csg_active_dump_cs_status_wait() - Dump active queue sync status information.
*
* @kctx: Pointer to kbase context.
* @kbpr: Pointer to printer instance.
* @glb_version: The GLB version.
* @wait_status: The CS_STATUS_WAIT value.
* @wait_sync_value: The queue's cached sync value.
* @wait_sync_live_value: The queue's sync object current value.
* @wait_sync_pointer: The queue's sync object pointer.
* @sb_status: The CS_STATUS_SCOREBOARDS value.
* @blocked_reason: The CS_STATUS_BLCOKED_REASON value.
*/
static void kbasep_csf_csg_active_dump_cs_status_wait(struct kbase_context *kctx,
struct kbasep_printer *kbpr, u32 glb_version,
u32 wait_status, u32 wait_sync_value,
u64 wait_sync_live_value,
u64 wait_sync_pointer, u32 sb_status,
u32 blocked_reason)
{
kbasep_print(kbpr, "SB_MASK: %d\n", CS_STATUS_WAIT_SB_MASK_GET(wait_status));
if (sb_source_supported(glb_version))
kbasep_print(kbpr, "SB_SOURCE: %d\n", CS_STATUS_WAIT_SB_SOURCE_GET(wait_status));
{
kbasep_print(kbpr, "PROGRESS_WAIT: %s\n",
CS_STATUS_WAIT_PROGRESS_WAIT_GET(wait_status) ? WAITING : NOT_WAITING);
}
kbasep_print(kbpr, "PROTM_PEND: %s\n",
CS_STATUS_WAIT_PROTM_PEND_GET(wait_status) ? WAITING : NOT_WAITING);
kbasep_print(kbpr, "SYNC_WAIT: %s\n",
CS_STATUS_WAIT_SYNC_WAIT_GET(wait_status) ? WAITING : NOT_WAITING);
kbasep_print(kbpr, "WAIT_CONDITION: %s\n",
CS_STATUS_WAIT_SYNC_WAIT_CONDITION_GET(wait_status) ? "greater than" :
"less or equal");
kbasep_print(kbpr, "SYNC_POINTER: 0x%llx\n", wait_sync_pointer);
kbasep_print(kbpr, "SYNC_VALUE: %d\n", wait_sync_value);
kbasep_print(kbpr, "SYNC_LIVE_VALUE: 0x%016llx\n", wait_sync_live_value);
kbasep_print(kbpr, "SB_STATUS: %u\n", CS_STATUS_SCOREBOARDS_NONZERO_GET(sb_status));
kbasep_print(kbpr, "BLOCKED_REASON: %s\n",
blocked_reason_to_string(CS_STATUS_BLOCKED_REASON_REASON_GET(blocked_reason)));
}
/**
* kbasep_csf_csg_active_dump_cs_trace() - Dump active queue CS trace information.
*
* @kctx: Pointer to kbase context.
* @kbpr: Pointer to printer instance.
* @group_id: CSG index.
* @stream_id: CS index.
*/
static void kbasep_csf_csg_active_dump_cs_trace(struct kbase_context *kctx,
struct kbasep_printer *kbpr, u32 group_id,
u32 stream_id)
{
u32 val;
u64 addr;
val = kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_BUFFER_BASE_LO);
addr = ((u64)kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_BUFFER_BASE_HI)
<< 32) |
val;
val = kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_BUFFER_SIZE);
kbasep_print(kbpr, "CS_TRACE_BUF_ADDR: 0x%16llx, SIZE: %u\n", addr, val);
/* Write offset variable address (pointer) */
val = kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_BUFFER_OFFSET_POINTER_LO);
addr = ((u64)kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_BUFFER_OFFSET_POINTER_HI)
<< 32) |
val;
kbasep_print(kbpr, "CS_TRACE_BUF_OFFSET_PTR: 0x%16llx\n", addr);
/* EVENT_SIZE and EVENT_STATEs */
val = kbase_csf_fw_io_stream_input_read(&kctx->kbdev->csf.fw_io, group_id, stream_id,
CS_INSTR_CONFIG);
kbasep_print(kbpr, "TRACE_EVENT_SIZE: 0x%x, TRACE_EVENT_STATES 0x%x\n",
CS_INSTR_CONFIG_EVENT_SIZE_GET(val), CS_INSTR_CONFIG_EVENT_STATE_GET(val));
}
/**
* kbasep_csf_read_cmdbuff_value() - Read a command from a queue offset.
*
* @queue: Address of a GPU command queue to examine.
* @cmdbuff_offset: GPU address offset in queue's memory buffer.
*
* Return: Encoded CSF command (64-bit)
*/
static u64 kbasep_csf_read_cmdbuff_value(struct kbase_queue *queue, u32 cmdbuff_offset)
{
u64 page_off = cmdbuff_offset >> PAGE_SHIFT;
u64 offset_within_page = cmdbuff_offset & ~PAGE_MASK;
struct page *page = as_page(queue->queue_reg->gpu_alloc->pages[page_off]);
u64 *cmdbuff = vmap(&page, 1, VM_MAP, pgprot_noncached(PAGE_KERNEL));
u64 value;
if (!cmdbuff) {
struct kbase_context *kctx = queue->kctx;
dev_info(kctx->kbdev->dev, "%s failed to map the buffer page for read a command!",
__func__);
/* Return an alternative 0 for dumping operation*/
value = 0;
} else {
value = cmdbuff[offset_within_page / sizeof(u64)];
vunmap(cmdbuff);
}
return value;
}
/**
* kbasep_csf_csg_active_dump_cs_status_cmd_ptr() - Dump CMD_PTR information and nearby commands.
*
* @kbpr: Pointer to printer instance.
* @queue: Address of a GPU command queue to examine.
* @cmd_ptr: CMD_PTR address.
*/
static void kbasep_csf_csg_active_dump_cs_status_cmd_ptr(struct kbasep_printer *kbpr,
struct kbase_queue *queue, u64 cmd_ptr)
{
u64 cmd_ptr_offset;
u64 cursor, end_cursor, instr;
u32 nr_nearby_instr_size;
struct kbase_va_region *reg;
kbase_gpu_vm_lock(queue->kctx);
reg = kbase_region_tracker_find_region_enclosing_address(queue->kctx, cmd_ptr);
if (reg && !(reg->flags & KBASE_REG_FREE) && (reg->flags & KBASE_REG_CPU_RD) &&
(reg->gpu_alloc->type == KBASE_MEM_TYPE_NATIVE)) {
kbasep_print(kbpr, "CMD_PTR region nr_pages: %zu\n", reg->nr_pages);
nr_nearby_instr_size = MAX_NR_NEARBY_INSTR * sizeof(u64);
cmd_ptr_offset = cmd_ptr - queue->base_addr;
cursor = (cmd_ptr_offset > nr_nearby_instr_size) ?
cmd_ptr_offset - nr_nearby_instr_size :
0;
end_cursor = cmd_ptr_offset + nr_nearby_instr_size;
if (end_cursor > queue->size)
end_cursor = queue->size;
kbasep_print(kbpr,
"queue:GPU-%u-%u-%u at:0x%.16llx cmd_ptr:0x%.16llx "
"dump_begin:0x%.16llx dump_end:0x%.16llx\n",
queue->kctx->id, queue->group->handle, queue->csi_index,
(queue->base_addr + cursor), cmd_ptr, (queue->base_addr + cursor),
(queue->base_addr + end_cursor));
while ((cursor < end_cursor)) {
instr = kbasep_csf_read_cmdbuff_value(queue, (u32)cursor);
if (instr != 0)
kbasep_print(kbpr,
"queue:GPU-%u-%u-%u at:0x%.16llx cmd:0x%.16llx\n",
queue->kctx->id, queue->group->handle,
queue->csi_index, (queue->base_addr + cursor), instr);
cursor += sizeof(u64);
}
}
kbase_gpu_vm_unlock(queue->kctx);
}
/**
* kbasep_csf_csg_active_dump_queue() - Dump GPU command queue debug information.
*
* @kbpr: Pointer to printer instance.
* @queue: Address of a GPU command queue to examine
*/
static void kbasep_csf_csg_active_dump_queue(struct kbasep_printer *kbpr, struct kbase_queue *queue)
{
u64 *addr;
u32 *addr32;
u64 cs_extract;
u64 cs_insert;
u32 cs_active;
u64 wait_sync_pointer;
u32 wait_status, wait_sync_value;
u32 sb_status;
u32 blocked_reason;
struct kbase_vmap_struct *mapping;
u64 *evt;
u64 wait_sync_live_value;
u32 glb_version;
u64 cmd_ptr;
if (!queue)
return;
glb_version = queue->kctx->kbdev->csf.global_iface.version;
if (WARN_ON(queue->csi_index == KBASEP_IF_NR_INVALID || !queue->group))
return;
addr = queue->user_io_addr;
cs_insert = addr[CS_INSERT_LO / sizeof(*addr)];
addr = queue->user_io_addr + PAGE_SIZE / sizeof(*addr);
cs_extract = addr[CS_EXTRACT_LO / sizeof(*addr)];
addr32 = (u32 *)(queue->user_io_addr + PAGE_SIZE / sizeof(*addr));
cs_active = addr32[CS_ACTIVE / sizeof(*addr32)];
kbasep_puts(kbpr, KBASEP_CSF_CSG_DUMP_CS_HEADER_USER_IO);
kbasep_print(kbpr, "%8d, %16llx, %8x, %4u, %16llx, %16llx, %6u, %8d\n", queue->csi_index,
queue->base_addr, queue->size, queue->priority, cs_insert, cs_extract,
cs_active, queue->doorbell_nr);
/* Print status information for blocked group waiting for sync object. For on-slot queues,
* if cs_trace is enabled, dump the interface's cs_trace configuration.
*/
if (kbase_csf_scheduler_group_get_slot(queue->group) < 0) {
kbasep_print(kbpr, "SAVED_CMD_PTR: 0x%llx\n", queue->saved_cmd_ptr);
if (CS_STATUS_WAIT_SYNC_WAIT_GET(queue->status_wait)) {
wait_status = queue->status_wait;
wait_sync_value = queue->sync_value;
wait_sync_pointer = queue->sync_ptr;
sb_status = queue->sb_status;
blocked_reason = queue->blocked_reason;
evt = (u64 *)kbase_phy_alloc_mapping_get(queue->kctx, wait_sync_pointer,
&mapping);
if (evt) {
wait_sync_live_value = evt[0];
kbase_phy_alloc_mapping_put(queue->kctx, mapping);
} else {
wait_sync_live_value = U64_MAX;
}
kbasep_csf_csg_active_dump_cs_status_wait(
queue->kctx, kbpr, glb_version, wait_status, wait_sync_value,
wait_sync_live_value, wait_sync_pointer, sb_status, blocked_reason);
}
kbasep_csf_csg_active_dump_cs_status_cmd_ptr(kbpr, queue, queue->saved_cmd_ptr);
} else {
struct kbase_device *kbdev = queue->group->kctx->kbdev;
u32 group_id = queue->group->csg_nr;
u32 stream_id = queue->csi_index;
u32 req_res;
cmd_ptr = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_CMD_PTR_LO);
cmd_ptr |= (u64)kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_CMD_PTR_HI)
<< 32;
req_res = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_REQ_RESOURCE);
kbasep_print(kbpr, "CMD_PTR: 0x%llx\n", cmd_ptr);
kbasep_print(kbpr, "REQ_RESOURCE [COMPUTE]: %d\n",
CS_STATUS_REQ_RESOURCE_COMPUTE_RESOURCES_GET(req_res));
kbasep_print(kbpr, "REQ_RESOURCE [FRAGMENT]: %d\n",
CS_STATUS_REQ_RESOURCE_FRAGMENT_RESOURCES_GET(req_res));
kbasep_print(kbpr, "REQ_RESOURCE [TILER]: %d\n",
CS_STATUS_REQ_RESOURCE_TILER_RESOURCES_GET(req_res));
kbasep_print(kbpr, "REQ_RESOURCE [IDVS]: %d\n",
CS_STATUS_REQ_RESOURCE_IDVS_RESOURCES_GET(req_res));
wait_status = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_WAIT);
wait_sync_value = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id,
stream_id, CS_STATUS_WAIT_SYNC_VALUE);
wait_sync_pointer = kbase_csf_fw_io_stream_read(
&kbdev->csf.fw_io, group_id, stream_id, CS_STATUS_WAIT_SYNC_POINTER_LO);
wait_sync_pointer |=
(u64)kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_WAIT_SYNC_POINTER_HI)
<< 32;
sb_status = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_SCOREBOARDS);
blocked_reason = kbase_csf_fw_io_stream_read(&kbdev->csf.fw_io, group_id, stream_id,
CS_STATUS_BLOCKED_REASON);
evt = (u64 *)kbase_phy_alloc_mapping_get(queue->kctx, wait_sync_pointer, &mapping);
if (evt) {
wait_sync_live_value = evt[0];
kbase_phy_alloc_mapping_put(queue->kctx, mapping);
} else {
wait_sync_live_value = U64_MAX;
}
kbasep_csf_csg_active_dump_cs_status_wait(queue->kctx, kbpr, glb_version,
wait_status, wait_sync_value,
wait_sync_live_value, wait_sync_pointer,
sb_status, blocked_reason);
/* Dealing with cs_trace */
if (kbase_csf_scheduler_queue_has_trace(queue))
kbasep_csf_csg_active_dump_cs_trace(queue->kctx, kbpr, group_id, stream_id);
else
kbasep_print(kbpr, "NO CS_TRACE\n");
kbasep_csf_csg_active_dump_cs_status_cmd_ptr(kbpr, queue, cmd_ptr);
}
}
/**
* kbasep_csf_csg_active_dump_group() - Dump an active group.
*
* @kbpr: Pointer to printer instance.
* @group: GPU group.
*/
static void kbasep_csf_csg_active_dump_group(struct kbasep_printer *kbpr,
struct kbase_queue_group *const group)
{
if (kbase_csf_scheduler_group_get_slot(group) >= 0) {
struct kbase_device *const kbdev = group->kctx->kbdev;
u32 ep_c, ep_r;
char exclusive;
char idle = 'N';
u8 slot_priority = kbdev->csf.scheduler.csg_slots[group->csg_nr].priority;
ep_c = kbase_csf_fw_io_group_read(&kbdev->csf.fw_io, group->csg_nr,
CSG_STATUS_EP_CURRENT);
ep_r = kbase_csf_fw_io_group_read(&kbdev->csf.fw_io, group->csg_nr,
CSG_STATUS_EP_REQ);
if (CSG_STATUS_EP_REQ_EXCLUSIVE_COMPUTE_GET(ep_r))
exclusive = 'C';
else if (CSG_STATUS_EP_REQ_EXCLUSIVE_FRAGMENT_GET(ep_r))
exclusive = 'F';
else
exclusive = '0';
if (kbase_csf_fw_io_group_read(&kbdev->csf.fw_io, group->csg_nr, CSG_STATUS_STATE) &
CSG_STATUS_STATE_IDLE_MASK)
idle = 'Y';
if (!test_bit(group->csg_nr, csg_slots_status_updated)) {
kbasep_print(kbpr, "*** Warn: Timed out for STATUS_UPDATE on slot %d\n",
group->csg_nr);
kbasep_print(kbpr, "*** The following group-record is likely stale\n");
}
kbasep_print(
kbpr,
"GroupID, CSG NR, CSG Prio, Run State, Priority, C_EP(Alloc/Req),"
" F_EP(Alloc/Req), T_EP(Alloc/Req), Exclusive, Idle\n");
kbasep_print(
kbpr,
"%7d, %6d, %8d, %9d, %8d, %11d/%3d, %11d/%3d, %11d/%3d, %9c, %4c\n",
group->handle, group->csg_nr, slot_priority, group->run_state,
group->priority, CSG_STATUS_EP_CURRENT_COMPUTE_EP_GET(ep_c),
CSG_STATUS_EP_REQ_COMPUTE_EP_GET(ep_r),
CSG_STATUS_EP_CURRENT_FRAGMENT_EP_GET(ep_c),
CSG_STATUS_EP_REQ_FRAGMENT_EP_GET(ep_r),
CSG_STATUS_EP_CURRENT_TILER_EP_GET(ep_c),
CSG_STATUS_EP_REQ_TILER_EP_GET(ep_r), exclusive, idle);
} else {
kbasep_print(kbpr, "GroupID, CSG NR, Run State, Priority\n");
kbasep_print(kbpr, "%7d, %6d, %9d, %8d\n", group->handle, group->csg_nr,
group->run_state, group->priority);
}
if (group->run_state != KBASE_CSF_GROUP_TERMINATED) {
unsigned int i;
kbasep_print(kbpr, "Bound queues:\n");
for (i = 0; i < MAX_SUPPORTED_STREAMS_PER_GROUP; i++)
kbasep_csf_csg_active_dump_queue(kbpr, group->bound_queues[i]);
}
}
void kbase_csf_csg_update_status(struct kbase_device *kbdev)
{
u32 max_csg_slots = kbdev->csf.global_iface.group_num;
DECLARE_BITMAP(used_csgs, MAX_SUPPORTED_CSGS) = { 0 };
u32 csg_nr;
unsigned long flags, fw_io_flags;
lockdep_assert_held(&kbdev->csf.scheduler.lock);
/* Global doorbell ring for CSG STATUS_UPDATE request or User doorbell
* ring for Extract offset update, shall not be made when MCU has been
* put to sleep otherwise it will undesirably make MCU exit the sleep
* state. Also it isn't really needed as FW will implicitly update the
* status of all on-slot groups when MCU sleep request is sent to it.
*/
if (kbdev->csf.scheduler.state == SCHED_SLEEPING) {
/* Wait for the MCU sleep request to complete. */
kbase_pm_wait_for_desired_state(kbdev);
bitmap_copy(csg_slots_status_updated, kbdev->csf.scheduler.csg_inuse_bitmap,
max_csg_slots);
return;
}
for (csg_nr = 0; csg_nr < max_csg_slots; csg_nr++) {
struct kbase_queue_group *const group =
kbdev->csf.scheduler.csg_slots[csg_nr].resident_group;
if (!group)
continue;
/* Ring the User doorbell for FW to update the Extract offset */
kbase_csf_ring_doorbell(kbdev, group->doorbell_nr);
set_bit(csg_nr, used_csgs);
}
/* Return early if there are no on-slot groups */
if (bitmap_empty(used_csgs, max_csg_slots))
return;
kbase_csf_scheduler_spin_lock(kbdev, &flags);
/* Return early if FW is unresponsive. */
if (kbase_csf_fw_io_open(&kbdev->csf.fw_io, &fw_io_flags)) {
kbase_csf_scheduler_spin_unlock(kbdev, flags);
return;
}
for_each_set_bit(csg_nr, used_csgs, max_csg_slots)
kbase_csf_fw_io_group_write_mask(&kbdev->csf.fw_io, csg_nr, CSG_REQ,
~kbase_csf_fw_io_group_read(&kbdev->csf.fw_io,
csg_nr, CSG_ACK),
CSG_REQ_STATUS_UPDATE_MASK);
BUILD_BUG_ON(MAX_SUPPORTED_CSGS > (sizeof(used_csgs[0]) * BITS_PER_BYTE));
kbase_csf_ring_csg_slots_doorbell(kbdev, used_csgs[0]);
kbase_csf_fw_io_close(&kbdev->csf.fw_io, fw_io_flags);
kbase_csf_scheduler_spin_unlock(kbdev, flags);
wait_csg_slots_status_update_finish(kbdev, used_csgs);
/* Wait for the user doorbell ring to take effect */
msleep(100);
}
int kbasep_csf_csg_dump_print(struct kbase_context *const kctx, struct kbasep_printer *kbpr)
{
u32 gr;
struct kbase_device *kbdev;
if (WARN_ON(!kctx))
return -EINVAL;
kbdev = kctx->kbdev;
kbasep_print(kbpr,
"CSF groups status (version: v" __stringify(MALI_CSF_CSG_DUMP_VERSION) "):\n");
mutex_lock(&kctx->csf.lock);
kbase_csf_scheduler_lock(kbdev);
kbase_csf_csg_update_status(kbdev);
kbasep_print(kbpr, "Ctx %d_%d\n", kctx->tgid, kctx->id);
for (gr = 0; gr < MAX_QUEUE_GROUP_NUM; gr++) {
struct kbase_queue_group *const group = kctx->csf.queue_groups[gr];
if (!group)
continue;
kbasep_csf_csg_active_dump_group(kbpr, group);
}
kbase_csf_scheduler_unlock(kbdev);
mutex_unlock(&kctx->csf.lock);
return 0;
}
int kbasep_csf_csg_active_dump_print(struct kbase_device *kbdev, struct kbasep_printer *kbpr)
{
u32 csg_nr;
u32 num_groups;
if (WARN_ON(!kbdev))
return -EINVAL;
num_groups = kbdev->csf.global_iface.group_num;
kbasep_print(kbpr, "CSF active groups status (version: v" __stringify(
MALI_CSF_CSG_DUMP_VERSION) "):\n");
kbase_csf_scheduler_lock(kbdev);
kbase_csf_csg_update_status(kbdev);
for (csg_nr = 0; csg_nr < num_groups; csg_nr++) {
struct kbase_queue_group *const group =
kbdev->csf.scheduler.csg_slots[csg_nr].resident_group;
if (!group)
continue;
kbasep_print(kbpr, "Ctx %d_%d\n", group->kctx->tgid, group->kctx->id);
kbasep_csf_csg_active_dump_group(kbpr, group);
}
kbase_csf_scheduler_unlock(kbdev);
return 0;
}

View file

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_CSG_H_
#define _KBASE_CSF_CSG_H_
/* Forward declaration */
struct kbase_context;
struct kbase_device;
struct kbasep_printer;
#define MALI_CSF_CSG_DUMP_VERSION 0
/**
* kbase_csf_csg_update_status() - Update on-slot gpu group statuses
*
* @kbdev: Pointer to the device.
*/
void kbase_csf_csg_update_status(struct kbase_device *kbdev);
/**
* kbasep_csf_csg_dump_print() - Dump all gpu groups information to file
*
* @kctx: The kbase_context which gpu group dumped belongs to.
* @kbpr: Pointer to printer instance.
*
* Return: Return 0 for dump successfully, or error code.
*/
int kbasep_csf_csg_dump_print(struct kbase_context *const kctx, struct kbasep_printer *kbpr);
/**
* kbasep_csf_csg_active_dump_print() - Dump on-slot gpu groups information to file
*
* @kbdev: Pointer to the device.
* @kbpr: Pointer to printer instance.
*
* Return: Return 0 for dump successfully, or error code.
*/
int kbasep_csf_csg_active_dump_print(struct kbase_device *kbdev, struct kbasep_printer *kbpr);
#endif /* _KBASE_CSF_CSG_H_ */

View file

@ -0,0 +1,384 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_csf_csg_debugfs.h"
#if IS_ENABLED(CONFIG_DEBUG_FS)
#include "mali_kbase_csf_csg.h"
#include "mali_kbase_csf_tl_reader.h"
#include "mali_kbase_csf_util.h"
#include <mali_kbase.h>
#include <linux/seq_file.h>
#include <linux/version_compat_defs.h>
#include <mali_kbase_reset_gpu.h>
#include <mali_kbase_config_defaults.h>
#define MAX_SCHED_STATE_STRING_LEN (16)
/**
* scheduler_state_to_string() - Get string name of scheduler state.
*
* @kbdev: Pointer to kbase device.
* @sched_state: Scheduler state.
*
* Return: Suitable string.
*/
static const char *scheduler_state_to_string(struct kbase_device *kbdev,
enum kbase_csf_scheduler_state sched_state)
{
switch (sched_state) {
case SCHED_BUSY:
return "BUSY";
case SCHED_INACTIVE:
return "INACTIVE";
case SCHED_SUSPENDED:
return "SUSPENDED";
#ifdef KBASE_PM_RUNTIME
case SCHED_SLEEPING:
return "SLEEPING";
#endif
default:
dev_warn(kbdev->dev, "Unknown Scheduler state %d", sched_state);
return NULL;
}
}
/**
* kbasep_csf_queue_show_groups() - Print per-context GPU command queue
* group debug information
*
* @file: The seq_file for printing to
* @data: The debugfs dentry private data, a pointer to kbase context
*
* Return: Negative error code or 0 on success.
*/
static int kbasep_csf_queue_show_groups(struct seq_file *file, void *data)
{
struct kbasep_printer *kbpr;
struct kbase_context *const kctx = file->private;
int ret = -EINVAL;
CSTD_UNUSED(data);
kbpr = kbasep_printer_file_init(file);
if (kbpr != NULL) {
ret = kbasep_csf_csg_dump_print(kctx, kbpr);
kbasep_printer_term(kbpr);
}
return ret;
}
/**
* kbasep_csf_csg_active_show_groups() - Print debug info for active GPU command queue groups
*
* @file: The seq_file for printing to
* @data: The debugfs dentry private data, a pointer to kbase_device
*
* Return: Negative error code or 0 on success.
*/
static int kbasep_csf_csg_active_show_groups(struct seq_file *file, void *data)
{
struct kbase_device *kbdev = file->private;
struct kbasep_printer *kbpr;
int ret = -EINVAL;
CSTD_UNUSED(data);
kbpr = kbasep_printer_file_init(file);
if (kbpr != NULL) {
ret = kbasep_csf_csg_active_dump_print(kbdev, kbpr);
kbasep_printer_term(kbpr);
}
return ret;
}
static int kbasep_csf_queue_group_debugfs_open(struct inode *in, struct file *file)
{
return single_open(file, kbasep_csf_queue_show_groups, in->i_private);
}
static int kbasep_csf_active_queue_groups_debugfs_open(struct inode *in, struct file *file)
{
return single_open(file, kbasep_csf_csg_active_show_groups, in->i_private);
}
static const struct file_operations kbasep_csf_queue_group_debugfs_fops = {
.open = kbasep_csf_queue_group_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void kbase_csf_queue_group_debugfs_init(struct kbase_context *kctx)
{
struct dentry *file;
const mode_t mode = 0444;
if (WARN_ON(!kctx || IS_ERR_OR_NULL(kctx->kctx_dentry)))
return;
file = debugfs_create_file("groups", mode, kctx->kctx_dentry, kctx,
&kbasep_csf_queue_group_debugfs_fops);
if (IS_ERR_OR_NULL(file)) {
dev_warn(kctx->kbdev->dev,
"Unable to create per context queue groups debugfs entry");
}
}
static const struct file_operations kbasep_csf_active_queue_groups_debugfs_fops = {
.open = kbasep_csf_active_queue_groups_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int kbasep_csf_debugfs_scheduling_timer_enabled_get(void *data, u64 *val)
{
struct kbase_device *const kbdev = data;
*val = kbase_csf_scheduler_timer_is_enabled(kbdev);
return 0;
}
static int kbasep_csf_debugfs_scheduling_timer_enabled_set(void *data, u64 val)
{
struct kbase_device *const kbdev = data;
kbase_csf_scheduler_timer_set_enabled(kbdev, val != 0);
return 0;
}
static int kbasep_csf_debugfs_scheduling_timer_kick_set(void *data, u64 val)
{
struct kbase_device *const kbdev = data;
CSTD_UNUSED(val);
kbase_csf_scheduler_kick(kbdev);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(kbasep_csf_debugfs_scheduling_timer_enabled_fops,
&kbasep_csf_debugfs_scheduling_timer_enabled_get,
&kbasep_csf_debugfs_scheduling_timer_enabled_set, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(kbasep_csf_debugfs_scheduling_timer_kick_fops, NULL,
&kbasep_csf_debugfs_scheduling_timer_kick_set, "%llu\n");
/**
* kbase_csf_debugfs_scheduler_state_get() - Get the state of scheduler.
*
* @file: Object of the file that is being read.
* @user_buf: User buffer that contains the string.
* @count: Length of user buffer
* @ppos: Offset within file object
*
* This function will return the current Scheduler state to Userspace
* Scheduler may exit that state by the time the state string is received
* by the Userspace.
*
* Return: 0 if Scheduler was found in an unexpected state, or the
* size of the state string if it was copied successfully to the
* User buffer or a negative value in case of an error.
*/
static ssize_t kbase_csf_debugfs_scheduler_state_get(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
struct kbase_device *kbdev = file->private_data;
struct kbase_csf_scheduler *scheduler = &kbdev->csf.scheduler;
const char *state_string;
kbase_csf_scheduler_lock(kbdev);
state_string = scheduler_state_to_string(kbdev, scheduler->state);
kbase_csf_scheduler_unlock(kbdev);
if (!state_string)
count = 0;
return simple_read_from_buffer(user_buf, count, ppos, state_string, strlen(state_string));
}
/**
* kbase_csf_debugfs_scheduler_state_set() - Set the state of scheduler.
*
* @file: Object of the file that is being written to.
* @ubuf: User buffer that contains the string.
* @count: Length of user buffer
* @ppos: Offset within file object
*
* This function will update the Scheduler state as per the state string
* passed by the Userspace. Scheduler may or may not remain in new state
* for long.
*
* Return: Negative value if the string doesn't correspond to a valid Scheduler
* state or if copy from user buffer failed, otherwise the length of
* the User buffer.
*/
static ssize_t kbase_csf_debugfs_scheduler_state_set(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
struct kbase_device *kbdev = file->private_data;
char buf[MAX_SCHED_STATE_STRING_LEN];
CSTD_UNUSED(ppos);
count = min_t(size_t, sizeof(buf) - 1, count);
if (copy_from_user(buf, ubuf, count))
return -EFAULT;
buf[count] = 0;
if (sysfs_streq(buf, "SUSPENDED"))
kbase_csf_scheduler_pm_suspend(kbdev);
#ifdef KBASE_PM_RUNTIME
else if (sysfs_streq(buf, "SLEEPING"))
kbase_csf_scheduler_force_sleep(kbdev);
#endif
else if (sysfs_streq(buf, "INACTIVE"))
kbase_csf_scheduler_force_wakeup(kbdev);
else {
dev_dbg(kbdev->dev, "Bad scheduler state %s", buf);
return -EINVAL;
}
return (ssize_t)count;
}
static const struct file_operations kbasep_csf_debugfs_scheduler_state_fops = {
.owner = THIS_MODULE,
.read = kbase_csf_debugfs_scheduler_state_get,
.write = kbase_csf_debugfs_scheduler_state_set,
.open = simple_open,
.llseek = default_llseek,
};
static int kbasep_csf_debugfs_eviction_timeout_get(void *data, u64 *val)
{
struct kbase_device *const kbdev = data;
unsigned long flags;
kbase_csf_scheduler_spin_lock(kbdev, &flags);
*val = kbdev->csf.csg_suspend_timeout_ms - CSG_SUSPEND_TIMEOUT_HOST_ADDED_MS;
kbase_csf_scheduler_spin_unlock(kbdev, flags);
return 0;
}
static int kbasep_csf_debugfs_eviction_timeout_set(void *data, u64 val)
{
struct kbase_device *const kbdev = data;
unsigned long flags_schd, flags_hw;
u64 dur_ms = val;
int ret = 0;
if (unlikely(dur_ms < CSG_SUSPEND_TIMEOUT_FIRMWARE_MS_MIN ||
dur_ms > CSG_SUSPEND_TIMEOUT_FIRMWARE_MS_MAX)) {
dev_err(kbdev->dev, "Invalid CSG suspend timeout input (%llu)", dur_ms);
return -EFAULT;
}
dur_ms = dur_ms + CSG_SUSPEND_TIMEOUT_HOST_ADDED_MS;
/* The 'fw_load_lock' is taken to synchronize against the deferred
* loading of FW, update will take effect after firmware gets loaded.
*/
mutex_lock(&kbdev->fw_load_lock);
if (unlikely(!kbdev->csf.firmware_inited)) {
kbase_csf_scheduler_spin_lock(kbdev, &flags_schd);
kbdev->csf.csg_suspend_timeout_ms = (unsigned int)dur_ms;
kbase_csf_scheduler_spin_unlock(kbdev, flags_schd);
mutex_unlock(&kbdev->fw_load_lock);
dev_info(kbdev->dev, "CSF set csg suspend timeout deferred till fw is loaded");
goto end;
}
mutex_unlock(&kbdev->fw_load_lock);
/* Firmware reloading is triggered by silent reset, and then update will take effect.
*/
kbase_csf_scheduler_pm_active(kbdev);
if (kbase_csf_scheduler_killable_wait_mcu_active(kbdev)) {
dev_err(kbdev->dev,
"Unable to activate the MCU, the csg suspend timeout value shall remain unchanged");
kbase_csf_scheduler_pm_idle(kbdev);
ret = -EFAULT;
goto exit;
}
spin_lock_irqsave(&kbdev->hwaccess_lock, flags_hw);
if (kbase_reset_gpu_silent(kbdev)) {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags_hw);
dev_err(kbdev->dev, "CSF set csg suspend timeout pending reset, try again");
kbase_csf_scheduler_pm_idle(kbdev);
ret = -EFAULT;
goto exit;
}
/* GPU reset is placed and it will take place only after hwaccess_lock is released,
* update on host side should be done after GPU reset is placed and before it takes place.
*/
kbase_csf_scheduler_spin_lock(kbdev, &flags_schd);
kbdev->csf.csg_suspend_timeout_ms = (unsigned int)dur_ms;
kbase_csf_scheduler_spin_unlock(kbdev, flags_schd);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags_hw);
/* Keep PM active until reset finished to allow FW reloading to take place,
* and then update request will be sent to FW during initialization.
*/
kbase_reset_gpu_wait(kbdev);
kbase_csf_scheduler_pm_idle(kbdev);
end:
dev_info(kbdev->dev, "CSF set csg suspend timeout: %u ms", (unsigned int)dur_ms);
exit:
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(kbasep_csf_debugfs_eviction_timeout_fops,
&kbasep_csf_debugfs_eviction_timeout_get,
&kbasep_csf_debugfs_eviction_timeout_set, "%llu\n");
void kbase_csf_debugfs_init(struct kbase_device *kbdev)
{
debugfs_create_file("active_groups", 0444, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_active_queue_groups_debugfs_fops);
debugfs_create_file("scheduling_timer_enabled", 0644, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_debugfs_scheduling_timer_enabled_fops);
debugfs_create_file("scheduling_timer_kick", 0200, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_debugfs_scheduling_timer_kick_fops);
debugfs_create_file("scheduler_state", 0644, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_debugfs_scheduler_state_fops);
debugfs_create_file("eviction_timeout_ms", 0644, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_debugfs_eviction_timeout_fops);
kbase_csf_tl_reader_debugfs_init(kbdev);
}
#else
/*
* Stub functions for when debugfs is disabled
*/
void kbase_csf_queue_group_debugfs_init(struct kbase_context *kctx)
{
}
void kbase_csf_debugfs_init(struct kbase_device *kbdev)
{
}
#endif /* CONFIG_DEBUG_FS */

View file

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_CSG_DEBUGFS_H_
#define _KBASE_CSF_CSG_DEBUGFS_H_
/* Forward declaration */
struct kbase_context;
struct kbase_device;
/**
* kbase_csf_queue_group_debugfs_init() - Add debugfs entry for queue groups
* associated with @kctx.
*
* @kctx: Pointer to kbase_context
*/
void kbase_csf_queue_group_debugfs_init(struct kbase_context *kctx);
/**
* kbase_csf_debugfs_init() - Add a global debugfs entry for queue groups
*
* @kbdev: Pointer to the device
*/
void kbase_csf_debugfs_init(struct kbase_device *kbdev);
#endif /* _KBASE_CSF_CSG_DEBUGFS_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include "mali_kbase_csf_event.h"
/**
* struct kbase_csf_event_cb - CSF event callback.
*
* @link: Link to the rest of the list.
* @kctx: Pointer to the Kbase context this event belongs to.
* @callback: Callback function to call when a CSF event is signalled.
* @param: Parameter to pass to the callback function.
*
* This structure belongs to the list of events which is part of a Kbase
* context, and describes a callback function with a custom parameter to pass
* to it when a CSF event is signalled.
*/
struct kbase_csf_event_cb {
struct list_head link;
struct kbase_context *kctx;
kbase_csf_event_callback *callback;
void *param;
};
int kbase_csf_event_wait_add(struct kbase_context *kctx, kbase_csf_event_callback *callback,
void *param)
{
int err = -ENOMEM;
struct kbase_csf_event_cb *event_cb =
kzalloc(sizeof(struct kbase_csf_event_cb), GFP_KERNEL);
if (event_cb) {
unsigned long flags;
event_cb->kctx = kctx;
event_cb->callback = callback;
event_cb->param = param;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
list_add_tail(&event_cb->link, &kctx->csf.event.callback_list);
dev_dbg(kctx->kbdev->dev, "Added event handler %pK with param %pK\n", event_cb,
event_cb->param);
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
err = 0;
}
return err;
}
void kbase_csf_event_wait_remove(struct kbase_context *kctx, kbase_csf_event_callback *callback,
void *param)
{
struct kbase_csf_event_cb *event_cb;
unsigned long flags;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
list_for_each_entry(event_cb, &kctx->csf.event.callback_list, link) {
if ((event_cb->callback == callback) && (event_cb->param == param)) {
list_del(&event_cb->link);
dev_dbg(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n",
event_cb, event_cb->param);
kfree(event_cb);
break;
}
}
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}
static void sync_update_notify_gpu(struct kbase_context *kctx)
{
bool can_notify_gpu;
unsigned long flags;
spin_lock_irqsave(&kctx->kbdev->hwaccess_lock, flags);
can_notify_gpu = kctx->kbdev->pm.backend.gpu_powered;
#ifdef KBASE_PM_RUNTIME
if (kctx->kbdev->pm.backend.gpu_sleep_mode_active)
can_notify_gpu = false;
#endif
if (can_notify_gpu) {
kbase_csf_ring_doorbell(kctx->kbdev, CSF_KERNEL_DOORBELL_NR);
KBASE_KTRACE_ADD(kctx->kbdev, CSF_SYNC_UPDATE_NOTIFY_GPU_EVENT, kctx, 0u);
}
spin_unlock_irqrestore(&kctx->kbdev->hwaccess_lock, flags);
}
void kbase_csf_event_signal(struct kbase_context *kctx, bool notify_gpu)
{
struct kbase_csf_event_cb *event_cb, *next_event_cb;
unsigned long flags;
dev_dbg(kctx->kbdev->dev, "Signal event (%s GPU notify) for context %pK\n",
notify_gpu ? "with" : "without", (void *)kctx);
/* First increment the signal count and wake up event thread.
*/
atomic_set(&kctx->event_count, 1);
kbase_event_wakeup(kctx);
/* Signal the CSF firmware. This is to ensure that pending command
* stream synch object wait operations are re-evaluated.
* Write to GLB_DOORBELL would suffice as spec says that all pending
* synch object wait operations are re-evaluated on a write to any
* CS_DOORBELL/GLB_DOORBELL register.
*/
if (notify_gpu)
sync_update_notify_gpu(kctx);
/* Now invoke the callbacks registered on backend side.
* Allow item removal inside the loop, if requested by the callback.
*/
spin_lock_irqsave(&kctx->csf.event.lock, flags);
list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) {
enum kbase_csf_event_callback_action action;
dev_dbg(kctx->kbdev->dev, "Calling event handler %pK with param %pK\n",
(void *)event_cb, event_cb->param);
action = event_cb->callback(event_cb->param);
if (action == KBASE_CSF_EVENT_CALLBACK_REMOVE) {
list_del(&event_cb->link);
kfree(event_cb);
}
}
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}
void kbase_csf_event_term(struct kbase_context *kctx)
{
struct kbase_csf_event_cb *event_cb, *next_event_cb;
unsigned long flags;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
list_for_each_entry_safe(event_cb, next_event_cb, &kctx->csf.event.callback_list, link) {
list_del(&event_cb->link);
dev_warn(kctx->kbdev->dev, "Removed event handler %pK with param %pK\n",
(void *)event_cb, event_cb->param);
kfree(event_cb);
}
WARN(!list_empty(&kctx->csf.event.error_list), "Error list not empty for ctx %d_%d\n",
kctx->tgid, kctx->id);
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}
void kbase_csf_event_init(struct kbase_context *const kctx)
{
INIT_LIST_HEAD(&kctx->csf.event.callback_list);
INIT_LIST_HEAD(&kctx->csf.event.error_list);
spin_lock_init(&kctx->csf.event.lock);
}
void kbase_csf_event_remove_error(struct kbase_context *kctx, struct kbase_csf_notification *error)
{
unsigned long flags;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
list_del_init(&error->link);
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}
bool kbase_csf_event_read_error(struct kbase_context *kctx,
struct base_csf_notification *event_data)
{
struct kbase_csf_notification *error_data = NULL;
unsigned long flags;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
if (likely(!list_empty(&kctx->csf.event.error_list))) {
error_data = list_first_entry(&kctx->csf.event.error_list,
struct kbase_csf_notification, link);
list_del_init(&error_data->link);
*event_data = error_data->data;
dev_dbg(kctx->kbdev->dev, "Dequeued error %pK in context %pK\n", (void *)error_data,
(void *)kctx);
}
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
return !!error_data;
}
void kbase_csf_event_add_error(struct kbase_context *const kctx,
struct kbase_csf_notification *const error,
struct base_csf_notification const *const data)
{
unsigned long flags;
if (WARN_ON(!kctx))
return;
if (WARN_ON(!error))
return;
if (WARN_ON(!data))
return;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
if (list_empty(&error->link)) {
error->data = *data;
list_add_tail(&error->link, &kctx->csf.event.error_list);
dev_dbg(kctx->kbdev->dev, "Added error %pK of type %d in context %pK\n",
(void *)error, data->type, (void *)kctx);
} else {
dev_dbg(kctx->kbdev->dev, "Error %pK of type %d already pending in context %pK",
(void *)error, error->data.type, (void *)kctx);
}
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
}
bool kbase_csf_event_error_pending(struct kbase_context *kctx)
{
bool error_pending = false;
unsigned long flags;
/* Withhold the error event if the dump on fault is ongoing.
* This would prevent the Userspace from taking error recovery actions
* (which can potentially affect the state that is being dumped).
* Event handling thread would eventually notice the error event.
*/
if (unlikely(!kbase_debug_csf_fault_dump_complete(kctx->kbdev)))
return false;
spin_lock_irqsave(&kctx->csf.event.lock, flags);
error_pending = !list_empty(&kctx->csf.event.error_list);
dev_dbg(kctx->kbdev->dev, "%s error is pending in context %pK\n",
error_pending ? "An" : "No", (void *)kctx);
spin_unlock_irqrestore(&kctx->csf.event.lock, flags);
return error_pending;
}

View file

@ -0,0 +1,170 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_EVENT_H_
#define _KBASE_CSF_EVENT_H_
#include <linux/types.h>
#include <linux/wait.h>
struct kbase_context;
struct kbase_csf_event;
enum kbase_csf_event_callback_action;
/**
* kbase_csf_event_callback - type for callback functions to be
* called upon CSF events.
* @param: Generic parameter to pass to the callback function.
*
* This is the type of callback functions that can be registered
* for CSF events. These function calls shall be triggered by any call
* to kbase_csf_event_signal.
*
* Return: KBASE_CSF_EVENT_CALLBACK_KEEP if the callback should remain
* registered, or KBASE_CSF_EVENT_CALLBACK_REMOVE if it should be removed.
*/
typedef enum kbase_csf_event_callback_action kbase_csf_event_callback(void *param);
/**
* kbase_csf_event_wait_add - Add a CSF event callback
*
* @kctx: The Kbase context the @callback should be registered to.
* @callback: The callback function to register.
* @param: Custom parameter to be passed to the @callback function.
*
* This function adds an event callback to the list of CSF event callbacks
* belonging to a given Kbase context, to be triggered when a CSF event is
* signalled by kbase_csf_event_signal.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_event_wait_add(struct kbase_context *kctx, kbase_csf_event_callback *callback,
void *param);
/**
* kbase_csf_event_wait_remove - Remove a CSF event callback
*
* @kctx: The kbase context the @callback should be removed from.
* @callback: The callback function to remove.
* @param: Custom parameter that would have been passed to the @p callback
* function.
*
* This function removes an event callback from the list of CSF event callbacks
* belonging to a given Kbase context.
*/
void kbase_csf_event_wait_remove(struct kbase_context *kctx, kbase_csf_event_callback *callback,
void *param);
/**
* kbase_csf_event_term - Removes all CSF event callbacks
*
* @kctx: The kbase context for which CSF event callbacks have to be removed.
*
* This function empties the list of CSF event callbacks belonging to a given
* Kbase context.
*/
void kbase_csf_event_term(struct kbase_context *kctx);
/**
* kbase_csf_event_signal - Signal a CSF event
*
* @kctx: The kbase context whose CSF event callbacks shall be triggered.
* @notify_gpu: Flag to indicate if CSF firmware should be notified of the
* signaling of event that happened on the Driver side, either
* the signal came from userspace or from kcpu queues.
*
* This function triggers all the CSF event callbacks that are registered to
* a given Kbase context, and also signals the event handling thread of
* userspace driver waiting for the CSF event.
*/
void kbase_csf_event_signal(struct kbase_context *kctx, bool notify_gpu);
static inline void kbase_csf_event_signal_notify_gpu(struct kbase_context *kctx)
{
kbase_csf_event_signal(kctx, true);
}
static inline void kbase_csf_event_signal_cpu_only(struct kbase_context *kctx)
{
kbase_csf_event_signal(kctx, false);
}
/**
* kbase_csf_event_init - Initialize event object
*
* @kctx: The kbase context whose event object will be initialized.
*
* This function initializes the event object.
*/
void kbase_csf_event_init(struct kbase_context *const kctx);
struct kbase_csf_notification;
struct base_csf_notification;
/**
* kbase_csf_event_read_error - Read and remove an error from error list in event
*
* @kctx: The kbase context.
* @event_data: Caller-provided buffer to copy the fatal error to
*
* This function takes the CS fatal error from context's ordered
* error_list, copies its contents to @event_data.
*
* Return: true if error is read out or false if there is no error in error list.
*/
bool kbase_csf_event_read_error(struct kbase_context *kctx,
struct base_csf_notification *event_data);
/**
* kbase_csf_event_add_error - Add an error into event error list
*
* @kctx: Address of a base context associated with a GPU address space.
* @error: Address of the item to be added to the context's pending error list.
* @data: Error data to be returned to userspace.
*
* Does not wake up the event queue blocking a user thread in kbase_poll. This
* is to make it more efficient to add multiple errors.
*
* The added error must not already be on the context's list of errors waiting
* to be reported (e.g. because a previous error concerning the same object has
* not yet been reported).
*
*/
void kbase_csf_event_add_error(struct kbase_context *const kctx,
struct kbase_csf_notification *const error,
struct base_csf_notification const *const data);
/**
* kbase_csf_event_remove_error - Remove an error from event error list
*
* @kctx: Address of a base context associated with a GPU address space.
* @error: Address of the item to be removed from the context's event error list.
*/
void kbase_csf_event_remove_error(struct kbase_context *kctx, struct kbase_csf_notification *error);
/**
* kbase_csf_event_error_pending - Check the error pending status
*
* @kctx: The kbase context to check fatal error upon.
*
* Return: true if there is error in the list.
*/
bool kbase_csf_event_error_pending(struct kbase_context *kctx);
#endif /* _KBASE_CSF_EVENT_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,848 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2018-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_FIRMWARE_H_
#define _KBASE_CSF_FIRMWARE_H_
#include "device/mali_kbase_device.h"
#include <csf/mali_kbase_csf_registers.h>
#include <hw_access/mali_kbase_hw_access_regmap.h>
#include <uapi/gpu/arm/bv_r51p0/gpu/mali_kbase_gpu_regmap.h>
/*
* PAGE_KERNEL_RO was only defined on 32bit ARM in 4.19 in:
* Commit a3266bd49c721e2e0a71f352d83713fbd60caadb
* Author: Luis R. Rodriguez <mcgrof@kernel.org>
* Date: Fri Aug 17 15:46:29 2018 -0700
*
* mm: provide a fallback for PAGE_KERNEL_RO for architectures
*
* Some architectures do not define certain PAGE_KERNEL_* flags, this is
* either because:
*
* a) The way to implement some of these flags is *not yet ported*, or
* b) The architecture *has no way* to describe them
*
* [snip]
*
* This can be removed once support of 32bit ARM kernels predating 4.19 is no
* longer required.
*/
#ifndef PAGE_KERNEL_RO
#define PAGE_KERNEL_RO PAGE_KERNEL
#endif
/* Address space number to claim for the firmware. */
#define MCU_AS_NR 0
#define MCU_AS_BITMASK (1 << MCU_AS_NR)
/* Number of available Doorbells */
#define CSF_NUM_DOORBELL_MAX ((u8)64)
/* Offset to the first HW doorbell page */
#define CSF_HW_DOORBELL_PAGE_OFFSET ((u32)DOORBELLS_BASE)
/* Size of HW Doorbell page, used to calculate the offset to subsequent pages */
#define CSF_HW_DOORBELL_PAGE_SIZE ((u32)0x10000)
/* Doorbell 0 is used by the driver. */
#define CSF_KERNEL_DOORBELL_NR ((u32)0)
/* Offset of name inside a trace buffer entry in the firmware image */
#define TRACE_BUFFER_ENTRY_NAME_OFFSET (0x1C)
/* All implementations of the host interface with major version 0 must comply
* with these restrictions:
*/
/* GLB_GROUP_NUM: At least 3 CSGs, but no more than 31 */
#define MIN_SUPPORTED_CSGS 3
#define MAX_SUPPORTED_CSGS 31
/* GROUP_STREAM_NUM: At least 8 CSs per CSG, but no more than 32 */
#define MIN_SUPPORTED_STREAMS_PER_GROUP 8
/* MAX_SUPPORTED_STREAMS_PER_GROUP: Maximum CSs per csg. */
#define MAX_SUPPORTED_STREAMS_PER_GROUP 32
struct kbase_device;
/**
* struct kbase_csf_mapping - Memory mapping for CSF memory.
* @phys: Physical memory allocation used by the mapping.
* @cpu_addr: Starting CPU address for the mapping.
* @va_reg: GPU virtual address region for the mapping.
* @num_pages: Size of the mapping, in memory pages.
*/
struct kbase_csf_mapping {
struct tagged_addr *phys;
void *cpu_addr;
struct kbase_va_region *va_reg;
unsigned int num_pages;
};
/**
* struct kbase_csf_trace_buffers - List and state of firmware trace buffers.
* @list: List of trace buffers descriptors.
* @mcu_rw: Metadata for the MCU shared memory mapping used for
* GPU-readable,writable/CPU-writable variables.
* @mcu_write: Metadata for the MCU shared memory mapping used for
* GPU-writable/CPU-readable variables.
*/
struct kbase_csf_trace_buffers {
struct list_head list;
struct kbase_csf_mapping mcu_rw;
struct kbase_csf_mapping mcu_write;
};
/**
* struct kbase_csf_cmd_stream_info - CSI provided by the firmware.
*
* @kbdev: Address of the instance of a GPU platform device that implements
* this interface.
* @features: Bit field of CS features (e.g. which types of jobs
* are supported). Bits 7:0 specify the number of work registers(-1).
* Bits 11:8 specify the number of scoreboard entries(-1).
* @gid: CSG index to which the CSI is assigned.
* @sid: CSI index.
*/
struct kbase_csf_cmd_stream_info {
struct kbase_device *kbdev;
u32 features;
u32 gid;
u32 sid;
};
/**
* struct kbase_csf_cmd_stream_group_info - CSG interface provided by the
* firmware.
*
* @kbdev: Address of the instance of a GPU platform device that implements
* this interface.
* @features: Bit mask of features. Reserved bits should be 0, and should
* be ignored.
* @gid: CSG index.
* @suspend_size: Size in bytes for normal suspend buffer for the CSG
* @protm_suspend_size: Size in bytes for protected mode suspend buffer
* for the CSG.
* @stream_num: Number of CSs in the CSG.
* @stream_stride: Stride in bytes in JASID0 virtual address between
* CS capability structures.
* @streams: Address of an array of CS capability structures.
*/
struct kbase_csf_cmd_stream_group_info {
struct kbase_device *kbdev;
u32 features;
u32 gid;
u32 suspend_size;
u32 protm_suspend_size;
u32 stream_num;
u32 stream_stride;
struct kbase_csf_cmd_stream_info *streams;
};
/**
* struct kbase_csf_global_iface - Global CSF interface
* provided by the firmware.
*
* @kbdev: Address of the instance of a GPU platform device that implements
* this interface.
* @version: Bits 31:16 hold the major version number and 15:0 hold the minor
* version number. A higher minor version is backwards-compatible
* with a lower minor version for the same major version.
* @features: Bit mask of features (e.g. whether certain types of job can
* be suspended). Reserved bits should be 0, and should be ignored.
* @group_num: Number of CSGs supported.
* @group_stride: Stride in bytes in JASID0 virtual address between
* CSG capability structures.
* @prfcnt_size: Performance counters size.
* @instr_features: Instrumentation features. (csf >= 1.1.0)
* @groups: Address of an array of CSG capability structures.
*/
struct kbase_csf_global_iface {
struct kbase_device *kbdev;
u32 version;
u32 features;
u32 group_num;
u32 group_stride;
u32 prfcnt_size;
u32 instr_features;
struct kbase_csf_cmd_stream_group_info *groups;
};
/**
* kbase_csf_ring_doorbell() - Ring the doorbell
*
* @kbdev: An instance of the GPU platform device
* @doorbell_nr: Index of the HW doorbell page
*/
void kbase_csf_ring_doorbell(struct kbase_device *kbdev, int doorbell_nr);
/**
* kbase_csf_read_firmware_memory - Read a value in a GPU address
*
* @kbdev: Device pointer
* @gpu_addr: GPU address to read
* @value: output pointer to which the read value will be written.
*
* This function read a value in a GPU address that belongs to
* a private firmware memory region. The function assumes that the location
* is not permanently mapped on the CPU address space, therefore it maps it
* and then unmaps it to access it independently.
*/
void kbase_csf_read_firmware_memory(struct kbase_device *kbdev, u32 gpu_addr, u32 *value);
/**
* kbase_csf_update_firmware_memory - Write a value in a GPU address
*
* @kbdev: Device pointer
* @gpu_addr: GPU address to write
* @value: Value to write
*
* This function writes a given value in a GPU address that belongs to
* a private firmware memory region. The function assumes that the destination
* is not permanently mapped on the CPU address space, therefore it maps it
* and then unmaps it to access it independently.
*/
void kbase_csf_update_firmware_memory(struct kbase_device *kbdev, u32 gpu_addr, u32 value);
/**
* kbase_csf_read_firmware_memory_exe - Read a value in a GPU address in the
* region of its final execution location.
*
* @kbdev: Device pointer
* @gpu_addr: GPU address to read
* @value: Output pointer to which the read value will be written
*
* This function read a value in a GPU address that belongs to a private loaded
* firmware memory region based on its final execution location. The function
* assumes that the location is not permanently mapped on the CPU address space,
* therefore it maps it and then unmaps it to access it independently. This function
* needs to be used when accessing firmware memory regions which will be moved to
* their final execution location during firmware boot using an address based on the
* final execution location.
*/
void kbase_csf_read_firmware_memory_exe(struct kbase_device *kbdev, u32 gpu_addr, u32 *value);
/**
* kbase_csf_update_firmware_memory_exe - Write a value in a GPU address in the
* region of its final execution location.
*
* @kbdev: Device pointer
* @gpu_addr: GPU address to write
* @value: Value to write
*
* This function writes a value in a GPU address that belongs to a private loaded
* firmware memory region based on its final execution location. The function
* assumes that the location is not permanently mapped on the CPU address space,
* therefore it maps it and then unmaps it to access it independently. This function
* needs to be used when accessing firmware memory regions which will be moved to
* their final execution location during firmware boot using an address based on the
* final execution location.
*/
void kbase_csf_update_firmware_memory_exe(struct kbase_device *kbdev, u32 gpu_addr, u32 value);
/**
* kbase_csf_firmware_early_init() - Early initialization for the firmware.
* @kbdev: Kbase device
*
* Initialize resources related to the firmware. Must be called at kbase probe.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_early_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_early_term() - Terminate resources related to the firmware
* after the firmware unload has been done.
*
* @kbdev: Device pointer
*
* This should be called only when kbase probe fails or gets rmmoded.
*/
void kbase_csf_firmware_early_term(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_late_init() - Late initialization for the firmware.
* @kbdev: Kbase device
*
* Initialize resources related to the firmware. But must be called after
* backend late init is done. Must be used at probe time only.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_late_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_load_init() - Load the firmware for the CSF MCU
* @kbdev: Kbase device
*
* Request the firmware from user space and load it into memory.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_load_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_unload_term() - Unload the firmware
* @kbdev: Kbase device
*
* Frees the memory allocated by kbase_csf_firmware_load_init()
*/
void kbase_csf_firmware_unload_term(struct kbase_device *kbdev);
#if IS_ENABLED(CONFIG_MALI_CORESIGHT)
/**
* kbase_csf_firmware_mcu_register_write - Write to MCU register
*
* @kbdev: Instance of a gpu platform device that implements a csf interface.
* @reg_addr: Register address to write into
* @reg_val: Value to be written
*
* Write a desired value to a register in MCU address space.
*
* return: 0 on success, or negative on failure.
*/
int kbase_csf_firmware_mcu_register_write(struct kbase_device *const kbdev, u32 const reg_addr,
u32 const reg_val);
/**
* kbase_csf_firmware_mcu_register_read - Read from MCU register
*
* @kbdev: Instance of a gpu platform device that implements a csf interface.
* @reg_addr: Register address to read from
* @reg_val: Value as present in reg_addr register
*
* Read a value from MCU address space.
*
* return: 0 on success, or negative on failure.
*/
int kbase_csf_firmware_mcu_register_read(struct kbase_device *const kbdev, u32 const reg_addr,
u32 *reg_val);
/**
* kbase_csf_firmware_mcu_register_poll - Poll MCU register
*
* @kbdev: Instance of a gpu platform device that implements a csf interface.
* @reg_addr: Register address to read from
* @val_mask: Value to mask the read value for comparison
* @reg_val: Value to be compared against
*
* Continue to read a value from MCU address space until it matches given mask and value.
*
* return: 0 on success, or negative on failure.
*/
int kbase_csf_firmware_mcu_register_poll(struct kbase_device *const kbdev, u32 const reg_addr,
u32 const val_mask, u32 const reg_val);
#endif /* IS_ENABLED(CONFIG_MALI_CORESIGHT) */
/**
* kbase_csf_firmware_ping - Send the ping request to firmware.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* The function sends the ping request to firmware.
*/
void kbase_csf_firmware_ping(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_ping_wait - Send the ping request to firmware and waits.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @wait_timeout_ms: Timeout to get the acknowledgment for PING request from FW.
*
* The function sends the ping request to firmware and waits to confirm it is
* alive.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_firmware_ping_wait(struct kbase_device *kbdev, unsigned int wait_timeout_ms);
/**
* kbase_csf_firmware_set_timeout - Set a hardware endpoint progress timeout.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @timeout: The maximum number of GPU cycles that is allowed to elapse
* without forward progress before the driver terminates a GPU
* command queue group.
*
* Configures the progress timeout value used by the firmware to decide
* when to report that a task is not making progress on an endpoint.
*
* Return: 0 on success,
* -ENODEV on unresponsive MCU,
* or negative error code on other failure.
*/
int kbase_csf_firmware_set_timeout(struct kbase_device *kbdev, u64 timeout);
/**
* kbase_csf_enter_protected_mode - Send the Global request to firmware to
* enter protected mode.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* The function must be called with kbdev->csf.scheduler.interrupt_lock held
* and it does not wait for the protected mode entry to complete.
*
* Return: 0 on success, -ENODEV if MCU is unresponsive.
*/
int kbase_csf_enter_protected_mode(struct kbase_device *kbdev);
/**
* kbase_csf_wait_protected_mode_enter - Wait for the completion of PROTM_ENTER
* Global request sent to firmware.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* This function needs to be called after kbase_csf_enter_protected_mode() to
* wait for the GPU to actually enter protected mode. GPU reset is triggered if
* the wait is unsuccessful.
*
* Return: 0 on success, or negative on failure.
*/
int kbase_csf_wait_protected_mode_enter(struct kbase_device *kbdev);
static inline bool kbase_csf_firmware_mcu_halted(struct kbase_device *kbdev)
{
#if IS_ENABLED(CONFIG_MALI_NO_MALI)
return true;
#else
return (kbase_reg_read32(kbdev, GPU_CONTROL_ENUM(MCU_STATUS)) == MCU_STATUS_VALUE_HALT);
#endif /* CONFIG_MALI_NO_MALI */
}
/**
* kbase_csf_firmware_mcu_halt_req_complete - Check if the MCU Halt request is complete
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* This function needs to be called after Halt request has been sent to the FW.
*
* Return: true if the Halt request is complete, otherwise false.
*/
bool kbase_csf_firmware_mcu_halt_req_complete(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_trigger_mcu_halt - Send the Global request to firmware to
* halt its operation and bring itself
* into a known internal state for warm
* boot later.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_trigger_mcu_halt(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_enable_mcu - Send the command to enable MCU
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_enable_mcu(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_disable_mcu - Send the command to disable MCU
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_disable_mcu(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_disable_mcu_wait - Wait for the MCU to reach disabled status.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_disable_mcu_wait(struct kbase_device *kbdev);
/**
* kbase_csf_stop_firmware_and_wait - Disable firmware and wait for the MCU to reach
* disabled status.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_stop_firmware_and_wait(struct kbase_device *kbdev);
#ifdef KBASE_PM_RUNTIME
/**
* kbase_csf_firmware_trigger_mcu_sleep - Send the command to put MCU in sleep
* state.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_trigger_mcu_sleep(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_is_mcu_in_sleep - Check if sleep request has completed
* and MCU has halted.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: true if sleep request has completed, otherwise false.
*/
bool kbase_csf_firmware_is_mcu_in_sleep(struct kbase_device *kbdev);
#endif
/**
* kbase_csf_firmware_trigger_reload() - Trigger the reboot of MCU firmware, for
* the cold boot case firmware image would
* be reloaded from filesystem into memory.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_trigger_reload(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_reload_completed - The reboot of MCU firmware has
* completed.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_firmware_reload_completed(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_global_reinit - Send the Global configuration requests
* after the reboot of MCU firmware.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @core_mask: Mask of the enabled shader cores.
*/
void kbase_csf_firmware_global_reinit(struct kbase_device *kbdev, u64 core_mask);
/**
* kbase_csf_firmware_global_reinit_complete - Check the Global configuration
* requests, sent after the reboot of MCU firmware, have
* completed or not.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: true if the Global configuration requests completed otherwise false.
*/
bool kbase_csf_firmware_global_reinit_complete(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_update_core_attr - Send the Global configuration request
* to update the requested core attribute
* changes.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @update_core_pwroff_timer: If true, signal the firmware needs to update
* the MCU power-off timer value.
* @update_core_mask: If true, need to do the core_mask update with
* the supplied core_mask value.
* @core_mask: New core mask value if update_core_mask is true,
* otherwise unused.
*/
void kbase_csf_firmware_update_core_attr(struct kbase_device *kbdev, bool update_core_pwroff_timer,
bool update_core_mask, u64 core_mask);
/**
* kbase_csf_firmware_core_attr_updated - Check the Global configuration
* request has completed or not, that was sent to update
* the core attributes.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: true if the Global configuration request to update the core
* attributes has completed, otherwise false.
*/
bool kbase_csf_firmware_core_attr_updated(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_get_glb_iface - Request the global control block of CSF
* interface capabilities
*
* @kbdev: Kbase device.
* @group_data: Pointer where to store all the group data
* (sequentially).
* @max_group_num: The maximum number of groups to be read.
* Can be 0, in which case group_data is unused.
* @stream_data: Pointer where to store all the CS data
* (sequentially).
* @max_total_stream_num: The maximum number of CSs to be read.
* Can be 0, in which case stream_data is unused.
* @glb_version: Where to store the global interface version.
* @features: Where to store a bit mask of features (e.g.
* whether certain types of job can be suspended).
* @group_num: Where to store the number of CSGs
* supported.
* @prfcnt_size: Where to store the size of CSF performance counters,
* in bytes. Bits 31:16 hold the size of firmware
* performance counter data and 15:0 hold the size of
* hardware performance counter data.
* @instr_features: Instrumentation features. Bits 7:4 hold the max size
* of events. Bits 3:0 hold the offset update rate.
* (csf >= 1,1,0)
*
* Return: Total number of CSs, summed across all groups.
*/
u32 kbase_csf_firmware_get_glb_iface(struct kbase_device *kbdev,
struct basep_cs_group_control *group_data, u32 max_group_num,
struct basep_cs_stream_control *stream_data,
u32 max_total_stream_num, u32 *glb_version, u32 *features,
u32 *group_num, u32 *prfcnt_size, u32 *instr_features);
/**
* kbase_csf_firmware_get_timeline_metadata - Get CSF firmware header timeline
* metadata content
*
* @kbdev: Kbase device.
* @name: Name of the metadata which metadata content to be returned.
* @size: Metadata size if specified metadata found.
*
* Return: The firmware timeline metadata content which match @p name.
*/
const char *kbase_csf_firmware_get_timeline_metadata(struct kbase_device *kbdev, const char *name,
size_t *size);
/**
* kbase_csf_firmware_mcu_shared_mapping_init - Allocate and map MCU shared memory.
*
* @kbdev: Kbase device the memory mapping shall belong to.
* @num_pages: Number of memory pages to map.
* @cpu_map_properties: Either PROT_READ or PROT_WRITE.
* @gpu_map_properties: Either KBASE_REG_GPU_RD or KBASE_REG_GPU_WR.
* @csf_mapping: Object where to write metadata for the memory mapping.
*
* This helper function allocates memory and maps it on both the CPU
* and the GPU address spaces. Most of the properties of the mapping
* are implicit and will be automatically determined by the function,
* e.g. whether memory is cacheable.
*
* The client is only expected to specify whether the mapping is readable
* or writable in the CPU and the GPU address spaces; any other flag
* will be ignored by the function.
*
* Return: 0 if success, or an error code on failure.
*/
int kbase_csf_firmware_mcu_shared_mapping_init(struct kbase_device *kbdev, unsigned int num_pages,
unsigned long cpu_map_properties,
unsigned long gpu_map_properties,
struct kbase_csf_mapping *csf_mapping);
/**
* kbase_csf_firmware_mcu_shared_mapping_term - Unmap and free MCU shared memory.
*
* @kbdev: Device pointer.
* @csf_mapping: Metadata of the memory mapping to terminate.
*/
void kbase_csf_firmware_mcu_shared_mapping_term(struct kbase_device *kbdev,
struct kbase_csf_mapping *csf_mapping);
#ifdef CONFIG_MALI_DEBUG
extern bool fw_debug;
#endif
static inline long kbase_csf_timeout_in_jiffies(const unsigned int msecs)
{
#ifdef CONFIG_MALI_DEBUG
return (fw_debug ? MAX_SCHEDULE_TIMEOUT : (long)msecs_to_jiffies(msecs));
#else
return (long)msecs_to_jiffies(msecs);
#endif
}
/**
* kbase_csf_firmware_enable_gpu_idle_timer() - Activate the idle hysteresis
* monitoring operation
*
* @kbdev: Kbase device structure
*
* Program the firmware interface with its configured hysteresis count value
* and enable the firmware to act on it. The Caller is
* assumed to hold the kbdev->csf.scheduler.interrupt_lock.
*
* Return: -ENODEV on unresponsive MCU, 0 otherwise.
*/
int kbase_csf_firmware_enable_gpu_idle_timer(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_disable_gpu_idle_timer() - Disable the idle time
* hysteresis monitoring operation
*
* @kbdev: Kbase device structure
*
* Program the firmware interface to disable the idle hysteresis timer. The
* Caller is assumed to hold the kbdev->csf.scheduler.interrupt_lock.
*
* Return: -ENODEV on unresponsive MCU, 0 otherwise.
*/
int kbase_csf_firmware_disable_gpu_idle_timer(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_get_gpu_idle_hysteresis_time - Get the firmware GPU idle
* detection hysteresis duration
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: the internally recorded hysteresis (nominal) value.
*/
u64 kbase_csf_firmware_get_gpu_idle_hysteresis_time(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_set_gpu_idle_hysteresis_time - Set the firmware GPU idle
* detection hysteresis duration
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @dur_ns: The duration value (unit: nanoseconds) for the configuring
* hysteresis field for GPU idle detection
*
* The supplied value will be recorded internally without any change. But the
* actual field value will be subject to hysteresis source frequency scaling
* and maximum value limiting. The default source will be SYSTEM_TIMESTAMP
* counter. But in case the platform is not able to supply it, the GPU
* CYCLE_COUNTER source will be used as an alternative. Bit-31 on the
* returned value is the source configuration flag, and it is set to '1'
* when CYCLE_COUNTER alternative source is used.
*
* Return: the actual internally configured hysteresis field value.
*/
u32 kbase_csf_firmware_set_gpu_idle_hysteresis_time(struct kbase_device *kbdev, u64 dur_ns);
/**
* kbase_csf_firmware_get_mcu_core_pwroff_time - Get the MCU shader Core power-off
* time value
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: the internally recorded MCU shader Core power-off (nominal) timeout value. The unit
* of the value is in micro-seconds.
*/
u64 kbase_csf_firmware_get_mcu_core_pwroff_time(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_set_mcu_core_pwroff_time - Set the MCU shader Core power-off
* time value
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @dur_ns: The duration value (unit: nanoseconds) for configuring MCU
* core power-off timer, when the shader cores' power
* transitions are delegated to the MCU (normal operational
* mode)
*
* The supplied value will be recorded internally without any change. But the
* actual field value will be subject to core power-off timer source frequency
* scaling and maximum value limiting. The default source will be
* SYSTEM_TIMESTAMP counter. But in case the platform is not able to supply it,
* the GPU CYCLE_COUNTER source will be used as an alternative. Bit-31 on the
* returned value is the source configuration flag, and it is set to '1'
* when CYCLE_COUNTER alternative source is used.
*
* The configured MCU shader Core power-off timer will only have effect when the host
* driver has delegated the shader cores' power management to MCU.
*
* Return: the actual internal core power-off timer value in register defined
* format.
*/
u32 kbase_csf_firmware_set_mcu_core_pwroff_time(struct kbase_device *kbdev, u64 dur_ns);
/**
* kbase_csf_firmware_reset_mcu_core_pwroff_time - Reset the MCU shader Core power-off
* time value
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Sets the MCU Shader Core power-off time value to the default.
*
* The configured MCU shader Core power-off timer will only have effect when the host
* driver has delegated the shader cores' power management to MCU.
*
* Return: the actual internal core power-off timer value in register defined
* format.
*/
u32 kbase_csf_firmware_reset_mcu_core_pwroff_time(struct kbase_device *kbdev);
/**
* kbase_csf_interface_version - Helper function to build the full firmware
* interface version in a format compatible with
* GLB_VERSION register
*
* @major: major version of csf interface
* @minor: minor version of csf interface
* @patch: patch version of csf interface
*
* Return: firmware interface version
*/
static inline u32 kbase_csf_interface_version(u32 major, u32 minor, u32 patch)
{
return ((major << GLB_VERSION_MAJOR_SHIFT) | (minor << GLB_VERSION_MINOR_SHIFT) |
(patch << GLB_VERSION_PATCH_SHIFT));
}
/**
* kbase_csf_trigger_firmware_config_update - Send a firmware config update.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Any changes done to firmware configuration entry or tracebuffer entry
* requires a GPU silent reset to reflect the configuration changes
* requested, but if Firmware.header.entry.bit(30) is set then we can request a
* FIRMWARE_CONFIG_UPDATE rather than doing a silent reset.
*
* Return: 0 if success, or negative error code on failure.
*/
int kbase_csf_trigger_firmware_config_update(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_req_core_dump - Request a firmware core dump
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Request a firmware core dump and wait for for firmware to acknowledge.
* Firmware will enter infinite loop after the firmware core dump is created.
*
* Return: 0 if success,
* -ENODEV on unresponsive MCU,
* or negative error code on other failure.
*/
int kbase_csf_firmware_req_core_dump(struct kbase_device *const kbdev);
#ifdef KBASE_PM_RUNTIME
/**
* kbase_csf_firmware_soi_update - Update FW Sleep-on-Idle config
*
* @kbdev: Device pointer
*
* This function reconfigures the FW Sleep-on-Idle configuration if necessary.
*/
void kbase_csf_firmware_soi_update(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_glb_idle_timer_update - Update GLB_IDLE timer config
*
* @kbdev: Device pointer
*
* This function reconfigures the GLB_IDLE timer configuration if necessary.
*/
void kbase_csf_firmware_glb_idle_timer_update(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_soi_disable_on_scheduler_suspend - Disable FW Sleep-on-Idle config
* on scheduler suspension
*
* @kbdev: Device pointer
*
* Return: 0 on success, otherwise failure
*/
int kbase_csf_firmware_soi_disable_on_scheduler_suspend(struct kbase_device *kbdev);
#endif /* KBASE_PM_RUNTIME */
#endif

View file

@ -0,0 +1,436 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <mali_kbase_reset_gpu.h>
#include <linux/version.h>
#include "mali_kbase_csf_firmware_cfg.h"
#include "mali_kbase_csf_firmware_log.h"
#if CONFIG_SYSFS
#define CSF_FIRMWARE_CFG_SYSFS_DIR_NAME "firmware_config"
#define CSF_FIRMWARE_CFG_LOG_VERBOSITY_ENTRY_NAME "Log verbosity"
#define CSF_FIRMWARE_CFG_WA_CFG0_ENTRY_NAME "WA_CFG0"
/**
* struct firmware_config - Configuration item within the MCU firmware
*
* @node: List head linking all options to
* kbase_device:csf.firmware_config
* @kbdev: Pointer to the Kbase device
* @kobj: Kobject corresponding to the sysfs sub-directory,
* inside CSF_FIRMWARE_CFG_SYSFS_DIR_NAME directory,
* representing the configuration option @name.
* @kobj_inited: kobject initialization state
* @updatable: Indicates whether config items can be updated with
* FIRMWARE_CONFIG_UPDATE
* @name: NUL-terminated string naming the option
* @address: The address in the firmware image of the configuration option
* @min: The lowest legal value of the configuration option
* @max: The maximum legal value of the configuration option
* @cur_val: The current value of the configuration option
*
* The firmware may expose configuration options. Each option has a name, the
* address where the option is controlled and the minimum and maximum values
* that the option can take.
*/
struct firmware_config {
struct list_head node;
struct kbase_device *kbdev;
struct kobject kobj;
bool kobj_inited;
bool updatable;
char *name;
u32 address;
u32 min;
u32 max;
u32 cur_val;
};
#define FW_CFG_ATTR(_name, _mode) \
struct attribute fw_cfg_attr_##_name = { \
.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode), \
}
static FW_CFG_ATTR(min, 0444);
static FW_CFG_ATTR(max, 0444);
static FW_CFG_ATTR(cur, 0644);
static void fw_cfg_kobj_release(struct kobject *kobj)
{
struct firmware_config *config = container_of(kobj, struct firmware_config, kobj);
kfree(config);
}
static ssize_t show_fw_cfg(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct firmware_config *config = container_of(kobj, struct firmware_config, kobj);
struct kbase_device *kbdev = config->kbdev;
u32 val = 0;
if (!kbdev)
return -ENODEV;
if (attr == &fw_cfg_attr_max)
val = config->max;
else if (attr == &fw_cfg_attr_min)
val = config->min;
else if (attr == &fw_cfg_attr_cur) {
unsigned long flags;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
val = config->cur_val;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
} else {
dev_warn(kbdev->dev, "Unexpected read from entry %s/%s", config->name, attr->name);
return -EINVAL;
}
return scnprintf(buf, PAGE_SIZE, "%u\n", val);
}
static ssize_t store_fw_cfg(struct kobject *kobj, struct attribute *attr, const char *buf,
size_t count)
{
struct firmware_config *config = container_of(kobj, struct firmware_config, kobj);
struct kbase_device *kbdev = config->kbdev;
if (!kbdev)
return -ENODEV;
if (attr == &fw_cfg_attr_cur) {
unsigned long flags;
u32 val, cur_val;
int ret = kstrtouint(buf, 0, &val);
if (ret) {
dev_err(kbdev->dev,
"Couldn't process %s/%s write operation.\n"
"Use format <value>\n",
config->name, attr->name);
return -EINVAL;
}
if (!strcmp(config->name, CSF_FIRMWARE_CFG_WA_CFG0_ENTRY_NAME))
return -EPERM;
if ((val < config->min) || (val > config->max))
return -EINVAL;
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
cur_val = config->cur_val;
if (cur_val == val) {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return (ssize_t)count;
}
/* If configuration update cannot be performed with
* FIRMWARE_CONFIG_UPDATE then we need to do a
* silent reset before we update the memory.
*/
if (!config->updatable) {
/*
* If there is already a GPU reset pending then inform
* the User to retry the write.
*/
if (kbase_reset_gpu_silent(kbdev)) {
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return -EAGAIN;
}
}
/*
* GPU reset request has been placed, now update the
* firmware image. GPU reset will take place only after
* hwaccess_lock is released.
* Update made to firmware image in memory would not
* be lost on GPU reset as configuration entries reside
* in the RONLY section of firmware image, which is not
* reloaded on firmware reboot due to GPU reset.
*/
kbase_csf_update_firmware_memory(kbdev, config->address, val);
config->cur_val = val;
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
/* Enable FW logging only if Log verbosity is non-zero */
if (!strcmp(config->name, CSF_FIRMWARE_CFG_LOG_VERBOSITY_ENTRY_NAME) &&
(!cur_val || !val)) {
ret = kbase_csf_firmware_log_toggle_logging_calls(kbdev, val);
if (ret) {
/* Undo FW configuration changes */
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
config->cur_val = cur_val;
kbase_csf_update_firmware_memory(kbdev, config->address, cur_val);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
return ret;
}
}
/* If we can update the config without firmware reset then
* we need to just trigger FIRMWARE_CONFIG_UPDATE.
*/
if (config->updatable) {
ret = kbase_csf_trigger_firmware_config_update(kbdev);
if (ret)
return ret;
}
/* Wait for the config update to take effect */
if (!config->updatable)
kbase_reset_gpu_wait(kbdev);
} else {
dev_warn(kbdev->dev, "Unexpected write to entry %s/%s", config->name, attr->name);
return -EINVAL;
}
return (ssize_t)count;
}
static const struct sysfs_ops fw_cfg_ops = {
.show = &show_fw_cfg,
.store = &store_fw_cfg,
};
static struct attribute *fw_cfg_attrs[] = {
&fw_cfg_attr_min,
&fw_cfg_attr_max,
&fw_cfg_attr_cur,
NULL,
};
#if (KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE)
ATTRIBUTE_GROUPS(fw_cfg);
#endif
static struct kobj_type fw_cfg_kobj_type = {
.release = &fw_cfg_kobj_release,
.sysfs_ops = &fw_cfg_ops,
#if (KERNEL_VERSION(5, 2, 0) <= LINUX_VERSION_CODE)
.default_groups = fw_cfg_groups,
#else
.default_attrs = fw_cfg_attrs,
#endif
};
int kbase_csf_firmware_cfg_init(struct kbase_device *kbdev)
{
struct firmware_config *config;
kbdev->csf.fw_cfg_kobj =
kobject_create_and_add(CSF_FIRMWARE_CFG_SYSFS_DIR_NAME, &kbdev->dev->kobj);
if (!kbdev->csf.fw_cfg_kobj) {
kobject_put(kbdev->csf.fw_cfg_kobj);
dev_err(kbdev->dev, "Creation of %s sysfs sub-directory failed\n",
CSF_FIRMWARE_CFG_SYSFS_DIR_NAME);
return -ENOMEM;
}
list_for_each_entry(config, &kbdev->csf.firmware_config, node) {
int err;
kbase_csf_read_firmware_memory(kbdev, config->address, &config->cur_val);
if (!strcmp(config->name, CSF_FIRMWARE_CFG_LOG_VERBOSITY_ENTRY_NAME) &&
(config->cur_val)) {
err = kbase_csf_firmware_log_toggle_logging_calls(config->kbdev,
config->cur_val);
if (err) {
kobject_put(&config->kobj);
dev_err(kbdev->dev, "Failed to enable logging (result: %d)", err);
return err;
}
}
err = kobject_init_and_add(&config->kobj, &fw_cfg_kobj_type, kbdev->csf.fw_cfg_kobj,
"%s", config->name);
if (err) {
kobject_put(&config->kobj);
dev_err(kbdev->dev, "Creation of %s sysfs sub-directory failed\n",
config->name);
return err;
}
config->kobj_inited = true;
}
return 0;
}
void kbase_csf_firmware_cfg_term(struct kbase_device *kbdev)
{
while (!list_empty(&kbdev->csf.firmware_config)) {
struct firmware_config *config;
config =
list_first_entry(&kbdev->csf.firmware_config, struct firmware_config, node);
list_del(&config->node);
if (config->kobj_inited) {
kobject_del(&config->kobj);
kobject_put(&config->kobj);
} else
kfree(config);
}
kobject_del(kbdev->csf.fw_cfg_kobj);
kobject_put(kbdev->csf.fw_cfg_kobj);
}
int kbase_csf_firmware_cfg_option_entry_parse(struct kbase_device *kbdev,
const struct kbase_csf_mcu_fw *const fw,
const u32 *entry, unsigned int size, bool updatable)
{
const char *name = (char *)&entry[3];
struct firmware_config *config;
const unsigned int name_len = size - CONFIGURATION_ENTRY_NAME_OFFSET;
CSTD_UNUSED(fw);
/* Allocate enough space for struct firmware_config and the
* configuration option name (with NULL termination)
*/
config = kzalloc(sizeof(*config) + name_len + 1, GFP_KERNEL);
if (!config)
return -ENOMEM;
config->kbdev = kbdev;
config->updatable = updatable;
config->name = (char *)(config + 1);
config->address = entry[0];
config->min = entry[1];
config->max = entry[2];
memcpy(config->name, name, name_len);
config->name[name_len] = 0;
list_add(&config->node, &kbdev->csf.firmware_config);
dev_dbg(kbdev->dev, "Configuration option '%s' at 0x%x range %u-%u", config->name,
config->address, config->min, config->max);
return 0;
}
int kbase_csf_firmware_cfg_fw_wa_enable(struct kbase_device *kbdev)
{
struct firmware_config *config;
/* "quirks_ext" property is optional */
if (!kbdev->csf.quirks_ext)
return 0;
list_for_each_entry(config, &kbdev->csf.firmware_config, node) {
if (strcmp(config->name, CSF_FIRMWARE_CFG_WA_CFG0_ENTRY_NAME))
continue;
dev_info(kbdev->dev, "External quirks 0: 0x%08x", kbdev->csf.quirks_ext[0]);
kbase_csf_update_firmware_memory(kbdev, config->address, kbdev->csf.quirks_ext[0]);
return 0;
}
return -ENOENT;
}
int kbase_csf_firmware_cfg_fw_wa_init(struct kbase_device *kbdev)
{
int ret;
int entry_count;
size_t entry_bytes;
/* "quirks-ext" property is optional and may have no value.
* Also try fallback "quirks_ext" property if it doesn't exist.
*/
entry_count = of_property_count_u32_elems(kbdev->dev->of_node, "quirks-ext");
if (entry_count < 0)
entry_count = of_property_count_u32_elems(kbdev->dev->of_node, "quirks_ext");
if (entry_count < 0)
return 0;
entry_bytes = (size_t)entry_count * sizeof(u32);
kbdev->csf.quirks_ext = kzalloc(entry_bytes, GFP_KERNEL);
if (!kbdev->csf.quirks_ext)
return -ENOMEM;
ret = of_property_read_u32_array(kbdev->dev->of_node, "quirks-ext", kbdev->csf.quirks_ext,
(size_t)entry_count);
if (ret == -EINVAL)
ret = of_property_read_u32_array(kbdev->dev->of_node, "quirks_ext",
kbdev->csf.quirks_ext, (size_t)entry_count);
if (ret == -EINVAL || ret == -ENODATA) {
/* This is unexpected since the property is already accessed for counting the number
* of its elements.
*/
dev_err(kbdev->dev, "\"quirks_ext\" DTB property data read failed");
return ret;
}
if (ret == -EOVERFLOW) {
dev_err(kbdev->dev, "\"quirks_ext\" DTB property data size exceeds 32 bits");
return ret;
}
return kbase_csf_firmware_cfg_fw_wa_enable(kbdev);
}
void kbase_csf_firmware_cfg_fw_wa_term(struct kbase_device *kbdev)
{
kfree(kbdev->csf.quirks_ext);
}
#else
int kbase_csf_firmware_cfg_init(struct kbase_device *kbdev)
{
return 0;
}
void kbase_csf_firmware_cfg_term(struct kbase_device *kbdev)
{
/* !CONFIG_SYSFS: Nothing to do here */
}
int kbase_csf_firmware_cfg_option_entry_parse(struct kbase_device *kbdev,
const struct kbase_csf_mcu_fw *const fw,
const u32 *entry, unsigned int size)
{
return 0;
}
int kbase_csf_firmware_cfg_fw_wa_enable(struct kbase_device *kbdev)
{
return 0;
}
int kbase_csf_firmware_cfg_fw_wa_init(struct kbase_device *kbdev)
{
return 0;
}
#endif /* CONFIG_SYSFS */

View file

@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2020-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_FIRMWARE_CFG_H_
#define _KBASE_CSF_FIRMWARE_CFG_H_
#include <mali_kbase.h>
#include "mali_kbase_csf_firmware.h"
#include <linux/firmware.h>
#define CONFIGURATION_ENTRY_NAME_OFFSET (0xC)
/**
* kbase_csf_firmware_cfg_init - Create the sysfs directory for configuration
* options present in firmware image.
*
* @kbdev: Pointer to the Kbase device
*
* This function would create a sysfs directory and populate it with a
* sub-directory, that would contain a file per attribute, for every
* configuration option parsed from firmware image.
*
* Return: The initialization error code.
*/
int kbase_csf_firmware_cfg_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_cfg_term - Delete the sysfs directory that was created
* for firmware configuration options.
*
* @kbdev: Pointer to the Kbase device
*
*/
void kbase_csf_firmware_cfg_term(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_cfg_option_entry_parse() - Process a
* "configuration option" section.
*
* @kbdev: Kbase device structure
* @fw: Firmware image containing the section
* @entry: Pointer to the section
* @size: Size (in bytes) of the section
* @updatable: Indicates if entry can be updated with FIRMWARE_CONFIG_UPDATE
*
* Read a "configuration option" section adding it to the
* kbase_device:csf.firmware_config list.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_cfg_option_entry_parse(struct kbase_device *kbdev,
const struct kbase_csf_mcu_fw *const fw,
const u32 *entry, unsigned int size, bool updatable);
/**
* kbase_csf_firmware_cfg_fw_wa_enable() - Enable firmware workarounds configuration.
*
* @kbdev: Kbase device structure
*
* Look for the config entry that enables support in FW for workarounds and set it according to
* the firmware workaround configuration before the initial boot or reload of firmware.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_cfg_fw_wa_enable(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_cfg_fw_wa_init() - Initialize firmware workarounds configuration.
*
* @kbdev: Kbase device structure
*
* Retrieve and save the firmware workarounds configuration from device-tree "quirks_ext" property.
* Then, look for the config entry that enables support in FW for workarounds and set it according
* to the configuration before the initial firmware boot.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_cfg_fw_wa_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_cfg_fw_wa_term - Delete local cache for firmware workarounds configuration.
*
* @kbdev: Pointer to the Kbase device
*
*/
void kbase_csf_firmware_cfg_fw_wa_term(struct kbase_device *kbdev);
#endif /* _KBASE_CSF_FIRMWARE_CFG_H_ */

View file

@ -0,0 +1,810 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/file.h>
#include <linux/elf.h>
#include <linux/elfcore.h>
#include <linux/version_compat_defs.h>
#include "mali_kbase.h"
#include "mali_kbase_csf_firmware_core_dump.h"
#include "backend/gpu/mali_kbase_pm_internal.h"
/* Page size in bytes in use by MCU. */
#define FW_PAGE_SIZE 4096
/*
* FW image header core dump data format supported.
* Currently only version 0.1 is supported.
*/
#define FW_CORE_DUMP_DATA_VERSION_MAJOR 0
#define FW_CORE_DUMP_DATA_VERSION_MINOR 1
/* Full version of the image header core dump data format */
#define FW_CORE_DUMP_DATA_VERSION \
((FW_CORE_DUMP_DATA_VERSION_MAJOR << 8) | FW_CORE_DUMP_DATA_VERSION_MINOR)
/* Validity flag to indicate if the MCU registers in the buffer are valid */
#define FW_MCU_STATUS_MASK 0x1
#define FW_MCU_STATUS_VALID (1 << 0)
/* Core dump entry fields */
#define FW_CORE_DUMP_VERSION_INDEX 0
#define FW_CORE_DUMP_START_ADDR_INDEX 1
/* MCU registers stored by a firmware core dump */
struct fw_core_dump_mcu {
u32 r0;
u32 r1;
u32 r2;
u32 r3;
u32 r4;
u32 r5;
u32 r6;
u32 r7;
u32 r8;
u32 r9;
u32 r10;
u32 r11;
u32 r12;
u32 sp;
u32 lr;
u32 pc;
};
/* Any ELF definitions used in this file are from elf.h/elfcore.h except
* when specific 32-bit versions are required (mainly for the
* ELF_PRSTATUS32 note that is used to contain the MCU registers).
*/
/* - 32-bit version of timeval structures used in ELF32 PRSTATUS note. */
struct prstatus32_timeval {
int tv_sec;
int tv_usec;
};
/* - Structure defining ELF32 PRSTATUS note contents, as defined by the
* GNU binutils BFD library used by GDB, in bfd/hosts/x86-64linux.h.
* Note: GDB checks for the size of this structure to be 0x94.
* Modified pr_reg (array containing the Arm 32-bit MCU registers) to
* use u32[18] instead of elf_gregset32_t to prevent introducing new typedefs.
*/
struct elf_prstatus32 {
struct elf_siginfo pr_info; /* Info associated with signal. */
short int pr_cursig; /* Current signal. */
unsigned int pr_sigpend; /* Set of pending signals. */
unsigned int pr_sighold; /* Set of held signals. */
pid_t pr_pid;
pid_t pr_ppid;
pid_t pr_pgrp;
pid_t pr_sid;
struct prstatus32_timeval pr_utime; /* User time. */
struct prstatus32_timeval pr_stime; /* System time. */
struct prstatus32_timeval pr_cutime; /* Cumulative user time. */
struct prstatus32_timeval pr_cstime; /* Cumulative system time. */
u32 pr_reg[18]; /* GP registers. */
int pr_fpvalid; /* True if math copro being used. */
};
/**
* struct fw_core_dump_data - Context for seq_file operations used on 'fw_core_dump'
* debugfs file.
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*/
struct fw_core_dump_data {
struct kbase_device *kbdev;
};
/*
* struct fw_core_dump_seq_off - Iterator for seq_file operations used on 'fw_core_dump'
* debugfs file.
* @interface: current firmware memory interface
* @page_num: current page number (0..) within @interface
*/
struct fw_core_dump_seq_off {
struct kbase_csf_firmware_interface *interface;
u32 page_num;
};
/**
* fw_get_core_dump_mcu - Get the MCU registers saved by a firmware core dump
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @regs: Pointer to a core dump mcu struct where the MCU registers are copied
* to. Should be allocated by the called.
*
* Return: 0 if successfully copied the MCU registers, negative error code otherwise.
*/
static int fw_get_core_dump_mcu(struct kbase_device *kbdev, struct fw_core_dump_mcu *regs)
{
unsigned int i;
u32 status = 0;
u32 data_addr = kbdev->csf.fw_core_dump.mcu_regs_addr;
u32 *data = (u32 *)regs;
/* Check if the core dump entry exposed the buffer */
if (!regs || !kbdev->csf.fw_core_dump.available)
return -EPERM;
/* Check if the data in the buffer is valid, if not, return error */
kbase_csf_read_firmware_memory(kbdev, data_addr, &status);
if ((status & FW_MCU_STATUS_MASK) != FW_MCU_STATUS_VALID)
return -EPERM;
/* According to image header documentation, the MCU registers core dump
* buffer is 32-bit aligned.
*/
for (i = 1; i <= sizeof(struct fw_core_dump_mcu) / sizeof(u32); ++i)
kbase_csf_read_firmware_memory(kbdev, data_addr + i * sizeof(u32), &data[i - 1]);
return 0;
}
/**
* fw_core_dump_fill_elf_header - Initializes an ELF32 header
* @hdr: ELF32 header to initialize
* @sections: Number of entries in the ELF program header table
*
* Initializes an ELF32 header for an ARM 32-bit little-endian
* 'Core file' object file.
*/
static void fw_core_dump_fill_elf_header(struct elf32_hdr *hdr, unsigned int sections)
{
/* Reset all members in header. */
memset(hdr, 0, sizeof(*hdr));
/* Magic number identifying file as an ELF object. */
memcpy(hdr->e_ident, ELFMAG, SELFMAG);
/* Identify file as 32-bit, little-endian, using current
* ELF header version, with no OS or ABI specific ELF
* extensions used.
*/
hdr->e_ident[EI_CLASS] = ELFCLASS32;
hdr->e_ident[EI_DATA] = ELFDATA2LSB;
hdr->e_ident[EI_VERSION] = EV_CURRENT;
hdr->e_ident[EI_OSABI] = ELFOSABI_NONE;
/* 'Core file' type of object file. */
hdr->e_type = ET_CORE;
/* ARM 32-bit architecture (AARCH32) */
hdr->e_machine = EM_ARM;
/* Object file version: the original format. */
hdr->e_version = EV_CURRENT;
/* Offset of program header table in file. */
hdr->e_phoff = sizeof(struct elf32_hdr);
/* No processor specific flags. */
hdr->e_flags = 0;
/* Size of the ELF header in bytes. */
hdr->e_ehsize = sizeof(struct elf32_hdr);
/* Size of the ELF program header entry in bytes. */
hdr->e_phentsize = sizeof(struct elf32_phdr);
/* Number of entries in the program header table. */
hdr->e_phnum = sections;
}
/**
* fw_core_dump_fill_elf_program_header_note - Initializes an ELF32 program header
* for holding auxiliary information
* @phdr: ELF32 program header
* @file_offset: Location of the note in the file in bytes
* @size: Size of the note in bytes.
*
* Initializes an ELF32 program header describing auxiliary information (containing
* one or more notes) of @size bytes alltogether located in the file at offset
* @file_offset.
*/
static void fw_core_dump_fill_elf_program_header_note(struct elf32_phdr *phdr, u32 file_offset,
u32 size)
{
/* Auxiliary information (note) in program header. */
phdr->p_type = PT_NOTE;
/* Location of first note in file in bytes. */
phdr->p_offset = file_offset;
/* Size of all notes combined in bytes. */
phdr->p_filesz = size;
/* Other members not relevant for a note. */
phdr->p_vaddr = 0;
phdr->p_paddr = 0;
phdr->p_memsz = 0;
phdr->p_align = 0;
phdr->p_flags = 0;
}
/**
* fw_core_dump_fill_elf_program_header - Initializes an ELF32 program header for a loadable segment
* @phdr: ELF32 program header to initialize.
* @file_offset: Location of loadable segment in file in bytes
* (aligned to FW_PAGE_SIZE bytes)
* @vaddr: 32-bit virtual address where to write the segment
* (aligned to FW_PAGE_SIZE bytes)
* @size: Size of the segment in bytes.
* @flags: CSF_FIRMWARE_ENTRY_* flags describing access permissions.
*
* Initializes an ELF32 program header describing a loadable segment of
* @size bytes located in the file at offset @file_offset to be loaded
* at virtual address @vaddr with access permissions as described by
* CSF_FIRMWARE_ENTRY_* flags in @flags.
*/
static void fw_core_dump_fill_elf_program_header(struct elf32_phdr *phdr, u32 file_offset,
u32 vaddr, u32 size, u32 flags)
{
/* Loadable segment in program header. */
phdr->p_type = PT_LOAD;
/* Location of segment in file in bytes. Aligned to p_align bytes. */
phdr->p_offset = file_offset;
/* Virtual address of segment. Aligned to p_align bytes. */
phdr->p_vaddr = vaddr;
/* Physical address of segment. Not relevant. */
phdr->p_paddr = 0;
/* Size of segment in file and memory. */
phdr->p_filesz = size;
phdr->p_memsz = size;
/* Alignment of segment in the file and memory in bytes (integral power of 2). */
phdr->p_align = FW_PAGE_SIZE;
/* Set segment access permissions. */
phdr->p_flags = 0;
if (flags & CSF_FIRMWARE_ENTRY_READ)
phdr->p_flags |= PF_R;
if (flags & CSF_FIRMWARE_ENTRY_WRITE)
phdr->p_flags |= PF_W;
if (flags & CSF_FIRMWARE_ENTRY_EXECUTE)
phdr->p_flags |= PF_X;
}
/**
* fw_core_dump_get_prstatus_note_size - Calculates size of a ELF32 PRSTATUS note
* @name: Name given to the PRSTATUS note.
*
* Calculates the size of a 32-bit PRSTATUS note (which contains information
* about a process like the current MCU registers) taking into account
* @name must be padded to a 4-byte multiple.
*
* Return: size of 32-bit PRSTATUS note in bytes.
*/
static unsigned int fw_core_dump_get_prstatus_note_size(char *name)
{
return sizeof(struct elf32_note) + roundup(strlen(name) + 1, 4) +
sizeof(struct elf_prstatus32);
}
/**
* fw_core_dump_fill_elf_prstatus - Initializes an ELF32 PRSTATUS structure
* @prs: ELF32 PRSTATUS note to initialize
* @regs: MCU registers to copy into the PRSTATUS note
*
* Initializes an ELF32 PRSTATUS structure with MCU registers @regs.
* Other process information is N/A for CSF Firmware.
*/
static void fw_core_dump_fill_elf_prstatus(struct elf_prstatus32 *prs,
struct fw_core_dump_mcu *regs)
{
/* Only fill in registers (32-bit) of PRSTATUS note. */
memset(prs, 0, sizeof(*prs));
prs->pr_reg[0] = regs->r0;
prs->pr_reg[1] = regs->r1;
prs->pr_reg[2] = regs->r2;
prs->pr_reg[3] = regs->r3;
prs->pr_reg[4] = regs->r4;
prs->pr_reg[5] = regs->r5;
prs->pr_reg[6] = regs->r0;
prs->pr_reg[7] = regs->r7;
prs->pr_reg[8] = regs->r8;
prs->pr_reg[9] = regs->r9;
prs->pr_reg[10] = regs->r10;
prs->pr_reg[11] = regs->r11;
prs->pr_reg[12] = regs->r12;
prs->pr_reg[13] = regs->sp;
prs->pr_reg[14] = regs->lr;
prs->pr_reg[15] = regs->pc;
}
/**
* fw_core_dump_create_prstatus_note - Creates an ELF32 PRSTATUS note
* @name: Name for the PRSTATUS note
* @prs: ELF32 PRSTATUS structure to put in the PRSTATUS note
* @created_prstatus_note:
* Pointer to the allocated ELF32 PRSTATUS note
*
* Creates an ELF32 note with one PRSTATUS entry containing the
* ELF32 PRSTATUS structure @prs. Caller needs to free the created note in
* @created_prstatus_note.
*
* Return: 0 on failure, otherwise size of ELF32 PRSTATUS note in bytes.
*/
static unsigned int fw_core_dump_create_prstatus_note(char *name, struct elf_prstatus32 *prs,
struct elf32_note **created_prstatus_note)
{
struct elf32_note *note;
unsigned int note_name_sz;
unsigned int note_sz;
/* Allocate memory for ELF32 note containing a PRSTATUS note. */
note_name_sz = strlen(name) + 1;
note_sz = sizeof(struct elf32_note) + roundup(note_name_sz, 4) +
sizeof(struct elf_prstatus32);
note = kmalloc(note_sz, GFP_KERNEL);
if (!note)
return 0;
/* Fill in ELF32 note with one entry for a PRSTATUS note. */
note->n_namesz = note_name_sz;
note->n_descsz = sizeof(struct elf_prstatus32);
note->n_type = NT_PRSTATUS;
memcpy(note + 1, name, note_name_sz);
memcpy((char *)(note + 1) + roundup(note_name_sz, 4), prs, sizeof(*prs));
/* Return pointer and size of the created ELF32 note. */
*created_prstatus_note = note;
return note_sz;
}
/**
* fw_core_dump_write_elf_header - Writes ELF header for the FW core dump
* @m: the seq_file handle
*
* Writes the ELF header of the core dump including program headers for
* memory sections and a note containing the current MCU register
* values.
*
* Excludes memory sections without read access permissions or
* are for protected memory.
*
* The data written is as follows:
* - ELF header
* - ELF PHDRs for memory sections
* - ELF PHDR for program header NOTE
* - ELF PRSTATUS note
* - 0-bytes padding to multiple of ELF_EXEC_PAGESIZE
*
* The actual memory section dumps should follow this (not written
* by this function).
*
* Retrieves the necessary information via the struct
* fw_core_dump_data stored in the private member of the seq_file
* handle.
*
* Return:
* * 0 - success
* * -ENOMEM - not enough memory for allocating ELF32 note
*/
static int fw_core_dump_write_elf_header(struct seq_file *m)
{
struct elf32_hdr hdr;
struct elf32_phdr phdr;
struct fw_core_dump_data *dump_data = m->private;
struct kbase_device *const kbdev = dump_data->kbdev;
struct kbase_csf_firmware_interface *interface;
struct elf_prstatus32 elf_prs;
struct elf32_note *elf_prstatus_note;
unsigned int sections = 0;
unsigned int elf_prstatus_note_size;
u32 elf_prstatus_offset;
u32 elf_phdr_note_offset;
u32 elf_memory_sections_data_offset;
u32 total_pages = 0;
u32 padding_size, *padding;
struct fw_core_dump_mcu regs = { 0 };
/* Count number of memory sections. */
list_for_each_entry(interface, &kbdev->csf.firmware_interfaces, node) {
/* Skip memory sections that cannot be read or are protected. */
if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
(interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
continue;
sections++;
}
/* Prepare ELF header. */
fw_core_dump_fill_elf_header(&hdr, sections + 1);
seq_write(m, &hdr, sizeof(struct elf32_hdr));
elf_prstatus_note_size = fw_core_dump_get_prstatus_note_size("CORE");
/* PHDRs of PT_LOAD type. */
elf_phdr_note_offset = sizeof(struct elf32_hdr) + sections * sizeof(struct elf32_phdr);
/* PHDR of PT_NOTE type. */
elf_prstatus_offset = elf_phdr_note_offset + sizeof(struct elf32_phdr);
elf_memory_sections_data_offset = elf_prstatus_offset + elf_prstatus_note_size;
/* Calculate padding size to page offset. */
padding_size = roundup(elf_memory_sections_data_offset, ELF_EXEC_PAGESIZE) -
elf_memory_sections_data_offset;
elf_memory_sections_data_offset += padding_size;
/* Prepare ELF program header table. */
list_for_each_entry(interface, &kbdev->csf.firmware_interfaces, node) {
/* Skip memory sections that cannot be read or are protected. */
if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
(interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
continue;
fw_core_dump_fill_elf_program_header(&phdr, elf_memory_sections_data_offset,
interface->virtual,
interface->num_pages * FW_PAGE_SIZE,
interface->flags);
seq_write(m, &phdr, sizeof(struct elf32_phdr));
elf_memory_sections_data_offset += interface->num_pages * FW_PAGE_SIZE;
total_pages += interface->num_pages;
}
/* Prepare PHDR of PT_NOTE type. */
fw_core_dump_fill_elf_program_header_note(&phdr, elf_prstatus_offset,
elf_prstatus_note_size);
seq_write(m, &phdr, sizeof(struct elf32_phdr));
/* Prepare ELF note of PRSTATUS type. */
if (fw_get_core_dump_mcu(kbdev, &regs))
dev_dbg(kbdev->dev, "MCU Registers not available, all registers set to zero");
/* Even if MCU Registers are not available the ELF prstatus is still
* filled with the registers equal to zero.
*/
fw_core_dump_fill_elf_prstatus(&elf_prs, &regs);
elf_prstatus_note_size =
fw_core_dump_create_prstatus_note("CORE", &elf_prs, &elf_prstatus_note);
if (elf_prstatus_note_size == 0)
return -ENOMEM;
seq_write(m, elf_prstatus_note, elf_prstatus_note_size);
kfree(elf_prstatus_note);
/* Pad file to page size. */
padding = kzalloc(padding_size, GFP_KERNEL);
seq_write(m, padding, padding_size);
kfree(padding);
return 0;
}
/**
* fw_core_dump_create - Requests firmware to save state for a firmware core dump
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
*
* Return: 0 on success, error code otherwise.
*/
static int fw_core_dump_create(struct kbase_device *kbdev)
{
int err;
/* Ensure MCU is active before requesting the core dump. */
kbase_csf_scheduler_pm_active(kbdev);
err = kbase_csf_scheduler_killable_wait_mcu_active(kbdev);
if (!err)
err = kbase_csf_firmware_req_core_dump(kbdev);
kbase_csf_scheduler_pm_idle(kbdev);
return err;
}
/**
* fw_core_dump_seq_start - seq_file start operation for firmware core dump file
* @m: the seq_file handle
* @_pos: holds the current position in pages
* (0 or most recent position used in previous session)
*
* Starts a seq_file session, positioning the iterator for the session to page @_pos - 1
* within the firmware interface memory sections. @_pos value 0 is used to indicate the
* position of the ELF header at the start of the file.
*
* Retrieves the necessary information via the struct fw_core_dump_data stored in
* the private member of the seq_file handle.
*
* Return:
* * iterator pointer - pointer to iterator struct fw_core_dump_seq_off
* * SEQ_START_TOKEN - special iterator pointer indicating its is the start of the file
* * NULL - iterator could not be allocated
*/
static void *fw_core_dump_seq_start(struct seq_file *m, loff_t *_pos)
{
struct fw_core_dump_data *dump_data = m->private;
struct fw_core_dump_seq_off *data;
struct kbase_csf_firmware_interface *interface;
loff_t pos = *_pos;
if (pos == 0)
return SEQ_START_TOKEN;
/* Move iterator in the right position based on page number within
* available pages of firmware interface memory sections.
*/
pos--; /* ignore start token */
list_for_each_entry(interface, &dump_data->kbdev->csf.firmware_interfaces, node) {
/* Skip memory sections that cannot be read or are protected. */
if ((interface->flags & CSF_FIRMWARE_ENTRY_PROTECTED) ||
(interface->flags & CSF_FIRMWARE_ENTRY_READ) == 0)
continue;
if (pos >= interface->num_pages) {
pos -= interface->num_pages;
} else {
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
data->interface = interface;
data->page_num = pos;
return data;
}
}
return NULL;
}
/**
* fw_core_dump_seq_stop - seq_file stop operation for firmware core dump file
* @m: the seq_file handle
* @v: the current iterator (pointer to struct fw_core_dump_seq_off)
*
* Closes the current session and frees any memory related.
*/
static void fw_core_dump_seq_stop(struct seq_file *m, void *v)
{
CSTD_UNUSED(m);
kfree(v);
}
/**
* fw_core_dump_seq_next - seq_file next operation for firmware core dump file
* @m: the seq_file handle
* @v: the current iterator (pointer to struct fw_core_dump_seq_off)
* @pos: holds the current position in pages
* (0 or most recent position used in previous session)
*
* Moves the iterator @v forward to the next page within the firmware interface
* memory sections and returns the updated position in @pos.
* @v value SEQ_START_TOKEN indicates the ELF header position.
*
* Return:
* * iterator pointer - pointer to iterator struct fw_core_dump_seq_off
* * NULL - iterator could not be allocated
*/
static void *fw_core_dump_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
struct fw_core_dump_data *dump_data = m->private;
struct fw_core_dump_seq_off *data = v;
struct kbase_csf_firmware_interface *interface;
struct list_head *interfaces = &dump_data->kbdev->csf.firmware_interfaces;
/* Is current position at the ELF header ? */
if (v == SEQ_START_TOKEN) {
if (list_empty(interfaces))
return NULL;
/* Prepare iterator for starting at first page in firmware interface
* memory sections.
*/
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return NULL;
data->interface =
list_first_entry(interfaces, struct kbase_csf_firmware_interface, node);
data->page_num = 0;
++*pos;
return data;
}
/* First attempt to satisfy from current firmware interface memory section. */
interface = data->interface;
if (data->page_num + 1 < interface->num_pages) {
data->page_num++;
++*pos;
return data;
}
/* Need next firmware interface memory section. This could be the last one. */
if (list_is_last(&interface->node, interfaces)) {
kfree(data);
return NULL;
}
/* Move to first page in next firmware interface memory section. */
data->interface = list_next_entry(interface, node);
data->page_num = 0;
++*pos;
return data;
}
/**
* fw_core_dump_seq_show - seq_file show operation for firmware core dump file
* @m: the seq_file handle
* @v: the current iterator (pointer to struct fw_core_dump_seq_off)
*
* Writes the current page in a firmware interface memory section indicated
* by the iterator @v to the file. If @v is SEQ_START_TOKEN the ELF
* header is written.
*
* Return: 0 on success, error code otherwise.
*/
static int fw_core_dump_seq_show(struct seq_file *m, void *v)
{
struct fw_core_dump_seq_off *data = v;
struct page *page;
u32 *p;
/* Either write the ELF header or current page. */
if (v == SEQ_START_TOKEN)
return fw_core_dump_write_elf_header(m);
/* Write the current page. */
page = as_page(data->interface->phys[data->page_num]);
p = kbase_kmap_atomic(page);
seq_write(m, p, FW_PAGE_SIZE);
kbase_kunmap_atomic(p);
return 0;
}
/* Sequence file operations for firmware core dump file. */
static const struct seq_operations fw_core_dump_seq_ops = {
.start = fw_core_dump_seq_start,
.next = fw_core_dump_seq_next,
.stop = fw_core_dump_seq_stop,
.show = fw_core_dump_seq_show,
};
/**
* fw_core_dump_debugfs_open - callback for opening the 'fw_core_dump' debugfs file
* @inode: inode of the file
* @file: file pointer
*
* Prepares for servicing a write request to request a core dump from firmware and
* a read request to retrieve the core dump.
*
* Returns an error if the firmware is not initialized yet.
*
* Return: 0 on success, error code otherwise.
*/
static int fw_core_dump_debugfs_open(struct inode *inode, struct file *file)
{
struct kbase_device *const kbdev = inode->i_private;
struct fw_core_dump_data *dump_data;
int ret;
/* Fail if firmware is not initialized yet. */
if (!kbdev->csf.firmware_inited) {
ret = -ENODEV;
goto open_fail;
}
/* Open a sequence file for iterating through the pages in the
* firmware interface memory pages. seq_open stores a
* struct seq_file * in the private_data field of @file.
*/
ret = seq_open(file, &fw_core_dump_seq_ops);
if (ret)
goto open_fail;
/* Allocate a context for sequence file operations. */
dump_data = kmalloc(sizeof(*dump_data), GFP_KERNEL);
if (!dump_data) {
ret = -ENOMEM;
goto out;
}
/* Kbase device will be shared with sequence file operations. */
dump_data->kbdev = kbdev;
/* Link our sequence file context. */
((struct seq_file *)file->private_data)->private = dump_data;
return 0;
out:
seq_release(inode, file);
open_fail:
return ret;
}
/**
* fw_core_dump_debugfs_write - callback for a write to the 'fw_core_dump' debugfs file
* @file: file pointer
* @ubuf: user buffer containing data to store
* @count: number of bytes in user buffer
* @ppos: file position
*
* Any data written to the file triggers a firmware core dump request which
* subsequently can be retrieved by reading from the file.
*
* Return: @count if the function succeeded. An error code on failure.
*/
static ssize_t fw_core_dump_debugfs_write(struct file *file, const char __user *ubuf, size_t count,
loff_t *ppos)
{
ssize_t err;
struct fw_core_dump_data *dump_data = ((struct seq_file *)file->private_data)->private;
struct kbase_device *const kbdev = dump_data->kbdev;
CSTD_UNUSED(ubuf);
CSTD_UNUSED(ppos);
err = fw_core_dump_create(kbdev);
return err ? err : (ssize_t)count;
}
/**
* fw_core_dump_debugfs_release - callback for releasing the 'fw_core_dump' debugfs file
* @inode: inode of the file
* @file: file pointer
*
* Return: 0 on success, error code otherwise.
*/
static int fw_core_dump_debugfs_release(struct inode *inode, struct file *file)
{
struct fw_core_dump_data *dump_data = ((struct seq_file *)file->private_data)->private;
seq_release(inode, file);
kfree(dump_data);
return 0;
}
/* Debugfs file operations for firmware core dump file. */
static const struct file_operations kbase_csf_fw_core_dump_fops = {
.owner = THIS_MODULE,
.open = fw_core_dump_debugfs_open,
.read = seq_read,
.write = fw_core_dump_debugfs_write,
.llseek = seq_lseek,
.release = fw_core_dump_debugfs_release,
};
void kbase_csf_firmware_core_dump_init(struct kbase_device *const kbdev)
{
#if IS_ENABLED(CONFIG_DEBUG_FS)
debugfs_create_file("fw_core_dump", 0600, kbdev->mali_debugfs_directory, kbdev,
&kbase_csf_fw_core_dump_fops);
#endif /* CONFIG_DEBUG_FS */
}
int kbase_csf_firmware_core_dump_entry_parse(struct kbase_device *kbdev, const u32 *entry)
{
/* Casting to u16 as version is defined by bits 15:0 */
kbdev->csf.fw_core_dump.version = (u16)entry[FW_CORE_DUMP_VERSION_INDEX];
if (kbdev->csf.fw_core_dump.version != FW_CORE_DUMP_DATA_VERSION)
return -EPERM;
kbdev->csf.fw_core_dump.mcu_regs_addr = entry[FW_CORE_DUMP_START_ADDR_INDEX];
kbdev->csf.fw_core_dump.available = true;
return 0;
}

View file

@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2021-2022 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_FIRMWARE_CORE_DUMP_H_
#define _KBASE_CSF_FIRMWARE_CORE_DUMP_H_
struct kbase_device;
/** Offset of the last field of core dump entry from the image header */
#define CORE_DUMP_ENTRY_START_ADDR_OFFSET (0x4)
/**
* kbase_csf_firmware_core_dump_entry_parse() - Parse a "core dump" entry from
* the image header.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @entry: Pointer to section.
*
* Read a "core dump" entry from the image header, check the version for
* compatibility and store the address pointer.
*
* Return: 0 if successfully parse entry, negative error code otherwise.
*/
int kbase_csf_firmware_core_dump_entry_parse(struct kbase_device *kbdev, const u32 *entry);
/**
* kbase_csf_firmware_core_dump_init() - Initialize firmware core dump support
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* Must be zero-initialized.
*
* Creates the fw_core_dump debugfs file through which to request a firmware
* core dump. The created debugfs file is cleaned up as part of kbdev debugfs
* cleanup.
*
* The fw_core_dump debugs file that case be used in the following way:
*
* To explicitly request core dump:
* echo 1 >/sys/kernel/debug/mali0/fw_core_dump
*
* To output current core dump (after explicitly requesting a core dump, or
* kernel driver reported an internal firmware error):
* cat /sys/kernel/debug/mali0/fw_core_dump
*/
void kbase_csf_firmware_core_dump_init(struct kbase_device *const kbdev);
#endif /* _KBASE_CSF_FIRMWARE_CORE_DUMP_H_ */

View file

@ -0,0 +1,546 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include "backend/gpu/mali_kbase_pm_internal.h"
#include <csf/mali_kbase_csf_firmware_log.h>
#include <csf/mali_kbase_csf_trace_buffer.h>
#include <linux/debugfs.h>
#include <linux/string.h>
#include <linux/workqueue.h>
/*
* ARMv7 instruction: Branch with Link calls a subroutine at a PC-relative address.
*/
#define ARMV7_T1_BL_IMM_INSTR 0xd800f000
/*
* ARMv7 instruction: Branch with Link calls a subroutine at a PC-relative address, maximum
* negative jump offset.
*/
#define ARMV7_T1_BL_IMM_RANGE_MIN -16777216
/*
* ARMv7 instruction: Branch with Link calls a subroutine at a PC-relative address, maximum
* positive jump offset.
*/
#define ARMV7_T1_BL_IMM_RANGE_MAX 16777214
/*
* ARMv7 instruction: Double NOP instructions.
*/
#define ARMV7_DOUBLE_NOP_INSTR 0xbf00bf00
#if defined(CONFIG_DEBUG_FS)
static int kbase_csf_firmware_log_enable_mask_read(void *data, u64 *val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct firmware_trace_buffer *tb =
kbase_csf_firmware_get_trace_buffer(kbdev, KBASE_CSFFW_LOG_BUF_NAME);
if (tb == NULL) {
dev_err(kbdev->dev, "Couldn't get the firmware trace buffer");
return -EIO;
}
/* The enabled traces limited to u64 here, regarded practical */
*val = kbase_csf_firmware_trace_buffer_get_active_mask64(tb);
return 0;
}
static int kbase_csf_firmware_log_enable_mask_write(void *data, u64 val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct firmware_trace_buffer *tb =
kbase_csf_firmware_get_trace_buffer(kbdev, KBASE_CSFFW_LOG_BUF_NAME);
u64 new_mask;
unsigned int enable_bits_count;
if (tb == NULL) {
dev_err(kbdev->dev, "Couldn't get the firmware trace buffer");
return -EIO;
}
/* Ignore unsupported types */
enable_bits_count = kbase_csf_firmware_trace_buffer_get_trace_enable_bits_count(tb);
if (enable_bits_count > 64) {
dev_dbg(kbdev->dev, "Limit enabled bits count from %u to 64", enable_bits_count);
enable_bits_count = 64;
}
new_mask = val & (UINT64_MAX >> (64 - enable_bits_count));
if (new_mask != kbase_csf_firmware_trace_buffer_get_active_mask64(tb))
return kbase_csf_firmware_trace_buffer_set_active_mask64(tb, new_mask);
else
return 0;
}
static int kbasep_csf_firmware_log_debugfs_open(struct inode *in, struct file *file)
{
struct kbase_device *kbdev = in->i_private;
file->private_data = kbdev;
dev_dbg(kbdev->dev, "Opened firmware trace buffer dump debugfs file");
return 0;
}
static ssize_t kbasep_csf_firmware_log_debugfs_read(struct file *file, char __user *buf,
size_t size, loff_t *ppos)
{
struct kbase_device *kbdev = file->private_data;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
unsigned int n_read;
unsigned long not_copied;
/* Limit reads to the kernel dump buffer size */
size_t mem = MIN(size, FIRMWARE_LOG_DUMP_BUF_SIZE);
int ret;
struct firmware_trace_buffer *tb =
kbase_csf_firmware_get_trace_buffer(kbdev, KBASE_CSFFW_LOG_BUF_NAME);
if (tb == NULL) {
dev_err(kbdev->dev, "Couldn't get the firmware trace buffer");
return -EIO;
}
if (atomic_cmpxchg(&fw_log->busy, 0, 1) != 0)
return -EBUSY;
/* Reading from userspace is only allowed in manual mode or auto-discard mode */
if (fw_log->mode != KBASE_CSF_FIRMWARE_LOG_MODE_MANUAL &&
fw_log->mode != KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_DISCARD) {
ret = -EINVAL;
goto out;
}
n_read = kbase_csf_firmware_trace_buffer_read_data(tb, fw_log->dump_buf, mem);
/* Do the copy, if we have obtained some trace data */
not_copied = (n_read) ? copy_to_user(buf, fw_log->dump_buf, n_read) : 0;
if (not_copied) {
dev_err(kbdev->dev, "Couldn't copy trace buffer data to user space buffer");
ret = -EFAULT;
goto out;
}
*ppos += n_read;
ret = (int)n_read;
out:
atomic_set(&fw_log->busy, 0);
return ret;
}
static int kbase_csf_firmware_log_mode_read(void *data, u64 *val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
*val = fw_log->mode;
return 0;
}
static int kbase_csf_firmware_log_mode_write(void *data, u64 val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
int ret = 0;
if (atomic_cmpxchg(&fw_log->busy, 0, 1) != 0)
return -EBUSY;
if (val == fw_log->mode)
goto out;
switch (val) {
case KBASE_CSF_FIRMWARE_LOG_MODE_MANUAL:
cancel_delayed_work_sync(&fw_log->poll_work);
break;
case KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_PRINT:
case KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_DISCARD:
schedule_delayed_work(
&fw_log->poll_work,
msecs_to_jiffies((unsigned int)atomic_read(&fw_log->poll_period_ms)));
break;
default:
ret = -EINVAL;
goto out;
}
fw_log->mode = val;
out:
atomic_set(&fw_log->busy, 0);
return ret;
}
static int kbase_csf_firmware_log_poll_period_read(void *data, u64 *val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
*val = (u64)atomic_read(&fw_log->poll_period_ms);
return 0;
}
static int kbase_csf_firmware_log_poll_period_write(void *data, u64 val)
{
struct kbase_device *kbdev = (struct kbase_device *)data;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
atomic_set(&fw_log->poll_period_ms, val);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(kbase_csf_firmware_log_enable_mask_fops,
kbase_csf_firmware_log_enable_mask_read,
kbase_csf_firmware_log_enable_mask_write, "%llx\n");
static const struct file_operations kbasep_csf_firmware_log_debugfs_fops = {
.owner = THIS_MODULE,
.open = kbasep_csf_firmware_log_debugfs_open,
.read = kbasep_csf_firmware_log_debugfs_read,
.llseek = no_llseek,
};
DEFINE_DEBUGFS_ATTRIBUTE(kbase_csf_firmware_log_mode_fops, kbase_csf_firmware_log_mode_read,
kbase_csf_firmware_log_mode_write, "%llu\n");
DEFINE_DEBUGFS_ATTRIBUTE(kbase_csf_firmware_log_poll_period_fops,
kbase_csf_firmware_log_poll_period_read,
kbase_csf_firmware_log_poll_period_write, "%llu\n");
#endif /* CONFIG_DEBUG_FS */
static void kbase_csf_firmware_log_discard_buffer(struct kbase_device *kbdev)
{
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
struct firmware_trace_buffer *tb =
kbase_csf_firmware_get_trace_buffer(kbdev, KBASE_CSFFW_LOG_BUF_NAME);
if (tb == NULL) {
dev_dbg(kbdev->dev, "Can't get the trace buffer, firmware log discard skipped");
return;
}
if (atomic_cmpxchg(&fw_log->busy, 0, 1) != 0)
return;
kbase_csf_firmware_trace_buffer_discard(tb);
atomic_set(&fw_log->busy, 0);
}
static void kbase_csf_firmware_log_poll(struct work_struct *work)
{
struct kbase_device *kbdev =
container_of(work, struct kbase_device, csf.fw_log.poll_work.work);
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
if (fw_log->mode == KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_PRINT)
kbase_csf_firmware_log_dump_buffer(kbdev);
else if (fw_log->mode == KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_DISCARD)
kbase_csf_firmware_log_discard_buffer(kbdev);
else
return;
schedule_delayed_work(&fw_log->poll_work,
msecs_to_jiffies((unsigned int)atomic_read(&fw_log->poll_period_ms)));
}
int kbase_csf_firmware_log_init(struct kbase_device *kbdev)
{
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
int err = 0;
#if defined(CONFIG_DEBUG_FS)
struct dentry *dentry;
#endif /* CONFIG_DEBUG_FS */
/* Add one byte for null-termination */
fw_log->dump_buf = kmalloc(FIRMWARE_LOG_DUMP_BUF_SIZE + 1, GFP_KERNEL);
if (fw_log->dump_buf == NULL) {
err = -ENOMEM;
goto out;
}
/* Ensure null-termination for all strings */
fw_log->dump_buf[FIRMWARE_LOG_DUMP_BUF_SIZE] = 0;
/* Set default log polling period */
atomic_set(&fw_log->poll_period_ms, KBASE_CSF_FIRMWARE_LOG_POLL_PERIOD_MS_DEFAULT);
INIT_DEFERRABLE_WORK(&fw_log->poll_work, kbase_csf_firmware_log_poll);
#ifdef CONFIG_MALI_FW_TRACE_MODE_AUTO_DISCARD
fw_log->mode = KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_DISCARD;
schedule_delayed_work(&fw_log->poll_work,
msecs_to_jiffies(KBASE_CSF_FIRMWARE_LOG_POLL_PERIOD_MS_DEFAULT));
#elif defined(CONFIG_MALI_FW_TRACE_MODE_AUTO_PRINT)
fw_log->mode = KBASE_CSF_FIRMWARE_LOG_MODE_AUTO_PRINT;
schedule_delayed_work(&fw_log->poll_work,
msecs_to_jiffies(KBASE_CSF_FIRMWARE_LOG_POLL_PERIOD_MS_DEFAULT));
#else /* CONFIG_MALI_FW_TRACE_MODE_MANUAL */
fw_log->mode = KBASE_CSF_FIRMWARE_LOG_MODE_MANUAL;
#endif
atomic_set(&fw_log->busy, 0);
#if !defined(CONFIG_DEBUG_FS)
return 0;
#else /* !CONFIG_DEBUG_FS */
dentry = debugfs_create_file("fw_trace_enable_mask", 0644, kbdev->mali_debugfs_directory,
kbdev, &kbase_csf_firmware_log_enable_mask_fops);
if (IS_ERR_OR_NULL(dentry)) {
dev_err(kbdev->dev, "Unable to create fw_trace_enable_mask\n");
err = -ENOENT;
goto free_out;
}
dentry = debugfs_create_file("fw_traces", 0444, kbdev->mali_debugfs_directory, kbdev,
&kbasep_csf_firmware_log_debugfs_fops);
if (IS_ERR_OR_NULL(dentry)) {
dev_err(kbdev->dev, "Unable to create fw_traces\n");
err = -ENOENT;
goto free_out;
}
dentry = debugfs_create_file("fw_trace_mode", 0644, kbdev->mali_debugfs_directory, kbdev,
&kbase_csf_firmware_log_mode_fops);
if (IS_ERR_OR_NULL(dentry)) {
dev_err(kbdev->dev, "Unable to create fw_trace_mode\n");
err = -ENOENT;
goto free_out;
}
dentry = debugfs_create_file("fw_trace_poll_period_ms", 0644, kbdev->mali_debugfs_directory,
kbdev, &kbase_csf_firmware_log_poll_period_fops);
if (IS_ERR_OR_NULL(dentry)) {
dev_err(kbdev->dev, "Unable to create fw_trace_poll_period_ms");
err = -ENOENT;
goto free_out;
}
return 0;
free_out:
kfree(fw_log->dump_buf);
fw_log->dump_buf = NULL;
#endif /* CONFIG_DEBUG_FS */
out:
return err;
}
void kbase_csf_firmware_log_term(struct kbase_device *kbdev)
{
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
if (fw_log->dump_buf) {
cancel_delayed_work_sync(&fw_log->poll_work);
kfree(fw_log->dump_buf);
fw_log->dump_buf = NULL;
}
}
void kbase_csf_firmware_log_dump_buffer(struct kbase_device *kbdev)
{
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
u8 *buf = fw_log->dump_buf, *p, *pnewline, *pend, *pendbuf;
unsigned int read_size, remaining_size;
struct firmware_trace_buffer *tb =
kbase_csf_firmware_get_trace_buffer(kbdev, KBASE_CSFFW_LOG_BUF_NAME);
if (tb == NULL) {
dev_dbg(kbdev->dev, "Can't get the trace buffer, firmware trace dump skipped");
return;
}
if (atomic_cmpxchg(&fw_log->busy, 0, 1) != 0)
return;
/* FW should only print complete messages, so there's no need to handle
* partial messages over multiple invocations of this function
*/
p = buf;
pendbuf = &buf[FIRMWARE_LOG_DUMP_BUF_SIZE];
while ((read_size = kbase_csf_firmware_trace_buffer_read_data(tb, p, pendbuf - p))) {
pend = p + read_size;
p = buf;
while (p < pend && (pnewline = memchr(p, '\n', (size_t)(pend - p)))) {
/* Null-terminate the string */
*pnewline = 0;
dev_err(kbdev->dev, "FW> %s", p);
p = pnewline + 1;
}
remaining_size = pend - p;
if (!remaining_size) {
p = buf;
} else if (remaining_size < FIRMWARE_LOG_DUMP_BUF_SIZE) {
/* Copy unfinished string to the start of the buffer */
memmove(buf, p, remaining_size);
p = &buf[remaining_size];
} else {
/* Print abnormally long string without newlines */
dev_err(kbdev->dev, "FW> %s", buf);
p = buf;
}
}
if (p != buf) {
/* Null-terminate and print last unfinished string */
*p = 0;
dev_err(kbdev->dev, "FW> %s", buf);
}
atomic_set(&fw_log->busy, 0);
}
void kbase_csf_firmware_log_parse_logging_call_list_entry(struct kbase_device *kbdev,
const uint32_t *entry)
{
kbdev->csf.fw_log.func_call_list_va_start = entry[0];
kbdev->csf.fw_log.func_call_list_va_end = entry[1];
}
/**
* toggle_logging_calls_in_loaded_image - Toggles FW log func calls in loaded FW image.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @enable: Whether to enable or disable the function calls.
*/
static void toggle_logging_calls_in_loaded_image(struct kbase_device *kbdev, bool enable)
{
uint32_t bl_instruction, diff;
uint32_t imm11, imm10, i1, i2, j1, j2, sign;
uint32_t calling_address = 0, callee_address = 0;
uint32_t list_entry = kbdev->csf.fw_log.func_call_list_va_start;
const uint32_t list_va_end = kbdev->csf.fw_log.func_call_list_va_end;
if (list_entry == 0 || list_va_end == 0)
return;
if (enable) {
for (; list_entry < list_va_end; list_entry += 2 * sizeof(uint32_t)) {
/* Read calling address */
kbase_csf_read_firmware_memory(kbdev, list_entry, &calling_address);
/* Read callee address */
kbase_csf_read_firmware_memory(kbdev, list_entry + sizeof(uint32_t),
&callee_address);
diff = callee_address - calling_address - 4;
sign = !!(diff & 0x80000000);
if (ARMV7_T1_BL_IMM_RANGE_MIN > (int32_t)diff ||
ARMV7_T1_BL_IMM_RANGE_MAX < (int32_t)diff) {
dev_warn(kbdev->dev, "FW log patch 0x%x out of range, skipping",
calling_address);
continue;
}
i1 = (diff & 0x00800000) >> 23;
j1 = !i1 ^ sign;
i2 = (diff & 0x00400000) >> 22;
j2 = !i2 ^ sign;
imm11 = (diff & 0xffe) >> 1;
imm10 = (diff & 0x3ff000) >> 12;
/* Compose BL instruction */
bl_instruction = ARMV7_T1_BL_IMM_INSTR;
bl_instruction |= j1 << 29;
bl_instruction |= j2 << 27;
bl_instruction |= imm11 << 16;
bl_instruction |= sign << 10;
bl_instruction |= imm10;
/* Patch logging func calls in their load location */
dev_dbg(kbdev->dev, "FW log patch 0x%x: 0x%x\n", calling_address,
bl_instruction);
kbase_csf_update_firmware_memory_exe(kbdev, calling_address,
bl_instruction);
}
} else {
for (; list_entry < list_va_end; list_entry += 2 * sizeof(uint32_t)) {
/* Read calling address */
kbase_csf_read_firmware_memory(kbdev, list_entry, &calling_address);
/* Overwrite logging func calls with 2 NOP instructions */
kbase_csf_update_firmware_memory_exe(kbdev, calling_address,
ARMV7_DOUBLE_NOP_INSTR);
}
}
}
int kbase_csf_firmware_log_toggle_logging_calls(struct kbase_device *kbdev, u32 val)
{
unsigned long flags;
struct kbase_csf_firmware_log *fw_log = &kbdev->csf.fw_log;
bool mcu_inactive;
bool resume_needed = false;
int ret = 0;
struct kbase_csf_scheduler *scheduler = &kbdev->csf.scheduler;
if (atomic_cmpxchg(&fw_log->busy, 0, 1) != 0)
return -EBUSY;
/* Suspend all the active CS groups */
dev_dbg(kbdev->dev, "Suspend all the active CS groups");
kbase_csf_scheduler_lock(kbdev);
while (scheduler->state != SCHED_SUSPENDED) {
kbase_csf_scheduler_unlock(kbdev);
kbase_csf_scheduler_pm_suspend(kbdev);
kbase_csf_scheduler_lock(kbdev);
resume_needed = true;
}
/* Wait for the MCU to get disabled */
dev_info(kbdev->dev, "Wait for the MCU to get disabled");
ret = kbase_pm_killable_wait_for_desired_state(kbdev);
if (ret) {
dev_err(kbdev->dev, "wait for PM state failed when toggling FW logging calls");
ret = -EAGAIN;
goto out;
}
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
mcu_inactive = kbase_pm_is_mcu_inactive(kbdev, kbdev->pm.backend.mcu_state);
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
if (!mcu_inactive) {
dev_err(kbdev->dev,
"MCU not inactive after PM state wait when toggling FW logging calls");
ret = -EAGAIN;
goto out;
}
/* Toggle FW logging call in the loaded FW image */
toggle_logging_calls_in_loaded_image(kbdev, val);
dev_dbg(kbdev->dev, "FW logging: %s", val ? "enabled" : "disabled");
out:
kbase_csf_scheduler_unlock(kbdev);
if (resume_needed)
/* Resume queue groups and start mcu */
kbase_csf_scheduler_pm_resume(kbdev);
atomic_set(&fw_log->busy, 0);
return ret;
}

View file

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2022 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_FIRMWARE_LOG_H_
#define _KBASE_CSF_FIRMWARE_LOG_H_
#include <mali_kbase.h>
/** Offset of the last field of functions call list entry from the image header */
#define FUNC_CALL_LIST_ENTRY_NAME_OFFSET (0x8)
/*
* Firmware log dumping buffer size.
*/
#define FIRMWARE_LOG_DUMP_BUF_SIZE PAGE_SIZE
/**
* kbase_csf_firmware_log_init - Initialize firmware log handling.
*
* @kbdev: Pointer to the Kbase device
*
* Return: The initialization error code.
*/
int kbase_csf_firmware_log_init(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_log_term - Terminate firmware log handling.
*
* @kbdev: Pointer to the Kbase device
*/
void kbase_csf_firmware_log_term(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_log_dump_buffer - Read remaining data in the firmware log
* buffer and print it to dmesg.
*
* @kbdev: Pointer to the Kbase device
*/
void kbase_csf_firmware_log_dump_buffer(struct kbase_device *kbdev);
/**
* kbase_csf_firmware_log_parse_logging_call_list_entry - Parse FW logging function call list entry.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @entry: Pointer to section.
*/
void kbase_csf_firmware_log_parse_logging_call_list_entry(struct kbase_device *kbdev,
const uint32_t *entry);
/**
* kbase_csf_firmware_log_toggle_logging_calls - Enables/Disables FW logging function calls.
*
* @kbdev: Instance of a GPU platform device that implements a CSF interface.
* @val: Configuration option value.
*
* Return: 0 if successful, negative error code on failure
*/
int kbase_csf_firmware_log_toggle_logging_calls(struct kbase_device *kbdev, u32 val);
#endif /* _KBASE_CSF_FIRMWARE_LOG_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,352 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase.h"
#include "mali_kbase_csf_fw_io.h"
#include <mali_kbase_linux.h>
#include <linux/mutex.h>
static inline u32 input_page_read(const u32 *const input, const u32 offset)
{
WARN_ON(offset % sizeof(u32));
return input[offset / sizeof(u32)];
}
static inline void input_page_write(u32 *const input, const u32 offset, const u32 value)
{
WARN_ON(offset % sizeof(u32));
input[offset / sizeof(u32)] = value;
}
static inline void input_page_partial_write(u32 *const input, const u32 offset, u32 value, u32 mask)
{
WARN_ON(offset % sizeof(u32));
input[offset / sizeof(u32)] = (input_page_read(input, offset) & ~mask) | (value & mask);
}
static inline u32 output_page_read(const u32 *const output, const u32 offset)
{
WARN_ON(offset % sizeof(u32));
return output[offset / sizeof(u32)];
}
#ifdef CONFIG_MALI_DEBUG
static inline void output_page_write(u32 *const output, const u32 offset, const u32 value)
{
WARN_ON(offset % sizeof(u32));
output[offset / sizeof(u32)] = value;
}
#endif /* CONFIG_MALI_DEBUG */
void kbase_csf_fw_io_init(struct kbase_csf_fw_io *fw_io, struct kbase_device *kbdev)
{
spin_lock_init(&fw_io->lock);
bitmap_zero(fw_io->status, KBASEP_FW_IO_STATUS_NUM_BITS);
fw_io->kbdev = kbdev;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_init);
void kbase_csf_fw_io_term(struct kbase_csf_fw_io *fw_io)
{
/* Nothing to do. */
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_term);
int kbase_csf_fw_io_groups_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_num)
{
struct kbasep_csf_fw_io_group_pages **groups_pages = &fw_io->pages.groups_pages;
*groups_pages = kcalloc(group_num, sizeof(**groups_pages), GFP_KERNEL);
return *groups_pages ? 0 : -ENOMEM;
}
int kbase_csf_fw_io_streams_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_num)
{
struct kbasep_csf_fw_io_stream_pages **streams_pages =
&fw_io->pages.groups_pages[group_id].streams_pages;
*streams_pages = kcalloc(stream_num, sizeof(**streams_pages), GFP_KERNEL);
return *streams_pages ? 0 : -ENOMEM;
}
void kbase_csf_fw_io_set_global_pages(struct kbase_csf_fw_io *fw_io, void *input, void *output)
{
fw_io->pages.input = input;
fw_io->pages.output = output;
}
void kbase_csf_fw_io_set_group_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, void *input,
void *output)
{
fw_io->pages.groups_pages[group_id].input = input;
fw_io->pages.groups_pages[group_id].output = output;
}
void kbase_csf_fw_io_set_stream_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
void *input, void *output)
{
fw_io->pages.groups_pages[group_id].streams_pages[stream_id].input = input;
fw_io->pages.groups_pages[group_id].streams_pages[stream_id].output = output;
}
void kbase_csf_fw_io_pages_term(struct kbase_csf_fw_io *fw_io, u32 group_num)
{
struct kbasep_csf_fw_io_pages *fw_io_pages = &fw_io->pages;
if (fw_io_pages->groups_pages) {
unsigned int gid;
for (gid = 0; gid < group_num; ++gid)
kfree(fw_io_pages->groups_pages[gid].streams_pages);
kfree(fw_io_pages->groups_pages);
fw_io_pages->groups_pages = NULL;
}
}
void kbase_csf_fw_io_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "glob input w: reg %08x val %08x\n", offset, value);
input_page_write(pages->input, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_write);
void kbase_csf_fw_io_global_write_mask(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value,
u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "glob input w: reg %08x val %08x mask %08x\n", offset, value, mask);
input_page_partial_write(pages->input, offset, value, mask);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_write_mask);
u32 kbase_csf_fw_io_global_input_read(struct kbase_csf_fw_io *fw_io, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
u32 val;
val = input_page_read(pages->input, offset);
dev_dbg(kbdev->dev, "glob input r: reg %08x val %08x\n", offset, val);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_input_read);
u32 kbase_csf_fw_io_global_read(struct kbase_csf_fw_io *fw_io, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
u32 val;
val = output_page_read(pages->output, offset);
dev_dbg(kbdev->dev, "glob output r: reg %08x val %08x\n", offset, val);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_read);
void kbase_csf_fw_io_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "csg input w: reg %08x val %08x csg_id %u\n", offset, value, group_id);
input_page_write(group_pages->input, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_write);
void kbase_csf_fw_io_group_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value, u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "csg input w: reg %08x val %08x mask %08x csg_id %u\n", offset, value,
mask, group_id);
input_page_partial_write(group_pages->input, offset, value, mask);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_write_mask);
u32 kbase_csf_fw_io_group_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
u32 val;
val = input_page_read(group_pages->input, offset);
dev_dbg(kbdev->dev, "csg input r: reg %08x val %08x csg_id %u\n", offset, val, group_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_input_read);
u32 kbase_csf_fw_io_group_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
u32 val;
val = output_page_read(group_pages->output, offset);
dev_dbg(kbdev->dev, "csg output r: reg %08x val %08x csg_id %u\n", offset, val, group_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_read);
void kbase_csf_fw_io_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "cs input w: reg %08x val %08x csg_id %u cs_id %u\n", offset, value,
group_id, stream_id);
input_page_write(stream_pages->input, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_write);
void kbase_csf_fw_io_stream_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value, u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "cs input w: reg %08x val %08x mask %08x csg_id %u cs_id %u\n", offset,
value, mask, group_id, stream_id);
input_page_partial_write(stream_pages->input, offset, value, mask);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_write_mask);
u32 kbase_csf_fw_io_stream_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
u32 val;
val = input_page_read(stream_pages->input, offset);
dev_dbg(kbdev->dev, "cs input r: reg %08x val %08x csg_id %u cs_id %u\n", offset, val,
group_id, stream_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_input_read);
u32 kbase_csf_fw_io_stream_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
u32 val;
val = output_page_read(stream_pages->output, offset);
dev_dbg(kbdev->dev, "cs output r: reg %08x val %08x csg_id %u cs_id %u\n", offset, val,
group_id, stream_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_read);
void kbase_csf_fw_io_set_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
set_bit(KBASEP_FW_IO_STATUS_GPU_SUSPENDED, fw_io->status);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_set_status_gpu_suspended);
void kbase_csf_fw_io_clear_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
clear_bit(KBASEP_FW_IO_STATUS_GPU_SUSPENDED, fw_io->status);
}
bool kbase_csf_fw_io_check_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
return !bitmap_empty(fw_io->status, KBASEP_FW_IO_STATUS_NUM_BITS);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_check_status_gpu_suspended);
#if IS_ENABLED(CONFIG_MALI_DEBUG)
void kbase_csf_fw_io_mock_fw_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
dev_dbg(kbdev->dev, "mock fw glob output w: reg %08x val %08x\n", offset, value);
output_page_write(pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_global_write);
void kbase_csf_fw_io_mock_fw_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
dev_dbg(kbdev->dev, "mock fw csg output w: reg %08x val %08x csg_id %u\n", offset, value,
group_id);
output_page_write(group_pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_group_write);
void kbase_csf_fw_io_mock_fw_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id,
u32 stream_id, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
dev_dbg(kbdev->dev, "mock fw cs output w: reg %08x val %08x csg_id %u cs_id %u\n", offset,
value, group_id, stream_id);
output_page_write(stream_pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_stream_write);
#endif /* IS_ENABLED(CONFIG_MALI_DEBUG) */

View file

@ -0,0 +1,467 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_FW_IO_H_
#define _KBASE_CSF_FW_IO_H_
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
/** The wait completed because the GPU was lost. */
#define KBASE_CSF_FW_IO_WAIT_GPU_LOST 1
/**
* enum kbasep_csf_fw_io_status_bits - Status bits for firmware I/O interface.
*
* @KBASEP_FW_IO_STATUS_GPU_SUSPENDED: The GPU is suspended.
* @KBASEP_FW_IO_STATUS_NUM_BITS: Number of bits used to encode the status.
*/
enum kbasep_csf_fw_io_status_bits {
KBASEP_FW_IO_STATUS_GPU_SUSPENDED = 0,
KBASEP_FW_IO_STATUS_NUM_BITS,
};
/**
* struct kbasep_csf_fw_io_stream_pages - Addresses to CS I/O pages.
*
* @input: Address of CS input page.
* @output: Address of CS output page.
*/
struct kbasep_csf_fw_io_stream_pages {
void *input;
void *output;
};
/**
* struct kbasep_csf_fw_io_group_pages - Addresses to CSG I/O pages.
*
* @input: Address of CSG input page.
* @output: Address of CSG output page.
* @streams_pages: Array of CSs' I/O pages.
*/
struct kbasep_csf_fw_io_group_pages {
void *input;
void *output;
struct kbasep_csf_fw_io_stream_pages *streams_pages;
};
/**
* struct kbasep_csf_fw_io_pages - Addresses to FW I/O pages.
*
* @input: Address of global input page.
* @output: Address of global output page.
* @groups_pages: Array of CSGs' I/O pages.
*/
struct kbasep_csf_fw_io_pages {
void *input;
void *output;
struct kbasep_csf_fw_io_group_pages *groups_pages;
};
/**
* struct kbase_csf_fw_io - Manager of firmware input/output interface.
*
* @lock: Mutex to serialize access to the interface.
* @status: Internal status of the MCU interface.
* @pages: Addresses to FW I/O pages
* @kbdev: Pointer to the instance of a GPU platform device that implements a CSF interface.
*/
struct kbase_csf_fw_io {
spinlock_t lock;
DECLARE_BITMAP(status, KBASEP_FW_IO_STATUS_NUM_BITS);
struct kbasep_csf_fw_io_pages pages;
struct kbase_device *kbdev;
};
/**
* kbase_csf_fw_io_init() - Initialize manager of firmware input/output interface.
*
* @fw_io: Firmware I/O interface to initialize.
* @kbdev: Pointer to the instance of a GPU platform device that implements a CSF interface.
*/
void kbase_csf_fw_io_init(struct kbase_csf_fw_io *fw_io, struct kbase_device *kbdev);
/**
* kbase_csf_fw_io_term() - Terminate manager of firmware input/output interface.
*
* @fw_io: Firmware I/O interface to terminate.
*/
void kbase_csf_fw_io_term(struct kbase_csf_fw_io *fw_io);
/**
* kbase_csf_fw_io_groups_pages_init() - Initialize an array for CSGs' I/O pages.
*
* @fw_io: Firmware I/O interface.
* @group_num: Number of CSGs.
*
* Return: 0 on success, -ENOMEM on failed initialization.
*/
int kbase_csf_fw_io_groups_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_num);
/**
* kbase_csf_fw_io_streams_pages_init() - Initialize an array for CSs' I/O pages.
*
* @fw_io: Firmware I/O interface.
* @group_id: CSG index.
* @stream_num: Number of CSs.
*
* Return: 0 on success, -ENOMEM on failed initialization.
*/
int kbase_csf_fw_io_streams_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_num);
/**
* kbase_csf_fw_io_set_global_pages() - Set addresses to global I/O pages.
*
* @fw_io: Firmware I/O interface.
* @input: Source address to global input page.
* @output: Source address to global output page.
*/
void kbase_csf_fw_io_set_global_pages(struct kbase_csf_fw_io *fw_io, void *input, void *output);
/**
* kbase_csf_fw_io_set_group_pages() - Set addresses to CSG's I/O pages.
*
* @fw_io: Firmware I/O interface.
* @group_id: CSG index.
* @input: Source address to CSG's input page.
* @output: Source address to CSG's output page.
*/
void kbase_csf_fw_io_set_group_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, void *input,
void *output);
/**
* kbase_csf_fw_io_set_stream_pages() - Set addresses to CS's I/O pages.
*
* @fw_io: Firmware I/O interface.
* @group_id: CSG index.
* @stream_id: CS index.
* @input: Source address to CS's input page.
* @output: Source address to CS's output page.
*/
void kbase_csf_fw_io_set_stream_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
void *input, void *output);
/**
* kbase_csf_fw_io_pages_term() - Terminate arrays of addresses to CSGs and CSs I/O pages.
*
* @fw_io: Firmware I/O interface.
* @group_num: Number of CSGs.
*/
void kbase_csf_fw_io_pages_term(struct kbase_csf_fw_io *fw_io, u32 group_num);
/**
* kbase_csf_fw_io_open() - Start a transaction with the firmware input/output interface.
*
* @fw_io: Firmware I/O interface to open.
* @flags: Pointer to the memory location that would store the previous
* interrupt state
*
* Return: 0 on success, otherwise an error code reflecting the status of the
* interface.
*/
static inline int kbase_csf_fw_io_open(struct kbase_csf_fw_io *fw_io, unsigned long *flags)
{
if (test_bit(KBASEP_FW_IO_STATUS_GPU_SUSPENDED, fw_io->status))
return -KBASE_CSF_FW_IO_WAIT_GPU_LOST;
spin_lock_irqsave(&fw_io->lock, *flags);
return 0;
}
/**
* kbase_csf_fw_io_open_force() - Force a transaction with the firmware input/output interface.
*
* @fw_io: Firmware I/O interface to open.
* @flags: Pointer to the memory location that would store the previous
* interrupt state
*
* This function forces the start of a transaction regardless of the status
* of the interface.
*/
static inline void kbase_csf_fw_io_open_force(struct kbase_csf_fw_io *fw_io, unsigned long *flags)
{
spin_lock_irqsave(&fw_io->lock, *flags);
}
/**
* kbase_csf_fw_io_close() - End a transaction with the firmware input/output interface.
*
* @fw_io: Firmware I/O interface to close.
* @flags: Previously stored interrupt state when FW IO interrupt
* spinlock was acquired.
*/
static inline void kbase_csf_fw_io_close(struct kbase_csf_fw_io *fw_io, unsigned long flags)
{
spin_unlock_irqrestore(&fw_io->lock, flags);
}
/**
* kbase_csf_fw_io_assert_opened() - Assert if a transaction with the firmware input/output
* interface has started.
*
* @fw_io: Firmware I/O interface.
*/
static inline void kbase_csf_fw_io_assert_opened(struct kbase_csf_fw_io *fw_io)
{
lockdep_assert_held(&fw_io->lock);
}
/**
* kbase_csf_fw_io_global_write() - Write a word in the global input page.
*
* @fw_io: Firmware I/O manager.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value);
/**
* kbase_csf_fw_io_global_write_mask() - Write part of a word in the global input page.
*
* @fw_io: Firmware I/O manager.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
* @mask: Bitmask with the bits to be modified set.
*/
void kbase_csf_fw_io_global_write_mask(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value,
u32 mask);
/**
* kbase_csf_fw_io_global_input_read() - Read a word in the global input page.
*
* @fw_io: Firmware I/O manager.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from the global input page.
*/
u32 kbase_csf_fw_io_global_input_read(struct kbase_csf_fw_io *fw_io, u32 offset);
/**
* kbase_csf_fw_io_global_read() - Read a word in the global output page.
*
* @fw_io: Firmware I/O manager.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from the global output page.
*/
u32 kbase_csf_fw_io_global_read(struct kbase_csf_fw_io *fw_io, u32 offset);
/**
* kbase_csf_fw_io_group_write() - Write a word in a CSG's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value);
/**
* kbase_csf_fw_io_group_write_mask() - Write part of a word in a CSG's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
* @mask: Bitmask with the bits to be modified set.
*/
void kbase_csf_fw_io_group_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value, u32 mask);
/**
* kbase_csf_fw_io_group_input_read() - Read a word in a CSG's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from a CSG's input page.
*/
u32 kbase_csf_fw_io_group_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset);
/**
* kbase_csf_fw_io_group_read() - Read a word in a CSG's output page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from the CSG's output page.
*/
u32 kbase_csf_fw_io_group_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset);
/**
* kbase_csf_fw_io_stream_write() - Write a word in a CS's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @stream_id: CS index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value);
/**
* kbase_csf_fw_io_stream_write_mask() - Write part of a word in a CS's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @stream_id: CS index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
* @mask: Bitmask with the bits to be modified set.
*/
void kbase_csf_fw_io_stream_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value, u32 mask);
/**
* kbase_csf_fw_io_stream_input_read() - Read a word in a CS's input page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @stream_id: CS index.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from a CS's input page.
*/
u32 kbase_csf_fw_io_stream_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset);
/**
* kbase_csf_fw_io_stream_read() - Read a word in a CS's output page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @stream_id: CS index.
* @offset: Offset of the word to be read, in bytes.
*
* Return: Value of the word read from the CS's output page.
*/
u32 kbase_csf_fw_io_stream_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset);
/**
* kbase_csf_fw_io_set_status_gpu_suspended() - Set GPU_SUSPENDED FW I/O status bit.
*
* @fw_io: Firmware I/O manager.
*/
void kbase_csf_fw_io_set_status_gpu_suspended(struct kbase_csf_fw_io *fw_io);
/**
* kbase_csf_fw_io_clear_status_gpu_suspended() - Clear GPU_SUSPENDED FW I/O status bit.
*
* @fw_io: Firmware I/O manager.
*/
void kbase_csf_fw_io_clear_status_gpu_suspended(struct kbase_csf_fw_io *fw_io);
/**
* kbase_csf_fw_io_check_status_gpu_suspended() - Check if GPU_SUSPENDED FW I/O status bit is set.
*
* @fw_io: Firmware I/O manager.
*
* Return: True if GPU_SUSPENDED FW I/O status bit is set, false otherwise.
*/
bool kbase_csf_fw_io_check_status_gpu_suspended(struct kbase_csf_fw_io *fw_io);
/**
* kbase_csf_fw_io_wait_event_timeout() - Wait until condition gets true, timeout
* occurs or a GPU_SUSPENDED FW I/O status bit is set. The rest of the functionalities is equal
* to wait_event_timeout().
*
* @fw_io: Firmware I/O manager.
* @wq_head: The waitqueue to wait on.
* @condition: C expression for the event to wait for
* @timeout: Timeout, in jiffies
*
* Return: Remaining jiffies (at least 1) on success,
* 0 on timeout,
* negative KBASE_CSF_FW_IO_WAIT_LOST error if GPU_SUSPENDED FW I/O status bit is set.
*/
#define kbase_csf_fw_io_wait_event_timeout(fw_io, wq_head, condition, timeout) \
({ \
int __ret; \
int __wait_remaining = wait_event_timeout( \
wq_head, (condition) || kbase_csf_fw_io_check_status_gpu_suspended(fw_io), \
timeout); \
__ret = kbasep_csf_fw_io_handle_wait_result(fw_io, __wait_remaining); \
__ret; \
})
/**
* kbasep_csf_fw_io_handle_wait_result() - Private function to handle the wait_event_timeout()
* result.
*
* @fw_io: Firmware I/O manager
* @wait_remaining: Remaining jiffies returned by wait_event_timeout()
*
* Return: Remaining jiffies (at least 1) on success,
* 0 on timeout,
* negative KBASE_CSF_FW_IO_WAIT_LOST error if GPU_SUSPENDED FW I/O status bit is set.
*/
static inline int kbasep_csf_fw_io_handle_wait_result(struct kbase_csf_fw_io *fw_io,
int wait_remaining)
{
return kbase_csf_fw_io_check_status_gpu_suspended(fw_io) ? -KBASE_CSF_FW_IO_WAIT_GPU_LOST :
wait_remaining;
}
#if IS_ENABLED(CONFIG_MALI_DEBUG) || IS_ENABLED(CONFIG_MALI_NO_MALI)
/**
* kbase_csf_fw_io_mock_fw_global_write() - Mock a FW write to the global output page.
*
* @fw_io: Firmware I/O manager.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_mock_fw_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value);
/**
* kbase_csf_fw_io_mock_fw_group_write() - Mock a FW write to CSG's output page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_mock_fw_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value);
/**
* kbase_csf_fw_io_mock_fw_stream_write() - Mock a FW write to CS's output page.
*
* @fw_io: Firmware I/O manager.
* @group_id: CSG index.
* @stream_id: CS index.
* @offset: Offset of the word to write, in bytes.
* @value: Value to be written.
*/
void kbase_csf_fw_io_mock_fw_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id,
u32 stream_id, u32 offset, u32 value);
#endif /* IS_ENABLED(CONFIG_MALI_DEBUG) || IS_ENABLED(CONFIG_MALI_NO_MALI) */
#endif /* _KBASE_CSF_FW_IO_H_ */

View file

@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase.h"
#include "mali_kbase_csf_fw_io.h"
#include <mali_kbase_linux.h>
#include <linux/mutex.h>
static inline u32 input_page_read(const u32 *const input, const u32 offset)
{
WARN_ON(offset % sizeof(u32));
return input[offset / sizeof(u32)];
}
static inline void input_page_write(u32 *const input, const u32 offset, const u32 value)
{
WARN_ON(offset % sizeof(u32));
input[offset / sizeof(u32)] = value;
}
static inline void input_page_partial_write(u32 *const input, const u32 offset, u32 value, u32 mask)
{
WARN_ON(offset % sizeof(u32));
input[offset / sizeof(u32)] = (input_page_read(input, offset) & ~mask) | (value & mask);
}
static inline u32 output_page_read(const u32 *const output, const u32 offset)
{
WARN_ON(offset % sizeof(u32));
return output[offset / sizeof(u32)];
}
static inline void output_page_write(u32 *const output, const u32 offset, const u32 value)
{
WARN_ON(offset % sizeof(u32));
output[offset / sizeof(u32)] = value;
}
void kbase_csf_fw_io_init(struct kbase_csf_fw_io *fw_io, struct kbase_device *kbdev)
{
spin_lock_init(&fw_io->lock);
bitmap_zero(fw_io->status, KBASEP_FW_IO_STATUS_NUM_BITS);
fw_io->kbdev = kbdev;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_init);
void kbase_csf_fw_io_term(struct kbase_csf_fw_io *fw_io)
{
/* Nothing to do. */
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_term);
int kbase_csf_fw_io_groups_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_num)
{
struct kbasep_csf_fw_io_group_pages **groups_pages = &fw_io->pages.groups_pages;
*groups_pages = kcalloc(group_num, sizeof(**groups_pages), GFP_KERNEL);
return *groups_pages ? 0 : -ENOMEM;
}
int kbase_csf_fw_io_streams_pages_init(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_num)
{
struct kbasep_csf_fw_io_stream_pages **streams_pages =
&fw_io->pages.groups_pages[group_id].streams_pages;
*streams_pages = kcalloc(stream_num, sizeof(**streams_pages), GFP_KERNEL);
return *streams_pages ? 0 : -ENOMEM;
}
void kbase_csf_fw_io_set_global_pages(struct kbase_csf_fw_io *fw_io, void *input, void *output)
{
fw_io->pages.input = input;
fw_io->pages.output = output;
}
void kbase_csf_fw_io_set_group_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, void *input,
void *output)
{
fw_io->pages.groups_pages[group_id].input = input;
fw_io->pages.groups_pages[group_id].output = output;
}
void kbase_csf_fw_io_set_stream_pages(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
void *input, void *output)
{
fw_io->pages.groups_pages[group_id].streams_pages[stream_id].input = input;
fw_io->pages.groups_pages[group_id].streams_pages[stream_id].output = output;
}
void kbase_csf_fw_io_pages_term(struct kbase_csf_fw_io *fw_io, u32 group_num)
{
struct kbasep_csf_fw_io_pages *fw_io_pages = &fw_io->pages;
if (fw_io_pages->groups_pages) {
unsigned int gid;
for (gid = 0; gid < group_num; ++gid)
kfree(fw_io_pages->groups_pages[gid].streams_pages);
kfree(fw_io_pages->groups_pages);
fw_io_pages->groups_pages = NULL;
}
}
void kbase_csf_fw_io_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "glob input w: reg %08x val %08x\n", offset, value);
input_page_write(pages->input, offset, value);
if (offset == GLB_REQ) {
/* NO_MALI: Immediately acknowledge requests - except for PRFCNT_ENABLE
* and PRFCNT_SAMPLE. These will be processed along with the
* corresponding performance counter registers when the global doorbell
* is rung in order to emulate the performance counter sampling behavior
* of the real firmware.
*/
const u32 ack = output_page_read(pages->output, GLB_ACK);
const u32 req_mask = ~(GLB_REQ_PRFCNT_ENABLE_MASK | GLB_REQ_PRFCNT_SAMPLE_MASK);
const u32 toggled = (value ^ ack) & req_mask;
output_page_write(pages->output, GLB_ACK, ack ^ toggled);
}
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_write);
void kbase_csf_fw_io_global_write_mask(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value,
u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "glob input w: reg %08x val %08x mask %08x\n", offset, value, mask);
/* NO_MALI: Go through existing function to capture writes */
kbase_csf_fw_io_global_write(
fw_io, offset, (input_page_read(pages->input, offset) & ~mask) | (value & mask));
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_write_mask);
u32 kbase_csf_fw_io_global_input_read(struct kbase_csf_fw_io *fw_io, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
u32 val;
val = input_page_read(pages->input, offset);
dev_dbg(kbdev->dev, "glob input r: reg %08x val %08x\n", offset, val);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_input_read);
u32 kbase_csf_fw_io_global_read(struct kbase_csf_fw_io *fw_io, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
u32 val;
val = output_page_read(pages->output, offset);
dev_dbg(kbdev->dev, "glob output r: reg %08x val %08x\n", offset, val);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_global_read);
void kbase_csf_fw_io_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "csg input w: reg %08x val %08x csg_id %u\n", offset, value, group_id);
input_page_write(group_pages->input, offset, value);
if (offset == CSG_REQ) {
/* NO_MALI: Immediately acknowledge requests */
output_page_write(group_pages->output, CSG_ACK, value);
}
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_write);
void kbase_csf_fw_io_group_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value, u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "csg input w: reg %08x val %08x mask %08x csg_id %u\n", offset, value,
mask, group_id);
/* NO_MALI: Go through existing function to capture writes */
kbase_csf_fw_io_group_write(fw_io, group_id, offset,
(input_page_read(group_pages->input, offset) & ~mask) |
(value & mask));
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_write_mask);
u32 kbase_csf_fw_io_group_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
u32 val;
val = input_page_read(group_pages->input, offset);
dev_dbg(kbdev->dev, "csg input r: reg %08x val %08x csg_id %u\n", offset, val, group_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_input_read);
u32 kbase_csf_fw_io_group_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
u32 val;
val = output_page_read(group_pages->output, offset);
dev_dbg(kbdev->dev, "csg output r: reg %08x val %08x csg_id %u\n", offset, val, group_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_group_read);
void kbase_csf_fw_io_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "cs input w: reg %08x val %08x csg_id %u cs_id %u\n", offset, value,
group_id, stream_id);
input_page_write(stream_pages->input, offset, value);
if (offset == CS_REQ) {
/* NO_MALI: Immediately acknowledge requests */
output_page_write(stream_pages->output, CS_ACK, value);
}
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_write);
void kbase_csf_fw_io_stream_write_mask(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset, u32 value, u32 mask)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
lockdep_assert_held(&fw_io->lock);
dev_dbg(kbdev->dev, "cs input w: reg %08x val %08x mask %08x csg_id %u cs_id %u\n", offset,
value, mask, group_id, stream_id);
/* NO_MALI: Go through existing function to capture writes */
kbase_csf_fw_io_stream_write(fw_io, group_id, stream_id, offset,
(input_page_read(stream_pages->input, offset) & ~mask) |
(value & mask));
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_write_mask);
u32 kbase_csf_fw_io_stream_input_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
u32 val;
val = input_page_read(stream_pages->input, offset);
dev_dbg(kbdev->dev, "cs input r: reg %08x val %08x csg_id %u cs_id %u\n", offset, val,
group_id, stream_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_input_read);
u32 kbase_csf_fw_io_stream_read(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 stream_id,
u32 offset)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
u32 val;
val = output_page_read(stream_pages->output, offset);
dev_dbg(kbdev->dev, "cs output r: reg %08x val %08x csg_id %u cs_id %u\n", offset, val,
group_id, stream_id);
return val;
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_stream_read);
void kbase_csf_fw_io_set_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
set_bit(KBASEP_FW_IO_STATUS_GPU_SUSPENDED, fw_io->status);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_set_status_gpu_suspended);
void kbase_csf_fw_io_clear_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
clear_bit(KBASEP_FW_IO_STATUS_GPU_SUSPENDED, fw_io->status);
}
bool kbase_csf_fw_io_check_status_gpu_suspended(struct kbase_csf_fw_io *fw_io)
{
return !bitmap_empty(fw_io->status, KBASEP_FW_IO_STATUS_NUM_BITS);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_check_status_gpu_suspended);
void kbase_csf_fw_io_mock_fw_global_write(struct kbase_csf_fw_io *fw_io, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_pages *pages = &fw_io->pages;
dev_dbg(kbdev->dev, "mock fw glob output w: reg %08x val %08x\n", offset, value);
output_page_write(pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_global_write);
void kbase_csf_fw_io_mock_fw_group_write(struct kbase_csf_fw_io *fw_io, u32 group_id, u32 offset,
u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_group_pages *group_pages = &fw_io->pages.groups_pages[group_id];
dev_dbg(kbdev->dev, "mock fw csg output w: reg %08x val %08x csg_id %u\n", offset, value,
group_id);
output_page_write(group_pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_group_write);
void kbase_csf_fw_io_mock_fw_stream_write(struct kbase_csf_fw_io *fw_io, u32 group_id,
u32 stream_id, u32 offset, u32 value)
{
const struct kbase_device *const kbdev = fw_io->kbdev;
struct kbasep_csf_fw_io_stream_pages *stream_pages =
&fw_io->pages.groups_pages[group_id].streams_pages[stream_id];
dev_dbg(kbdev->dev, "mock fw cs output w: reg %08x val %08x csg_id %u cs_id %u\n", offset,
value, group_id, stream_id);
output_page_write(stream_pages->output, offset, value);
}
KBASE_EXPORT_TEST_API(kbase_csf_fw_io_mock_fw_stream_write);

View file

@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2024 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#include <mali_kbase_mem_flags.h>
#include "mali_kbase_csf_heap_context_alloc.h"
/* Size of one heap context structure, in bytes. */
#define HEAP_CTX_SIZE ((u32)32)
/**
* sub_alloc - Sub-allocate a heap context from a GPU memory region
*
* @ctx_alloc: Pointer to the heap context allocator.
*
* Return: GPU virtual address of the allocated heap context or 0 on failure.
*/
static u64 sub_alloc(struct kbase_csf_heap_context_allocator *const ctx_alloc)
{
struct kbase_context *const kctx = ctx_alloc->kctx;
unsigned long heap_nr = 0;
u32 ctx_offset = 0;
u64 heap_gpu_va = 0;
struct kbase_vmap_struct mapping;
void *ctx_ptr = NULL;
lockdep_assert_held(&ctx_alloc->lock);
heap_nr = find_first_zero_bit(ctx_alloc->in_use, MAX_TILER_HEAPS);
if (unlikely(heap_nr >= MAX_TILER_HEAPS)) {
dev_dbg(kctx->kbdev->dev, "No free tiler heap contexts in the pool");
return 0;
}
ctx_offset = heap_nr * ctx_alloc->heap_context_size_aligned;
heap_gpu_va = ctx_alloc->gpu_va + ctx_offset;
ctx_ptr = kbase_vmap_prot(kctx, heap_gpu_va, ctx_alloc->heap_context_size_aligned,
KBASE_REG_CPU_WR, &mapping);
if (unlikely(!ctx_ptr)) {
dev_err(kctx->kbdev->dev, "Failed to map tiler heap context %lu (0x%llX)\n",
heap_nr, heap_gpu_va);
return 0;
}
memset(ctx_ptr, 0, ctx_alloc->heap_context_size_aligned);
kbase_vunmap(ctx_ptr, &mapping);
bitmap_set(ctx_alloc->in_use, heap_nr, 1);
dev_dbg(kctx->kbdev->dev, "Allocated tiler heap context %lu (0x%llX)\n", heap_nr,
heap_gpu_va);
return heap_gpu_va;
}
/**
* evict_heap_context - Evict the data of heap context from GPU's L2 cache.
*
* @ctx_alloc: Pointer to the heap context allocator.
* @heap_gpu_va: The GPU virtual address of a heap context structure to free.
*
* This function is called when memory for the heap context is freed. It uses the
* FLUSH_PA_RANGE command to evict the data of heap context, so on older CSF GPUs
* there is nothing done. The whole GPU cache is anyways expected to be flushed
* on older GPUs when initial chunks of the heap are freed just before the memory
* for heap context is freed.
*/
static void evict_heap_context(struct kbase_csf_heap_context_allocator *const ctx_alloc,
u64 const heap_gpu_va)
{
struct kbase_context *const kctx = ctx_alloc->kctx;
u32 offset_in_bytes = (u32)(heap_gpu_va - ctx_alloc->gpu_va);
u32 offset_within_page = offset_in_bytes & ~PAGE_MASK;
u32 page_index = offset_in_bytes >> PAGE_SHIFT;
struct tagged_addr page = kbase_get_gpu_phy_pages(ctx_alloc->region)[page_index];
phys_addr_t heap_context_pa = as_phys_addr_t(page) + offset_within_page;
lockdep_assert_held(&ctx_alloc->lock);
/* There is no need to take vm_lock here as the ctx_alloc region is protected
* via a nonzero no_user_free_count. The region and the backing page can't
* disappear whilst this function is executing. Flush type is passed as FLUSH_PT
* to CLN+INV L2 only.
*/
kbase_mmu_flush_pa_range(kctx->kbdev, kctx, heap_context_pa,
ctx_alloc->heap_context_size_aligned, KBASE_MMU_OP_FLUSH_PT);
}
/**
* sub_free - Free a heap context sub-allocated from a GPU memory region
*
* @ctx_alloc: Pointer to the heap context allocator.
* @heap_gpu_va: The GPU virtual address of a heap context structure to free.
*/
static void sub_free(struct kbase_csf_heap_context_allocator *const ctx_alloc,
u64 const heap_gpu_va)
{
struct kbase_context *const kctx = ctx_alloc->kctx;
u32 ctx_offset = 0;
unsigned int heap_nr = 0;
lockdep_assert_held(&ctx_alloc->lock);
if (WARN_ON(!ctx_alloc->region))
return;
if (WARN_ON(heap_gpu_va < ctx_alloc->gpu_va))
return;
ctx_offset = (u32)(heap_gpu_va - ctx_alloc->gpu_va);
if (WARN_ON(ctx_offset >= (ctx_alloc->region->nr_pages << PAGE_SHIFT)) ||
WARN_ON(ctx_offset % ctx_alloc->heap_context_size_aligned))
return;
evict_heap_context(ctx_alloc, heap_gpu_va);
heap_nr = ctx_offset / ctx_alloc->heap_context_size_aligned;
dev_dbg(kctx->kbdev->dev, "Freed tiler heap context %d (0x%llX)\n", heap_nr, heap_gpu_va);
bitmap_clear(ctx_alloc->in_use, heap_nr, 1);
}
int kbase_csf_heap_context_allocator_init(struct kbase_csf_heap_context_allocator *const ctx_alloc,
struct kbase_context *const kctx)
{
const u32 gpu_cache_line_size = (1U << kctx->kbdev->gpu_props.log2_line_size);
/* We cannot pre-allocate GPU memory here because the
* custom VA zone may not have been created yet.
*/
ctx_alloc->kctx = kctx;
ctx_alloc->heap_context_size_aligned = (HEAP_CTX_SIZE + gpu_cache_line_size - 1) &
~(gpu_cache_line_size - 1);
mutex_init(&ctx_alloc->lock);
dev_dbg(kctx->kbdev->dev, "Initialized a tiler heap context allocator\n");
return 0;
}
void kbase_csf_heap_context_allocator_term(struct kbase_csf_heap_context_allocator *const ctx_alloc)
{
struct kbase_context *const kctx = ctx_alloc->kctx;
dev_dbg(kctx->kbdev->dev, "Terminating tiler heap context allocator\n");
if (ctx_alloc->region) {
kbase_gpu_vm_lock(kctx);
WARN_ON(!kbase_va_region_is_no_user_free(ctx_alloc->region));
kbase_va_region_no_user_free_dec(ctx_alloc->region);
kbase_mem_free_region(kctx, ctx_alloc->region);
kbase_gpu_vm_unlock(kctx);
}
mutex_destroy(&ctx_alloc->lock);
}
u64 kbase_csf_heap_context_allocator_alloc(struct kbase_csf_heap_context_allocator *const ctx_alloc)
{
struct kbase_context *const kctx = ctx_alloc->kctx;
base_mem_alloc_flags flags = BASE_MEM_PROT_GPU_RD | BASE_MEM_PROT_GPU_WR |
BASE_MEM_PROT_CPU_WR | BASEP_MEM_NO_USER_FREE |
BASE_MEM_PROT_CPU_RD;
u64 nr_pages = PFN_UP(MAX_TILER_HEAPS * ctx_alloc->heap_context_size_aligned);
u64 heap_gpu_va = 0;
/* Calls to this function are inherently asynchronous, with respect to
* MMU operations.
*/
const enum kbase_caller_mmu_sync_info mmu_sync_info = CALLER_MMU_ASYNC;
mutex_lock(&ctx_alloc->lock);
/* If the pool of heap contexts wasn't already allocated then
* allocate it.
*/
if (!ctx_alloc->region) {
ctx_alloc->region = kbase_mem_alloc(kctx, nr_pages, nr_pages, 0, &flags,
&ctx_alloc->gpu_va, mmu_sync_info);
}
/* If the pool still isn't allocated then an error occurred. */
if (unlikely(!ctx_alloc->region))
dev_dbg(kctx->kbdev->dev, "Failed to allocate a pool of tiler heap contexts");
else
heap_gpu_va = sub_alloc(ctx_alloc);
mutex_unlock(&ctx_alloc->lock);
return heap_gpu_va;
}
void kbase_csf_heap_context_allocator_free(struct kbase_csf_heap_context_allocator *const ctx_alloc,
u64 const heap_gpu_va)
{
mutex_lock(&ctx_alloc->lock);
sub_free(ctx_alloc, heap_gpu_va);
mutex_unlock(&ctx_alloc->lock);
}

View file

@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include <mali_kbase.h>
#ifndef _KBASE_CSF_HEAP_CONTEXT_ALLOC_H_
#define _KBASE_CSF_HEAP_CONTEXT_ALLOC_H_
/**
* kbase_csf_heap_context_allocator_init - Initialize an allocator for heap
* contexts
* @ctx_alloc: Pointer to the heap context allocator to initialize.
* @kctx: Pointer to the kbase context.
*
* This function must be called only when a kbase context is instantiated.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_heap_context_allocator_init(struct kbase_csf_heap_context_allocator *const ctx_alloc,
struct kbase_context *const kctx);
/**
* kbase_csf_heap_context_allocator_term - Terminate an allocator for heap
* contexts
* @ctx_alloc: Pointer to the heap context allocator to terminate.
*/
void kbase_csf_heap_context_allocator_term(struct kbase_csf_heap_context_allocator *const ctx_alloc);
/**
* kbase_csf_heap_context_allocator_alloc - Allocate a heap context structure
*
* @ctx_alloc: Pointer to the heap context allocator.
*
* If this function is successful then it returns the address of a
* zero-initialized heap context structure for use by the firmware.
*
* Return: GPU virtual address of the allocated heap context or 0 on failure.
*/
u64 kbase_csf_heap_context_allocator_alloc(struct kbase_csf_heap_context_allocator *const ctx_alloc);
/**
* kbase_csf_heap_context_allocator_free - Free a heap context structure
*
* @ctx_alloc: Pointer to the heap context allocator.
* @heap_gpu_va: The GPU virtual address of a heap context structure that
* was allocated for the firmware.
*
* This function returns a heap context structure to the free pool of unused
* contexts for possible reuse by a future call to
* @kbase_csf_heap_context_allocator_alloc.
*/
void kbase_csf_heap_context_allocator_free(struct kbase_csf_heap_context_allocator *const ctx_alloc,
u64 const heap_gpu_va);
#endif /* _KBASE_CSF_HEAP_CONTEXT_ALLOC_H_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,437 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2018-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_KCPU_H_
#define _KBASE_CSF_KCPU_H_
#include <mali_kbase_fence.h>
#include <mali_kbase_sync.h>
#include <linux/version_compat_defs.h>
/* The maximum number of KCPU commands in flight, enqueueing more commands
* than this value shall block.
*/
#define KBASEP_KCPU_QUEUE_SIZE ((size_t)256)
/**
* struct kbase_kcpu_command_import_info - Structure which holds information
* about the buffer to be imported
*
* @gpu_va: Address of the buffer to be imported.
*/
struct kbase_kcpu_command_import_info {
u64 gpu_va;
};
/**
* struct kbase_kcpu_command_fence_info - Structure which holds information about the
* fence object enqueued in the kcpu command queue
*
* @fence_cb: Fence callback
* @fence: Fence
* @kcpu_queue: kcpu command queue
* @fence_has_force_signaled: fence has forced signaled after fence timeouted
*/
struct kbase_kcpu_command_fence_info {
struct dma_fence_cb fence_cb;
struct dma_fence *fence;
struct kbase_kcpu_command_queue *kcpu_queue;
bool fence_has_force_signaled;
};
/**
* struct kbase_kcpu_command_cqs_set_info - Structure which holds information
* about CQS objects for the kcpu CQS set command
*
* @objs: Array of structures which define CQS objects to be used by
* the kcpu command.
* @nr_objs: Number of CQS objects in the array.
*/
struct kbase_kcpu_command_cqs_set_info {
struct base_cqs_set *objs;
unsigned int nr_objs;
};
/**
* struct kbase_kcpu_command_cqs_wait_info - Structure which holds information
* about CQS objects for the kcpu CQS wait command
*
* @objs: Array of structures which define CQS objects to be used by
* the kcpu command.
* @signaled: Bit array used to report the status of the CQS wait objects.
* 1 is signaled, 0 otherwise.
* @nr_objs: Number of CQS objects in the array.
* @inherit_err_flags: Bit-pattern for the CQSs in the array who's error field
* to be served as the source for importing into the
* queue's error-state.
*/
struct kbase_kcpu_command_cqs_wait_info {
struct base_cqs_wait_info *objs;
unsigned long *signaled;
unsigned int nr_objs;
u32 inherit_err_flags;
};
/**
* struct kbase_kcpu_command_cqs_set_operation_info - Structure which holds information
* about CQS objects for the kcpu CQS timeline set command
*
* @objs: Array of structures which define CQS timeline objects to be used by
* the kcpu command.
* @nr_objs: Number of CQS objects in the array.
*/
struct kbase_kcpu_command_cqs_set_operation_info {
struct base_cqs_set_operation_info *objs;
unsigned int nr_objs;
};
/**
* struct kbase_kcpu_command_cqs_wait_operation_info - Structure which holds information
* about CQS objects for the kcpu CQS timeline wait command
*
* @objs: Array of structures which define CQS timeline objects to be used by
* the kcpu command.
* @signaled: Bit array used to report the status of the CQS wait objects.
* 1 is signaled, 0 otherwise.
* @nr_objs: Number of CQS objects in the array.
* @inherit_err_flags: Bit-pattern for CQSs in the array who's error field is to
* be used as the source to import into the queue's error-state
*/
struct kbase_kcpu_command_cqs_wait_operation_info {
struct base_cqs_wait_operation_info *objs;
unsigned long *signaled;
unsigned int nr_objs;
u32 inherit_err_flags;
};
/**
* struct kbase_kcpu_command_jit_alloc_info - Structure which holds information
* needed for the kcpu command for jit allocations
*
* @node: Used to keep track of all JIT free/alloc commands in submission
* order. This must be located in the front of this struct to
* match that of kbase_kcpu_command_jit_free_info.
* @info: Array of objects of the struct base_jit_alloc_info type which
* specify jit allocations to be made by the kcpu command.
* @count: Number of jit alloc objects in the array.
* @blocked: Whether this allocation has been put into the pending list to
* be retried later.
*/
struct kbase_kcpu_command_jit_alloc_info {
struct list_head node;
struct base_jit_alloc_info *info;
u8 count;
bool blocked;
};
/**
* struct kbase_kcpu_command_jit_free_info - Structure which holds information
* needed for the kcpu jit free command
*
* @node: Used to keep track of all JIT free/alloc commands in submission
* order. This must be located in the front of this struct to
* match that of kbase_kcpu_command_jit_alloc_info.
* @ids: Array of identifiers of jit allocations which are to be freed
* by the kcpu command.
* @count: Number of elements in the array.
*/
struct kbase_kcpu_command_jit_free_info {
struct list_head node;
u8 *ids;
u8 count;
};
/**
* struct kbase_suspend_copy_buffer - information about the suspend buffer
* to be copied.
*
* @size: size of the suspend buffer in bytes.
* @pages: pointer to an array of pointers to the pages which contain
* the user buffer.
* @nr_pages: number of pages.
* @offset: offset into the pages
* @cpu_alloc: Reference to physical pages of suspend buffer allocation.
*/
struct kbase_suspend_copy_buffer {
size_t size;
struct page **pages;
unsigned int nr_pages;
size_t offset;
struct kbase_mem_phy_alloc *cpu_alloc;
};
#if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST
/**
* struct kbase_kcpu_command_group_suspend_info - structure which contains
* suspend buffer data captured for a suspended queue group.
*
* @sus_buf: Pointer to the structure which contains details of the
* user buffer and its kernel pinned pages.
* @group_handle: Handle to the mapping of CSG.
*/
struct kbase_kcpu_command_group_suspend_info {
struct kbase_suspend_copy_buffer *sus_buf;
u8 group_handle;
};
#endif
/**
* struct kbase_kcpu_command - Command which is to be part of the kernel
* command queue
*
* @type: Type of the command.
* @enqueue_ts: Denotes the relative time of enqueueing, a smaller value
* indicates that it has been enqueued earlier.
* @info: Structure which holds information about the command
* dependent on the command type.
* @info.fence: Fence
* @info.cqs_wait: CQS wait
* @info.cqs_set: CQS set
* @info.cqs_wait_operation: CQS wait operation
* @info.cqs_set_operation: CQS set operation
* @info.import: import
* @info.jit_alloc: JIT allocation
* @info.jit_free: JIT deallocation
* @info.suspend_buf_copy: suspend buffer copy
* @info.sample_time: sample time
*/
struct kbase_kcpu_command {
enum base_kcpu_command_type type;
u64 enqueue_ts;
union {
struct kbase_kcpu_command_fence_info fence;
struct kbase_kcpu_command_cqs_wait_info cqs_wait;
struct kbase_kcpu_command_cqs_set_info cqs_set;
struct kbase_kcpu_command_cqs_wait_operation_info cqs_wait_operation;
struct kbase_kcpu_command_cqs_set_operation_info cqs_set_operation;
struct kbase_kcpu_command_import_info import;
struct kbase_kcpu_command_jit_alloc_info jit_alloc;
struct kbase_kcpu_command_jit_free_info jit_free;
#if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST
struct kbase_kcpu_command_group_suspend_info suspend_buf_copy;
#endif
} info;
};
/**
* struct kbase_kcpu_command_queue - a command queue executed by the kernel
*
* @lock: Lock to protect accesses to this queue.
* @kctx: The context to which this command queue belongs.
* @commands: Array of commands which have been successfully
* enqueued to this command queue.
* @work: struct work_struct which contains a pointer to
* the function which handles processing of kcpu
* commands enqueued into a kcpu command queue;
* part of kernel API for processing workqueues.
* This would be used if the context is not
* prioritised, otherwise it would be handled by
* kbase_csf_scheduler_kthread().
* @high_prio_work: A counterpart to @work, this queue would be
* added to a list to be processed by
* kbase_csf_scheduler_kthread() if it is
* prioritised.
* @pending_kick: Indicates that kbase_csf_scheduler_kthread()
* should re-evaluate pending commands for this
* queue. This would be set to false when the work
* is done. This is used mainly for
* synchronisation with queue termination.
* @timeout_work: struct work_struct which contains a pointer to the
* function which handles post-timeout actions
* queue when a fence signal timeout occurs.
* @start_offset: Index of the command to be executed next
* @id: KCPU command queue ID.
* @num_pending_cmds: The number of commands enqueued but not yet
* executed or pending
* @cqs_wait_count: Tracks the number of CQS wait commands enqueued
* @fence_context: The dma-buf fence context number for this kcpu
* queue. A unique context number is allocated for
* each kcpu queue.
* @fence_seqno: The dma-buf fence sequence number for the fence
* that is returned on the enqueue of fence signal
* command. This is increased every time the
* fence signal command is queued.
* @fence_wait_processed: Used to avoid reprocessing of the fence wait
* command which has blocked the processing of
* commands that follow it.
* @enqueue_failed: Indicates that no space has become available in
* the buffer since an enqueue operation failed
* because of insufficient free space.
* @command_started: Indicates that the command at the front of the
* queue has been started in a previous queue
* process, but was not completed due to some
* unmet dependencies. Ensures that instrumentation
* of the execution start of these commands is only
* fired exactly once.
* @has_error: Indicates that the kcpu queue is in error mode
* or without errors since last cleaned.
* @jit_blocked: Used to keep track of command queues blocked
* by a pending JIT allocation command.
* @fence_timeout: Timer used to detect the fence wait timeout.
* @metadata: Metadata structure containing basic information about
* this queue for any fence objects associated with this queue.
* @fence_signal_timeout: Timer used for detect a fence signal command has
* been blocked for too long.
* @fence_signal_pending_cnt: Enqueued fence signal commands in the queue.
*/
struct kbase_kcpu_command_queue {
struct mutex lock;
struct kbase_context *kctx;
struct kbase_kcpu_command commands[KBASEP_KCPU_QUEUE_SIZE];
struct work_struct work;
struct list_head high_prio_work;
atomic_t pending_kick;
struct work_struct timeout_work;
u8 start_offset;
u8 id;
u16 num_pending_cmds;
u32 cqs_wait_count;
u64 fence_context;
unsigned int fence_seqno;
bool fence_wait_processed;
bool enqueue_failed;
bool command_started;
struct list_head jit_blocked;
bool has_error;
struct timer_list fence_timeout;
#if IS_ENABLED(CONFIG_SYNC_FILE)
struct kbase_kcpu_dma_fence_meta *metadata;
#endif /* CONFIG_SYNC_FILE */
struct timer_list fence_signal_timeout;
atomic_t fence_signal_pending_cnt;
};
/**
* kbase_csf_kcpu_queue_new - Create new KCPU command queue.
*
* @kctx: Pointer to the kbase context within which the KCPU command
* queue will be created.
* @newq: Pointer to the structure which contains information about
* the new KCPU command queue to be created.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_kcpu_queue_new(struct kbase_context *kctx, struct kbase_ioctl_kcpu_queue_new *newq);
/**
* kbase_csf_kcpu_queue_delete - Delete KCPU command queue.
*
* @kctx: Pointer to the kbase context from which the KCPU command
* queue is to be deleted.
* @del: Pointer to the structure which specifies the KCPU command
* queue to be deleted.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_kcpu_queue_delete(struct kbase_context *kctx,
struct kbase_ioctl_kcpu_queue_delete *del);
/**
* kbase_csf_kcpu_queue_process - Proces pending KCPU queue commands
*
* @queue: The queue to process pending commands for
* @drain_queue: Whether to skip all blocking commands in the queue.
* This is expected to be set to true on queue
* termination.
*
* Return: 0 if successful or a negative error code on failure.
*/
void kbase_csf_kcpu_queue_process(struct kbase_kcpu_command_queue *queue, bool drain_queue);
/**
* kbase_csf_kcpu_queue_enqueue - Enqueue a KCPU command into a KCPU command
* queue.
*
* @kctx: Pointer to the kbase context within which the KCPU command
* is to be enqueued into the KCPU command queue.
* @enq: Pointer to the structure which specifies the KCPU command
* as well as the KCPU command queue into which the command
* is to be enqueued.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_kcpu_queue_enqueue(struct kbase_context *kctx,
struct kbase_ioctl_kcpu_queue_enqueue *enq);
/**
* kbase_csf_kcpu_queue_context_init - Initialize the kernel CPU queues context
* for a GPU address space
*
* @kctx: Pointer to the kbase context being initialized.
*
* This function must be called only when a kbase context is instantiated.
*
* Return: 0 if successful or a negative error code on failure.
*/
int kbase_csf_kcpu_queue_context_init(struct kbase_context *kctx);
/**
* kbase_csf_kcpu_queue_context_term - Terminate the kernel CPU queues context
* for a GPU address space
* @kctx: Pointer to the kbase context being terminated.
*
* This function deletes any kernel CPU queues that weren't deleted before
* context termination.
*
*/
void kbase_csf_kcpu_queue_context_term(struct kbase_context *kctx);
#if IS_ENABLED(CONFIG_SYNC_FILE)
/* Test wrappers for dma fence operations. */
int kbase_kcpu_fence_signal_process(struct kbase_kcpu_command_queue *kcpu_queue,
struct kbase_kcpu_command_fence_info *fence_info);
int kbase_kcpu_fence_signal_init(struct kbase_kcpu_command_queue *kcpu_queue,
struct kbase_kcpu_command *current_command,
struct base_fence *fence, struct sync_file **sync_file, int *fd);
#endif /* CONFIG_SYNC_FILE */
/*
* kbase_csf_kcpu_queue_halt_timers - Halt the KCPU fence timers associated with
* the kbase device.
*
* @kbdev: Kbase device
*
* Note that this function assumes that the caller has ensured that the
* kbase_device::kctx_list does not get updated during this function's runtime.
* At the moment, the function is only safe to call during system suspend, when
* the device PM active count has reached zero.
*
* Return: 0 on success, negative value otherwise.
*/
int kbase_csf_kcpu_queue_halt_timers(struct kbase_device *kbdev);
/*
* kbase_csf_kcpu_queue_resume_timers - Resume the KCPU fence timers associated
* with the kbase device.
*
* @kbdev: Kbase device
*
* Note that this function assumes that the caller has ensured that the
* kbase_device::kctx_list does not get updated during this function's runtime.
* At the moment, the function is only safe to call during system resume.
*/
void kbase_csf_kcpu_queue_resume_timers(struct kbase_device *kbdev);
bool kbase_kcpu_command_fence_has_force_signaled(struct kbase_kcpu_command_fence_info *fence_info);
#endif /* _KBASE_CSF_KCPU_H_ */

View file

@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2019-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#include "mali_kbase_csf_kcpu_debugfs.h"
#include <mali_kbase.h>
#include <linux/seq_file.h>
#if IS_ENABLED(CONFIG_SYNC_FILE)
#include "mali_kbase_sync.h"
#endif
#if IS_ENABLED(CONFIG_DEBUG_FS)
/**
* kbasep_csf_kcpu_debugfs_print_cqs_waits() - Print additional info for KCPU
* queues blocked on CQS wait commands.
*
* @file: The seq_file to print to
* @kctx: The context of the KCPU queue
* @waits: Pointer to the KCPU CQS wait command info
*/
static void kbasep_csf_kcpu_debugfs_print_cqs_waits(struct seq_file *file,
struct kbase_context *kctx,
struct kbase_kcpu_command_cqs_wait_info *waits)
{
unsigned int i;
for (i = 0; i < waits->nr_objs; i++) {
struct kbase_vmap_struct *mapping;
u32 val;
char const *msg;
u32 *const cpu_ptr =
(u32 *)kbase_phy_alloc_mapping_get(kctx, waits->objs[i].addr, &mapping);
if (!cpu_ptr)
return;
val = *cpu_ptr;
kbase_phy_alloc_mapping_put(kctx, mapping);
msg = (waits->inherit_err_flags && (1U << i)) ? "true" : "false";
seq_printf(file, " %llx(%u > %u, inherit_err: %s), ", waits->objs[i].addr, val,
waits->objs[i].val, msg);
}
}
/**
* kbasep_csf_kcpu_debugfs_print_queue() - Print debug data for a KCPU queue
*
* @file: The seq_file to print to
* @kctx: The context of the KCPU queue
* @queue: Pointer to the KCPU queue
*/
static void kbasep_csf_kcpu_debugfs_print_queue(struct seq_file *file, struct kbase_context *kctx,
struct kbase_kcpu_command_queue *queue)
{
if (WARN_ON(!queue))
return;
lockdep_assert_held(&kctx->csf.kcpu_queues.lock);
seq_printf(file, "%16u, %11u, %7u, %13llu %8u", queue->num_pending_cmds,
queue->enqueue_failed, queue->command_started ? 1 : 0, queue->fence_context,
queue->fence_seqno);
if (queue->command_started) {
struct kbase_kcpu_command *cmd = &queue->commands[queue->start_offset];
switch (cmd->type) {
#if IS_ENABLED(CONFIG_SYNC_FILE)
case BASE_KCPU_COMMAND_TYPE_FENCE_WAIT: {
struct kbase_sync_fence_info info;
kbase_sync_fence_info_get(cmd->info.fence.fence, &info);
seq_printf(file, ", Fence %pK %s %s", info.fence, info.name,
kbase_sync_status_string(info.status));
break;
}
#endif
case BASE_KCPU_COMMAND_TYPE_CQS_WAIT:
seq_puts(file, ", CQS ");
kbasep_csf_kcpu_debugfs_print_cqs_waits(file, kctx, &cmd->info.cqs_wait);
break;
default:
seq_puts(file, ", U, Unknown blocking command");
break;
}
}
seq_puts(file, "\n");
}
/**
* kbasep_csf_kcpu_debugfs_show() - Print the KCPU queues debug information
*
* @file: The seq_file for printing to
* @data: The debugfs dentry private data, a pointer to kbase_context
*
* Return: Negative error code or 0 on success.
*/
static int kbasep_csf_kcpu_debugfs_show(struct seq_file *file, void *data)
{
struct kbase_context *kctx = file->private;
unsigned long idx;
CSTD_UNUSED(data);
seq_printf(file, "MALI_CSF_KCPU_DEBUGFS_VERSION: v%u\n", MALI_CSF_KCPU_DEBUGFS_VERSION);
seq_puts(
file,
"Queue Idx(err-mode), Pending Commands, Enqueue err, Blocked, Fence context & seqno, (Wait Type, Additional info)\n");
mutex_lock(&kctx->csf.kcpu_queues.lock);
idx = find_first_bit(kctx->csf.kcpu_queues.in_use, KBASEP_MAX_KCPU_QUEUES);
while (idx < KBASEP_MAX_KCPU_QUEUES) {
struct kbase_kcpu_command_queue *queue = kctx->csf.kcpu_queues.array[idx];
seq_printf(file, "%9lu( %s ), ", idx, queue->has_error ? "InErr" : "NoErr");
kbasep_csf_kcpu_debugfs_print_queue(file, kctx, kctx->csf.kcpu_queues.array[idx]);
idx = find_next_bit(kctx->csf.kcpu_queues.in_use, KBASEP_MAX_KCPU_QUEUES, idx + 1);
}
mutex_unlock(&kctx->csf.kcpu_queues.lock);
return 0;
}
static int kbasep_csf_kcpu_debugfs_open(struct inode *in, struct file *file)
{
return single_open(file, kbasep_csf_kcpu_debugfs_show, in->i_private);
}
static const struct file_operations kbasep_csf_kcpu_debugfs_fops = {
.open = kbasep_csf_kcpu_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void kbase_csf_kcpu_debugfs_init(struct kbase_context *kctx)
{
struct dentry *file;
const mode_t mode = 0444;
if (WARN_ON(!kctx || IS_ERR_OR_NULL(kctx->kctx_dentry)))
return;
file = debugfs_create_file("kcpu_queues", mode, kctx->kctx_dentry, kctx,
&kbasep_csf_kcpu_debugfs_fops);
if (IS_ERR_OR_NULL(file)) {
dev_warn(kctx->kbdev->dev, "Unable to create KCPU debugfs entry");
}
}
#else
/*
* Stub functions for when debugfs is disabled
*/
void kbase_csf_kcpu_debugfs_init(struct kbase_context *kctx)
{
}
#endif /* CONFIG_DEBUG_FS */

View file

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
*
* (C) COPYRIGHT 2019-2021 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
*/
#ifndef _KBASE_CSF_KCPU_DEBUGFS_H_
#define _KBASE_CSF_KCPU_DEBUGFS_H_
/* Forward declaration */
struct kbase_context;
#define MALI_CSF_KCPU_DEBUGFS_VERSION 0
/**
* kbase_csf_kcpu_debugfs_init() - Create a debugfs entry for KCPU queues
*
* @kctx: The kbase_context for which to create the debugfs entry
*/
void kbase_csf_kcpu_debugfs_init(struct kbase_context *kctx);
#endif /* _KBASE_CSF_KCPU_DEBUGFS_H_ */

Some files were not shown because too many files have changed in this diff Show more