Unverified Commit cc1aeedb authored by Sarah Walker's avatar Sarah Walker Committed by Maxime Ripard
Browse files

drm/imagination: Implement firmware infrastructure and META FW support



The infrastructure includes parsing of the firmware image, initialising
FW-side structures, handling the kernel and firmware command
ringbuffers and starting & stopping the firmware processor.

This patch also adds the necessary support code for the META firmware
processor.

Changes since v8:
- Fix documentation for pvr_fwccb_process()
- Corrected license identifiers

Changes since v6:
- Add a minimum retry count to pvr_kccb_reserve_slot_sync()

Changes since v5:
- Add workaround for BRN 71242
- Attempt to recover GPU on MMU flush command failure

Changes since v4:
- Remove use of drm_gem_shmem_get_pages()
- Remove interrupt resource name

Changes since v3:
- Hard reset FW processor on watchdog timeout
- Switch to threaded IRQ
- Rework FW object creation/initialisation to aid hard reset
- Added MODULE_FIRMWARE()
- Use drm_dev_{enter,exit}

Signed-off-by: default avatarSarah Walker <sarah.walker@imgtec.com>
Signed-off-by: default avatarDonald Robson <donald.robson@imgtec.com>
Link: https://lore.kernel.org/r/bb52a8dc84f296b37dc6668dfe8fbaf2ba551139.1700668843.git.donald.robson@imgtec.com


Signed-off-by: default avatarMaxime Ripard <mripard@kernel.org>
parent 727538a4
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -4,10 +4,14 @@
subdir-ccflags-y := -I$(srctree)/$(src)

powervr-y := \
	pvr_ccb.o \
	pvr_device.o \
	pvr_device_info.o \
	pvr_drv.o \
	pvr_fw.o \
	pvr_fw_meta.o \
	pvr_fw_startstop.o \
	pvr_fw_trace.o \
	pvr_gem.o \
	pvr_mmu.o \
	pvr_power.o \
+635 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (c) 2023 Imagination Technologies Ltd. */

#include "pvr_ccb.h"
#include "pvr_device.h"
#include "pvr_drv.h"
#include "pvr_fw.h"
#include "pvr_gem.h"
#include "pvr_power.h"

#include <drm/drm_managed.h>
#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/workqueue.h>

#define RESERVE_SLOT_TIMEOUT (1 * HZ) /* 1s */
#define RESERVE_SLOT_MIN_RETRIES 10

static void
ccb_ctrl_init(void *cpu_ptr, void *priv)
{
	struct rogue_fwif_ccb_ctl *ctrl = cpu_ptr;
	struct pvr_ccb *pvr_ccb = priv;

	ctrl->write_offset = 0;
	ctrl->read_offset = 0;
	ctrl->wrap_mask = pvr_ccb->num_cmds - 1;
	ctrl->cmd_size = pvr_ccb->cmd_size;
}

/**
 * pvr_ccb_init() - Initialise a CCB
 * @pvr_dev: Device pointer.
 * @pvr_ccb: Pointer to CCB structure to initialise.
 * @num_cmds_log2: Log2 of number of commands in this CCB.
 * @cmd_size: Command size for this CCB.
 *
 * Return:
 *  * Zero on success, or
 *  * Any error code returned by pvr_fw_object_create_and_map().
 */
