// SPDX-License-Identifier: MIT
/*
 * Copyright © 2025 Intel Corporation
 */

#include <drm/drm_managed.h>

#include "regs/xe_gtt_defs.h"

#include "xe_assert.h"
#include "xe_ggtt.h"
#include "xe_sriov.h"
#include "xe_sriov_printk.h"
#include "xe_tile_sriov_vf.h"
#include "xe_wopcm.h"

static int vf_init_ggtt_balloons(struct xe_tile *tile)
{
	struct xe_ggtt *ggtt = tile->mem.ggtt;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	tile->sriov.vf.ggtt_balloon[0] = xe_ggtt_node_init(ggtt);
	if (IS_ERR(tile->sriov.vf.ggtt_balloon[0]))
		return PTR_ERR(tile->sriov.vf.ggtt_balloon[0]);

	tile->sriov.vf.ggtt_balloon[1] = xe_ggtt_node_init(ggtt);
	if (IS_ERR(tile->sriov.vf.ggtt_balloon[1])) {
		xe_ggtt_node_fini(tile->sriov.vf.ggtt_balloon[0]);
		return PTR_ERR(tile->sriov.vf.ggtt_balloon[1]);
	}

	return 0;
}

/**
 * xe_tile_sriov_vf_balloon_ggtt_locked - Insert balloon nodes to limit used GGTT address range.
 * @tile: the &xe_tile struct instance
 *
 * Return: 0 on success or a negative error code on failure.
 */
static int xe_tile_sriov_vf_balloon_ggtt_locked(struct xe_tile *tile)
{
	u64 ggtt_base = tile->sriov.vf.self_config.ggtt_base;
	u64 ggtt_size = tile->sriov.vf.self_config.ggtt_size;
	struct xe_device *xe = tile_to_xe(tile);
	u64 wopcm = xe_wopcm_size(xe);
	u64 start, end;
	int err;

	xe_tile_assert(tile, IS_SRIOV_VF(xe));
	xe_tile_assert(tile, ggtt_size);
	lockdep_assert_held(&tile->mem.ggtt->lock);

	/*
	 * VF can only use part of the GGTT as allocated by the PF:
	 *
	 *      WOPCM                                  GUC_GGTT_TOP
	 *      |<------------ Total GGTT size ------------------>|
	 *
	 *           VF GGTT base -->|<- size ->|
	 *
	 *      +--------------------+----------+-----------------+
	 *      |////////////////////|   block  |\\\\\\\\\\\\\\\\\|
	 *      +--------------------+----------+-----------------+
	 *
	 *      |<--- balloon[0] --->|<-- VF -->|<-- balloon[1] ->|
	 */

	if (ggtt_base < wopcm || ggtt_base > GUC_GGTT_TOP ||
	    ggtt_size > GUC_GGTT_TOP - ggtt_base) {
		xe_sriov_err(xe, "tile%u: Invalid GGTT configuration: %#llx-%#llx\n",
			     tile->id, ggtt_base, ggtt_base + ggtt_size - 1);
		return -ERANGE;
	}

	start = wopcm;
	end = ggtt_base;
	if (end != start) {
		err = xe_ggtt_node_insert_balloon_locked(tile->sriov.vf.ggtt_balloon[0],
							 start, end);
		if (err)
			return err;
	}

	start = ggtt_base + ggtt_size;
	end = GUC_GGTT_TOP;
	if (end != start) {
		err = xe_ggtt_node_insert_balloon_locked(tile->sriov.vf.ggtt_balloon[1],
							 start, end);
		if (err) {
			xe_ggtt_node_remove_balloon_locked(tile->sriov.vf.ggtt_balloon[0]);
			return err;
		}
	}

	return 0;
}

static int vf_balloon_ggtt(struct xe_tile *tile)
{
	struct xe_ggtt *ggtt = tile->mem.ggtt;
	int err;

	mutex_lock(&ggtt->lock);
	err = xe_tile_sriov_vf_balloon_ggtt_locked(tile);
	mutex_unlock(&ggtt->lock);

	return err;
}

