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

drm/imagination: Implement context creation/destruction ioctls



Implement ioctls for the creation and destruction of contexts. Contexts are
used for job submission and each is associated with a particular job type.

Changes since v8:
- Fixed one error path in pvr_stream_process_1()
- Corrected license identifiers

Changes since v5:
- Fix context release in final error path in pvr_context_create()

Changes since v3:
- Use drm_dev_{enter,exit}

Co-developed-by: default avatarBoris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: default avatarBoris Brezillon <boris.brezillon@collabora.com>
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/ac474a1f7dda2582d290798e4837140a2989aa2a.1700668843.git.donald.robson@imgtec.com


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

powervr-y := \
	pvr_ccb.o \
	pvr_cccb.o \
	pvr_context.o \
	pvr_device.o \
	pvr_device_info.o \
	pvr_drv.o \
@@ -18,6 +20,8 @@ powervr-y := \
	pvr_hwrt.o \
	pvr_mmu.o \
	pvr_power.o \
	pvr_stream.o \
	pvr_stream_defs.o \
	pvr_vm.o \
	pvr_vm_mips.o

+267 −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_cccb.h"
#include "pvr_device.h"
#include "pvr_gem.h"
#include "pvr_hwrt.h"

#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/types.h>

static __always_inline u32
get_ccb_space(u32 w_off, u32 r_off, u32 ccb_size)
{
	return (((r_off) - (w_off)) + ((ccb_size) - 1)) & ((ccb_size) - 1);
}

static void
cccb_ctrl_init(void *cpu_ptr, void *priv)
{
	struct rogue_fwif_cccb_ctl *ctrl = cpu_ptr;
	struct pvr_cccb *pvr_cccb = priv;

	WRITE_ONCE(ctrl->write_offset, 0);
	WRITE_ONCE(ctrl->read_offset, 0);
	WRITE_ONCE(ctrl->dep_offset, 0);
	WRITE_ONCE(ctrl->wrap_mask, pvr_cccb->wrap_mask);
}

/**
 * pvr_cccb_init() - Initialise a Client CCB
 * @pvr_dev: Device pointer.
 * @pvr_cccb: Pointer to Client CCB structure to initialise.
 * @size_log2: Log2 size of Client CCB in bytes.
 * @name: Name of owner of Client CCB. Used for fence context.
 *
 * Return:
 *  * Zero on success, or
 *  * Any error code returned by pvr_fw_object_create_and_map().
 */
int
pvr_cccb_init(struct pvr_device *pvr_dev, struct pvr_cccb *pvr_cccb,
	      u32 size_log2, const char *name)
{
	size_t size = 1 << size_log2;
	int err;

	pvr_cccb->size = size;
	pvr_cccb->write_offset = 0;
	pvr_cccb->wrap_mask = size - 1;

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

	pvr_cccb->cccb = pvr_fw_object_create_and_map(pvr_dev, size,
						      PVR_BO_FW_FLAGS_DEVICE_UNCACHED,
						      NULL, NULL, &pvr_cccb->cccb_obj);
	if (IS_ERR(pvr_cccb->cccb)) {
		err = PTR_ERR(pvr_cccb->cccb);
		goto err_free_ctrl;
	}

	pvr_fw_object_get_fw_addr(pvr_cccb->ctrl_obj, &pvr_cccb->ctrl_fw_addr);
	pvr_fw_object_get_fw_addr(pvr_cccb->cccb_obj, &pvr_cccb->cccb_fw_addr);

	return 0;

err_free_ctrl:
	pvr_fw_object_unmap_and_destroy(pvr_cccb->ctrl_obj);

	return err;
}

/**
 * pvr_cccb_fini() - Release Client CCB structure
 * @pvr_cccb: Client CCB to release.
 */
void
pvr_cccb_fini(struct pvr_cccb *pvr_cccb)
{
	pvr_fw_object_unmap_and_destroy(pvr_cccb->cccb_obj);
	pvr_fw_object_unmap_and_destroy(pvr_cccb->ctrl_obj);
}

/**
 * pvr_cccb_cmdseq_fits() - Check if a command sequence fits in the CCCB
 * @pvr_cccb: Target Client CCB.
 * @size: Size of the command sequence.
 *
 * Check if a command sequence fits in the CCCB we have at hand.
 *
 * Return:
 *  * true if the command sequence fits in the CCCB, or
 *  * false otherwise.
 */
