/* * ADSP mailbox manager * * 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 "dev.h" #include #include #define NVADSP_MAILBOX_START 512 #define NVADSP_MAILBOX_MAX 1024 #define NVADSP_MAILBOX_OS_MAX 16 static struct nvadsp_mbox *nvadsp_mboxes[NVADSP_MAILBOX_MAX]; static DECLARE_BITMAP(nvadsp_mbox_ids, NVADSP_MAILBOX_MAX); static struct nvadsp_drv_data *nvadsp_drv_data; static inline bool is_mboxq_empty(struct nvadsp_mbox_queue *queue) { return (queue->count == 0); } static inline bool is_mboxq_full(struct nvadsp_mbox_queue *queue) { return (queue->count == NVADSP_MBOX_QUEUE_SIZE); } static void mboxq_init(struct nvadsp_mbox_queue *queue) { queue->head = 0; queue->tail = 0; queue->count = 0; init_completion(&queue->comp); spin_lock_init(&queue->lock); } static void mboxq_destroy(struct nvadsp_mbox_queue *queue) { if (!is_mboxq_empty(queue)) pr_info("Mbox queue %p is not empty.\n", queue); queue->head = 0; queue->tail = 0; queue->count = 0; } static status_t mboxq_enqueue(struct nvadsp_mbox_queue *queue, uint32_t data) { unsigned long flags; int ret = 0; if (is_mboxq_full(queue)) { ret = -EINVAL; goto out; } spin_lock_irqsave(&queue->lock, flags); if (is_mboxq_empty(queue)) complete_all(&queue->comp); queue->array[queue->tail] = data; queue->tail = (queue->tail + 1) & NVADSP_MBOX_QUEUE_SIZE_MASK; queue->count++; spin_unlock_irqrestore(&queue->lock, flags); out: return ret; } status_t nvadsp_mboxq_enqueue(struct nvadsp_mbox_queue *queue, uint32_t data) { return mboxq_enqueue(queue, data); } static status_t mboxq_dequeue(struct nvadsp_mbox_queue *queue, uint32_t *data) { unsigned long flags; int ret = 0; spin_lock_irqsave(&queue->lock, flags); if (is_mboxq_empty(queue)) { ret = -EBUSY; goto comp; } *data = queue->array[queue->head]; queue->head = (queue->head + 1) & NVADSP_MBOX_QUEUE_SIZE_MASK; queue->count--; if (is_mboxq_empty(queue)) goto comp; else goto out; comp: reinit_completion(&queue->comp); out: spin_unlock_irqrestore(&queue->lock, flags); return ret; } static void mboxq_dump(struct nvadsp_mbox_queue *queue) { unsigned long flags; uint16_t head, count; uint32_t data; spin_lock_irqsave(&queue->lock, flags); count = queue->count; pr_info("nvadsp: queue %p count:%d\n", queue, count); pr_info("nvadsp: queue data: "); head = queue->head; while (count) { data = queue->array[head]; head = (head + 1) & NVADSP_MBOX_QUEUE_SIZE_MASK; count--; pr_info("0x%x ", data); } pr_info(" dumped\n"); spin_unlock_irqrestore(&queue->lock, flags); } static uint16_t nvadsp_mbox_alloc_mboxid(void) { unsigned long start = NVADSP_MAILBOX_START; unsigned int nr = 1; unsigned long align = 0; uint16_t mid; mid = bitmap_find_next_zero_area(nvadsp_drv_data->mbox_ids, NVADSP_MAILBOX_MAX - 1, start, nr, align); bitmap_set(nvadsp_drv_data->mbox_ids, mid, 1); return mid; } static status_t nvadsp_mbox_free_mboxid(uint16_t mid) { bitmap_clear(nvadsp_drv_data->mbox_ids, mid, 1); return 0; } status_t nvadsp_mbox_open(struct nvadsp_mbox *mbox, uint16_t *mid, const char *name, nvadsp_mbox_handler_t handler, void *hdata) { unsigned long flags; int ret = 0; if (!nvadsp_drv_data) { ret = -ENOSYS; goto err; } spin_lock_irqsave(&nvadsp_drv_data->mbox_lock, flags); if (!mbox) { ret = -EINVAL; goto out; } if (*mid == 0) { mbox->id = nvadsp_mbox_alloc_mboxid(); if (mbox->id >= NVADSP_MAILBOX_MAX) { ret = -ENOMEM; mbox->id = 0; goto out; } *mid = mbox->id; } else { if (*mid >= NVADSP_MAILBOX_MAX) { pr_debug("%s: Invalid mailbox %d.\n", __func__, *mid); ret = -ERANGE; goto out; } *mid = array_index_nospec(*mid, NVADSP_MAILBOX_MAX); if (nvadsp_drv_data->mboxes[*mid]) { pr_debug("%s: mailbox %d already opened.\n", __func__, *mid); ret = -EADDRINUSE; goto out; } mbox->id = *mid; } strncpy(mbox->name, name, NVADSP_MBOX_NAME_MAX); mboxq_init(&mbox->recv_queue); mbox->handler = handler; mbox->hdata = hdata; nvadsp_drv_data->mboxes[mbox->id] = mbox; out: spin_unlock_irqrestore(&nvadsp_drv_data->mbox_lock, flags); err: return ret; } EXPORT_SYMBOL(nvadsp_mbox_open); status_t nvadsp_mbox_send(struct nvadsp_mbox *mbox, uint32_t data, uint32_t flags, bool block, unsigned int timeout) { int ret = 0; if (!nvadsp_drv_data) { ret = -ENOSYS; goto out; } if (!mbox) { ret = -EINVAL; goto out; } retry: ret = nvadsp_hwmbox_send_data(mbox->id, data, flags); if (!ret) goto out; if (ret == -EBUSY) { if (block) { ret = wait_for_completion_timeout( &nvadsp_drv_data->hwmbox_send_queue.comp, msecs_to_jiffies(timeout)); if (ret) { block = false; goto retry; } else { ret = -ETIME; goto out; } } else { pr_debug("Failed to enqueue data 0x%x. ret: %d\n", data, ret); } } else if (ret) { pr_debug("Failed to enqueue data 0x%x. ret: %d\n", data, ret); goto out; } out: return ret; } EXPORT_SYMBOL(nvadsp_mbox_send); status_t nvadsp_mbox_recv(struct nvadsp_mbox *mbox, uint32_t *data, bool block, unsigned int timeout) { int ret = 0; if (!nvadsp_drv_data) { ret = -ENOSYS; goto out; } if (!mbox) { ret = -EINVAL; goto out; } retry: ret = mboxq_dequeue(&mbox->recv_queue, data); if (!ret) goto out; if (ret == -EBUSY) { if (block) { ret = wait_for_completion_timeout( &mbox->recv_queue.comp, msecs_to_jiffies(timeout)); if (ret) { block = false; goto retry; } else { ret = -ETIME; goto out; } } else { pr_debug("Failed to receive data. ret: %d\n", ret); } } else if (ret) { pr_debug("Failed to receive data. ret: %d\n", ret); goto out; } out: return ret; } EXPORT_SYMBOL(nvadsp_mbox_recv); status_t nvadsp_mbox_close(struct nvadsp_mbox *mbox) { unsigned long flags; int ret = 0; if (!nvadsp_drv_data) { ret = -ENOSYS; goto err; } spin_lock_irqsave(&nvadsp_drv_data->mbox_lock, flags); if (!mbox) { ret = -EINVAL; goto out; } if (!is_mboxq_empty(&mbox->recv_queue)) { ret = -ENOTEMPTY; mboxq_dump(&mbox->recv_queue); goto out; } nvadsp_mbox_free_mboxid(mbox->id); mboxq_destroy(&mbox->recv_queue); nvadsp_drv_data->mboxes[mbox->id] = NULL; out: spin_unlock_irqrestore(&nvadsp_drv_data->mbox_lock, flags); err: return ret; } EXPORT_SYMBOL(nvadsp_mbox_close); status_t __init nvadsp_mbox_init(struct platform_device *pdev) { struct nvadsp_drv_data *drv = platform_get_drvdata(pdev); drv->mboxes = nvadsp_mboxes; drv->mbox_ids = nvadsp_mbox_ids; spin_lock_init(&drv->mbox_lock); nvadsp_drv_data = drv; return 0; }