/*
* Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "qmgr.h"
static void
nvkm_falcon_msgq_open(struct nvkm_falcon_msgq *msgq)
{
spin_lock(&msgq->lock);
msgq->position = nvkm_falcon_rd32(msgq->qmgr->falcon, msgq->tail_reg);
}
static void
nvkm_falcon_msgq_close(struct nvkm_falcon_msgq *msgq, bool commit)
{
struct nvkm_falcon *falcon = msgq->qmgr->falcon;
if (commit)
nvkm_falcon_wr32(falcon, msgq->tail_reg, msgq->position);
spin_unlock(&msgq->lock);
}
bool
nvkm_falcon_msgq_empty(struct nvkm_falcon_msgq *msgq)
{
u32 head = nvkm_falcon_rd32(msgq->qmgr->falcon, msgq->head_reg);
u32 tail = nvkm_falcon_rd32(msgq->qmgr->falcon, msgq->tail_reg);
return head == tail;
}
static int
nvkm_falcon_msgq_pop(struct nvkm_falcon_msgq *msgq, void *data, u32 size)
{
struct nvkm_falcon *falcon = msgq->qmgr->falcon;
u32 head, tail, available;
head = nvkm_falcon_rd32(falcon, msgq->head_reg);
/* has the buffer looped? */
if (head < msgq->position)
msgq->position = msgq->offset;
tail = msgq->position;
available = head - tail;
if (size > available) {
FLCNQ_ERR(msgq, "requested %d bytes, but only %d available",
size, available);
return -EINVAL;
}
nvkm_falcon_pio_rd(falcon, 0, DMEM, tail, data, 0, size);
msgq->position += ALIGN(size, QUEUE_ALIGNMENT);
return 0;
}
static int
nvkm_falcon_msgq_read(struct nvkm_falcon_msgq *msgq, struct nvfw_falcon_msg *hdr)
{
int ret = 0;
nvkm_falcon_msgq_open(msgq);
if (nvkm_falcon_msgq_empty(msgq))
goto close;
ret = nvkm_falcon_msgq_pop(msgq, hdr, HDR_SIZE);
if (ret) {
FLCNQ_ERR(msgq, "failed to read message header");
goto close;
}
if (hdr->size > MSG_BUF_SIZE) {
FLCNQ_ERR(msgq, "message too big, %d bytes", hdr->size);
ret = -ENOSPC;
goto close;
}
if (hdr->size > HDR_SIZE) {
u32 read_size = hdr->size - HDR_SIZE;
ret = nvkm_falcon_msgq_pop(msgq, (hdr + 1), read_size);
if (ret) {
FLCNQ_ERR(msgq, "failed to read message data");
goto close;
}
}
ret = 1;
close:
nvkm_falcon_msgq_close(msgq, (ret >= 0));
return ret;
}
static int
nvkm_falcon_msgq_exec(struct nvkm_falcon_msgq *msgq, struct nvfw_falcon_msg *hdr)
{
struct nvkm_falcon_qmgr_seq *seq;
seq = &msgq->qmgr->seq.id[hdr->seq_id];
if (seq->state != SEQ_STATE_USED && seq->state != SEQ_STATE_CANCELLED) {
FLCNQ_ERR(msgq, "message for unknown sequence %08x", seq->id);
return -EINVAL;
}
if (seq->state == SEQ_STATE_USED) {
if (seq->callback)
seq->result = seq->callback(seq->priv, hdr);
}
if (seq->async) {
nvkm_falcon_qmgr_seq_release(msgq->qmgr, seq);
return 0;
}
complete_all(&seq->done);
return 0;
}
void
nvkm_falcon_msgq_recv(struct nvkm_falcon_msgq *msgq)
{
/*
* We are invoked from a worker thread, so normally we have plenty of
* stack space to work with.
*/
u8 msg_buffer[MSG_BUF_SIZE];
struct nvfw_falcon_msg *hdr = (void *)msg_buffer;
while (nvkm_falcon_msgq_read(msgq, hdr) > 0)
nvkm_falcon_msgq_exec(msgq, hdr);
}
int
nvkm_falcon_msgq_recv_initmsg(struct nvkm_falcon_msgq *msgq,
void *data, u32 size)
{
struct nvkm_falcon *falcon = msgq->qmgr->falcon;
struct nvfw_falcon_msg *hdr = data;
int ret;
msgq->head_reg = falcon->func->msgq.head;
msgq->tail_reg = falcon->func->msgq.tail;
msgq->offset = nvkm_falcon_rd32(falcon, falcon->func->msgq.tail);
nvkm_falcon_msgq_open(msgq);
ret = nvkm_falcon_msgq_pop(msgq, data, size);
if (ret == 0 && hdr->size != size) {
FLCN_ERR(falcon, "unexpected init message size %d vs %d",
hdr->size, size);
ret = -EINVAL;
}
nvkm_falcon_msgq_close(msgq, ret == 0);
return ret;
}
void
nvkm_falcon_msgq_init(struct nvkm_falcon_msgq *msgq,
u32 index, u32 offset, u32 size)
{
const struct nvkm_falcon_func *func = msgq->qmgr->falcon->func;
msgq->head_reg = func->msgq.head + index * func->msgq.stride;
msgq->tail_reg = func->msgq.tail + index * func->msgq.stride;
msgq->offset = offset;
FLCNQ_DBG(msgq, "initialised @ index %d offset 0x%08x size 0x%08x",
index, msgq->offset, size);
}
void
nvkm_falcon_msgq_del(struct nvkm_falcon_msgq **pmsgq)
{
struct nvkm_falcon_msgq *msgq = *pmsgq;
if (msgq) {
kfree(*pmsgq);
*pmsgq = NULL;
}
}
int
nvkm_falcon_msgq_new(struct nvkm_falcon_qmgr *qmgr, const char *name,
struct nvkm_falcon_msgq **pmsgq)
{
struct nvkm_falcon_msgq *msgq = *pmsgq;
if (!(msgq = *pmsgq = kzalloc(sizeof(*msgq), GFP_KERNEL)))
return -ENOMEM;
msgq->qmgr = qmgr;
msgq->name = name;
spin_lock_init(&msgq->lock);
return 0;
}