bool pvr_cccb_cmdseq_fits(struct pvr_cccb *pvr_cccb, size_t size)
{
	struct rogue_fwif_cccb_ctl *ctrl = pvr_cccb->ctrl;
	u32 read_offset, remaining;
	bool fits = false;

	read_offset = READ_ONCE(ctrl->read_offset);
	remaining = pvr_cccb->size - pvr_cccb->write_offset;

	/* Always ensure we have enough room for a padding command at the end of the CCCB.
	 * If our command sequence does not fit, reserve the remaining space for a padding
	 * command.
	 */
	if (size + PADDING_COMMAND_SIZE > remaining)
		size += remaining;

	if (get_ccb_space(pvr_cccb->write_offset, read_offset, pvr_cccb->size) >= size)
		fits = true;

	return fits;
}

/**
 * pvr_cccb_write_command_with_header() - Write a command + command header to a
 *                                        Client CCB
 * @pvr_cccb: Target Client CCB.
 * @cmd_type: Client CCB command type. Must be one of %ROGUE_FWIF_CCB_CMD_TYPE_*.
 * @cmd_size: Size of command in bytes.
 * @cmd_data: Pointer to command to write.
 * @ext_job_ref: External job reference.
 * @int_job_ref: Internal job reference.
 *
 * Caller must make sure there's enough space in CCCB to queue this command. This
 * can be done by calling pvr_cccb_cmdseq_fits().
 *
 * This function is not protected by any lock. The caller must ensure there's
 * no concurrent caller, which should be guaranteed by the drm_sched model (job
 * submission is serialized in drm_sched_main()).
 */
void
pvr_cccb_write_command_with_header(struct pvr_cccb *pvr_cccb, u32 cmd_type, u32 cmd_size,
				   void *cmd_data, u32 ext_job_ref, u32 int_job_ref)
{
	u32 sz_with_hdr = pvr_cccb_get_size_of_cmd_with_hdr(cmd_size);
	struct rogue_fwif_ccb_cmd_header cmd_header = {
		.cmd_type = cmd_type,
		.cmd_size = ALIGN(cmd_size, 8),
		.ext_job_ref = ext_job_ref,
		.int_job_ref = int_job_ref,
	};
	struct rogue_fwif_cccb_ctl *ctrl = pvr_cccb->ctrl;
	u32 remaining = pvr_cccb->size - pvr_cccb->write_offset;
	u32 required_size, cccb_space, read_offset;

	/*
	 * Always ensure we have enough room for a padding command at the end of
	 * the CCCB.
	 */
	if (remaining < sz_with_hdr + PADDING_COMMAND_SIZE) {
		/*
		 * Command would need to wrap, so we need to pad the remainder
		 * of the CCCB.
		 */
		required_size = sz_with_hdr + remaining;
	} else {
		required_size = sz_with_hdr;
	}

	read_offset = READ_ONCE(ctrl->read_offset);
	cccb_space = get_ccb_space(pvr_cccb->write_offset, read_offset, pvr_cccb->size);
	if (WARN_ON(cccb_space < required_size))
		return;

	if (required_size != sz_with_hdr) {
		/* Add padding command */
		struct rogue_fwif_ccb_cmd_header pad_cmd = {
			.cmd_type = ROGUE_FWIF_CCB_CMD_TYPE_PADDING,
			.cmd_size = remaining - sizeof(pad_cmd),
		};

		memcpy(&pvr_cccb->cccb[pvr_cccb->write_offset], &pad_cmd, sizeof(pad_cmd));
		pvr_cccb->write_offset = 0;
	}

	memcpy(&pvr_cccb->cccb[pvr_cccb->write_offset], &cmd_header, sizeof(cmd_header));
	memcpy(&pvr_cccb->cccb[pvr_cccb->write_offset + sizeof(cmd_header)], cmd_data, cmd_size);
	pvr_cccb->write_offset += sz_with_hdr;
}

static void fill_cmd_kick_data(struct pvr_cccb *cccb, u32 ctx_fw_addr,
			       struct pvr_hwrt_data *hwrt,
			       struct rogue_fwif_kccb_cmd_kick_data *k)
{
	k->context_fw_addr = ctx_fw_addr;
	k->client_woff_update = cccb->write_offset;
	k->client_wrap_mask_update = cccb->wrap_mask;

