/*
 * Copyright (c) 2014-2017 The Linux Foundation. All rights reserved.
 *
 * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
 *
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * This file was originally distributed by Qualcomm Atheros, Inc.
 * under proprietary terms before Copyright ownership was assigned
 * to the Linux Foundation.
 */

#include <scheduler_api.h>
#include <scheduler_core.h>
#include <qdf_atomic.h>

/* Debug variable to detect if controller thread is stuck */
static qdf_atomic_t scheduler_msg_post_fail_count;

static void scheduler_flush_mqs(struct scheduler_ctx *sched_ctx)
{
	int i;

	/* Here each of the MC thread MQ shall be drained and returned to the
	 * Core. Before returning a wrapper to the Core, the Scheduler message
	 * shall be freed first
	 */
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO,
		  "%s: Flushing scheduler message queue", __func__);

	if (!sched_ctx) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			  "%s: sched_ctx is NULL", __func__);
		return;
	}
	for (i = 0; i < SCHEDULER_NUMBER_OF_MSG_QUEUE; i++)
		scheduler_cleanup_queues(sched_ctx, i);
}

static QDF_STATUS scheduler_close(struct scheduler_ctx *sched_ctx)
{
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO_HIGH,
			"%s: invoked", __func__);
	if (!sched_ctx) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				"%s: sched_ctx == NULL", __func__);
		return QDF_STATUS_E_FAILURE;
	}
	/* shut down scheduler thread */
	qdf_set_bit(MC_SHUTDOWN_EVENT_MASK, &sched_ctx->sch_event_flag);
	qdf_set_bit(MC_POST_EVENT_MASK, &sched_ctx->sch_event_flag);
	qdf_wake_up_interruptible(&sched_ctx->sch_wait_queue);
	/* Wait for MC to exit */
	qdf_wait_single_event(&sched_ctx->sch_shutdown, 0);
	sched_ctx->sch_thread = 0;

	/* Clean up message queues of MC thread */
	scheduler_flush_mqs(sched_ctx);

	/* Deinit all the queues */
	scheduler_queues_deinit(sched_ctx);

	return QDF_STATUS_SUCCESS;
}

static QDF_STATUS scheduler_open(struct scheduler_ctx *sched_ctx)
{
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO_HIGH,
		  "%s: Opening the QDF Scheduler", __func__);
	/* Sanity checks */
	if (!sched_ctx) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			  "%s: Null params being passed", __func__);
		return QDF_STATUS_E_FAILURE;
	}
	/* Initialize the helper events and event queues */
	qdf_event_create(&sched_ctx->sch_start_event);
	qdf_event_create(&sched_ctx->sch_shutdown);
	qdf_event_create(&sched_ctx->resume_sch_event);
	qdf_spinlock_create(&sched_ctx->sch_thread_lock);
	qdf_init_waitqueue_head(&sched_ctx->sch_wait_queue);
	sched_ctx->sch_event_flag = 0;
	/* Create the Scheduler Main Controller thread */
	sched_ctx->sch_thread = qdf_create_thread(scheduler_thread,
					sched_ctx, "scheduler_thread");
	if (IS_ERR(sched_ctx->sch_thread)) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_FATAL,
			  "%s: Could not Create QDF Main Thread Controller",
			  __func__);
		scheduler_queues_deinit(sched_ctx);
		return QDF_STATUS_E_RESOURCES;
	}
	/* start the thread here */
	qdf_wake_up_process(sched_ctx->sch_thread);
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
		  "%s: QDF Main Controller thread Created", __func__);

	/*
	 * Now make sure all threads have started before we exit.
	 * Each thread should normally ACK back when it starts.
	 */
	qdf_wait_single_event(&sched_ctx->sch_start_event, 0);
	/* We're good now: Let's get the ball rolling!!! */
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
		  "%s: Scheduler thread has started", __func__);
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_init(void)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct scheduler_ctx *sched_ctx;

	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO_HIGH,
			FL("Opening Scheduler"));
	status = scheduler_create_ctx();
	if (QDF_STATUS_SUCCESS != status) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("can't create scheduler ctx"));
		return status;
	}
	sched_ctx = scheduler_get_context();
	status = scheduler_queues_init(sched_ctx);
	if (QDF_STATUS_SUCCESS != status) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("Queue init failed"));
		scheduler_destroy_ctx();
		return status;
	}
	status = scheduler_open(sched_ctx);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		/* Critical Error ...  Cannot proceed further */
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_FATAL,
				"Failed to open QDF Scheduler");
		QDF_ASSERT(0);
		scheduler_queues_deinit(sched_ctx);
		scheduler_destroy_ctx();
	}
	qdf_register_mc_timer_callback(scheduler_mc_timer_callback);
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_deinit(void)
{
	QDF_STATUS status = QDF_STATUS_SUCCESS;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO_HIGH,
			FL("Closing Scheduler"));
	status = scheduler_close(sched_ctx);
	if (QDF_STATUS_SUCCESS != status) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("Scheduler close failed"));
		return status;
	}
	return scheduler_destroy_ctx();
}