/**
 * xe_tile_sriov_vf_deballoon_ggtt_locked - Remove balloon nodes.
 * @tile: the &xe_tile struct instance
 */
void xe_tile_sriov_vf_deballoon_ggtt_locked(struct xe_tile *tile)
{
	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	xe_ggtt_node_remove_balloon_locked(tile->sriov.vf.ggtt_balloon[1]);
	xe_ggtt_node_remove_balloon_locked(tile->sriov.vf.ggtt_balloon[0]);
}

static void vf_deballoon_ggtt(struct xe_tile *tile)
{
	mutex_lock(&tile->mem.ggtt->lock);
	xe_tile_sriov_vf_deballoon_ggtt_locked(tile);
	mutex_unlock(&tile->mem.ggtt->lock);
}

static void vf_fini_ggtt_balloons(struct xe_tile *tile)
{
	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	xe_ggtt_node_fini(tile->sriov.vf.ggtt_balloon[1]);
	xe_ggtt_node_fini(tile->sriov.vf.ggtt_balloon[0]);
}

static void cleanup_ggtt(struct drm_device *drm, void *arg)
{
	struct xe_tile *tile = arg;

	vf_deballoon_ggtt(tile);
	vf_fini_ggtt_balloons(tile);
}

/**
 * xe_tile_sriov_vf_prepare_ggtt - Prepare a VF's GGTT configuration.
 * @tile: the &xe_tile
 *
 * This function is for VF use only.
 *
 * Return: 0 on success or a negative error code on failure.
 */
int xe_tile_sriov_vf_prepare_ggtt(struct xe_tile *tile)
{
	struct xe_device *xe = tile_to_xe(tile);
	int err;

	err = vf_init_ggtt_balloons(tile);
	if (err)
		return err;

	err = vf_balloon_ggtt(tile);
	if (err) {
		vf_fini_ggtt_balloons(tile);
		return err;
	}

	return drmm_add_action_or_reset(&xe->drm, cleanup_ggtt, tile);
}

/**
 * DOC: GGTT nodes shifting during VF post-migration recovery
 *
 * The first fixup applied to the VF KMD structures as part of post-migration
 * recovery is shifting nodes within &xe_ggtt instance. The nodes are moved
 * from range previously assigned to this VF, into newly provisioned area.
 * The changes include balloons, which are resized accordingly.
 *
 * The balloon nodes are there to eliminate unavailable ranges from use: one
 * reserves the GGTT area below the range for current VF, and another one
 * reserves area above.
 *
 * Below is a GGTT layout of example VF, with a certain address range assigned to
 * said VF, and inaccessible areas above and below:
 *
 *  0                                                                        4GiB
 *  |<--------------------------- Total GGTT size ----------------------------->|
 *      WOPCM                                                         GUC_TOP
 *      |<-------------- Area mappable by xe_ggtt instance ---------------->|
 *
 *  +---+---------------------------------+----------+----------------------+---+
 *  |\\\|/////////////////////////////////|  VF mem  |//////////////////////|\\\|
 *  +---+---------------------------------+----------+----------------------+---+
 *
 * Hardware enforced access rules before migration:
 *
 *  |<------- inaccessible for VF ------->|<VF owned>|<-- inaccessible for VF ->|
 *
 * GGTT nodes used for tracking allocations:
 *
 *      |<---------- balloon ------------>|<- nodes->|<----- balloon ------>|
 *
 * After the migration, GGTT area assigned to the VF might have shifted, either
 * to lower or to higher address. But we expect the total size and extra areas to
 * be identical, as migration can only happen between matching platforms.
 * Below is an example of GGTT layout of the VF after migration. Content of the
 * GGTT for VF has been moved to a new area, and we receive its address from GuC:
 *
 *  +---+----------------------+----------+---------------------------------+---+
 *  |\\\|//////////////////////|  VF mem  |/////////////////////////////////|\\\|
 *  +---+----------------------+----------+---------------------------------+---+
 *
 * Hardware enforced access rules after migration:
 *
 *  |<- inaccessible for VF -->|<VF owned>|<------- inaccessible for VF ------->|
 *
 * So the VF has a new slice of GGTT assigned, and during migration process, the
 * memory content was copied to that new area. But the &xe_ggtt nodes are still
 * tracking allocations using the old addresses. The nodes within VF owned area
 * have to be shifted, and balloon nodes need to be resized to properly mask out
 * areas not owned by the VF.
 *
 * Fixed &xe_ggtt nodes used for tracking allocations:
 *
 *     |<------ balloon ------>|<- nodes->|<----------- balloon ----------->|
 *
 * Due to use of GPU profiles, we do not expect the old and new GGTT ares to
 * overlap; but our node shifting will fix addresses properly regardless.
 */

