/*
 *
 * (C) COPYRIGHT 2015-2019 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_timeline.h"
#include "mali_kbase_timeline_priv.h"
#include "mali_kbase_tracepoints.h"

#include <mali_kbase.h>
#include <mali_kbase_jm.h>

#include <linux/anon_inodes.h>
#include <linux/atomic.h>
#include <linux/file.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/stringify.h>
#include <linux/timer.h>
#include <linux/wait.h>


/* The period of autoflush checker execution in milliseconds. */
#define AUTOFLUSH_INTERVAL 1000 /* ms */

/*****************************************************************************/

/* These values are used in mali_kbase_tracepoints.h
 * to retrieve the streams from a kbase_timeline instance.
 */
const size_t __obj_stream_offset =
	offsetof(struct kbase_timeline, streams)
	+ sizeof(struct kbase_tlstream) * TL_STREAM_TYPE_OBJ;

const size_t __aux_stream_offset =
	offsetof(struct kbase_timeline, streams)
	+ sizeof(struct kbase_tlstream) * TL_STREAM_TYPE_AUX;

/**
 * kbasep_timeline_autoflush_timer_callback - autoflush timer callback
 * @timer:  Timer list
 *
 * Timer is executed periodically to check if any of the stream contains
 * buffer ready to be submitted to user space.
 */
static void kbasep_timeline_autoflush_timer_callback(struct timer_list *timer)
{
	enum tl_stream_type stype;
	int                 rcode;
	struct kbase_timeline *timeline =
		container_of(timer, struct kbase_timeline, autoflush_timer);

	CSTD_UNUSED(timer);

	for (stype = (enum tl_stream_type)0; stype < TL_STREAM_TYPE_COUNT;
			stype++) {
		struct kbase_tlstream *stream = &timeline->streams[stype];

		int af_cnt = atomic_read(&stream->autoflush_counter);

		/* Check if stream contain unflushed data. */
		if (af_cnt < 0)
			continue;

		/* Check if stream should be flushed now. */
		if (af_cnt != atomic_cmpxchg(
					&stream->autoflush_counter,
					af_cnt,
					af_cnt + 1))
			continue;
		if (!af_cnt)
			continue;

		/* Autoflush this stream. */
		kbase_tlstream_flush_stream(stream);
	}

	if (atomic_read(&timeline->autoflush_timer_active))
		rcode = mod_timer(
				&timeline->autoflush_timer,
				jiffies + msecs_to_jiffies(AUTOFLUSH_INTERVAL));
	CSTD_UNUSED(rcode);
}



/*****************************************************************************/

int kbase_timeline_init(struct kbase_timeline **timeline,
		atomic_t *timeline_is_enabled)
{
	enum tl_stream_type i;
	struct kbase_timeline *result;

	if (!timeline || !timeline_is_enabled)
		return -EINVAL;

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

	mutex_init(&result->reader_lock);
	init_waitqueue_head(&result->event_queue);

	/* Prepare stream structures. */
	for (i = 0; i < TL_STREAM_TYPE_COUNT; i++)
		kbase_tlstream_init(&result->streams[i], i,
			&result->event_queue);

	/* Initialize autoflush timer. */
	atomic_set(&result->autoflush_timer_active, 0);
	kbase_timer_setup(&result->autoflush_timer,
			  kbasep_timeline_autoflush_timer_callback);
	result->is_enabled = timeline_is_enabled;

	*timeline = result;
	return 0;
}

void kbase_timeline_term(struct kbase_timeline *timeline)
{
	enum tl_stream_type i;

	if (!timeline)
		return;

	for (i = (enum tl_stream_type)0; i < TL_STREAM_TYPE_COUNT; i++)
		kbase_tlstream_term(&timeline->streams[i]);

	kfree(timeline);
}

static void kbase_create_timeline_objects(struct kbase_device *kbdev)
{
	unsigned int lpu_id;
	unsigned int as_nr;
	struct kbase_context *kctx;
	struct kbase_timeline *timeline = kbdev->timeline;
	struct kbase_tlstream *summary =
		&timeline->streams[TL_STREAM_TYPE_OBJ_SUMMARY];

	/* Summarize the LPU objects. */
	for (lpu_id = 0; lpu_id < kbdev->gpu_props.num_job_slots; lpu_id++) {
		u32 *lpu =
			&kbdev->gpu_props.props.raw_props.js_features[lpu_id];
		__kbase_tlstream_tl_new_lpu(summary, lpu, lpu_id, *lpu);
	}

	/* Summarize the Address Space objects. */
	for (as_nr = 0; as_nr < kbdev->nr_hw_address_spaces; as_nr++)
		__kbase_tlstream_tl_new_as(summary, &kbdev->as[as_nr], as_nr);

	/* Create GPU object and make it retain all LPUs and address spaces. */
	__kbase_tlstream_tl_new_gpu(summary,
			kbdev,
			kbdev->gpu_props.props.raw_props.gpu_id,
			kbdev->gpu_props.num_cores);

	for (lpu_id = 0; lpu_id < kbdev->gpu_props.num_job_slots; lpu_id++) {
		void *lpu =
			&kbdev->gpu_props.props.raw_props.js_features[lpu_id];
		__kbase_tlstream_tl_lifelink_lpu_gpu(summary, lpu, kbdev);
	}

	for (as_nr = 0; as_nr < kbdev->nr_hw_address_spaces; as_nr++)
		__kbase_tlstream_tl_lifelink_as_gpu(summary,
				&kbdev->as[as_nr],
				kbdev);

	/* Lock the context list, to ensure no changes to the list are made
	 * while we're summarizing the contexts and their contents.
	 */
	mutex_lock(&kbdev->kctx_list_lock);

	/* For each context in the device... */
	list_for_each_entry(kctx, &kbdev->kctx_list, kctx_list_link) {
		/* Summarize the context itself */
		__kbase_tlstream_tl_new_ctx(summary,
				kctx,
				kctx->id,
				(u32)(kctx->tgid));
	};

	/* Reset body stream buffers while holding the kctx lock.
	 * This ensures we can't fire both summary and normal tracepoints for
	 * the same objects.
	 * If we weren't holding the lock, it's possible that the summarized
	 * objects could have been created, destroyed, or used after we
	 * constructed the summary stream tracepoints, but before we reset
	 * the body stream, resulting in losing those object event tracepoints.
	 */
	kbase_timeline_streams_body_reset(timeline);

	mutex_unlock(&kbdev->kctx_list_lock);

	/* Static object are placed into summary packet that needs to be
	 * transmitted first. Flush all streams to make it available to
	 * user space.
	 */
	kbase_timeline_streams_flush(timeline);
}