QDF_STATUS scheduler_post_msg_by_priority(QDF_MODULE_ID qid,
		struct scheduler_msg *pMsg, bool is_high_priority)
{
	uint8_t qidx;
	uint32_t msg_wrapper_fail_count;
	struct scheduler_mq_type *target_mq = NULL;
	struct scheduler_msg_wrapper *msg_wrapper = NULL;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (!sched_ctx || !pMsg || !sched_ctx->sch_thread) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				"%s: Null params or global sch context is null",
				__func__);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				"%s: sched_ctx[%d], pMsg[%d]",
				__func__, !!sched_ctx, !!pMsg);
		QDF_ASSERT(0);
		return QDF_STATUS_E_FAILURE;
	}

	if ((0 != pMsg->reserved) && (SYS_MSG_COOKIE != pMsg->reserved)) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			"%s: Un-initialized message pointer.. please initialize it",
			__func__);
		QDF_BUG(0);
		return QDF_STATUS_E_FAILURE;
	}

	/* Target_If is a special message queue in phase 3 convergence beacause
	 * its used by both legacy WMA and as well as new UMAC components which
	 * directly populate callback handlers in message body.
	 * 1) WMA legacy messages should not have callback
	 * 2) New target_if message needs to have valid callback
	 * Clear callback handler for legacy WMA messages such that in case
	 * if someone is sending legacy WMA message from stack which has
	 * uninitialized callback then its handled properly. Also change
	 * legacy WMA message queue id to target_if queue such that its  always
	 * handled in right order.
	 */
	if (QDF_MODULE_ID_WMA == qid) {
		pMsg->callback = NULL;
		/* change legacy WMA message id to new target_if mq id */
		qid = QDF_MODULE_ID_TARGET_IF;
	}

	qidx = sched_ctx->queue_ctx.scheduler_msg_qid_to_qidx[qid];
	if (qidx >= SCHEDULER_NUMBER_OF_MSG_QUEUE) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("Scheduler is deinitialized ignore msg"));
		return QDF_STATUS_E_FAILURE;
	}
	if (!sched_ctx->queue_ctx.scheduler_msg_process_fn[qidx]) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("callback not registered for qid[%d]"), qid);
		QDF_ASSERT(0);
		return QDF_STATUS_E_FAILURE;
	}
	target_mq = &(sched_ctx->queue_ctx.sch_msg_q[qidx]);
	QDF_ASSERT(target_mq);
	if (target_mq == NULL) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				"%s: target_mq == NULL", __func__);
		return QDF_STATUS_E_FAILURE;
	}

	/* Try and get a free Msg wrapper */
	msg_wrapper = scheduler_mq_get(&sched_ctx->queue_ctx.free_msg_q);
	if (NULL == msg_wrapper) {
		msg_wrapper_fail_count =
			qdf_atomic_inc_return(&scheduler_msg_post_fail_count);
		/* log only 1st failure to avoid over running log buffer */
		if (1 == msg_wrapper_fail_count) {
			QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
				QDF_TRACE_LEVEL_ERROR,
				FL("Scheduler message wrapper empty"));
		}
		if (SCHEDULER_WRAPPER_MAX_FAIL_COUNT == msg_wrapper_fail_count)
			QDF_BUG(0);

		return QDF_STATUS_E_RESOURCES;
	}
	qdf_atomic_set(&scheduler_msg_post_fail_count, 0);

	/* Copy the message now */
	qdf_mem_copy((void *)msg_wrapper->msg_buf,
			(void *)pMsg, sizeof(struct scheduler_msg));

	if (is_high_priority)
		scheduler_mq_put_front(target_mq, msg_wrapper);
	else
		scheduler_mq_put(target_mq, msg_wrapper);

	qdf_set_bit(MC_POST_EVENT_MASK, &sched_ctx->sch_event_flag);
	qdf_wake_up_interruptible(&sched_ctx->sch_wait_queue);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_register_module(QDF_MODULE_ID qid,
		scheduler_msg_process_fn_t callback)
{
	struct scheduler_mq_ctx *ctx;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO,
		FL("Enter"));
	if (!sched_ctx) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			FL("sched_ctx is NULL"));
		return QDF_STATUS_E_FAILURE;
	}

	if (sched_ctx->sch_last_qidx >= SCHEDULER_NUMBER_OF_MSG_QUEUE) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR,
			FL("Already registered max %d no of message queues"),
				SCHEDULER_NUMBER_OF_MSG_QUEUE);
		return QDF_STATUS_E_FAILURE;
	}

	ctx = &sched_ctx->queue_ctx;
	ctx->scheduler_msg_qid_to_qidx[qid] = sched_ctx->sch_last_qidx;
	ctx->sch_msg_q[sched_ctx->sch_last_qidx].qid = qid;
	ctx->scheduler_msg_process_fn[sched_ctx->sch_last_qidx] = callback;
	sched_ctx->sch_last_qidx++;
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO,
		FL("Exit"));
	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_deregister_module(QDF_MODULE_ID qid)
{
	struct scheduler_mq_ctx *ctx;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();
	uint8_t qidx;

	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO,
		FL("Enter"));
	if (!sched_ctx) {
		QDF_ASSERT(0);
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
				FL("sched_ctx is NULL"));
		return QDF_STATUS_E_FAILURE;
	}
	ctx = &sched_ctx->queue_ctx;
	qidx = ctx->scheduler_msg_qid_to_qidx[qid];
	ctx->scheduler_msg_process_fn[qidx] = NULL;
	sched_ctx->sch_last_qidx--;
	ctx->scheduler_msg_qid_to_qidx[qidx] = SCHEDULER_NUMBER_OF_MSG_QUEUE;
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_INFO,
		FL("Exit"));
	return QDF_STATUS_SUCCESS;
}

