// SPDX-License-Identifier: GPL-2.0
/*
 *
 * (C) COPYRIGHT 2019-2021 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_csf_tl_reader.h"

#include "mali_kbase_csf_trace_buffer.h"
#include "mali_kbase_reset_gpu.h"

#include "tl/mali_kbase_tlstream.h"
#include "tl/mali_kbase_tl_serialize.h"
#include "tl/mali_kbase_tracepoints.h"

#include "mali_kbase_pm.h"
#include "mali_kbase_hwaccess_time.h"

#include <linux/gcd.h>
#include <linux/math64.h>
#include <asm/arch_timer.h>

#ifdef CONFIG_DEBUG_FS
#include "tl/mali_kbase_timeline_priv.h"
#include <linux/debugfs.h>

#if (KERNEL_VERSION(4, 7, 0) > LINUX_VERSION_CODE)
#define DEFINE_DEBUGFS_ATTRIBUTE DEFINE_SIMPLE_ATTRIBUTE
#endif
#endif

/* Name of the CSFFW timeline tracebuffer. */
#define KBASE_CSFFW_TRACEBUFFER_NAME "timeline"
/* Name of the timeline header metatadata */
#define KBASE_CSFFW_TIMELINE_HEADER_NAME "timeline_header"

/**
 * struct kbase_csffw_tl_message - CSFFW timeline message.
 *
 * @msg_id: Message ID.
 * @timestamp: Timestamp of the event.
 * @cycle_counter: Cycle number of the event.
 *
 * Contain fields that are common for all CSFFW timeline messages.
 */
struct kbase_csffw_tl_message {
	u32 msg_id;
	u64 timestamp;
	u64 cycle_counter;
} __packed __aligned(4);

#ifdef CONFIG_DEBUG_FS
static int kbase_csf_tl_debugfs_poll_interval_read(void *data, u64 *val)
{
	struct kbase_device *kbdev = (struct kbase_device *)data;
	struct kbase_csf_tl_reader *self = &kbdev->timeline->csf_tl_reader;

	*val = self->timer_interval;

	return 0;
}