static int
pvr_ccb_init(struct pvr_device *pvr_dev, struct pvr_ccb *pvr_ccb,
	     u32 num_cmds_log2, size_t cmd_size)
{
	u32 num_cmds = 1 << num_cmds_log2;
	u32 ccb_size = num_cmds * cmd_size;
	int err;

	pvr_ccb->num_cmds = num_cmds;
	pvr_ccb->cmd_size = cmd_size;

	err = drmm_mutex_init(from_pvr_device(pvr_dev), &pvr_ccb->lock);
	if (err)
		return err;

	/*
	 * Map CCB and control structure as uncached, so we don't have to flush
	 * CPU cache repeatedly when polling for space.
	 */
	pvr_ccb->ctrl = pvr_fw_object_create_and_map(pvr_dev, sizeof(*pvr_ccb->ctrl),
						     PVR_BO_FW_FLAGS_DEVICE_UNCACHED,
						     ccb_ctrl_init, pvr_ccb, &pvr_ccb->ctrl_obj);
	if (IS_ERR(pvr_ccb->ctrl))
		return PTR_ERR(pvr_ccb->ctrl);

	pvr_ccb->ccb = pvr_fw_object_create_and_map(pvr_dev, ccb_size,
						    PVR_BO_FW_FLAGS_DEVICE_UNCACHED,
						    NULL, NULL, &pvr_ccb->ccb_obj);
	if (IS_ERR(pvr_ccb->ccb)) {
		err = PTR_ERR(pvr_ccb->ccb);
		goto err_free_ctrl;
	}

	pvr_fw_object_get_fw_addr(pvr_ccb->ctrl_obj, &pvr_ccb->ctrl_fw_addr);
	pvr_fw_object_get_fw_addr(pvr_ccb->ccb_obj, &pvr_ccb->ccb_fw_addr);

	WRITE_ONCE(pvr_ccb->ctrl->write_offset, 0);
	WRITE_ONCE(pvr_ccb->ctrl->read_offset, 0);
	WRITE_ONCE(pvr_ccb->ctrl->wrap_mask, num_cmds - 1);
	WRITE_ONCE(pvr_ccb->ctrl->cmd_size, cmd_size);

	return 0;

err_free_ctrl:
	pvr_fw_object_unmap_and_destroy(pvr_ccb->ctrl_obj);

	return err;
}

/**
 * pvr_ccb_fini() - Release CCB structure
 * @pvr_ccb: CCB to release.
 */
void
pvr_ccb_fini(struct pvr_ccb *pvr_ccb)
{
	pvr_fw_object_unmap_and_destroy(pvr_ccb->ccb_obj);
	pvr_fw_object_unmap_and_destroy(pvr_ccb->ctrl_obj);
}

/**
 * pvr_ccb_slot_available_locked() - Test whether any slots are available in CCB
 * @pvr_ccb: CCB to test.
 * @write_offset: Address to store number of next available slot. May be %NULL.
 *
 * Caller must hold @pvr_ccb->lock.
 *
 * Return:
 *  * %true if a slot is available, or
 *  * %false if no slot is available.
 */
static __always_inline bool
pvr_ccb_slot_available_locked(struct pvr_ccb *pvr_ccb, u32 *write_offset)
{
	struct rogue_fwif_ccb_ctl *ctrl = pvr_ccb->ctrl;
	u32 next_write_offset = (READ_ONCE(ctrl->write_offset) + 1) & READ_ONCE(ctrl->wrap_mask);

	lockdep_assert_held(&pvr_ccb->lock);

	if (READ_ONCE(ctrl->read_offset) != next_write_offset) {
		if (write_offset)
			*write_offset = next_write_offset;
		return true;
	}

	return false;
}

static void
process_fwccb_command(struct pvr_device *pvr_dev, struct rogue_fwif_fwccb_cmd *cmd)
{
	switch (cmd->cmd_type) {
	case ROGUE_FWIF_FWCCB_CMD_REQUEST_GPU_RESTART:
		pvr_power_reset(pvr_dev, false);
		break;

	default:
		drm_info(from_pvr_device(pvr_dev), "Received unknown FWCCB command %x\n",
			 cmd->cmd_type);
		break;
	}
}

/**
 * pvr_fwccb_process() - Process any pending FWCCB commands
 * @pvr_dev: Target PowerVR device
 */
void pvr_fwccb_process(struct pvr_device *pvr_dev)
{
	struct rogue_fwif_fwccb_cmd *fwccb = pvr_dev->fwccb.ccb;
	struct rogue_fwif_ccb_ctl *ctrl = pvr_dev->fwccb.ctrl;
	u32 read_offset;

	mutex_lock(&pvr_dev->fwccb.lock);

	while ((read_offset = READ_ONCE(ctrl->read_offset)) != READ_ONCE(ctrl->write_offset)) {
		struct rogue_fwif_fwccb_cmd cmd = fwccb[read_offset];

		WRITE_ONCE(ctrl->read_offset, (read_offset + 1) & READ_ONCE(ctrl->wrap_mask));

		/* Drop FWCCB lock while we process command. */
		mutex_unlock(&pvr_dev->fwccb.lock);

		process_fwccb_command(pvr_dev, &cmd);

		mutex_lock(&pvr_dev->fwccb.lock);
	}

	mutex_unlock(&pvr_dev->fwccb.lock);
}

/**
 * pvr_kccb_capacity() - Returns the maximum number of usable KCCB slots.
 * @pvr_dev: Target PowerVR device
 *
 * Return:
 *  * The maximum number of active slots.
 */
