/** Start building context buffer. * Subsequent etna_reserve and other state setting commands will go to * the context buffer instead of the command buffer. * initial_pipe is the pipe as it has to be at the beginning of the context * buffer. * * @return ETNA_OK if succesful (can start building context) * or an error code otherwise. */ static int gpu_context_build_start(struct etna_ctx *ctx) { if(ctx->cur_buf == ETNA_CTX_BUFFER) return ETNA_INTERNAL_ERROR; /* Save current buffer id and position */ ctx->cmdbuf[ctx->cur_buf]->offset = ctx->offset * 4; ctx->stored_buf = ctx->cur_buf; /* Switch to context buffer */ ctx->cur_buf = ETNA_CTX_BUFFER; ctx->buf = GCCTX(ctx)->logical; ctx->offset = GCCTX(ctx)->bufferSize / 4; return ETNA_OK; }
int etna_set_pipe(struct etna_ctx *ctx, enum etna_pipe pipe) { int status; if(ctx == NULL) return ETNA_INVALID_ADDR; if((status = etna_reserve(ctx, 2)) != ETNA_OK) return status; ETNA_EMIT_LOAD_STATE(ctx, VIVS_GL_FLUSH_CACHE>>2, 1, 0); switch(pipe) { case ETNA_PIPE_2D: ETNA_EMIT(ctx, VIVS_GL_FLUSH_CACHE_PE2D); break; case ETNA_PIPE_3D: ETNA_EMIT(ctx, VIVS_GL_FLUSH_CACHE_DEPTH | VIVS_GL_FLUSH_CACHE_COLOR); break; default: return ETNA_INVALID_VALUE; } etna_stall(ctx, SYNC_RECIPIENT_FE, SYNC_RECIPIENT_PE); if((status = etna_reserve(ctx, 2)) != ETNA_OK) return status; ETNA_EMIT_LOAD_STATE(ctx, VIVS_GL_PIPE_SELECT>>2, 1, 0); ETNA_EMIT(ctx, pipe); #ifdef GCABI_HAS_CONTEXT if(ctx->cur_buf != ETNA_CTX_BUFFER) { GCCTX(ctx)->currentPipe = pipe; } #endif return ETNA_OK; }
/** Finish building context buffer. * final_pipe is the current pipe at the end of the context buffer. */ static int gpu_context_build_end(struct etna_ctx *ctx, enum etna_pipe final_pipe) { if(ctx->cur_buf != ETNA_CTX_BUFFER) return ETNA_INTERNAL_ERROR; /* If closing pipe of context is different from entry pipe, add a switch * command, as we want the context to end in the entry pipe. The kernel * will handle switching to the entry pipe only when this is a new context. */ if(final_pipe != GCCTX(ctx)->entryPipe) { etna_set_pipe(ctx, GCCTX(ctx)->entryPipe); } /* Set current size -- finishing up context before flush * will add space for a LINK command and inUse flag. */ GCCTX(ctx)->bufferSize = ctx->offset * 4; /* Switch back to stored buffer */ ctx->cur_buf = ctx->stored_buf; ctx->buf = VIV_TO_PTR(ctx->cmdbuf[ctx->cur_buf]->logical); ctx->offset = ctx->cmdbuf[ctx->cur_buf]->offset / 4; return ETNA_OK; }
/* Clear GPU context, to rebuild it for next flush */ static int gpu_context_clear(struct etna_ctx *ctx) { /* If context was used, queue free it and allocate new buffer to prevent * overwriting it while being used by the GPU. Otherwise we can just * re-use it. */ int rv; #ifdef DEBUG fprintf(stderr, "gpu_context_clear (context %i)\n", (int)GCCTX(ctx)->id); #endif if(GCCTX(ctx)->inUse != NULL && *GCCTX(ctx)->inUse) { #ifdef DEBUG fprintf(stderr, "gpu_context_clear: context was in use, deferred freeing and reallocating it\n"); #endif if((rv = etna_bo_del(ctx->conn, ctx->ctx_bo, ctx->queue)) != ETNA_OK) { return rv; } if((ctx->ctx_bo = etna_bo_new(ctx->conn, COMMAND_BUFFER_SIZE, DRM_ETNA_GEM_TYPE_CMD)) == NULL) { return ETNA_OUT_OF_MEMORY; } } /* Leave space at beginning of buffer for PIPE switch */ GCCTX(ctx)->bufferSize = BEGIN_COMMIT_CLEARANCE; GCCTX(ctx)->logical = etna_bo_map(ctx->ctx_bo); #ifdef GCABI_CONTEXT_HAS_PHYSICAL GCCTX(ctx)->bytes = etna_bo_size(ctx->ctx_bo); /* actual size of buffer */ GCCTX(ctx)->physical = HANDLE_TO_VIV(etna_bo_gpu_address(ctx->ctx_bo)); #endif /* When context is empty, initial pipe should default to entry pipe so that * no pipe switch is needed within the context and the kernel does the * right thing. */ GCCTX(ctx)->initialPipe = GCCTX(ctx)->entryPipe; return ETNA_OK; }
/* Clear GPU context, to rebuild it for next flush */ static int gpu_context_clear(struct etna_ctx *ctx) { /* If context was used, queue free it and allocate new buffer to prevent * overwriting it while being used by the GPU. Otherwise we can just * re-use it. */ int rv; #ifdef DEBUG printf("gpu_context_clear (context %i)\n", (int)GCCTX(ctx)->id); #endif if(GCCTX(ctx)->inUse != NULL && *GCCTX(ctx)->inUse) { #ifdef DEBUG printf("gpu_context_clear: context was in use, deferred freeing and reallocating it\n"); #endif if((rv = gpu_context_free_buffer(ctx, &ctx->ctx_info, true)) != ETNA_OK) { return rv; } if((rv = gpu_context_allocate_buffer(ctx, &ctx->ctx_info)) != ETNA_OK) { return rv; } } /* Leave space at beginning of buffer for PIPE switch */ GCCTX(ctx)->bufferSize = BEGIN_COMMIT_CLEARANCE; GCCTX(ctx)->logical = ctx->ctx_info.logical; #ifdef GCABI_CONTEXT_HAS_PHYSICAL GCCTX(ctx)->bytes = ctx->ctx_info.bytes; /* actual size of buffer */ GCCTX(ctx)->physical = HANDLE_TO_VIV(ctx->ctx_info.physical); #endif /* When context is empty, initial pipe should default to entry pipe so that * no pipe switch is needed within the context and the kernel does the * right thing. */ GCCTX(ctx)->initialPipe = GCCTX(ctx)->entryPipe; return ETNA_OK; }
/** Finish up GPU context, make it ready for submission to kernel. Append space for LINK and inUse flag. */ static int gpu_context_finish_up(struct etna_ctx *ctx) { uint32_t *logical = GCCTX(ctx)->logical; uint32_t ptr = GCCTX(ctx)->bufferSize/4; /* Append LINK (8 bytes) */ GCCTX(ctx)->link = &logical[ptr]; logical[ptr++] = VIV_FE_NOP_HEADER_OP_NOP; logical[ptr++] = VIV_FE_NOP_HEADER_OP_NOP; /* Append inUse (4 bytes) */ GCCTX(ctx)->inUse = (int*)&logical[ptr]; logical[ptr++] = 0; /* Update buffer size to final value */ GCCTX(ctx)->bufferSize = ptr*4; #ifdef DEBUG fprintf(stderr, "gpu_context_finish_up: bufferSize %i link %p inUse %p\n", (int)GCCTX(ctx)->bufferSize, GCCTX(ctx)->link, GCCTX(ctx)->inUse); #endif return ETNA_OK; }
int etna_flush(struct etna_ctx *ctx, uint32_t *fence_out) { int status = ETNA_OK; if(ctx == NULL) return ETNA_INVALID_ADDR; if(ctx->cur_buf == ETNA_CTX_BUFFER) /* Can never flush while building context buffer */ return ETNA_INTERNAL_ERROR; if(fence_out) /* is a fence handle requested? */ { uint32_t fence; int signal; /* Need to lock the fence mutex to make sure submits are ordered by * fence number. */ pthread_mutex_lock(&ctx->conn->fence_mutex); do { /* Get next fence ID */ if((status = _viv_fence_new(ctx->conn, &fence, &signal)) != VIV_STATUS_OK) { fprintf(stderr, "%s: could not request fence\n", __func__); goto unlock_and_return_status; } } while(fence == 0); /* don't return fence handle 0 as it is interpreted as error value downstream */ /* Queue the signal. This can call in turn call this function (but * without fence) if the queue was full, so we should be able to handle * that. In that case, we will exit from this function with only * this fence in the queue and an empty command buffer. */ if((status = etna_queue_signal(ctx->queue, signal, VIV_WHERE_PIXEL)) != ETNA_OK) { fprintf(stderr, "%s: error %i queueing fence signal %i\n", __func__, status, signal); goto unlock_and_return_status; } *fence_out = fence; } /***** Start fence mutex locked */ /* Make sure to unlock the mutex before returning */ struct _gcsQUEUE *queue_first = _etna_queue_first(ctx->queue); gcoCMDBUF cur_buf = (ctx->cur_buf != ETNA_NO_BUFFER) ? ctx->cmdbuf[ctx->cur_buf] : NULL; if(cur_buf == NULL || (ctx->offset*4 <= (cur_buf->startOffset + BEGIN_COMMIT_CLEARANCE))) { /* Nothing in command buffer; but if we end up here there may be kernel commands to submit. Do this seperately. */ if(queue_first != NULL) { ctx->flushes = 0; if((status = viv_event_commit(ctx->conn, queue_first)) != 0) { #ifdef DEBUG fprintf(stderr, "Error committing kernel commands\n"); #endif goto unlock_and_return_status; } if(fence_out) /* mark fence as submitted to kernel */ _viv_fence_mark_pending(ctx->conn, *fence_out); } goto unlock_and_return_status; } cur_buf->offset = ctx->offset*4; /* Copy over current end offset into CMDBUF, for kernel */ #ifdef DEBUG fprintf(stderr, "Committing command buffer %i startOffset=%x offset=%x\n", ctx->cur_buf, cur_buf->startOffset, ctx->offset*4); #endif #ifdef DEBUG_CMDBUF etna_dump_cmd_buffer(ctx); #endif #ifdef GCABI_HAS_CONTEXT gpu_context_finish_up(ctx); #endif if(!queue_first) ctx->flushes += 1; else ctx->flushes = 0; if((status = viv_commit(ctx->conn, cur_buf, ctx->ctx, queue_first)) != 0) { #ifdef DEBUG fprintf(stderr, "Error committing command buffer\n"); #endif goto unlock_and_return_status; } if(fence_out) { _viv_fence_mark_pending(ctx->conn, *fence_out); pthread_mutex_unlock(&ctx->conn->fence_mutex); } /***** End fence mutex locked */ #ifdef GCABI_HAS_CONTEXT /* set context entryPipe to currentPipe (next commit will start with current pipe) */ GCCTX(ctx)->entryPipe = GCCTX(ctx)->currentPipe; gpu_context_clear(ctx); if(ctx->ctx_cb) { enum etna_pipe initial_pipe, final_pipe; /* Start building GPU context */ if((status = gpu_context_build_start(ctx)) != ETNA_OK) { fprintf(stderr, "%s: gpu_context_build_start failed with status %i\n", __func__, status); return status; } if((status = ctx->ctx_cb(ctx->ctx_cb_data, ctx, &initial_pipe, &final_pipe)) != ETNA_OK) { fprintf(stderr, "%s: Context callback failed with status %i\n", __func__, status); return status; } /* Set initial pipe in context */ GCCTX(ctx)->initialPipe = initial_pipe; /* Finish building GPU context */ if((status = gpu_context_build_end(ctx, final_pipe)) != ETNA_OK) { fprintf(stderr, "%s: gpu_context_build_end failed with status %i\n", __func__, status); return status; } } #endif cur_buf->startOffset = cur_buf->offset + END_COMMIT_CLEARANCE; cur_buf->offset = cur_buf->startOffset + BEGIN_COMMIT_CLEARANCE; if((cur_buf->offset + END_COMMIT_CLEARANCE) >= COMMAND_BUFFER_SIZE || ctx->flushes > ETNA_MAX_UNSIGNALED_FLUSHES) { /* nothing more fits in buffer, prevent warning about buffer overflow on next etna_reserve. */ cur_buf->startOffset = cur_buf->offset = COMMAND_BUFFER_SIZE - END_COMMIT_CLEARANCE; } /* Set writing offset for next etna_reserve. For convenience this is stored as an index instead of a byte offset. */ ctx->offset = cur_buf->offset / 4; #ifdef DEBUG #ifdef GCABI_HAS_CONTEXT fprintf(stderr, " New start offset: %x New offset: %x Contextbuffer used: %i\n", cur_buf->startOffset, cur_buf->offset, *(GCCTX(ctx)->inUse)); #else fprintf(stderr, " New start offset: %x New offset: %x\n", cur_buf->startOffset, cur_buf->offset); #endif #endif return ETNA_OK; unlock_and_return_status: /* Unlock fence mutex (if necessary) and return status */ if(fence_out) pthread_mutex_unlock(&ctx->conn->fence_mutex); return status; }
int etna_flush(struct etna_ctx *ctx) { int status; if(ctx == NULL) return ETNA_INVALID_ADDR; if(ctx->cur_buf == ETNA_CTX_BUFFER) /* Can never flush while building context buffer */ return ETNA_INTERNAL_ERROR; struct _gcsQUEUE *queue_first = etna_queue_first(ctx->queue); if(ctx->cur_buf == ETNA_NO_BUFFER) goto nothing_to_do; gcoCMDBUF cur_buf = ctx->cmdbuf[ctx->cur_buf]; ETNA_ALIGN(ctx); /* make sure end of submitted command buffer end is aligned */ #ifdef DEBUG printf("Committing command buffer %i startOffset=%x offset=%x\n", ctx->cur_buf, cur_buf->startOffset, ctx->offset*4); #endif if(ctx->offset*4 <= (cur_buf->startOffset + BEGIN_COMMIT_CLEARANCE)) goto nothing_to_do; cur_buf->offset = ctx->offset*4; /* Copy over current ending offset into CMDBUF, for kernel */ #ifdef DEBUG_CMDBUF etna_dump_cmd_buffer(ctx); #endif #ifdef GCABI_HAS_CONTEXT gpu_context_finish_up(ctx); #endif if(!queue_first) ctx->flushes += 1; else ctx->flushes = 0; if((status = viv_commit(ctx->conn, cur_buf, ctx->ctx, queue_first)) != 0) { #ifdef DEBUG fprintf(stderr, "Error committing command buffer\n"); #endif return status; } if((status = etna_queue_clear(ctx->queue)) != ETNA_OK) return status; #ifdef GCABI_HAS_CONTEXT /* set context entryPipe to currentPipe (next commit will start with current pipe) */ GCCTX(ctx)->entryPipe = GCCTX(ctx)->currentPipe; gpu_context_clear(ctx); if(ctx->ctx_cb) { enum etna_pipe initial_pipe, final_pipe; /* Start building GPU context */ if((status = gpu_context_build_start(ctx)) != ETNA_OK) { printf("%s: gpu_context_build_start failed with status %i\n", __func__, status); return status; } if((status = ctx->ctx_cb(ctx->ctx_cb_data, ctx, &initial_pipe, &final_pipe)) != ETNA_OK) { printf("%s: Context callback failed with status %i\n", __func__, status); return status; } /* Set initial pipe in context */ GCCTX(ctx)->initialPipe = initial_pipe; /* Finish building GPU context */ if((status = gpu_context_build_end(ctx, final_pipe)) != ETNA_OK) { printf("%s: gpu_context_build_end failed with status %i\n", __func__, status); return status; } } #endif cur_buf->startOffset = cur_buf->offset + END_COMMIT_CLEARANCE; cur_buf->offset = cur_buf->startOffset + BEGIN_COMMIT_CLEARANCE; if((cur_buf->offset + END_COMMIT_CLEARANCE) >= COMMAND_BUFFER_SIZE || ctx->flushes > ETNA_MAX_UNSIGNALED_FLUSHES) { /* nothing more fits in buffer, prevent warning about buffer overflow on next etna_reserve. */ cur_buf->startOffset = cur_buf->offset = COMMAND_BUFFER_SIZE - END_COMMIT_CLEARANCE; } /* Set writing offset for next etna_reserve. For convenience this is stored as an index instead of a byte offset. */ ctx->offset = cur_buf->offset / 4; #ifdef DEBUG #ifdef GCABI_HAS_CONTEXT printf(" New start offset: %x New offset: %x Contextbuffer used: %i\n", cur_buf->startOffset, cur_buf->offset, *(GCCTX(ctx)->inUse)); #else printf(" New start offset: %x New offset: %x\n", cur_buf->startOffset, cur_buf->offset); #endif #endif return ETNA_OK; nothing_to_do: /* Nothing in command buffer; but there may be kernel commands to submit. Do this seperately. */ if(queue_first != NULL) { ctx->flushes = 0; if((status = viv_event_commit(ctx->conn, queue_first)) != 0) { #ifdef DEBUG fprintf(stderr, "Error committing kernel commands\n"); #endif return status; } if((status = etna_queue_clear(ctx->queue)) != ETNA_OK) return status; } return ETNA_OK; }