	if (hwrt) {
		u32 cleanup_state_offset = offsetof(struct rogue_fwif_hwrtdata, cleanup_state);

		pvr_fw_object_get_fw_addr_offset(hwrt->fw_obj, cleanup_state_offset,
						 &k->cleanup_ctl_fw_addr[k->num_cleanup_ctl++]);
	}
}

/**
 * pvr_cccb_send_kccb_kick: Send KCCB kick to trigger command processing
 * @pvr_dev: Device pointer.
 * @pvr_cccb: Pointer to CCCB to process.
 * @cctx_fw_addr: FW virtual address for context owning this Client CCB.
 * @hwrt: HWRT data set associated with this kick. May be %NULL.
 *
 * You must call pvr_kccb_reserve_slot() and wait for the returned fence to
 * signal (if this function didn't return NULL) before calling
 * pvr_cccb_send_kccb_kick().
 */
void
pvr_cccb_send_kccb_kick(struct pvr_device *pvr_dev,
			struct pvr_cccb *pvr_cccb, u32 cctx_fw_addr,
			struct pvr_hwrt_data *hwrt)
{
	struct rogue_fwif_kccb_cmd cmd_kick = {
		.cmd_type = ROGUE_FWIF_KCCB_CMD_KICK,
	};

	fill_cmd_kick_data(pvr_cccb, cctx_fw_addr, hwrt, &cmd_kick.cmd_data.cmd_kick_data);

	/* Make sure the writes to the CCCB are flushed before sending the KICK. */
	wmb();

	pvr_kccb_send_cmd_reserved_powered(pvr_dev, &cmd_kick, NULL);
}

void
pvr_cccb_send_kccb_combined_kick(struct pvr_device *pvr_dev,
				 struct pvr_cccb *geom_cccb,
				 struct pvr_cccb *frag_cccb,
				 u32 geom_ctx_fw_addr,
				 u32 frag_ctx_fw_addr,
				 struct pvr_hwrt_data *hwrt,
				 bool frag_is_pr)
{
	struct rogue_fwif_kccb_cmd cmd_kick = {
		.cmd_type = ROGUE_FWIF_KCCB_CMD_COMBINED_GEOM_FRAG_KICK,
	};

	fill_cmd_kick_data(geom_cccb, geom_ctx_fw_addr, hwrt,
			   &cmd_kick.cmd_data.combined_geom_frag_cmd_kick_data.geom_cmd_kick_data);

	/* If this is a partial-render job, we don't attach resources to cleanup-ctl array,
	 * because the resources are already retained by the geometry job.
	 */
	fill_cmd_kick_data(frag_cccb, frag_ctx_fw_addr, frag_is_pr ? NULL : hwrt,
			   &cmd_kick.cmd_data.combined_geom_frag_cmd_kick_data.frag_cmd_kick_data);

	/* Make sure the writes to the CCCB are flushed before sending the KICK. */
	wmb();

	pvr_kccb_send_cmd_reserved_powered(pvr_dev, &cmd_kick, NULL);
}
+109 −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_CCCB_H
#define PVR_CCCB_H

#include "pvr_rogue_fwif.h"
#include "pvr_rogue_fwif_shared.h"

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

#define PADDING_COMMAND_SIZE sizeof(struct rogue_fwif_ccb_cmd_header)

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

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

/* Forward declaration from pvr_hwrt.h. */
struct pvr_hwrt_data;

struct pvr_cccb {
	/** @ctrl_obj: FW object representing CCCB control structure. */
	struct pvr_fw_object *ctrl_obj;

	/** @ccb_obj: FW object representing CCCB. */
	struct pvr_fw_object *cccb_obj;

	/**
	 * @ctrl: Kernel mapping of CCCB control structure. @lock must be held
	 *        when accessing.
	 */
	struct rogue_fwif_cccb_ctl *ctrl;

	/** @cccb: Kernel mapping of CCCB. @lock must be held when accessing.*/
	u8 *cccb;

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

	/** @size: Size of CCCB in bytes. */
	size_t size;

	/** @write_offset: CCCB write offset. */
	u32 write_offset;

	/** @wrap_mask: CCCB wrap mask. */
	u32 wrap_mask;
};

int pvr_cccb_init(struct pvr_device *pvr_dev, struct pvr_cccb *cccb,
		  u32 size_log2, const char *name);
void pvr_cccb_fini(struct pvr_cccb *cccb);

