// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright 2021 HabanaLabs, Ltd.
 * All Rights Reserved.
 */

#include <linux/vmalloc.h>
#include <uapi/drm/habanalabs_accel.h>
#include "habanalabs.h"

/**
 * hl_format_as_binary - helper function, format an integer as binary
 *                       using supplied scratch buffer
 * @buf: the buffer to use
 * @buf_len: buffer capacity
 * @n: number to format
 *
 * Returns pointer to buffer
 */
char *hl_format_as_binary(char *buf, size_t buf_len, u32 n)
{
	int i;
	u32 bit;
	bool leading0 = true;
	char *wrptr = buf;

	if (buf_len > 0 && buf_len < 3) {
		*wrptr = '\0';
		return buf;
	}

	wrptr[0] = '0';
	wrptr[1] = 'b';
	wrptr += 2;
	/* Remove 3 characters from length for '0b' and '\0' termination */
	buf_len -= 3;

	for (i = 0; i < sizeof(n) * BITS_PER_BYTE && buf_len; ++i, n <<= 1) {
		/* Writing bit calculation in one line would cause a false
		 * positive static code analysis error, so splitting.
		 */
		bit = n & (1 << (sizeof(n) * BITS_PER_BYTE - 1));
		bit = !!bit;
		leading0 &= !bit;
		if (!leading0) {
			*wrptr = '0' + bit;
			++wrptr;
		}
	}

	*wrptr = '\0';

	return buf;
}

/**
 * resize_to_fit - helper function, resize buffer to fit given amount of data
 * @buf: destination buffer double pointer
 * @size: pointer to the size container
 * @desired_size: size the buffer must contain
 *
 * Returns 0 on success or error code on failure.
 * On success, the size of buffer is at least desired_size. Buffer is allocated
 * via vmalloc and must be freed with vfree.
 */
static int resize_to_fit(char **buf, size_t *size, size_t desired_size)
{
	char *resized_buf;
	size_t new_size;

	if (*size >= desired_size)
		return 0;

	/* Not enough space to print all, have to resize */
	new_size = max_t(size_t, PAGE_SIZE, round_up(desired_size, PAGE_SIZE));
	resized_buf = vmalloc(new_size);
	if (!resized_buf)
		return -ENOMEM;
	memcpy(resized_buf, *buf, *size);
	vfree(*buf);
	*buf = resized_buf;
	*size = new_size;

	return 1;
}

/**
 * hl_snprintf_resize() - print formatted data to buffer, resize as needed
 * @buf: buffer double pointer, to be written to and resized, must be either
 *       NULL or allocated with vmalloc.
 * @size: current size of the buffer
 * @offset: current offset to write to
 * @format: format of the data
 *
 * This function will write formatted data into the buffer. If buffer is not
 * large enough, it will be resized using vmalloc. Size may be modified if the
 * buffer was resized, offset will be advanced by the number of bytes written
 * not including the terminating character
 *
 * Returns 0 on success or error code on failure
 *
 * Note that the buffer has to be manually released using vfree.
 */
int hl_snprintf_resize(char **buf, size_t *size, size_t *offset,
			   const char *format, ...)
{
	va_list args;
	size_t length;
	int rc;

	if (*buf == NULL && (*size != 0 || *offset != 0))
		return -EINVAL;

	va_start(args, format);
	length = vsnprintf(*buf + *offset, *size - *offset, format, args);
	va_end(args);

	rc = resize_to_fit(buf, size, *offset + length + 1);
	if (rc < 0)
		return rc;
	else if (rc > 0) {
		/* Resize was needed, write again */
		va_start(args, format);
		length = vsnprintf(*buf + *offset, *size - *offset, format,
				   args);
		va_end(args);
	}

	*offset += length;

	return 0;
}

/**
 * hl_sync_engine_to_string - convert engine type enum to string literal
 * @engine_type: engine type (TPC/MME/DMA)
 *
 * Return the resolved string literal
 */
const char *hl_sync_engine_to_string(enum hl_sync_engine_type engine_type)
{
	switch (engine_type) {
	case ENGINE_DMA:
		return "DMA";
	case ENGINE_MME:
		return "MME";
	case ENGINE_TPC:
		return "TPC";
	}
	return "Invalid Engine Type";
}

/**
 * hl_print_resize_sync_engine - helper function, format engine name and ID
 * using hl_snprintf_resize
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 * @engine_type: engine type (TPC/MME/DMA)
 * @engine_id: engine numerical id
 *
 * Returns 0 on success or error code on failure
 */
static int hl_print_resize_sync_engine(char **buf, size_t *size, size_t *offset,
				enum hl_sync_engine_type engine_type,
				u32 engine_id)
{
	return hl_snprintf_resize(buf, size, offset, "%s%u",
			hl_sync_engine_to_string(engine_type), engine_id);
}

