blob: 055af4fff5e6ea05ba51fc9870e8aa608e34e452 [file] [log] [blame]
/*
* Copyright (c) 2014-2019 The Linux Foundation. All rights reserved.
*
* 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.
*/
#include <scheduler_core.h>
#include <qdf_atomic.h>
#include "qdf_flex_mem.h"
static struct scheduler_ctx g_sched_ctx;
static struct scheduler_ctx *gp_sched_ctx;
DEFINE_QDF_FLEX_MEM_POOL(sched_pool, sizeof(struct scheduler_msg),
WLAN_SCHED_REDUCTION_LIMIT);
#ifdef WLAN_SCHED_HISTORY_SIZE
/**
* struct sched_history_item - metrics for a scheduler message
* @callback: the message's execution callback
* @type_id: the message's type_id
* @queue_id: Id of the queue the message was added to
* @queue_start_us: timestamp when the message was queued in microseconds
* @queue_duration_us: duration the message was queued in microseconds
* @queue_depth: depth of the queue when the message was queued
* @run_start_us: timesatmp when the message started execution in microseconds
* @run_duration_us: duration the message was executed in microseconds
*/
struct sched_history_item {
void *callback;
uint32_t type_id;
QDF_MODULE_ID queue_id;
uint64_t queue_start_us;
uint32_t queue_duration_us;
uint32_t queue_depth;
uint64_t run_start_us;
uint32_t run_duration_us;
};
static struct sched_history_item sched_history[WLAN_SCHED_HISTORY_SIZE];
static uint32_t sched_history_index;
static void sched_history_queue(struct scheduler_mq_type *queue,
struct scheduler_msg *msg)
{
msg->queue_id = queue->qid;
msg->queue_depth = qdf_list_size(&queue->mq_list);
msg->queued_at_us = qdf_get_log_timestamp_usecs();
}
static void sched_history_start(struct scheduler_msg *msg)
{
uint64_t started_at_us = qdf_get_log_timestamp_usecs();
struct sched_history_item hist = {
.callback = msg->callback,
.type_id = msg->type,
.queue_start_us = msg->queued_at_us,
.queue_duration_us = started_at_us - msg->queued_at_us,
.queue_depth = msg->queue_depth,
.run_start_us = started_at_us,
};
sched_history[sched_history_index] = hist;
}
static void sched_history_stop(void)
{
struct sched_history_item *hist = &sched_history[sched_history_index];
uint64_t stopped_at_us = qdf_get_log_timestamp_usecs();
hist->run_duration_us = stopped_at_us - hist->run_start_us;
sched_history_index++;
sched_history_index %= WLAN_SCHED_HISTORY_SIZE;
}
#else /* WLAN_SCHED_HISTORY_SIZE */
static inline void sched_history_queue(struct scheduler_mq_type *queue,
struct scheduler_msg *msg) { }
static inline void sched_history_start(struct scheduler_msg *msg) { }
static inline void sched_history_stop(void) { }
#endif /* WLAN_SCHED_HISTORY_SIZE */
QDF_STATUS scheduler_create_ctx(void)
{
qdf_flex_mem_init(&sched_pool);
gp_sched_ctx = &g_sched_ctx;
return QDF_STATUS_SUCCESS;
}
QDF_STATUS scheduler_destroy_ctx(void)
{
gp_sched_ctx = NULL;
qdf_flex_mem_deinit(&sched_pool);
return QDF_STATUS_SUCCESS;
}
struct scheduler_ctx *scheduler_get_context(void)
{
QDF_BUG(gp_sched_ctx);
return gp_sched_ctx;
}
static QDF_STATUS scheduler_mq_init(struct scheduler_mq_type *msg_q)
{
sched_enter();
qdf_spinlock_create(&msg_q->mq_lock);
qdf_list_create(&msg_q->mq_list, SCHEDULER_CORE_MAX_MESSAGES);
sched_exit();
return QDF_STATUS_SUCCESS;
}
static void scheduler_mq_deinit(struct scheduler_mq_type *msg_q)
{
sched_enter();
qdf_list_destroy(&msg_q->mq_list);
qdf_spinlock_destroy(&msg_q->mq_lock);
sched_exit();
}
static qdf_atomic_t __sched_queue_depth;
static qdf_atomic_t __sched_dup_fail_count;
static QDF_STATUS scheduler_all_queues_init(struct scheduler_ctx *sched_ctx)
{
QDF_STATUS status;
int i;
sched_enter();
QDF_BUG(sched_ctx);
if (!sched_ctx)
return QDF_STATUS_E_FAILURE;
qdf_atomic_set(&__sched_queue_depth, 0);
/* Initialize all message queues */
for (i = 0; i < SCHEDULER_NUMBER_OF_MSG_QUEUE; i++) {
status = scheduler_mq_init(&sched_ctx->queue_ctx.sch_msg_q[i]);
if (QDF_STATUS_SUCCESS != status)
return status;
}
/* Initialize all qid to qidx mapping to invalid values */
for (i = 0; i < QDF_MODULE_ID_MAX; i++)
sched_ctx->queue_ctx.scheduler_msg_qid_to_qidx[i] =
SCHEDULER_NUMBER_OF_MSG_QUEUE;
sched_exit();
return status;
}
static QDF_STATUS scheduler_all_queues_deinit(struct scheduler_ctx *sched_ctx)
{
int i;
sched_enter();
QDF_BUG(sched_ctx);
if (!sched_ctx)
return QDF_STATUS_E_FAILURE;
/* De-Initialize all message queues */
for (i = 0; i < SCHEDULER_NUMBER_OF_MSG_QUEUE; i++)
scheduler_mq_deinit(&sched_ctx->queue_ctx.sch_msg_q[i]);
/* Initialize all qid to qidx mapping to invalid values */
for (i = 0; i < QDF_MODULE_ID_MAX; i++)
sched_ctx->queue_ctx.scheduler_msg_qid_to_qidx[i] =
SCHEDULER_NUMBER_OF_MSG_QUEUE;
sched_exit();
return QDF_STATUS_SUCCESS;
}
void scheduler_mq_put(struct scheduler_mq_type *msg_q,
struct scheduler_msg *msg)
{
qdf_spin_lock_irqsave(&msg_q->mq_lock);
sched_history_queue(msg_q, msg);
qdf_list_insert_back(&msg_q->mq_list, &msg->node);
qdf_spin_unlock_irqrestore(&msg_q->mq_lock);
}
void scheduler_mq_put_front(struct scheduler_mq_type *msg_q,
struct scheduler_msg *msg)
{
qdf_spin_lock_irqsave(&msg_q->mq_lock);
sched_history_queue(msg_q, msg);
qdf_list_insert_front(&msg_q->mq_list, &msg->node);
qdf_spin_unlock_irqrestore(&msg_q->mq_lock);
}
struct scheduler_msg *scheduler_mq_get(struct scheduler_mq_type *msg_q)
{
QDF_STATUS status;
qdf_list_node_t *node;
qdf_spin_lock_irqsave(&msg_q->mq_lock);
status = qdf_list_remove_front(&msg_q->mq_list, &node);
qdf_spin_unlock_irqrestore(&msg_q->mq_lock);
if (QDF_IS_STATUS_ERROR(status))
return NULL;
return qdf_container_of(node, struct scheduler_msg, node);
}
QDF_STATUS scheduler_queues_deinit(struct scheduler_ctx *sched_ctx)
{
return scheduler_all_queues_deinit(sched_ctx);
}
QDF_STATUS scheduler_queues_init(struct scheduler_ctx *sched_ctx)
{
QDF_STATUS status;
sched_enter();
QDF_BUG(sched_ctx);
if (!sched_ctx)
return QDF_STATUS_E_FAILURE;
status = scheduler_all_queues_init(sched_ctx);
if (QDF_IS_STATUS_ERROR(status)) {
scheduler_all_queues_deinit(sched_ctx);
sched_err("Failed to initialize the msg queues");
return status;
}
sched_debug("Queue init passed");
sched_exit();
return QDF_STATUS_SUCCESS;
}
struct scheduler_msg *scheduler_core_msg_dup(struct scheduler_msg *msg)
{
struct scheduler_msg *dup;
if (qdf_atomic_inc_return(&__sched_queue_depth) >
SCHEDULER_CORE_MAX_MESSAGES)
goto buffer_full;
dup = qdf_flex_mem_alloc(&sched_pool);
if (!dup) {
sched_err("out of memory");
goto dec_queue_count;
}
qdf_mem_copy(dup, msg, sizeof(*dup));
qdf_atomic_set(&__sched_dup_fail_count, 0);
return dup;
buffer_full:
if (qdf_atomic_inc_return(&__sched_dup_fail_count) >
SCHEDULER_WRAPPER_MAX_FAIL_COUNT)
QDF_DEBUG_PANIC("Scheduler buffer is full");
dec_queue_count:
qdf_atomic_dec(&__sched_queue_depth);
return NULL;
}
void scheduler_core_msg_free(struct scheduler_msg *msg)
{
qdf_flex_mem_free(&sched_pool, msg);
qdf_atomic_dec(&__sched_queue_depth);
}
static void scheduler_thread_process_queues(struct scheduler_ctx *sch_ctx,
bool *shutdown)
{
int i;
QDF_STATUS status;
struct scheduler_msg *msg;
if (!sch_ctx) {
QDF_DEBUG_PANIC("sch_ctx is null");
return;
}
/* start with highest priority queue : timer queue at index 0 */
i = 0;
while (i < SCHEDULER_NUMBER_OF_MSG_QUEUE) {
/* Check if MC needs to shutdown */
if (qdf_atomic_test_bit(MC_SHUTDOWN_EVENT_MASK,
&sch_ctx->sch_event_flag)) {
sched_debug("scheduler thread signaled to shutdown");
*shutdown = true;
/* Check for any Suspend Indication */
if (qdf_atomic_test_and_clear_bit(MC_SUSPEND_EVENT_MASK,
&sch_ctx->sch_event_flag)) {
/* Unblock anyone waiting on suspend */
if (gp_sched_ctx->hdd_callback)
gp_sched_ctx->hdd_callback();
}
break;
}
msg = scheduler_mq_get(&sch_ctx->queue_ctx.sch_msg_q[i]);
if (!msg) {
/* check next queue */
i++;
continue;
}
if (sch_ctx->queue_ctx.scheduler_msg_process_fn[i]) {
sch_ctx->watchdog_msg_type = msg->type;
sch_ctx->watchdog_callback = msg->callback;
sched_history_start(msg);
qdf_timer_start(&sch_ctx->watchdog_timer,
SCHEDULER_WATCHDOG_TIMEOUT);
status = sch_ctx->queue_ctx.
scheduler_msg_process_fn[i](msg);
qdf_timer_stop(&sch_ctx->watchdog_timer);
sched_history_stop();
if (QDF_IS_STATUS_ERROR(status))
sched_err("Failed processing Qid[%d] message",
sch_ctx->queue_ctx.sch_msg_q[i].qid);
scheduler_core_msg_free(msg);
}
/* start again with highest priority queue at index 0 */
i = 0;
}
/* Check for any Suspend Indication */
if (qdf_atomic_test_and_clear_bit(MC_SUSPEND_EVENT_MASK,
&sch_ctx->sch_event_flag)) {
qdf_spin_lock(&sch_ctx->sch_thread_lock);
qdf_event_reset(&sch_ctx->resume_sch_event);
/* controller thread suspend completion callback */
if (gp_sched_ctx->hdd_callback)
gp_sched_ctx->hdd_callback();
qdf_spin_unlock(&sch_ctx->sch_thread_lock);
/* Wait for resume indication */
qdf_wait_single_event(&sch_ctx->resume_sch_event, 0);
}
return; /* Nothing to process wait on wait queue */
}
int scheduler_thread(void *arg)
{
struct scheduler_ctx *sch_ctx = (struct scheduler_ctx *)arg;
int retWaitStatus = 0;
bool shutdown = false;
if (!arg) {
QDF_DEBUG_PANIC("arg is null");
return 0;
}
qdf_set_user_nice(current, -2);
/* Ack back to the context from which the main controller thread
* has been created
*/
qdf_event_set(&sch_ctx->sch_start_event);
sched_debug("scheduler thread %d (%s) starting up",
current->pid, current->comm);
while (!shutdown) {
/* This implements the execution model algorithm */
retWaitStatus = qdf_wait_queue_interruptible(
sch_ctx->sch_wait_queue,
qdf_atomic_test_bit(MC_POST_EVENT_MASK,
&sch_ctx->sch_event_flag) ||
qdf_atomic_test_bit(MC_SUSPEND_EVENT_MASK,
&sch_ctx->sch_event_flag));
if (retWaitStatus == -ERESTARTSYS)
QDF_DEBUG_PANIC("Scheduler received -ERESTARTSYS");
qdf_atomic_clear_bit(MC_POST_EVENT_MASK, &sch_ctx->sch_event_flag);
scheduler_thread_process_queues(sch_ctx, &shutdown);
}
/* If we get here the scheduler thread must exit */
sched_debug("Scheduler thread exiting");
qdf_event_set(&sch_ctx->sch_shutdown);
qdf_exit_thread(QDF_STATUS_SUCCESS);
return 0;
}
static void scheduler_flush_single_queue(struct scheduler_mq_type *mq)
{
struct scheduler_msg *msg;
QDF_STATUS (*flush_cb)(struct scheduler_msg *);
while ((msg = scheduler_mq_get(mq))) {
if (msg->flush_callback) {
sched_debug("Calling flush callback; type: %x",
msg->type);
flush_cb = msg->flush_callback;
flush_cb(msg);
} else if (msg->bodyptr) {
sched_debug("Freeing scheduler msg bodyptr; type: %x",
msg->type);
qdf_mem_free(msg->bodyptr);
}
scheduler_core_msg_free(msg);
}
}
void scheduler_queues_flush(struct scheduler_ctx *sched_ctx)
{
struct scheduler_mq_type *mq;
int i;
sched_debug("Flushing scheduler message queues");
for (i = 0; i < SCHEDULER_NUMBER_OF_MSG_QUEUE; i++) {
mq = &sched_ctx->queue_ctx.sch_msg_q[i];
scheduler_flush_single_queue(mq);
}
}