xref: /openbmc/linux/drivers/infiniband/sw/rxe/rxe_task.c (revision 060f35a317ef09101b128f399dce7ed13d019461)
163fa15dbSBob Pearson // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
28700e3e7SMoni Shoua /*
38700e3e7SMoni Shoua  * Copyright (c) 2016 Mellanox Technologies Ltd. All rights reserved.
48700e3e7SMoni Shoua  * Copyright (c) 2015 System Fabric Works, Inc. All rights reserved.
58700e3e7SMoni Shoua  */
68700e3e7SMoni Shoua 
7eff6d998SBob Pearson #include "rxe.h"
88700e3e7SMoni Shoua 
99b4b7c1fSBob Pearson static struct workqueue_struct *rxe_wq;
109b4b7c1fSBob Pearson 
rxe_alloc_wq(void)119b4b7c1fSBob Pearson int rxe_alloc_wq(void)
129b4b7c1fSBob Pearson {
139b4b7c1fSBob Pearson 	rxe_wq = alloc_workqueue("rxe_wq", WQ_UNBOUND, WQ_MAX_ACTIVE);
149b4b7c1fSBob Pearson 	if (!rxe_wq)
159b4b7c1fSBob Pearson 		return -ENOMEM;
169b4b7c1fSBob Pearson 
179b4b7c1fSBob Pearson 	return 0;
189b4b7c1fSBob Pearson }
199b4b7c1fSBob Pearson 
rxe_destroy_wq(void)209b4b7c1fSBob Pearson void rxe_destroy_wq(void)
219b4b7c1fSBob Pearson {
229b4b7c1fSBob Pearson 	destroy_workqueue(rxe_wq);
239b4b7c1fSBob Pearson }
249b4b7c1fSBob Pearson 
25d9467163SBob Pearson /* Check if task is idle i.e. not running, not scheduled in
269b4b7c1fSBob Pearson  * work queue and not draining. If so move to busy to
27d9467163SBob Pearson  * reserve a slot in do_task() by setting to busy and taking
28d9467163SBob Pearson  * a qp reference to cover the gap from now until the task finishes.
29d9467163SBob Pearson  * state will move out of busy if task returns a non zero value
30d9467163SBob Pearson  * in do_task(). If state is already busy it is raised to armed
31d9467163SBob Pearson  * to indicate to do_task that additional pass should be made
32d9467163SBob Pearson  * over the task.
33d9467163SBob Pearson  * Context: caller should hold task->lock.
34d9467163SBob Pearson  * Returns: true if state transitioned from idle to busy else false.
35d9467163SBob Pearson  */
__reserve_if_idle(struct rxe_task * task)36d9467163SBob Pearson static bool __reserve_if_idle(struct rxe_task *task)
37d9467163SBob Pearson {
38d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
39d9467163SBob Pearson 
40d9467163SBob Pearson 	if (task->state == TASK_STATE_IDLE) {
41d9467163SBob Pearson 		rxe_get(task->qp);
42d9467163SBob Pearson 		task->state = TASK_STATE_BUSY;
43d9467163SBob Pearson 		task->num_sched++;
44d9467163SBob Pearson 		return true;
45d9467163SBob Pearson 	}
46d9467163SBob Pearson 
47d9467163SBob Pearson 	if (task->state == TASK_STATE_BUSY)
48d9467163SBob Pearson 		task->state = TASK_STATE_ARMED;
49d9467163SBob Pearson 
50d9467163SBob Pearson 	return false;
51d9467163SBob Pearson }
52d9467163SBob Pearson 
53d9467163SBob Pearson /* check if task is idle or drained and not currently
549b4b7c1fSBob Pearson  * scheduled in the work queue. This routine is
55d9467163SBob Pearson  * called by rxe_cleanup_task or rxe_disable_task to
56d9467163SBob Pearson  * see if the queue is empty.
57d9467163SBob Pearson  * Context: caller should hold task->lock.
58d9467163SBob Pearson  * Returns true if done else false.
59d9467163SBob Pearson  */
__is_done(struct rxe_task * task)60d9467163SBob Pearson static bool __is_done(struct rxe_task *task)
61d9467163SBob Pearson {
629b4b7c1fSBob Pearson 	if (work_pending(&task->work))
63d9467163SBob Pearson 		return false;
64d9467163SBob Pearson 
65d9467163SBob Pearson 	if (task->state == TASK_STATE_IDLE ||
66d9467163SBob Pearson 	    task->state == TASK_STATE_DRAINED) {
67d9467163SBob Pearson 		return true;
68d9467163SBob Pearson 	}
69d9467163SBob Pearson 
70d9467163SBob Pearson 	return false;
71d9467163SBob Pearson }
72d9467163SBob Pearson 
73d9467163SBob Pearson /* a locked version of __is_done */
is_done(struct rxe_task * task)74d9467163SBob Pearson static bool is_done(struct rxe_task *task)
75d9467163SBob Pearson {
76d9467163SBob Pearson 	unsigned long flags;
77d9467163SBob Pearson 	int done;
78d9467163SBob Pearson 
79d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
80d9467163SBob Pearson 	done = __is_done(task);
81d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
82d9467163SBob Pearson 
83d9467163SBob Pearson 	return done;
84d9467163SBob Pearson }
85d9467163SBob Pearson 
86d9467163SBob Pearson /* do_task is a wrapper for the three tasks (requester,
87d9467163SBob Pearson  * completer, responder) and calls them in a loop until
88d9467163SBob Pearson  * they return a non-zero value. It is called either
89d9467163SBob Pearson  * directly by rxe_run_task or indirectly if rxe_sched_task
90d9467163SBob Pearson  * schedules the task. They must call __reserve_if_idle to
91d9467163SBob Pearson  * move the task to busy before calling or scheduling.
92d9467163SBob Pearson  * The task can also be moved to drained or invalid
939b4b7c1fSBob Pearson  * by calls to rxe_cleanup_task or rxe_disable_task.
94d9467163SBob Pearson  * In that case tasks which get here are not executed but
95d9467163SBob Pearson  * just flushed. The tasks are designed to look to see if
969b4b7c1fSBob Pearson  * there is work to do and then do part of it before returning
97d9467163SBob Pearson  * here with a return value of zero until all the work
989b4b7c1fSBob Pearson  * has been consumed then it returns a non-zero value.
99d9467163SBob Pearson  * The number of times the task can be run is limited by
100d9467163SBob Pearson  * max iterations so one task cannot hold the cpu forever.
1019b4b7c1fSBob Pearson  * If the limit is hit and work remains the task is rescheduled.
1028700e3e7SMoni Shoua  */
do_task(struct rxe_task * task)1039b4b7c1fSBob Pearson static void do_task(struct rxe_task *task)
1048700e3e7SMoni Shoua {
105d9467163SBob Pearson 	unsigned int iterations;
106d9467163SBob Pearson 	unsigned long flags;
107d9467163SBob Pearson 	int resched = 0;
1089b4b7c1fSBob Pearson 	int cont;
1099b4b7c1fSBob Pearson 	int ret;
1108700e3e7SMoni Shoua 
111d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
1128700e3e7SMoni Shoua 
113d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
114d9467163SBob Pearson 	if (task->state >= TASK_STATE_DRAINED) {
115d9467163SBob Pearson 		rxe_put(task->qp);
116d9467163SBob Pearson 		task->num_done++;
117d9467163SBob Pearson 		spin_unlock_irqrestore(&task->lock, flags);
1188700e3e7SMoni Shoua 		return;
1198700e3e7SMoni Shoua 	}
120d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
1218700e3e7SMoni Shoua 
1228700e3e7SMoni Shoua 	do {
123d9467163SBob Pearson 		iterations = RXE_MAX_ITERATIONS;
1248700e3e7SMoni Shoua 		cont = 0;
1258700e3e7SMoni Shoua 
126d9467163SBob Pearson 		do {
127d9467163SBob Pearson 			ret = task->func(task->qp);
128d9467163SBob Pearson 		} while (ret == 0 && iterations-- > 0);
129d9467163SBob Pearson 
130d9467163SBob Pearson 		spin_lock_irqsave(&task->lock, flags);
1319b4b7c1fSBob Pearson 		/* we're not done yet but we ran out of iterations.
1329b4b7c1fSBob Pearson 		 * yield the cpu and reschedule the task
133eff6d998SBob Pearson 		 */
1349b4b7c1fSBob Pearson 		if (!ret) {
135d9467163SBob Pearson 			task->state = TASK_STATE_IDLE;
136d9467163SBob Pearson 			resched = 1;
1379b4b7c1fSBob Pearson 			goto exit;
138eff6d998SBob Pearson 		}
1399b4b7c1fSBob Pearson 
1409b4b7c1fSBob Pearson 		switch (task->state) {
1419b4b7c1fSBob Pearson 		case TASK_STATE_BUSY:
1429b4b7c1fSBob Pearson 			task->state = TASK_STATE_IDLE;
1438700e3e7SMoni Shoua 			break;
1448700e3e7SMoni Shoua 
1459b4b7c1fSBob Pearson 		/* someone tried to schedule the task while we
1469b4b7c1fSBob Pearson 		 * were running, keep going
1478700e3e7SMoni Shoua 		 */
1488700e3e7SMoni Shoua 		case TASK_STATE_ARMED:
1498700e3e7SMoni Shoua 			task->state = TASK_STATE_BUSY;
1508700e3e7SMoni Shoua 			cont = 1;
1518700e3e7SMoni Shoua 			break;
1528700e3e7SMoni Shoua 
153d9467163SBob Pearson 		case TASK_STATE_DRAINING:
154d9467163SBob Pearson 			task->state = TASK_STATE_DRAINED;
155d9467163SBob Pearson 			break;
156d9467163SBob Pearson 
1578700e3e7SMoni Shoua 		default:
158d9467163SBob Pearson 			WARN_ON(1);
159*5dfd5a88SLi Zhijian 			rxe_dbg_qp(task->qp, "unexpected task state = %d\n",
1609b4b7c1fSBob Pearson 				   task->state);
1619b4b7c1fSBob Pearson 			task->state = TASK_STATE_IDLE;
1628700e3e7SMoni Shoua 		}
163d9467163SBob Pearson 
1649b4b7c1fSBob Pearson exit:
165d9467163SBob Pearson 		if (!cont) {
166d9467163SBob Pearson 			task->num_done++;
167d9467163SBob Pearson 			if (WARN_ON(task->num_done != task->num_sched))
1689b4b7c1fSBob Pearson 				rxe_dbg_qp(
1699b4b7c1fSBob Pearson 					task->qp,
170*5dfd5a88SLi Zhijian 					"%ld tasks scheduled, %ld tasks done\n",
171d9467163SBob Pearson 					task->num_sched, task->num_done);
172d9467163SBob Pearson 		}
173d9467163SBob Pearson 		spin_unlock_irqrestore(&task->lock, flags);
1748700e3e7SMoni Shoua 	} while (cont);
1758700e3e7SMoni Shoua 
1768700e3e7SMoni Shoua 	task->ret = ret;
177d9467163SBob Pearson 
178d9467163SBob Pearson 	if (resched)
179d9467163SBob Pearson 		rxe_sched_task(task);
180d9467163SBob Pearson 
181d9467163SBob Pearson 	rxe_put(task->qp);
1828700e3e7SMoni Shoua }
1838700e3e7SMoni Shoua 
1849b4b7c1fSBob Pearson /* wrapper around do_task to fix argument for work queue */
do_work(struct work_struct * work)1859b4b7c1fSBob Pearson static void do_work(struct work_struct *work)
1869b4b7c1fSBob Pearson {
1879b4b7c1fSBob Pearson 	do_task(container_of(work, struct rxe_task, work));
1889b4b7c1fSBob Pearson }
1899b4b7c1fSBob Pearson 
rxe_init_task(struct rxe_task * task,struct rxe_qp * qp,int (* func)(struct rxe_qp *))1903946fc2aSBob Pearson int rxe_init_task(struct rxe_task *task, struct rxe_qp *qp,
1913946fc2aSBob Pearson 		  int (*func)(struct rxe_qp *))
1928700e3e7SMoni Shoua {
193d9467163SBob Pearson 	WARN_ON(rxe_read(qp) <= 0);
194d9467163SBob Pearson 
1953946fc2aSBob Pearson 	task->qp = qp;
1968700e3e7SMoni Shoua 	task->func = func;
197d9467163SBob Pearson 	task->state = TASK_STATE_IDLE;
19863a18baeSBob Pearson 	spin_lock_init(&task->lock);
1999b4b7c1fSBob Pearson 	INIT_WORK(&task->work, do_work);
2008700e3e7SMoni Shoua 
2018700e3e7SMoni Shoua 	return 0;
2028700e3e7SMoni Shoua }
2038700e3e7SMoni Shoua 
204d9467163SBob Pearson /* rxe_cleanup_task is only called from rxe_do_qp_cleanup in
205d9467163SBob Pearson  * process context. The qp is already completed with no
206d9467163SBob Pearson  * remaining references. Once the queue is drained the
207d9467163SBob Pearson  * task is moved to invalid and returns. The qp cleanup
208d9467163SBob Pearson  * code then calls the task functions directly without
209d9467163SBob Pearson  * using the task struct to drain any late arriving packets
210d9467163SBob Pearson  * or work requests.
211d9467163SBob Pearson  */
rxe_cleanup_task(struct rxe_task * task)2128700e3e7SMoni Shoua void rxe_cleanup_task(struct rxe_task *task)
2138700e3e7SMoni Shoua {
214d9467163SBob Pearson 	unsigned long flags;
21507bf9627SAndrew Boyer 
216d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
217d9467163SBob Pearson 	if (!__is_done(task) && task->state < TASK_STATE_DRAINED) {
218d9467163SBob Pearson 		task->state = TASK_STATE_DRAINING;
219d9467163SBob Pearson 	} else {
220d9467163SBob Pearson 		task->state = TASK_STATE_INVALID;
221d9467163SBob Pearson 		spin_unlock_irqrestore(&task->lock, flags);
222d9467163SBob Pearson 		return;
223d9467163SBob Pearson 	}
224d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
225d9467163SBob Pearson 
226d9467163SBob Pearson 	/* now the task cannot be scheduled or run just wait
227d9467163SBob Pearson 	 * for the previously scheduled tasks to finish.
22807bf9627SAndrew Boyer 	 */
229d9467163SBob Pearson 	while (!is_done(task))
230d9467163SBob Pearson 		cond_resched();
23107bf9627SAndrew Boyer 
232d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
233d9467163SBob Pearson 	task->state = TASK_STATE_INVALID;
234d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
2358700e3e7SMoni Shoua }
2368700e3e7SMoni Shoua 
237d9467163SBob Pearson /* run the task inline if it is currently idle
238d9467163SBob Pearson  * cannot call do_task holding the lock
239d9467163SBob Pearson  */
rxe_run_task(struct rxe_task * task)240dccb23f6SBob Pearson void rxe_run_task(struct rxe_task *task)
2418700e3e7SMoni Shoua {
242d9467163SBob Pearson 	unsigned long flags;
2439b4b7c1fSBob Pearson 	bool run;
24407bf9627SAndrew Boyer 
245d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
246d9467163SBob Pearson 
247d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
248d9467163SBob Pearson 	run = __reserve_if_idle(task);
249d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
250d9467163SBob Pearson 
251d9467163SBob Pearson 	if (run)
2529b4b7c1fSBob Pearson 		do_task(task);
2538700e3e7SMoni Shoua }
2548700e3e7SMoni Shoua 
2559b4b7c1fSBob Pearson /* schedule the task to run later as a work queue entry.
2569b4b7c1fSBob Pearson  * the queue_work call can be called holding
257d9467163SBob Pearson  * the lock.
258d9467163SBob Pearson  */
rxe_sched_task(struct rxe_task * task)259dccb23f6SBob Pearson void rxe_sched_task(struct rxe_task *task)
260dccb23f6SBob Pearson {
261d9467163SBob Pearson 	unsigned long flags;
262dccb23f6SBob Pearson 
263d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
264d9467163SBob Pearson 
265d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
266d9467163SBob Pearson 	if (__reserve_if_idle(task))
2679b4b7c1fSBob Pearson 		queue_work(rxe_wq, &task->work);
268d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
269dccb23f6SBob Pearson }
270dccb23f6SBob Pearson 
271d9467163SBob Pearson /* rxe_disable/enable_task are only called from
272d9467163SBob Pearson  * rxe_modify_qp in process context. Task is moved
273d9467163SBob Pearson  * to the drained state by do_task.
274d9467163SBob Pearson  */
rxe_disable_task(struct rxe_task * task)2758700e3e7SMoni Shoua void rxe_disable_task(struct rxe_task *task)
2768700e3e7SMoni Shoua {
277d9467163SBob Pearson 	unsigned long flags;
278d9467163SBob Pearson 
279d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
280d9467163SBob Pearson 
281d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
282d9467163SBob Pearson 	if (!__is_done(task) && task->state < TASK_STATE_DRAINED) {
283d9467163SBob Pearson 		task->state = TASK_STATE_DRAINING;
284d9467163SBob Pearson 	} else {
285d9467163SBob Pearson 		task->state = TASK_STATE_DRAINED;
286d9467163SBob Pearson 		spin_unlock_irqrestore(&task->lock, flags);
287d9467163SBob Pearson 		return;
288d9467163SBob Pearson 	}
289d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
290d9467163SBob Pearson 
291d9467163SBob Pearson 	while (!is_done(task))
292d9467163SBob Pearson 		cond_resched();
293d9467163SBob Pearson 
2949b4b7c1fSBob Pearson 	spin_lock_irqsave(&task->lock, flags);
2959b4b7c1fSBob Pearson 	task->state = TASK_STATE_DRAINED;
2969b4b7c1fSBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
2978700e3e7SMoni Shoua }
2988700e3e7SMoni Shoua 
rxe_enable_task(struct rxe_task * task)2998700e3e7SMoni Shoua void rxe_enable_task(struct rxe_task *task)
3008700e3e7SMoni Shoua {
301d9467163SBob Pearson 	unsigned long flags;
302d9467163SBob Pearson 
303d9467163SBob Pearson 	WARN_ON(rxe_read(task->qp) <= 0);
304d9467163SBob Pearson 
305d9467163SBob Pearson 	spin_lock_irqsave(&task->lock, flags);
306d9467163SBob Pearson 	if (task->state == TASK_STATE_INVALID) {
307d9467163SBob Pearson 		spin_unlock_irqrestore(&task->lock, flags);
308d9467163SBob Pearson 		return;
309d9467163SBob Pearson 	}
3109b4b7c1fSBob Pearson 
311d9467163SBob Pearson 	task->state = TASK_STATE_IDLE;
312d9467163SBob Pearson 	spin_unlock_irqrestore(&task->lock, flags);
3138700e3e7SMoni Shoua }
314