/**
 * hl_state_dump_get_sync_name - transform sync object id to name if available
 * @hdev: pointer to the device
 * @sync_id: sync object id
 *
 * Returns a name literal or NULL if not resolved.
 * Note: returning NULL shall not be considered as a failure, as not all
 * sync objects are named.
 */
const char *hl_state_dump_get_sync_name(struct hl_device *hdev, u32 sync_id)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	struct hl_hw_obj_name_entry *entry;

	hash_for_each_possible(sds->so_id_to_str_tb, entry,
				node, sync_id)
		if (sync_id == entry->id)
			return entry->name;

	return NULL;
}

/**
 * hl_state_dump_get_monitor_name - transform monitor object dump to monitor
 * name if available
 * @hdev: pointer to the device
 * @mon: monitor state dump
 *
 * Returns a name literal or NULL if not resolved.
 * Note: returning NULL shall not be considered as a failure, as not all
 * monitors are named.
 */
const char *hl_state_dump_get_monitor_name(struct hl_device *hdev,
					struct hl_mon_state_dump *mon)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	struct hl_hw_obj_name_entry *entry;

	hash_for_each_possible(sds->monitor_id_to_str_tb,
				entry, node, mon->id)
		if (mon->id == entry->id)
			return entry->name;

	return NULL;
}

/**
 * hl_state_dump_free_sync_to_engine_map - free sync object to engine map
 * @map: sync object to engine map
 *
 * Note: generic free implementation, the allocation is implemented per ASIC.
 */
void hl_state_dump_free_sync_to_engine_map(struct hl_sync_to_engine_map *map)
{
	struct hl_sync_to_engine_map_entry *entry;
	struct hlist_node *tmp_node;
	int i;

	hash_for_each_safe(map->tb, i, tmp_node, entry, node) {
		hash_del(&entry->node);
		kfree(entry);
	}
}

/**
 * hl_state_dump_get_sync_to_engine - transform sync_id to
 * hl_sync_to_engine_map_entry if available for current id
 * @map: sync object to engine map
 * @sync_id: sync object id
 *
 * Returns the translation entry if found or NULL if not.
 * Note, returned NULL shall not be considered as a failure as the map
 * does not cover all possible, it is a best effort sync ids.
 */
static struct hl_sync_to_engine_map_entry *
hl_state_dump_get_sync_to_engine(struct hl_sync_to_engine_map *map, u32 sync_id)
{
	struct hl_sync_to_engine_map_entry *entry;

	hash_for_each_possible(map->tb, entry, node, sync_id)
		if (entry->sync_id == sync_id)
			return entry;
	return NULL;
}

/**
 * hl_state_dump_read_sync_objects - read sync objects array
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 *
 * Returns array of size SP_SYNC_OBJ_AMOUNT on success or NULL on failure
 */
static u32 *hl_state_dump_read_sync_objects(struct hl_device *hdev, u32 index)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	u32 *sync_objects;
	s64 base_addr; /* Base addr can be negative */
	int i;

	base_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
			sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;

	sync_objects = vmalloc(sds->props[SP_SYNC_OBJ_AMOUNT] * sizeof(u32));
	if (!sync_objects)
		return NULL;

	for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i)
		sync_objects[i] = RREG32(base_addr + i * sizeof(u32));

	return sync_objects;
}

/**
 * hl_state_dump_free_sync_objects - free sync objects array allocated by
 * hl_state_dump_read_sync_objects
 * @sync_objects: sync objects array
 */
static void hl_state_dump_free_sync_objects(u32 *sync_objects)
{
	vfree(sync_objects);
}


/**
 * hl_state_dump_print_syncs_single_block - print active sync objects on a
 * single block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 * @map: sync engines names map
 *
 * Returns 0 on success or error code on failure
 */
static int
hl_state_dump_print_syncs_single_block(struct hl_device *hdev, u32 index,
				char **buf, size_t *size, size_t *offset,
				struct hl_sync_to_engine_map *map)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	const char *sync_name;
	u32 *sync_objects = NULL;
	int rc = 0, i;

	if (sds->sync_namager_names) {
		rc = hl_snprintf_resize(
			buf, size, offset, "%s\n",
			sds->sync_namager_names[index]);
		if (rc)
			goto out;
	}

	sync_objects = hl_state_dump_read_sync_objects(hdev, index);
	if (!sync_objects) {
		rc = -ENOMEM;
		goto out;
	}

	for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i) {
		struct hl_sync_to_engine_map_entry *entry;
		u64 sync_object_addr;

		if (!sync_objects[i])
			continue;

		sync_object_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
				sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index +
				i * sizeof(u32);

		rc = hl_snprintf_resize(buf, size, offset, "sync id: %u", i);
		if (rc)
			goto free_sync_objects;
		sync_name = hl_state_dump_get_sync_name(hdev, i);
		if (sync_name) {
			rc = hl_snprintf_resize(buf, size, offset, " %s",
						sync_name);
			if (rc)
				goto free_sync_objects;
		}
		rc = hl_snprintf_resize(buf, size, offset, ", value: %u",
					sync_objects[i]);
		if (rc)
			goto free_sync_objects;

		/* Append engine string */
		entry = hl_state_dump_get_sync_to_engine(map,
			(u32)sync_object_addr);
		if (entry) {
			rc = hl_snprintf_resize(buf, size, offset,
						", Engine: ");
			if (rc)
				goto free_sync_objects;
			rc = hl_print_resize_sync_engine(buf, size, offset,
						entry->engine_type,
						entry->engine_id);
			if (rc)
				goto free_sync_objects;
		}

		rc = hl_snprintf_resize(buf, size, offset, "\n");
		if (rc)
			goto free_sync_objects;
	}