void scheduler_resume(void)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (sched_ctx)
		qdf_event_set(&sched_ctx->resume_sch_event);
}

void scheduler_register_hdd_suspend_callback(hdd_suspend_callback callback)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (sched_ctx)
		sched_ctx->hdd_callback = callback;
}
void scheduler_wake_up_controller_thread(void)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (sched_ctx)
		qdf_wake_up_interruptible(&sched_ctx->sch_wait_queue);
}
void scheduler_set_event_mask(uint32_t event_mask)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (sched_ctx)
		qdf_set_bit(event_mask, &sched_ctx->sch_event_flag);
}

void scheduler_clear_event_mask(uint32_t event_mask)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (sched_ctx)
		qdf_clear_bit(event_mask, &sched_ctx->sch_event_flag);
}

QDF_STATUS scheduler_target_if_mq_handler(struct scheduler_msg *msg)
{
	QDF_STATUS status;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();
	QDF_STATUS (*target_if_msg_handler)(struct scheduler_msg *);

	if (NULL == msg || NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("msg %p sch %p"),
			msg, sched_ctx);
		return QDF_STATUS_E_FAILURE;
	}

	target_if_msg_handler = msg->callback;

	/* Target_If is a special message queue in phase 3 convergence beacause
	 * its used by both legacy WMA and as well as new UMAC components. New
	 * UMAC components directly pass their message handlers as callback in
	 * message body.
	 * 1) All Legacy WMA messages do not contain message callback so invoke
	 *    registered legacy WMA handler. Scheduler message posting APIs
	 *    makes sure legacy WMA messages do not have callbacks.
	 * 2) For new messages which have valid callbacks invoke their callbacks
	 *    directly.
	 */
	if (NULL == target_if_msg_handler)
		status = sched_ctx->legacy_wma_handler(msg);
	else
		status = target_if_msg_handler(msg);

	return status;
}

QDF_STATUS scheduler_os_if_mq_handler(struct scheduler_msg *msg)
{
	QDF_STATUS (*os_if_msg_handler)(struct scheduler_msg *);

	if (NULL == msg) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("Msg is NULL"));
		return QDF_STATUS_E_FAILURE;
	}

	os_if_msg_handler = msg->callback;

	if (NULL == os_if_msg_handler) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("Msg callback is NULL"));
		QDF_ASSERT(0);
		return QDF_STATUS_E_FAILURE;
	}
	os_if_msg_handler(msg);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_timer_q_mq_handler(struct scheduler_msg *msg)
{
	QDF_STATUS status;
	struct scheduler_ctx *sched_ctx = scheduler_get_context();
	qdf_mc_timer_callback_t timer_q_msg_handler;

	if (NULL == msg || NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("msg %p sch %p"),
			msg, sched_ctx);
		return QDF_STATUS_E_FAILURE;
	}

	timer_q_msg_handler = msg->callback;

	/* Timer message handler */
	if (SYS_MSG_COOKIE == msg->reserved &&
		SYS_MSG_ID_MC_TIMER == msg->type) {
		if (timer_q_msg_handler) {
			status = QDF_STATUS_SUCCESS;
			timer_q_msg_handler(msg->bodyptr);
		} else {
			QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
				QDF_TRACE_LEVEL_ERROR, FL("Timer cb is null"));
			status = QDF_STATUS_E_FAILURE;
		}
		return status;
	} else {
		/* Legacy sys message handler */
		status = sched_ctx->legacy_sys_handler(msg);
		return status;
	}
}

