// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/delay.h>

#include "mdrv_demux_stub.h"
#include "mdrv_demux_debug.h"

#define MAX_DMX_STUB_NUM		(10)
#define IS_VALID_HANDLE(hdl)	((hdl >= 0) && (hdl < MAX_DMX_STUB_NUM))

struct dmx_stub_context {
	struct demux_stub_params param;
	struct task_struct *task;
	bool bStop;
};

static DEFINE_MUTEX(dmx_stub_mutex);
static struct dmx_stub_context dmx_stub_ctx[MAX_DMX_STUB_NUM];

static int dmx_stub_task(void *arg);

// AMT (table id = 0xFE)
static u8 dummy_tlv_si[] = {
	0xFE, 0xF0, 0x57, 0x00, 0x00, 0xCB, 0x00, 0x00,
	0x00, 0xBF, 0x00, 0x01, 0xFC, 0x22, 0x20, 0x01,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
	0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x80, 0xFF,
	0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80,
	0x00, 0x02, 0xFC, 0x22, 0x20, 0x01, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,
	0x00, 0x00, 0x00, 0x01, 0x80, 0xFF, 0x12, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x8B, 0xBE,
	0x87, 0x4F
};

// CDT (table id = 0xA2)
static u8 dummy_mmtp_si[] = {
	0xA2, 0xF1, 0x19, 0x00, 0x01, 0xCF, 0x00, 0x02,
	0x00, 0x0B, 0x01, 0xF0, 0x00, 0x05, 0xFE, 0x01,
	0xF0, 0x01, 0x01, 0x04, 0x89, 0x50, 0x4E, 0x47,
	0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
	0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40,
	0x00, 0x00, 0x00, 0x24, 0x08, 0x03, 0x00, 0x00,
	0x00, 0x0E, 0xD2, 0xCC, 0xA0, 0x00, 0x00, 0x00,
	0xCB, 0x49, 0x44, 0x41, 0x54, 0x48, 0xC7, 0xED,
	0x94, 0x39, 0x16, 0x03, 0x21, 0x0C, 0x43, 0xE9,
	0x54, 0xEA, 0x06, 0xBA, 0xFF, 0x31, 0x93, 0xC9,
	0xB0, 0x78, 0x8C, 0x59, 0x52, 0xE4, 0xBD, 0x14,
	0xA8, 0x32, 0x60, 0x7F, 0xB0, 0x31, 0xA4, 0x74,
	0xF4, 0x4F, 0x12, 0x49, 0xD9, 0xB1, 0x3A, 0xF3,
	0xB1, 0xDC, 0x85, 0x5F, 0x33, 0x64, 0x9B, 0x00,
	0x8A, 0x0F, 0x81, 0x62, 0xB4, 0x00, 0x38, 0x02,
	0x0B, 0x67, 0x06, 0x30, 0x51, 0x42, 0x18, 0x6F,
	0x8C, 0x18, 0x50, 0x97, 0x9B, 0xE5, 0xE3, 0x34,
	0x03, 0xB4, 0x7D, 0x07, 0x19, 0xD8, 0xA2, 0x44,
	0x80, 0x1A, 0x47, 0x78, 0x7F, 0xEE, 0x01, 0x4A,
	0x95, 0xD1, 0xFB, 0x53, 0x1D, 0x80, 0xBA, 0x65,
	0x00, 0x39, 0x07, 0x21, 0xEC, 0x03, 0x39, 0x80,
	0x91, 0xBB, 0x3D, 0x62, 0xD4, 0x4A, 0x4B, 0xC0,
	0xED, 0xE2, 0x4B, 0x68, 0x4A, 0xA1, 0x20, 0x05,
	0x0B, 0xF8, 0x1C, 0x5E, 0x18, 0x37, 0x34, 0xA7,
	0xD7, 0x98, 0x37, 0x27, 0x27, 0x4F, 0x82, 0x0B,
	0xC0, 0x95, 0x7E, 0x9F, 0x01, 0xF7, 0x01, 0x82,
	0xA2, 0x0C, 0x5A, 0x27, 0xAE, 0x00, 0xEF, 0x16,
	0x88, 0x32, 0xA8, 0x1D, 0x3C, 0x7F, 0x0B, 0x79,
	0xA4, 0x61, 0x17, 0x88, 0x69, 0x09, 0x48, 0xC0,
	0xD6, 0x87, 0xA2, 0x36, 0xD2, 0xF3, 0xCC, 0xD2,
	0xF9, 0x7D, 0xC3, 0xEA, 0x39, 0x61, 0x4F, 0x63,
	0x66, 0xE4, 0xDD, 0x6F, 0xC3, 0xEF, 0x00, 0xE7,
	0xF2, 0x7E, 0xA7, 0x17, 0xC5, 0xDE, 0x16, 0x5E,
	0xA4, 0xC7, 0x4B, 0xCF, 0x00, 0x00, 0x00, 0x00,
	0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
	0x82, 0x34, 0xDE, 0xA4
};

