diff options
Diffstat (limited to 'drivers/gpu/drm/imagination')
40 files changed, 1865 insertions, 645 deletions
diff --git a/drivers/gpu/drm/imagination/Kconfig b/drivers/gpu/drm/imagination/Kconfig index 3bfa2ac212dc..1fd4c635c2c9 100644 --- a/drivers/gpu/drm/imagination/Kconfig +++ b/drivers/gpu/drm/imagination/Kconfig @@ -3,9 +3,11 @@ config DRM_POWERVR tristate "Imagination Technologies PowerVR (Series 6 and later) & IMG Graphics" - depends on ARM64 + depends on (ARM64 || RISCV && 64BIT) depends on DRM + depends on MMU depends on PM + depends on POWER_SEQUENCING || !POWER_SEQUENCING select DRM_EXEC select DRM_GEM_SHMEM_HELPER select DRM_SCHED @@ -16,3 +18,15 @@ config DRM_POWERVR Technologies PowerVR (Series 6 or later) or IMG GPU. If "M" is selected, the module will be called powervr. + +config DRM_POWERVR_KUNIT_TEST + tristate "KUnit tests for the drm powervr driver" if !KUNIT_ALL_TESTS + depends on DRM_POWERVR && KUNIT + default KUNIT_ALL_TESTS + help + Choose this option to allow the driver to perform selftests under + the kunit framework + + Recommended for driver developers only. + + If in doubt, say "N". diff --git a/drivers/gpu/drm/imagination/Makefile b/drivers/gpu/drm/imagination/Makefile index 3d9d4d40fb80..1222a14262e4 100644 --- a/drivers/gpu/drm/imagination/Makefile +++ b/drivers/gpu/drm/imagination/Makefile @@ -8,17 +8,19 @@ powervr-y := \ pvr_device.o \ pvr_device_info.o \ pvr_drv.o \ + pvr_dump.o \ pvr_free_list.o \ pvr_fw.o \ pvr_fw_meta.o \ pvr_fw_mips.o \ + pvr_fw_riscv.o \ pvr_fw_startstop.o \ pvr_fw_trace.o \ + pvr_fw_util.o \ pvr_gem.o \ pvr_hwrt.o \ pvr_job.o \ pvr_mmu.o \ - pvr_params.o \ pvr_power.o \ pvr_queue.o \ pvr_stream.o \ @@ -31,3 +33,5 @@ powervr-$(CONFIG_DEBUG_FS) += \ pvr_debugfs.o obj-$(CONFIG_DRM_POWERVR) += powervr.o + +obj-$(CONFIG_DRM_POWERVR_KUNIT_TEST) += pvr_test.o diff --git a/drivers/gpu/drm/imagination/pvr_ccb.c b/drivers/gpu/drm/imagination/pvr_ccb.c index 2bbdc05a3b97..4accf18e2341 100644 --- a/drivers/gpu/drm/imagination/pvr_ccb.c +++ b/drivers/gpu/drm/imagination/pvr_ccb.c @@ -4,12 +4,14 @@ #include "pvr_ccb.h" #include "pvr_device.h" #include "pvr_drv.h" +#include "pvr_dump.h" #include "pvr_free_list.h" #include "pvr_fw.h" #include "pvr_gem.h" #include "pvr_power.h" #include <drm/drm_managed.h> +#include <drm/drm_print.h> #include <linux/compiler.h> #include <linux/delay.h> #include <linux/jiffies.h> @@ -135,6 +137,14 @@ pvr_ccb_slot_available_locked(struct pvr_ccb *pvr_ccb, u32 *write_offset) static void process_fwccb_command(struct pvr_device *pvr_dev, struct rogue_fwif_fwccb_cmd *cmd) { + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + + if ((cmd->cmd_type & ROGUE_CMD_MAGIC_DWORD_MASK) != ROGUE_CMD_MAGIC_DWORD_SHIFTED) { + drm_warn_once(drm_dev, "Received FWCCB command with bad magic value; ignoring (type=0x%08x)\n", + cmd->cmd_type); + return; + } + switch (cmd->cmd_type) { case ROGUE_FWIF_FWCCB_CMD_REQUEST_GPU_RESTART: pvr_power_reset(pvr_dev, false); @@ -149,9 +159,21 @@ process_fwccb_command(struct pvr_device *pvr_dev, struct rogue_fwif_fwccb_cmd *c pvr_free_list_process_grow_req(pvr_dev, &cmd->cmd_data.cmd_free_list_gs); break; + case ROGUE_FWIF_FWCCB_CMD_UPDATE_STATS: + /* + * We currently have no infrastructure for processing these + * stats. It may be added in the future, but for now just + * suppress the "unknown" warning when receiving this command. + */ + break; + case ROGUE_FWIF_FWCCB_CMD_CONTEXT_RESET_NOTIFICATION: + pvr_dump_context_reset_notification(pvr_dev, + &cmd->cmd_data.cmd_context_reset_notification); + break; + default: - drm_info(from_pvr_device(pvr_dev), "Received unknown FWCCB command %x\n", - cmd->cmd_type); + drm_info(drm_dev, "Received unknown FWCCB command (type=%d)\n", + cmd->cmd_type & ~ROGUE_CMD_MAGIC_DWORD_MASK); break; } } @@ -542,7 +564,7 @@ struct dma_fence *pvr_kccb_fence_alloc(void) { struct pvr_kccb_fence *kccb_fence; - kccb_fence = kzalloc(sizeof(*kccb_fence), GFP_KERNEL); + kccb_fence = kzalloc_obj(*kccb_fence); if (!kccb_fence) return NULL; diff --git a/drivers/gpu/drm/imagination/pvr_context.c b/drivers/gpu/drm/imagination/pvr_context.c index 5edc3c01af72..8de70c30b9de 100644 --- a/drivers/gpu/drm/imagination/pvr_context.c +++ b/drivers/gpu/drm/imagination/pvr_context.c @@ -292,7 +292,7 @@ int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_co if (ctx_size < 0) return ctx_size; - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx = kzalloc_obj(*ctx); if (!ctx) return -ENOMEM; diff --git a/drivers/gpu/drm/imagination/pvr_debugfs.c b/drivers/gpu/drm/imagination/pvr_debugfs.c index 6b77c9b4bde8..ebdb05de4072 100644 --- a/drivers/gpu/drm/imagination/pvr_debugfs.c +++ b/drivers/gpu/drm/imagination/pvr_debugfs.c @@ -5,7 +5,6 @@ #include "pvr_device.h" #include "pvr_fw_trace.h" -#include "pvr_params.h" #include <linux/dcache.h> #include <linux/debugfs.h> @@ -18,7 +17,6 @@ #include <drm/drm_print.h> static const struct pvr_debugfs_entry pvr_debugfs_entries[] = { - {"pvr_params", pvr_params_debugfs_init}, {"pvr_fw", pvr_fw_trace_debugfs_init}, }; @@ -28,9 +26,8 @@ pvr_debugfs_init(struct drm_minor *minor) struct drm_device *drm_dev = minor->dev; struct pvr_device *pvr_dev = to_pvr_device(drm_dev); struct dentry *root = minor->debugfs_root; - size_t i; - for (i = 0; i < ARRAY_SIZE(pvr_debugfs_entries); ++i) { + for (size_t i = 0; i < ARRAY_SIZE(pvr_debugfs_entries); ++i) { const struct pvr_debugfs_entry *entry = &pvr_debugfs_entries[i]; struct dentry *dir; diff --git a/drivers/gpu/drm/imagination/pvr_device.c b/drivers/gpu/drm/imagination/pvr_device.c index 1704c0268589..dbb6f5a8ded1 100644 --- a/drivers/gpu/drm/imagination/pvr_device.c +++ b/drivers/gpu/drm/imagination/pvr_device.c @@ -5,7 +5,6 @@ #include "pvr_device_info.h" #include "pvr_fw.h" -#include "pvr_params.h" #include "pvr_power.h" #include "pvr_queue.h" #include "pvr_rogue_cr_defs.h" @@ -23,13 +22,17 @@ #include <linux/firmware.h> #include <linux/gfp.h> #include <linux/interrupt.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/reset.h> #include <linux/slab.h> #include <linux/stddef.h> #include <linux/types.h> #include <linux/workqueue.h> +#include <kunit/visibility.h> + /* Major number for the supported version of the firmware. */ #define PVR_FW_VERSION_MAJOR 1 @@ -46,7 +49,7 @@ * * Return: * * 0 on success, or - * * Any error returned by devm_platform_ioremap_resource(). + * * Any error returned by devm_platform_get_and_ioremap_resource(). */ static int pvr_device_reg_init(struct pvr_device *pvr_dev) @@ -146,9 +149,61 @@ static void pvr_device_process_active_queues(struct pvr_device *pvr_dev) mutex_unlock(&pvr_dev->queues.lock); } +static bool pvr_device_safety_irq_pending(struct pvr_device *pvr_dev) +{ + u32 events; + + WARN_ON_ONCE(!pvr_dev->has_safety_events); + + events = pvr_cr_read32(pvr_dev, ROGUE_CR_EVENT_STATUS); + + return (events & ROGUE_CR_EVENT_STATUS_SAFETY_EN) != 0; +} + +static void pvr_device_safety_irq_clear(struct pvr_device *pvr_dev) +{ + WARN_ON_ONCE(!pvr_dev->has_safety_events); + + pvr_cr_write32(pvr_dev, ROGUE_CR_EVENT_CLEAR, + ROGUE_CR_EVENT_CLEAR_SAFETY_EN); +} + +static void pvr_device_handle_safety_events(struct pvr_device *pvr_dev) +{ + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + u32 events; + + WARN_ON_ONCE(!pvr_dev->has_safety_events); + + events = pvr_cr_read32(pvr_dev, ROGUE_CR_SAFETY_EVENT_STATUS__ROGUEXE); + + /* Handle only these events on the host and leave the rest to the FW. */ + events &= ROGUE_CR_SAFETY_EVENT_STATUS__ROGUEXE__FAULT_FW_EN | + ROGUE_CR_SAFETY_EVENT_STATUS__ROGUEXE__WATCHDOG_TIMEOUT_EN; + + pvr_cr_write32(pvr_dev, ROGUE_CR_SAFETY_EVENT_CLEAR__ROGUEXE, events); + + if (events & ROGUE_CR_SAFETY_EVENT_STATUS__ROGUEXE__FAULT_FW_EN) { + u32 fault_fw = pvr_cr_read32(pvr_dev, ROGUE_CR_FAULT_FW_STATUS); + + pvr_cr_write32(pvr_dev, ROGUE_CR_FAULT_FW_CLEAR, fault_fw); + + drm_info(drm_dev, "Safety event: FW fault (mask=0x%08x)\n", fault_fw); + } + + if (events & ROGUE_CR_SAFETY_EVENT_STATUS__ROGUEXE__WATCHDOG_TIMEOUT_EN) { + /* + * The watchdog timer is disabled by the driver so this event + * should never be fired. + */ + drm_info(drm_dev, "Safety event: Watchdog timeout\n"); + } +} + static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data) { struct pvr_device *pvr_dev = data; + struct drm_device *drm_dev = from_pvr_device(pvr_dev); irqreturn_t ret = IRQ_NONE; /* We are in the threaded handler, we can keep dequeuing events until we @@ -164,30 +219,59 @@ static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data) pvr_device_process_active_queues(pvr_dev); } - pm_runtime_mark_last_busy(from_pvr_device(pvr_dev)->dev); + pm_runtime_mark_last_busy(drm_dev->dev); ret = IRQ_HANDLED; } - /* Unmask FW irqs before returning, so new interrupts can be received. */ - pvr_fw_irq_enable(pvr_dev); + if (pvr_dev->has_safety_events) { + while (pvr_device_safety_irq_pending(pvr_dev)) { + pvr_device_safety_irq_clear(pvr_dev); + pvr_device_handle_safety_events(pvr_dev); + + ret = IRQ_HANDLED; + } + } + return ret; } static irqreturn_t pvr_device_irq_handler(int irq, void *data) { struct pvr_device *pvr_dev = data; + bool safety_irq_pending = false; - if (!pvr_fw_irq_pending(pvr_dev)) + if (pvr_dev->has_safety_events) + safety_irq_pending = pvr_device_safety_irq_pending(pvr_dev); + + if (!pvr_fw_irq_pending(pvr_dev) && !safety_irq_pending) return IRQ_NONE; /* Spurious IRQ - ignore. */ - /* Mask the FW interrupts before waking up the thread. Will be unmasked - * when the thread handler is done processing events. - */ - pvr_fw_irq_disable(pvr_dev); return IRQ_WAKE_THREAD; } +static void pvr_device_safety_irq_init(struct pvr_device *pvr_dev) +{ + u32 num_ecc_rams = 0; + + /* + * Safety events are an optional feature of the RogueXE platform. They + * are only enabled if at least one of ECC memory or the watchdog timer + * are present in HW. While safety events can be generated by other + * systems, that will never happen if the above mentioned hardware is + * not present. + */ + if (!PVR_HAS_FEATURE(pvr_dev, roguexe)) { + pvr_dev->has_safety_events = false; + return; + } + + PVR_FEATURE_VALUE(pvr_dev, ecc_rams, &num_ecc_rams); + + pvr_dev->has_safety_events = + num_ecc_rams > 0 || PVR_HAS_FEATURE(pvr_dev, watchdog_timer); +} + /** * pvr_device_irq_init() - Initialise IRQ required by a PowerVR device * @pvr_dev: Target PowerVR device. @@ -205,17 +289,25 @@ pvr_device_irq_init(struct pvr_device *pvr_dev) init_waitqueue_head(&pvr_dev->kccb.rtn_q); + pvr_device_safety_irq_init(pvr_dev); + pvr_dev->irq = platform_get_irq(plat_dev, 0); if (pvr_dev->irq < 0) return pvr_dev->irq; /* Clear any pending events before requesting the IRQ line. */ pvr_fw_irq_clear(pvr_dev); - pvr_fw_irq_enable(pvr_dev); + if (pvr_dev->has_safety_events) + pvr_device_safety_irq_clear(pvr_dev); + + /* + * The ONESHOT flag ensures IRQs are masked while the thread handler is + * running. + */ return request_threaded_irq(pvr_dev->irq, pvr_device_irq_handler, pvr_device_irq_thread_handler, - IRQF_SHARED, "gpu", pvr_dev); + IRQF_SHARED | IRQF_ONESHOT, "gpu", pvr_dev); } /** @@ -314,23 +406,21 @@ err_free_filename: } /** - * pvr_load_gpu_id() - Load a PowerVR device's GPU ID (BVNC) from control registers. + * pvr_gpuid_decode_reg() - Decode the GPU ID from GPU register * - * Sets struct pvr_dev.gpu_id. + * Sets the b, v, n, c fields of struct pvr_dev.gpu_id. * * @pvr_dev: Target PowerVR device. + * @gpu_id: Output to be updated with the GPU ID. */ static void -pvr_load_gpu_id(struct pvr_device *pvr_dev) +pvr_gpuid_decode_reg(const struct pvr_device *pvr_dev, struct pvr_gpu_id *gpu_id) { - struct pvr_gpu_id *gpu_id = &pvr_dev->gpu_id; - u64 bvnc; - /* * Try reading the BVNC using the newer (cleaner) method first. If the * B value is zero, fall back to the older method. */ - bvnc = pvr_cr_read64(pvr_dev, ROGUE_CR_CORE_ID__PBVNC); + u64 bvnc = pvr_cr_read64(pvr_dev, ROGUE_CR_CORE_ID__PBVNC); gpu_id->b = PVR_CR_FIELD_GET(bvnc, CORE_ID__PBVNC__BRANCH_ID); if (gpu_id->b != 0) { @@ -350,6 +440,179 @@ pvr_load_gpu_id(struct pvr_device *pvr_dev) } /** + * pvr_gpuid_decode_string() - Decode the GPU ID from a module input string + * + * Sets the b, v, n, c fields of struct pvr_dev.gpu_id. + * + * @pvr_dev: Target PowerVR device. + * @param_bvnc: GPU ID (BVNC) module parameter. + * @gpu_id: Output to be updated with the GPU ID. + */ +VISIBLE_IF_KUNIT int +pvr_gpuid_decode_string(const struct pvr_device *pvr_dev, + const char *param_bvnc, struct pvr_gpu_id *gpu_id) +{ + const struct drm_device *drm_dev = &pvr_dev->base; + char str_cpy[PVR_GPUID_STRING_MAX_LENGTH]; + char *pos, *tkn; + int ret, idx = 0; + u16 user_bvnc_u16[4]; + u8 dot_cnt = 0; + + ret = strscpy(str_cpy, param_bvnc); + + /* + * strscpy() should return at least a size 7 for the input to be valid. + * Returns -E2BIG for the case when the string is empty or too long. + */ + if (ret < PVR_GPUID_STRING_MIN_LENGTH) { + drm_info(drm_dev, + "Invalid size of the input GPU ID (BVNC): %s", + str_cpy); + return -EINVAL; + } + + while (*param_bvnc) { + if (*param_bvnc == '.') + dot_cnt++; + param_bvnc++; + } + + if (dot_cnt != 3) { + drm_info(drm_dev, + "Invalid format of the input GPU ID (BVNC): %s", + str_cpy); + return -EINVAL; + } + + pos = str_cpy; + + while ((tkn = strsep(&pos, ".")) != NULL && idx < 4) { + /* kstrtou16() will also handle the case of consecutive dots */ + ret = kstrtou16(tkn, 10, &user_bvnc_u16[idx]); + if (ret) { + drm_info(drm_dev, + "Invalid format of the input GPU ID (BVNC): %s", + str_cpy); + return -EINVAL; + } + idx++; + } + + gpu_id->b = user_bvnc_u16[0]; + gpu_id->v = user_bvnc_u16[1]; + gpu_id->n = user_bvnc_u16[2]; + gpu_id->c = user_bvnc_u16[3]; + + return 0; +} +EXPORT_SYMBOL_IF_KUNIT(pvr_gpuid_decode_string); + +static bool pvr_exp_hw_support; +module_param_named(exp_hw_support, pvr_exp_hw_support, bool, 0600); +MODULE_PARM_DESC(exp_hw_support, "Bypass runtime checks for fully supported GPU cores. WARNING: enabling this option may result in a buggy, insecure, or otherwise unusable driver."); + +/** + * enum pvr_gpu_support_level - The level of support for a gpu_id in the current + * version of the driver. + * + * @PVR_GPU_UNKNOWN: Cores that are unknown to the driver. These may not even exist. + * @PVR_GPU_EXPERIMENTAL: Cores that have experimental support. + * @PVR_GPU_SUPPORTED: Cores that are supported and maintained. + */ +enum pvr_gpu_support_level { + PVR_GPU_UNKNOWN, + PVR_GPU_EXPERIMENTAL, + PVR_GPU_SUPPORTED, +}; + +static enum pvr_gpu_support_level +pvr_gpu_support_level(const struct pvr_gpu_id *gpu_id) +{ + switch (pvr_gpu_id_to_packed_bvnc(gpu_id)) { + case PVR_PACKED_BVNC(33, 15, 11, 3): + case PVR_PACKED_BVNC(36, 53, 104, 796): + return PVR_GPU_SUPPORTED; + + case PVR_PACKED_BVNC(36, 52, 104, 182): + return PVR_GPU_EXPERIMENTAL; + + default: + return PVR_GPU_UNKNOWN; + } +} + +static int +pvr_check_gpu_supported(struct pvr_device *pvr_dev, + const struct pvr_gpu_id *gpu_id) +{ + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + + switch (pvr_gpu_support_level(gpu_id)) { + case PVR_GPU_SUPPORTED: + if (pvr_exp_hw_support) + drm_info(drm_dev, "Module parameter 'exp_hw_support' was set, but this hardware is fully supported by the current driver."); + + break; + + case PVR_GPU_EXPERIMENTAL: + if (!pvr_exp_hw_support) { + drm_err(drm_dev, "Unsupported GPU! Set 'exp_hw_support' to bypass this check."); + return -ENODEV; + } + + drm_warn(drm_dev, "Running on unsupported hardware; you may encounter bugs!"); + break; + + /* NOTE: This code path may indicate misbehaving hardware. */ + case PVR_GPU_UNKNOWN: + default: + if (!pvr_exp_hw_support) { + drm_err(drm_dev, "Unknown GPU! Set 'exp_hw_support' to bypass this check."); + return -ENODEV; + } + + drm_warn(drm_dev, "Running on unknown hardware; expect issues."); + break; + } + + return 0; +} + +static char *pvr_gpuid_override; +module_param_named(gpuid, pvr_gpuid_override, charp, 0400); +MODULE_PARM_DESC(gpuid, "GPU ID (BVNC) to be used instead of the value read from hardware."); + +/** + * pvr_load_gpu_id() - Load a PowerVR device's GPU ID (BVNC) from control + * registers or input parameter. The input parameter is processed instead + * of the GPU register if provided. + * + * Sets the arch field of struct pvr_dev.gpu_id. + * + * @pvr_dev: Target PowerVR device. + */ +static int +pvr_load_gpu_id(struct pvr_device *pvr_dev) +{ + struct pvr_gpu_id *gpu_id = &pvr_dev->gpu_id; + + if (!pvr_gpuid_override || !pvr_gpuid_override[0]) { + pvr_gpuid_decode_reg(pvr_dev, gpu_id); + } else { + drm_warn(from_pvr_device(pvr_dev), + "Using custom GPU ID (BVNC) provided by the user!"); + + int err = pvr_gpuid_decode_string(pvr_dev, pvr_gpuid_override, + gpu_id); + if (err) + return err; + } + + return pvr_check_gpu_supported(pvr_dev, gpu_id); +} + +/** * pvr_set_dma_info() - Set PowerVR device DMA information * @pvr_dev: Target PowerVR device. * @@ -409,7 +672,9 @@ pvr_device_gpu_init(struct pvr_device *pvr_dev) { int err; - pvr_load_gpu_id(pvr_dev); + err = pvr_load_gpu_id(pvr_dev); + if (err) + return err; err = pvr_request_firmware(pvr_dev); if (err) @@ -496,19 +761,18 @@ pvr_device_init(struct pvr_device *pvr_dev) struct device *dev = drm_dev->dev; int err; - /* - * Setup device parameters. We do this first in case other steps - * depend on them. - */ - err = pvr_device_params_init(&pvr_dev->params); - if (err) - return err; + /* Get the platform-specific data based on the compatible string. */ + pvr_dev->device_data = of_device_get_match_data(dev); /* Enable and initialize clocks required for the device to operate. */ err = pvr_device_clk_init(pvr_dev); if (err) return err; + err = pvr_dev->device_data->pwr_ops->init(pvr_dev); + if (err) + return err; + /* Explicitly power the GPU so we can access control registers before the FW is booted. */ err = pm_runtime_resume_and_get(dev); if (err) diff --git a/drivers/gpu/drm/imagination/pvr_device.h b/drivers/gpu/drm/imagination/pvr_device.h index 6d0dfacb677b..d51c57cf9332 100644 --- a/drivers/gpu/drm/imagination/pvr_device.h +++ b/drivers/gpu/drm/imagination/pvr_device.h @@ -7,7 +7,6 @@ #include "pvr_ccb.h" #include "pvr_device_info.h" #include "pvr_fw.h" -#include "pvr_params.h" #include "pvr_rogue_fwif_stream.h" #include "pvr_stream.h" @@ -18,6 +17,7 @@ #include <linux/bits.h> #include <linux/compiler_attributes.h> #include <linux/compiler_types.h> +#include <linux/device.h> #include <linux/io.h> #include <linux/iopoll.h> #include <linux/kernel.h> @@ -36,6 +36,12 @@ struct clk; /* Forward declaration from <linux/firmware.h>. */ struct firmware; +/* Forward declaration from <linux/pwrseq/consumer.h> */ +struct pwrseq_desc; + +#define PVR_GPUID_STRING_MIN_LENGTH 7U +#define PVR_GPUID_STRING_MAX_LENGTH 32U + /** * struct pvr_gpu_id - Hardware GPU ID information for a PowerVR device * @b: Branch ID. @@ -57,6 +63,14 @@ struct pvr_fw_version { }; /** + * struct pvr_device_data - Platform specific data associated with a compatible string. + * @pwr_ops: Pointer to a structure with platform-specific power functions. + */ +struct pvr_device_data { + const struct pvr_power_sequence_ops *pwr_ops; +}; + +/** * struct pvr_device - powervr-specific wrapper for &struct drm_device */ struct pvr_device { @@ -97,6 +111,9 @@ struct pvr_device { /** @fw_version: Firmware version detected at runtime. */ struct pvr_fw_version fw_version; + /** @device_data: Pointer to platform-specific data. */ + const struct pvr_device_data *device_data; + /** @regs_resource: Resource representing device control registers. */ struct resource *regs_resource; @@ -131,6 +148,31 @@ struct pvr_device { */ struct clk *mem_clk; + /** + * @power: Optional power domain devices. + * + * On platforms with more than one power domain for the GPU, they are + * stored here in @domains, along with links between them in + * @domain_links. The size of @domain_links is one less than + * struct dev_pm_domain_list->num_pds in @domains. + */ + struct pvr_device_power { + struct dev_pm_domain_list *domains; + struct device_link **domain_links; + } power; + + /** + * @reset: Optional reset line. + * + * This may be used on some platforms to provide a reset line that needs to be de-asserted + * after power-up procedure. It would also need to be asserted after the power-down + * procedure. + */ + struct reset_control *reset; + + /** @pwrseq: Pointer to a power sequencer, if one is used. */ + struct pwrseq_desc *pwrseq; + /** @irq: IRQ number. */ int irq; @@ -150,15 +192,6 @@ struct pvr_device { /** @fw_dev: Firmware related data. */ struct pvr_fw_device fw_dev; - /** - * @params: Device-specific parameters. - * - * The values of these parameters are initialized from the - * defaults specified as module parameters. They may be - * modified at runtime via debugfs (if enabled). - */ - struct pvr_device_params params; - /** @stream_musthave_quirks: Bit array of "must-have" quirks for stream commands. */ u32 stream_musthave_quirks[PVR_STREAM_TYPE_MAX][PVR_STREAM_EXTHDR_TYPE_MAX]; @@ -300,6 +333,9 @@ struct pvr_device { * struct pvr_file. */ spinlock_t ctx_list_lock; + + /** @has_safety_events: Whether this device can raise safety events. */ + bool has_safety_events; }; /** @@ -481,7 +517,7 @@ struct pvr_file { * Return: Packed BVNC. */ static __always_inline u64 -pvr_gpu_id_to_packed_bvnc(struct pvr_gpu_id *gpu_id) +pvr_gpu_id_to_packed_bvnc(const struct pvr_gpu_id *gpu_id) { return PVR_PACKED_BVNC(gpu_id->b, gpu_id->v, gpu_id->n, gpu_id->c); } @@ -506,6 +542,11 @@ pvr_device_has_uapi_enhancement(struct pvr_device *pvr_dev, u32 enhancement); bool pvr_device_has_feature(struct pvr_device *pvr_dev, u32 feature); +#if IS_ENABLED(CONFIG_KUNIT) +int pvr_gpuid_decode_string(const struct pvr_device *pvr_dev, + const char *param_bvnc, struct pvr_gpu_id *gpu_id); +#endif + /** * PVR_CR_FIELD_GET() - Extract a single field from a PowerVR control register * @val: Value of the target register. @@ -523,7 +564,7 @@ pvr_device_has_feature(struct pvr_device *pvr_dev, u32 feature); * Return: The value of the requested register. */ static __always_inline u32 -pvr_cr_read32(struct pvr_device *pvr_dev, u32 reg) +pvr_cr_read32(const struct pvr_device *pvr_dev, u32 reg) { return ioread32(pvr_dev->regs + reg); } @@ -536,7 +577,7 @@ pvr_cr_read32(struct pvr_device *pvr_dev, u32 reg) * Return: The value of the requested register. */ static __always_inline u64 -pvr_cr_read64(struct pvr_device *pvr_dev, u32 reg) +pvr_cr_read64(const struct pvr_device *pvr_dev, u32 reg) { return ioread64(pvr_dev->regs + reg); } @@ -728,8 +769,22 @@ pvr_ioctl_union_padding_check(void *instance, size_t union_offset, __union_size, __member_size); \ }) -#define PVR_FW_PROCESSOR_TYPE_META 0 -#define PVR_FW_PROCESSOR_TYPE_MIPS 1 -#define PVR_FW_PROCESSOR_TYPE_RISCV 2 +/* + * These utility functions should more properly be placed in pvr_fw.h, but that + * would cause a dependency cycle between that header and this one. Since + * they're primarily used in pvr_device.c, let's put them in here for now. + */ + +static __always_inline bool +pvr_fw_irq_pending(struct pvr_device *pvr_dev) +{ + return pvr_dev->fw_dev.defs->irq_pending(pvr_dev); +} + +static __always_inline void +pvr_fw_irq_clear(struct pvr_device *pvr_dev) +{ + pvr_dev->fw_dev.defs->irq_clear(pvr_dev); +} #endif /* PVR_DEVICE_H */ diff --git a/drivers/gpu/drm/imagination/pvr_drv.c b/drivers/gpu/drm/imagination/pvr_drv.c index 0639502137b4..268900464ab6 100644 --- a/drivers/gpu/drm/imagination/pvr_drv.c +++ b/drivers/gpu/drm/imagination/pvr_drv.c @@ -44,6 +44,7 @@ * This driver supports the following PowerVR/IMG graphics cores from Imagination Technologies: * * * AXE-1-16M (found in Texas Instruments AM62) + * * BXS-4-64 MC1 (found in Texas Instruments J721S2/AM68) */ /** @@ -1311,7 +1312,7 @@ pvr_drm_driver_open(struct drm_device *drm_dev, struct drm_file *file) struct pvr_device *pvr_dev = to_pvr_device(drm_dev); struct pvr_file *pvr_file; - pvr_file = kzalloc(sizeof(*pvr_file), GFP_KERNEL); + pvr_file = kzalloc_obj(*pvr_file); if (!pvr_file) return -ENOMEM; @@ -1411,6 +1412,10 @@ pvr_probe(struct platform_device *plat_dev) platform_set_drvdata(plat_dev, drm_dev); + err = pvr_power_domains_init(pvr_dev); + if (err) + return err; + init_rwsem(&pvr_dev->reset_sem); pvr_context_device_init(pvr_dev); @@ -1450,6 +1455,8 @@ err_watchdog_fini: err_context_fini: pvr_context_device_fini(pvr_dev); + pvr_power_domains_fini(pvr_dev); + return err; } @@ -1470,10 +1477,36 @@ static void pvr_remove(struct platform_device *plat_dev) pvr_watchdog_fini(pvr_dev); pvr_queue_device_fini(pvr_dev); pvr_context_device_fini(pvr_dev); + pvr_power_domains_fini(pvr_dev); } +static const struct pvr_device_data pvr_device_data_manual = { + .pwr_ops = &pvr_power_sequence_ops_manual, +}; + +static const struct pvr_device_data pvr_device_data_pwrseq = { + .pwr_ops = &pvr_power_sequence_ops_pwrseq, +}; + static const struct of_device_id dt_match[] = { - { .compatible = "img,img-axe", .data = NULL }, + { + .compatible = "thead,th1520-gpu", + .data = &pvr_device_data_pwrseq, + }, + { + .compatible = "img,img-rogue", + .data = &pvr_device_data_manual, + }, + + /* + * This legacy compatible string was introduced early on before the more generic + * "img,img-rogue" was added. Keep it around here for compatibility, but never use + * "img,img-axe" in new devicetrees. + */ + { + .compatible = "img,img-axe", + .data = &pvr_device_data_manual, + }, {} }; MODULE_DEVICE_TABLE(of, dt_match); @@ -1498,3 +1531,5 @@ MODULE_DESCRIPTION(PVR_DRIVER_DESC); MODULE_LICENSE("Dual MIT/GPL"); MODULE_IMPORT_NS("DMA_BUF"); MODULE_FIRMWARE("powervr/rogue_33.15.11.3_v1.fw"); +MODULE_FIRMWARE("powervr/rogue_36.52.104.182_v1.fw"); +MODULE_FIRMWARE("powervr/rogue_36.53.104.796_v1.fw"); diff --git a/drivers/gpu/drm/imagination/pvr_dump.c b/drivers/gpu/drm/imagination/pvr_dump.c new file mode 100644 index 000000000000..52e95fce2817 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_dump.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright (c) 2026 Imagination Technologies Ltd. */ + +#include "pvr_device.h" +#include "pvr_dump.h" +#include "pvr_rogue_fwif.h" + +#include <drm/drm_print.h> +#include <linux/types.h> + +static const char * +get_reset_reason_desc(enum rogue_context_reset_reason reason) +{ + switch (reason) { + case ROGUE_CONTEXT_RESET_REASON_NONE: + return "None"; + case ROGUE_CONTEXT_RESET_REASON_GUILTY_LOCKUP: + return "Guilty lockup"; + case ROGUE_CONTEXT_RESET_REASON_INNOCENT_LOCKUP: + return "Innocent lockup"; + case ROGUE_CONTEXT_RESET_REASON_GUILTY_OVERRUNING: + return "Guilty overrunning"; + case ROGUE_CONTEXT_RESET_REASON_INNOCENT_OVERRUNING: + return "Innocent overrunning"; + case ROGUE_CONTEXT_RESET_REASON_HARD_CONTEXT_SWITCH: + return "Hard context switch"; + case ROGUE_CONTEXT_RESET_REASON_WGP_CHECKSUM: + return "CDM Mission/safety checksum mismatch"; + case ROGUE_CONTEXT_RESET_REASON_TRP_CHECKSUM: + return "TRP checksum mismatch"; + case ROGUE_CONTEXT_RESET_REASON_GPU_ECC_OK: + return "GPU ECC error (corrected, OK)"; + case ROGUE_CONTEXT_RESET_REASON_GPU_ECC_HWR: + return "GPU ECC error (uncorrected, HWR)"; + case ROGUE_CONTEXT_RESET_REASON_FW_ECC_OK: + return "Firmware ECC error (corrected, OK)"; + case ROGUE_CONTEXT_RESET_REASON_FW_ECC_ERR: + return "Firmware ECC error (uncorrected, ERR)"; + case ROGUE_CONTEXT_RESET_REASON_FW_WATCHDOG: + return "Firmware watchdog"; + case ROGUE_CONTEXT_RESET_REASON_FW_PAGEFAULT: + return "Firmware pagefault"; + case ROGUE_CONTEXT_RESET_REASON_FW_EXEC_ERR: + return "Firmware execution error"; + case ROGUE_CONTEXT_RESET_REASON_HOST_WDG_FW_ERR: + return "Host watchdog"; + case ROGUE_CONTEXT_GEOM_OOM_DISABLED: + return "Geometry OOM disabled"; + + default: + return "Unknown"; + } +} + +static const char * +get_dm_name(u32 dm) +{ + switch (dm) { + case PVR_FWIF_DM_GP: + return "General purpose"; + /* PVR_FWIF_DM_TDM has the same index, but is discriminated by a device feature */ + case PVR_FWIF_DM_2D: + return "2D or TDM"; + case PVR_FWIF_DM_GEOM: + return "Geometry"; + case PVR_FWIF_DM_FRAG: + return "Fragment"; + case PVR_FWIF_DM_CDM: + return "Compute"; + case PVR_FWIF_DM_RAY: + return "Raytracing"; + case PVR_FWIF_DM_GEOM2: + return "Geometry 2"; + case PVR_FWIF_DM_GEOM3: + return "Geometry 3"; + case PVR_FWIF_DM_GEOM4: + return "Geometry 4"; + + default: + return "Unknown"; + } +} + +/** + * pvr_dump_context_reset_notification() - Handle context reset notification from FW + * @pvr_dev: Device pointer. + * @data: Data provided by FW. + * + * This will decode the data structure provided by FW and print the results via drm_info(). + */ +void +pvr_dump_context_reset_notification(struct pvr_device *pvr_dev, + struct rogue_fwif_fwccb_cmd_context_reset_data *data) +{ + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + + if (data->flags & ROGUE_FWIF_FWCCB_CMD_CONTEXT_RESET_FLAG_ALL_CTXS) { + drm_info(drm_dev, "Received context reset notification for all contexts\n"); + } else { + drm_info(drm_dev, "Received context reset notification on context %u\n", + data->server_common_context_id); + } + + drm_info(drm_dev, " Reset reason=%u (%s)\n", data->reset_reason, + get_reset_reason_desc((enum rogue_context_reset_reason)data->reset_reason)); + drm_info(drm_dev, " Data Master=%u (%s)\n", data->dm, get_dm_name(data->dm)); + drm_info(drm_dev, " Job ref=%u\n", data->reset_job_ref); + + if (data->flags & ROGUE_FWIF_FWCCB_CMD_CONTEXT_RESET_FLAG_PF) { + drm_info(drm_dev, " Page fault occurred, fault address=%llx\n", + data->fault_address); + } +} diff --git a/drivers/gpu/drm/imagination/pvr_dump.h b/drivers/gpu/drm/imagination/pvr_dump.h new file mode 100644 index 000000000000..3c0728c05596 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_dump.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright (c) 2026 Imagination Technologies Ltd. */ + +#ifndef PVR_DUMP_H +#define PVR_DUMP_H + +/* Forward declaration from pvr_device.h. */ +struct pvr_device; + +/* Forward declaration from pvr_rogue_fwif.h. */ +struct rogue_fwif_fwccb_cmd_context_reset_data; + +void +pvr_dump_context_reset_notification(struct pvr_device *pvr_dev, + struct rogue_fwif_fwccb_cmd_context_reset_data *data); + +#endif /* PVR_DUMP_H */ diff --git a/drivers/gpu/drm/imagination/pvr_free_list.c b/drivers/gpu/drm/imagination/pvr_free_list.c index 5e51bc980751..e85cac83834c 100644 --- a/drivers/gpu/drm/imagination/pvr_free_list.c +++ b/drivers/gpu/drm/imagination/pvr_free_list.c @@ -237,11 +237,10 @@ pvr_free_list_insert_pages_locked(struct pvr_free_list *free_list, dma_addr_t dma_addr = sg_page_iter_dma_address(&dma_iter); u64 dma_pfn = dma_addr >> ROGUE_BIF_PM_PHYSICAL_PAGE_ALIGNSHIFT; - u32 dma_addr_offset; BUILD_BUG_ON(ROGUE_BIF_PM_PHYSICAL_PAGE_SIZE > PAGE_SIZE); - for (dma_addr_offset = 0; dma_addr_offset < PAGE_SIZE; + for (u32 dma_addr_offset = 0; dma_addr_offset < PAGE_SIZE; dma_addr_offset += ROGUE_BIF_PM_PHYSICAL_PAGE_SIZE) { WARN_ON_ONCE(dma_pfn >> 32); @@ -308,7 +307,7 @@ pvr_free_list_grow(struct pvr_free_list *free_list, u32 num_pages) goto err_unlock; } - free_list_node = kzalloc(sizeof(*free_list_node), GFP_KERNEL); + free_list_node = kzalloc_obj(*free_list_node); if (!free_list_node) { err = -ENOMEM; goto err_unlock; @@ -416,7 +415,7 @@ pvr_free_list_create(struct pvr_file *pvr_file, int err; /* Create and fill out the kernel structure */ - free_list = kzalloc(sizeof(*free_list), GFP_KERNEL); + free_list = kzalloc_obj(*free_list); if (!free_list) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/imagination/pvr_fw.c b/drivers/gpu/drm/imagination/pvr_fw.c index 3debc9870a82..288516dc2560 100644 --- a/drivers/gpu/drm/imagination/pvr_fw.c +++ b/drivers/gpu/drm/imagination/pvr_fw.c @@ -17,6 +17,7 @@ #include <drm/drm_drv.h> #include <drm/drm_managed.h> #include <drm/drm_mm.h> +#include <drm/drm_print.h> #include <linux/clk.h> #include <linux/firmware.h> #include <linux/math.h> @@ -50,9 +51,8 @@ pvr_fw_find_layout_entry(struct pvr_device *pvr_dev, enum pvr_fw_section_id id) { const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; - u32 entry; - for (entry = 0; entry < num_layout_entries; entry++) { + for (u32 entry = 0; entry < num_layout_entries; entry++) { if (layout_entries[entry].id == id) return &layout_entries[entry]; } @@ -65,9 +65,8 @@ pvr_fw_find_private_data(struct pvr_device *pvr_dev) { const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; - u32 entry; - for (entry = 0; entry < num_layout_entries; entry++) { + for (u32 entry = 0; entry < num_layout_entries; entry++) { if (layout_entries[entry].id == META_PRIVATE_DATA || layout_entries[entry].id == MIPS_PRIVATE_DATA || layout_entries[entry].id == RISCV_PRIVATE_DATA) @@ -97,7 +96,6 @@ pvr_fw_validate(struct pvr_device *pvr_dev) const u8 *fw = firmware->data; u32 fw_offset = firmware->size - SZ_4K; u32 layout_table_size; - u32 entry; if (firmware->size < SZ_4K || (firmware->size % FW_BLOCK_SIZE)) return -EINVAL; @@ -144,7 +142,7 @@ pvr_fw_validate(struct pvr_device *pvr_dev) return -EINVAL; layout_entries = (const struct pvr_fw_layout_entry *)&fw[fw_offset]; - for (entry = 0; entry < header->layout_entry_num; entry++) { + for (u32 entry = 0; entry < header->layout_entry_num; entry++) { u32 start_addr = layout_entries[entry].base_addr; u32 end_addr = start_addr + layout_entries[entry].alloc_size; @@ -233,13 +231,12 @@ pvr_fw_find_mmu_segment(struct pvr_device *pvr_dev, u32 addr, u32 size, void *fw const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; u32 end_addr = addr + size; - int entry = 0; /* Ensure requested range is not zero, and size is not causing addr to overflow. */ if (end_addr <= addr) return -EINVAL; - for (entry = 0; entry < num_layout_entries; entry++) { + for (int entry = 0; entry < num_layout_entries; entry++) { u32 entry_start_addr = layout_entries[entry].base_addr; u32 entry_end_addr = entry_start_addr + layout_entries[entry].alloc_size; @@ -441,6 +438,9 @@ fw_runtime_cfg_init(void *cpu_ptr, void *priv) runtime_cfg->active_pm_latency_persistant = true; WARN_ON(PVR_FEATURE_VALUE(pvr_dev, num_clusters, &runtime_cfg->default_dusts_num_init) != 0); + + /* Keep watchdog timer disabled. */ + runtime_cfg->wdg_period_us = 0; } static void @@ -663,7 +663,7 @@ pvr_fw_process(struct pvr_device *pvr_dev) return PTR_ERR(fw_code_ptr); } - if (pvr_dev->fw_dev.defs->has_fixed_data_addr()) { + if (pvr_dev->fw_dev.defs->has_fixed_data_addr) { u32 base_addr = private_data->base_addr & pvr_dev->fw_dev.fw_heap_info.offset_mask; fw_data_ptr = @@ -732,7 +732,7 @@ pvr_fw_process(struct pvr_device *pvr_dev) fw_mem->core_data, fw_mem->core_code_alloc_size); if (err) - goto err_free_fw_core_data_obj; + goto err_free_kdata; memcpy(fw_code_ptr, fw_mem->code, fw_mem->code_alloc_size); memcpy(fw_data_ptr, fw_mem->data, fw_mem->data_alloc_size); @@ -742,10 +742,14 @@ pvr_fw_process(struct pvr_device *pvr_dev) memcpy(fw_core_data_ptr, fw_mem->core_data, fw_mem->core_data_alloc_size); /* We're finished with the firmware section memory on the CPU, unmap. */ - if (fw_core_data_ptr) + if (fw_core_data_ptr) { pvr_fw_object_vunmap(fw_mem->core_data_obj); - if (fw_core_code_ptr) + fw_core_data_ptr = NULL; + } + if (fw_core_code_ptr) { pvr_fw_object_vunmap(fw_mem->core_code_obj); + fw_core_code_ptr = NULL; + } pvr_fw_object_vunmap(fw_mem->data_obj); fw_data_ptr = NULL; pvr_fw_object_vunmap(fw_mem->code_obj); @@ -753,7 +757,7 @@ pvr_fw_process(struct pvr_device *pvr_dev) err = pvr_fw_create_fwif_connection_ctl(pvr_dev); if (err) - goto err_free_fw_core_data_obj; + goto err_free_kdata; return 0; @@ -763,13 +767,16 @@ err_free_kdata: kfree(fw_mem->data); kfree(fw_mem->code); -err_free_fw_core_data_obj: if (fw_core_data_ptr) - pvr_fw_object_unmap_and_destroy(fw_mem->core_data_obj); + pvr_fw_object_vunmap(fw_mem->core_data_obj); + if (fw_mem->core_data_obj) + pvr_fw_object_destroy(fw_mem->core_data_obj); err_free_fw_core_code_obj: if (fw_core_code_ptr) - pvr_fw_object_unmap_and_destroy(fw_mem->core_code_obj); + pvr_fw_object_vunmap(fw_mem->core_code_obj); + if (fw_mem->core_code_obj) + pvr_fw_object_destroy(fw_mem->core_code_obj); err_free_fw_data_obj: if (fw_data_ptr) @@ -836,6 +843,12 @@ pvr_fw_cleanup(struct pvr_device *pvr_dev) struct pvr_fw_mem *fw_mem = &pvr_dev->fw_dev.mem; pvr_fw_fini_fwif_connection_ctl(pvr_dev); + + kfree(fw_mem->core_data); + kfree(fw_mem->core_code); + kfree(fw_mem->data); + kfree(fw_mem->code); + if (fw_mem->core_code_obj) pvr_fw_object_destroy(fw_mem->core_code_obj); if (fw_mem->core_data_obj) @@ -926,18 +939,22 @@ pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev) int pvr_fw_init(struct pvr_device *pvr_dev) { + static const struct pvr_fw_defs *fw_defs[PVR_FW_PROCESSOR_TYPE_COUNT] = { + [PVR_FW_PROCESSOR_TYPE_META] = &pvr_fw_defs_meta, + [PVR_FW_PROCESSOR_TYPE_MIPS] = &pvr_fw_defs_mips, + [PVR_FW_PROCESSOR_TYPE_RISCV] = &pvr_fw_defs_riscv, + }; + u32 kccb_size_log2 = ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT; u32 kccb_rtn_size = (1 << kccb_size_log2) * sizeof(*pvr_dev->kccb.rtn); struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; int err; - if (fw_dev->processor_type == PVR_FW_PROCESSOR_TYPE_META) - fw_dev->defs = &pvr_fw_defs_meta; - else if (fw_dev->processor_type == PVR_FW_PROCESSOR_TYPE_MIPS) - fw_dev->defs = &pvr_fw_defs_mips; - else + if (fw_dev->processor_type >= PVR_FW_PROCESSOR_TYPE_COUNT) return -EINVAL; + fw_dev->defs = fw_defs[fw_dev->processor_type]; + err = fw_dev->defs->init(pvr_dev); if (err) return err; @@ -1255,7 +1272,7 @@ pvr_fw_object_create_and_map_common(struct pvr_device *pvr_dev, size_t size, /* %DRM_PVR_BO_PM_FW_PROTECT is implicit for FW objects. */ flags |= DRM_PVR_BO_PM_FW_PROTECT; - fw_obj = kzalloc(sizeof(*fw_obj), GFP_KERNEL); + fw_obj = kzalloc_obj(*fw_obj); if (!fw_obj) return ERR_PTR(-ENOMEM); @@ -1443,6 +1460,15 @@ void pvr_fw_object_get_fw_addr_offset(struct pvr_fw_object *fw_obj, u32 offset, *fw_addr_out = pvr_dev->fw_dev.defs->get_fw_addr_with_offset(fw_obj, offset); } +u64 +pvr_fw_obj_get_gpu_addr(struct pvr_fw_object *fw_obj) +{ + struct pvr_device *pvr_dev = to_pvr_device(gem_from_pvr_gem(fw_obj->gem)->dev); + struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; + + return fw_dev->fw_heap_info.gpu_addr + fw_obj->fw_addr_offset; +} + /* * pvr_fw_hard_reset() - Re-initialise the FW code and data segments, and reset all global FW * structures diff --git a/drivers/gpu/drm/imagination/pvr_fw.h b/drivers/gpu/drm/imagination/pvr_fw.h index b7966bd574a9..1404dd492d7c 100644 --- a/drivers/gpu/drm/imagination/pvr_fw.h +++ b/drivers/gpu/drm/imagination/pvr_fw.h @@ -167,47 +167,30 @@ struct pvr_fw_defs { int (*wrapper_init)(struct pvr_device *pvr_dev); /** - * @has_fixed_data_addr: + * @irq_pending: Check interrupt status register for pending interrupts. * - * Called to check if firmware fixed data must be loaded at the address given by the - * firmware layout table. + * @pvr_dev: Target PowerVR device. * * This function is mandatory. + */ + bool (*irq_pending)(struct pvr_device *pvr_dev); + + /** + * @irq_clear: Clear pending interrupts. * - * Returns: - * * %true if firmware fixed data must be loaded at the address given by the firmware - * layout table. - * * %false otherwise. + * @pvr_dev: Target PowerVR device. + * + * This function is mandatory. */ - bool (*has_fixed_data_addr)(void); + void (*irq_clear)(struct pvr_device *pvr_dev); /** - * @irq: FW Interrupt information. + * @has_fixed_data_addr: Specify whether the firmware fixed data must be loaded at the + * address given by the firmware layout table. * - * Those are processor dependent, and should be initialized by the - * processor backend in pvr_fw_funcs::init(). + * This value is mandatory. */ - struct { - /** @enable_reg: FW interrupt enable register. */ - u32 enable_reg; - - /** @status_reg: FW interrupt status register. */ - u32 status_reg; - - /** - * @clear_reg: FW interrupt clear register. - * - * If @status_reg == @clear_reg, we clear by write a bit to zero, - * otherwise we clear by writing a bit to one. - */ - u32 clear_reg; - - /** @event_mask: Bitmask of events to listen for. */ - u32 event_mask; - - /** @clear_mask: Value to write to the clear_reg in order to clear FW IRQs. */ - u32 clear_mask; - } irq; + bool has_fixed_data_addr; }; /** @@ -400,26 +383,16 @@ struct pvr_fw_device { } fw_objs; }; -#define pvr_fw_irq_read_reg(pvr_dev, name) \ - pvr_cr_read32((pvr_dev), (pvr_dev)->fw_dev.defs->irq.name ## _reg) - -#define pvr_fw_irq_write_reg(pvr_dev, name, value) \ - pvr_cr_write32((pvr_dev), (pvr_dev)->fw_dev.defs->irq.name ## _reg, value) - -#define pvr_fw_irq_pending(pvr_dev) \ - (pvr_fw_irq_read_reg(pvr_dev, status) & (pvr_dev)->fw_dev.defs->irq.event_mask) - -#define pvr_fw_irq_clear(pvr_dev) \ - pvr_fw_irq_write_reg(pvr_dev, clear, (pvr_dev)->fw_dev.defs->irq.clear_mask) - -#define pvr_fw_irq_enable(pvr_dev) \ - pvr_fw_irq_write_reg(pvr_dev, enable, (pvr_dev)->fw_dev.defs->irq.event_mask) - -#define pvr_fw_irq_disable(pvr_dev) \ - pvr_fw_irq_write_reg(pvr_dev, enable, 0) +enum pvr_fw_processor_type { + PVR_FW_PROCESSOR_TYPE_META = 0, + PVR_FW_PROCESSOR_TYPE_MIPS, + PVR_FW_PROCESSOR_TYPE_RISCV, + PVR_FW_PROCESSOR_TYPE_COUNT, +}; extern const struct pvr_fw_defs pvr_fw_defs_meta; extern const struct pvr_fw_defs pvr_fw_defs_mips; +extern const struct pvr_fw_defs pvr_fw_defs_riscv; int pvr_fw_validate_init_device_info(struct pvr_device *pvr_dev); int pvr_fw_init(struct pvr_device *pvr_dev); @@ -506,4 +479,18 @@ pvr_fw_object_get_fw_addr(struct pvr_fw_object *fw_obj, u32 *fw_addr_out) pvr_fw_object_get_fw_addr_offset(fw_obj, 0, fw_addr_out); } +u64 +pvr_fw_obj_get_gpu_addr(struct pvr_fw_object *fw_obj); + +static __always_inline size_t +pvr_fw_obj_get_object_size(struct pvr_fw_object *fw_obj) +{ + return pvr_gem_object_size(fw_obj->gem); +} + +/* Util functions defined in pvr_fw_util.c. These are intended for use in pvr_fw_<arch>.c files. */ +int +pvr_fw_process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, u8 *fw_code_ptr, + u8 *fw_data_ptr, u8 *fw_core_code_ptr, u8 *fw_core_data_ptr); + #endif /* PVR_FW_H */ diff --git a/drivers/gpu/drm/imagination/pvr_fw_meta.c b/drivers/gpu/drm/imagination/pvr_fw_meta.c index 6d13864851fc..9ff03bc60a08 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_meta.c +++ b/drivers/gpu/drm/imagination/pvr_fw_meta.c @@ -16,6 +16,8 @@ #include <linux/ktime.h> #include <linux/types.h> +#include <drm/drm_print.h> + #define ROGUE_FW_HEAP_META_SHIFT 25 /* 32 MB */ #define POLL_TIMEOUT_USEC 1000000 @@ -370,13 +372,12 @@ configure_seg_mmu(struct pvr_device *pvr_dev, u32 **boot_conf_ptr) const struct pvr_fw_layout_entry *layout_entries = pvr_dev->fw_dev.layout_entries; u32 num_layout_entries = pvr_dev->fw_dev.header->layout_entry_num; u64 seg_out_addr_top; - u32 i; seg_out_addr_top = ROGUE_FW_SEGMMU_OUTADDR_TOP_SLC(MMU_CONTEXT_MAPPING_FWPRIV, ROGUE_FW_SEGMMU_META_BIFDM_ID); - for (i = 0; i < num_layout_entries; i++) { + for (u32 i = 0; i < num_layout_entries; i++) { /* * FW code is using the bootloader segment which is already * configured on boot. FW coremem code and data don't use the @@ -534,9 +535,17 @@ pvr_meta_vm_unmap(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj) } static bool -pvr_meta_has_fixed_data_addr(void) +pvr_meta_irq_pending(struct pvr_device *pvr_dev) +{ + return pvr_cr_read32(pvr_dev, ROGUE_CR_META_SP_MSLVIRQSTATUS) & + ROGUE_CR_META_SP_MSLVIRQSTATUS_TRIGVECT2_EN; +} + +static void +pvr_meta_irq_clear(struct pvr_device *pvr_dev) { - return false; + pvr_cr_write32(pvr_dev, ROGUE_CR_META_SP_MSLVIRQSTATUS, + ROGUE_CR_META_SP_MSLVIRQSTATUS_TRIGVECT2_CLRMSK); } const struct pvr_fw_defs pvr_fw_defs_meta = { @@ -546,12 +555,7 @@ const struct pvr_fw_defs pvr_fw_defs_meta = { .vm_unmap = pvr_meta_vm_unmap, .get_fw_addr_with_offset = pvr_meta_get_fw_addr_with_offset, .wrapper_init = pvr_meta_wrapper_init, - .has_fixed_data_addr = pvr_meta_has_fixed_data_addr, - .irq = { - .enable_reg = ROGUE_CR_META_SP_MSLVIRQENABLE, - .status_reg = ROGUE_CR_META_SP_MSLVIRQSTATUS, - .clear_reg = ROGUE_CR_META_SP_MSLVIRQSTATUS, - .event_mask = ROGUE_CR_META_SP_MSLVIRQSTATUS_TRIGVECT2_EN, - .clear_mask = ROGUE_CR_META_SP_MSLVIRQSTATUS_TRIGVECT2_CLRMSK, - }, + .irq_pending = pvr_meta_irq_pending, + .irq_clear = pvr_meta_irq_clear, + .has_fixed_data_addr = false, }; diff --git a/drivers/gpu/drm/imagination/pvr_fw_mips.c b/drivers/gpu/drm/imagination/pvr_fw_mips.c index 0bed0257e2ab..6914fc46db50 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_mips.c +++ b/drivers/gpu/drm/imagination/pvr_fw_mips.c @@ -8,7 +8,6 @@ #include "pvr_rogue_mips.h" #include "pvr_vm_mips.h" -#include <linux/elf.h> #include <linux/err.h> #include <linux/types.h> @@ -16,60 +15,6 @@ #define ROGUE_FW_HEAP_MIPS_SHIFT 24 /* 16 MB */ #define ROGUE_FW_HEAP_MIPS_RESERVED_SIZE SZ_1M -/** - * process_elf_command_stream() - Process ELF firmware image and populate - * firmware sections - * @pvr_dev: Device pointer. - * @fw: Pointer to firmware image. - * @fw_code_ptr: Pointer to FW code section. - * @fw_data_ptr: Pointer to FW data section. - * @fw_core_code_ptr: Pointer to FW coremem code section. - * @fw_core_data_ptr: Pointer to FW coremem data section. - * - * Returns : - * * 0 on success, or - * * -EINVAL on any error in ELF command stream. - */ -static int -process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, u8 *fw_code_ptr, - u8 *fw_data_ptr, u8 *fw_core_code_ptr, u8 *fw_core_data_ptr) -{ - struct elf32_hdr *header = (struct elf32_hdr *)fw; - struct elf32_phdr *program_header = (struct elf32_phdr *)(fw + header->e_phoff); - struct drm_device *drm_dev = from_pvr_device(pvr_dev); - u32 entry; - int err; - - for (entry = 0; entry < header->e_phnum; entry++, program_header++) { - void *write_addr; - - /* Only consider loadable entries in the ELF segment table */ - if (program_header->p_type != PT_LOAD) - continue; - - err = pvr_fw_find_mmu_segment(pvr_dev, program_header->p_vaddr, - program_header->p_memsz, fw_code_ptr, fw_data_ptr, - fw_core_code_ptr, fw_core_data_ptr, &write_addr); - if (err) { - drm_err(drm_dev, - "Addr 0x%x (size: %d) not found in any firmware segment", - program_header->p_vaddr, program_header->p_memsz); - return err; - } - - /* Write to FW allocation only if available */ - if (write_addr) { - memcpy(write_addr, fw + program_header->p_offset, - program_header->p_filesz); - - memset((u8 *)write_addr + program_header->p_filesz, 0, - program_header->p_memsz - program_header->p_filesz); - } - } - - return 0; -} - static int pvr_mips_init(struct pvr_device *pvr_dev) { @@ -97,11 +42,10 @@ pvr_mips_fw_process(struct pvr_device *pvr_dev, const u8 *fw, const struct pvr_fw_layout_entry *stack_entry; struct rogue_mipsfw_boot_data *boot_data; dma_addr_t dma_addr; - u32 page_nr; int err; - err = process_elf_command_stream(pvr_dev, fw, fw_code_ptr, fw_data_ptr, fw_core_code_ptr, - fw_core_data_ptr); + err = pvr_fw_process_elf_command_stream(pvr_dev, fw, fw_code_ptr, fw_data_ptr, + fw_core_code_ptr, fw_core_data_ptr); if (err) return err; @@ -132,7 +76,7 @@ pvr_mips_fw_process(struct pvr_device *pvr_dev, const u8 *fw, boot_data->reg_base = pvr_dev->regs_resource->start; - for (page_nr = 0; page_nr < ARRAY_SIZE(boot_data->pt_phys_addr); page_nr++) { + for (u32 page_nr = 0; page_nr < ARRAY_SIZE(boot_data->pt_phys_addr); page_nr++) { /* Firmware expects 4k pages, but host page size might be different. */ u32 src_page_nr = (page_nr * ROGUE_MIPSFW_PAGE_SIZE_4K) >> PAGE_SHIFT; u32 page_offset = (page_nr * ROGUE_MIPSFW_PAGE_SIZE_4K) & ~PAGE_MASK; @@ -228,9 +172,17 @@ pvr_mips_get_fw_addr_with_offset(struct pvr_fw_object *fw_obj, u32 offset) } static bool -pvr_mips_has_fixed_data_addr(void) +pvr_mips_irq_pending(struct pvr_device *pvr_dev) +{ + return pvr_cr_read32(pvr_dev, ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS) & + ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS_EVENT_EN; +} + +static void +pvr_mips_irq_clear(struct pvr_device *pvr_dev) { - return true; + pvr_cr_write32(pvr_dev, ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR, + ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR_EVENT_EN); } const struct pvr_fw_defs pvr_fw_defs_mips = { @@ -241,12 +193,7 @@ const struct pvr_fw_defs pvr_fw_defs_mips = { .vm_unmap = pvr_vm_mips_unmap, .get_fw_addr_with_offset = pvr_mips_get_fw_addr_with_offset, .wrapper_init = pvr_mips_wrapper_init, - .has_fixed_data_addr = pvr_mips_has_fixed_data_addr, - .irq = { - .enable_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_ENABLE, - .status_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS, - .clear_reg = ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR, - .event_mask = ROGUE_CR_MIPS_WRAPPER_IRQ_STATUS_EVENT_EN, - .clear_mask = ROGUE_CR_MIPS_WRAPPER_IRQ_CLEAR_EVENT_EN, - }, + .irq_pending = pvr_mips_irq_pending, + .irq_clear = pvr_mips_irq_clear, + .has_fixed_data_addr = true, }; diff --git a/drivers/gpu/drm/imagination/pvr_fw_riscv.c b/drivers/gpu/drm/imagination/pvr_fw_riscv.c new file mode 100644 index 000000000000..fc13d483be9a --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_fw_riscv.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* Copyright (c) 2024 Imagination Technologies Ltd. */ + +#include "pvr_device.h" +#include "pvr_fw.h" +#include "pvr_fw_info.h" +#include "pvr_fw_mips.h" +#include "pvr_gem.h" +#include "pvr_rogue_cr_defs.h" +#include "pvr_rogue_riscv.h" +#include "pvr_vm.h" + +#include <linux/compiler.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/ktime.h> +#include <linux/types.h> + +#define ROGUE_FW_HEAP_RISCV_SHIFT 25 /* 32 MB */ +#define ROGUE_FW_HEAP_RISCV_SIZE (1u << ROGUE_FW_HEAP_RISCV_SHIFT) + +static int +pvr_riscv_wrapper_init(struct pvr_device *pvr_dev) +{ + const u64 common_opts = + ((u64)(ROGUE_FW_HEAP_RISCV_SIZE >> FWCORE_ADDR_REMAP_CONFIG0_SIZE_ALIGNSHIFT) + << ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_SIZE_SHIFT) | + ((u64)MMU_CONTEXT_MAPPING_FWPRIV + << FWCORE_ADDR_REMAP_CONFIG0_MMU_CONTEXT_SHIFT); + + u64 code_addr = pvr_fw_obj_get_gpu_addr(pvr_dev->fw_dev.mem.code_obj); + u64 data_addr = pvr_fw_obj_get_gpu_addr(pvr_dev->fw_dev.mem.data_obj); + + /* This condition allows us to OR the addresses into the register directly. */ + static_assert(ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_SHIFT == + ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_ALIGNSHIFT); + + WARN_ON(code_addr & ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_CLRMSK); + WARN_ON(data_addr & ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG1_DEVVADDR_CLRMSK); + + pvr_cr_write64(pvr_dev, ROGUE_RISCVFW_REGION_REMAP_CR(BOOTLDR_CODE), + code_addr | common_opts | ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_FETCH_EN_EN); + + pvr_cr_write64(pvr_dev, ROGUE_RISCVFW_REGION_REMAP_CR(BOOTLDR_DATA), + data_addr | common_opts | + ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0_LOAD_STORE_EN_EN); + + /* Garten IDLE bit controlled by RISC-V. */ + pvr_cr_write64(pvr_dev, ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG, + ROGUE_CR_MTS_GARTEN_WRAPPER_CONFIG_IDLE_CTRL_META); + + return 0; +} + +struct rogue_riscv_fw_boot_data { + u64 coremem_code_dev_vaddr; + u64 coremem_data_dev_vaddr; + u32 coremem_code_fw_addr; + u32 coremem_data_fw_addr; + u32 coremem_code_size; + u32 coremem_data_size; + u32 flags; + u32 reserved; +}; + +static int +pvr_riscv_fw_process(struct pvr_device *pvr_dev, const u8 *fw, + u8 *fw_code_ptr, u8 *fw_data_ptr, u8 *fw_core_code_ptr, u8 *fw_core_data_ptr, + u32 core_code_alloc_size) +{ + struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; + struct pvr_fw_mem *fw_mem = &fw_dev->mem; + struct rogue_riscv_fw_boot_data *boot_data; + int err; + + err = pvr_fw_process_elf_command_stream(pvr_dev, fw, fw_code_ptr, fw_data_ptr, + fw_core_code_ptr, fw_core_data_ptr); + if (err) + goto err_out; + + boot_data = (struct rogue_riscv_fw_boot_data *)fw_data_ptr; + + if (fw_mem->core_code_obj) { + boot_data->coremem_code_dev_vaddr = pvr_fw_obj_get_gpu_addr(fw_mem->core_code_obj); + pvr_fw_object_get_fw_addr(fw_mem->core_code_obj, &boot_data->coremem_code_fw_addr); + boot_data->coremem_code_size = pvr_fw_obj_get_object_size(fw_mem->core_code_obj); + } + + if (fw_mem->core_data_obj) { + boot_data->coremem_data_dev_vaddr = pvr_fw_obj_get_gpu_addr(fw_mem->core_data_obj); + pvr_fw_object_get_fw_addr(fw_mem->core_data_obj, &boot_data->coremem_data_fw_addr); + boot_data->coremem_data_size = pvr_fw_obj_get_object_size(fw_mem->core_data_obj); + } + + return 0; + +err_out: + return err; +} + +static int +pvr_riscv_init(struct pvr_device *pvr_dev) +{ + pvr_fw_heap_info_init(pvr_dev, ROGUE_FW_HEAP_RISCV_SHIFT, 0); + + return 0; +} + +static u32 +pvr_riscv_get_fw_addr_with_offset(struct pvr_fw_object *fw_obj, u32 offset) +{ + u32 fw_addr = fw_obj->fw_addr_offset + offset; + + /* RISC-V cacheability is determined by address. */ + if (fw_obj->gem->flags & PVR_BO_FW_FLAGS_DEVICE_UNCACHED) + fw_addr |= ROGUE_RISCVFW_REGION_BASE(SHARED_UNCACHED_DATA); + else + fw_addr |= ROGUE_RISCVFW_REGION_BASE(SHARED_CACHED_DATA); + + return fw_addr; +} + +static int +pvr_riscv_vm_map(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj) +{ + struct pvr_gem_object *pvr_obj = fw_obj->gem; + + return pvr_vm_map(pvr_dev->kernel_vm_ctx, pvr_obj, 0, fw_obj->fw_mm_node.start, + pvr_gem_object_size(pvr_obj)); +} + +static void +pvr_riscv_vm_unmap(struct pvr_device *pvr_dev, struct pvr_fw_object *fw_obj) +{ + struct pvr_gem_object *pvr_obj = fw_obj->gem; + + pvr_vm_unmap_obj(pvr_dev->kernel_vm_ctx, pvr_obj, + fw_obj->fw_mm_node.start, fw_obj->fw_mm_node.size); +} + +static bool +pvr_riscv_irq_pending(struct pvr_device *pvr_dev) +{ + return pvr_cr_read32(pvr_dev, ROGUE_CR_IRQ_OS0_EVENT_STATUS) & + ROGUE_CR_IRQ_OS0_EVENT_STATUS_SOURCE_EN; +} + +static void +pvr_riscv_irq_clear(struct pvr_device *pvr_dev) +{ + pvr_cr_write32(pvr_dev, ROGUE_CR_IRQ_OS0_EVENT_CLEAR, + ROGUE_CR_IRQ_OS0_EVENT_CLEAR_SOURCE_EN); +} + +const struct pvr_fw_defs pvr_fw_defs_riscv = { + .init = pvr_riscv_init, + .fw_process = pvr_riscv_fw_process, + .vm_map = pvr_riscv_vm_map, + .vm_unmap = pvr_riscv_vm_unmap, + .get_fw_addr_with_offset = pvr_riscv_get_fw_addr_with_offset, + .wrapper_init = pvr_riscv_wrapper_init, + .irq_pending = pvr_riscv_irq_pending, + .irq_clear = pvr_riscv_irq_clear, + .has_fixed_data_addr = false, +}; diff --git a/drivers/gpu/drm/imagination/pvr_fw_startstop.c b/drivers/gpu/drm/imagination/pvr_fw_startstop.c index 36cec227cfe3..e24ed6fc4362 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_startstop.c +++ b/drivers/gpu/drm/imagination/pvr_fw_startstop.c @@ -49,6 +49,14 @@ rogue_bif_init(struct pvr_device *pvr_dev) pvr_cr_write64(pvr_dev, BIF_CAT_BASEX(MMU_CONTEXT_MAPPING_FWPRIV), pc_addr); + + if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV) { + pc_addr = (((u64)pc_dma_addr >> ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_ALIGNSHIFT) + << ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_SHIFT) & + ~ROGUE_CR_FWCORE_MEM_CAT_BASE0_ADDR_CLRMSK; + + pvr_cr_write64(pvr_dev, FWCORE_MEM_CAT_BASEX(MMU_CONTEXT_MAPPING_FWPRIV), pc_addr); + } } static int @@ -114,6 +122,9 @@ pvr_fw_start(struct pvr_device *pvr_dev) (void)pvr_cr_read32(pvr_dev, ROGUE_CR_SYS_BUS_SECURE); /* Fence write */ } + if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV) + pvr_cr_write32(pvr_dev, ROGUE_CR_FWCORE_BOOT, 0); + /* Set Rogue in soft-reset. */ pvr_cr_write64(pvr_dev, ROGUE_CR_SOFT_RESET, soft_reset_mask); if (has_reset2) @@ -167,6 +178,12 @@ pvr_fw_start(struct pvr_device *pvr_dev) /* ... and afterwards. */ udelay(3); + if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_RISCV) { + /* Boot the FW. */ + pvr_cr_write32(pvr_dev, ROGUE_CR_FWCORE_BOOT, 1); + udelay(3); + } + return 0; err_reset: @@ -192,18 +209,32 @@ pvr_fw_stop(struct pvr_device *pvr_dev) ROGUE_CR_SIDEKICK_IDLE_SOCIF_EN | ROGUE_CR_SIDEKICK_IDLE_HOSTIF_EN); bool skip_garten_idle = false; + u64 layout_mars_value = 0; + bool layout_mars = false; + bool meta_fw = pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_META; u32 reg_value; int err; + if (PVR_FEATURE_VALUE(pvr_dev, layout_mars, &layout_mars_value) == 0) + layout_mars = layout_mars_value > 0; + /* - * Wait for Sidekick/Jones to signal IDLE except for the Garten Wrapper. - * For cores with the LAYOUT_MARS feature, SIDEKICK would have been + * For cores with the LAYOUT_MARS feature, SIDEKICK and SLC would have been * powered down by the FW. */ - err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, sidekick_idle_mask, - sidekick_idle_mask, POLL_TIMEOUT_USEC); - if (err) - return err; + if (!layout_mars) { + /* Wait for Sidekick/Jones to signal IDLE except for the Garten Wrapper. */ + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, sidekick_idle_mask, + sidekick_idle_mask, POLL_TIMEOUT_USEC); + if (err) + return err; + + /* Wait for SLC to signal IDLE. */ + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SLC_IDLE, ROGUE_CR_SLC_IDLE_MASKFULL, + ROGUE_CR_SLC_IDLE_MASKFULL, POLL_TIMEOUT_USEC); + if (err) + return err; + } /* Unset MTS DM association with threads. */ pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_INTCTX_THREAD0_DM_ASSOC, @@ -212,12 +243,15 @@ pvr_fw_stop(struct pvr_device *pvr_dev) pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_BGCTX_THREAD0_DM_ASSOC, ROGUE_CR_MTS_BGCTX_THREAD0_DM_ASSOC_MASKFULL & ROGUE_CR_MTS_BGCTX_THREAD0_DM_ASSOC_DM_ASSOC_CLRMSK); - pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC, - ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC_MASKFULL & - ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC_DM_ASSOC_CLRMSK); - pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC, - ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC_MASKFULL & - ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC_DM_ASSOC_CLRMSK); + + if (meta_fw) { + pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC, + ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC_MASKFULL & + ROGUE_CR_MTS_INTCTX_THREAD1_DM_ASSOC_DM_ASSOC_CLRMSK); + pvr_cr_write32(pvr_dev, ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC, + ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC_MASKFULL & + ROGUE_CR_MTS_BGCTX_THREAD1_DM_ASSOC_DM_ASSOC_CLRMSK); + } /* Extra Idle checks. */ err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_BIF_STATUS_MMU, 0, @@ -253,27 +287,25 @@ pvr_fw_stop(struct pvr_device *pvr_dev) return err; /* - * Wait for SLC to signal IDLE. - * For cores with the LAYOUT_MARS feature, SLC would have been powered - * down by the FW. + * For cores with the LAYOUT_MARS feature, SIDEKICK and SLC would have been + * powered down by the FW. */ - err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SLC_IDLE, - ROGUE_CR_SLC_IDLE_MASKFULL, - ROGUE_CR_SLC_IDLE_MASKFULL, POLL_TIMEOUT_USEC); - if (err) - return err; + if (!layout_mars) { + /* Wait for SLC to signal IDLE. */ + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SLC_IDLE, + ROGUE_CR_SLC_IDLE_MASKFULL, + ROGUE_CR_SLC_IDLE_MASKFULL, POLL_TIMEOUT_USEC); + if (err) + return err; - /* - * Wait for Sidekick/Jones to signal IDLE except for the Garten Wrapper. - * For cores with the LAYOUT_MARS feature, SIDEKICK would have been powered - * down by the FW. - */ - err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, sidekick_idle_mask, - sidekick_idle_mask, POLL_TIMEOUT_USEC); - if (err) - return err; + /* Wait for Sidekick/Jones to signal IDLE except for the Garten Wrapper. */ + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, sidekick_idle_mask, + sidekick_idle_mask, POLL_TIMEOUT_USEC); + if (err) + return err; + } - if (pvr_dev->fw_dev.processor_type == PVR_FW_PROCESSOR_TYPE_META) { + if (meta_fw) { err = pvr_meta_cr_read32(pvr_dev, META_CR_TxVECINT_BHALT, ®_value); if (err) return err; @@ -287,11 +319,28 @@ pvr_fw_stop(struct pvr_device *pvr_dev) skip_garten_idle = true; } - if (!skip_garten_idle) { - err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, - ROGUE_CR_SIDEKICK_IDLE_GARTEN_EN, - ROGUE_CR_SIDEKICK_IDLE_GARTEN_EN, + if (meta_fw || !layout_mars) { + if (!skip_garten_idle) { + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_SIDEKICK_IDLE, + ROGUE_CR_SIDEKICK_IDLE_GARTEN_EN, + ROGUE_CR_SIDEKICK_IDLE_GARTEN_EN, + POLL_TIMEOUT_USEC); + if (err) + return err; + } + } else { + /* + * As FW core has been moved from SIDEKICK to the new MARS domain, checking + * idle bits for CPU & System Arbiter excluding SOCIF which will never be + * idle if Host polling on this register + */ + err = pvr_cr_poll_reg32(pvr_dev, ROGUE_CR_MARS_IDLE, + ROGUE_CR_MARS_IDLE_CPU_EN | + ROGUE_CR_MARS_IDLE_MH_SYSARB0_EN, + ROGUE_CR_MARS_IDLE_CPU_EN | + ROGUE_CR_MARS_IDLE_MH_SYSARB0_EN, POLL_TIMEOUT_USEC); + if (err) return err; } diff --git a/drivers/gpu/drm/imagination/pvr_fw_trace.c b/drivers/gpu/drm/imagination/pvr_fw_trace.c index 5dbb636d7d4f..6193811ef7be 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_trace.c +++ b/drivers/gpu/drm/imagination/pvr_fw_trace.c @@ -9,29 +9,91 @@ #include <drm/drm_drv.h> #include <drm/drm_file.h> +#include <drm/drm_print.h> #include <linux/build_bug.h> +#include <linux/compiler_attributes.h> #include <linux/dcache.h> #include <linux/debugfs.h> +#include <linux/moduleparam.h> #include <linux/sysfs.h> #include <linux/types.h> +static int +validate_group_mask(struct pvr_device *pvr_dev, const u32 group_mask) +{ + if (group_mask & ~ROGUE_FWIF_LOG_TYPE_GROUP_MASK) { + drm_warn(from_pvr_device(pvr_dev), + "Invalid fw_trace group mask 0x%08x (must be a subset of 0x%08x)", + group_mask, ROGUE_FWIF_LOG_TYPE_GROUP_MASK); + return -EINVAL; + } + + return 0; +} + +static inline u32 +build_log_type(const u32 group_mask) +{ + if (!group_mask) + return ROGUE_FWIF_LOG_TYPE_NONE; + + return group_mask | ROGUE_FWIF_LOG_TYPE_TRACE; +} + +/* + * Don't gate this behind CONFIG_DEBUG_FS so that it can be used as an initial + * value without further conditional code... + */ +static u32 pvr_fw_trace_init_mask; + +/* + * ...but do only expose the module parameter if debugfs is enabled, since + * there's no reason to turn on fw_trace without it. + */ +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int +pvr_fw_trace_init_mask_set(const char *val, const struct kernel_param *kp) +{ + u32 mask = 0; + int err; + + err = kstrtouint(val, 0, &mask); + if (err) + return err; + + err = validate_group_mask(NULL, mask); + if (err) + return err; + + *(unsigned int *)kp->arg = mask; + + return 0; +} + +const struct kernel_param_ops pvr_fw_trace_init_mask_ops = { + .set = pvr_fw_trace_init_mask_set, + .get = param_get_hexint, +}; + +param_check_hexint(init_fw_trace_mask, &pvr_fw_trace_init_mask); +module_param_cb(init_fw_trace_mask, &pvr_fw_trace_init_mask_ops, &pvr_fw_trace_init_mask, 0600); +__MODULE_PARM_TYPE(init_fw_trace_mask, "hexint"); +MODULE_PARM_DESC(init_fw_trace_mask, + "Enable FW trace for the specified groups at device init time"); +#endif + static void tracebuf_ctrl_init(void *cpu_ptr, void *priv) { struct rogue_fwif_tracebuf *tracebuf_ctrl = cpu_ptr; struct pvr_fw_trace *fw_trace = priv; - u32 thread_nr; tracebuf_ctrl->tracebuf_size_in_dwords = ROGUE_FW_TRACE_BUF_DEFAULT_SIZE_IN_DWORDS; tracebuf_ctrl->tracebuf_flags = 0; + tracebuf_ctrl->log_type = build_log_type(fw_trace->group_mask); - if (fw_trace->group_mask) - tracebuf_ctrl->log_type = fw_trace->group_mask | ROGUE_FWIF_LOG_TYPE_TRACE; - else - tracebuf_ctrl->log_type = ROGUE_FWIF_LOG_TYPE_NONE; - - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { struct rogue_fwif_tracebuf_space *tracebuf_space = &tracebuf_ctrl->tracebuf[thread_nr]; struct pvr_fw_trace_buffer *trace_buffer = &fw_trace->buffers[thread_nr]; @@ -48,10 +110,9 @@ int pvr_fw_trace_init(struct pvr_device *pvr_dev) { struct pvr_fw_trace *fw_trace = &pvr_dev->fw_dev.fw_trace; struct drm_device *drm_dev = from_pvr_device(pvr_dev); - u32 thread_nr; int err; - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { struct pvr_fw_trace_buffer *trace_buffer = &fw_trace->buffers[thread_nr]; trace_buffer->buf = @@ -69,8 +130,13 @@ int pvr_fw_trace_init(struct pvr_device *pvr_dev) } } - /* TODO: Provide control of group mask. */ - fw_trace->group_mask = 0; + /* + * Load the initial group_mask from the init_fw_trace_mask module + * parameter. This allows early tracing before the user can write to + * debugfs. Unlike update_logtype(), we don't set log_type here as that + * is initialised by tracebuf_ctrl_init(). + */ + fw_trace->group_mask = pvr_fw_trace_init_mask; fw_trace->tracebuf_ctrl = pvr_fw_object_create_and_map(pvr_dev, @@ -88,7 +154,7 @@ int pvr_fw_trace_init(struct pvr_device *pvr_dev) BUILD_BUG_ON(ARRAY_SIZE(fw_trace->tracebuf_ctrl->tracebuf) != ARRAY_SIZE(fw_trace->buffers)); - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { struct rogue_fwif_tracebuf_space *tracebuf_space = &fw_trace->tracebuf_ctrl->tracebuf[thread_nr]; struct pvr_fw_trace_buffer *trace_buffer = &fw_trace->buffers[thread_nr]; @@ -99,7 +165,7 @@ int pvr_fw_trace_init(struct pvr_device *pvr_dev) return 0; err_free_buf: - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { struct pvr_fw_trace_buffer *trace_buffer = &fw_trace->buffers[thread_nr]; if (trace_buffer->buf) @@ -112,9 +178,8 @@ err_free_buf: void pvr_fw_trace_fini(struct pvr_device *pvr_dev) { struct pvr_fw_trace *fw_trace = &pvr_dev->fw_dev.fw_trace; - u32 thread_nr; - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); thread_nr++) { struct pvr_fw_trace_buffer *trace_buffer = &fw_trace->buffers[thread_nr]; pvr_fw_object_unmap_and_destroy(trace_buffer->buf_obj); @@ -122,14 +187,14 @@ void pvr_fw_trace_fini(struct pvr_device *pvr_dev) pvr_fw_object_unmap_and_destroy(fw_trace->tracebuf_ctrl_obj); } -#if defined(CONFIG_DEBUG_FS) - /** * update_logtype() - Send KCCB command to trigger FW to update logtype * @pvr_dev: Target PowerVR device - * @group_mask: New log group mask. + * @group_mask: New log group mask; must pass validate_group_mask(). * * Returns: + * * 0 if the provided @group_mask is the same as the current value (this is a + * short-circuit evaluation), * * 0 on success, * * Any error returned by pvr_kccb_send_cmd(), or * * -%EIO if the device is lost. @@ -138,19 +203,21 @@ static int update_logtype(struct pvr_device *pvr_dev, u32 group_mask) { struct pvr_fw_trace *fw_trace = &pvr_dev->fw_dev.fw_trace; + struct drm_device *drm_dev = from_pvr_device(pvr_dev); struct rogue_fwif_kccb_cmd cmd; int idx; int err; + int slot; - if (group_mask) - fw_trace->tracebuf_ctrl->log_type = ROGUE_FWIF_LOG_TYPE_TRACE | group_mask; - else - fw_trace->tracebuf_ctrl->log_type = ROGUE_FWIF_LOG_TYPE_NONE; + /* No change in group_mask => nothing to update. */ + if (fw_trace->group_mask == group_mask) + return 0; fw_trace->group_mask = group_mask; + fw_trace->tracebuf_ctrl->log_type = build_log_type(group_mask); down_read(&pvr_dev->reset_sem); - if (!drm_dev_enter(from_pvr_device(pvr_dev), &idx)) { + if (!drm_dev_enter(drm_dev, &idx)) { err = -EIO; goto err_up_read; } @@ -158,8 +225,13 @@ update_logtype(struct pvr_device *pvr_dev, u32 group_mask) cmd.cmd_type = ROGUE_FWIF_KCCB_CMD_LOGTYPE_UPDATE; cmd.kccb_flags = 0; - err = pvr_kccb_send_cmd(pvr_dev, &cmd, NULL); + err = pvr_kccb_send_cmd(pvr_dev, &cmd, &slot); + if (err) + goto err_drm_dev_exit; + + err = pvr_kccb_wait_for_completion(pvr_dev, slot, HZ, NULL); +err_drm_dev_exit: drm_dev_exit(idx); err_up_read: @@ -184,9 +256,7 @@ struct pvr_fw_trace_seq_data { static u32 find_sfid(u32 id) { - u32 i; - - for (i = 0; i < ARRAY_SIZE(stid_fmts); i++) { + for (u32 i = 0; i < ARRAY_SIZE(stid_fmts); i++) { if (stid_fmts[i].id == id) return i; } @@ -285,12 +355,11 @@ static void fw_trace_get_first(struct pvr_fw_trace_seq_data *trace_seq_data) static void *fw_trace_seq_start(struct seq_file *s, loff_t *pos) { struct pvr_fw_trace_seq_data *trace_seq_data = s->private; - u32 i; /* Reset trace index, then advance to *pos. */ fw_trace_get_first(trace_seq_data); - for (i = 0; i < *pos; i++) { + for (u32 i = 0; i < *pos; i++) { if (!fw_trace_get_next(trace_seq_data)) return NULL; } @@ -386,7 +455,7 @@ static int fw_trace_open(struct inode *inode, struct file *file) struct pvr_fw_trace_seq_data *trace_seq_data; int err; - trace_seq_data = kzalloc(sizeof(*trace_seq_data), GFP_KERNEL); + trace_seq_data = kzalloc_obj(*trace_seq_data); if (!trace_seq_data) return -ENOMEM; @@ -444,23 +513,43 @@ static const struct file_operations pvr_fw_trace_fops = { .release = fw_trace_release, }; -void -pvr_fw_trace_mask_update(struct pvr_device *pvr_dev, u32 old_mask, u32 new_mask) +static int pvr_fw_trace_mask_get(void *data, u64 *value) { - if (old_mask != new_mask) - update_logtype(pvr_dev, new_mask); + struct pvr_device *pvr_dev = data; + + *value = pvr_dev->fw_dev.fw_trace.group_mask; + + return 0; } +static int pvr_fw_trace_mask_set(void *data, u64 value) +{ + struct pvr_device *pvr_dev = data; + const u32 group_mask = (u32)value; + int err; + + err = validate_group_mask(pvr_dev, group_mask); + if (err) + return err; + + return update_logtype(pvr_dev, group_mask); +} + +DEFINE_DEBUGFS_ATTRIBUTE(pvr_fw_trace_mask_fops, pvr_fw_trace_mask_get, + pvr_fw_trace_mask_set, "0x%08llx\n"); + void pvr_fw_trace_debugfs_init(struct pvr_device *pvr_dev, struct dentry *dir) { struct pvr_fw_trace *fw_trace = &pvr_dev->fw_dev.fw_trace; - u32 thread_nr; + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; static_assert(ARRAY_SIZE(fw_trace->buffers) <= 10, "The filename buffer is only large enough for a single-digit thread count"); - for (thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); ++thread_nr) { + for (u32 thread_nr = 0; thread_nr < ARRAY_SIZE(fw_trace->buffers); ++thread_nr) { char filename[8]; snprintf(filename, ARRAY_SIZE(filename), "trace_%u", thread_nr); @@ -468,5 +557,7 @@ pvr_fw_trace_debugfs_init(struct pvr_device *pvr_dev, struct dentry *dir) &fw_trace->buffers[thread_nr], &pvr_fw_trace_fops); } + + debugfs_create_file("trace_mask", 0600, dir, pvr_dev, + &pvr_fw_trace_mask_fops); } -#endif diff --git a/drivers/gpu/drm/imagination/pvr_fw_trace.h b/drivers/gpu/drm/imagination/pvr_fw_trace.h index 0074d2b18da0..0cc57f66675d 100644 --- a/drivers/gpu/drm/imagination/pvr_fw_trace.h +++ b/drivers/gpu/drm/imagination/pvr_fw_trace.h @@ -65,14 +65,9 @@ struct pvr_fw_trace { int pvr_fw_trace_init(struct pvr_device *pvr_dev); void pvr_fw_trace_fini(struct pvr_device *pvr_dev); -#if defined(CONFIG_DEBUG_FS) /* Forward declaration from <linux/dcache.h>. */ struct dentry; -void pvr_fw_trace_mask_update(struct pvr_device *pvr_dev, u32 old_mask, - u32 new_mask); - void pvr_fw_trace_debugfs_init(struct pvr_device *pvr_dev, struct dentry *dir); -#endif /* defined(CONFIG_DEBUG_FS) */ #endif /* PVR_FW_TRACE_H */ diff --git a/drivers/gpu/drm/imagination/pvr_fw_util.c b/drivers/gpu/drm/imagination/pvr_fw_util.c new file mode 100644 index 000000000000..377fe72d86b8 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_fw_util.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (c) 2024 Imagination Technologies Ltd. */ + +#include "pvr_device.h" +#include "pvr_fw.h" + +#include <drm/drm_device.h> +#include <drm/drm_print.h> + +#include <linux/elf.h> +#include <linux/string.h> +#include <linux/types.h> + +/** + * pvr_fw_process_elf_command_stream() - Process ELF firmware image and populate + * firmware sections + * @pvr_dev: Device pointer. + * @fw: Pointer to firmware image. + * @fw_code_ptr: Pointer to FW code section. + * @fw_data_ptr: Pointer to FW data section. + * @fw_core_code_ptr: Pointer to FW coremem code section. + * @fw_core_data_ptr: Pointer to FW coremem data section. + * + * Returns : + * * 0 on success, or + * * -EINVAL on any error in ELF command stream. + */ +int +pvr_fw_process_elf_command_stream(struct pvr_device *pvr_dev, const u8 *fw, + u8 *fw_code_ptr, u8 *fw_data_ptr, + u8 *fw_core_code_ptr, u8 *fw_core_data_ptr) +{ + struct elf32_hdr *header = (struct elf32_hdr *)fw; + struct elf32_phdr *program_header = (struct elf32_phdr *)(fw + header->e_phoff); + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + int err; + + for (u32 entry = 0; entry < header->e_phnum; entry++, program_header++) { + void *write_addr; + + /* Only consider loadable entries in the ELF segment table */ + if (program_header->p_type != PT_LOAD) + continue; + + err = pvr_fw_find_mmu_segment(pvr_dev, program_header->p_vaddr, + program_header->p_memsz, fw_code_ptr, fw_data_ptr, + fw_core_code_ptr, fw_core_data_ptr, &write_addr); + if (err) { + drm_err(drm_dev, + "Addr 0x%x (size: %d) not found in any firmware segment", + program_header->p_vaddr, program_header->p_memsz); + return err; + } + + /* Write to FW allocation only if available */ + if (write_addr) { + memcpy(write_addr, fw + program_header->p_offset, + program_header->p_filesz); + + memset((u8 *)write_addr + program_header->p_filesz, 0, + program_header->p_memsz - program_header->p_filesz); + } + } + + return 0; +} diff --git a/drivers/gpu/drm/imagination/pvr_gem.c b/drivers/gpu/drm/imagination/pvr_gem.c index 6a8c81fe8c1e..d8660d6a8e01 100644 --- a/drivers/gpu/drm/imagination/pvr_gem.c +++ b/drivers/gpu/drm/imagination/pvr_gem.c @@ -19,12 +19,26 @@ #include <linux/log2.h> #include <linux/mutex.h> #include <linux/pagemap.h> +#include <linux/property.h> #include <linux/refcount.h> #include <linux/scatterlist.h> static void pvr_gem_object_free(struct drm_gem_object *obj) { - drm_gem_shmem_object_free(obj); + struct drm_gem_shmem_object *shmem_obj = to_drm_gem_shmem_obj(obj); + + shmem_obj->pages_mark_dirty_on_put = true; + drm_gem_shmem_free(shmem_obj); +} + +static struct dma_buf *pvr_gem_export(struct drm_gem_object *obj, int flags) +{ + struct pvr_gem_object *pvr_obj = gem_to_pvr_gem(obj); + + if (pvr_obj->flags & DRM_PVR_BO_PM_FW_PROTECT) + return ERR_PTR(-EPERM); + + return drm_gem_prime_export(obj, flags); } static int pvr_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *vma) @@ -41,6 +55,7 @@ static int pvr_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *v static const struct drm_gem_object_funcs pvr_gem_object_funcs = { .free = pvr_gem_object_free, .print_info = drm_gem_shmem_object_print_info, + .export = pvr_gem_export, .pin = drm_gem_shmem_object_pin, .unpin = drm_gem_shmem_object_unpin, .get_sg_table = drm_gem_shmem_object_get_sg_table, @@ -76,8 +91,6 @@ pvr_gem_object_flags_validate(u64 flags) DRM_PVR_BO_ALLOW_CPU_USERSPACE_ACCESS), }; - int i; - /* * Check for bits set in undefined regions. Reserved regions refer to * options that can only be set by the kernel. These are explicitly @@ -91,7 +104,7 @@ pvr_gem_object_flags_validate(u64 flags) * Check for all combinations of flags marked as invalid in the array * above. */ - for (i = 0; i < ARRAY_SIZE(invalid_combinations); ++i) { + for (int i = 0; i < ARRAY_SIZE(invalid_combinations); ++i) { u64 combo = invalid_combinations[i]; if ((flags & combo) == combo) @@ -203,7 +216,7 @@ pvr_gem_object_vmap(struct pvr_gem_object *pvr_obj) dma_resv_lock(obj->resv, NULL); - err = drm_gem_shmem_vmap(shmem_obj, &map); + err = drm_gem_shmem_vmap_locked(shmem_obj, &map); if (err) goto err_unlock; @@ -257,7 +270,7 @@ pvr_gem_object_vunmap(struct pvr_gem_object *pvr_obj) dma_sync_sgtable_for_device(dev, shmem_obj->sgt, DMA_BIDIRECTIONAL); } - drm_gem_shmem_vunmap(shmem_obj, &map); + drm_gem_shmem_vunmap_locked(shmem_obj, &map); dma_resv_unlock(obj->resv); } @@ -304,7 +317,7 @@ struct drm_gem_object *pvr_gem_create_object(struct drm_device *drm_dev, size_t struct drm_gem_object *gem_obj; struct pvr_gem_object *pvr_obj; - pvr_obj = kzalloc(sizeof(*pvr_obj), GFP_KERNEL); + pvr_obj = kzalloc_obj(*pvr_obj); if (!pvr_obj) return ERR_PTR(-ENOMEM); @@ -336,6 +349,7 @@ struct drm_gem_object *pvr_gem_create_object(struct drm_device *drm_dev, size_t struct pvr_gem_object * pvr_gem_object_create(struct pvr_device *pvr_dev, size_t size, u64 flags) { + struct drm_device *drm_dev = from_pvr_device(pvr_dev); struct drm_gem_shmem_object *shmem_obj; struct pvr_gem_object *pvr_obj; struct sg_table *sgt; @@ -345,11 +359,13 @@ pvr_gem_object_create(struct pvr_device *pvr_dev, size_t size, u64 flags) if (size == 0 || !pvr_gem_object_flags_validate(flags)) return ERR_PTR(-EINVAL); - shmem_obj = drm_gem_shmem_create(from_pvr_device(pvr_dev), size); + if (device_get_dma_attr(drm_dev->dev) == DEV_DMA_COHERENT) + flags |= PVR_BO_CPU_CACHED; + + shmem_obj = drm_gem_shmem_create(drm_dev, size); if (IS_ERR(shmem_obj)) return ERR_CAST(shmem_obj); - shmem_obj->pages_mark_dirty_on_put = true; shmem_obj->map_wc = !(flags & PVR_BO_CPU_CACHED); pvr_obj = shmem_gem_to_pvr_gem(shmem_obj); pvr_obj->flags = flags; @@ -360,8 +376,7 @@ pvr_gem_object_create(struct pvr_device *pvr_dev, size_t size, u64 flags) goto err_shmem_object_free; } - dma_sync_sgtable_for_device(shmem_obj->base.dev->dev, sgt, - DMA_BIDIRECTIONAL); + dma_sync_sgtable_for_device(drm_dev->dev, sgt, DMA_BIDIRECTIONAL); /* * Do this last because pvr_gem_object_zero() requires a fully diff --git a/drivers/gpu/drm/imagination/pvr_gem.h b/drivers/gpu/drm/imagination/pvr_gem.h index e0e5ea509a2e..c99f30cc6208 100644 --- a/drivers/gpu/drm/imagination/pvr_gem.h +++ b/drivers/gpu/drm/imagination/pvr_gem.h @@ -44,8 +44,10 @@ struct pvr_file; * Bits not defined anywhere are "undefined". * * CPU mapping options - * :PVR_BO_CPU_CACHED: By default, all GEM objects are mapped write-combined on the CPU. Set this - * flag to override this behaviour and map the object cached. + * :PVR_BO_CPU_CACHED: By default, all GEM objects are mapped write-combined on the CPU. Set + * this flag to override this behaviour and map the object cached. If the dma_coherent + * property is present in devicetree, all allocations will be mapped as if this flag was set. + * This does not require any additional consideration at allocation time. * * Firmware options * :PVR_BO_FW_NO_CLEAR_ON_RESET: By default, all FW objects are cleared and reinitialised on hard diff --git a/drivers/gpu/drm/imagination/pvr_hwrt.c b/drivers/gpu/drm/imagination/pvr_hwrt.c index 54f88d6c01e5..05c68c78da22 100644 --- a/drivers/gpu/drm/imagination/pvr_hwrt.c +++ b/drivers/gpu/drm/imagination/pvr_hwrt.c @@ -44,13 +44,12 @@ hwrt_init_kernel_structure(struct pvr_file *pvr_file, { struct pvr_device *pvr_dev = pvr_file->pvr_dev; int err; - int i; hwrt->pvr_dev = pvr_dev; hwrt->max_rts = args->layers; /* Get pointers to the free lists */ - for (i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { + for (int i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { hwrt->free_lists[i] = pvr_free_list_lookup(pvr_file, args->free_list_handles[i]); if (!hwrt->free_lists[i]) { err = -EINVAL; @@ -67,7 +66,7 @@ hwrt_init_kernel_structure(struct pvr_file *pvr_file, return 0; err_put_free_lists: - for (i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { + for (int i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { pvr_free_list_put(hwrt->free_lists[i]); hwrt->free_lists[i] = NULL; } @@ -78,9 +77,7 @@ err_put_free_lists: static void hwrt_fini_kernel_structure(struct pvr_hwrt_dataset *hwrt) { - int i; - - for (i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { + for (int i = 0; i < ARRAY_SIZE(hwrt->free_lists); i++) { pvr_free_list_put(hwrt->free_lists[i]); hwrt->free_lists[i] = NULL; } @@ -363,13 +360,12 @@ hwrt_data_init_fw_structure(struct pvr_file *pvr_file, struct drm_pvr_create_hwrt_geom_data_args *geom_data_args = &args->geom_data_args; struct pvr_device *pvr_dev = pvr_file->pvr_dev; struct rogue_fwif_rta_ctl *rta_ctl; - int free_list_i; int err; pvr_fw_object_get_fw_addr(hwrt->common_fw_obj, &hwrt_data->data.hwrt_data_common_fw_addr); - for (free_list_i = 0; free_list_i < ARRAY_SIZE(hwrt->free_lists); free_list_i++) { + for (int free_list_i = 0; free_list_i < ARRAY_SIZE(hwrt->free_lists); free_list_i++) { pvr_fw_object_get_fw_addr(hwrt->free_lists[free_list_i]->fw_obj, &hwrt_data->data.freelists_fw_addr[free_list_i]); } @@ -461,7 +457,7 @@ pvr_hwrt_dataset_create(struct pvr_file *pvr_file, int err, i = 0; /* Create and fill out the kernel structure */ - hwrt = kzalloc(sizeof(*hwrt), GFP_KERNEL); + hwrt = kzalloc_obj(*hwrt); if (!hwrt) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/imagination/pvr_job.c b/drivers/gpu/drm/imagination/pvr_job.c index 618503a212a7..0c2f511a6178 100644 --- a/drivers/gpu/drm/imagination/pvr_job.c +++ b/drivers/gpu/drm/imagination/pvr_job.c @@ -415,7 +415,7 @@ create_job(struct pvr_device *pvr_dev, (args->hwrt.set_handle || args->hwrt.data_index)) return ERR_PTR(-EINVAL); - job = kzalloc(sizeof(*job), GFP_KERNEL); + job = kzalloc_obj(*job); if (!job) return ERR_PTR(-ENOMEM); @@ -446,7 +446,7 @@ create_job(struct pvr_device *pvr_dev, if (err) goto err_put_job; - err = pvr_queue_job_init(job); + err = pvr_queue_job_init(job, pvr_file->file->client_id); if (err) goto err_put_job; @@ -597,8 +597,6 @@ update_job_resvs_for_each(struct pvr_job_data *job_data, u32 job_count) static bool can_combine_jobs(struct pvr_job *a, struct pvr_job *b) { struct pvr_job *geom_job = a, *frag_job = b; - struct dma_fence *fence; - unsigned long index; /* Geometry and fragment jobs can be combined if they are queued to the * same context and targeting the same HWRT. @@ -609,13 +607,9 @@ static bool can_combine_jobs(struct pvr_job *a, struct pvr_job *b) a->hwrt != b->hwrt) return false; - xa_for_each(&frag_job->base.dependencies, index, fence) { - /* We combine when we see an explicit geom -> frag dep. */ - if (&geom_job->base.s_fence->scheduled == fence) - return true; - } - - return false; + /* We combine when we see an explicit geom -> frag dep. */ + return drm_sched_job_has_dependency(&frag_job->base, + &geom_job->base.s_fence->scheduled); } static struct dma_fence * @@ -677,6 +671,13 @@ pvr_jobs_link_geom_frag(struct pvr_job_data *job_data, u32 *job_count) geom_job->paired_job = frag_job; frag_job->paired_job = geom_job; + /* The geometry job pvr_job structure is used when the fragment + * job is being prepared by the GPU scheduler. Have the fragment + * job hold a reference on the geometry job to prevent it being + * freed until the fragment job has finished with it. + */ + pvr_job_get(geom_job); + /* Skip the fragment job we just paired to the geometry job. */ i++; } @@ -717,8 +718,8 @@ pvr_submit_jobs(struct pvr_device *pvr_dev, struct pvr_file *pvr_file, if (err) return err; - job_data = kvmalloc_array(args->jobs.count, sizeof(*job_data), - GFP_KERNEL | __GFP_ZERO); + job_data = kvmalloc_objs(*job_data, args->jobs.count, + GFP_KERNEL | __GFP_ZERO); if (!job_data) { err = -ENOMEM; goto out_free; diff --git a/drivers/gpu/drm/imagination/pvr_mmu.c b/drivers/gpu/drm/imagination/pvr_mmu.c index 4fe70610ed94..2e4da5b2c499 100644 --- a/drivers/gpu/drm/imagination/pvr_mmu.c +++ b/drivers/gpu/drm/imagination/pvr_mmu.c @@ -17,6 +17,7 @@ #include <linux/dma-mapping.h> #include <linux/kmemleak.h> #include <linux/minmax.h> +#include <linux/property.h> #include <linux/sizes.h> #define PVR_SHIFT_FROM_SIZE(size_) (__builtin_ctzll(size_)) @@ -259,6 +260,7 @@ pvr_mmu_backing_page_init(struct pvr_mmu_backing_page *page, struct device *dev = from_pvr_device(pvr_dev)->dev; struct page *raw_page; + pgprot_t prot; int err; dma_addr_t dma_addr; @@ -268,7 +270,11 @@ pvr_mmu_backing_page_init(struct pvr_mmu_backing_page *page, if (!raw_page) return -ENOMEM; - host_ptr = vmap(&raw_page, 1, VM_MAP, pgprot_writecombine(PAGE_KERNEL)); + prot = PAGE_KERNEL; + if (device_get_dma_attr(dev) != DEV_DMA_COHERENT) + prot = pgprot_writecombine(prot); + + host_ptr = vmap(&raw_page, 1, VM_MAP, prot); if (!host_ptr) { err = -ENOMEM; goto err_free_page; @@ -1822,7 +1828,7 @@ pvr_page_table_l0_get_or_insert(struct pvr_mmu_op_context *op_ctx, */ struct pvr_mmu_context *pvr_mmu_context_create(struct pvr_device *pvr_dev) { - struct pvr_mmu_context *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + struct pvr_mmu_context *ctx = kzalloc_obj(*ctx); int err; if (!ctx) @@ -1871,8 +1877,7 @@ pvr_page_table_l1_alloc(struct pvr_mmu_context *ctx) { int err; - struct pvr_page_table_l1 *table = - kzalloc(sizeof(*table), GFP_KERNEL); + struct pvr_page_table_l1 *table = kzalloc_obj(*table); if (!table) return ERR_PTR(-ENOMEM); @@ -1900,8 +1905,7 @@ pvr_page_table_l0_alloc(struct pvr_mmu_context *ctx) { int err; - struct pvr_page_table_l0 *table = - kzalloc(sizeof(*table), GFP_KERNEL); + struct pvr_page_table_l0 *table = kzalloc_obj(*table); if (!table) return ERR_PTR(-ENOMEM); @@ -2346,8 +2350,7 @@ pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt, { int err; - struct pvr_mmu_op_context *op_ctx = - kzalloc(sizeof(*op_ctx), GFP_KERNEL); + struct pvr_mmu_op_context *op_ctx = kzalloc_obj(*op_ctx); if (!op_ctx) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/imagination/pvr_params.c b/drivers/gpu/drm/imagination/pvr_params.c deleted file mode 100644 index b91759f362c5..000000000000 --- a/drivers/gpu/drm/imagination/pvr_params.c +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT -/* Copyright (c) 2023 Imagination Technologies Ltd. */ - -#include "pvr_params.h" - -#include <linux/cache.h> -#include <linux/moduleparam.h> - -static struct pvr_device_params pvr_device_param_defaults __read_mostly = { -#define X(type_, name_, value_, desc_, ...) .name_ = (value_), - PVR_DEVICE_PARAMS -#undef X -}; - -#define PVR_DEVICE_PARAM_NAMED(name_, type_, desc_) \ - module_param_named(name_, pvr_device_param_defaults.name_, type_, \ - 0400); \ - MODULE_PARM_DESC(name_, desc_); - -/* - * This list of defines must contain every type specified in "pvr_params.h" as - * ``PVR_PARAM_TYPE_*_C``. - */ -#define PVR_PARAM_TYPE_X32_MODPARAM uint - -#define X(type_, name_, value_, desc_, ...) \ - PVR_DEVICE_PARAM_NAMED(name_, PVR_PARAM_TYPE_##type_##_MODPARAM, desc_); -PVR_DEVICE_PARAMS -#undef X - -int -pvr_device_params_init(struct pvr_device_params *params) -{ - /* - * If heap-allocated parameters are added in the future (e.g. - * modparam's charp type), they must be handled specially here (via - * kstrdup() in the case of charp). Since that's not necessary yet, - * a straight copy will do for now. This change will also require a - * pvr_device_params_fini() function to free any heap-allocated copies. - */ - - *params = pvr_device_param_defaults; - - return 0; -} - -#if defined(CONFIG_DEBUG_FS) -#include "pvr_device.h" - -#include <linux/dcache.h> -#include <linux/debugfs.h> -#include <linux/export.h> -#include <linux/fs.h> -#include <linux/stddef.h> - -/* - * This list of defines must contain every type specified in "pvr_params.h" as - * ``PVR_PARAM_TYPE_*_C``. - */ -#define PVR_PARAM_TYPE_X32_FMT "0x%08llx" - -#define X_SET(name_, mode_) X_SET_##mode_(name_) -#define X_SET_DEF(name_, update_, mode_) X_SET_DEF_##mode_(name_, update_) - -#define X_SET_RO(name_) NULL -#define X_SET_RW(name_) __pvr_device_param_##name_##set - -#define X_SET_DEF_RO(name_, update_) -#define X_SET_DEF_RW(name_, update_) \ - static int \ - X_SET_RW(name_)(void *data, u64 val) \ - { \ - struct pvr_device *pvr_dev = data; \ - /* This is not just (update_) to suppress -Waddress. */ \ - if ((void *)(update_) != NULL) \ - (update_)(pvr_dev, pvr_dev->params.name_, val); \ - pvr_dev->params.name_ = val; \ - return 0; \ - } - -#define X(type_, name_, value_, desc_, mode_, update_) \ - static int \ - __pvr_device_param_##name_##_get(void *data, u64 *val) \ - { \ - struct pvr_device *pvr_dev = data; \ - *val = pvr_dev->params.name_; \ - return 0; \ - } \ - X_SET_DEF(name_, update_, mode_) \ - static int \ - __pvr_device_param_##name_##_open(struct inode *inode, \ - struct file *file) \ - { \ - __simple_attr_check_format(PVR_PARAM_TYPE_##type_##_FMT, \ - 0ull); \ - return simple_attr_open(inode, file, \ - __pvr_device_param_##name_##_get, \ - X_SET(name_, mode_), \ - PVR_PARAM_TYPE_##type_##_FMT); \ - } -PVR_DEVICE_PARAMS -#undef X - -#undef X_SET -#undef X_SET_RO -#undef X_SET_RW -#undef X_SET_DEF -#undef X_SET_DEF_RO -#undef X_SET_DEF_RW - -static struct { -#define X(type_, name_, value_, desc_, mode_, update_) \ - const struct file_operations name_; - PVR_DEVICE_PARAMS -#undef X -} pvr_device_param_debugfs_fops = { -#define X(type_, name_, value_, desc_, mode_, update_) \ - .name_ = { \ - .owner = THIS_MODULE, \ - .open = __pvr_device_param_##name_##_open, \ - .release = simple_attr_release, \ - .read = simple_attr_read, \ - .write = simple_attr_write, \ - .llseek = generic_file_llseek, \ - }, - PVR_DEVICE_PARAMS -#undef X -}; - -void -pvr_params_debugfs_init(struct pvr_device *pvr_dev, struct dentry *dir) -{ -#define X_MODE(mode_) X_MODE_##mode_ -#define X_MODE_RO 0400 -#define X_MODE_RW 0600 - -#define X(type_, name_, value_, desc_, mode_, update_) \ - debugfs_create_file(#name_, X_MODE(mode_), dir, pvr_dev, \ - &pvr_device_param_debugfs_fops.name_); - PVR_DEVICE_PARAMS -#undef X - -#undef X_MODE -#undef X_MODE_RO -#undef X_MODE_RW -} -#endif diff --git a/drivers/gpu/drm/imagination/pvr_params.h b/drivers/gpu/drm/imagination/pvr_params.h deleted file mode 100644 index 5807915b456b..000000000000 --- a/drivers/gpu/drm/imagination/pvr_params.h +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ -/* Copyright (c) 2023 Imagination Technologies Ltd. */ - -#ifndef PVR_PARAMS_H -#define PVR_PARAMS_H - -#include "pvr_rogue_fwif.h" - -#include <linux/cache.h> -#include <linux/compiler_attributes.h> - -/* - * This is the definitive list of types allowed in the definition of - * %PVR_DEVICE_PARAMS. - */ -#define PVR_PARAM_TYPE_X32_C u32 - -/* - * This macro defines all device-specific parameters; that is parameters which - * are set independently per device. - * - * The X-macro accepts the following arguments. Arguments marked with [debugfs] - * are ignored when debugfs is disabled; values used for these arguments may - * safely be gated behind CONFIG_DEBUG_FS. - * - * @type_: The definitive list of allowed values is PVR_PARAM_TYPE_*_C. - * @name_: Name of the parameter. This is used both as the field name in C and - * stringified as the parameter name. - * @value_: Initial/default value. - * @desc_: String literal used as help text to describe the usage of this - * parameter. - * @mode_: [debugfs] One of {RO,RW}. The access mode of the debugfs entry for - * this parameter. - * @update_: [debugfs] When debugfs support is enabled, parameters may be - * updated at runtime. When this happens, this function will be - * called to allow changes to propagate. The signature of this - * function is: - * - * void (*)(struct pvr_device *pvr_dev, T old_val, T new_val) - * - * Where T is the C type associated with @type_. - * - * If @mode_ does not allow write access, this function will never be - * called. In this case, or if no update callback is required, you - * should specify NULL for this argument. - */ -#define PVR_DEVICE_PARAMS \ - X(X32, fw_trace_mask, ROGUE_FWIF_LOG_TYPE_NONE, \ - "Enable FW trace for the specified groups. Specifying 0 disables " \ - "all FW tracing.", \ - RW, pvr_fw_trace_mask_update) - -struct pvr_device_params { -#define X(type_, name_, value_, desc_, ...) \ - PVR_PARAM_TYPE_##type_##_C name_; - PVR_DEVICE_PARAMS -#undef X -}; - -int pvr_device_params_init(struct pvr_device_params *params); - -#if defined(CONFIG_DEBUG_FS) -/* Forward declaration from "pvr_device.h". */ -struct pvr_device; - -/* Forward declaration from <linux/dcache.h>. */ -struct dentry; - -void pvr_params_debugfs_init(struct pvr_device *pvr_dev, struct dentry *dir); -#endif /* defined(CONFIG_DEBUG_FS) */ - -#endif /* PVR_PARAMS_H */ diff --git a/drivers/gpu/drm/imagination/pvr_power.c b/drivers/gpu/drm/imagination/pvr_power.c index ba7816fd28ec..88fcce433d12 100644 --- a/drivers/gpu/drm/imagination/pvr_power.c +++ b/drivers/gpu/drm/imagination/pvr_power.c @@ -10,11 +10,17 @@ #include <drm/drm_drv.h> #include <drm/drm_managed.h> +#include <drm/drm_print.h> +#include <linux/cleanup.h> #include <linux/clk.h> #include <linux/interrupt.h> #include <linux/mutex.h> +#include <linux/of.h> #include <linux/platform_device.h> +#include <linux/pm_domain.h> #include <linux/pm_runtime.h> +#include <linux/pwrseq/consumer.h> +#include <linux/reset.h> #include <linux/timer.h> #include <linux/types.h> #include <linux/workqueue.h> @@ -84,11 +90,11 @@ pvr_power_request_pwr_off(struct pvr_device *pvr_dev) } static int -pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset) +pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset, bool rpm_suspend) { - if (!hard_reset) { - int err; + int err; + if (!hard_reset) { cancel_delayed_work_sync(&pvr_dev->watchdog.work); err = pvr_power_request_idle(pvr_dev); @@ -100,29 +106,47 @@ pvr_power_fw_disable(struct pvr_device *pvr_dev, bool hard_reset) return err; } - return pvr_fw_stop(pvr_dev); + if (rpm_suspend) { + /* This also waits for late processing of GPU or firmware IRQs in other cores */ + disable_irq(pvr_dev->irq); + } + + err = pvr_fw_stop(pvr_dev); + if (err && rpm_suspend) + enable_irq(pvr_dev->irq); + + return err; } static int -pvr_power_fw_enable(struct pvr_device *pvr_dev) +pvr_power_fw_enable(struct pvr_device *pvr_dev, bool rpm_resume) { int err; + if (rpm_resume) + enable_irq(pvr_dev->irq); + err = pvr_fw_start(pvr_dev); if (err) - return err; + goto out; err = pvr_wait_for_fw_boot(pvr_dev); if (err) { drm_err(from_pvr_device(pvr_dev), "Firmware failed to boot\n"); pvr_fw_stop(pvr_dev); - return err; + goto out; } queue_delayed_work(pvr_dev->sched_wq, &pvr_dev->watchdog.work, msecs_to_jiffies(WATCHDOG_TIME_MS)); return 0; + +out: + if (rpm_resume) + disable_irq(pvr_dev->irq); + + return err; } bool @@ -230,6 +254,118 @@ pvr_watchdog_init(struct pvr_device *pvr_dev) return 0; } +static int pvr_power_init_manual(struct pvr_device *pvr_dev) +{ + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + struct reset_control *reset; + + reset = devm_reset_control_get_optional_exclusive(drm_dev->dev, NULL); + if (IS_ERR(reset)) + return dev_err_probe(drm_dev->dev, PTR_ERR(reset), + "failed to get gpu reset line\n"); + + pvr_dev->reset = reset; + + return 0; +} + +static int pvr_power_on_sequence_manual(struct pvr_device *pvr_dev) +{ + int err; + + err = clk_prepare_enable(pvr_dev->core_clk); + if (err) + return err; + + err = clk_prepare_enable(pvr_dev->sys_clk); + if (err) + goto err_core_clk_disable; + + err = clk_prepare_enable(pvr_dev->mem_clk); + if (err) + goto err_sys_clk_disable; + + /* + * According to the hardware manual, a delay of at least 32 clock + * cycles is required between de-asserting the clkgen reset and + * de-asserting the GPU reset. Assuming a worst-case scenario with + * a very high GPU clock frequency, a delay of 1 microsecond is + * sufficient to ensure this requirement is met across all + * feasible GPU clock speeds. + */ + udelay(1); + + err = reset_control_deassert(pvr_dev->reset); + if (err) + goto err_mem_clk_disable; + + return 0; + +err_mem_clk_disable: + clk_disable_unprepare(pvr_dev->mem_clk); + +err_sys_clk_disable: + clk_disable_unprepare(pvr_dev->sys_clk); + +err_core_clk_disable: + clk_disable_unprepare(pvr_dev->core_clk); + + return err; +} + +static int pvr_power_off_sequence_manual(struct pvr_device *pvr_dev) +{ + int err; + + err = reset_control_assert(pvr_dev->reset); + + clk_disable_unprepare(pvr_dev->mem_clk); + clk_disable_unprepare(pvr_dev->sys_clk); + clk_disable_unprepare(pvr_dev->core_clk); + + return err; +} + +const struct pvr_power_sequence_ops pvr_power_sequence_ops_manual = { + .init = pvr_power_init_manual, + .power_on = pvr_power_on_sequence_manual, + .power_off = pvr_power_off_sequence_manual, +}; + +static int pvr_power_init_pwrseq(struct pvr_device *pvr_dev) +{ + struct device *dev = from_pvr_device(pvr_dev)->dev; + + pvr_dev->pwrseq = devm_pwrseq_get(dev, "gpu-power"); + if (IS_ERR(pvr_dev->pwrseq)) { + /* + * This platform requires a sequencer. If we can't get it, we + * must return the error (including -EPROBE_DEFER to wait for + * the provider to appear) + */ + return dev_err_probe(dev, PTR_ERR(pvr_dev->pwrseq), + "Failed to get required power sequencer\n"); + } + + return 0; +} + +static int pvr_power_on_sequence_pwrseq(struct pvr_device *pvr_dev) +{ + return pwrseq_power_on(pvr_dev->pwrseq); +} + +static int pvr_power_off_sequence_pwrseq(struct pvr_device *pvr_dev) +{ + return pwrseq_power_off(pvr_dev->pwrseq); +} + +const struct pvr_power_sequence_ops pvr_power_sequence_ops_pwrseq = { + .init = pvr_power_init_pwrseq, + .power_on = pvr_power_on_sequence_pwrseq, + .power_off = pvr_power_off_sequence_pwrseq, +}; + int pvr_power_device_suspend(struct device *dev) { @@ -243,14 +379,12 @@ pvr_power_device_suspend(struct device *dev) return -EIO; if (pvr_dev->fw_dev.booted) { - err = pvr_power_fw_disable(pvr_dev, false); + err = pvr_power_fw_disable(pvr_dev, false, true); if (err) goto err_drm_dev_exit; } - clk_disable_unprepare(pvr_dev->mem_clk); - clk_disable_unprepare(pvr_dev->sys_clk); - clk_disable_unprepare(pvr_dev->core_clk); + err = pvr_dev->device_data->pwr_ops->power_off(pvr_dev); err_drm_dev_exit: drm_dev_exit(idx); @@ -270,36 +404,22 @@ pvr_power_device_resume(struct device *dev) if (!drm_dev_enter(drm_dev, &idx)) return -EIO; - err = clk_prepare_enable(pvr_dev->core_clk); + err = pvr_dev->device_data->pwr_ops->power_on(pvr_dev); if (err) goto err_drm_dev_exit; - err = clk_prepare_enable(pvr_dev->sys_clk); - if (err) - goto err_core_clk_disable; - - err = clk_prepare_enable(pvr_dev->mem_clk); - if (err) - goto err_sys_clk_disable; - if (pvr_dev->fw_dev.booted) { - err = pvr_power_fw_enable(pvr_dev); + err = pvr_power_fw_enable(pvr_dev, true); if (err) - goto err_mem_clk_disable; + goto err_power_off; } drm_dev_exit(idx); return 0; -err_mem_clk_disable: - clk_disable_unprepare(pvr_dev->mem_clk); - -err_sys_clk_disable: - clk_disable_unprepare(pvr_dev->sys_clk); - -err_core_clk_disable: - clk_disable_unprepare(pvr_dev->core_clk); +err_power_off: + pvr_dev->device_data->pwr_ops->power_off(pvr_dev); err_drm_dev_exit: drm_dev_exit(idx); @@ -317,6 +437,63 @@ pvr_power_device_idle(struct device *dev) return pvr_power_is_idle(pvr_dev) ? 0 : -EBUSY; } +static int +pvr_power_clear_error(struct pvr_device *pvr_dev) +{ + struct device *dev = from_pvr_device(pvr_dev)->dev; + int err; + + /* Ensure the device state is known and nothing is happening past this point */ + pm_runtime_disable(dev); + + /* Attempt to clear the runtime PM error by setting the current state again */ + if (pm_runtime_status_suspended(dev)) + err = pm_runtime_set_suspended(dev); + else + err = pm_runtime_set_active(dev); + + if (err) { + drm_err(from_pvr_device(pvr_dev), + "%s: Failed to clear runtime PM error (new error %d)\n", + __func__, err); + } + + pm_runtime_enable(dev); + + return err; +} + +/** + * pvr_power_get_clear() - Acquire a power reference, correcting any errors + * @pvr_dev: Device pointer + * + * Attempt to acquire a power reference on the device. If the runtime PM + * is in error state, attempt to clear the error and retry. + * + * Returns: + * * 0 on success, or + * * Any error code returned by pvr_power_get() or the runtime PM API. + */ +static int +pvr_power_get_clear(struct pvr_device *pvr_dev) +{ + int err; + + err = pvr_power_get(pvr_dev); + if (err == 0) + return err; + + drm_warn(from_pvr_device(pvr_dev), + "%s: pvr_power_get returned error %d, attempting recovery\n", + __func__, err); + + err = pvr_power_clear_error(pvr_dev); + if (err) + return err; + + return pvr_power_get(pvr_dev); +} + /** * pvr_power_reset() - Reset the GPU * @pvr_dev: Device pointer @@ -341,7 +518,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) * Take a power reference during the reset. This should prevent any interference with the * power state during reset. */ - WARN_ON(pvr_power_get(pvr_dev)); + WARN_ON(pvr_power_get_clear(pvr_dev)); down_write(&pvr_dev->reset_sem); @@ -351,7 +528,16 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) } /* Disable IRQs for the duration of the reset. */ - disable_irq(pvr_dev->irq); + if (hard_reset) { + disable_irq(pvr_dev->irq); + } else { + /* + * Soft reset is triggered as a response to a FW command to the Host and is + * processed from the threaded IRQ handler. This code cannot (nor needs to) + * wait for any IRQ processing to complete. + */ + disable_irq_nosync(pvr_dev->irq); + } do { if (hard_reset) { @@ -359,17 +545,17 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) queues_disabled = true; } - err = pvr_power_fw_disable(pvr_dev, hard_reset); + err = pvr_power_fw_disable(pvr_dev, hard_reset, false); if (!err) { if (hard_reset) { pvr_dev->fw_dev.booted = false; - WARN_ON(pm_runtime_force_suspend(from_pvr_device(pvr_dev)->dev)); + WARN_ON(pvr_power_device_suspend(from_pvr_device(pvr_dev)->dev)); err = pvr_fw_hard_reset(pvr_dev); if (err) goto err_device_lost; - err = pm_runtime_force_resume(from_pvr_device(pvr_dev)->dev); + err = pvr_power_device_resume(from_pvr_device(pvr_dev)->dev); pvr_dev->fw_dev.booted = true; if (err) goto err_device_lost; @@ -382,7 +568,7 @@ pvr_power_reset(struct pvr_device *pvr_dev, bool hard_reset) pvr_fw_irq_clear(pvr_dev); - err = pvr_power_fw_enable(pvr_dev); + err = pvr_power_fw_enable(pvr_dev, false); } if (err && hard_reset) @@ -431,3 +617,110 @@ pvr_watchdog_fini(struct pvr_device *pvr_dev) { cancel_delayed_work_sync(&pvr_dev->watchdog.work); } + +int pvr_power_domains_init(struct pvr_device *pvr_dev) +{ + static const char *const ROGUE_PD_NAMES[] = { "a", "b", "c", "d", "e" }; + + struct drm_device *drm_dev = from_pvr_device(pvr_dev); + struct device *dev = drm_dev->dev; + + struct dev_pm_domain_list *domains = NULL; + struct device_link **domain_links = NULL; + int domain_count; + int link_count; + + int err; + int i; + + domain_count = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + if (domain_count < 0) { + err = domain_count; + goto out; + } + + if (domain_count <= 1) { + err = 0; + goto out; + } + + if (domain_count > ARRAY_SIZE(ROGUE_PD_NAMES)) { + drm_err(drm_dev, "%s() only supports %zu domains on Rogue", + __func__, ARRAY_SIZE(ROGUE_PD_NAMES)); + err = -EOPNOTSUPP; + goto out; + } + + link_count = domain_count - 1; + + domain_links = kzalloc_objs(*domain_links, link_count); + if (!domain_links) { + err = -ENOMEM; + goto out; + } + + const struct dev_pm_domain_attach_data pd_attach_data = { + .pd_names = ROGUE_PD_NAMES, + .num_pd_names = domain_count, + .pd_flags = 0, + }; + + err = dev_pm_domain_attach_list(dev, &pd_attach_data, &domains); + if (err < 0) + goto err_free_links; + + for (i = 0; i < link_count; i++) { + struct device_link *link; + + link = device_link_add(domains->pd_devs[i + 1], + domains->pd_devs[i], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); + if (!link) { + err = -ENODEV; + goto err_unlink; + } + + domain_links[i] = link; + } + + err = 0; + goto out; + +err_unlink: + while (--i >= 0) + device_link_del(domain_links[i]); + + dev_pm_domain_detach_list(domains); + domains = NULL; + +err_free_links: + kfree(domain_links); + domain_links = NULL; + +out: + pvr_dev->power = (struct pvr_device_power){ + .domains = domains, + .domain_links = domain_links, + }; + + return err; +} + +void pvr_power_domains_fini(struct pvr_device *pvr_dev) +{ + struct pvr_device_power *pvr_power = &pvr_dev->power; + + if (!pvr_power->domains) + goto out; + + for (int i = (int)pvr_power->domains->num_pds - 2; i >= 0; --i) + device_link_del(pvr_power->domain_links[i]); + + dev_pm_domain_detach_list(pvr_power->domains); + + kfree(pvr_power->domain_links); + +out: + *pvr_power = (struct pvr_device_power){ 0 }; +} diff --git a/drivers/gpu/drm/imagination/pvr_power.h b/drivers/gpu/drm/imagination/pvr_power.h index 9a9312dcb2da..c34252bda078 100644 --- a/drivers/gpu/drm/imagination/pvr_power.h +++ b/drivers/gpu/drm/imagination/pvr_power.h @@ -30,12 +30,30 @@ pvr_power_get(struct pvr_device *pvr_dev) return pm_runtime_resume_and_get(drm_dev->dev); } -static __always_inline int +static __always_inline void pvr_power_put(struct pvr_device *pvr_dev) { struct drm_device *drm_dev = from_pvr_device(pvr_dev); - return pm_runtime_put(drm_dev->dev); + pm_runtime_put(drm_dev->dev); } +int pvr_power_domains_init(struct pvr_device *pvr_dev); +void pvr_power_domains_fini(struct pvr_device *pvr_dev); + +/** + * struct pvr_power_sequence_ops - Platform specific power sequence operations. + * @init: Pointer to the platform-specific initialization function. + * @power_on: Pointer to the platform-specific power on function. + * @power_off: Pointer to the platform-specific power off function. + */ +struct pvr_power_sequence_ops { + int (*init)(struct pvr_device *pvr_dev); + int (*power_on)(struct pvr_device *pvr_dev); + int (*power_off)(struct pvr_device *pvr_dev); +}; + +extern const struct pvr_power_sequence_ops pvr_power_sequence_ops_manual; +extern const struct pvr_power_sequence_ops pvr_power_sequence_ops_pwrseq; + #endif /* PVR_POWER_H */ diff --git a/drivers/gpu/drm/imagination/pvr_queue.c b/drivers/gpu/drm/imagination/pvr_queue.c index 43411be930a2..dd88949f6194 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.c +++ b/drivers/gpu/drm/imagination/pvr_queue.c @@ -249,7 +249,7 @@ pvr_queue_fence_alloc(void) { struct pvr_queue_fence *fence; - fence = kzalloc(sizeof(*fence), GFP_KERNEL); + fence = kzalloc_obj(*fence); if (!fence) return NULL; @@ -803,7 +803,7 @@ static void pvr_queue_start(struct pvr_queue *queue) * the scheduler, and re-assign parent fences in the middle. * * Return: - * * DRM_GPU_SCHED_STAT_NOMINAL. + * * DRM_GPU_SCHED_STAT_RESET. */ static enum drm_gpu_sched_stat pvr_queue_timedout_job(struct drm_sched_job *s_job) @@ -854,7 +854,7 @@ pvr_queue_timedout_job(struct drm_sched_job *s_job) drm_sched_start(sched, 0); - return DRM_GPU_SCHED_STAT_NOMINAL; + return DRM_GPU_SCHED_STAT_RESET; } /** @@ -866,6 +866,10 @@ static void pvr_queue_free_job(struct drm_sched_job *sched_job) struct pvr_job *job = container_of(sched_job, struct pvr_job, base); drm_sched_job_cleanup(sched_job); + + if (job->type == DRM_PVR_JOB_TYPE_FRAGMENT && job->paired_job) + pvr_job_put(job->paired_job); + job->paired_job = NULL; pvr_job_put(job); } @@ -1069,6 +1073,7 @@ static int pvr_queue_cleanup_fw_context(struct pvr_queue *queue) /** * pvr_queue_job_init() - Initialize queue related fields in a pvr_job object. * @job: The job to initialize. + * @drm_client_id: drm_file.client_id submitting the job * * Bind the job to a queue and allocate memory to guarantee pvr_queue_job_arm() * and pvr_queue_job_push() can't fail. We also make sure the context type is @@ -1078,7 +1083,7 @@ static int pvr_queue_cleanup_fw_context(struct pvr_queue *queue) * * 0 on success, or * * An error code if something failed. */ -int pvr_queue_job_init(struct pvr_job *job) +int pvr_queue_job_init(struct pvr_job *job, u64 drm_client_id) { /* Fragment jobs need at least one native fence wait on the geometry job fence. */ u32 min_native_dep_count = job->type == DRM_PVR_JOB_TYPE_FRAGMENT ? 1 : 0; @@ -1095,7 +1100,7 @@ int pvr_queue_job_init(struct pvr_job *job) if (!pvr_cccb_cmdseq_can_fit(&queue->cccb, job_cmds_size(job, min_native_dep_count))) return -E2BIG; - err = drm_sched_job_init(&job->base, &queue->entity, 1, THIS_MODULE); + err = drm_sched_job_init(&job->base, &queue->entity, 1, THIS_MODULE, drm_client_id); if (err) return err; @@ -1220,6 +1225,17 @@ struct pvr_queue *pvr_queue_create(struct pvr_context *ctx, }, }; struct pvr_device *pvr_dev = ctx->pvr_dev; + const struct drm_sched_init_args sched_args = { + .ops = &pvr_queue_sched_ops, + .submit_wq = pvr_dev->sched_wq, + .num_rqs = 1, + .credit_limit = 64 * 1024, + .hang_limit = 1, + .timeout = msecs_to_jiffies(500), + .timeout_wq = pvr_dev->sched_wq, + .name = "pvr-queue", + .dev = pvr_dev->base.dev, + }; struct drm_gpu_scheduler *sched; struct pvr_queue *queue; int ctx_state_size, err; @@ -1250,7 +1266,7 @@ struct pvr_queue *pvr_queue_create(struct pvr_context *ctx, if (ctx_state_size < 0) return ERR_PTR(ctx_state_size); - queue = kzalloc(sizeof(*queue), GFP_KERNEL); + queue = kzalloc_obj(*queue); if (!queue) return ERR_PTR(-ENOMEM); @@ -1292,12 +1308,7 @@ struct pvr_queue *pvr_queue_create(struct pvr_context *ctx, queue->timeline_ufo.value = cpu_map; - err = drm_sched_init(&queue->scheduler, - &pvr_queue_sched_ops, - pvr_dev->sched_wq, 1, 64 * 1024, 1, - msecs_to_jiffies(500), - pvr_dev->sched_wq, NULL, "pvr-queue", - pvr_dev->base.dev); + err = drm_sched_init(&queue->scheduler, &sched_args); if (err) goto err_release_ufo; diff --git a/drivers/gpu/drm/imagination/pvr_queue.h b/drivers/gpu/drm/imagination/pvr_queue.h index 93fe9ac9f58c..fc1986d73fc8 100644 --- a/drivers/gpu/drm/imagination/pvr_queue.h +++ b/drivers/gpu/drm/imagination/pvr_queue.h @@ -143,7 +143,7 @@ struct pvr_queue { bool pvr_queue_fence_is_ufo_backed(struct dma_fence *f); -int pvr_queue_job_init(struct pvr_job *job); +int pvr_queue_job_init(struct pvr_job *job, u64 drm_client_id); void pvr_queue_job_cleanup(struct pvr_job *job); diff --git a/drivers/gpu/drm/imagination/pvr_rogue_cr_defs.h b/drivers/gpu/drm/imagination/pvr_rogue_cr_defs.h index 2a90d02796d3..790c97f80a2a 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_cr_defs.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_cr_defs.h @@ -827,6 +827,120 @@ #define ROGUE_CR_EVENT_STATUS_TLA_COMPLETE_CLRMSK 0xFFFFFFFEU #define ROGUE_CR_EVENT_STATUS_TLA_COMPLETE_EN 0x00000001U +/* Register ROGUE_CR_EVENT_CLEAR */ +#define ROGUE_CR_EVENT_CLEAR 0x0138U +#define ROGUE_CR_EVENT_CLEAR__ROGUEXE__MASKFULL 0x00000000E01DFFFFULL +#define ROGUE_CR_EVENT_CLEAR__SIGNALS__MASKFULL 0x00000000E007FFFFULL +#define ROGUE_CR_EVENT_CLEAR_MASKFULL 0x00000000FFFFFFFFULL +#define ROGUE_CR_EVENT_CLEAR_TDM_FENCE_FINISHED_SHIFT 31U +#define ROGUE_CR_EVENT_CLEAR_TDM_FENCE_FINISHED_CLRMSK 0x7FFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_TDM_FENCE_FINISHED_EN 0x80000000U +#define ROGUE_CR_EVENT_CLEAR_TDM_BUFFER_STALL_SHIFT 30U +#define ROGUE_CR_EVENT_CLEAR_TDM_BUFFER_STALL_CLRMSK 0xBFFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_TDM_BUFFER_STALL_EN 0x40000000U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_SIGNAL_FAILURE_SHIFT 29U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_SIGNAL_FAILURE_CLRMSK 0xDFFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_SIGNAL_FAILURE_EN 0x20000000U +#define ROGUE_CR_EVENT_CLEAR_DPX_OUT_OF_MEMORY_SHIFT 28U +#define ROGUE_CR_EVENT_CLEAR_DPX_OUT_OF_MEMORY_CLRMSK 0xEFFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_DPX_OUT_OF_MEMORY_EN 0x10000000U +#define ROGUE_CR_EVENT_CLEAR_DPX_MMU_PAGE_FAULT_SHIFT 27U +#define ROGUE_CR_EVENT_CLEAR_DPX_MMU_PAGE_FAULT_CLRMSK 0xF7FFFFFFU +#define ROGUE_CR_EVENT_CLEAR_DPX_MMU_PAGE_FAULT_EN 0x08000000U +#define ROGUE_CR_EVENT_CLEAR_RPM_OUT_OF_MEMORY_SHIFT 26U +#define ROGUE_CR_EVENT_CLEAR_RPM_OUT_OF_MEMORY_CLRMSK 0xFBFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_RPM_OUT_OF_MEMORY_EN 0x04000000U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC3_FINISHED_SHIFT 25U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC3_FINISHED_CLRMSK 0xFDFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_FBA_FC3_FINISHED_EN 0x02000000U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC2_FINISHED_SHIFT 24U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC2_FINISHED_CLRMSK 0xFEFFFFFFU +#define ROGUE_CR_EVENT_CLEAR_FBA_FC2_FINISHED_EN 0x01000000U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC1_FINISHED_SHIFT 23U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC1_FINISHED_CLRMSK 0xFF7FFFFFU +#define ROGUE_CR_EVENT_CLEAR_FBA_FC1_FINISHED_EN 0x00800000U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC0_FINISHED_SHIFT 22U +#define ROGUE_CR_EVENT_CLEAR_FBA_FC0_FINISHED_CLRMSK 0xFFBFFFFFU +#define ROGUE_CR_EVENT_CLEAR_FBA_FC0_FINISHED_EN 0x00400000U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC3_FINISHED_SHIFT 21U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC3_FINISHED_CLRMSK 0xFFDFFFFFU +#define ROGUE_CR_EVENT_CLEAR_RDM_FC3_FINISHED_EN 0x00200000U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC2_FINISHED_SHIFT 20U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC2_FINISHED_CLRMSK 0xFFEFFFFFU +#define ROGUE_CR_EVENT_CLEAR_RDM_FC2_FINISHED_EN 0x00100000U +#define ROGUE_CR_EVENT_CLEAR_SAFETY_SHIFT 20U +#define ROGUE_CR_EVENT_CLEAR_SAFETY_CLRMSK 0xFFEFFFFFU +#define ROGUE_CR_EVENT_CLEAR_SAFETY_EN 0x00100000U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC1_FINISHED_SHIFT 19U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC1_FINISHED_CLRMSK 0xFFF7FFFFU +#define ROGUE_CR_EVENT_CLEAR_RDM_FC1_FINISHED_EN 0x00080000U +#define ROGUE_CR_EVENT_CLEAR_SLAVE_REQ_SHIFT 19U +#define ROGUE_CR_EVENT_CLEAR_SLAVE_REQ_CLRMSK 0xFFF7FFFFU +#define ROGUE_CR_EVENT_CLEAR_SLAVE_REQ_EN 0x00080000U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC0_FINISHED_SHIFT 18U +#define ROGUE_CR_EVENT_CLEAR_RDM_FC0_FINISHED_CLRMSK 0xFFFBFFFFU +#define ROGUE_CR_EVENT_CLEAR_RDM_FC0_FINISHED_EN 0x00040000U +#define ROGUE_CR_EVENT_CLEAR_TDM_CONTEXT_STORE_FINISHED_SHIFT 18U +#define ROGUE_CR_EVENT_CLEAR_TDM_CONTEXT_STORE_FINISHED_CLRMSK 0xFFFBFFFFU +#define ROGUE_CR_EVENT_CLEAR_TDM_CONTEXT_STORE_FINISHED_EN 0x00040000U +#define ROGUE_CR_EVENT_CLEAR_SHG_FINISHED_SHIFT 17U +#define ROGUE_CR_EVENT_CLEAR_SHG_FINISHED_CLRMSK 0xFFFDFFFFU +#define ROGUE_CR_EVENT_CLEAR_SHG_FINISHED_EN 0x00020000U +#define ROGUE_CR_EVENT_CLEAR_SPFILTER_SIGNAL_UPDATE_SHIFT 17U +#define ROGUE_CR_EVENT_CLEAR_SPFILTER_SIGNAL_UPDATE_CLRMSK 0xFFFDFFFFU +#define ROGUE_CR_EVENT_CLEAR_SPFILTER_SIGNAL_UPDATE_EN 0x00020000U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_BUFFER_STALL_SHIFT 16U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_BUFFER_STALL_CLRMSK 0xFFFEFFFFU +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_BUFFER_STALL_EN 0x00010000U +#define ROGUE_CR_EVENT_CLEAR_USC_TRIGGER_SHIFT 15U +#define ROGUE_CR_EVENT_CLEAR_USC_TRIGGER_CLRMSK 0xFFFF7FFFU +#define ROGUE_CR_EVENT_CLEAR_USC_TRIGGER_EN 0x00008000U +#define ROGUE_CR_EVENT_CLEAR_ZLS_FINISHED_SHIFT 14U +#define ROGUE_CR_EVENT_CLEAR_ZLS_FINISHED_CLRMSK 0xFFFFBFFFU +#define ROGUE_CR_EVENT_CLEAR_ZLS_FINISHED_EN 0x00004000U +#define ROGUE_CR_EVENT_CLEAR_GPIO_ACK_SHIFT 13U +#define ROGUE_CR_EVENT_CLEAR_GPIO_ACK_CLRMSK 0xFFFFDFFFU +#define ROGUE_CR_EVENT_CLEAR_GPIO_ACK_EN 0x00002000U +#define ROGUE_CR_EVENT_CLEAR_GPIO_REQ_SHIFT 12U +#define ROGUE_CR_EVENT_CLEAR_GPIO_REQ_CLRMSK 0xFFFFEFFFU +#define ROGUE_CR_EVENT_CLEAR_GPIO_REQ_EN 0x00001000U +#define ROGUE_CR_EVENT_CLEAR_POWER_ABORT_SHIFT 11U +#define ROGUE_CR_EVENT_CLEAR_POWER_ABORT_CLRMSK 0xFFFFF7FFU +#define ROGUE_CR_EVENT_CLEAR_POWER_ABORT_EN 0x00000800U +#define ROGUE_CR_EVENT_CLEAR_POWER_COMPLETE_SHIFT 10U +#define ROGUE_CR_EVENT_CLEAR_POWER_COMPLETE_CLRMSK 0xFFFFFBFFU +#define ROGUE_CR_EVENT_CLEAR_POWER_COMPLETE_EN 0x00000400U +#define ROGUE_CR_EVENT_CLEAR_MMU_PAGE_FAULT_SHIFT 9U +#define ROGUE_CR_EVENT_CLEAR_MMU_PAGE_FAULT_CLRMSK 0xFFFFFDFFU +#define ROGUE_CR_EVENT_CLEAR_MMU_PAGE_FAULT_EN 0x00000200U +#define ROGUE_CR_EVENT_CLEAR_PM_3D_MEM_FREE_SHIFT 8U +#define ROGUE_CR_EVENT_CLEAR_PM_3D_MEM_FREE_CLRMSK 0xFFFFFEFFU +#define ROGUE_CR_EVENT_CLEAR_PM_3D_MEM_FREE_EN 0x00000100U +#define ROGUE_CR_EVENT_CLEAR_PM_OUT_OF_MEMORY_SHIFT 7U +#define ROGUE_CR_EVENT_CLEAR_PM_OUT_OF_MEMORY_CLRMSK 0xFFFFFF7FU +#define ROGUE_CR_EVENT_CLEAR_PM_OUT_OF_MEMORY_EN 0x00000080U +#define ROGUE_CR_EVENT_CLEAR_TA_TERMINATE_SHIFT 6U +#define ROGUE_CR_EVENT_CLEAR_TA_TERMINATE_CLRMSK 0xFFFFFFBFU +#define ROGUE_CR_EVENT_CLEAR_TA_TERMINATE_EN 0x00000040U +#define ROGUE_CR_EVENT_CLEAR_TA_FINISHED_SHIFT 5U +#define ROGUE_CR_EVENT_CLEAR_TA_FINISHED_CLRMSK 0xFFFFFFDFU +#define ROGUE_CR_EVENT_CLEAR_TA_FINISHED_EN 0x00000020U +#define ROGUE_CR_EVENT_CLEAR_ISP_END_MACROTILE_SHIFT 4U +#define ROGUE_CR_EVENT_CLEAR_ISP_END_MACROTILE_CLRMSK 0xFFFFFFEFU +#define ROGUE_CR_EVENT_CLEAR_ISP_END_MACROTILE_EN 0x00000010U +#define ROGUE_CR_EVENT_CLEAR_PIXELBE_END_RENDER_SHIFT 3U +#define ROGUE_CR_EVENT_CLEAR_PIXELBE_END_RENDER_CLRMSK 0xFFFFFFF7U +#define ROGUE_CR_EVENT_CLEAR_PIXELBE_END_RENDER_EN 0x00000008U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_FINISHED_SHIFT 2U +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_FINISHED_CLRMSK 0xFFFFFFFBU +#define ROGUE_CR_EVENT_CLEAR_COMPUTE_FINISHED_EN 0x00000004U +#define ROGUE_CR_EVENT_CLEAR_KERNEL_FINISHED_SHIFT 1U +#define ROGUE_CR_EVENT_CLEAR_KERNEL_FINISHED_CLRMSK 0xFFFFFFFDU +#define ROGUE_CR_EVENT_CLEAR_KERNEL_FINISHED_EN 0x00000002U +#define ROGUE_CR_EVENT_CLEAR_TLA_COMPLETE_SHIFT 0U +#define ROGUE_CR_EVENT_CLEAR_TLA_COMPLETE_CLRMSK 0xFFFFFFFEU +#define ROGUE_CR_EVENT_CLEAR_TLA_COMPLETE_EN 0x00000001U + /* Register ROGUE_CR_TIMER */ #define ROGUE_CR_TIMER 0x0160U #define ROGUE_CR_TIMER_MASKFULL 0x8000FFFFFFFFFFFFULL @@ -6031,25 +6145,6 @@ #define ROGUE_CR_MULTICORE_COMPUTE_CTRL_COMMON_GPU_ENABLE_SHIFT 0U #define ROGUE_CR_MULTICORE_COMPUTE_CTRL_COMMON_GPU_ENABLE_CLRMSK 0xFFFFFF00U -/* Register ROGUE_CR_ECC_RAM_ERR_INJ */ -#define ROGUE_CR_ECC_RAM_ERR_INJ 0xF340U -#define ROGUE_CR_ECC_RAM_ERR_INJ_MASKFULL 0x000000000000001FULL -#define ROGUE_CR_ECC_RAM_ERR_INJ_SLC_SIDEKICK_SHIFT 4U -#define ROGUE_CR_ECC_RAM_ERR_INJ_SLC_SIDEKICK_CLRMSK 0xFFFFFFEFU -#define ROGUE_CR_ECC_RAM_ERR_INJ_SLC_SIDEKICK_EN 0x00000010U -#define ROGUE_CR_ECC_RAM_ERR_INJ_USC_SHIFT 3U -#define ROGUE_CR_ECC_RAM_ERR_INJ_USC_CLRMSK 0xFFFFFFF7U -#define ROGUE_CR_ECC_RAM_ERR_INJ_USC_EN 0x00000008U -#define ROGUE_CR_ECC_RAM_ERR_INJ_TPU_MCU_L0_SHIFT 2U -#define ROGUE_CR_ECC_RAM_ERR_INJ_TPU_MCU_L0_CLRMSK 0xFFFFFFFBU -#define ROGUE_CR_ECC_RAM_ERR_INJ_TPU_MCU_L0_EN 0x00000004U -#define ROGUE_CR_ECC_RAM_ERR_INJ_RASCAL_SHIFT 1U -#define ROGUE_CR_ECC_RAM_ERR_INJ_RASCAL_CLRMSK 0xFFFFFFFDU -#define ROGUE_CR_ECC_RAM_ERR_INJ_RASCAL_EN 0x00000002U -#define ROGUE_CR_ECC_RAM_ERR_INJ_MARS_SHIFT 0U -#define ROGUE_CR_ECC_RAM_ERR_INJ_MARS_CLRMSK 0xFFFFFFFEU -#define ROGUE_CR_ECC_RAM_ERR_INJ_MARS_EN 0x00000001U - /* Register ROGUE_CR_ECC_RAM_INIT_KICK */ #define ROGUE_CR_ECC_RAM_INIT_KICK 0xF348U #define ROGUE_CR_ECC_RAM_INIT_KICK_MASKFULL 0x000000000000001FULL @@ -6163,6 +6258,26 @@ #define ROGUE_CR_SAFETY_EVENT_CLEAR__ROGUEXE__GPU_PAGE_FAULT_CLRMSK 0xFFFFFFFEU #define ROGUE_CR_SAFETY_EVENT_CLEAR__ROGUEXE__GPU_PAGE_FAULT_EN 0x00000001U +/* Register ROGUE_CR_FAULT_FW_STATUS */ +#define ROGUE_CR_FAULT_FW_STATUS 0xF3B0U +#define ROGUE_CR_FAULT_FW_STATUS_MASKFULL 0x0000000000010001ULL +#define ROGUE_CR_FAULT_FW_STATUS_CPU_CORRECT_SHIFT 16U +#define ROGUE_CR_FAULT_FW_STATUS_CPU_CORRECT_CLRMSK 0xFFFEFFFFU +#define ROGUE_CR_FAULT_FW_STATUS_CPU_CORRECT_EN 0x00010000U +#define ROGUE_CR_FAULT_FW_STATUS_CPU_DETECT_SHIFT 0U +#define ROGUE_CR_FAULT_FW_STATUS_CPU_DETECT_CLRMSK 0xFFFFFFFEU +#define ROGUE_CR_FAULT_FW_STATUS_CPU_DETECT_EN 0x00000001U + +/* Register ROGUE_CR_FAULT_FW_CLEAR */ +#define ROGUE_CR_FAULT_FW_CLEAR 0xF3B8U +#define ROGUE_CR_FAULT_FW_CLEAR_MASKFULL 0x0000000000010001ULL +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_CORRECT_SHIFT 16U +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_CORRECT_CLRMSK 0xFFFEFFFFU +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_CORRECT_EN 0x00010000U +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_DETECT_SHIFT 0U +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_DETECT_CLRMSK 0xFFFFFFFEU +#define ROGUE_CR_FAULT_FW_CLEAR_CPU_DETECT_EN 0x00000001U + /* Register ROGUE_CR_MTS_SAFETY_EVENT_ENABLE */ #define ROGUE_CR_MTS_SAFETY_EVENT_ENABLE__ROGUEXE 0xF3D8U #define ROGUE_CR_MTS_SAFETY_EVENT_ENABLE__ROGUEXE__MASKFULL 0x000000000000007FULL diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif.h index 172886be4c82..5d590c4c2566 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif.h @@ -1347,8 +1347,12 @@ struct rogue_fwif_fwccb_cmd_freelists_reconstruction_data { struct rogue_fwif_fwccb_cmd_context_reset_data { /* Context affected by the reset */ u32 server_common_context_id; - /* Reason for reset */ - enum rogue_context_reset_reason reset_reason; + /* + * Reason for reset + * The valid values for reset_reason are the ones from + * enum rogue_context_reset_reason + */ + u32 reset_reason; /* Data Master affected by the reset */ u32 dm; /* Job ref running at the time of reset */ diff --git a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h index 6c09c15bf9bd..869d904e3649 100644 --- a/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h +++ b/drivers/gpu/drm/imagination/pvr_rogue_fwif_shared.h @@ -236,6 +236,18 @@ enum rogue_context_reset_reason { ROGUE_CONTEXT_RESET_REASON_INNOCENT_OVERRUNING = 4, /* Forced reset to ensure scheduling requirements */ ROGUE_CONTEXT_RESET_REASON_HARD_CONTEXT_SWITCH = 5, + /* CDM Mission/safety checksum mismatch */ + ROGUE_CONTEXT_RESET_REASON_WGP_CHECKSUM = 6, + /* TRP checksum mismatch */ + ROGUE_CONTEXT_RESET_REASON_TRP_CHECKSUM = 7, + /* GPU ECC error (corrected, OK) */ + ROGUE_CONTEXT_RESET_REASON_GPU_ECC_OK = 8, + /* GPU ECC error (uncorrected, HWR) */ + ROGUE_CONTEXT_RESET_REASON_GPU_ECC_HWR = 9, + /* FW ECC error (corrected, OK) */ + ROGUE_CONTEXT_RESET_REASON_FW_ECC_OK = 10, + /* FW ECC error (uncorrected, ERR) */ + ROGUE_CONTEXT_RESET_REASON_FW_ECC_ERR = 11, /* FW Safety watchdog triggered */ ROGUE_CONTEXT_RESET_REASON_FW_WATCHDOG = 12, /* FW page fault (no HWR) */ @@ -249,7 +261,11 @@ enum rogue_context_reset_reason { }; struct rogue_context_reset_reason_data { - enum rogue_context_reset_reason reset_reason; + /* + * The valid values for reset_reason are the ones from + * enum rogue_context_reset_reason + */ + u32 reset_reason; u32 reset_ext_job_ref; }; diff --git a/drivers/gpu/drm/imagination/pvr_rogue_riscv.h b/drivers/gpu/drm/imagination/pvr_rogue_riscv.h new file mode 100644 index 000000000000..9a070e24fa6a --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_rogue_riscv.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* Copyright (c) 2024 Imagination Technologies Ltd. */ + +#ifndef PVR_ROGUE_RISCV_H +#define PVR_ROGUE_RISCV_H + +#include "pvr_rogue_cr_defs.h" + +#include <linux/bitops.h> +#include <linux/sizes.h> +#include <linux/types.h> + +#define ROGUE_RISCVFW_REGION_SIZE SZ_256M +#define ROGUE_RISCVFW_REGION_SHIFT __ffs(ROGUE_RISCVFW_REGION_SIZE) + +enum rogue_riscvfw_region { + ROGUE_RISCV_REGION__RESERVED_0 = 0, + ROGUE_RISCV_REGION__RESERVED_1, + ROGUE_RISCV_REGION_SOCIF, + ROGUE_RISCV_REGION__RESERVED_3, + ROGUE_RISCV_REGION__RESERVED_4, + ROGUE_RISCV_REGION_BOOTLDR_DATA, + ROGUE_RISCV_REGION_SHARED_CACHED_DATA, + ROGUE_RISCV_REGION__RESERVED_7, + ROGUE_RISCV_REGION_COREMEM, + ROGUE_RISCV_REGION__RESERVED_9, + ROGUE_RISCV_REGION__RESERVED_A, + ROGUE_RISCV_REGION__RESERVED_B, + ROGUE_RISCV_REGION_BOOTLDR_CODE, + ROGUE_RISCV_REGION_SHARED_UNCACHED_DATA, + ROGUE_RISCV_REGION__RESERVED_E, + ROGUE_RISCV_REGION__RESERVED_F, + + ROGUE_RISCV_REGION__COUNT, +}; + +#define ROGUE_RISCVFW_REGION_BASE(r) ((u32)(ROGUE_RISCV_REGION_##r) << ROGUE_RISCVFW_REGION_SHIFT) +#define ROGUE_RISCVFW_REGION_REMAP_CR(r) \ + (ROGUE_CR_FWCORE_ADDR_REMAP_CONFIG0 + (u32)(ROGUE_RISCV_REGION_##r) * 8U) + +#endif /* PVR_ROGUE_RISCV_H */ diff --git a/drivers/gpu/drm/imagination/pvr_stream.c b/drivers/gpu/drm/imagination/pvr_stream.c index 975336a4facf..679aa618b7a9 100644 --- a/drivers/gpu/drm/imagination/pvr_stream.c +++ b/drivers/gpu/drm/imagination/pvr_stream.c @@ -67,9 +67,8 @@ pvr_stream_process_1(struct pvr_device *pvr_dev, const struct pvr_stream_def *st u8 *dest, u32 dest_size, u32 *stream_offset_out) { int err = 0; - u32 i; - for (i = 0; i < nr_entries; i++) { + for (u32 i = 0; i < nr_entries; i++) { if (stream_def[i].offset >= dest_size) { err = -EINVAL; break; @@ -131,7 +130,6 @@ pvr_stream_process_ext_stream(struct pvr_device *pvr_dev, u32 musthave_masks[PVR_STREAM_EXTHDR_TYPE_MAX]; u32 ext_header; int err = 0; - u32 i; /* Copy "must have" mask from device. We clear this as we process the stream. */ memcpy(musthave_masks, pvr_dev->stream_musthave_quirks[cmd_defs->type], @@ -159,7 +157,7 @@ pvr_stream_process_ext_stream(struct pvr_device *pvr_dev, musthave_masks[type] &= ~data; - for (i = 0; i < header->ext_streams_num; i++) { + for (u32 i = 0; i < header->ext_streams_num; i++) { const struct pvr_stream_ext_def *ext_def = &header->ext_streams[i]; if (!(ext_header & ext_def->header_mask)) @@ -181,7 +179,7 @@ pvr_stream_process_ext_stream(struct pvr_device *pvr_dev, * Verify that "must have" mask is now zero. If it isn't then one of the "must have" quirks * for this command was not present. */ - for (i = 0; i < cmd_defs->ext_nr_headers; i++) { + for (u32 i = 0; i < cmd_defs->ext_nr_headers; i++) { if (musthave_masks[i]) return -EINVAL; } @@ -245,13 +243,11 @@ pvr_stream_process(struct pvr_device *pvr_dev, const struct pvr_stream_cmd_defs if (err) return err; } else { - u32 i; - /* * If we don't have an extension stream then there must not be any "must have" * quirks for this command. */ - for (i = 0; i < cmd_defs->ext_nr_headers; i++) { + for (u32 i = 0; i < cmd_defs->ext_nr_headers; i++) { if (pvr_dev->stream_musthave_quirks[cmd_defs->type][i]) return -EINVAL; } diff --git a/drivers/gpu/drm/imagination/pvr_sync.c b/drivers/gpu/drm/imagination/pvr_sync.c index 129f646d14ba..3582616ff722 100644 --- a/drivers/gpu/drm/imagination/pvr_sync.c +++ b/drivers/gpu/drm/imagination/pvr_sync.c @@ -64,7 +64,7 @@ pvr_sync_signal_array_add(struct xarray *array, struct drm_file *file, u32 handl int err; u32 id; - sig_sync = kzalloc(sizeof(*sig_sync), GFP_KERNEL); + sig_sync = kzalloc_obj(*sig_sync); if (!sig_sync) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/imagination/pvr_test.c b/drivers/gpu/drm/imagination/pvr_test.c new file mode 100644 index 000000000000..506cfa5a02f1 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_test.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (c) 2025 Imagination Technologies Ltd. */ + +#include "pvr_device.h" + +#include <linux/errno.h> +#include <linux/stddef.h> +#include <linux/string.h> +#include <linux/types.h> + +#include <kunit/test.h> +#include <kunit/visibility.h> + +static void decode_gpuid_string(struct kunit *test) +{ + const struct pvr_gpu_id bad_gpuid = { 0xdead, 0xbeef, 0xcafe, 0xface }; + const u64 packed_bad_gpuid = pvr_gpu_id_to_packed_bvnc(&bad_gpuid); + +#define GPUID_TEST_CASE(str_, err_, value_) \ + do { \ + struct pvr_gpu_id _gpuid_out = bad_gpuid; \ + int _err; \ + _err = pvr_gpuid_decode_string(NULL, str_, &_gpuid_out); \ + KUNIT_EXPECT_EQ(test, _err, err_); \ + KUNIT_EXPECT_EQ(test, \ + pvr_gpu_id_to_packed_bvnc(&_gpuid_out), \ + value_); \ + } while (0) + +#define GPUID_TEST_CASE_OK(str_, b_, v_, n_, c_) \ + GPUID_TEST_CASE(str_, 0, PVR_PACKED_BVNC(b_, v_, n_, c_)) + +#define GPUID_TEST_CASE_INVAL(str_) \ + GPUID_TEST_CASE(str_, -EINVAL, packed_bad_gpuid) + + GPUID_TEST_CASE_OK("12.34.56.78", 12, 34, 56, 78); + GPUID_TEST_CASE_OK("0.0.0.0", 0, 0, 0, 0); + + GPUID_TEST_CASE_INVAL(""); + GPUID_TEST_CASE_INVAL("42.foobar-invalid.gpuid.bvnc"); + + /* String longer than PVR_GPUID_STRING_MAX_LENGTH. */ + GPUID_TEST_CASE_INVAL("12.34.56.789012345678901234567890123456"); + + /* Single value overflowing u16. */ + GPUID_TEST_CASE_INVAL("12.34.56.999999"); + + /* Wrong number of parts and/or dots. */ + GPUID_TEST_CASE_INVAL("12.34.56.78.90"); + GPUID_TEST_CASE_INVAL("12.34.56..78"); + GPUID_TEST_CASE_INVAL("12.34..56"); + GPUID_TEST_CASE_INVAL("12.34.56"); + +#undef GPUID_TEST_CASE_INVAL +#undef GPUID_TEST_CASE_OK +#undef GPUID_TEST_CASE +} + +static struct kunit_case pvr_tests_cases[] = { + KUNIT_CASE(decode_gpuid_string), + {}, +}; + +static struct kunit_suite pvr_tests_suite = { + .name = "pvr_tests", + .test_cases = pvr_tests_cases, +}; +kunit_test_suite(pvr_tests_suite); + +MODULE_AUTHOR("Imagination Technologies Ltd."); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("pvr kunit tests"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/drivers/gpu/drm/imagination/pvr_vm.c b/drivers/gpu/drm/imagination/pvr_vm.c index 2896fa7501b1..e1ec60f34b6e 100644 --- a/drivers/gpu/drm/imagination/pvr_vm.c +++ b/drivers/gpu/drm/imagination/pvr_vm.c @@ -13,6 +13,7 @@ #include <drm/drm_exec.h> #include <drm/drm_gem.h> #include <drm/drm_gpuvm.h> +#include <drm/drm_print.h> #include <linux/bug.h> #include <linux/container_of.h> @@ -185,12 +186,17 @@ struct pvr_vm_bind_op { static int pvr_vm_bind_op_exec(struct pvr_vm_bind_op *bind_op) { switch (bind_op->type) { - case PVR_VM_BIND_TYPE_MAP: + case PVR_VM_BIND_TYPE_MAP: { + const struct drm_gpuvm_map_req map_req = { + .map.va.addr = bind_op->device_addr, + .map.va.range = bind_op->size, + .map.gem.obj = gem_from_pvr_gem(bind_op->pvr_obj), + .map.gem.offset = bind_op->offset, + }; + return drm_gpuvm_sm_map(&bind_op->vm_ctx->gpuvm_mgr, - bind_op, bind_op->device_addr, - bind_op->size, - gem_from_pvr_gem(bind_op->pvr_obj), - bind_op->offset); + bind_op, &map_req); + } case PVR_VM_BIND_TYPE_UNMAP: return drm_gpuvm_sm_unmap(&bind_op->vm_ctx->gpuvm_mgr, @@ -250,14 +256,14 @@ pvr_vm_bind_op_map_init(struct pvr_vm_bind_op *bind_op, bind_op->type = PVR_VM_BIND_TYPE_MAP; dma_resv_lock(obj->resv, NULL); - bind_op->gpuvm_bo = drm_gpuvm_bo_obtain(&vm_ctx->gpuvm_mgr, obj); + bind_op->gpuvm_bo = drm_gpuvm_bo_obtain_locked(&vm_ctx->gpuvm_mgr, obj); dma_resv_unlock(obj->resv); if (IS_ERR(bind_op->gpuvm_bo)) return PTR_ERR(bind_op->gpuvm_bo); - bind_op->new_va = kzalloc(sizeof(*bind_op->new_va), GFP_KERNEL); - bind_op->prev_va = kzalloc(sizeof(*bind_op->prev_va), GFP_KERNEL); - bind_op->next_va = kzalloc(sizeof(*bind_op->next_va), GFP_KERNEL); + bind_op->new_va = kzalloc_obj(*bind_op->new_va); + bind_op->prev_va = kzalloc_obj(*bind_op->prev_va); + bind_op->next_va = kzalloc_obj(*bind_op->next_va); if (!bind_op->new_va || !bind_op->prev_va || !bind_op->next_va) { err = -ENOMEM; goto err_bind_op_fini; @@ -304,8 +310,8 @@ pvr_vm_bind_op_unmap_init(struct pvr_vm_bind_op *bind_op, bind_op->type = PVR_VM_BIND_TYPE_UNMAP; - bind_op->prev_va = kzalloc(sizeof(*bind_op->prev_va), GFP_KERNEL); - bind_op->next_va = kzalloc(sizeof(*bind_op->next_va), GFP_KERNEL); + bind_op->prev_va = kzalloc_obj(*bind_op->prev_va); + bind_op->next_va = kzalloc_obj(*bind_op->next_va); if (!bind_op->prev_va || !bind_op->next_va) { err = -ENOMEM; goto err_bind_op_fini; @@ -559,7 +565,7 @@ pvr_vm_create_context(struct pvr_device *pvr_dev, bool is_userspace_context) return ERR_PTR(-EINVAL); } - vm_ctx = kzalloc(sizeof(*vm_ctx), GFP_KERNEL); + vm_ctx = kzalloc_obj(*vm_ctx); if (!vm_ctx) return ERR_PTR(-ENOMEM); diff --git a/drivers/gpu/drm/imagination/pvr_vm_mips.c b/drivers/gpu/drm/imagination/pvr_vm_mips.c index 94af854547d6..5847a1c92bea 100644 --- a/drivers/gpu/drm/imagination/pvr_vm_mips.c +++ b/drivers/gpu/drm/imagination/pvr_vm_mips.c @@ -100,10 +100,9 @@ pvr_vm_mips_fini(struct pvr_device *pvr_dev) { struct pvr_fw_device *fw_dev = &pvr_dev->fw_dev; struct pvr_fw_mips_data *mips_data = fw_dev->processor_data.mips_data; - int page_nr; vunmap(mips_data->pt); - for (page_nr = PVR_MIPS_PT_PAGE_COUNT - 1; page_nr >= 0; page_nr--) { + for (int page_nr = PVR_MIPS_PT_PAGE_COUNT - 1; page_nr >= 0; page_nr--) { dma_unmap_page(from_pvr_device(pvr_dev)->dev, mips_data->pt_dma_addr[page_nr], PAGE_SIZE, DMA_TO_DEVICE); |