static u32 pvr_kccb_capacity(struct pvr_device *pvr_dev)
{
	/* Capacity is the number of slot minus one to cope with the wrapping
	 * mechanisms. If we were to use all slots, we might end up with
	 * read_offset == write_offset, which the FW considers as a KCCB-is-empty
	 * condition.
	 */
	return pvr_dev->kccb.slot_count - 1;
}

/**
 * pvr_kccb_used_slot_count_locked() - Get the number of used slots
 * @pvr_dev: Device pointer.
 *
 * KCCB lock must be held.
 *
 * Return:
 *  * The number of slots currently used.
 */
static u32
pvr_kccb_used_slot_count_locked(struct pvr_device *pvr_dev)
{
	struct pvr_ccb *pvr_ccb = &pvr_dev->kccb.ccb;
	struct rogue_fwif_ccb_ctl *ctrl = pvr_ccb->ctrl;
	u32 wr_offset = READ_ONCE(ctrl->write_offset);
	u32 rd_offset = READ_ONCE(ctrl->read_offset);
	u32 used_count;

	lockdep_assert_held(&pvr_ccb->lock);

	if (wr_offset >= rd_offset)
		used_count = wr_offset - rd_offset;
	else
		used_count = wr_offset + pvr_dev->kccb.slot_count - rd_offset;

	return used_count;
}

/**
 * pvr_kccb_send_cmd_reserved_powered() - Send command to the KCCB, with the PM ref
 * held and a slot pre-reserved
 * @pvr_dev: Device pointer.
 * @cmd: Command to sent.
 * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL.
 */
void
pvr_kccb_send_cmd_reserved_powered(struct pvr_device *pvr_dev,
				   struct rogue_fwif_kccb_cmd *cmd,
				   u32 *kccb_slot)
{
	struct pvr_ccb *pvr_ccb = &pvr_dev->kccb.ccb;
	struct rogue_fwif_kccb_cmd *kccb = pvr_ccb->ccb;
	struct rogue_fwif_ccb_ctl *ctrl = pvr_ccb->ctrl;
	u32 old_write_offset;
	u32 new_write_offset;

	WARN_ON(pvr_dev->lost);

	mutex_lock(&pvr_ccb->lock);

	if (WARN_ON(!pvr_dev->kccb.reserved_count))
		goto out_unlock;

	old_write_offset = READ_ONCE(ctrl->write_offset);

	/* We reserved the slot, we should have one available. */
	if (WARN_ON(!pvr_ccb_slot_available_locked(pvr_ccb, &new_write_offset)))
		goto out_unlock;

	memcpy(&kccb[old_write_offset], cmd,
	       sizeof(struct rogue_fwif_kccb_cmd));
	if (kccb_slot) {
		*kccb_slot = old_write_offset;
		/* Clear return status for this slot. */
		WRITE_ONCE(pvr_dev->kccb.rtn[old_write_offset],
			   ROGUE_FWIF_KCCB_RTN_SLOT_NO_RESPONSE);
	}
	mb(); /* memory barrier */
	WRITE_ONCE(ctrl->write_offset, new_write_offset);
	pvr_dev->kccb.reserved_count--;

	/* Kick MTS */
	pvr_fw_mts_schedule(pvr_dev,
			    PVR_FWIF_DM_GP & ~ROGUE_CR_MTS_SCHEDULE_DM_CLRMSK);

out_unlock:
	mutex_unlock(&pvr_ccb->lock);
}

/**
 * pvr_kccb_try_reserve_slot() - Try to reserve a KCCB slot
 * @pvr_dev: Device pointer.
 *
 * Return:
 *  * true if a KCCB slot was reserved, or
 *  * false otherwise.
 */
static bool pvr_kccb_try_reserve_slot(struct pvr_device *pvr_dev)
{
	bool reserved = false;
	u32 used_count;

	mutex_lock(&pvr_dev->kccb.ccb.lock);

	used_count = pvr_kccb_used_slot_count_locked(pvr_dev);
	if (pvr_dev->kccb.reserved_count < pvr_kccb_capacity(pvr_dev) - used_count) {
		pvr_dev->kccb.reserved_count++;
		reserved = true;
	}

	mutex_unlock(&pvr_dev->kccb.ccb.lock);

	return reserved;
}