QDF_STATUS scheduler_register_wma_legacy_handler(scheduler_msg_process_fn_t
						wma_callback)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("scheduler context is null"));
		return QDF_STATUS_E_FAILURE;
	}

	sched_ctx->legacy_wma_handler = wma_callback;

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_register_sys_legacy_handler(scheduler_msg_process_fn_t
						sys_callback)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("scheduler context is null"));
		return QDF_STATUS_E_FAILURE;
	}

	sched_ctx->legacy_sys_handler = sys_callback;

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_deregister_wma_legacy_handler(void)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("scheduler context is null"));
		return QDF_STATUS_E_FAILURE;
	}

	sched_ctx->legacy_wma_handler = NULL;

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS scheduler_deregister_sys_legacy_handler(void)
{
	struct scheduler_ctx *sched_ctx = scheduler_get_context();

	if (NULL == sched_ctx) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER,
			QDF_TRACE_LEVEL_ERROR, FL("scheduler context is null"));
		return QDF_STATUS_E_FAILURE;
	}

	sched_ctx->legacy_sys_handler = NULL;

	return QDF_STATUS_SUCCESS;
}

void scheduler_mc_timer_callback(unsigned long data)
{
	qdf_mc_timer_t *timer = (qdf_mc_timer_t *)data;
	struct scheduler_msg msg = {0};
	QDF_STATUS status;

	qdf_mc_timer_callback_t callback = NULL;
	void *user_data = NULL;
	QDF_TIMER_TYPE type = QDF_TIMER_TYPE_SW;

	QDF_ASSERT(timer);

	if (timer == NULL) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			  "%s Null pointer passed in!", __func__);
		return;
	}

	qdf_spin_lock_irqsave(&timer->platform_info.spinlock);

	switch (timer->state) {
	case QDF_TIMER_STATE_STARTING:
		/* we are in this state because someone just started the timer,
		 * MC timer got started and expired, but the time content have
		 * not been updated this is a rare race condition!
		 */
		timer->state = QDF_TIMER_STATE_STOPPED;
		status = QDF_STATUS_E_ALREADY;
		break;

	case QDF_TIMER_STATE_STOPPED:
		status = QDF_STATUS_E_ALREADY;
		break;

	case QDF_TIMER_STATE_UNUSED:
		status = QDF_STATUS_E_EXISTS;
		break;

	case QDF_TIMER_STATE_RUNNING:
		/* need to go to stop state here because the call-back function
		 * may restart timer (to emulate periodic timer)
		 */
		timer->state = QDF_TIMER_STATE_STOPPED;
		/* copy the relevant timer information to local variables;
		 * once we exits from this critical section, the timer content
		 * may be modified by other tasks
		 */
		callback = timer->callback;
		user_data = timer->user_data;
		type = timer->type;
		status = QDF_STATUS_SUCCESS;
		break;

	default:
		QDF_ASSERT(0);
		status = QDF_STATUS_E_FAULT;
		break;
	}

	qdf_spin_unlock_irqrestore(&timer->platform_info.spinlock);

	if (QDF_STATUS_SUCCESS != status) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			  "TIMER callback called in a wrong state=%d",
			  timer->state);
		return;
	}

	qdf_try_allowing_sleep(type);

	if (callback == NULL) {
		QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
			  "%s: No TIMER callback, Couldn't enqueue timer to any queue",
			  __func__);
		QDF_ASSERT(0);
		return;
	}

	/* serialize to scheduler controller thread */
	msg.type = SYS_MSG_ID_MC_TIMER;
	msg.reserved = SYS_MSG_COOKIE;
	msg.callback = callback;
	msg.bodyptr = user_data;
	msg.bodyval = 0;

	if (scheduler_post_msg(QDF_MODULE_ID_SYS, &msg) == QDF_STATUS_SUCCESS)
		return;
	QDF_TRACE(QDF_MODULE_ID_SCHEDULER, QDF_TRACE_LEVEL_ERROR,
		  "%s: Could not enqueue timer to timer queue", __func__);
}
