xref: /linux/drivers/net/wwan/iosm/iosm_ipc_task_queue.c (revision 4bdffd2708d65e68ff254d90793bb167d828219f)
1  // SPDX-License-Identifier: GPL-2.0-only
2  /*
3   * Copyright (C) 2020-21 Intel Corporation.
4   */
5  
6  #include "iosm_ipc_imem.h"
7  #include "iosm_ipc_task_queue.h"
8  
9  /* Actual tasklet function, will be called whenever tasklet is scheduled.
10   * Calls event handler involves callback for each element in the message queue
11   */
12  static void ipc_task_queue_handler(unsigned long data)
13  {
14  	struct ipc_task_queue *ipc_task = (struct ipc_task_queue *)data;
15  	unsigned int q_rpos = ipc_task->q_rpos;
16  
17  	/* Loop over the input queue contents. */
18  	while (q_rpos != ipc_task->q_wpos) {
19  		/* Get the current first queue element. */
20  		struct ipc_task_queue_args *args = &ipc_task->args[q_rpos];
21  
22  		/* Process the input message. */
23  		if (args->func)
24  			args->response = args->func(args->ipc_imem, args->arg,
25  						    args->msg, args->size);
26  
27  		/* Signal completion for synchronous calls */
28  		if (args->completion)
29  			complete(args->completion);
30  
31  		/* Free message if copy was allocated. */
32  		if (args->is_copy)
33  			kfree(args->msg);
34  
35  		/* Set invalid queue element. Technically
36  		 * spin_lock_irqsave is not required here as
37  		 * the array element has been processed already
38  		 * so we can assume that immediately after processing
39  		 * ipc_task element, queue will not rotate again to
40  		 * ipc_task same element within such short time.
41  		 */
42  		args->completion = NULL;
43  		args->func = NULL;
44  		args->msg = NULL;
45  		args->size = 0;
46  		args->is_copy = false;
47  
48  		/* calculate the new read ptr and update the volatile read
49  		 * ptr
50  		 */
51  		q_rpos = (q_rpos + 1) % IPC_THREAD_QUEUE_SIZE;
52  		ipc_task->q_rpos = q_rpos;
53  	}
54  }
55  
56  /* Free memory alloc and trigger completions left in the queue during dealloc */
57  static void ipc_task_queue_cleanup(struct ipc_task_queue *ipc_task)
58  {
59  	unsigned int q_rpos = ipc_task->q_rpos;
60  
61  	while (q_rpos != ipc_task->q_wpos) {
62  		struct ipc_task_queue_args *args = &ipc_task->args[q_rpos];
63  
64  		if (args->completion)
65  			complete(args->completion);
66  
67  		if (args->is_copy)
68  			kfree(args->msg);
69  
70  		q_rpos = (q_rpos + 1) % IPC_THREAD_QUEUE_SIZE;
71  		ipc_task->q_rpos = q_rpos;
72  	}
73  }
74  
75  /* Add a message to the queue and trigger the ipc_task. */
76  static int
77  ipc_task_queue_add_task(struct iosm_imem *ipc_imem,
78  			int arg, void *msg,
79  			int (*func)(struct iosm_imem *ipc_imem, int arg,
80  				    void *msg, size_t size),
81  			size_t size, bool is_copy, bool wait)
82  {
83  	struct tasklet_struct *ipc_tasklet = ipc_imem->ipc_task->ipc_tasklet;
84  	struct ipc_task_queue *ipc_task = &ipc_imem->ipc_task->ipc_queue;
85  	struct completion completion;
86  	unsigned int pos, nextpos;
87  	unsigned long flags;
88  	int result = -EIO;
89  
90  	init_completion(&completion);
91  
92  	/* tasklet send may be called from both interrupt or thread
93  	 * context, therefore protect queue operation by spinlock
94  	 */
95  	spin_lock_irqsave(&ipc_task->q_lock, flags);
96  
97  	pos = ipc_task->q_wpos;
98  	nextpos = (pos + 1) % IPC_THREAD_QUEUE_SIZE;
99  
100  	/* Get next queue position. */
101  	if (nextpos != ipc_task->q_rpos) {
102  		/* Get the reference to the queue element and save the passed
103  		 * values.
104  		 */
105  		ipc_task->args[pos].arg = arg;
106  		ipc_task->args[pos].msg = msg;
107  		ipc_task->args[pos].func = func;
108  		ipc_task->args[pos].ipc_imem = ipc_imem;
109  		ipc_task->args[pos].size = size;
110  		ipc_task->args[pos].is_copy = is_copy;
111  		ipc_task->args[pos].completion = wait ? &completion : NULL;
112  		ipc_task->args[pos].response = -1;
113  
114  		/* apply write barrier so that ipc_task->q_rpos elements
115  		 * are updated before ipc_task->q_wpos is being updated.
116  		 */
117  		smp_wmb();
118  
119  		/* Update the status of the free queue space. */
120  		ipc_task->q_wpos = nextpos;
121  		result = 0;
122  	}
123  
124  	spin_unlock_irqrestore(&ipc_task->q_lock, flags);
125  
126  	if (result == 0) {
127  		tasklet_schedule(ipc_tasklet);
128  
129  		if (wait) {
130  			wait_for_completion(&completion);
131  			result = ipc_task->args[pos].response;
132  		}
133  	} else {
134  		dev_err(ipc_imem->ipc_task->dev, "queue is full");
135  	}
136  
137  	return result;
138  }
139  
140  int ipc_task_queue_send_task(struct iosm_imem *imem,
141  			     int (*func)(struct iosm_imem *ipc_imem, int arg,
142  					 void *msg, size_t size),
143  			     int arg, void *msg, size_t size, bool wait)
144  {
145  	bool is_copy = false;
146  	void *copy = msg;
147  	int ret = -ENOMEM;
148  
149  	if (size > 0) {
150  		copy = kmemdup(msg, size, GFP_ATOMIC);
151  		if (!copy)
152  			goto out;
153  
154  		is_copy = true;
155  	}
156  
157  	ret = ipc_task_queue_add_task(imem, arg, copy, func,
158  				      size, is_copy, wait);
159  	if (ret < 0) {
160  		dev_err(imem->ipc_task->dev,
161  			"add task failed for %ps %d, %p, %zu, %d", func, arg,
162  			copy, size, is_copy);
163  		if (is_copy)
164  			kfree(copy);
165  		goto out;
166  	}
167  
168  	ret = 0;
169  out:
170  	return ret;
171  }
172  
173  int ipc_task_init(struct ipc_task *ipc_task)
174  {
175  	struct ipc_task_queue *ipc_queue = &ipc_task->ipc_queue;
176  
177  	ipc_task->ipc_tasklet = kzalloc(sizeof(*ipc_task->ipc_tasklet),
178  					GFP_KERNEL);
179  
180  	if (!ipc_task->ipc_tasklet)
181  		return -ENOMEM;
182  
183  	/* Initialize the spinlock needed to protect the message queue of the
184  	 * ipc_task
185  	 */
186  	spin_lock_init(&ipc_queue->q_lock);
187  
188  	tasklet_init(ipc_task->ipc_tasklet, ipc_task_queue_handler,
189  		     (unsigned long)ipc_queue);
190  	return 0;
191  }
192  
193  void ipc_task_deinit(struct ipc_task *ipc_task)
194  {
195  	tasklet_kill(ipc_task->ipc_tasklet);
196  
197  	kfree(ipc_task->ipc_tasklet);
198  	/* This will free/complete any outstanding messages,
199  	 * without calling the actual handler
200  	 */
201  	ipc_task_queue_cleanup(&ipc_task->ipc_queue);
202  }
203