/**
 * pvr_kccb_reserve_slot_sync() - Try to reserve a slot synchronously
 * @pvr_dev: Device pointer.
 *
 * Return:
 *  * 0 on success, or
 *  * -EBUSY if no slots were reserved after %RESERVE_SLOT_TIMEOUT, with a minimum of
 *    %RESERVE_SLOT_MIN_RETRIES retries.
 */
static int pvr_kccb_reserve_slot_sync(struct pvr_device *pvr_dev)
{
	unsigned long start_timestamp = jiffies;
	bool reserved = false;
	u32 retries = 0;

	while ((jiffies - start_timestamp) < (u32)RESERVE_SLOT_TIMEOUT ||
	       retries < RESERVE_SLOT_MIN_RETRIES) {
		reserved = pvr_kccb_try_reserve_slot(pvr_dev);
		if (reserved)
			break;

		usleep_range(1, 50);

		if (retries < U32_MAX)
			retries++;
	}

	return reserved ? 0 : -EBUSY;
}

/**
 * pvr_kccb_send_cmd_powered() - Send command to the KCCB, with a PM ref held
 * @pvr_dev: Device pointer.
 * @cmd: Command to sent.
 * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL.
 *
 * Returns:
 *  * Zero on success, or
 *  * -EBUSY if timeout while waiting for a free KCCB slot.
 */
int
pvr_kccb_send_cmd_powered(struct pvr_device *pvr_dev, struct rogue_fwif_kccb_cmd *cmd,
			  u32 *kccb_slot)
{
	int err;

	err = pvr_kccb_reserve_slot_sync(pvr_dev);
	if (err)
		return err;

	pvr_kccb_send_cmd_reserved_powered(pvr_dev, cmd, kccb_slot);
	return 0;
}

/**
 * pvr_kccb_send_cmd() - Send command to the KCCB
 * @pvr_dev: Device pointer.
 * @cmd: Command to sent.
 * @kccb_slot: Address to store the KCCB slot for this command. May be %NULL.
 *
 * Returns:
 *  * Zero on success, or
 *  * -EBUSY if timeout while waiting for a free KCCB slot.
 */
int
pvr_kccb_send_cmd(struct pvr_device *pvr_dev, struct rogue_fwif_kccb_cmd *cmd,
		  u32 *kccb_slot)
{
	int err;

	err = pvr_power_get(pvr_dev);
	if (err)
		return err;

	err = pvr_kccb_send_cmd_powered(pvr_dev, cmd, kccb_slot);

	pvr_power_put(pvr_dev);

	return err;
}

/**
 * pvr_kccb_wait_for_completion() - Wait for a KCCB command to complete
 * @pvr_dev: Device pointer.
 * @slot_nr: KCCB slot to wait on.
 * @timeout: Timeout length (in jiffies).
 * @rtn_out: Location to store KCCB command result. May be %NULL.
 *
 * Returns:
 *  * Zero on success, or
 *  * -ETIMEDOUT on timeout.
 */
int
pvr_kccb_wait_for_completion(struct pvr_device *pvr_dev, u32 slot_nr,
			     u32 timeout, u32 *rtn_out)
{
	int ret = wait_event_timeout(pvr_dev->kccb.rtn_q, READ_ONCE(pvr_dev->kccb.rtn[slot_nr]) &
				     ROGUE_FWIF_KCCB_RTN_SLOT_CMD_EXECUTED, timeout);

	if (ret && rtn_out)
		*rtn_out = READ_ONCE(pvr_dev->kccb.rtn[slot_nr]);

	return ret ? 0 : -ETIMEDOUT;
}

/**
 * pvr_kccb_is_idle() - Returns whether the device's KCCB is idle
 * @pvr_dev: Device pointer
 *
 * Returns:
 *  * %true if the KCCB is idle (contains no commands), or
 *  * %false if the KCCB contains pending commands.
 */
bool
pvr_kccb_is_idle(struct pvr_device *pvr_dev)
{
	struct rogue_fwif_ccb_ctl *ctrl = pvr_dev->kccb.ccb.ctrl;
	bool idle;

	mutex_lock(&pvr_dev->kccb.ccb.lock);

	idle = (READ_ONCE(ctrl->write_offset) == READ_ONCE(ctrl->read_offset));

	mutex_unlock(&pvr_dev->kccb.ccb.lock);

	return idle;
}

static const char *
pvr_kccb_fence_get_driver_name(struct dma_fence *f)
{
	return PVR_DRIVER_NAME;
}

