summaryrefslogtreecommitdiff
path: root/drivers/infiniband/sw/rxe/rxe_task.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/infiniband/sw/rxe/rxe_task.c')
-rw-r--r--drivers/infiniband/sw/rxe/rxe_task.c305
1 files changed, 229 insertions, 76 deletions
diff --git a/drivers/infiniband/sw/rxe/rxe_task.c b/drivers/infiniband/sw/rxe/rxe_task.c
index 6951fdcb31bf..f522820b950c 100644
--- a/drivers/infiniband/sw/rxe/rxe_task.c
+++ b/drivers/infiniband/sw/rxe/rxe_task.c
@@ -4,143 +4,296 @@
* Copyright (c) 2015 System Fabric Works, Inc. All rights reserved.
*/
-#include <linux/kernel.h>
-#include <linux/interrupt.h>
-#include <linux/hardirq.h>
+#include "rxe.h"
-#include "rxe_task.h"
+static struct workqueue_struct *rxe_wq;
-int __rxe_do_task(struct rxe_task *task)
+int rxe_alloc_wq(void)
+{
+ rxe_wq = alloc_workqueue("rxe_wq", WQ_UNBOUND, WQ_MAX_ACTIVE);
+ if (!rxe_wq)
+ return -ENOMEM;
+ return 0;
+}
+
+void rxe_destroy_wq(void)
{
- int ret;
+ destroy_workqueue(rxe_wq);
+}
- while ((ret = task->func(task->arg)) == 0)
- ;
+/* Check if task is idle i.e. not running, not scheduled in
+ * work queue and not draining. If so move to busy to
+ * reserve a slot in do_task() by setting to busy and taking
+ * a qp reference to cover the gap from now until the task finishes.
+ * state will move out of busy if task returns a non zero value
+ * in do_task(). If state is already busy it is raised to armed
+ * to indicate to do_task that additional pass should be made
+ * over the task.
+ * Context: caller should hold task->lock.
+ * Returns: true if state transitioned from idle to busy else false.
+ */
+static bool __reserve_if_idle(struct rxe_task *task)
+{
+ WARN_ON(rxe_read(task->qp) <= 0);
- task->ret = ret;
+ if (task->state == TASK_STATE_IDLE) {
+ rxe_get(task->qp);
+ task->state = TASK_STATE_BUSY;
+ task->num_sched++;
+ return true;
+ }
- return ret;
+ if (task->state == TASK_STATE_BUSY)
+ task->state = TASK_STATE_ARMED;
+
+ return false;
}
-/*
- * this locking is due to a potential race where
- * a second caller finds the task already running
- * but looks just after the last call to func
+/* check if task is idle or drained and not currently
+ * scheduled in the work queue. This routine is
+ * called by rxe_cleanup_task or rxe_disable_task to
+ * see if the queue is empty.
+ * Context: caller should hold task->lock.
+ * Returns true if done else false.
*/
-void rxe_do_task(struct tasklet_struct *t)
+static bool __is_done(struct rxe_task *task)
+{
+ if (work_pending(&task->work))
+ return false;
+
+ if (task->state == TASK_STATE_IDLE ||
+ task->state == TASK_STATE_DRAINED) {
+ return true;
+ }
+
+ return false;
+}
+
+/* a locked version of __is_done */
+static bool is_done(struct rxe_task *task)
{
- int cont;
- int ret;
unsigned long flags;
- struct rxe_task *task = from_tasklet(task, t, tasklet);
+ int done;
- spin_lock_irqsave(&task->state_lock, flags);
- switch (task->state) {
- case TASK_STATE_START:
- task->state = TASK_STATE_BUSY;
- spin_unlock_irqrestore(&task->state_lock, flags);
- break;
+ spin_lock_irqsave(&task->lock, flags);
+ done = __is_done(task);
+ spin_unlock_irqrestore(&task->lock, flags);
- case TASK_STATE_BUSY:
- task->state = TASK_STATE_ARMED;
- fallthrough;
- case TASK_STATE_ARMED:
- spin_unlock_irqrestore(&task->state_lock, flags);
- return;
+ return done;
+}
+
+/* do_task is a wrapper for the three tasks (requester,
+ * completer, responder) and calls them in a loop until
+ * they return a non-zero value. It is called indirectly
+ * when rxe_sched_task schedules the task. They must
+ * call __reserve_if_idle to move the task to busy before
+ * calling or scheduling. The task can also be moved to
+ * drained or invalid by calls to rxe_cleanup_task or
+ * rxe_disable_task. In that case tasks which get here
+ * are not executed but just flushed. The tasks are
+ * designed to look to see if there is work to do and
+ * then do part of it before returning here with a return
+ * value of zero until all the work has been consumed then
+ * it returns a non-zero value.
+ * The number of times the task can be run is limited by
+ * max iterations so one task cannot hold the cpu forever.
+ * If the limit is hit and work remains the task is rescheduled.
+ */
+static void do_task(struct rxe_task *task)
+{
+ unsigned int iterations;
+ unsigned long flags;
+ int resched = 0;
+ int cont;
+ int ret;
- default:
- spin_unlock_irqrestore(&task->state_lock, flags);
- pr_warn("%s failed with bad state %d\n", __func__, task->state);
+ WARN_ON(rxe_read(task->qp) <= 0);
+
+ spin_lock_irqsave(&task->lock, flags);
+ if (task->state >= TASK_STATE_DRAINED) {
+ rxe_put(task->qp);
+ task->num_done++;
+ spin_unlock_irqrestore(&task->lock, flags);
return;
}
+ spin_unlock_irqrestore(&task->lock, flags);
do {
+ iterations = RXE_MAX_ITERATIONS;
cont = 0;
- ret = task->func(task->arg);
- spin_lock_irqsave(&task->state_lock, flags);
+ do {
+ ret = task->func(task->qp);
+ } while (ret == 0 && iterations-- > 0);
+
+ spin_lock_irqsave(&task->lock, flags);
+ /* we're not done yet but we ran out of iterations.
+ * yield the cpu and reschedule the task
+ */
+ if (!ret) {
+ if (task->state != TASK_STATE_DRAINING) {
+ task->state = TASK_STATE_IDLE;
+ resched = 1;
+ } else {
+ cont = 1;
+ }
+ goto exit;
+ }
+
switch (task->state) {
case TASK_STATE_BUSY:
- if (ret)
- task->state = TASK_STATE_START;
- else
- cont = 1;
+ task->state = TASK_STATE_IDLE;
break;
- /* soneone tried to run the task since the last time we called
- * func, so we will call one more time regardless of the
- * return value
+ /* someone tried to schedule the task while we
+ * were running, keep going
*/
case TASK_STATE_ARMED:
task->state = TASK_STATE_BUSY;
cont = 1;
break;
+ case TASK_STATE_DRAINING:
+ task->state = TASK_STATE_DRAINED;
+ break;
+
default:
- pr_warn("%s failed with bad state %d\n", __func__,
- task->state);
+ WARN_ON(1);
+ rxe_dbg_qp(task->qp, "unexpected task state = %d\n",
+ task->state);
+ task->state = TASK_STATE_IDLE;
}
- spin_unlock_irqrestore(&task->state_lock, flags);
+
+exit:
+ if (!cont) {
+ task->num_done++;
+ if (WARN_ON(task->num_done != task->num_sched))
+ rxe_dbg_qp(
+ task->qp,
+ "%ld tasks scheduled, %ld tasks done\n",
+ task->num_sched, task->num_done);
+ }
+ spin_unlock_irqrestore(&task->lock, flags);
} while (cont);
task->ret = ret;
+
+ if (resched)
+ rxe_sched_task(task);
+
+ rxe_put(task->qp);
}
-int rxe_init_task(void *obj, struct rxe_task *task,
- void *arg, int (*func)(void *), char *name)
+/* wrapper around do_task to fix argument for work queue */
+static void do_work(struct work_struct *work)
{
- task->obj = obj;
- task->arg = arg;
- task->func = func;
- snprintf(task->name, sizeof(task->name), "%s", name);
- task->destroyed = false;
+ do_task(container_of(work, struct rxe_task, work));
+}
- tasklet_setup(&task->tasklet, rxe_do_task);
+int rxe_init_task(struct rxe_task *task, struct rxe_qp *qp,
+ int (*func)(struct rxe_qp *))
+{
+ WARN_ON(rxe_read(qp) <= 0);
- task->state = TASK_STATE_START;
- spin_lock_init(&task->state_lock);
+ task->qp = qp;
+ task->func = func;
+ task->state = TASK_STATE_IDLE;
+ spin_lock_init(&task->lock);
+ INIT_WORK(&task->work, do_work);
return 0;
}
+/* rxe_cleanup_task is only called from rxe_do_qp_cleanup in
+ * process context. The qp is already completed with no
+ * remaining references. Once the queue is drained the
+ * task is moved to invalid and returns. The qp cleanup
+ * code then calls the task functions directly without
+ * using the task struct to drain any late arriving packets
+ * or work requests.
+ */
void rxe_cleanup_task(struct rxe_task *task)
{
unsigned long flags;
- bool idle;
- /*
- * Mark the task, then wait for it to finish. It might be
- * running in a non-tasklet (direct call) context.
- */
- task->destroyed = true;
+ spin_lock_irqsave(&task->lock, flags);
+ if (!__is_done(task) && task->state < TASK_STATE_DRAINED) {
+ task->state = TASK_STATE_DRAINING;
+ } else {
+ task->state = TASK_STATE_INVALID;
+ spin_unlock_irqrestore(&task->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&task->lock, flags);
- do {
- spin_lock_irqsave(&task->state_lock, flags);
- idle = (task->state == TASK_STATE_START);
- spin_unlock_irqrestore(&task->state_lock, flags);
- } while (!idle);
+ /* now the task cannot be scheduled or run just wait
+ * for the previously scheduled tasks to finish.
+ */
+ while (!is_done(task))
+ cond_resched();
- tasklet_kill(&task->tasklet);
+ spin_lock_irqsave(&task->lock, flags);
+ task->state = TASK_STATE_INVALID;
+ spin_unlock_irqrestore(&task->lock, flags);
}
-void rxe_run_task(struct rxe_task *task, int sched)
+/* schedule the task to run later as a work queue entry.
+ * the queue_work call can be called holding
+ * the lock.
+ */
+void rxe_sched_task(struct rxe_task *task)
{
- if (task->destroyed)
- return;
+ unsigned long flags;
+
+ WARN_ON(rxe_read(task->qp) <= 0);
- if (sched)
- tasklet_schedule(&task->tasklet);
- else
- rxe_do_task(&task->tasklet);
+ spin_lock_irqsave(&task->lock, flags);
+ if (__reserve_if_idle(task))
+ queue_work(rxe_wq, &task->work);
+ spin_unlock_irqrestore(&task->lock, flags);
}
+/* rxe_disable/enable_task are only called from
+ * rxe_modify_qp in process context. Task is moved
+ * to the drained state by do_task.
+ */
void rxe_disable_task(struct rxe_task *task)
{
- tasklet_disable(&task->tasklet);
+ unsigned long flags;
+
+ WARN_ON(rxe_read(task->qp) <= 0);
+
+ spin_lock_irqsave(&task->lock, flags);
+ if (!__is_done(task) && task->state < TASK_STATE_DRAINED) {
+ task->state = TASK_STATE_DRAINING;
+ } else {
+ task->state = TASK_STATE_DRAINED;
+ spin_unlock_irqrestore(&task->lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&task->lock, flags);
+
+ while (!is_done(task))
+ cond_resched();
+
+ spin_lock_irqsave(&task->lock, flags);
+ task->state = TASK_STATE_DRAINED;
+ spin_unlock_irqrestore(&task->lock, flags);
}
void rxe_enable_task(struct rxe_task *task)
{
- tasklet_enable(&task->tasklet);
+ unsigned long flags;
+
+ WARN_ON(rxe_read(task->qp) <= 0);
+
+ spin_lock_irqsave(&task->lock, flags);
+ if (task->state == TASK_STATE_INVALID) {
+ spin_unlock_irqrestore(&task->lock, flags);
+ return;
+ }
+
+ task->state = TASK_STATE_IDLE;
+ spin_unlock_irqrestore(&task->lock, flags);
}