179 lines
5.4 KiB
C
179 lines
5.4 KiB
C
/*
|
|
* ADSP circular message queue
|
|
*
|
|
* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
|
|
#include <linux/tegra_nvadsp.h>
|
|
|
|
#define msgq_wmemcpy(dest, src, words) \
|
|
memcpy(dest, src, (words) * sizeof(int32_t))
|
|
|
|
|
|
/**
|
|
* msgq_init - Initialize message queue
|
|
* @msgq: pointer to the client message queue
|
|
* @size: size of message queue in words
|
|
* size will be capped to MSGQ_MAX_WSIZE
|
|
*
|
|
* This function returns 0 if no error has occurred.
|
|
*
|
|
* The message queue requires space for the queue to be
|
|
* preallocated and should only be initialized once. The queue
|
|
* space immediately follows the queue header and begins at
|
|
* msgq_t::message_queue. All messages are queued directly with
|
|
* no pointer address space translation.
|
|
*
|
|
*
|
|
*/
|
|
void msgq_init(msgq_t *msgq, int32_t size)
|
|
{
|
|
if (MSGQ_MAX_QUEUE_WSIZE < size) {
|
|
/* cap the maximum size */
|
|
pr_info("msgq_init: %d size capped to MSGQ_MAX_QUEUE_WSIZE\n",
|
|
size);
|
|
size = MSGQ_MAX_QUEUE_WSIZE;
|
|
}
|
|
|
|
msgq->size = size;
|
|
msgq->read_index = 0;
|
|
msgq->write_index = 0;
|
|
}
|
|
EXPORT_SYMBOL(msgq_init);
|
|
/**
|
|
* msgq_queue_message - Queues a message in the queue
|
|
* @msgq: pointer to the client message queue
|
|
* @message: Message buffer to copy from
|
|
*
|
|
* This function returns 0 if no error has occurred. ERR_NO_MEMORY will
|
|
* be returned if no space is available in the queue for the
|
|
* entire message. On ERR_NO_MEMORY, it may be possible the
|
|
* queue size was capped at init time to MSGQ_MAX_WSIZE if an
|
|
* unreasonable size was sepecified.
|
|
*
|
|
*
|
|
*/
|
|
int32_t msgq_queue_message(msgq_t *msgq, const msgq_message_t *message)
|
|
{
|
|
int32_t ret = 0;
|
|
|
|
if (msgq && message) {
|
|
int32_t ri = msgq->read_index;
|
|
int32_t wi = msgq->write_index;
|
|
bool wrap = ri <= wi;
|
|
int32_t *start = msgq->queue;
|
|
int32_t *end = &msgq->queue[msgq->size];
|
|
int32_t *first = &msgq->queue[wi];
|
|
int32_t *last = &msgq->queue[ri];
|
|
int32_t qremainder = wrap ? end - first : last - first;
|
|
int32_t qsize = wrap ? qremainder + (last - start) : qremainder;
|
|
int32_t msize = &message->payload[message->size] -
|
|
(int32_t *)message;
|
|
|
|
if (qsize <= msize) {
|
|
/* don't allow read == write */
|
|
pr_err("%s failed: msgq ri: %d, wi %d, msg size %d\n",
|
|
__func__, msgq->read_index,
|
|
msgq->write_index, message->size);
|
|
ret = -ENOSPC;
|
|
} else if (msize < qremainder) {
|
|
msgq_wmemcpy(first, message, msize);
|
|
msgq->write_index = wi + MSGQ_MESSAGE_HEADER_WSIZE +
|
|
message->size;
|
|
} else {
|
|
/* message wrapped */
|
|
msgq_wmemcpy(first, message, qremainder);
|
|
msgq_wmemcpy(msgq->queue, (int32_t *)message +
|
|
qremainder, msize - qremainder);
|
|
msgq->write_index = wi + MSGQ_MESSAGE_HEADER_WSIZE +
|
|
message->size - msgq->size;
|
|
}
|
|
} else {
|
|
pr_err("NULL: msgq %p message %p\n", msgq, message);
|
|
ret = -EFAULT; /* Bad Address */
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(msgq_queue_message);
|
|
/**
|
|
* msgq_dequeue_message - Dequeues a message from the queue
|
|
* @msgq: pointer to the client message queue
|
|
* @message: Message buffer to copy to or
|
|
* NULL to discard the current message
|
|
*
|
|
* This function returns 0 if no error has occurred.
|
|
* msgq_message_t::size will be set to the size of the message
|
|
* in words. ERR_NO_MEMORY will be returned if the buffer is too small
|
|
* for the queued message. ERR_NO_MSG will be returned if there is no
|
|
* message in the queue.
|
|
*
|
|
*
|
|
*/
|
|
int32_t msgq_dequeue_message(msgq_t *msgq, msgq_message_t *message)
|
|
{
|
|
int32_t ret = 0;
|
|
int32_t ri;
|
|
int32_t wi;
|
|
msgq_message_t *msg;
|
|
|
|
if (!msgq) {
|
|
pr_err("NULL: msgq %p\n", msgq);
|
|
return -EFAULT; /* Bad Address */
|
|
}
|
|
|
|
ri = msgq->read_index;
|
|
wi = msgq->write_index;
|
|
msg = (msgq_message_t *)&msgq->queue[msgq->read_index];
|
|
|
|
if (ri == wi) {
|
|
/* empty queue */
|
|
if (message)
|
|
message->size = 0;
|
|
pr_err("%s failed: msgq ri: %d, wi %d; NO MSG\n",
|
|
__func__, msgq->read_index, msgq->write_index);
|
|
ret = -ENOMSG;
|
|
} else if (!message) {
|
|
/* no input buffer, discard top message */
|
|
ri += MSGQ_MESSAGE_HEADER_WSIZE + msg->size;
|
|
msgq->read_index = ri < msgq->size ? ri : ri - msgq->size;
|
|
} else if (message->size < msg->size) {
|
|
/* return buffer too small */
|
|
pr_err("%s failed: msgq ri: %d, wi %d, NO SPACE\n",
|
|
__func__, msgq->read_index, msgq->write_index);
|
|
message->size = msg->size;
|
|
ret = -ENOSPC;
|
|
} else {
|
|
/* copy message to the output buffer */
|
|
int32_t msize = MSGQ_MESSAGE_HEADER_WSIZE + msg->size;
|
|
int32_t *first = &msgq->queue[msgq->read_index];
|
|
int32_t *end = &msgq->queue[msgq->size];
|
|
int32_t qremainder = end - first;
|
|
|
|
if (msize < qremainder) {
|
|
msgq_wmemcpy(message, first, msize);
|
|
msgq->read_index = ri + MSGQ_MESSAGE_HEADER_WSIZE +
|
|
msg->size;
|
|
} else {
|
|
/* message wrapped */
|
|
msgq_wmemcpy(message, first, qremainder);
|
|
msgq_wmemcpy((int32_t *)message + qremainder,
|
|
msgq->queue, msize - qremainder);
|
|
msgq->read_index = ri + MSGQ_MESSAGE_HEADER_WSIZE +
|
|
msg->size - msgq->size;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(msgq_dequeue_message);
|