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 */
ipc_task_queue_handler(unsigned long data)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 */
ipc_task_queue_cleanup(struct ipc_task_queue * ipc_task)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
ipc_task_queue_add_task(struct iosm_imem * ipc_imem,int arg,void * msg,int (* func)(struct iosm_imem * ipc_imem,int arg,void * msg,size_t size),size_t size,bool is_copy,bool wait)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
ipc_task_queue_send_task(struct iosm_imem * imem,int (* func)(struct iosm_imem * ipc_imem,int arg,void * msg,size_t size),int arg,void * msg,size_t size,bool wait)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
ipc_task_init(struct ipc_task * ipc_task)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
ipc_task_deinit(struct ipc_task * ipc_task)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