/* * Copyright (c) 2011 Intel Corporation. All Rights Reserved. * Copyright (c) Imagination Technologies Limited, UK * * 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 PRECISION INSIGHT 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. * * Authors: * Zeng Li * Shengquan Yuan * Binglin Chen * */ #include "lnc_cmdbuf.h" #include #include #include #include #include #include #include "psb_def.h" #include "lnc_hostcode.h" #include "psb_ws_driver.h" #include "psb_drm.h" /* * Buffer layout: * cmd_base <= cmd_idx < CMD_END() == reloc_base * reloc_base <= reloc_idx < RELOC_END() == (reloc_size) */ #define RELOC_END(cmdbuf) (cmdbuf->cmd_base + cmdbuf->size) #define CMD_END(cmdbuf) (cmdbuf->reloc_base) #define RELOC_SIZE (0x3000) #define CMD_SIZE (0x3000) #define RELOC_MARGIN (0x0800) #define CMD_MARGIN (0x0400) #define MAX_CMD_COUNT 12 #define MTX_SEG_SIZE (0x0800) /* * Create command buffer */ VAStatus lnc_cmdbuf_create( object_context_p obj_context, psb_driver_data_p driver_data, lnc_cmdbuf_p cmdbuf) { context_ENC_p ctx = (context_ENC_p) obj_context->format_data; VAStatus vaStatus = VA_STATUS_SUCCESS; unsigned int size = CMD_SIZE + RELOC_SIZE; cmdbuf->size = 0; cmdbuf->cmd_base = NULL; cmdbuf->cmd_idx = NULL; cmdbuf->reloc_base = NULL; cmdbuf->reloc_idx = NULL; cmdbuf->buffer_refs_count = 0; cmdbuf->buffer_refs_allocated = 10; cmdbuf->buffer_refs = (psb_buffer_p *) calloc(1, sizeof(psb_buffer_p) * cmdbuf->buffer_refs_allocated); if (NULL == cmdbuf->buffer_refs) { cmdbuf->buffer_refs_allocated = 0; vaStatus = VA_STATUS_ERROR_ALLOCATION_FAILED; } if (VA_STATUS_SUCCESS == vaStatus) { vaStatus = psb_buffer_create(driver_data, size, psb_bt_cpu_only, &cmdbuf->buf); cmdbuf->size = size; } if (VA_STATUS_SUCCESS != vaStatus) { free(cmdbuf->buffer_refs); cmdbuf->buffer_refs = NULL; cmdbuf->buffer_refs_allocated = 0; return vaStatus; } /* create topaz parameter buffer */ vaStatus = psb_buffer_create(driver_data, ctx->pic_params_size, psb_bt_cpu_vpu, &cmdbuf->pic_params); if (VA_STATUS_SUCCESS != vaStatus) goto error_out3; /* create header buffer */ vaStatus = psb_buffer_create(driver_data, ctx->header_buffer_size, psb_bt_cpu_vpu, &cmdbuf->header_mem); if (VA_STATUS_SUCCESS != vaStatus) goto error_out2; /* create slice parameter buffer */ vaStatus = psb_buffer_create(driver_data, ctx->sliceparam_buffer_size, psb_bt_cpu_vpu, &cmdbuf->slice_params); if (VA_STATUS_SUCCESS != vaStatus) goto error_out1; /* all cmdbuf share one MTX_CURRENT_IN_PARAMS since every MB has a MTX_CURRENT_IN_PARAMS structure * and filling this structure for all MB is very time-consuming */ cmdbuf->topaz_in_params_I = &ctx->topaz_in_params_I; cmdbuf->topaz_in_params_P = &ctx->topaz_in_params_P; cmdbuf->topaz_above_bellow_params = &ctx->topaz_above_bellow_params; return vaStatus; error_out1: psb_buffer_destroy(&cmdbuf->header_mem); error_out2: psb_buffer_destroy(&cmdbuf->pic_params); error_out3: lnc_cmdbuf_destroy(cmdbuf); return vaStatus; } /* * Destroy buffer */ void lnc_cmdbuf_destroy(lnc_cmdbuf_p cmdbuf) { if (cmdbuf->size) { psb_buffer_destroy(&cmdbuf->buf); cmdbuf->size = 0; } if (cmdbuf->buffer_refs_allocated) { free(cmdbuf->buffer_refs); cmdbuf->buffer_refs = NULL; cmdbuf->buffer_refs_allocated = 0; } psb_buffer_destroy(&cmdbuf->pic_params); psb_buffer_destroy(&cmdbuf->header_mem); psb_buffer_destroy(&cmdbuf->slice_params); } /* * Reset buffer & map * * Returns 0 on success */ int lnc_cmdbuf_reset(lnc_cmdbuf_p cmdbuf) { int ret; cmdbuf->cmd_base = NULL; cmdbuf->cmd_idx = NULL; cmdbuf->reloc_base = NULL; cmdbuf->reloc_idx = NULL; cmdbuf->buffer_refs_count = 0; cmdbuf->cmd_count = 0; ret = psb_buffer_map(&cmdbuf->buf, &cmdbuf->cmd_base); if (ret) { return ret; } cmdbuf->cmd_start = cmdbuf->cmd_base; cmdbuf->cmd_idx = (uint32_t *) cmdbuf->cmd_base; cmdbuf->reloc_base = cmdbuf->cmd_base + CMD_SIZE; cmdbuf->reloc_idx = (struct drm_psb_reloc *) cmdbuf->reloc_base; /* Add ourselves to the buffer list */ lnc_cmdbuf_buffer_ref(cmdbuf, &cmdbuf->buf); /* cmd buf == 0 */ return ret; } /* * Unmap buffer * * Returns 0 on success */ int lnc_cmdbuf_unmap(lnc_cmdbuf_p cmdbuf) { cmdbuf->cmd_base = NULL; cmdbuf->cmd_start = NULL; cmdbuf->cmd_idx = NULL; cmdbuf->reloc_base = NULL; cmdbuf->reloc_idx = NULL; cmdbuf->cmd_count = 0; psb_buffer_unmap(&cmdbuf->buf); return 0; } /* * Reference an addtional buffer "buf" in the command stream * Returns a reference index that can be used to refer to "buf" in * relocation records, -1 on error */ int lnc_cmdbuf_buffer_ref(lnc_cmdbuf_p cmdbuf, psb_buffer_p buf) { int item_loc = 0; while ((item_loc < cmdbuf->buffer_refs_count) && (cmdbuf->buffer_refs[item_loc] != buf)) { item_loc++; } if (item_loc == cmdbuf->buffer_refs_count) { /* Add new entry */ if (item_loc >= cmdbuf->buffer_refs_allocated) { /* Allocate more entries */ int new_size = cmdbuf->buffer_refs_allocated + 10; psb_buffer_p *new_array; new_array = (psb_buffer_p *) calloc(1, sizeof(psb_buffer_p) * new_size); if (NULL == new_array) { return -1; /* Allocation failure */ } memcpy(new_array, cmdbuf->buffer_refs, sizeof(psb_buffer_p) * cmdbuf->buffer_refs_allocated); free(cmdbuf->buffer_refs); cmdbuf->buffer_refs_allocated = new_size; cmdbuf->buffer_refs = new_array; } cmdbuf->buffer_refs[item_loc] = buf; cmdbuf->buffer_refs_count++; buf->status = psb_bs_queued; } return item_loc; } /* Creates a relocation record for a DWORD in the mapped "cmdbuf" at address * "addr_in_cmdbuf" * The relocation is based on the device virtual address of "ref_buffer" * "buf_offset" is be added to the device virtual address, and the sum is then * right shifted with "align_shift". * "mask" determines which bits of the target DWORD will be updated with the so * constructed address. The remaining bits will be filled with bits from "background". */ void lnc_cmdbuf_add_relocation(lnc_cmdbuf_p cmdbuf, uint32_t *addr_in_dst_buffer,/*addr of dst_buffer for the DWORD*/ psb_buffer_p ref_buffer, uint32_t buf_offset, uint32_t mask, uint32_t background, uint32_t align_shift, uint32_t dst_buffer, uint32_t *start_of_dst_buffer) /*Index of the list refered by cmdbuf->buffer_refs */ { struct drm_psb_reloc *reloc = cmdbuf->reloc_idx; uint64_t presumed_offset = wsbmBOOffsetHint(ref_buffer->drm_buf); reloc->where = addr_in_dst_buffer - start_of_dst_buffer; /* Offset in DWORDs */ reloc->buffer = lnc_cmdbuf_buffer_ref(cmdbuf, ref_buffer); ASSERT(reloc->buffer != -1); reloc->reloc_op = PSB_RELOC_OP_OFFSET; #ifndef VA_EMULATOR if (presumed_offset) { uint32_t new_val = presumed_offset + buf_offset; new_val = ((new_val >> align_shift) << (align_shift << PSB_RELOC_ALSHIFT_SHIFT)); new_val = (background & ~mask) | (new_val & mask); *addr_in_dst_buffer = new_val; } else { *addr_in_dst_buffer = PSB_RELOC_MAGIC; } #else /* indicate subscript of relocation buffer */ *addr_in_dst_buffer = reloc - (struct drm_psb_reloc *)cmdbuf->reloc_base; #endif reloc->mask = mask; reloc->shift = align_shift << PSB_RELOC_ALSHIFT_SHIFT; reloc->pre_add = buf_offset; reloc->background = background; reloc->dst_buffer = dst_buffer; cmdbuf->reloc_idx++; ASSERT(((unsigned char *)(cmdbuf->reloc_idx)) < RELOC_END(cmdbuf)); } /* * Advances "obj_context" to the next cmdbuf * * Returns 0 on success */ int lnc_context_get_next_cmdbuf(object_context_p obj_context) { lnc_cmdbuf_p cmdbuf; int ret; if (obj_context->lnc_cmdbuf) { return 0; } obj_context->cmdbuf_current++; if (obj_context->cmdbuf_current >= LNC_MAX_CMDBUFS_ENCODE) { obj_context->cmdbuf_current = 0; } cmdbuf = obj_context->lnc_cmdbuf_list[obj_context->cmdbuf_current]; ret = lnc_cmdbuf_reset(cmdbuf); if (!ret) { /* Success */ obj_context->lnc_cmdbuf = cmdbuf; } /* added pic_params/slice_params into ref, so the index is 1/2 */ lnc_cmdbuf_buffer_ref(cmdbuf, &cmdbuf->pic_params); lnc_cmdbuf_buffer_ref(cmdbuf, &cmdbuf->slice_params); return ret; } /* * This is the user-space do-it-all interface to the drm cmdbuf ioctl. * It allows different buffers as command- and reloc buffer. A list of * cliprects to apply and whether to copy the clipRect content to all * scanout buffers (damage = 1). */ /* * Don't add debug statements in this function, it gets called with the * DRM lock held and output to an X terminal can cause X to deadlock */ static int lncDRMCmdBuf(int fd, int ioctl_offset, psb_buffer_p *buffer_list, int buffer_count, unsigned cmdBufHandle, unsigned cmdBufOffset, unsigned cmdBufSize, unsigned relocBufHandle, unsigned relocBufOffset, unsigned numRelocs, drm_clip_rect_t * clipRects, int damage, unsigned engine, unsigned fence_flags, struct psb_ttm_fence_rep *fence_rep) { drm_psb_cmdbuf_arg_t ca; struct psb_validate_arg *arg_list; int i; int ret; uint64_t mask = PSB_GPU_ACCESS_MASK; arg_list = (struct psb_validate_arg *) calloc(1, sizeof(struct psb_validate_arg) * buffer_count); if (arg_list == NULL) { psb__error_message("Allocate memory failed\n"); return -ENOMEM; } for (i = 0; i < buffer_count; i++) { struct psb_validate_arg *arg = &(arg_list[i]); struct psb_validate_req *req = &arg->d.req; memset(arg, 0, sizeof(*arg)); req->next = (unsigned long) & (arg_list[i+1]); req->buffer_handle = wsbmKBufHandle(wsbmKBuf(buffer_list[i]->drm_buf)); req->group = 0; req->set_flags = (PSB_GPU_ACCESS_READ | PSB_GPU_ACCESS_WRITE) & mask; req->clear_flags = (~(PSB_GPU_ACCESS_READ | PSB_GPU_ACCESS_WRITE)) & mask; #if 1 req->presumed_gpu_offset = (uint64_t)wsbmBOOffsetHint(buffer_list[i]->drm_buf); req->presumed_flags = PSB_USE_PRESUMED; if ((req->presumed_gpu_offset >> 28) & 0x1) { psb__error_message("buffer is at the address topaz can not access\n"); ret = -1; goto out; } #else req->presumed_flags = 0; #endif req->pad64 = (uint32_t)buffer_list[i]->pl_flags; } arg_list[buffer_count-1].d.req.next = 0; memset(&ca, 0, sizeof(ca)); ca.buffer_list = (uint64_t)((unsigned long)arg_list); ca.clip_rects = (uint64_t)((unsigned long)clipRects); ca.cmdbuf_handle = cmdBufHandle; ca.cmdbuf_offset = cmdBufOffset; ca.cmdbuf_size = cmdBufSize; ca.reloc_handle = relocBufHandle; ca.reloc_offset = relocBufOffset; ca.num_relocs = numRelocs; ca.engine = engine; ca.fence_flags = fence_flags; ca.fence_arg = (uint64_t)((unsigned long)fence_rep); do { ret = drmCommandWrite(fd, ioctl_offset, &ca, sizeof(ca)); } while (ret == EAGAIN); if (ret) goto out; for (i = 0; i < buffer_count; i++) { struct psb_validate_arg *arg = &(arg_list[i]); struct psb_validate_rep *rep = &arg->d.rep; if (!arg->handled) { ret = -EFAULT; goto out; } if (arg->ret != 0) { ret = arg->ret; goto out; } wsbmUpdateKBuf(wsbmKBuf(buffer_list[i]->drm_buf), rep->gpu_offset, rep->placement, rep->fence_type_mask); } out: free(arg_list); for (i = 0; i < buffer_count; i++) { /* * Buffer no longer queued in userspace */ switch (buffer_list[i]->status) { case psb_bs_queued: buffer_list[i]->status = psb_bs_ready; break; case psb_bs_abandoned: psb_buffer_destroy(buffer_list[i]); free(buffer_list[i]); break; default: /* Not supposed to happen */ ASSERT(0); } } return ret; } #if 0 static struct _WsbmFenceObject * lnc_fence_wait(psb_driver_data_p driver_data, struct psb_ttm_fence_rep *fence_rep, int *status) { struct _WsbmFenceObject *fence = NULL; int ret = -1; /* copy fence information */ if (fence_rep->error != 0) { psb__error_message("drm failed to create a fence" " and has idled the HW\n"); DEBUG_FAILURE_RET; return NULL; } fence = wsbmFenceCreate(driver_data->fence_mgr, fence_rep->fence_class, fence_rep->fence_type, (unsigned char *)fence_rep->handle, 0); if (fence) *status = wsbmFenceFinish(fence, fence_rep->fence_type, 0); return fence; } #endif /* * Submits the current cmdbuf * * Returns 0 on success */ int lnc_context_submit_cmdbuf(object_context_p obj_context) { return 0; } /* * FrameSkip is only meaningful for RC enabled mode * Topaz raises this flag after surface N encoding is finished (vaSyncSurface gets back) * then for the next encode surface N+1 (ctx->src_surface) frameskip flag is cleared in vaBeginPicuture * and is always set in vaEndPicture:lnc_PatchRCMode * vaQuerySurfaceStatus is supposed only to be called after vaEndPicture/vaSyncSurface, * The caller should ensure the surface pertains to an encode context */ int lnc_surface_get_frameskip(psb_driver_data_p driver_data, psb_surface_p surface, int *frame_skip) { struct drm_lnc_video_getparam_arg arg; unsigned long temp; int ret = 0; /* bit31 indicate if frameskip is already settled, it is used to record the frame skip flag for old surfaces * because current FRAMESKIP in hardware can't be applied to the old surfaces * bit31 is cleared when the surface is used as encode render target or reference/reconstrucure target */ if (GET_SURFACE_INFO_skipped_flag(surface) & SURFACE_INFO_SKIP_FLAG_SETTLED) { *frame_skip = GET_SURFACE_INFO_skipped_flag(surface) & 1; return 0; } /* not settled, we get it from current HW FRAMESKIP flag */ arg.key = LNC_VIDEO_FRAME_SKIP; arg.value = (uint64_t)((unsigned long) & temp); ret = drmCommandWriteRead(driver_data->drm_fd, driver_data->getParamIoctlOffset, &arg, sizeof(arg)); if (ret == 0) { SET_SURFACE_INFO_skipped_flag(surface, temp); *frame_skip = temp; if (temp == 1) psb__information_message("Detected a skipped frame for encode\n"); } return ret; } /* * Flushes all cmdbufs */ int lnc_context_flush_cmdbuf(object_context_p obj_context) { lnc_cmdbuf_p cmdbuf = obj_context->lnc_cmdbuf; psb_driver_data_p driver_data = obj_context->driver_data; unsigned int fence_flags; struct psb_ttm_fence_rep fence_rep; unsigned int reloc_offset; unsigned int num_relocs; int ret; unsigned int cmdbuffer_size = (unsigned char *) cmdbuf->cmd_idx - cmdbuf->cmd_start; /* In bytes */ ASSERT(cmdbuffer_size < CMD_SIZE); ASSERT((unsigned char *) cmdbuf->cmd_idx < CMD_END(cmdbuf)); /* LOCK */ ret = LOCK_HARDWARE(driver_data); if (ret) { UNLOCK_HARDWARE(driver_data); DEBUG_FAILURE_RET; return ret; } /* Now calculate the total number of relocations */ reloc_offset = cmdbuf->reloc_base - cmdbuf->cmd_base; num_relocs = (((unsigned char *) cmdbuf->reloc_idx) - cmdbuf->reloc_base) / sizeof(struct drm_psb_reloc); lnc_cmdbuf_unmap(cmdbuf); ASSERT(NULL == cmdbuf->reloc_base); #ifdef DEBUG_TRACE fence_flags = 0; #else fence_flags = DRM_PSB_FENCE_NO_USER; #endif #ifndef LNC_ENGINE_ENCODE #define LNC_ENGINE_ENCODE 5 #endif wsbmWriteLockKernelBO(); ret = lncDRMCmdBuf(driver_data->drm_fd, driver_data->execIoctlOffset, cmdbuf->buffer_refs, cmdbuf->buffer_refs_count, wsbmKBufHandle(wsbmKBuf(cmdbuf->buf.drm_buf)), 0, cmdbuffer_size,/*unsigned cmdBufSize*/ wsbmKBufHandle(wsbmKBuf(cmdbuf->buf.drm_buf)), reloc_offset, num_relocs, 0 /* clipRects */, 0, LNC_ENGINE_ENCODE, fence_flags, &fence_rep); wsbmWriteUnlockKernelBO(); UNLOCK_HARDWARE(driver_data); if (ret) { obj_context->lnc_cmdbuf = NULL; DEBUG_FAILURE_RET; return ret; } #if 0 /*DEBUG_TRACE*/ int status = -1; struct _WsbmFenceObject *fence = NULL; fence = lnc_fence_wait(driver_data, &fence_rep, &status); psb__information_message("psb_fence_wait returns: %d (fence=0x%08x)\n", status, fence); if (fence) wsbmFenceUnreference(fence); #endif obj_context->lnc_cmdbuf = NULL; return 0; }