free_sync_objects:
	hl_state_dump_free_sync_objects(sync_objects);
out:
	return rc;
}

/**
 * hl_state_dump_print_syncs - print active sync objects
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_syncs(struct hl_device *hdev,
					char **buf, size_t *size,
					size_t *offset)

{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	struct hl_sync_to_engine_map *map;
	u32 index;
	int rc = 0;

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

	rc = sds->funcs.gen_sync_to_engine_map(hdev, map);
	if (rc)
		goto free_map_mem;

	rc = hl_snprintf_resize(buf, size, offset, "Non zero sync objects:\n");
	if (rc)
		goto out;

	if (sds->sync_namager_names) {
		for (index = 0; sds->sync_namager_names[index]; ++index) {
			rc = hl_state_dump_print_syncs_single_block(
				hdev, index, buf, size, offset, map);
			if (rc)
				goto out;
		}
	} else {
		for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
			rc = hl_state_dump_print_syncs_single_block(
				hdev, index, buf, size, offset, map);
			if (rc)
				goto out;
		}
	}

out:
	hl_state_dump_free_sync_to_engine_map(map);
free_map_mem:
	kfree(map);

	return rc;
}

/**
 * hl_state_dump_alloc_read_sm_block_monitors - read monitors for a specific
 * block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 *
 * Returns an array of monitor data of size SP_MONITORS_AMOUNT or NULL
 * on error
 */
static struct hl_mon_state_dump *
hl_state_dump_alloc_read_sm_block_monitors(struct hl_device *hdev, u32 index)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	struct hl_mon_state_dump *monitors;
	s64 base_addr; /* Base addr can be negative */
	int i;

	monitors = vmalloc(sds->props[SP_MONITORS_AMOUNT] *
			   sizeof(struct hl_mon_state_dump));
	if (!monitors)
		return NULL;

	base_addr = sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;

	for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
		monitors[i].id = i;
		monitors[i].wr_addr_low =
			RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_LOW] +
				i * sizeof(u32));

		monitors[i].wr_addr_high =
			RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_HIGH] +
				i * sizeof(u32));

		monitors[i].wr_data =
			RREG32(base_addr + sds->props[SP_MON_OBJ_WR_DATA] +
				i * sizeof(u32));

		monitors[i].arm_data =
			RREG32(base_addr + sds->props[SP_MON_OBJ_ARM_DATA] +
				i * sizeof(u32));

		monitors[i].status =
			RREG32(base_addr + sds->props[SP_MON_OBJ_STATUS] +
				i * sizeof(u32));
	}

	return monitors;
}

/**
 * hl_state_dump_free_monitors - free the monitors structure
 * @monitors: monitors array created with
 *            hl_state_dump_alloc_read_sm_block_monitors
 */
static void hl_state_dump_free_monitors(struct hl_mon_state_dump *monitors)
{
	vfree(monitors);
}

/**
 * hl_state_dump_print_monitors_single_block - print active monitors on a
 * single block
 * @hdev: pointer to the device
 * @index: sync manager block index starting with E_N
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_monitors_single_block(struct hl_device *hdev,
						u32 index,
						char **buf, size_t *size,
						size_t *offset)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	struct hl_mon_state_dump *monitors = NULL;
	int rc = 0, i;

	if (sds->sync_namager_names) {
		rc = hl_snprintf_resize(
			buf, size, offset, "%s\n",
			sds->sync_namager_names[index]);
		if (rc)
			goto out;
	}

	monitors = hl_state_dump_alloc_read_sm_block_monitors(hdev, index);
	if (!monitors) {
		rc = -ENOMEM;
		goto out;
	}

	for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
		if (!(sds->funcs.monitor_valid(&monitors[i])))
			continue;

		/* Monitor is valid, dump it */
		rc = sds->funcs.print_single_monitor(buf, size, offset, hdev,
							&monitors[i]);
		if (rc)
			goto free_monitors;

		hl_snprintf_resize(buf, size, offset, "\n");
	}