static const char *
pvr_kccb_fence_get_timeline_name(struct dma_fence *f)
{
	return "kccb";
}

static const struct dma_fence_ops pvr_kccb_fence_ops = {
	.get_driver_name = pvr_kccb_fence_get_driver_name,
	.get_timeline_name = pvr_kccb_fence_get_timeline_name,
};

/**
 * struct pvr_kccb_fence - Fence object used to wait for a KCCB slot
 */
struct pvr_kccb_fence {
	/** @base: Base dma_fence object. */
	struct dma_fence base;

	/** @node: Node used to insert the fence in the pvr_device::kccb::waiters list. */
	struct list_head node;
};

/**
 * pvr_kccb_wake_up_waiters() - Check the KCCB waiters
 * @pvr_dev: Target PowerVR device
 *
 * Signal as many KCCB fences as we have slots available.
 */
void pvr_kccb_wake_up_waiters(struct pvr_device *pvr_dev)
{
	struct pvr_kccb_fence *fence, *tmp_fence;
	u32 used_count, available_count;

	/* Wake up those waiting for KCCB slot execution. */
	wake_up_all(&pvr_dev->kccb.rtn_q);

	/* Then iterate over all KCCB fences and signal as many as we can. */
	mutex_lock(&pvr_dev->kccb.ccb.lock);
	used_count = pvr_kccb_used_slot_count_locked(pvr_dev);

	if (WARN_ON(used_count + pvr_dev->kccb.reserved_count > pvr_kccb_capacity(pvr_dev)))
		goto out_unlock;

	available_count = pvr_kccb_capacity(pvr_dev) - used_count - pvr_dev->kccb.reserved_count;
	list_for_each_entry_safe(fence, tmp_fence, &pvr_dev->kccb.waiters, node) {
		if (!available_count)
			break;

		list_del(&fence->node);
		pvr_dev->kccb.reserved_count++;
		available_count--;
		dma_fence_signal(&fence->base);
		dma_fence_put(&fence->base);
	}

out_unlock:
	mutex_unlock(&pvr_dev->kccb.ccb.lock);
}

/**
 * pvr_kccb_fini() - Cleanup device KCCB
 * @pvr_dev: Target PowerVR device
 */
void pvr_kccb_fini(struct pvr_device *pvr_dev)
{
	pvr_ccb_fini(&pvr_dev->kccb.ccb);
	WARN_ON(!list_empty(&pvr_dev->kccb.waiters));
	WARN_ON(pvr_dev->kccb.reserved_count);
}

/**
 * pvr_kccb_init() - Initialise device KCCB
 * @pvr_dev: Target PowerVR device
 *
 * Returns:
 *  * 0 on success, or
 *  * Any error returned by pvr_ccb_init().
 */
int
pvr_kccb_init(struct pvr_device *pvr_dev)
{
	pvr_dev->kccb.slot_count = 1 << ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT;
	INIT_LIST_HEAD(&pvr_dev->kccb.waiters);
	pvr_dev->kccb.fence_ctx.id = dma_fence_context_alloc(1);
	spin_lock_init(&pvr_dev->kccb.fence_ctx.lock);

	return pvr_ccb_init(pvr_dev, &pvr_dev->kccb.ccb,
			    ROGUE_FWIF_KCCB_NUMCMDS_LOG2_DEFAULT,
			    sizeof(struct rogue_fwif_kccb_cmd));
}

/**
 * pvr_kccb_fence_alloc() - Allocate a pvr_kccb_fence object
 *
 * Return:
 *  * NULL if the allocation fails, or
 *  * A valid dma_fence pointer otherwise.
 */
struct dma_fence *pvr_kccb_fence_alloc(void)
{
	struct pvr_kccb_fence *kccb_fence;

	kccb_fence = kzalloc(sizeof(*kccb_fence), GFP_KERNEL);
	if (!kccb_fence)
		return NULL;

	return &kccb_fence->base;
}

/**
 * pvr_kccb_fence_put() - Drop a KCCB fence reference
 * @fence: The fence to drop the reference on.
 *
 * If the fence hasn't been initialized yet, dma_fence_free() is called. This
 * way we have a single function taking care of both cases.
 */
void pvr_kccb_fence_put(struct dma_fence *fence)
{
	if (!fence)
		return;

	if (!fence->ops) {
		dma_fence_free(fence);
	} else {
		WARN_ON(fence->ops != &pvr_kccb_fence_ops);
		dma_fence_put(fence);
	}
}