void pvr_cccb_write_command_with_header(struct pvr_cccb *pvr_cccb,
					u32 cmd_type, u32 cmd_size, void *cmd_data,
					u32 ext_job_ref, u32 int_job_ref);
void pvr_cccb_send_kccb_kick(struct pvr_device *pvr_dev,
			     struct pvr_cccb *pvr_cccb, u32 cctx_fw_addr,
			     struct pvr_hwrt_data *hwrt);
void pvr_cccb_send_kccb_combined_kick(struct pvr_device *pvr_dev,
				      struct pvr_cccb *geom_cccb,
				      struct pvr_cccb *frag_cccb,
				      u32 geom_ctx_fw_addr,
				      u32 frag_ctx_fw_addr,
				      struct pvr_hwrt_data *hwrt,
				      bool frag_is_pr);
bool pvr_cccb_cmdseq_fits(struct pvr_cccb *pvr_cccb, size_t size);

/**
 * pvr_cccb_get_size_of_cmd_with_hdr() - Get the size of a command and its header.
 * @cmd_size: Command size.
 *
 * Returns the size of the command and its header.
 */
static __always_inline u32
pvr_cccb_get_size_of_cmd_with_hdr(u32 cmd_size)
{
	WARN_ON(!IS_ALIGNED(cmd_size, 8));
	return sizeof(struct rogue_fwif_ccb_cmd_header) + ALIGN(cmd_size, 8);
}

/**
 * pvr_cccb_cmdseq_can_fit() - Check if a command sequence can fit in the CCCB.
 * @size: Command sequence size.
 *
 * Returns:
 *  * true it the CCCB is big enough to contain a command sequence, or
 *  * false otherwise.
 */
static __always_inline bool
pvr_cccb_cmdseq_can_fit(struct pvr_cccb *pvr_cccb, size_t size)
{
	/* We divide the capacity by two to simplify our CCCB fencing logic:
	 * we want to be sure that, no matter what we had queued before, we
	 * are able to either queue our command sequence at the end or add a
	 * padding command and queue the command sequence at the beginning
	 * of the CCCB. If the command sequence size is bigger than half the
	 * CCCB capacity, we'd have to queue the padding command and make sure
	 * the FW is done processing it before queueing our command sequence.
	 */
	return size + PADDING_COMMAND_SIZE <= pvr_cccb->size / 2;
}

#endif /* PVR_CCCB_H */
+341 −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_cccb.h"
#include "pvr_context.h"
#include "pvr_device.h"
#include "pvr_drv.h"
#include "pvr_gem.h"
#include "pvr_power.h"
#include "pvr_rogue_fwif.h"
#include "pvr_rogue_fwif_common.h"
#include "pvr_rogue_fwif_resetframework.h"
#include "pvr_stream_defs.h"
#include "pvr_vm.h"

#include <drm/drm_auth.h>
#include <drm/drm_managed.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/xarray.h>