// LMT
static u8 dummy_alp_si[] = {
	0x00, 0x00, 0x01, 0x11, 0x11, 0x11, 0x11, 0x22,
	0x22, 0x22, 0x22, 0x12, 0x34, 0x56, 0x78, 0x00
};

// SLT
static u8 dummy_alp_lls[] = {
	0x01, 0x00, 0x00, 0x00, 0x3C, 0x53, 0x4C, 0x54,
	0x20, 0x62, 0x73, 0x69, 0x64, 0x20, 0x3D, 0x20,
	0x22, 0x34, 0x33, 0x32, 0x31, 0x22, 0x3E, 0x0D,
	0x0A, 0x09, 0x3C, 0x53, 0x65, 0x72, 0x76, 0x69,
	0x63, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69,
	0x63, 0x65, 0x49, 0x64, 0x3D, 0x22, 0x38, 0x37,
	0x36, 0x22, 0x0D, 0x0A, 0x09, 0x09, 0x09, 0x73,
	0x6C, 0x74, 0x53, 0x76, 0x63, 0x53, 0x65, 0x71,
	0x4E, 0x75, 0x6D, 0x3D, 0x22, 0x31, 0x22, 0x0D,
	0x0A, 0x09, 0x09, 0x09, 0x73, 0x65, 0x72, 0x76,
	0x69, 0x63, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
	0x6F, 0x72, 0x79, 0x3D, 0x22, 0x31, 0x22, 0x0D,
	0x0A, 0x09, 0x09, 0x09, 0x6D, 0x61, 0x6A, 0x6F,
	0x72, 0x43, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C,
	0x4E, 0x6F, 0x3D, 0x22, 0x31, 0x22, 0x0D, 0x0A,
	0x09, 0x09, 0x09, 0x6D, 0x69, 0x6E, 0x6F, 0x72,
	0x43, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x4E,
	0x6F, 0x3D, 0x22, 0x31, 0x22, 0x0D, 0x0A, 0x09,
	0x09, 0x09, 0x73, 0x68, 0x6F, 0x72, 0x74, 0x53,
	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4E, 0x61,
	0x6D, 0x65, 0x3D, 0x22, 0x57, 0x54, 0x52, 0x56,
	0x22, 0x3E, 0x0D, 0x0A, 0x09, 0x09, 0x3C, 0x42,
	0x72, 0x6F, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74,
	0x53, 0x76, 0x63, 0x53, 0x69, 0x67, 0x6E, 0x61,
	0x6C, 0x69, 0x6E, 0x67, 0x20, 0x73, 0x6C, 0x73,
	0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C,
	0x3D, 0x22, 0x31, 0x22, 0x0D, 0x0A, 0x09, 0x09,
	0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x6C,
	0x73, 0x50, 0x6C, 0x70, 0x49, 0x64, 0x3D, 0x22,
	0x31, 0x22, 0x0D, 0x0A, 0x09, 0x09, 0x09, 0x09,
	0x09, 0x09, 0x09, 0x09, 0x73, 0x6C, 0x73, 0x44,
	0x65, 0x73, 0x74, 0x69, 0x6E, 0x61, 0x74, 0x69,
	0x6F, 0x6E, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72,
	0x65, 0x73, 0x73, 0x3D, 0x22, 0x32, 0x33, 0x39,
	0x2E, 0x32, 0x35, 0x35, 0x2E, 0x30, 0x2E, 0x31,
	0x22, 0x0D, 0x0A, 0x09, 0x09, 0x09, 0x09, 0x09,
	0x09, 0x09, 0x09, 0x73, 0x6C, 0x73, 0x44, 0x65,
	0x73, 0x74, 0x69, 0x6E, 0x61, 0x74, 0x69, 0x6F,
	0x6E, 0x55, 0x64, 0x70, 0x50, 0x6F, 0x72, 0x74,
	0x3D, 0x22, 0x38, 0x30, 0x30, 0x30, 0x22, 0x0D,
	0x0A, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
	0x09, 0x73, 0x6C, 0x73, 0x53, 0x6F, 0x75, 0x72,
	0x63, 0x65, 0x49, 0x70, 0x41, 0x64, 0x64, 0x72,
	0x65, 0x73, 0x73, 0x3D, 0x22, 0x31, 0x39, 0x32,
	0x2E, 0x31, 0x36, 0x38, 0x2E, 0x31, 0x2E, 0x34,
	0x22, 0x2F, 0x3E, 0x0D, 0x0A, 0x09, 0x3C, 0x2F,
	0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x3E,
	0x0D, 0x0A, 0x3C, 0x2F, 0x53, 0x4C, 0x54, 0x3E
};