/**
 * pvr_kccb_reserve_slot() - Reserve a KCCB slot for later use
 * @pvr_dev: Target PowerVR device
 * @f: KCCB fence object previously allocated with pvr_kccb_fence_alloc()
 *
 * Try to reserve a KCCB slot, and if there's no slot available,
 * initializes the fence object and queue it to the waiters list.
 *
 * If NULL is returned, that means the slot is reserved. In that case,
 * the @f is freed and shouldn't be accessed after that point.
 *
 * Return:
 *  * NULL if a slot was available directly, or
 *  * A valid dma_fence object to wait on if no slot was available.
 */
struct dma_fence *
pvr_kccb_reserve_slot(struct pvr_device *pvr_dev, struct dma_fence *f)
{
	struct pvr_kccb_fence *fence = container_of(f, struct pvr_kccb_fence, base);
	struct dma_fence *out_fence = NULL;
	u32 used_count;

	mutex_lock(&pvr_dev->kccb.ccb.lock);

	used_count = pvr_kccb_used_slot_count_locked(pvr_dev);
	if (pvr_dev->kccb.reserved_count >= pvr_kccb_capacity(pvr_dev) - used_count) {
		dma_fence_init(&fence->base, &pvr_kccb_fence_ops,
			       &pvr_dev->kccb.fence_ctx.lock,
			       pvr_dev->kccb.fence_ctx.id,
			       atomic_inc_return(&pvr_dev->kccb.fence_ctx.seqno));
		out_fence = dma_fence_get(&fence->base);
		list_add_tail(&fence->node, &pvr_dev->kccb.waiters);
	} else {
		pvr_kccb_fence_put(f);
		pvr_dev->kccb.reserved_count++;
	}

	mutex_unlock(&pvr_dev->kccb.ccb.lock);

	return out_fence;
}

/**
 * pvr_kccb_release_slot() - Release a KCCB slot reserved with
 * pvr_kccb_reserve_slot()
 * @pvr_dev: Target PowerVR device
 *
 * Should only be called if something failed after the
 * pvr_kccb_reserve_slot() call and you know you won't call
 * pvr_kccb_send_cmd_reserved().
 */
void pvr_kccb_release_slot(struct pvr_device *pvr_dev)
{
	mutex_lock(&pvr_dev->kccb.ccb.lock);
	if (!WARN_ON(!pvr_dev->kccb.reserved_count))
		pvr_dev->kccb.reserved_count--;
	mutex_unlock(&pvr_dev->kccb.ccb.lock);
}

/**
 * pvr_fwccb_init() - Initialise device FWCCB
 * @pvr_dev: Target PowerVR device
 *
 * Returns:
 *  * 0 on success, or
 *  * Any error returned by pvr_ccb_init().
 */
int
pvr_fwccb_init(struct pvr_device *pvr_dev)
{
	return pvr_ccb_init(pvr_dev, &pvr_dev->fwccb,
			    ROGUE_FWIF_FWCCB_NUMCMDS_LOG2,
			    sizeof(struct rogue_fwif_fwccb_cmd));
}
+71 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
/* Copyright (c) 2023 Imagination Technologies Ltd. */

#ifndef PVR_CCB_H
#define PVR_CCB_H

#include "pvr_rogue_fwif.h"

#include <linux/mutex.h>
#include <linux/types.h>

/* Forward declaration from pvr_device.h. */
struct pvr_device;

/* Forward declaration from pvr_gem.h. */
struct pvr_fw_object;

struct pvr_ccb {
	/** @ctrl_obj: FW object representing CCB control structure. */
	struct pvr_fw_object *ctrl_obj;
	/** @ccb_obj: FW object representing CCB. */
	struct pvr_fw_object *ccb_obj;

	/** @ctrl_fw_addr: FW virtual address of CCB control structure. */
	u32 ctrl_fw_addr;
	/** @ccb_fw_addr: FW virtual address of CCB. */
	u32 ccb_fw_addr;

	/** @num_cmds: Number of commands in this CCB. */
	u32 num_cmds;

	/** @cmd_size: Size of each command in this CCB, in bytes. */
	u32 cmd_size;

	/** @lock: Mutex protecting @ctrl and @ccb. */
	struct mutex lock;
	/**
	 * @ctrl: Kernel mapping of CCB control structure. @lock must be held
	 *        when accessing.
	 */
	struct rogue_fwif_ccb_ctl *ctrl;
	/** @ccb: Kernel mapping of CCB. @lock must be held when accessing. */
	void *ccb;
};

