| /* |
| * Copyright (c) 2014-2018 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; |
| |
| #ifndef WLAN_SCHED_REDUCTION_LIMIT |
| #define WLAN_SCHED_REDUCTION_LIMIT 0 |
| #endif |
| |
| DEFINE_QDF_FLEX_MEM_POOL(sched_pool, sizeof(struct scheduler_msg), |
| WLAN_SCHED_REDUCTION_LIMIT); |
| |
| 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_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); |
| 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); |
| 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)); |
| |
| return dup; |
| |
| buffer_full: |
| 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; |
| 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); |
| |
| 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); |
| } |
| |
| qdf_flex_mem_release(&sched_pool); |
| } |
| |