static int kbase_csf_tl_debugfs_poll_interval_write(void *data, u64 val)
{
	struct kbase_device *kbdev = (struct kbase_device *)data;
	struct kbase_csf_tl_reader *self = &kbdev->timeline->csf_tl_reader;

	if (val > KBASE_CSF_TL_READ_INTERVAL_MAX || val < KBASE_CSF_TL_READ_INTERVAL_MIN) {
		return -EINVAL;
	}

	self->timer_interval = (u32)val;

	return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(kbase_csf_tl_poll_interval_fops,
		kbase_csf_tl_debugfs_poll_interval_read,
		kbase_csf_tl_debugfs_poll_interval_write, "%llu\n");

void kbase_csf_tl_reader_debugfs_init(struct kbase_device *kbdev)
{
	debugfs_create_file("csf_tl_poll_interval_in_ms", S_IRUGO | S_IWUSR,
		kbdev->debugfs_instr_directory, kbdev,
		&kbase_csf_tl_poll_interval_fops);
}
#endif

/**
 * get_cpu_gpu_time() - Get current CPU and GPU timestamps.
 *
 * @kbdev:	Kbase device.
 * @cpu_ts:	Output CPU timestamp.
 * @gpu_ts:	Output GPU timestamp.
 * @gpu_cycle:  Output GPU cycle counts.
 */
static void get_cpu_gpu_time(
	struct kbase_device *kbdev,
	u64 *cpu_ts,
	u64 *gpu_ts,
	u64 *gpu_cycle)
{
	struct timespec64 ts;

	kbase_pm_context_active(kbdev);
	kbase_backend_get_gpu_time(kbdev, gpu_cycle, gpu_ts, &ts);
	kbase_pm_context_idle(kbdev);

	if (cpu_ts)
		*cpu_ts = ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec;
}

/**
 * kbase_ts_converter_init() - Initialize system timestamp converter.
 *
 * @self:	System Timestamp Converter instance.
 * @kbdev:	Kbase device pointer
 *
 * Return: Zero on success, -1 otherwise.
 */
static int kbase_ts_converter_init(
	struct kbase_ts_converter *self,
	struct kbase_device *kbdev)
{
	u64 cpu_ts = 0;
	u64 gpu_ts = 0;
	u64 freq;
	u64 common_factor;

	get_cpu_gpu_time(kbdev, &cpu_ts, &gpu_ts, NULL);
	freq = arch_timer_get_cntfrq();

	if (!freq) {
		dev_warn(kbdev->dev, "arch_timer_get_rate() is zero!");
		return -1;
	}

	common_factor = gcd(NSEC_PER_SEC, freq);

	self->multiplier = div64_u64(NSEC_PER_SEC, common_factor);
	self->divisor = div64_u64(freq, common_factor);
	self->offset =
		cpu_ts - div64_u64(gpu_ts * self->multiplier, self->divisor);

	return 0;
}

/**
 * kbase_ts_converter_convert() - Convert GPU timestamp to CPU timestamp.
 *
 * @self:	System Timestamp Converter instance.
 * @gpu_ts:	System timestamp value to converter.
 *
 * Return: The CPU timestamp.
 */
void kbase_ts_converter_convert(
	const struct kbase_ts_converter *self,
	u64 *gpu_ts)
{
	u64 old_gpu_ts = *gpu_ts;
	*gpu_ts = div64_u64(old_gpu_ts * self->multiplier,
		self->divisor) + self->offset;
}

/**
 * tl_reader_overflow_notify() - Emit stream overflow tracepoint.
 *
 * @self:		CSFFW TL Reader instance.
 * @msg_buf_start:	Start of the message.
 * @msg_buf_end:	End of the message buffer.
 */
static void tl_reader_overflow_notify(
	const struct kbase_csf_tl_reader *self,
	u8 *const msg_buf_start,
	u8 *const msg_buf_end)
{
	struct kbase_device *kbdev = self->kbdev;
	struct kbase_csffw_tl_message message = {0};

	/* Reuse the timestamp and cycle count from current event if possible */
	if (msg_buf_start + sizeof(message) <= msg_buf_end)
		memcpy(&message, msg_buf_start, sizeof(message));

	KBASE_TLSTREAM_TL_KBASE_CSFFW_TLSTREAM_OVERFLOW(
		kbdev, message.timestamp, message.cycle_counter);
}

/**
 * tl_reader_overflow_check() - Check if an overflow has happened
 *
 * @self:	CSFFW TL Reader instance.
 * @event_id:	Incoming event id.
 *
 * Return: True, if an overflow has happened, False otherwise.
 */
static bool tl_reader_overflow_check(
	struct kbase_csf_tl_reader *self,
	u16 event_id)
{
	struct kbase_device *kbdev = self->kbdev;
	bool has_overflow = false;

	/* 0 is a special event_id and reserved for the very first tracepoint
	 * after reset, we should skip overflow check when reset happened.
	 */
	if (event_id != 0) {
		has_overflow = self->got_first_event
			&& self->expected_event_id != event_id;

		if (has_overflow)
			dev_warn(kbdev->dev,
				"CSFFW overflow, event_id: %u, expected: %u.",
				event_id, self->expected_event_id);
	}

	self->got_first_event = true;
	self->expected_event_id = event_id + 1;
	/* When event_id reaches its max value, it skips 0 and wraps to 1. */
	if (self->expected_event_id == 0)
		self->expected_event_id++;

	return has_overflow;
}

/**
 * tl_reader_reset() - Reset timeline tracebuffer reader state machine.
 *
 * @self:	CSFFW TL Reader instance.
 *
 * Reset the reader to the default state, i.e. set all the
 * mutable fields to zero.
 */
static void tl_reader_reset(struct kbase_csf_tl_reader *self)
{
	self->got_first_event = false;
	self->is_active = false;
	self->expected_event_id = 0;
	self->tl_header.btc = 0;
}

int kbase_csf_tl_reader_flush_buffer(struct kbase_csf_tl_reader *self)
{
	int ret = 0;
	struct kbase_device *kbdev = self->kbdev;
	struct kbase_tlstream *stream = self->stream;

	u8  *read_buffer = self->read_buffer;
	const size_t read_buffer_size = sizeof(self->read_buffer);

	u32 bytes_read;
	u8 *csffw_data_begin;
	u8 *csffw_data_end;
	u8 *csffw_data_it;

	unsigned long flags;

	spin_lock_irqsave(&self->read_lock, flags);

	/* If not running, early exit. */
	if (!self->is_active) {
		spin_unlock_irqrestore(&self->read_lock, flags);
		return -EBUSY;
	}

	/* Copying the whole buffer in a single shot. We assume
	 * that the buffer will not contain partially written messages.
	 */
	bytes_read = kbase_csf_firmware_trace_buffer_read_data(
		self->trace_buffer, read_buffer, read_buffer_size);
	csffw_data_begin = read_buffer;
	csffw_data_end   = read_buffer + bytes_read;

	for (csffw_data_it = csffw_data_begin;
	     csffw_data_it < csffw_data_end;) {
		u32 event_header;
		u16 event_id;
		u16 event_size;
		unsigned long acq_flags;
		char *buffer;

		/* Can we safely read event_id? */
		if (csffw_data_it + sizeof(event_header) > csffw_data_end) {
			dev_warn(
				kbdev->dev,
				"Unable to parse CSFFW tracebuffer event header.");
				ret = -EBUSY;
			break;
		}

		/* Read and parse the event header. */
		memcpy(&event_header, csffw_data_it, sizeof(event_header));
		event_id   = (event_header >> 0)  & 0xFFFF;
		event_size = (event_header >> 16) & 0xFFFF;
		csffw_data_it += sizeof(event_header);

		/* Detect if an overflow has happened. */
		if (tl_reader_overflow_check(self, event_id))
			tl_reader_overflow_notify(self,
				csffw_data_it,
				csffw_data_end);

		/* Can we safely read the message body? */
		if (csffw_data_it + event_size > csffw_data_end) {
			dev_warn(kbdev->dev,
				"event_id: %u, can't read with event_size: %u.",
				event_id, event_size);
				ret = -EBUSY;
			break;
		}

		/* Convert GPU timestamp to CPU timestamp. */
		{
			struct kbase_csffw_tl_message *msg =
				(struct kbase_csffw_tl_message *) csffw_data_it;
			kbase_ts_converter_convert(
				&self->ts_converter,
				&msg->timestamp);
		}

		/* Copy the message out to the tl_stream. */
		buffer = kbase_tlstream_msgbuf_acquire(
			stream, event_size, &acq_flags);
		kbasep_serialize_bytes(buffer, 0, csffw_data_it, event_size);
		kbase_tlstream_msgbuf_release(stream, acq_flags);
		csffw_data_it += event_size;
	}

	spin_unlock_irqrestore(&self->read_lock, flags);
	return ret;
}

static void kbasep_csf_tl_reader_read_callback(struct timer_list *timer)
{
	struct kbase_csf_tl_reader *self =
		container_of(timer, struct kbase_csf_tl_reader, read_timer);

	int rcode;

	kbase_csf_tl_reader_flush_buffer(self);

	rcode = mod_timer(&self->read_timer,
		jiffies + msecs_to_jiffies(self->timer_interval));

	CSTD_UNUSED(rcode);
}

/**
 * tl_reader_init_late() - Late CSFFW TL Reader initialization.
 *
 * @self:	CSFFW TL Reader instance.
 * @kbdev:	Kbase device.
 *
 * Late initialization is done once at kbase_csf_tl_reader_start() time.
 * This is because the firmware image is not parsed
 * by the kbase_csf_tl_reader_init() time.
 *
 * Return: Zero on success, -1 otherwise.
 */
static int tl_reader_init_late(
	struct kbase_csf_tl_reader *self,
	struct kbase_device *kbdev)
{
	struct firmware_trace_buffer *tb;
	size_t hdr_size = 0;
	const char *hdr = NULL;

	if (self->kbdev)
		return 0;

	tb = kbase_csf_firmware_get_trace_buffer(
		kbdev, KBASE_CSFFW_TRACEBUFFER_NAME);
	hdr = kbase_csf_firmware_get_timeline_metadata(
		kbdev, KBASE_CSFFW_TIMELINE_HEADER_NAME, &hdr_size);

	if (!tb) {
		dev_warn(
			kbdev->dev,
			"'%s' tracebuffer is not present in the firmware image.",
			KBASE_CSFFW_TRACEBUFFER_NAME);
		return -1;
	}

	if (!hdr) {
		dev_warn(
			kbdev->dev,
			"'%s' timeline metadata is not present in the firmware image.",
			KBASE_CSFFW_TIMELINE_HEADER_NAME);
		return -1;
	}

	if (kbase_ts_converter_init(&self->ts_converter, kbdev)) {
		return -1;
	}

	self->kbdev = kbdev;
	self->trace_buffer = tb;
	self->tl_header.data = hdr;
	self->tl_header.size = hdr_size;

	return 0;
}

/**
 * tl_reader_update_enable_bit() - Update the first bit of a CSFFW tracebuffer.
 *
 * @self:	CSFFW TL Reader instance.
 * @value:	The value to set.
 *
 * Update the first bit of a CSFFW tracebufer and then reset the GPU.
 * This is to make these changes visible to the MCU.
 *
 * Return: 0 on success, -EAGAIN if a GPU reset was in progress.
 */
static int tl_reader_update_enable_bit(
	struct kbase_csf_tl_reader *self,
	bool value)
{
	struct kbase_device *kbdev = self->kbdev;
	unsigned long flags;

	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);

	/* If there is already a GPU reset pending then inform
	 * the User to retry the update.
	 */
	if (kbase_reset_gpu_silent(kbdev)) {
		spin_unlock_irqrestore(
			&kbdev->hwaccess_lock, flags);
		dev_warn(
			kbdev->dev,
			"GPU reset already in progress when enabling firmware timeline.");
		return -EAGAIN;
	}

	/* GPU reset request has been placed, now update the
	 * firmware image. GPU reset will take place only after
	 * hwaccess_lock is released.
	 */
	kbase_csf_firmware_trace_buffer_update_trace_enable_bit(
		self->trace_buffer, 0, value);

	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	return 0;
}

