/*
 *
 * (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU licence.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 * SPDX-License-Identifier: GPL-2.0
 *
 */

#include "mali_kbase_hwcnt_legacy.h"
#include "mali_kbase_hwcnt_virtualizer.h"
#include "mali_kbase_hwcnt_types.h"
#include "mali_kbase_hwcnt_gpu.h"
#include "mali_kbase_ioctl.h"

#include <linux/slab.h>
#include <linux/uaccess.h>

/**
 * struct kbase_hwcnt_legacy_client - Legacy hardware counter client.
 * @user_dump_buf: Pointer to a non-NULL user buffer, where dumps are returned.
 * @enable_map:    Counter enable map.
 * @dump_buf:      Dump buffer used to manipulate dumps before copied to user.
 * @hvcli:         Hardware counter virtualizer client.
 */
struct kbase_hwcnt_legacy_client {
	void __user *user_dump_buf;
	struct kbase_hwcnt_enable_map enable_map;
	struct kbase_hwcnt_dump_buffer dump_buf;
	struct kbase_hwcnt_virtualizer_client *hvcli;
};

int kbase_hwcnt_legacy_client_create(
	struct kbase_hwcnt_virtualizer *hvirt,
	struct kbase_ioctl_hwcnt_enable *enable,
	struct kbase_hwcnt_legacy_client **out_hlcli)
{
	int errcode;
	struct kbase_hwcnt_legacy_client *hlcli;
	const struct kbase_hwcnt_metadata *metadata;
	struct kbase_hwcnt_physical_enable_map phys_em;

	if (!hvirt || !enable || !enable->dump_buffer || !out_hlcli)
		return -EINVAL;

	metadata = kbase_hwcnt_virtualizer_metadata(hvirt);

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

	hlcli->user_dump_buf = (void __user *)(uintptr_t)enable->dump_buffer;

	errcode = kbase_hwcnt_enable_map_alloc(metadata, &hlcli->enable_map);
	if (errcode)
		goto error;

	/* Translate from the ioctl enable map to the internal one */
	phys_em.jm_bm = enable->jm_bm;
	phys_em.shader_bm = enable->shader_bm;
	phys_em.tiler_bm = enable->tiler_bm;
	phys_em.mmu_l2_bm = enable->mmu_l2_bm;
	kbase_hwcnt_gpu_enable_map_from_physical(&hlcli->enable_map, &phys_em);

	errcode = kbase_hwcnt_dump_buffer_alloc(metadata, &hlcli->dump_buf);
	if (errcode)
		goto error;

	errcode = kbase_hwcnt_virtualizer_client_create(
		hvirt, &hlcli->enable_map, &hlcli->hvcli);
	if (errcode)
		goto error;

	*out_hlcli = hlcli;
	return 0;

error:
	kbase_hwcnt_legacy_client_destroy(hlcli);
	return errcode;
}

void kbase_hwcnt_legacy_client_destroy(struct kbase_hwcnt_legacy_client *hlcli)
{
	if (!hlcli)
		return;

	kbase_hwcnt_virtualizer_client_destroy(hlcli->hvcli);
	kbase_hwcnt_dump_buffer_free(&hlcli->dump_buf);
	kbase_hwcnt_enable_map_free(&hlcli->enable_map);
	kfree(hlcli);
}

int kbase_hwcnt_legacy_client_dump(struct kbase_hwcnt_legacy_client *hlcli)
{
	int errcode;
	u64 ts_start_ns;
	u64 ts_end_ns;

	if (!hlcli)
		return -EINVAL;

	/* Dump into the kernel buffer */
	errcode = kbase_hwcnt_virtualizer_client_dump(hlcli->hvcli,
		&ts_start_ns, &ts_end_ns, &hlcli->dump_buf);
	if (errcode)
		return errcode;

	/* Patch the dump buf headers, to hide the counters that other hwcnt
	 * clients are using.
	 */
	kbase_hwcnt_gpu_patch_dump_headers(
		&hlcli->dump_buf, &hlcli->enable_map);

	/* Zero all non-enabled counters (current values are undefined) */
	kbase_hwcnt_dump_buffer_zero_non_enabled(
		&hlcli->dump_buf, &hlcli->enable_map);

	/* Copy into the user's buffer */
	errcode = copy_to_user(hlcli->user_dump_buf, hlcli->dump_buf.dump_buf,
		hlcli->dump_buf.metadata->dump_buf_bytes);
	/* Non-zero errcode implies user buf was invalid or too small */
	if (errcode)
		return -EFAULT;

	return 0;
}

int kbase_hwcnt_legacy_client_clear(struct kbase_hwcnt_legacy_client *hlcli)
{
	u64 ts_start_ns;
	u64 ts_end_ns;

	if (!hlcli)
		return -EINVAL;

	/* Dump with a NULL buffer to clear this client's counters */
	return kbase_hwcnt_virtualizer_client_dump(hlcli->hvcli,
		&ts_start_ns, &ts_end_ns, NULL);
}
