blob: 6598cbae4ed448dd8111763fb6e086cc76d54592 [file] [log] [blame]
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
/* Implementation of Host Target Communication tasks,
* WorkTask and compl_task, which are used to manage
* the Mbox Pending Queues.
*
* A mailbox Send request is queued in arrival order on
* a per-mailbox Send queue until a credit is available
* from the Target. Requests in this queue are
* waiting for the Target to provide tx credits (i.e. recv
* buffers on the Target-side).
*
* A mailbox Recv request is queued in arrival order on
* a per-mailbox Recv queue until a message is available
* to be read. So requests in this queue are waiting for
* the Target to provide rx data.
*
* htca_work_task dequeues requests from the SendPendingQueue
* (once credits are available) and dequeues requests from
* the RecvPendingQueue (once rx data is available) and
* hands them to HIF for processing.
*
* htca_compl_task handles completion processing after
* HIF completes a request.
*
* The main purpose of these tasks is to provide a
* suitable suspendable context for processing requests
* and completions.
*/
#include <linux/completion.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/spinlock_types.h>
#include <linux/wait.h>
#include "../hif_sdio/hif.h"
#include "htca.h"
#include "htca_mbox_internal.h"
/* Wakeup the htca_work_task.
*
* Invoked whenever send/recv state changes:
* new Send buffer added to the send_pending_queue
* new Recv buffer added to the recv_pending_queue
* tx credits are reaped
* rx data available recognized
*/
void htca_work_task_poke(struct htca_target *target)
{
target->work_task_has_work = true;
wake_up_interruptible_sync(&target->work_task_wait);
}
/* Body of the htca_work_task, which hands Send and
* Receive requests to HIF.
*/
static int htca_work_task_core(struct htca_target *target)
{
int ep;
int work_done = 0;
/* TBD: We might consider alternative ordering policies, here,
* between Sends and Recvs and among mailboxes. The current
* algorithm is simple.
*/
/* Process sends/recvs */
for (ep = 0; ep < HTCA_NUM_MBOX; ep++) {
htcadebug("Call (%d)\n", ep);
work_done += htca_manage_pending_sends(target, ep);
htcadebug("Call (%d)\n", ep);
work_done += htca_manage_pending_recvs(target, ep);
}
return work_done;
}
/* Only this work_task is permitted to update
* interrupt enables. That restriction eliminates
* complex race conditions.
*/
static int htca_work_task(void *param)
{
struct htca_target *target = (struct htca_target *)param;
/* set_user_nice(current, -3); */
set_current_state(TASK_INTERRUPTIBLE);
for (;;) {
htcadebug("top of loop. intr_state=%d\n", target->intr_state);
/* Wait for htca_work_task_poke */
wait_event_interruptible(target->work_task_wait,
target->work_task_has_work);
if (target->work_task_shutdown)
break; /* htcaTaskStop invoked */
if (!target->work_task_has_work)
break; /* exit, if this task was interrupted */
/* reset before we start work */
target->work_task_has_work = false;
barrier();
if (target->need_start_polling) {
/* reset for next time */
target->need_start_polling = 0;
target->intr_state = HTCA_POLL;
htca_update_intr_enbs(target, 1);
}
while (htca_work_task_core(target))
;
if (target->pending_recv_mask ||
target->pending_register_refresh) {
continue;
}
/* When a Recv completes, it sets need_register_refresh=1
* and pokes the work_task.
*
* We won't actually initiate a register refresh until
* pending recvs on ALL eps have completed. This may
* increase latency slightly but it increases efficiency
* and reduces chatter which should improve throughput.
* Note that even though we don't initiate the register
* refresh immediately, SDIO is still 100% busy doing
* useful work. The refresh is issued shortly after.
*/
if (target->need_register_refresh) {
/* Continue to poll. When the RegsiterRefresh
* completes, the WorkTask will be poked.
*/
target->need_register_refresh = 0;
htca_register_refresh_start(target);
continue;
}
/* If more work has arrived since we last checked,
* make another pass.
*/
if (target->work_task_has_work)
continue;
/* As long as we are constantly refreshing register
* state and reprocessing, there is no need to
* enable interrupts. We are essentially POLLING for
* interrupts anyway. But if
* -we were in POLL mode and
* -we have processed all outstanding sends/recvs and
* -there are no PENDING recv operations and
* -there is no pending register refresh (so
* no recv operations have completed since the
* last time we refreshed register state)
* then we switch to INTERRUPT mode and re-enable
* Target-side interrupts.
*
* We'll sleep until poked:
* -DSR handler receives an interrupt
* -application enqueues a new send/recv buffer
* We must also UPDATE interrupt enables even if we
* were already in INTERRUPT mode, since some bits
* may have changed.
*/
if (target->intr_state == HTCA_POLL) {
target->intr_state = HTCA_INTERRUPT;
htca_update_intr_enbs(target, 0);
}
}
complete_and_exit(&target->work_task_completion, 0);
return 0;
}
int htca_work_task_start(struct htca_target *target)
{
int status = HTCA_ERROR;
if (mutex_lock_interruptible(&target->task_mutex))
return HTCA_ERROR; /* interrupted */
if (target->work_task)
goto done; /* already started */
target->work_task = kthread_create(htca_work_task, target, "htcaWork");
if (!target->work_task)
goto done; /* Failed to create task */
target->work_task_shutdown = false;
init_waitqueue_head(&target->work_task_wait);
init_completion(&target->work_task_completion);
wake_up_process(target->work_task);
status = HTCA_OK;
done:
mutex_unlock(&target->task_mutex);
return status;
}
void htca_work_task_stop(struct htca_target *target)
{
if (mutex_lock_interruptible(&target->task_mutex))
return; /* interrupted */
if (!target->work_task)
goto done;
target->work_task_shutdown = true;
htca_work_task_poke(target);
wait_for_completion(&target->work_task_completion);
target->work_task = NULL;
done:
mutex_unlock(&target->task_mutex);
}
/* Wakeup the compl_task.
* Invoked after adding a new completion to the compl_queue.
*/
void htca_compl_task_poke(struct htca_target *target)
{
target->compl_task_has_work = true;
wake_up_interruptible_sync(&target->compl_task_wait);
}
static int htca_manage_compl(struct htca_target *target)
{
struct htca_request *req;
unsigned long flags;
/* Pop a request from the completion queue */
spin_lock_irqsave(&target->compl_queue_lock, flags);
req = htca_request_deq_head(&target->compl_queue);
spin_unlock_irqrestore(&target->compl_queue_lock, flags);
if (!req)
return 0; /* nothing to do */
/* Invoke request's corresponding completion function */
if (req->completion_cb)
req->completion_cb(req, req->status);
return 1;
}
static int htca_compl_task(void *param)
{
struct htca_target *target = (struct htca_target *)param;
/* set_user_nice(current, -3); */
set_current_state(TASK_INTERRUPTIBLE);
for (;;) {
/* Wait for htca_compl_task_poke */
wait_event_interruptible(target->compl_task_wait,
target->compl_task_has_work);
if (target->compl_task_shutdown)
break; /* htcaTaskStop invoked */
if (!target->compl_task_has_work)
break; /* exit, if this task was interrupted */
/* reset before we start work */
target->compl_task_has_work = false;
barrier();
/* TBD: We could try to prioritize completions rather than
* handle them strictly in order. Could use separate queues for
* register completions and mailbox completion on each endpoint.
* In general, completion processing is expected to be short
* so this probably isn't worth the additional complexity.
*/
{
int did_work;
do {
did_work = htca_manage_compl(target);
} while (did_work);
}
}
complete_and_exit(&target->compl_cask_completion, 0);
return 0;
}
int htca_compl_task_start(struct htca_target *target)
{
int status = HTCA_ERROR;
if (mutex_lock_interruptible(&target->task_mutex))
return HTCA_ERROR; /* interrupted */
if (target->compl_task)
goto done; /* already started */
target->compl_task =
kthread_create(htca_compl_task, target, "htcaCompl");
if (!target->compl_task)
goto done; /* Failed to create task */
target->compl_task_shutdown = false;
init_waitqueue_head(&target->compl_task_wait);
init_completion(&target->compl_cask_completion);
wake_up_process(target->compl_task);
status = HTCA_OK;
done:
mutex_unlock(&target->task_mutex);
return status;
}
void htca_compl_task_stop(struct htca_target *target)
{
if (mutex_lock_interruptible(&target->task_mutex))
return; /* interrupted */
if (!target->compl_task)
goto done;
target->compl_task_shutdown = true;
htca_compl_task_poke(target);
wait_for_completion(&target->compl_cask_completion);
target->compl_task = NULL;
done:
mutex_unlock(&target->task_mutex);
}