int pvr_kccb_init(struct pvr_device *pvr_dev);
void pvr_kccb_fini(struct pvr_device *pvr_dev);
int pvr_fwccb_init(struct pvr_device *pvr_dev);
void pvr_ccb_fini(struct pvr_ccb *ccb);

void pvr_fwccb_process(struct pvr_device *pvr_dev);

struct dma_fence *pvr_kccb_fence_alloc(void);
void pvr_kccb_fence_put(struct dma_fence *fence);
struct dma_fence *
pvr_kccb_reserve_slot(struct pvr_device *pvr_dev, struct dma_fence *f);
void pvr_kccb_release_slot(struct pvr_device *pvr_dev);
int pvr_kccb_send_cmd(struct pvr_device *pvr_dev,
		      struct rogue_fwif_kccb_cmd *cmd, u32 *kccb_slot);
int pvr_kccb_send_cmd_powered(struct pvr_device *pvr_dev,
			      struct rogue_fwif_kccb_cmd *cmd,
			      u32 *kccb_slot);
void pvr_kccb_send_cmd_reserved_powered(struct pvr_device *pvr_dev,
					struct rogue_fwif_kccb_cmd *cmd,
					u32 *kccb_slot);
int pvr_kccb_wait_for_completion(struct pvr_device *pvr_dev, u32 slot_nr, u32 timeout,
				 u32 *rtn_out);
bool pvr_kccb_is_idle(struct pvr_device *pvr_dev);
void pvr_kccb_wake_up_waiters(struct pvr_device *pvr_dev);

#endif /* PVR_CCB_H */
+103 −0
Original line number Diff line number Diff line
@@ -114,6 +114,87 @@ static int pvr_device_clk_init(struct pvr_device *pvr_dev)
	return 0;
}

static irqreturn_t pvr_device_irq_thread_handler(int irq, void *data)
{
	struct pvr_device *pvr_dev = data;
	irqreturn_t ret = IRQ_NONE;

	/* We are in the threaded handler, we can keep dequeuing events until we
	 * don't see any. This should allow us to reduce the number of interrupts
	 * when the GPU is receiving a massive amount of short jobs.
	 */
	while (pvr_fw_irq_pending(pvr_dev)) {
		pvr_fw_irq_clear(pvr_dev);

		if (pvr_dev->fw_dev.booted) {
			pvr_fwccb_process(pvr_dev);
			pvr_kccb_wake_up_waiters(pvr_dev);
		}

		pm_runtime_mark_last_busy(from_pvr_device(pvr_dev)->dev);

		ret = IRQ_HANDLED;
	}

	/* Unmask FW irqs before returning, so new interrupts can be received. */
	pvr_fw_irq_enable(pvr_dev);
	return ret;
}

static irqreturn_t pvr_device_irq_handler(int irq, void *data)
{
	struct pvr_device *pvr_dev = data;

	if (!pvr_fw_irq_pending(pvr_dev))
		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;
}

/**
 * pvr_device_irq_init() - Initialise IRQ required by a PowerVR device
 * @pvr_dev: Target PowerVR device.
 *
 * Returns:
 *  * 0 on success,
 *  * Any error returned by platform_get_irq_byname(), or
 *  * Any error returned by request_irq().
 */
static int
pvr_device_irq_init(struct pvr_device *pvr_dev)
{
	struct drm_device *drm_dev = from_pvr_device(pvr_dev);
	struct platform_device *plat_dev = to_platform_device(drm_dev->dev);

	init_waitqueue_head(&pvr_dev->kccb.rtn_q);

	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);

	return request_threaded_irq(pvr_dev->irq, pvr_device_irq_handler,
				    pvr_device_irq_thread_handler,
				    IRQF_SHARED, "gpu", pvr_dev);
}

/**
 * pvr_device_irq_fini() - Deinitialise IRQ required by a PowerVR device
 * @pvr_dev: Target PowerVR device.
 */
static void
pvr_device_irq_fini(struct pvr_device *pvr_dev)
{
	free_irq(pvr_dev->irq, pvr_dev);
}

