| /************************************************************************** | |
| * | |
| * Copyright © 2015 VMware, Inc., Palo Alto, CA., USA | |
| * 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, sub license, 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 (including the | |
| * next paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL | |
| * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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 <drm/ttm/ttm_bo_api.h> | |
| #include "vmwgfx_drv.h" | |
| /* | |
| * Size of inline command buffers. Try to make sure that a page size is a | |
| * multiple of the DMA pool allocation size. | |
| */ | |
| #define VMW_CMDBUF_INLINE_ALIGN 64 | |
| #define VMW_CMDBUF_INLINE_SIZE \ | |
| (1024 - ALIGN(sizeof(SVGACBHeader), VMW_CMDBUF_INLINE_ALIGN)) | |
| /** | |
| * struct vmw_cmdbuf_context - Command buffer context queues | |
| * | |
| * @submitted: List of command buffers that have been submitted to the | |
| * manager but not yet submitted to hardware. | |
| * @hw_submitted: List of command buffers submitted to hardware. | |
| * @preempted: List of preempted command buffers. | |
| * @num_hw_submitted: Number of buffers currently being processed by hardware | |
| */ | |
| struct vmw_cmdbuf_context { | |
| struct list_head submitted; | |
| struct list_head hw_submitted; | |
| struct list_head preempted; | |
| unsigned num_hw_submitted; | |
| bool block_submission; | |
| }; | |
| /** | |
| * struct vmw_cmdbuf_man: - Command buffer manager | |
| * | |
| * @cur_mutex: Mutex protecting the command buffer used for incremental small | |
| * kernel command submissions, @cur. | |
| * @space_mutex: Mutex to protect against starvation when we allocate | |
| * main pool buffer space. | |
| * @error_mutex: Mutex to serialize the work queue error handling. | |
| * Note this is not needed if the same workqueue handler | |
| * can't race with itself... | |
| * @work: A struct work_struct implementeing command buffer error handling. | |
| * Immutable. | |
| * @dev_priv: Pointer to the device private struct. Immutable. | |
| * @ctx: Array of command buffer context queues. The queues and the context | |
| * data is protected by @lock. | |
| * @error: List of command buffers that have caused device errors. | |
| * Protected by @lock. | |
| * @mm: Range manager for the command buffer space. Manager allocations and | |
| * frees are protected by @lock. | |
| * @cmd_space: Buffer object for the command buffer space, unless we were | |
| * able to make a contigous coherent DMA memory allocation, @handle. Immutable. | |
| * @map_obj: Mapping state for @cmd_space. Immutable. | |
| * @map: Pointer to command buffer space. May be a mapped buffer object or | |
| * a contigous coherent DMA memory allocation. Immutable. | |
| * @cur: Command buffer for small kernel command submissions. Protected by | |
| * the @cur_mutex. | |
| * @cur_pos: Space already used in @cur. Protected by @cur_mutex. | |
| * @default_size: Default size for the @cur command buffer. Immutable. | |
| * @max_hw_submitted: Max number of in-flight command buffers the device can | |
| * handle. Immutable. | |
| * @lock: Spinlock protecting command submission queues. | |
| * @header: Pool of DMA memory for device command buffer headers. | |
| * Internal protection. | |
| * @dheaders: Pool of DMA memory for device command buffer headers with trailing | |
| * space for inline data. Internal protection. | |
| * @alloc_queue: Wait queue for processes waiting to allocate command buffer | |
| * space. | |
| * @idle_queue: Wait queue for processes waiting for command buffer idle. | |
| * @irq_on: Whether the process function has requested irq to be turned on. | |
| * Protected by @lock. | |
| * @using_mob: Whether the command buffer space is a MOB or a contigous DMA | |
| * allocation. Immutable. | |
| * @has_pool: Has a large pool of DMA memory which allows larger allocations. | |
| * Typically this is false only during bootstrap. | |
| * @handle: DMA address handle for the command buffer space if @using_mob is | |
| * false. Immutable. | |
| * @size: The size of the command buffer space. Immutable. | |
| * @num_contexts: Number of contexts actually enabled. | |
| */ | |
| struct vmw_cmdbuf_man { | |
| struct mutex cur_mutex; | |
| struct mutex space_mutex; | |
| struct mutex error_mutex; | |
| struct work_struct work; | |
| struct vmw_private *dev_priv; | |
| struct vmw_cmdbuf_context ctx[SVGA_CB_CONTEXT_MAX]; | |
| struct list_head error; | |
| struct drm_mm mm; | |
| struct ttm_buffer_object *cmd_space; | |
| struct ttm_bo_kmap_obj map_obj; | |
| u8 *map; | |
| struct vmw_cmdbuf_header *cur; | |
| size_t cur_pos; | |
| size_t default_size; | |
| unsigned max_hw_submitted; | |
| spinlock_t lock; | |
| struct dma_pool *headers; | |
| struct dma_pool *dheaders; | |
| wait_queue_head_t alloc_queue; | |
| wait_queue_head_t idle_queue; | |
| bool irq_on; | |
| bool using_mob; | |
| bool has_pool; | |
| dma_addr_t handle; | |
| size_t size; | |
| u32 num_contexts; | |
| }; | |
| /** | |
| * struct vmw_cmdbuf_header - Command buffer metadata | |
| * | |
| * @man: The command buffer manager. | |
| * @cb_header: Device command buffer header, allocated from a DMA pool. | |
| * @cb_context: The device command buffer context. | |
| * @list: List head for attaching to the manager lists. | |
| * @node: The range manager node. | |
| * @handle. The DMA address of @cb_header. Handed to the device on command | |
| * buffer submission. | |
| * @cmd: Pointer to the command buffer space of this buffer. | |
| * @size: Size of the command buffer space of this buffer. | |
| * @reserved: Reserved space of this buffer. | |
| * @inline_space: Whether inline command buffer space is used. | |
| */ | |
| struct vmw_cmdbuf_header { | |
| struct vmw_cmdbuf_man *man; | |
| SVGACBHeader *cb_header; | |
| SVGACBContext cb_context; | |
| struct list_head list; | |
| struct drm_mm_node node; | |
| dma_addr_t handle; | |
| u8 *cmd; | |
| size_t size; | |
| size_t reserved; | |
| bool inline_space; | |
| }; | |
| /** | |
| * struct vmw_cmdbuf_dheader - Device command buffer header with inline | |
| * command buffer space. | |
| * | |
| * @cb_header: Device command buffer header. | |
| * @cmd: Inline command buffer space. | |
| */ | |
| struct vmw_cmdbuf_dheader { | |
| SVGACBHeader cb_header; | |
| u8 cmd[VMW_CMDBUF_INLINE_SIZE] __aligned(VMW_CMDBUF_INLINE_ALIGN); | |
| }; | |
| /** | |
| * struct vmw_cmdbuf_alloc_info - Command buffer space allocation metadata | |
| * | |
| * @page_size: Size of requested command buffer space in pages. | |
| * @node: Pointer to the range manager node. | |
| * @done: True if this allocation has succeeded. | |
| */ | |
| struct vmw_cmdbuf_alloc_info { | |
| size_t page_size; | |
| struct drm_mm_node *node; | |
| bool done; | |
| }; | |
| /* Loop over each context in the command buffer manager. */ | |
| #define for_each_cmdbuf_ctx(_man, _i, _ctx) \ | |
| for (_i = 0, _ctx = &(_man)->ctx[0]; (_i) < (_man)->num_contexts; \ | |
| ++(_i), ++(_ctx)) | |
| static int vmw_cmdbuf_startstop(struct vmw_cmdbuf_man *man, u32 context, | |
| bool enable); | |
| static int vmw_cmdbuf_preempt(struct vmw_cmdbuf_man *man, u32 context); | |
| /** | |
| * vmw_cmdbuf_cur_lock - Helper to lock the cur_mutex. | |
| * | |
| * @man: The range manager. | |
| * @interruptible: Whether to wait interruptible when locking. | |
| */ | |
| static int vmw_cmdbuf_cur_lock(struct vmw_cmdbuf_man *man, bool interruptible) | |
| { | |
| if (interruptible) { | |
| if (mutex_lock_interruptible(&man->cur_mutex)) | |
| return -ERESTARTSYS; | |
| } else { | |
| mutex_lock(&man->cur_mutex); | |
| } | |
| return 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_cur_unlock - Helper to unlock the cur_mutex. | |
| * | |
| * @man: The range manager. | |
| */ | |
| static void vmw_cmdbuf_cur_unlock(struct vmw_cmdbuf_man *man) | |
| { | |
| mutex_unlock(&man->cur_mutex); | |
| } | |
| /** | |
| * vmw_cmdbuf_header_inline_free - Free a struct vmw_cmdbuf_header that has | |
| * been used for the device context with inline command buffers. | |
| * Need not be called locked. | |
| * | |
| * @header: Pointer to the header to free. | |
| */ | |
| static void vmw_cmdbuf_header_inline_free(struct vmw_cmdbuf_header *header) | |
| { | |
| struct vmw_cmdbuf_dheader *dheader; | |
| if (WARN_ON_ONCE(!header->inline_space)) | |
| return; | |
| dheader = container_of(header->cb_header, struct vmw_cmdbuf_dheader, | |
| cb_header); | |
| dma_pool_free(header->man->dheaders, dheader, header->handle); | |
| kfree(header); | |
| } | |
| /** | |
| * __vmw_cmdbuf_header_free - Free a struct vmw_cmdbuf_header and its | |
| * associated structures. | |
| * | |
| * header: Pointer to the header to free. | |
| * | |
| * For internal use. Must be called with man::lock held. | |
| */ | |
| static void __vmw_cmdbuf_header_free(struct vmw_cmdbuf_header *header) | |
| { | |
| struct vmw_cmdbuf_man *man = header->man; | |
| lockdep_assert_held_once(&man->lock); | |
| if (header->inline_space) { | |
| vmw_cmdbuf_header_inline_free(header); | |
| return; | |
| } | |
| drm_mm_remove_node(&header->node); | |
| wake_up_all(&man->alloc_queue); | |
| if (header->cb_header) | |
| dma_pool_free(man->headers, header->cb_header, | |
| header->handle); | |
| kfree(header); | |
| } | |
| /** | |
| * vmw_cmdbuf_header_free - Free a struct vmw_cmdbuf_header and its | |
| * associated structures. | |
| * | |
| * @header: Pointer to the header to free. | |
| */ | |
| void vmw_cmdbuf_header_free(struct vmw_cmdbuf_header *header) | |
| { | |
| struct vmw_cmdbuf_man *man = header->man; | |
| /* Avoid locking if inline_space */ | |
| if (header->inline_space) { | |
| vmw_cmdbuf_header_inline_free(header); | |
| return; | |
| } | |
| spin_lock(&man->lock); | |
| __vmw_cmdbuf_header_free(header); | |
| spin_unlock(&man->lock); | |
| } | |
| /** | |
| * vmw_cmbuf_header_submit: Submit a command buffer to hardware. | |
| * | |
| * @header: The header of the buffer to submit. | |
| */ | |
| static int vmw_cmdbuf_header_submit(struct vmw_cmdbuf_header *header) | |
| { | |
| struct vmw_cmdbuf_man *man = header->man; | |
| u32 val; | |
| val = upper_32_bits(header->handle); | |
| vmw_write(man->dev_priv, SVGA_REG_COMMAND_HIGH, val); | |
| val = lower_32_bits(header->handle); | |
| val |= header->cb_context & SVGA_CB_CONTEXT_MASK; | |
| vmw_write(man->dev_priv, SVGA_REG_COMMAND_LOW, val); | |
| return header->cb_header->status; | |
| } | |
| /** | |
| * vmw_cmdbuf_ctx_init: Initialize a command buffer context. | |
| * | |
| * @ctx: The command buffer context to initialize | |
| */ | |
| static void vmw_cmdbuf_ctx_init(struct vmw_cmdbuf_context *ctx) | |
| { | |
| INIT_LIST_HEAD(&ctx->hw_submitted); | |
| INIT_LIST_HEAD(&ctx->submitted); | |
| INIT_LIST_HEAD(&ctx->preempted); | |
| ctx->num_hw_submitted = 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_ctx_submit: Submit command buffers from a command buffer | |
| * context. | |
| * | |
| * @man: The command buffer manager. | |
| * @ctx: The command buffer context. | |
| * | |
| * Submits command buffers to hardware until there are no more command | |
| * buffers to submit or the hardware can't handle more command buffers. | |
| */ | |
| static void vmw_cmdbuf_ctx_submit(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_context *ctx) | |
| { | |
| while (ctx->num_hw_submitted < man->max_hw_submitted && | |
| !list_empty(&ctx->submitted) && | |
| !ctx->block_submission) { | |
| struct vmw_cmdbuf_header *entry; | |
| SVGACBStatus status; | |
| entry = list_first_entry(&ctx->submitted, | |
| struct vmw_cmdbuf_header, | |
| list); | |
| status = vmw_cmdbuf_header_submit(entry); | |
| /* This should never happen */ | |
| if (WARN_ON_ONCE(status == SVGA_CB_STATUS_QUEUE_FULL)) { | |
| entry->cb_header->status = SVGA_CB_STATUS_NONE; | |
| break; | |
| } | |
| list_del(&entry->list); | |
| list_add_tail(&entry->list, &ctx->hw_submitted); | |
| ctx->num_hw_submitted++; | |
| } | |
| } | |
| /** | |
| * vmw_cmdbuf_ctx_submit: Process a command buffer context. | |
| * | |
| * @man: The command buffer manager. | |
| * @ctx: The command buffer context. | |
| * | |
| * Submit command buffers to hardware if possible, and process finished | |
| * buffers. Typically freeing them, but on preemption or error take | |
| * appropriate action. Wake up waiters if appropriate. | |
| */ | |
| static void vmw_cmdbuf_ctx_process(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_context *ctx, | |
| int *notempty) | |
| { | |
| struct vmw_cmdbuf_header *entry, *next; | |
| vmw_cmdbuf_ctx_submit(man, ctx); | |
| list_for_each_entry_safe(entry, next, &ctx->hw_submitted, list) { | |
| SVGACBStatus status = entry->cb_header->status; | |
| if (status == SVGA_CB_STATUS_NONE) | |
| break; | |
| list_del(&entry->list); | |
| wake_up_all(&man->idle_queue); | |
| ctx->num_hw_submitted--; | |
| switch (status) { | |
| case SVGA_CB_STATUS_COMPLETED: | |
| __vmw_cmdbuf_header_free(entry); | |
| break; | |
| case SVGA_CB_STATUS_COMMAND_ERROR: | |
| entry->cb_header->status = SVGA_CB_STATUS_NONE; | |
| list_add_tail(&entry->list, &man->error); | |
| schedule_work(&man->work); | |
| break; | |
| case SVGA_CB_STATUS_PREEMPTED: | |
| entry->cb_header->status = SVGA_CB_STATUS_NONE; | |
| list_add_tail(&entry->list, &ctx->preempted); | |
| break; | |
| case SVGA_CB_STATUS_CB_HEADER_ERROR: | |
| WARN_ONCE(true, "Command buffer header error.\n"); | |
| __vmw_cmdbuf_header_free(entry); | |
| break; | |
| default: | |
| WARN_ONCE(true, "Undefined command buffer status.\n"); | |
| __vmw_cmdbuf_header_free(entry); | |
| break; | |
| } | |
| } | |
| vmw_cmdbuf_ctx_submit(man, ctx); | |
| if (!list_empty(&ctx->submitted)) | |
| (*notempty)++; | |
| } | |
| /** | |
| * vmw_cmdbuf_man_process - Process all command buffer contexts and | |
| * switch on and off irqs as appropriate. | |
| * | |
| * @man: The command buffer manager. | |
| * | |
| * Calls vmw_cmdbuf_ctx_process() on all contexts. If any context has | |
| * command buffers left that are not submitted to hardware, Make sure | |
| * IRQ handling is turned on. Otherwise, make sure it's turned off. | |
| */ | |
| static void vmw_cmdbuf_man_process(struct vmw_cmdbuf_man *man) | |
| { | |
| int notempty; | |
| struct vmw_cmdbuf_context *ctx; | |
| int i; | |
| retry: | |
| notempty = 0; | |
| for_each_cmdbuf_ctx(man, i, ctx) | |
| vmw_cmdbuf_ctx_process(man, ctx, ¬empty); | |
| if (man->irq_on && !notempty) { | |
| vmw_generic_waiter_remove(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| man->irq_on = false; | |
| } else if (!man->irq_on && notempty) { | |
| vmw_generic_waiter_add(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| man->irq_on = true; | |
| /* Rerun in case we just missed an irq. */ | |
| goto retry; | |
| } | |
| } | |
| /** | |
| * vmw_cmdbuf_ctx_add - Schedule a command buffer for submission on a | |
| * command buffer context | |
| * | |
| * @man: The command buffer manager. | |
| * @header: The header of the buffer to submit. | |
| * @cb_context: The command buffer context to use. | |
| * | |
| * This function adds @header to the "submitted" queue of the command | |
| * buffer context identified by @cb_context. It then calls the command buffer | |
| * manager processing to potentially submit the buffer to hardware. | |
| * @man->lock needs to be held when calling this function. | |
| */ | |
| static void vmw_cmdbuf_ctx_add(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_header *header, | |
| SVGACBContext cb_context) | |
| { | |
| if (!(header->cb_header->flags & SVGA_CB_FLAG_DX_CONTEXT)) | |
| header->cb_header->dxContext = 0; | |
| header->cb_context = cb_context; | |
| list_add_tail(&header->list, &man->ctx[cb_context].submitted); | |
| vmw_cmdbuf_man_process(man); | |
| } | |
| /** | |
| * vmw_cmdbuf_irqthread - The main part of the command buffer interrupt | |
| * handler implemented as a threaded irq task. | |
| * | |
| * @man: Pointer to the command buffer manager. | |
| * | |
| * The bottom half of the interrupt handler simply calls into the | |
| * command buffer processor to free finished buffers and submit any | |
| * queued buffers to hardware. | |
| */ | |
| void vmw_cmdbuf_irqthread(struct vmw_cmdbuf_man *man) | |
| { | |
| spin_lock(&man->lock); | |
| vmw_cmdbuf_man_process(man); | |
| spin_unlock(&man->lock); | |
| } | |
| /** | |
| * vmw_cmdbuf_work_func - The deferred work function that handles | |
| * command buffer errors. | |
| * | |
| * @work: The work func closure argument. | |
| * | |
| * Restarting the command buffer context after an error requires process | |
| * context, so it is deferred to this work function. | |
| */ | |
| static void vmw_cmdbuf_work_func(struct work_struct *work) | |
| { | |
| struct vmw_cmdbuf_man *man = | |
| container_of(work, struct vmw_cmdbuf_man, work); | |
| struct vmw_cmdbuf_header *entry, *next; | |
| uint32_t dummy; | |
| bool restart[SVGA_CB_CONTEXT_MAX]; | |
| bool send_fence = false; | |
| struct list_head restart_head[SVGA_CB_CONTEXT_MAX]; | |
| int i; | |
| struct vmw_cmdbuf_context *ctx; | |
| bool global_block = false; | |
| for_each_cmdbuf_ctx(man, i, ctx) { | |
| INIT_LIST_HEAD(&restart_head[i]); | |
| restart[i] = false; | |
| } | |
| mutex_lock(&man->error_mutex); | |
| spin_lock(&man->lock); | |
| list_for_each_entry_safe(entry, next, &man->error, list) { | |
| SVGACBHeader *cb_hdr = entry->cb_header; | |
| SVGA3dCmdHeader *header = (SVGA3dCmdHeader *) | |
| (entry->cmd + cb_hdr->errorOffset); | |
| u32 error_cmd_size, new_start_offset; | |
| const char *cmd_name; | |
| list_del_init(&entry->list); | |
| restart[entry->cb_context] = true; | |
| global_block = true; | |
| if (!vmw_cmd_describe(header, &error_cmd_size, &cmd_name)) { | |
| DRM_ERROR("Unknown command causing device error.\n"); | |
| DRM_ERROR("Command buffer offset is %lu\n", | |
| (unsigned long) cb_hdr->errorOffset); | |
| __vmw_cmdbuf_header_free(entry); | |
| send_fence = true; | |
| continue; | |
| } | |
| DRM_ERROR("Command \"%s\" causing device error.\n", cmd_name); | |
| DRM_ERROR("Command buffer offset is %lu\n", | |
| (unsigned long) cb_hdr->errorOffset); | |
| DRM_ERROR("Command size is %lu\n", | |
| (unsigned long) error_cmd_size); | |
| new_start_offset = cb_hdr->errorOffset + error_cmd_size; | |
| if (new_start_offset >= cb_hdr->length) { | |
| __vmw_cmdbuf_header_free(entry); | |
| send_fence = true; | |
| continue; | |
| } | |
| if (man->using_mob) | |
| cb_hdr->ptr.mob.mobOffset += new_start_offset; | |
| else | |
| cb_hdr->ptr.pa += (u64) new_start_offset; | |
| entry->cmd += new_start_offset; | |
| cb_hdr->length -= new_start_offset; | |
| cb_hdr->errorOffset = 0; | |
| cb_hdr->offset = 0; | |
| list_add_tail(&entry->list, &restart_head[entry->cb_context]); | |
| } | |
| for_each_cmdbuf_ctx(man, i, ctx) | |
| man->ctx[i].block_submission = true; | |
| spin_unlock(&man->lock); | |
| /* Preempt all contexts */ | |
| if (global_block && vmw_cmdbuf_preempt(man, 0)) | |
| DRM_ERROR("Failed preempting command buffer contexts\n"); | |
| spin_lock(&man->lock); | |
| for_each_cmdbuf_ctx(man, i, ctx) { | |
| /* Move preempted command buffers to the preempted queue. */ | |
| vmw_cmdbuf_ctx_process(man, ctx, &dummy); | |
| /* | |
| * Add the preempted queue after the command buffer | |
| * that caused an error. | |
| */ | |
| list_splice_init(&ctx->preempted, restart_head[i].prev); | |
| /* | |
| * Finally add all command buffers first in the submitted | |
| * queue, to rerun them. | |
| */ | |
| ctx->block_submission = false; | |
| list_splice_init(&restart_head[i], &ctx->submitted); | |
| } | |
| vmw_cmdbuf_man_process(man); | |
| spin_unlock(&man->lock); | |
| if (global_block && vmw_cmdbuf_startstop(man, 0, true)) | |
| DRM_ERROR("Failed restarting command buffer contexts\n"); | |
| /* Send a new fence in case one was removed */ | |
| if (send_fence) { | |
| vmw_fifo_send_fence(man->dev_priv, &dummy); | |
| wake_up_all(&man->idle_queue); | |
| } | |
| mutex_unlock(&man->error_mutex); | |
| } | |
| /** | |
| * vmw_cmdbuf_man idle - Check whether the command buffer manager is idle. | |
| * | |
| * @man: The command buffer manager. | |
| * @check_preempted: Check also the preempted queue for pending command buffers. | |
| * | |
| */ | |
| static bool vmw_cmdbuf_man_idle(struct vmw_cmdbuf_man *man, | |
| bool check_preempted) | |
| { | |
| struct vmw_cmdbuf_context *ctx; | |
| bool idle = false; | |
| int i; | |
| spin_lock(&man->lock); | |
| vmw_cmdbuf_man_process(man); | |
| for_each_cmdbuf_ctx(man, i, ctx) { | |
| if (!list_empty(&ctx->submitted) || | |
| !list_empty(&ctx->hw_submitted) || | |
| (check_preempted && !list_empty(&ctx->preempted))) | |
| goto out_unlock; | |
| } | |
| idle = list_empty(&man->error); | |
| out_unlock: | |
| spin_unlock(&man->lock); | |
| return idle; | |
| } | |
| /** | |
| * __vmw_cmdbuf_cur_flush - Flush the current command buffer for small kernel | |
| * command submissions | |
| * | |
| * @man: The command buffer manager. | |
| * | |
| * Flushes the current command buffer without allocating a new one. A new one | |
| * is automatically allocated when needed. Call with @man->cur_mutex held. | |
| */ | |
| static void __vmw_cmdbuf_cur_flush(struct vmw_cmdbuf_man *man) | |
| { | |
| struct vmw_cmdbuf_header *cur = man->cur; | |
| WARN_ON(!mutex_is_locked(&man->cur_mutex)); | |
| if (!cur) | |
| return; | |
| spin_lock(&man->lock); | |
| if (man->cur_pos == 0) { | |
| __vmw_cmdbuf_header_free(cur); | |
| goto out_unlock; | |
| } | |
| man->cur->cb_header->length = man->cur_pos; | |
| vmw_cmdbuf_ctx_add(man, man->cur, SVGA_CB_CONTEXT_0); | |
| out_unlock: | |
| spin_unlock(&man->lock); | |
| man->cur = NULL; | |
| man->cur_pos = 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_cur_flush - Flush the current command buffer for small kernel | |
| * command submissions | |
| * | |
| * @man: The command buffer manager. | |
| * @interruptible: Whether to sleep interruptible when sleeping. | |
| * | |
| * Flushes the current command buffer without allocating a new one. A new one | |
| * is automatically allocated when needed. | |
| */ | |
| int vmw_cmdbuf_cur_flush(struct vmw_cmdbuf_man *man, | |
| bool interruptible) | |
| { | |
| int ret = vmw_cmdbuf_cur_lock(man, interruptible); | |
| if (ret) | |
| return ret; | |
| __vmw_cmdbuf_cur_flush(man); | |
| vmw_cmdbuf_cur_unlock(man); | |
| return 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_idle - Wait for command buffer manager idle. | |
| * | |
| * @man: The command buffer manager. | |
| * @interruptible: Sleep interruptible while waiting. | |
| * @timeout: Time out after this many ticks. | |
| * | |
| * Wait until the command buffer manager has processed all command buffers, | |
| * or until a timeout occurs. If a timeout occurs, the function will return | |
| * -EBUSY. | |
| */ | |
| int vmw_cmdbuf_idle(struct vmw_cmdbuf_man *man, bool interruptible, | |
| unsigned long timeout) | |
| { | |
| int ret; | |
| ret = vmw_cmdbuf_cur_flush(man, interruptible); | |
| vmw_generic_waiter_add(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| if (interruptible) { | |
| ret = wait_event_interruptible_timeout | |
| (man->idle_queue, vmw_cmdbuf_man_idle(man, true), | |
| timeout); | |
| } else { | |
| ret = wait_event_timeout | |
| (man->idle_queue, vmw_cmdbuf_man_idle(man, true), | |
| timeout); | |
| } | |
| vmw_generic_waiter_remove(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| if (ret == 0) { | |
| if (!vmw_cmdbuf_man_idle(man, true)) | |
| ret = -EBUSY; | |
| else | |
| ret = 0; | |
| } | |
| if (ret > 0) | |
| ret = 0; | |
| return ret; | |
| } | |
| /** | |
| * vmw_cmdbuf_try_alloc - Try to allocate buffer space from the main pool. | |
| * | |
| * @man: The command buffer manager. | |
| * @info: Allocation info. Will hold the size on entry and allocated mm node | |
| * on successful return. | |
| * | |
| * Try to allocate buffer space from the main pool. Returns true if succeeded. | |
| * If a fatal error was hit, the error code is returned in @info->ret. | |
| */ | |
| static bool vmw_cmdbuf_try_alloc(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_alloc_info *info) | |
| { | |
| int ret; | |
| if (info->done) | |
| return true; | |
| memset(info->node, 0, sizeof(*info->node)); | |
| spin_lock(&man->lock); | |
| ret = drm_mm_insert_node(&man->mm, info->node, info->page_size); | |
| if (ret) { | |
| vmw_cmdbuf_man_process(man); | |
| ret = drm_mm_insert_node(&man->mm, info->node, info->page_size); | |
| } | |
| spin_unlock(&man->lock); | |
| info->done = !ret; | |
| return info->done; | |
| } | |
| /** | |
| * vmw_cmdbuf_alloc_space - Allocate buffer space from the main pool. | |
| * | |
| * @man: The command buffer manager. | |
| * @node: Pointer to pre-allocated range-manager node. | |
| * @size: The size of the allocation. | |
| * @interruptible: Whether to sleep interruptible while waiting for space. | |
| * | |
| * This function allocates buffer space from the main pool, and if there is | |
| * no space available ATM, it turns on IRQ handling and sleeps waiting for it to | |
| * become available. | |
| */ | |
| static int vmw_cmdbuf_alloc_space(struct vmw_cmdbuf_man *man, | |
| struct drm_mm_node *node, | |
| size_t size, | |
| bool interruptible) | |
| { | |
| struct vmw_cmdbuf_alloc_info info; | |
| info.page_size = PAGE_ALIGN(size) >> PAGE_SHIFT; | |
| info.node = node; | |
| info.done = false; | |
| /* | |
| * To prevent starvation of large requests, only one allocating call | |
| * at a time waiting for space. | |
| */ | |
| if (interruptible) { | |
| if (mutex_lock_interruptible(&man->space_mutex)) | |
| return -ERESTARTSYS; | |
| } else { | |
| mutex_lock(&man->space_mutex); | |
| } | |
| /* Try to allocate space without waiting. */ | |
| if (vmw_cmdbuf_try_alloc(man, &info)) | |
| goto out_unlock; | |
| vmw_generic_waiter_add(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| if (interruptible) { | |
| int ret; | |
| ret = wait_event_interruptible | |
| (man->alloc_queue, vmw_cmdbuf_try_alloc(man, &info)); | |
| if (ret) { | |
| vmw_generic_waiter_remove | |
| (man->dev_priv, SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| mutex_unlock(&man->space_mutex); | |
| return ret; | |
| } | |
| } else { | |
| wait_event(man->alloc_queue, vmw_cmdbuf_try_alloc(man, &info)); | |
| } | |
| vmw_generic_waiter_remove(man->dev_priv, | |
| SVGA_IRQFLAG_COMMAND_BUFFER, | |
| &man->dev_priv->cmdbuf_waiters); | |
| out_unlock: | |
| mutex_unlock(&man->space_mutex); | |
| return 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_space_pool - Set up a command buffer header with command buffer | |
| * space from the main pool. | |
| * | |
| * @man: The command buffer manager. | |
| * @header: Pointer to the header to set up. | |
| * @size: The requested size of the buffer space. | |
| * @interruptible: Whether to sleep interruptible while waiting for space. | |
| */ | |
| static int vmw_cmdbuf_space_pool(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_header *header, | |
| size_t size, | |
| bool interruptible) | |
| { | |
| SVGACBHeader *cb_hdr; | |
| size_t offset; | |
| int ret; | |
| if (!man->has_pool) | |
| return -ENOMEM; | |
| ret = vmw_cmdbuf_alloc_space(man, &header->node, size, interruptible); | |
| if (ret) | |
| return ret; | |
| header->cb_header = dma_pool_zalloc(man->headers, GFP_KERNEL, | |
| &header->handle); | |
| if (!header->cb_header) { | |
| ret = -ENOMEM; | |
| goto out_no_cb_header; | |
| } | |
| header->size = header->node.size << PAGE_SHIFT; | |
| cb_hdr = header->cb_header; | |
| offset = header->node.start << PAGE_SHIFT; | |
| header->cmd = man->map + offset; | |
| if (man->using_mob) { | |
| cb_hdr->flags = SVGA_CB_FLAG_MOB; | |
| cb_hdr->ptr.mob.mobid = man->cmd_space->mem.start; | |
| cb_hdr->ptr.mob.mobOffset = offset; | |
| } else { | |
| cb_hdr->ptr.pa = (u64)man->handle + (u64)offset; | |
| } | |
| return 0; | |
| out_no_cb_header: | |
| spin_lock(&man->lock); | |
| drm_mm_remove_node(&header->node); | |
| spin_unlock(&man->lock); | |
| return ret; | |
| } | |
| /** | |
| * vmw_cmdbuf_space_inline - Set up a command buffer header with | |
| * inline command buffer space. | |
| * | |
| * @man: The command buffer manager. | |
| * @header: Pointer to the header to set up. | |
| * @size: The requested size of the buffer space. | |
| */ | |
| static int vmw_cmdbuf_space_inline(struct vmw_cmdbuf_man *man, | |
| struct vmw_cmdbuf_header *header, | |
| int size) | |
| { | |
| struct vmw_cmdbuf_dheader *dheader; | |
| SVGACBHeader *cb_hdr; | |
| if (WARN_ON_ONCE(size > VMW_CMDBUF_INLINE_SIZE)) | |
| return -ENOMEM; | |
| dheader = dma_pool_zalloc(man->dheaders, GFP_KERNEL, | |
| &header->handle); | |
| if (!dheader) | |
| return -ENOMEM; | |
| header->inline_space = true; | |
| header->size = VMW_CMDBUF_INLINE_SIZE; | |
| cb_hdr = &dheader->cb_header; | |
| header->cb_header = cb_hdr; | |
| header->cmd = dheader->cmd; | |
| cb_hdr->status = SVGA_CB_STATUS_NONE; | |
| cb_hdr->flags = SVGA_CB_FLAG_NONE; | |
| cb_hdr->ptr.pa = (u64)header->handle + | |
| (u64)offsetof(struct vmw_cmdbuf_dheader, cmd); | |
| return 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_alloc - Allocate a command buffer header complete with | |
| * command buffer space. | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The requested size of the buffer space. | |
| * @interruptible: Whether to sleep interruptible while waiting for space. | |
| * @p_header: points to a header pointer to populate on successful return. | |
| * | |
| * Returns a pointer to command buffer space if successful. Otherwise | |
| * returns an error pointer. The header pointer returned in @p_header should | |
| * be used for upcoming calls to vmw_cmdbuf_reserve() and vmw_cmdbuf_commit(). | |
| */ | |
| void *vmw_cmdbuf_alloc(struct vmw_cmdbuf_man *man, | |
| size_t size, bool interruptible, | |
| struct vmw_cmdbuf_header **p_header) | |
| { | |
| struct vmw_cmdbuf_header *header; | |
| int ret = 0; | |
| *p_header = NULL; | |
| header = kzalloc(sizeof(*header), GFP_KERNEL); | |
| if (!header) | |
| return ERR_PTR(-ENOMEM); | |
| if (size <= VMW_CMDBUF_INLINE_SIZE) | |
| ret = vmw_cmdbuf_space_inline(man, header, size); | |
| else | |
| ret = vmw_cmdbuf_space_pool(man, header, size, interruptible); | |
| if (ret) { | |
| kfree(header); | |
| return ERR_PTR(ret); | |
| } | |
| header->man = man; | |
| INIT_LIST_HEAD(&header->list); | |
| header->cb_header->status = SVGA_CB_STATUS_NONE; | |
| *p_header = header; | |
| return header->cmd; | |
| } | |
| /** | |
| * vmw_cmdbuf_reserve_cur - Reserve space for commands in the current | |
| * command buffer. | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The requested size of the commands. | |
| * @ctx_id: The context id if any. Otherwise set to SVGA3D_REG_INVALID. | |
| * @interruptible: Whether to sleep interruptible while waiting for space. | |
| * | |
| * Returns a pointer to command buffer space if successful. Otherwise | |
| * returns an error pointer. | |
| */ | |
| static void *vmw_cmdbuf_reserve_cur(struct vmw_cmdbuf_man *man, | |
| size_t size, | |
| int ctx_id, | |
| bool interruptible) | |
| { | |
| struct vmw_cmdbuf_header *cur; | |
| void *ret; | |
| if (vmw_cmdbuf_cur_lock(man, interruptible)) | |
| return ERR_PTR(-ERESTARTSYS); | |
| cur = man->cur; | |
| if (cur && (size + man->cur_pos > cur->size || | |
| ((cur->cb_header->flags & SVGA_CB_FLAG_DX_CONTEXT) && | |
| ctx_id != cur->cb_header->dxContext))) | |
| __vmw_cmdbuf_cur_flush(man); | |
| if (!man->cur) { | |
| ret = vmw_cmdbuf_alloc(man, | |
| max_t(size_t, size, man->default_size), | |
| interruptible, &man->cur); | |
| if (IS_ERR(ret)) { | |
| vmw_cmdbuf_cur_unlock(man); | |
| return ret; | |
| } | |
| cur = man->cur; | |
| } | |
| if (ctx_id != SVGA3D_INVALID_ID) { | |
| cur->cb_header->flags |= SVGA_CB_FLAG_DX_CONTEXT; | |
| cur->cb_header->dxContext = ctx_id; | |
| } | |
| cur->reserved = size; | |
| return (void *) (man->cur->cmd + man->cur_pos); | |
| } | |
| /** | |
| * vmw_cmdbuf_commit_cur - Commit commands in the current command buffer. | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The size of the commands actually written. | |
| * @flush: Whether to flush the command buffer immediately. | |
| */ | |
| static void vmw_cmdbuf_commit_cur(struct vmw_cmdbuf_man *man, | |
| size_t size, bool flush) | |
| { | |
| struct vmw_cmdbuf_header *cur = man->cur; | |
| WARN_ON(!mutex_is_locked(&man->cur_mutex)); | |
| WARN_ON(size > cur->reserved); | |
| man->cur_pos += size; | |
| if (!size) | |
| cur->cb_header->flags &= ~SVGA_CB_FLAG_DX_CONTEXT; | |
| if (flush) | |
| __vmw_cmdbuf_cur_flush(man); | |
| vmw_cmdbuf_cur_unlock(man); | |
| } | |
| /** | |
| * vmw_cmdbuf_reserve - Reserve space for commands in a command buffer. | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The requested size of the commands. | |
| * @ctx_id: The context id if any. Otherwise set to SVGA3D_REG_INVALID. | |
| * @interruptible: Whether to sleep interruptible while waiting for space. | |
| * @header: Header of the command buffer. NULL if the current command buffer | |
| * should be used. | |
| * | |
| * Returns a pointer to command buffer space if successful. Otherwise | |
| * returns an error pointer. | |
| */ | |
| void *vmw_cmdbuf_reserve(struct vmw_cmdbuf_man *man, size_t size, | |
| int ctx_id, bool interruptible, | |
| struct vmw_cmdbuf_header *header) | |
| { | |
| if (!header) | |
| return vmw_cmdbuf_reserve_cur(man, size, ctx_id, interruptible); | |
| if (size > header->size) | |
| return ERR_PTR(-EINVAL); | |
| if (ctx_id != SVGA3D_INVALID_ID) { | |
| header->cb_header->flags |= SVGA_CB_FLAG_DX_CONTEXT; | |
| header->cb_header->dxContext = ctx_id; | |
| } | |
| header->reserved = size; | |
| return header->cmd; | |
| } | |
| /** | |
| * vmw_cmdbuf_commit - Commit commands in a command buffer. | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The size of the commands actually written. | |
| * @header: Header of the command buffer. NULL if the current command buffer | |
| * should be used. | |
| * @flush: Whether to flush the command buffer immediately. | |
| */ | |
| void vmw_cmdbuf_commit(struct vmw_cmdbuf_man *man, size_t size, | |
| struct vmw_cmdbuf_header *header, bool flush) | |
| { | |
| if (!header) { | |
| vmw_cmdbuf_commit_cur(man, size, flush); | |
| return; | |
| } | |
| (void) vmw_cmdbuf_cur_lock(man, false); | |
| __vmw_cmdbuf_cur_flush(man); | |
| WARN_ON(size > header->reserved); | |
| man->cur = header; | |
| man->cur_pos = size; | |
| if (!size) | |
| header->cb_header->flags &= ~SVGA_CB_FLAG_DX_CONTEXT; | |
| if (flush) | |
| __vmw_cmdbuf_cur_flush(man); | |
| vmw_cmdbuf_cur_unlock(man); | |
| } | |
| /** | |
| * vmw_cmdbuf_send_device_command - Send a command through the device context. | |
| * | |
| * @man: The command buffer manager. | |
| * @command: Pointer to the command to send. | |
| * @size: Size of the command. | |
| * | |
| * Synchronously sends a device context command. | |
| */ | |
| static int vmw_cmdbuf_send_device_command(struct vmw_cmdbuf_man *man, | |
| const void *command, | |
| size_t size) | |
| { | |
| struct vmw_cmdbuf_header *header; | |
| int status; | |
| void *cmd = vmw_cmdbuf_alloc(man, size, false, &header); | |
| if (IS_ERR(cmd)) | |
| return PTR_ERR(cmd); | |
| memcpy(cmd, command, size); | |
| header->cb_header->length = size; | |
| header->cb_context = SVGA_CB_CONTEXT_DEVICE; | |
| spin_lock(&man->lock); | |
| status = vmw_cmdbuf_header_submit(header); | |
| spin_unlock(&man->lock); | |
| vmw_cmdbuf_header_free(header); | |
| if (status != SVGA_CB_STATUS_COMPLETED) { | |
| DRM_ERROR("Device context command failed with status %d\n", | |
| status); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * vmw_cmdbuf_preempt - Send a preempt command through the device | |
| * context. | |
| * | |
| * @man: The command buffer manager. | |
| * | |
| * Synchronously sends a preempt command. | |
| */ | |
| static int vmw_cmdbuf_preempt(struct vmw_cmdbuf_man *man, u32 context) | |
| { | |
| struct { | |
| uint32 id; | |
| SVGADCCmdPreempt body; | |
| } __packed cmd; | |
| cmd.id = SVGA_DC_CMD_PREEMPT; | |
| cmd.body.context = SVGA_CB_CONTEXT_0 + context; | |
| cmd.body.ignoreIDZero = 0; | |
| return vmw_cmdbuf_send_device_command(man, &cmd, sizeof(cmd)); | |
| } | |
| /** | |
| * vmw_cmdbuf_startstop - Send a start / stop command through the device | |
| * context. | |
| * | |
| * @man: The command buffer manager. | |
| * @enable: Whether to enable or disable the context. | |
| * | |
| * Synchronously sends a device start / stop context command. | |
| */ | |
| static int vmw_cmdbuf_startstop(struct vmw_cmdbuf_man *man, u32 context, | |
| bool enable) | |
| { | |
| struct { | |
| uint32 id; | |
| SVGADCCmdStartStop body; | |
| } __packed cmd; | |
| cmd.id = SVGA_DC_CMD_START_STOP_CONTEXT; | |
| cmd.body.enable = (enable) ? 1 : 0; | |
| cmd.body.context = SVGA_CB_CONTEXT_0 + context; | |
| return vmw_cmdbuf_send_device_command(man, &cmd, sizeof(cmd)); | |
| } | |
| /** | |
| * vmw_cmdbuf_set_pool_size - Set command buffer manager sizes | |
| * | |
| * @man: The command buffer manager. | |
| * @size: The size of the main space pool. | |
| * @default_size: The default size of the command buffer for small kernel | |
| * submissions. | |
| * | |
| * Set the size and allocate the main command buffer space pool, | |
| * as well as the default size of the command buffer for | |
| * small kernel submissions. If successful, this enables large command | |
| * submissions. Note that this function requires that rudimentary command | |
| * submission is already available and that the MOB memory manager is alive. | |
| * Returns 0 on success. Negative error code on failure. | |
| */ | |
| int vmw_cmdbuf_set_pool_size(struct vmw_cmdbuf_man *man, | |
| size_t size, size_t default_size) | |
| { | |
| struct vmw_private *dev_priv = man->dev_priv; | |
| bool dummy; | |
| int ret; | |
| if (man->has_pool) | |
| return -EINVAL; | |
| /* First, try to allocate a huge chunk of DMA memory */ | |
| size = PAGE_ALIGN(size); | |
| man->map = dma_alloc_coherent(&dev_priv->dev->pdev->dev, size, | |
| &man->handle, GFP_KERNEL); | |
| if (man->map) { | |
| man->using_mob = false; | |
| } else { | |
| /* | |
| * DMA memory failed. If we can have command buffers in a | |
| * MOB, try to use that instead. Note that this will | |
| * actually call into the already enabled manager, when | |
| * binding the MOB. | |
| */ | |
| if (!(dev_priv->capabilities & SVGA_CAP_DX)) | |
| return -ENOMEM; | |
| ret = ttm_bo_create(&dev_priv->bdev, size, ttm_bo_type_device, | |
| &vmw_mob_ne_placement, 0, false, | |
| &man->cmd_space); | |
| if (ret) | |
| return ret; | |
| man->using_mob = true; | |
| ret = ttm_bo_kmap(man->cmd_space, 0, size >> PAGE_SHIFT, | |
| &man->map_obj); | |
| if (ret) | |
| goto out_no_map; | |
| man->map = ttm_kmap_obj_virtual(&man->map_obj, &dummy); | |
| } | |
| man->size = size; | |
| drm_mm_init(&man->mm, 0, size >> PAGE_SHIFT); | |
| man->has_pool = true; | |
| /* | |
| * For now, set the default size to VMW_CMDBUF_INLINE_SIZE to | |
| * prevent deadlocks from happening when vmw_cmdbuf_space_pool() | |
| * needs to wait for space and we block on further command | |
| * submissions to be able to free up space. | |
| */ | |
| man->default_size = VMW_CMDBUF_INLINE_SIZE; | |
| DRM_INFO("Using command buffers with %s pool.\n", | |
| (man->using_mob) ? "MOB" : "DMA"); | |
| return 0; | |
| out_no_map: | |
| if (man->using_mob) | |
| ttm_bo_unref(&man->cmd_space); | |
| return ret; | |
| } | |
| /** | |
| * vmw_cmdbuf_man_create: Create a command buffer manager and enable it for | |
| * inline command buffer submissions only. | |
| * | |
| * @dev_priv: Pointer to device private structure. | |
| * | |
| * Returns a pointer to a cummand buffer manager to success or error pointer | |
| * on failure. The command buffer manager will be enabled for submissions of | |
| * size VMW_CMDBUF_INLINE_SIZE only. | |
| */ | |
| struct vmw_cmdbuf_man *vmw_cmdbuf_man_create(struct vmw_private *dev_priv) | |
| { | |
| struct vmw_cmdbuf_man *man; | |
| struct vmw_cmdbuf_context *ctx; | |
| unsigned int i; | |
| int ret; | |
| if (!(dev_priv->capabilities & SVGA_CAP_COMMAND_BUFFERS)) | |
| return ERR_PTR(-ENOSYS); | |
| man = kzalloc(sizeof(*man), GFP_KERNEL); | |
| if (!man) | |
| return ERR_PTR(-ENOMEM); | |
| man->num_contexts = (dev_priv->capabilities & SVGA_CAP_HP_CMD_QUEUE) ? | |
| 2 : 1; | |
| man->headers = dma_pool_create("vmwgfx cmdbuf", | |
| &dev_priv->dev->pdev->dev, | |
| sizeof(SVGACBHeader), | |
| 64, PAGE_SIZE); | |
| if (!man->headers) { | |
| ret = -ENOMEM; | |
| goto out_no_pool; | |
| } | |
| man->dheaders = dma_pool_create("vmwgfx inline cmdbuf", | |
| &dev_priv->dev->pdev->dev, | |
| sizeof(struct vmw_cmdbuf_dheader), | |
| 64, PAGE_SIZE); | |
| if (!man->dheaders) { | |
| ret = -ENOMEM; | |
| goto out_no_dpool; | |
| } | |
| for_each_cmdbuf_ctx(man, i, ctx) | |
| vmw_cmdbuf_ctx_init(ctx); | |
| INIT_LIST_HEAD(&man->error); | |
| spin_lock_init(&man->lock); | |
| mutex_init(&man->cur_mutex); | |
| mutex_init(&man->space_mutex); | |
| mutex_init(&man->error_mutex); | |
| man->default_size = VMW_CMDBUF_INLINE_SIZE; | |
| init_waitqueue_head(&man->alloc_queue); | |
| init_waitqueue_head(&man->idle_queue); | |
| man->dev_priv = dev_priv; | |
| man->max_hw_submitted = SVGA_CB_MAX_QUEUED_PER_CONTEXT - 1; | |
| INIT_WORK(&man->work, &vmw_cmdbuf_work_func); | |
| vmw_generic_waiter_add(dev_priv, SVGA_IRQFLAG_ERROR, | |
| &dev_priv->error_waiters); | |
| ret = vmw_cmdbuf_startstop(man, 0, true); | |
| if (ret) { | |
| DRM_ERROR("Failed starting command buffer contexts\n"); | |
| vmw_cmdbuf_man_destroy(man); | |
| return ERR_PTR(ret); | |
| } | |
| return man; | |
| out_no_dpool: | |
| dma_pool_destroy(man->headers); | |
| out_no_pool: | |
| kfree(man); | |
| return ERR_PTR(ret); | |
| } | |
| /** | |
| * vmw_cmdbuf_remove_pool - Take down the main buffer space pool. | |
| * | |
| * @man: Pointer to a command buffer manager. | |
| * | |
| * This function removes the main buffer space pool, and should be called | |
| * before MOB memory management is removed. When this function has been called, | |
| * only small command buffer submissions of size VMW_CMDBUF_INLINE_SIZE or | |
| * less are allowed, and the default size of the command buffer for small kernel | |
| * submissions is also set to this size. | |
| */ | |
| void vmw_cmdbuf_remove_pool(struct vmw_cmdbuf_man *man) | |
| { | |
| if (!man->has_pool) | |
| return; | |
| man->has_pool = false; | |
| man->default_size = VMW_CMDBUF_INLINE_SIZE; | |
| (void) vmw_cmdbuf_idle(man, false, 10*HZ); | |
| if (man->using_mob) { | |
| (void) ttm_bo_kunmap(&man->map_obj); | |
| ttm_bo_unref(&man->cmd_space); | |
| } else { | |
| dma_free_coherent(&man->dev_priv->dev->pdev->dev, | |
| man->size, man->map, man->handle); | |
| } | |
| } | |
| /** | |
| * vmw_cmdbuf_man_destroy - Take down a command buffer manager. | |
| * | |
| * @man: Pointer to a command buffer manager. | |
| * | |
| * This function idles and then destroys a command buffer manager. | |
| */ | |
| void vmw_cmdbuf_man_destroy(struct vmw_cmdbuf_man *man) | |
| { | |
| WARN_ON_ONCE(man->has_pool); | |
| (void) vmw_cmdbuf_idle(man, false, 10*HZ); | |
| if (vmw_cmdbuf_startstop(man, 0, false)) | |
| DRM_ERROR("Failed stopping command buffer contexts.\n"); | |
| vmw_generic_waiter_remove(man->dev_priv, SVGA_IRQFLAG_ERROR, | |
| &man->dev_priv->error_waiters); | |
| (void) cancel_work_sync(&man->work); | |
| dma_pool_destroy(man->dheaders); | |
| dma_pool_destroy(man->headers); | |
| mutex_destroy(&man->cur_mutex); | |
| mutex_destroy(&man->space_mutex); | |
| mutex_destroy(&man->error_mutex); | |
| kfree(man); | |
| } |