static stub_handle_t get_dmx_stub_handle(void)
{
	short i;

	mutex_lock(&dmx_stub_mutex);

	for (i = 0; i < MAX_DMX_STUB_NUM; i++)
		if (!dmx_stub_ctx[i].task) {
			mutex_unlock(&dmx_stub_mutex);
			return i;
		}

	mutex_unlock(&dmx_stub_mutex);
	return -ENODEV;
}

static void put_dmx_stub_handle(stub_handle_t hdl)
{
	if (!IS_VALID_HANDLE(hdl)) {
		DEMUX_DEBUG_ERR("[%s][%d] handle out-of-range(%d)\n", __func__, __LINE__, hdl);
		return;
	}

	mutex_lock(&dmx_stub_mutex);
	memset(&dmx_stub_ctx[hdl], 0, sizeof(struct dmx_stub_context));
	mutex_unlock(&dmx_stub_mutex);
}

static int create_stub_task(stub_handle_t hdl, struct demux_stub_params param)
{
	if (!IS_VALID_HANDLE(hdl)) {
		DEMUX_DEBUG_ERR("[%s][%d] handle out-of-range(%d)\n", __func__, __LINE__, hdl);
		return -EINVAL;
	}

	mutex_lock(&dmx_stub_mutex);

	memcpy(&dmx_stub_ctx[hdl].param, &param, sizeof(struct demux_stub_params));
	dmx_stub_ctx[hdl].task =
		kthread_run(dmx_stub_task,
					&dmx_stub_ctx[hdl],
					"demux_stub_task%d",
					hdl);

	if (IS_ERR(dmx_stub_ctx[hdl].task)) {
		mutex_unlock(&dmx_stub_mutex);
		return -ENODEV;
	}

	mutex_unlock(&dmx_stub_mutex);
	return 0;
}

