/* * Tegra Graphics Virtualization Communication Framework * * Copyright (c) 2013-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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NUM_QUEUES 5 #define NUM_CONTEXTS 1 struct gr_comm_ivc_context { u32 peer; wait_queue_head_t wq; struct tegra_hv_ivc_cookie *cookie; struct gr_comm_queue *queue; struct platform_device *pdev; bool irq_requested; }; struct gr_comm_mempool_context { struct tegra_hv_ivm_cookie *cookie; void __iomem *ptr; }; struct gr_comm_element { u32 sender; size_t size; void *data; struct list_head list; struct gr_comm_queue *queue; }; struct gr_comm_queue { struct semaphore sem; struct mutex lock; struct mutex resp_lock; struct mutex mempool_lock; struct list_head pending; struct list_head free; size_t size; struct gr_comm_ivc_context *ivc_ctx; struct gr_comm_mempool_context *mempool_ctx; struct kmem_cache *element_cache; bool valid; }; struct gr_comm_context { struct gr_comm_queue queue[NUM_QUEUES]; }; enum { PROP_IVC_NODE = 0, PROP_IVC_INST, NUM_PROP }; enum { PROP_MEMPOOL_NODE = 0, PROP_MEMPOOL_INST, NUM_MEMPOOL_PROP }; static struct gr_comm_context comm_context; static u32 server_vmid; u32 tegra_gr_comm_get_server_vmid(void) { return server_vmid; } EXPORT_SYMBOL(tegra_gr_comm_get_server_vmid); static void free_mempool(u32 queue_start, u32 queue_end) { int i; for (i = queue_start; i < queue_end; ++i) { struct gr_comm_queue *queue = &comm_context.queue[i]; struct gr_comm_mempool_context *tmp = queue->mempool_ctx; if (!tmp) continue; if (tmp->ptr) iounmap(tmp->ptr); if (tmp->cookie) tegra_hv_mempool_unreserve(tmp->cookie); kfree(tmp); queue->mempool_ctx = NULL; } } static void free_ivc(u32 queue_start, u32 queue_end) { int i; for (i = queue_start; i < queue_end; ++i) { struct gr_comm_queue *queue = &comm_context.queue[i]; struct gr_comm_ivc_context *tmp = queue->ivc_ctx; if (!tmp) continue; if (tmp->irq_requested) free_irq(tmp->cookie->irq, tmp); if (tmp->cookie) tegra_hv_ivc_unreserve(tmp->cookie); kfree(tmp); queue->ivc_ctx = NULL; } } static int queue_add(struct gr_comm_queue *queue, const char *data, u32 peer, struct tegra_hv_ivc_cookie *ivck) { struct gr_comm_element *element; mutex_lock(&queue->lock); if (list_empty(&queue->free)) { element = kmem_cache_alloc(queue->element_cache, GFP_KERNEL); if (!element) { mutex_unlock(&queue->lock); return -ENOMEM; } element->data = (char *)element + sizeof(*element); element->queue = queue; } else { element = list_first_entry(&queue->free, struct gr_comm_element, list); list_del(&element->list); } element->sender = peer; element->size = queue->size; if (ivck) { int ret = tegra_hv_ivc_read(ivck, element->data, element->size); if (ret != element->size) { list_add(&element->list, &queue->free); mutex_unlock(&queue->lock); return -ENOMEM; } } else { /* local msg */ memcpy(element->data, data, element->size); } list_add_tail(&element->list, &queue->pending); mutex_unlock(&queue->lock); up(&queue->sem); return 0; } static irqreturn_t ivc_intr_isr(int irq, void *dev_id) { return IRQ_WAKE_THREAD; } static irqreturn_t ivc_intr_thread(int irq, void *dev_id) { struct gr_comm_ivc_context *ctx = dev_id; struct device *dev = &ctx->pdev->dev; /* handle ivc state changes -- MUST BE FIRST */ if (tegra_hv_ivc_channel_notified(ctx->cookie)) return IRQ_HANDLED; while (tegra_hv_ivc_can_read(ctx->cookie)) { if (queue_add(ctx->queue, NULL, ctx->peer, ctx->cookie)) { dev_err(dev, "%s cannot add to queue\n", __func__); break; } } if (tegra_hv_ivc_can_write(ctx->cookie)) wake_up(&ctx->wq); return IRQ_HANDLED; } static int setup_mempool(struct platform_device *pdev, u32 queue_start, u32 queue_end) { struct device *dev = &pdev->dev; int i, ret = -EINVAL; for (i = queue_start; i < queue_end; ++i) { char name[20]; u32 inst; snprintf(name, sizeof(name), "mempool%d", i); if (of_property_read_u32_index(dev->of_node, name, PROP_MEMPOOL_INST, &inst) == 0) { struct gr_comm_mempool_context *ctx; struct gr_comm_queue *queue = &comm_context.queue[i]; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) { ret = -ENOMEM; goto fail; } ctx->cookie = tegra_hv_mempool_reserve(inst); if (IS_ERR_OR_NULL(ctx->cookie)) { ret = PTR_ERR(ctx->cookie); kfree(ctx); goto fail; } queue->mempool_ctx = ctx; ctx->ptr = ioremap_cache(ctx->cookie->ipa, ctx->cookie->size); if (!ctx->ptr) { ret = -ENOMEM; /* free_mempool will take care of clean-up */ goto fail; } } } return 0; fail: free_mempool(queue_start, queue_end); return ret; } static int setup_ivc(struct platform_device *pdev, u32 queue_start, u32 queue_end) { struct device *dev = &pdev->dev; int i, ret = -EINVAL; for (i = queue_start; i < queue_end; ++i) { char name[20]; u32 inst; snprintf(name, sizeof(name), "ivc-queue%d", i); if (of_property_read_u32_index(dev->of_node, name, PROP_IVC_INST, &inst) == 0) { struct device_node *hv_dn; struct gr_comm_ivc_context *ctx; struct gr_comm_queue *queue = &comm_context.queue[i]; int err; hv_dn = of_parse_phandle(dev->of_node, name, PROP_IVC_NODE); if (!hv_dn) goto fail; ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); if (!ctx) { ret = -ENOMEM; goto fail; } ctx->pdev = pdev; ctx->queue = queue; init_waitqueue_head(&ctx->wq); ctx->cookie = tegra_hv_ivc_reserve(hv_dn, inst, NULL); if (IS_ERR_OR_NULL(ctx->cookie)) { ret = PTR_ERR(ctx->cookie); kfree(ctx); goto fail; } if (ctx->cookie->frame_size < queue->size) { ret = -ENOMEM; tegra_hv_ivc_unreserve(ctx->cookie); kfree(ctx); goto fail; } ctx->peer = ctx->cookie->peer_vmid; queue->ivc_ctx = ctx; /* ctx->peer will have same value for all queues */ server_vmid = ctx->peer; /* set ivc channel to invalid state */ tegra_hv_ivc_channel_reset(ctx->cookie); err = request_threaded_irq(ctx->cookie->irq, ivc_intr_isr, ivc_intr_thread, 0, "gr-virt", ctx); if (err) { ret = -ENOMEM; /* ivc context is on list, so free_ivc() * will take care of clean-up */ goto fail; } ctx->irq_requested = true; } else { /* no entry in DT? */ goto fail; } } return 0; fail: free_ivc(queue_start, queue_end); return ret; } int tegra_gr_comm_init(struct platform_device *pdev, u32 elems, const size_t *queue_sizes, u32 queue_start, u32 num_queues) { int i = 0, j; int ret = 0; struct device *dev = &pdev->dev; u32 queue_end = queue_start + num_queues; if (queue_end > NUM_QUEUES) return -EINVAL; for (i = queue_start; i < queue_end; ++i) { char name[30]; size_t size = queue_sizes[i - queue_start]; struct gr_comm_queue *queue = &comm_context.queue[i]; if (queue->valid) return -EEXIST; snprintf(name, sizeof(name), "gr-virt-comm-%d", i); queue->element_cache = kmem_cache_create(name, sizeof(struct gr_comm_element) + size, 0, SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD, NULL); if (!queue->element_cache) { ret = -ENOMEM; goto fail; } sema_init(&queue->sem, 0); mutex_init(&queue->lock); mutex_init(&queue->resp_lock); mutex_init(&queue->mempool_lock); INIT_LIST_HEAD(&queue->free); INIT_LIST_HEAD(&queue->pending); queue->size = size; for (j = 0; j < elems; ++j) { struct gr_comm_element *element = kmem_cache_alloc(queue->element_cache, GFP_KERNEL); if (!element) { ret = -ENOMEM; goto fail; } /* data is placed at end of element */ element->data = (char *)element + sizeof(*element); element->queue = queue; list_add(&element->list, &queue->free); } queue->valid = true; } ret = setup_ivc(pdev, queue_start, queue_end); if (ret) { dev_err(dev, "invalid IVC DT data\n"); goto fail; } ret = setup_mempool(pdev, queue_start, queue_end); if (ret) { dev_err(dev, "mempool setup failed\n"); goto fail; } return 0; fail: for (i = queue_start; i < queue_end; ++i) { struct gr_comm_element *tmp, *next; struct gr_comm_queue *queue = &comm_context.queue[i]; if (queue->element_cache) { list_for_each_entry_safe(tmp, next, &queue->free, list) { list_del(&tmp->list); kmem_cache_free(queue->element_cache, tmp); } kmem_cache_destroy(queue->element_cache); } } free_ivc(queue_start, queue_end); free_mempool(queue_start, queue_end); dev_err(dev, "%s insufficient memory\n", __func__); return ret; } EXPORT_SYMBOL(tegra_gr_comm_init); void tegra_gr_comm_deinit(u32 queue_start, u32 num_queues) { struct gr_comm_element *tmp, *next; u32 queue_end = queue_start + num_queues; int i; if (queue_end > NUM_QUEUES) return; for (i = queue_start; i < queue_end; ++i) { struct gr_comm_queue *queue = &comm_context.queue[i]; if (!queue->valid) continue; list_for_each_entry_safe(tmp, next, &queue->free, list) { list_del(&tmp->list); kmem_cache_free(queue->element_cache, tmp); } list_for_each_entry_safe(tmp, next, &queue->pending, list) { list_del(&tmp->list); kmem_cache_free(queue->element_cache, tmp); } kmem_cache_destroy(queue->element_cache); queue->valid = false; } free_ivc(queue_start, queue_end); free_mempool(queue_start, queue_end); } EXPORT_SYMBOL(tegra_gr_comm_deinit); int tegra_gr_comm_send(u32 peer, u32 index, void *data, size_t size) { struct gr_comm_ivc_context *ivc_ctx; struct gr_comm_queue *queue; int ret; if (index >= NUM_QUEUES) return -EINVAL; queue = &comm_context.queue[index]; if (!queue->valid) return -EINVAL; /* local msg is enqueued directly */ if (peer == TEGRA_GR_COMM_ID_SELF) return queue_add(queue, data, peer, NULL); ivc_ctx = queue->ivc_ctx; if (!ivc_ctx || ivc_ctx->peer != peer) return -EINVAL; if (!tegra_hv_ivc_can_write(ivc_ctx->cookie)) { ret = wait_event_timeout(ivc_ctx->wq, tegra_hv_ivc_can_write(ivc_ctx->cookie), msecs_to_jiffies(500)); if (!ret) { dev_err(&ivc_ctx->pdev->dev, "%s timeout waiting for buffer\n", __func__); return -ENOMEM; } } ret = tegra_hv_ivc_write(ivc_ctx->cookie, data, size); return (ret != size) ? -ENOMEM : 0; } EXPORT_SYMBOL(tegra_gr_comm_send); int tegra_gr_comm_recv(u32 index, void **handle, void **data, size_t *size, u32 *sender) { struct gr_comm_queue *queue; struct gr_comm_element *element; int err; if (index >= NUM_QUEUES) return -EINVAL; queue = &comm_context.queue[index]; if (!queue->valid) return -EINVAL; err = down_timeout(&queue->sem, 10 * HZ); if (unlikely(err)) return err; mutex_lock(&queue->lock); element = list_first_entry(&queue->pending, struct gr_comm_element, list); list_del(&element->list); mutex_unlock(&queue->lock); *handle = element; *data = element->data; *size = element->size; if (sender) *sender = element->sender; return err; } EXPORT_SYMBOL(tegra_gr_comm_recv); /* NOTE: tegra_gr_comm_recv() should not be running concurrently */ int tegra_gr_comm_sendrecv(u32 peer, u32 index, void **handle, void **data, size_t *size) { struct gr_comm_queue *queue; int err = 0; if (index >= NUM_QUEUES) return -EINVAL; queue = &comm_context.queue[index]; if (!queue->valid) return -EINVAL; mutex_lock(&queue->resp_lock); err = tegra_gr_comm_send(peer, index, *data, *size); if (err) goto fail; err = tegra_gr_comm_recv(index, handle, data, size, NULL); if (unlikely(err)) dev_err(&queue->ivc_ctx->pdev->dev, "tegra_gr_comm_recv: timeout for response!\n"); fail: mutex_unlock(&queue->resp_lock); return err; } EXPORT_SYMBOL(tegra_gr_comm_sendrecv); void tegra_gr_comm_release(void *handle) { struct gr_comm_element *element = (struct gr_comm_element *)handle; mutex_lock(&element->queue->lock); list_add(&element->list, &element->queue->free); mutex_unlock(&element->queue->lock); } EXPORT_SYMBOL(tegra_gr_comm_release); void *tegra_gr_comm_oob_get_ptr(u32 peer, u32 index, void **ptr, size_t *size) { struct gr_comm_mempool_context *mempool_ctx; struct gr_comm_queue *queue; if (index >= NUM_QUEUES) return NULL; queue = &comm_context.queue[index]; if (!queue->valid) return NULL; mempool_ctx = queue->mempool_ctx; if (!mempool_ctx || mempool_ctx->cookie->peer_vmid != peer) return NULL; mutex_lock(&queue->mempool_lock); *size = mempool_ctx->cookie->size; *ptr = mempool_ctx->ptr; return queue; } EXPORT_SYMBOL(tegra_gr_comm_oob_get_ptr); void tegra_gr_comm_oob_put_ptr(void *handle) { struct gr_comm_queue *queue = (struct gr_comm_queue *)handle; mutex_unlock(&queue->mempool_lock); } EXPORT_SYMBOL(tegra_gr_comm_oob_put_ptr);