Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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 */
 12static 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 */
 57static 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. */
 76static int
 77ipc_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
140int 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;
169out:
170	return ret;
171}
172
173int 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
193void 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}