/**
 * xe_tile_sriov_vf_fixup_ggtt_nodes_locked - Shift GGTT allocations to match assigned range.
 * @tile: the &xe_tile struct instance
 * @shift: the shift value
 *
 * Since Global GTT is not virtualized, each VF has an assigned range
 * within the global space. This range might have changed during migration,
 * which requires all memory addresses pointing to GGTT to be shifted.
 */
void xe_tile_sriov_vf_fixup_ggtt_nodes_locked(struct xe_tile *tile, s64 shift)
{
	struct xe_ggtt *ggtt = tile->mem.ggtt;

	lockdep_assert_held(&ggtt->lock);

	xe_tile_sriov_vf_deballoon_ggtt_locked(tile);
	xe_ggtt_shift_nodes_locked(ggtt, shift);
	xe_tile_sriov_vf_balloon_ggtt_locked(tile);
}

/**
 * xe_tile_sriov_vf_lmem - VF LMEM configuration.
 * @tile: the &xe_tile
 *
 * This function is for VF use only.
 *
 * Return: size of the LMEM assigned to VF.
 */
u64 xe_tile_sriov_vf_lmem(struct xe_tile *tile)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	return config->lmem_size;
}

/**
 * xe_tile_sriov_vf_lmem_store - Store VF LMEM configuration
 * @tile: the &xe_tile
 * @lmem_size: VF LMEM size to store
 *
 * This function is for VF use only.
 */
void xe_tile_sriov_vf_lmem_store(struct xe_tile *tile, u64 lmem_size)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	config->lmem_size = lmem_size;
}

/**
 * xe_tile_sriov_vf_ggtt - VF GGTT configuration.
 * @tile: the &xe_tile
 *
 * This function is for VF use only.
 *
 * Return: size of the GGTT assigned to VF.
 */
u64 xe_tile_sriov_vf_ggtt(struct xe_tile *tile)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	return config->ggtt_size;
}

/**
 * xe_tile_sriov_vf_ggtt_store - Store VF GGTT configuration
 * @tile: the &xe_tile
 * @ggtt_size: VF GGTT size to store
 *
 * This function is for VF use only.
 */
void xe_tile_sriov_vf_ggtt_store(struct xe_tile *tile, u64 ggtt_size)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	config->ggtt_size = ggtt_size;
}

/**
 * xe_tile_sriov_vf_ggtt_base - VF GGTT base configuration.
 * @tile: the &xe_tile
 *
 * This function is for VF use only.
 *
 * Return: base of the GGTT assigned to VF.
 */
u64 xe_tile_sriov_vf_ggtt_base(struct xe_tile *tile)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	return config->ggtt_base;
}

/**
 * xe_tile_sriov_vf_ggtt_base_store - Store VF GGTT base configuration
 * @tile: the &xe_tile
 * @ggtt_base: VF GGTT base to store
 *
 * This function is for VF use only.
 */
void xe_tile_sriov_vf_ggtt_base_store(struct xe_tile *tile, u64 ggtt_base)
{
	struct xe_tile_sriov_vf_selfconfig *config = &tile->sriov.vf.self_config;

	xe_tile_assert(tile, IS_SRIOV_VF(tile_to_xe(tile)));

	config->ggtt_base = ggtt_base;
}