/**
 * pvr_build_firmware_filename() - Construct a PowerVR firmware filename
 * @pvr_dev: Target PowerVR device.
@@ -324,7 +405,19 @@ pvr_device_gpu_init(struct pvr_device *pvr_dev)
			return PTR_ERR(pvr_dev->kernel_vm_ctx);
	}

	err = pvr_fw_init(pvr_dev);
	if (err)
		goto err_vm_ctx_put;

	return 0;

err_vm_ctx_put:
	if (pvr_dev->fw_dev.processor_type != PVR_FW_PROCESSOR_TYPE_MIPS) {
		pvr_vm_context_put(pvr_dev->kernel_vm_ctx);
		pvr_dev->kernel_vm_ctx = NULL;
	}

	return err;
}

/**
@@ -334,6 +427,8 @@ pvr_device_gpu_init(struct pvr_device *pvr_dev)
static void
pvr_device_gpu_fini(struct pvr_device *pvr_dev)
{
	pvr_fw_fini(pvr_dev);

	if (pvr_dev->fw_dev.processor_type != PVR_FW_PROCESSOR_TYPE_MIPS) {
		WARN_ON(!pvr_vm_context_put(pvr_dev->kernel_vm_ctx));
		pvr_dev->kernel_vm_ctx = NULL;
@@ -386,10 +481,17 @@ pvr_device_init(struct pvr_device *pvr_dev)
	if (err)
		goto err_pm_runtime_put;

	err = pvr_device_irq_init(pvr_dev);
	if (err)
		goto err_device_gpu_fini;

	pm_runtime_put(dev);

	return 0;

err_device_gpu_fini:
	pvr_device_gpu_fini(pvr_dev);

err_pm_runtime_put:
	pm_runtime_put_sync_suspend(dev);

@@ -407,6 +509,7 @@ pvr_device_fini(struct pvr_device *pvr_dev)
	 * Deinitialization stages are performed in reverse order compared to
	 * the initialization stages in pvr_device_init().
	 */
	pvr_device_irq_fini(pvr_dev);
	pvr_device_gpu_fini(pvr_dev);
}

+60 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
#ifndef PVR_DEVICE_H
#define PVR_DEVICE_H

#include "pvr_ccb.h"
#include "pvr_device_info.h"
#include "pvr_fw.h"

@@ -123,6 +124,12 @@ struct pvr_device {
	 */
	struct clk *mem_clk;

	/** @irq: IRQ number. */
	int irq;

	/** @fwccb: Firmware CCB. */
	struct pvr_ccb fwccb;

	/**
	 * @kernel_vm_ctx: Virtual memory context used for kernel mappings.
	 *
@@ -153,6 +160,49 @@ struct pvr_device {
		u32 kccb_stall_count;
	} watchdog;

	struct {
		/** @ccb: Kernel CCB. */
		struct pvr_ccb ccb;

		/** @rtn_q: Waitqueue for KCCB command return waiters. */
		wait_queue_head_t rtn_q;

		/** @rtn_obj: Object representing KCCB return slots. */
		struct pvr_fw_object *rtn_obj;

		/**
		 * @rtn: Pointer to CPU mapping of KCCB return slots. Must be accessed by
		 *       READ_ONCE()/WRITE_ONCE().
		 */
		u32 *rtn;

		/** @slot_count: Total number of KCCB slots available. */
		u32 slot_count;

		/** @reserved_count: Number of KCCB slots reserved for future use. */
		u32 reserved_count;

		/**
		 * @waiters: List of KCCB slot waiters.
		 */
		struct list_head waiters;

		/** @fence_ctx: KCCB fence context. */
		struct {
			/** @id: KCCB fence context ID allocated with dma_fence_context_alloc(). */
			u64 id;

			/** @seqno: Sequence number incremented each time a fence is created. */
			atomic_t seqno;

			/**
			 * @lock: Lock used to synchronize access to fences allocated by this
			 * context.
			 */
			spinlock_t lock;
		} fence_ctx;
	} kccb;

	/**
	 * @lost: %true if the device has been lost.
	 *
@@ -161,6 +211,16 @@ struct pvr_device {
	 */
	bool lost;

	/**
	 * @reset_sem: Reset semaphore.
	 *
	 * GPU reset code will lock this for writing. Any code that submits commands to the firmware
	 * that isn't in an IRQ handler or on the scheduler workqueue must lock this for reading.
	 * Once this has been successfully locked, &pvr_dev->lost _must_ be checked, and -%EIO must
	 * be returned if it is set.
	 */
	struct rw_semaphore reset_sem;

	/** @sched_wq: Workqueue for schedulers. */
	struct workqueue_struct *sched_wq;
};
Loading