void kbase_csf_tl_reader_init(struct kbase_csf_tl_reader *self,
	struct kbase_tlstream *stream)
{
	self->timer_interval = KBASE_CSF_TL_READ_INTERVAL_DEFAULT;

	kbase_timer_setup(&self->read_timer,
		kbasep_csf_tl_reader_read_callback);

	self->stream = stream;

	/* This will be initialized by tl_reader_init_late() */
	self->kbdev = NULL;
	self->trace_buffer = NULL;
	self->tl_header.data = NULL;
	self->tl_header.size = 0;

	spin_lock_init(&self->read_lock);

	tl_reader_reset(self);
}

void kbase_csf_tl_reader_term(struct kbase_csf_tl_reader *self)
{
	del_timer_sync(&self->read_timer);
}

int kbase_csf_tl_reader_start(struct kbase_csf_tl_reader *self,
	struct kbase_device *kbdev)
{
	int rcode;

	/* If already running, early exit. */
	if (self->is_active)
		return 0;

	if (tl_reader_init_late(self, kbdev)) {
#if defined(CONFIG_MALI_NO_MALI)
		dev_warn(
			kbdev->dev,
			"CSFFW timeline is not available for MALI_NO_MALI builds!");
		return 0;
#else
		return -EINVAL;
#endif
	}