static int
remap_priority(struct pvr_file *pvr_file, s32 uapi_priority,
	       enum pvr_context_priority *priority_out)
{
	switch (uapi_priority) {
	case DRM_PVR_CTX_PRIORITY_LOW:
		*priority_out = PVR_CTX_PRIORITY_LOW;
		break;
	case DRM_PVR_CTX_PRIORITY_NORMAL:
		*priority_out = PVR_CTX_PRIORITY_MEDIUM;
		break;
	case DRM_PVR_CTX_PRIORITY_HIGH:
		if (!capable(CAP_SYS_NICE) && !drm_is_current_master(from_pvr_file(pvr_file)))
			return -EACCES;
		*priority_out = PVR_CTX_PRIORITY_HIGH;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int get_fw_obj_size(enum drm_pvr_ctx_type type)
{
	switch (type) {
	case DRM_PVR_CTX_TYPE_RENDER:
		return sizeof(struct rogue_fwif_fwrendercontext);
	case DRM_PVR_CTX_TYPE_COMPUTE:
		return sizeof(struct rogue_fwif_fwcomputecontext);
	case DRM_PVR_CTX_TYPE_TRANSFER_FRAG:
		return sizeof(struct rogue_fwif_fwtransfercontext);
	}

	return -EINVAL;
}

static int
process_static_context_state(struct pvr_device *pvr_dev, const struct pvr_stream_cmd_defs *cmd_defs,
			     u64 stream_user_ptr, u32 stream_size, void *dest)
{
	void *stream;
	int err;

	stream = kzalloc(stream_size, GFP_KERNEL);
	if (!stream)
		return -ENOMEM;

	if (copy_from_user(stream, u64_to_user_ptr(stream_user_ptr), stream_size)) {
		err = -EFAULT;
		goto err_free;
	}

	err = pvr_stream_process(pvr_dev, cmd_defs, stream, stream_size, dest);
	if (err)
		goto err_free;

	kfree(stream);

	return 0;

err_free:
	kfree(stream);

	return err;
}

static int init_render_fw_objs(struct pvr_context *ctx,
			       struct drm_pvr_ioctl_create_context_args *args,
			       void *fw_ctx_map)
{
	struct rogue_fwif_static_rendercontext_state *static_rendercontext_state;
	struct rogue_fwif_fwrendercontext *fw_render_context = fw_ctx_map;

	if (!args->static_context_state_len)
		return -EINVAL;

	static_rendercontext_state = &fw_render_context->static_render_context_state;

	/* Copy static render context state from userspace. */
	return process_static_context_state(ctx->pvr_dev,
					    &pvr_static_render_context_state_stream,
					    args->static_context_state,
					    args->static_context_state_len,
					    &static_rendercontext_state->ctxswitch_regs[0]);
}

static int init_compute_fw_objs(struct pvr_context *ctx,
				struct drm_pvr_ioctl_create_context_args *args,
				void *fw_ctx_map)
{
	struct rogue_fwif_fwcomputecontext *fw_compute_context = fw_ctx_map;
	struct rogue_fwif_cdm_registers_cswitch *ctxswitch_regs;

	if (!args->static_context_state_len)
		return -EINVAL;

	ctxswitch_regs = &fw_compute_context->static_compute_context_state.ctxswitch_regs;

	/* Copy static render context state from userspace. */
	return process_static_context_state(ctx->pvr_dev,
					    &pvr_static_compute_context_state_stream,
					    args->static_context_state,
					    args->static_context_state_len,
					    ctxswitch_regs);
}

static int init_transfer_fw_objs(struct pvr_context *ctx,
				 struct drm_pvr_ioctl_create_context_args *args,
				 void *fw_ctx_map)
{
	if (args->static_context_state_len)
		return -EINVAL;

	return 0;
}

static int init_fw_objs(struct pvr_context *ctx,
			struct drm_pvr_ioctl_create_context_args *args,
			void *fw_ctx_map)
{
	switch (ctx->type) {
	case DRM_PVR_CTX_TYPE_RENDER:
		return init_render_fw_objs(ctx, args, fw_ctx_map);
	case DRM_PVR_CTX_TYPE_COMPUTE:
		return init_compute_fw_objs(ctx, args, fw_ctx_map);
	case DRM_PVR_CTX_TYPE_TRANSFER_FRAG:
		return init_transfer_fw_objs(ctx, args, fw_ctx_map);
	}

	return -EINVAL;
}

static void
ctx_fw_data_init(void *cpu_ptr, void *priv)
{
	struct pvr_context *ctx = priv;

	memcpy(cpu_ptr, ctx->data, ctx->data_size);
}

/**
 * pvr_context_create() - Create a context.
 * @pvr_file: File to attach the created context to.
 * @args: Context creation arguments.
 *
 * Return:
 *  * 0 on success, or
 *  * A negative error code on failure.
 */
int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_context_args *args)
{
	struct pvr_device *pvr_dev = pvr_file->pvr_dev;
	struct pvr_context *ctx;
	int ctx_size;
	int err;

	/* Context creation flags are currently unused and must be zero. */
	if (args->flags)
		return -EINVAL;

	ctx_size = get_fw_obj_size(args->type);
	if (ctx_size < 0)
		return ctx_size;

	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return -ENOMEM;

	ctx->data_size = ctx_size;
	ctx->type = args->type;
	ctx->flags = args->flags;
	ctx->pvr_dev = pvr_dev;
	kref_init(&ctx->ref_count);

	err = remap_priority(pvr_file, args->priority, &ctx->priority);
	if (err)
		goto err_free_ctx;

	ctx->vm_ctx = pvr_vm_context_lookup(pvr_file, args->vm_context_handle);
	if (IS_ERR(ctx->vm_ctx)) {
		err = PTR_ERR(ctx->vm_ctx);
		goto err_free_ctx;
	}

	ctx->data = kzalloc(ctx_size, GFP_KERNEL);
	if (!ctx->data) {
		err = -ENOMEM;
		goto err_put_vm;
	}

	err = init_fw_objs(ctx, args, ctx->data);
	if (err)
		goto err_free_ctx_data;

	err = pvr_fw_object_create(pvr_dev, ctx_size, PVR_BO_FW_FLAGS_DEVICE_UNCACHED,
				   ctx_fw_data_init, ctx, &ctx->fw_obj);
	if (err)
		goto err_free_ctx_data;

	err = xa_alloc(&pvr_dev->ctx_ids, &ctx->ctx_id, ctx, xa_limit_32b, GFP_KERNEL);
	if (err)
		goto err_destroy_fw_obj;

	err = xa_alloc(&pvr_file->ctx_handles, &args->handle, ctx, xa_limit_32b, GFP_KERNEL);
	if (err) {
		/*
		 * It's possible that another thread could have taken a reference on the context at
		 * this point as it is in the ctx_ids xarray. Therefore instead of directly
		 * destroying the context, drop a reference instead.
		 */
		pvr_context_put(ctx);
		return err;
	}

	return 0;

err_destroy_fw_obj:
	pvr_fw_object_destroy(ctx->fw_obj);

err_free_ctx_data:
	kfree(ctx->data);

err_put_vm:
	pvr_vm_context_put(ctx->vm_ctx);

err_free_ctx:
	kfree(ctx);
	return err;
}

static void
pvr_context_release(struct kref *ref_count)
{
	struct pvr_context *ctx =
		container_of(ref_count, struct pvr_context, ref_count);
	struct pvr_device *pvr_dev = ctx->pvr_dev;

	xa_erase(&pvr_dev->ctx_ids, ctx->ctx_id);
	pvr_fw_object_destroy(ctx->fw_obj);
	kfree(ctx->data);
	pvr_vm_context_put(ctx->vm_ctx);
	kfree(ctx);
}

/**
 * pvr_context_put() - Release reference on context
 * @ctx: Target context.
 */
void
pvr_context_put(struct pvr_context *ctx)
{
	if (ctx)
		kref_put(&ctx->ref_count, pvr_context_release);
}

/**
 * pvr_context_destroy() - Destroy context
 * @pvr_file: Pointer to pvr_file structure.
 * @handle: Userspace context handle.
 *
 * Removes context from context list and drops initial reference. Context will
 * then be destroyed once all outstanding references are dropped.
 *
 * Return:
 *  * 0 on success, or
 *  * -%EINVAL if context not in context list.
 */
int
pvr_context_destroy(struct pvr_file *pvr_file, u32 handle)
{
	struct pvr_context *ctx = xa_erase(&pvr_file->ctx_handles, handle);

	if (!ctx)
		return -EINVAL;

	/* Release the reference held by the handle set. */
	pvr_context_put(ctx);

	return 0;
}

/**
 * pvr_destroy_contexts_for_file: Destroy any contexts associated with the given file
 * @pvr_file: Pointer to pvr_file structure.
 *
 * Removes all contexts associated with @pvr_file from the device context list and drops initial
 * references. Contexts will then be destroyed once all outstanding references are dropped.
 */
void pvr_destroy_contexts_for_file(struct pvr_file *pvr_file)
{
	struct pvr_context *ctx;
	unsigned long handle;

	xa_for_each(&pvr_file->ctx_handles, handle, ctx)
		pvr_context_destroy(pvr_file, handle);
}

/**
 * pvr_context_device_init() - Device level initialization for queue related resources.
 * @pvr_dev: The device to initialize.
 */
void pvr_context_device_init(struct pvr_device *pvr_dev)
{
	xa_init_flags(&pvr_dev->ctx_ids, XA_FLAGS_ALLOC1);
}

/**
 * pvr_context_device_fini() - Device level cleanup for queue related resources.
 * @pvr_dev: The device to cleanup.
 */
void pvr_context_device_fini(struct pvr_device *pvr_dev)
{
	WARN_ON(!xa_empty(&pvr_dev->ctx_ids));
	xa_destroy(&pvr_dev->ctx_ids);
}
+161 −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_CONTEXT_H
#define PVR_CONTEXT_H

#include <drm/gpu_scheduler.h>

#include <linux/compiler_attributes.h>
#include <linux/dma-fence.h>
#include <linux/kref.h>
#include <linux/types.h>
#include <linux/xarray.h>
#include <uapi/drm/pvr_drm.h>

#include "pvr_cccb.h"
#include "pvr_device.h"

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

enum pvr_context_priority {
	PVR_CTX_PRIORITY_LOW = 0,
	PVR_CTX_PRIORITY_MEDIUM,
	PVR_CTX_PRIORITY_HIGH,
};

/**
 * struct pvr_context - Context data
 */
struct pvr_context {
	/** @ref_count: Refcount for context. */
	struct kref ref_count;