static int delete_stub_task(stub_handle_t hdl)
{
	struct task_struct *task;
	bool bStop;

	if (!IS_VALID_HANDLE(hdl)) {
		DEMUX_DEBUG_ERR("[%s][%d] handle out-of-range(%d)\n", __func__, __LINE__, hdl);
		return -EINVAL;
	}

	mutex_lock(&dmx_stub_mutex);
	task = dmx_stub_ctx[hdl].task;
	bStop = dmx_stub_ctx[hdl].bStop;
	mutex_unlock(&dmx_stub_mutex);

	if (!task)
		return -EFAULT;

	// @NOTE: avoid invoking kthread_stop() after threadfn has been terminated
	//        ---> NOT invoke kthread_stop() & do_exit() cocurrently
	if (!bStop)
		kthread_stop(task);

	return 0;
}

static int dmx_stub_task(void *arg)
{
	struct dmx_stub_context *pctx = (struct dmx_stub_context *)arg;
	stub_handle_t hdl;
	demux_stub_type_t type;
	stub_cb_t cb;
	void *priv;
	u8 *buf;
	size_t size;
	u32 cnt = 0, max_cnt, delay;

	mutex_lock(&dmx_stub_mutex);
	hdl = pctx - dmx_stub_ctx;
	type = pctx->param.type;
	cb = pctx->param.cb;
	priv = pctx->param.arg;
	max_cnt = pctx->param.loop_cnt;
	delay = pctx->param.delay;
	mutex_unlock(&dmx_stub_mutex);

	DEMUX_DEBUG_ERR("[%s][%d] start task(hdl = %d)\n", __func__, __LINE__, hdl);

	msleep(100);

	switch (type) {
	case DEMUX_STUB_TYPE_TLV_SI:
		buf = dummy_tlv_si;
		size = sizeof(dummy_tlv_si);
		break;
	case DEMUX_STUB_TYPE_MMTP_SI:
		buf = dummy_mmtp_si;
		size = sizeof(dummy_mmtp_si);
		break;
	case DEMUX_STUB_TYPE_ALP_SI:
		buf = dummy_alp_si;
		size = sizeof(dummy_alp_si);
		break;
	case DEMUX_STUB_TYPE_ALP_LLS:
		buf = dummy_alp_lls;
		size = sizeof(dummy_alp_lls);
		break;
	default:
		DEMUX_DEBUG_ERR("[%s][%d] not support type(%u)\n", __func__, __LINE__, type);
		return -EFAULT;
	}

	while (cnt < max_cnt) {
		if (kthread_should_stop())
			break;

		if (cb)
			cb(buf, size, priv);

		msleep(delay);
		cnt++;
	}

	mutex_lock(&dmx_stub_mutex);
	pctx->bStop = true;
	mutex_unlock(&dmx_stub_mutex);

	DEMUX_DEBUG_ERR("[%s][%d] end task(%d)\n", __func__, __LINE__, hdl);
	return 0;
}

int mdrv_demux_stub_start(struct demux_stub_params param, stub_handle_t *phdl)
{
	stub_handle_t hdl;

	hdl = get_dmx_stub_handle();

	if (!IS_VALID_HANDLE(hdl)) {
		DEMUX_DEBUG_ERR("[%s][%d] no free handle\n", __func__, __LINE__);
		return -ENODEV;
	}

	if (create_stub_task(hdl, param) < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] create stub task (%d) fail\n", __func__, __LINE__, hdl);
		put_dmx_stub_handle(hdl);
		return -EFAULT;
	}

	*phdl = hdl;
	return 0;
}

int mdrv_demux_stub_stop(stub_handle_t hdl)
{
	if (!IS_VALID_HANDLE(hdl)) {
		DEMUX_DEBUG_ERR("[%s][%d] handle out-of-range(%d)\n", __func__, __LINE__, hdl);
		return -EINVAL;
	}

	if (delete_stub_task(hdl) < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] delete stub task (%d) fail\n", __func__, __LINE__, hdl);
		return -EFAULT;
	}

	put_dmx_stub_handle(hdl);
	return 0;
}