	tl_reader_reset(self);

	self->is_active = true;
	/* Set bytes to copy to the header size. This is to trigger copying
	 * of the header to the user space.
	 */
	self->tl_header.btc = self->tl_header.size;

	/* Enable the tracebuffer on the CSFFW side. */
	rcode = tl_reader_update_enable_bit(self, true);
	if (rcode != 0)
		return rcode;

	rcode = mod_timer(&self->read_timer,
		jiffies + msecs_to_jiffies(self->timer_interval));

	return 0;
}

void kbase_csf_tl_reader_stop(struct kbase_csf_tl_reader *self)
{
	unsigned long flags;

	/* If is not running, early exit. */
	if (!self->is_active)
		return;

	/* Disable the tracebuffer on the CSFFW side. */
	tl_reader_update_enable_bit(self, false);

	del_timer_sync(&self->read_timer);

	spin_lock_irqsave(&self->read_lock, flags);

	tl_reader_reset(self);

	spin_unlock_irqrestore(&self->read_lock, flags);
}

void kbase_csf_tl_reader_reset(struct kbase_csf_tl_reader *self)
{
	u64 gpu_cycle = 0;
	struct kbase_device *kbdev = self->kbdev;

	if (!kbdev)
		return;

	kbase_csf_tl_reader_flush_buffer(self);

	get_cpu_gpu_time(kbdev, NULL, NULL, &gpu_cycle);
	KBASE_TLSTREAM_TL_KBASE_CSFFW_RESET(kbdev, gpu_cycle);
}