	/** @pvr_dev: Pointer to owning device. */
	struct pvr_device *pvr_dev;

	/** @vm_ctx: Pointer to associated VM context. */
	struct pvr_vm_context *vm_ctx;

	/** @type: Type of context. */
	enum drm_pvr_ctx_type type;

	/** @flags: Context flags. */
	u32 flags;

	/** @priority: Context priority*/
	enum pvr_context_priority priority;

	/** @fw_obj: FW object representing FW-side context data. */
	struct pvr_fw_object *fw_obj;

	/** @data: Pointer to local copy of FW context data. */
	void *data;

	/** @data_size: Size of FW context data, in bytes. */
	u32 data_size;

	/** @ctx_id: FW context ID. */
	u32 ctx_id;
};

/**
 * pvr_context_get() - Take additional reference on context.
 * @ctx: Context pointer.
 *
 * Call pvr_context_put() to release.
 *
 * Returns:
 *  * The requested context on success, or
 *  * %NULL if no context pointer passed.
 */
static __always_inline struct pvr_context *
pvr_context_get(struct pvr_context *ctx)
{
	if (ctx)
		kref_get(&ctx->ref_count);

	return ctx;
}

/**
 * pvr_context_lookup() - Lookup context pointer from handle and file.
 * @pvr_file: Pointer to pvr_file structure.
 * @handle: Context handle.
 *
 * Takes reference on context. Call pvr_context_put() to release.
 *
 * Return:
 *  * The requested context on success, or
 *  * %NULL on failure (context does not exist, or does not belong to @pvr_file).
 */
static __always_inline struct pvr_context *
pvr_context_lookup(struct pvr_file *pvr_file, u32 handle)
{
	struct pvr_context *ctx;

	/* Take the array lock to protect against context removal.  */
	xa_lock(&pvr_file->ctx_handles);
	ctx = pvr_context_get(xa_load(&pvr_file->ctx_handles, handle));
	xa_unlock(&pvr_file->ctx_handles);

	return ctx;
}

/**
 * pvr_context_lookup_id() - Lookup context pointer from ID.
 * @pvr_dev: Device pointer.
 * @id: FW context ID.
 *
 * Takes reference on context. Call pvr_context_put() to release.
 *
 * Return:
 *  * The requested context on success, or
 *  * %NULL on failure (context does not exist).
 */
static __always_inline struct pvr_context *
pvr_context_lookup_id(struct pvr_device *pvr_dev, u32 id)
{
	struct pvr_context *ctx;

	/* Take the array lock to protect against context removal.  */
	xa_lock(&pvr_dev->ctx_ids);

	/* Contexts are removed from the ctx_ids set in the context release path,
	 * meaning the ref_count reached zero before they get removed. We need
	 * to make sure we're not trying to acquire a context that's being
	 * destroyed.
	 */
	ctx = xa_load(&pvr_dev->ctx_ids, id);
	if (!kref_get_unless_zero(&ctx->ref_count))
		ctx = NULL;

	xa_unlock(&pvr_dev->ctx_ids);

	return ctx;
}

static __always_inline u32
pvr_context_get_fw_addr(struct pvr_context *ctx)
{
	u32 ctx_fw_addr = 0;

	pvr_fw_object_get_fw_addr(ctx->fw_obj, &ctx_fw_addr);

	return ctx_fw_addr;
}

void pvr_context_put(struct pvr_context *ctx);

int pvr_context_create(struct pvr_file *pvr_file, struct drm_pvr_ioctl_create_context_args *args);

int pvr_context_destroy(struct pvr_file *pvr_file, u32 handle);

void pvr_destroy_contexts_for_file(struct pvr_file *pvr_file);

void pvr_context_device_init(struct pvr_device *pvr_dev);

void pvr_context_device_fini(struct pvr_device *pvr_dev);

#endif /* PVR_CONTEXT_H */
Loading