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