#ifdef CONFIG_MALI_DEVFREQ
static void kbase_tlstream_current_devfreq_target(struct kbase_device *kbdev)
{
	struct devfreq *devfreq = kbdev->devfreq;

	/* Devfreq initialization failure isn't a fatal error, so devfreq might
	 * be null.
	 */
	if (devfreq) {
		unsigned long cur_freq = 0;

		mutex_lock(&devfreq->lock);
#if KERNEL_VERSION(4, 3, 0) > LINUX_VERSION_CODE
		cur_freq = kbdev->current_nominal_freq;
#else
		cur_freq = devfreq->last_status.current_frequency;
#endif
		KBASE_TLSTREAM_AUX_DEVFREQ_TARGET(kbdev, (u64)cur_freq);
		mutex_unlock(&devfreq->lock);
	}
}
#endif /* CONFIG_MALI_DEVFREQ */

int kbase_timeline_io_acquire(struct kbase_device *kbdev, u32 flags)
{
	int ret;
	u32 tlstream_enabled = TLSTREAM_ENABLED | flags;
	struct kbase_timeline *timeline = kbdev->timeline;

	if (!atomic_cmpxchg(timeline->is_enabled, 0, tlstream_enabled)) {
		int rcode;

		ret = anon_inode_getfd(
				"[mali_tlstream]",
				&kbasep_tlstream_fops,
				timeline,
				O_RDONLY | O_CLOEXEC);
		if (ret < 0) {
			atomic_set(timeline->is_enabled, 0);
			return ret;
		}

		/* Reset and initialize header streams. */
		kbase_tlstream_reset(
			&timeline->streams[TL_STREAM_TYPE_OBJ_SUMMARY]);

		timeline->obj_header_btc = obj_desc_header_size;
		timeline->aux_header_btc = aux_desc_header_size;

		/* Start autoflush timer. */
		atomic_set(&timeline->autoflush_timer_active, 1);
		rcode = mod_timer(
				&timeline->autoflush_timer,
				jiffies + msecs_to_jiffies(AUTOFLUSH_INTERVAL));
		CSTD_UNUSED(rcode);

		/* If job dumping is enabled, readjust the software event's
		 * timeout as the default value of 3 seconds is often
		 * insufficient.
		 */
		if (flags & BASE_TLSTREAM_JOB_DUMPING_ENABLED) {
			dev_info(kbdev->dev,
					"Job dumping is enabled, readjusting the software event's timeout\n");
			atomic_set(&kbdev->js_data.soft_job_timeout_ms,
					1800000);
		}

		/* Summary stream was cleared during acquire.
		 * Create static timeline objects that will be
		 * read by client.
		 */
		kbase_create_timeline_objects(kbdev);

#ifdef CONFIG_MALI_DEVFREQ
		/* Devfreq target tracepoints are only fired when the target
		 * changes, so we won't know the current target unless we
		 * send it now.
		 */
		kbase_tlstream_current_devfreq_target(kbdev);
#endif /* CONFIG_MALI_DEVFREQ */

	} else {
		ret = -EBUSY;
	}

	return ret;
}

void kbase_timeline_streams_flush(struct kbase_timeline *timeline)
{
	enum tl_stream_type stype;

	for (stype = 0; stype < TL_STREAM_TYPE_COUNT; stype++)
		kbase_tlstream_flush_stream(&timeline->streams[stype]);
}

void kbase_timeline_streams_body_reset(struct kbase_timeline *timeline)
{
	kbase_tlstream_reset(
			&timeline->streams[TL_STREAM_TYPE_OBJ]);
	kbase_tlstream_reset(
			&timeline->streams[TL_STREAM_TYPE_AUX]);
}

#if MALI_UNIT_TEST
void kbase_timeline_stats(struct kbase_timeline *timeline,
		u32 *bytes_collected, u32 *bytes_generated)
{
	enum tl_stream_type stype;

	KBASE_DEBUG_ASSERT(bytes_collected);

	/* Accumulate bytes generated per stream  */
	*bytes_generated = 0;
	for (stype = (enum tl_stream_type)0; stype < TL_STREAM_TYPE_COUNT;
			stype++)
		*bytes_generated += atomic_read(
			&timeline->streams[stype].bytes_generated);

	*bytes_collected = atomic_read(&timeline->bytes_collected);
}
#endif /* MALI_UNIT_TEST */