free_monitors:
	hl_state_dump_free_monitors(monitors);
out:
	return rc;
}

/**
 * hl_state_dump_print_monitors - print active monitors
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 *
 * Returns 0 on success or error code on failure
 */
static int hl_state_dump_print_monitors(struct hl_device *hdev,
					char **buf, size_t *size,
					size_t *offset)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	u32 index;
	int rc = 0;

	rc = hl_snprintf_resize(buf, size, offset,
		"Valid (armed) monitor objects:\n");
	if (rc)
		goto out;

	if (sds->sync_namager_names) {
		for (index = 0; sds->sync_namager_names[index]; ++index) {
			rc = hl_state_dump_print_monitors_single_block(
				hdev, index, buf, size, offset);
			if (rc)
				goto out;
		}
	} else {
		for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
			rc = hl_state_dump_print_monitors_single_block(
				hdev, index, buf, size, offset);
			if (rc)
				goto out;
		}
	}

out:
	return rc;
}

/**
 * hl_state_dump_print_engine_fences - print active fences for a specific
 * engine
 * @hdev: pointer to the device
 * @engine_type: engine type to use
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 */
static int
hl_state_dump_print_engine_fences(struct hl_device *hdev,
				  enum hl_sync_engine_type engine_type,
				  char **buf, size_t *size, size_t *offset)
{
	struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
	int rc = 0, i, n_fences;
	u64 base_addr, next_fence;

	switch (engine_type) {
	case ENGINE_TPC:
		n_fences = sds->props[SP_NUM_OF_TPC_ENGINES];
		base_addr = sds->props[SP_TPC0_CMDQ];
		next_fence = sds->props[SP_NEXT_TPC];
		break;
	case ENGINE_MME:
		n_fences = sds->props[SP_NUM_OF_MME_ENGINES];
		base_addr = sds->props[SP_MME_CMDQ];
		next_fence = sds->props[SP_NEXT_MME];
		break;
	case ENGINE_DMA:
		n_fences = sds->props[SP_NUM_OF_DMA_ENGINES];
		base_addr = sds->props[SP_DMA_CMDQ];
		next_fence = sds->props[SP_DMA_QUEUES_OFFSET];
		break;
	default:
		return -EINVAL;
	}
	for (i = 0; i < n_fences; ++i) {
		rc = sds->funcs.print_fences_single_engine(
			hdev,
			base_addr + next_fence * i +
				sds->props[SP_FENCE0_CNT_OFFSET],
			base_addr + next_fence * i +
				sds->props[SP_CP_STS_OFFSET],
			engine_type, i, buf, size, offset);
		if (rc)
			goto out;
	}
out:
	return rc;
}

/**
 * hl_state_dump_print_fences - print active fences
 * @hdev: pointer to the device
 * @buf: destination buffer double pointer to be used with hl_snprintf_resize
 * @size: pointer to the size container
 * @offset: pointer to the offset container
 */
static int hl_state_dump_print_fences(struct hl_device *hdev, char **buf,
				      size_t *size, size_t *offset)
{
	int rc = 0;

	rc = hl_snprintf_resize(buf, size, offset, "Valid (armed) fences:\n");
	if (rc)
		goto out;

	rc = hl_state_dump_print_engine_fences(hdev, ENGINE_TPC, buf, size, offset);
	if (rc)
		goto out;

	rc = hl_state_dump_print_engine_fences(hdev, ENGINE_MME, buf, size, offset);
	if (rc)
		goto out;

	rc = hl_state_dump_print_engine_fences(hdev, ENGINE_DMA, buf, size, offset);
	if (rc)
		goto out;

out:
	return rc;
}

/**
 * hl_state_dump() - dump system state
 * @hdev: pointer to device structure
 */
int hl_state_dump(struct hl_device *hdev)
{
	char *buf = NULL;
	size_t offset = 0, size = 0;
	int rc;

	rc = hl_snprintf_resize(&buf, &size, &offset,
				"Timestamp taken on: %llu\n\n",
				ktime_to_ns(ktime_get()));
	if (rc)
		goto err;

	rc = hl_state_dump_print_syncs(hdev, &buf, &size, &offset);
	if (rc)
		goto err;

	hl_snprintf_resize(&buf, &size, &offset, "\n");

	rc = hl_state_dump_print_monitors(hdev, &buf, &size, &offset);
	if (rc)
		goto err;

	hl_snprintf_resize(&buf, &size, &offset, "\n");

	rc = hl_state_dump_print_fences(hdev, &buf, &size, &offset);
	if (rc)
		goto err;

	hl_snprintf_resize(&buf, &size, &offset, "\n");

	hl_debugfs_set_state_dump(hdev, buf, size);

	return 0;
err:
	vfree(buf);
	return rc;
}