| /* |
| * Copyright (c) 2007-2017, Intel Corporation |
| * |
| * 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, sublicense, |
| * 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 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 NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS 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. |
| */ |
| //! |
| //! \file cm_wrapper_os.cpp |
| //! \brief Contains implementations of Linux-dependent functions for executing |
| //! commands from cmrtlib. |
| //! |
| |
| #include "cm_wrapper.h" |
| |
| #include "cm_device_rt.h" |
| #include "cm_surface_2d_rt.h" |
| #include "media_libva_util.h" |
| #include "cm_extension_creator.h" |
| #include "mos_os_specific.h" |
| |
| extern MOS_FORMAT Mos_Specific_FmtOsToMos( |
| MOS_OS_FORMAT format); |
| |
| extern MOS_OS_FORMAT Mos_Specific_FmtMosToOs( |
| MOS_FORMAT format); |
| |
| using CMRT_UMD::CmDeviceRT; |
| //! |
| //! \brief Create Cm Device from VA Driver Context. |
| //! \details Create a CmCtx and a associated MOS_CONTEXT. Put the CmCtx into |
| //! the heap of VA Context. |
| //! \param vaDriverCtx |
| //! [in] pointer to va drv conetext. |
| //! \param device |
| //! [in,out] reference to cm device pointer. |
| //! \param devOption |
| //! [in] cm device creation option. |
| //! \return int32_t |
| //! CM_SUCCESS if success, else fail reason. |
| //! |
| int32_t CreateCmDeviceFromVA(VADriverContextP vaDriverCtx, |
| CmDevice* &device, |
| uint32_t devOption) |
| { |
| int32_t hRes = CM_SUCCESS; |
| PDDI_MEDIA_CONTEXT mediaCtx; |
| PCM_CONTEXT cmCtx; |
| uint32_t ctxIndex; |
| PDDI_MEDIA_VACONTEXT_HEAP_ELEMENT vaCtxHeapElement; |
| VAContextID vaContextID; |
| |
| mediaCtx = DdiMedia_GetMediaContext(vaDriverCtx); |
| |
| // allocate cmCtx |
| cmCtx = (PCM_CONTEXT)MOS_AllocAndZeroMemory(sizeof(CM_CONTEXT)); |
| CM_CHK_NULL_RETURN_WITH_MSG(cmCtx, CM_INVALID_UMD_CONTEXT, "Null cmCtx!"); |
| |
| // init cmCtx |
| cmCtx->mosCtx.bufmgr = mediaCtx->pDrmBufMgr; |
| cmCtx->mosCtx.m_gpuContextMgr = mediaCtx->m_gpuContextMgr; |
| cmCtx->mosCtx.m_cmdBufMgr = mediaCtx->m_cmdBufMgr; |
| cmCtx->mosCtx.fd = mediaCtx->fd; |
| cmCtx->mosCtx.wRevision = 0; |
| cmCtx->mosCtx.iDeviceId = mediaCtx->iDeviceId; |
| cmCtx->mosCtx.SkuTable = mediaCtx->SkuTable; |
| cmCtx->mosCtx.WaTable = mediaCtx->WaTable; |
| cmCtx->mosCtx.gtSystemInfo = *(mediaCtx->pGtSystemInfo); |
| cmCtx->mosCtx.platform = mediaCtx->platform; |
| cmCtx->mosCtx.pGmmClientContext = mediaCtx->pGmmClientContext; |
| cmCtx->mosCtx.m_osDeviceContext = mediaCtx->m_osDeviceContext; |
| cmCtx->mosCtx.m_apoMosEnabled = mediaCtx->m_apoMosEnabled; |
| cmCtx->mosCtx.m_auxTableMgr = mediaCtx->m_auxTableMgr; |
| cmCtx->mosCtx.pPerfData = (PERF_DATA *)MOS_AllocAndZeroMemory(sizeof(PERF_DATA)); |
| |
| if (cmCtx->mosCtx.pPerfData == nullptr) |
| { |
| MOS_FreeMemAndSetNull(cmCtx); // free cm ctx |
| CM_ASSERTMESSAGE("Failed to allocate perfData in mos context \n"); |
| return CM_OUT_OF_HOST_MEMORY; |
| } |
| |
| // Create Cm Device |
| hRes = CreateCmDevice(&(cmCtx->mosCtx), device, devOption); |
| if(hRes != CM_SUCCESS) |
| { |
| MOS_FreeMemAndSetNull(cmCtx); // free cm ctx |
| CM_ASSERTMESSAGE("Failed to call CmDevice::Create Error %d \n",hRes); |
| return hRes; |
| } |
| CmDeviceRT* deviceRT = static_cast<CmDeviceRT*>(device); |
| DdiMediaUtil_LockMutex(&mediaCtx->CmMutex); |
| |
| // get Free Cm context index |
| vaCtxHeapElement = DdiMediaUtil_AllocPVAContextFromHeap(mediaCtx->pCmCtxHeap); |
| if (nullptr == vaCtxHeapElement) |
| { |
| CmDeviceRT::Destroy(deviceRT); // destroy cm device |
| device = nullptr; |
| MOS_FreeMemAndSetNull(cmCtx); // free cm ctx |
| DdiMediaUtil_UnLockMutex(&mediaCtx->CmMutex); |
| CM_ASSERTMESSAGE("CM Context number exceeds maximum."); |
| return VA_STATUS_ERROR_INVALID_CONTEXT; |
| } |
| |
| // store cmCtx in pMedia |
| vaCtxHeapElement->pVaContext = (void *)cmCtx; |
| vaContextID = (VAContextID)(vaCtxHeapElement->uiVaContextID + DDI_MEDIA_VACONTEXTID_OFFSET_CM); |
| |
| //Set VaCtx ID to Cm device |
| deviceRT->SetVaCtxID(vaContextID); |
| |
| // increate CM context number |
| mediaCtx->uiNumCMs++; |
| |
| DdiMediaUtil_UnLockMutex(&mediaCtx->CmMutex); |
| |
| return hRes; |
| } |
| |
| //! |
| //! \brief Destroy Cm Device and free heap in VA context. |
| //! \param vaDriverCtx |
| //! [in] pointer to va drv conetext. |
| //! \param device |
| //! [in] pointer to cm device to release. |
| //! \return int32_t |
| //! CM_SUCCESS if success, else fail reason. |
| //! |
| int32_t DestroyCmDeviceFromVA(VADriverContextP vaDriverCtx, CmDevice *device) |
| { |
| int32_t hr = CM_SUCCESS; |
| uint32_t index; |
| PDDI_MEDIA_CONTEXT mediaCtx; |
| PCM_CONTEXT cmCtx; |
| VAContextID vaContextID; |
| uint32_t ctxType; |
| VAStatus vaStatus; |
| |
| if (nullptr == vaDriverCtx) |
| { |
| CM_ASSERTMESSAGE("Pointer to VADriverContext is invalid."); |
| return CM_NULL_POINTER; |
| } |
| CmDeviceRT* deviceRT = static_cast<CmDeviceRT*>(device); |
| if (nullptr == deviceRT) |
| { |
| return CM_SUCCESS; |
| } |
| //Get VaCtx ID in MediaCtx |
| deviceRT->GetVaCtxID(vaContextID); |
| |
| // Get Cm context index |
| index = vaContextID & DDI_MEDIA_MASK_VACONTEXTID; |
| |
| //Get Cm Context |
| cmCtx = (PCM_CONTEXT)DdiMedia_GetContextFromContextID(vaDriverCtx, vaContextID, &ctxType); |
| CM_CHK_NULL_RETURN_WITH_MSG(cmCtx, VA_STATUS_ERROR_INVALID_CONTEXT, "Null cmCtx."); |
| |
| CM_CHK_CMSTATUS_GOTOFINISH(DestroyCmDevice(device)); |
| |
| // remove from context array |
| mediaCtx = DdiMedia_GetMediaContext(vaDriverCtx); |
| DdiMediaUtil_LockMutex(&mediaCtx->CmMutex); |
| |
| MOS_FreeMemAndSetNull(cmCtx->mosCtx.pPerfData); |
| |
| // destroy Cm context |
| MOS_FreeMemAndSetNull(cmCtx); |
| |
| DdiMediaUtil_ReleasePVAContextFromHeap(mediaCtx->pCmCtxHeap, index); |
| |
| mediaCtx->uiNumCMs--; |
| |
| DdiMediaUtil_UnLockMutex(&mediaCtx->CmMutex); |
| |
| finish: |
| return hr; |
| } |
| |
| extern MOS_FORMAT VpGetFormatFromMediaFormat(DDI_MEDIA_FORMAT mf); |
| //*----------------------------------------------------------------------------- |
| //| Purpose: Get resource information from LibVA-created surface and fill into OsResource |
| // vaSurfaceID is the index to VA's surface |
| //| Returns: Result of the operation. |
| //*----------------------------------------------------------------------------- |
| int32_t CmFillMosResource( VASurfaceID vaSurfaceID, |
| VADriverContext* vaDriverCtx, |
| PMOS_RESOURCE osResource) |
| { |
| PDDI_MEDIA_CONTEXT mediaCtx; |
| DDI_MEDIA_SURFACE *surface; |
| CmDevice *device; |
| |
| CM_CHK_NULL_RETURN_WITH_MSG(vaDriverCtx, CM_INVALID_UMD_CONTEXT, "Null umdCtx"); |
| |
| mediaCtx = DdiMedia_GetMediaContext(vaDriverCtx); |
| CM_CHK_NULL_RETURN_WITH_MSG(mediaCtx, CM_INVALID_UMD_CONTEXT, "Null mediaCtx"); |
| |
| CM_CHK_NULL_RETURN_WITH_MSG(mediaCtx->pSurfaceHeap, CM_INVALID_UMD_CONTEXT, "Null mediaCtx->pSurfaceHeap"); |
| CM_CHK_COND_RETURN(((uint32_t)vaSurfaceID >= mediaCtx->pSurfaceHeap->uiAllocatedHeapElements), CM_INVALID_LIBVA_SURFACE, "Invalid surface"); |
| surface = DdiMedia_GetSurfaceFromVASurfaceID(mediaCtx, vaSurfaceID); |
| CM_CHK_NULL_RETURN_WITH_MSG(surface, CM_INVALID_LIBVA_SURFACE, "Null surface"); |
| CM_ASSERT(surface->iPitch == GFX_ULONG_CAST(surface->pGmmResourceInfo->GetRenderPitch())); |
| CM_CHK_NULL_RETURN_WITH_MSG(surface->bo, CM_INVALID_LIBVA_SURFACE, "Null BO"); |
| CM_CHK_NULL_RETURN_WITH_MSG(surface->pGmmResourceInfo, CM_INVALID_LIBVA_SURFACE, "Null GMMResInfo"); |
| |
| // Resets the Resource |
| Mos_ResetResource(osResource); |
| |
| osResource->iWidth = surface->iWidth; |
| osResource->iHeight = surface->iHeight; |
| osResource->iDepth = 1; |
| osResource->iPitch = surface->iPitch; |
| |
| osResource->iCount = 0; |
| osResource->bufname = (char *)"Libva2DSurface"; |
| |
| osResource->Format = VpGetFormatFromMediaFormat(surface->format); |
| osResource->bo = surface->bo; |
| |
| osResource->TileType = LinuxToMosTileType(surface->TileType); |
| osResource->isTiled = surface->isTiled; |
| |
| osResource->bMapped = surface->bMapped; |
| osResource->pData = (uint8_t*) surface->bo->virt; |
| |
| osResource->pGmmResInfo = surface->pGmmResourceInfo; |
| |
| // for wrapper to new MOS MODS interface |
| osResource->bConvertedFromDDIResource = true; |
| |
| return CM_SUCCESS; |
| } |
| |
| MOS_FORMAT CmOSFmtToMosFmt(CM_OSAL_SURFACE_FORMAT format) |
| { |
| //CM_SURFACE_FORMAT_R8U/R16U are not valid va surface format. |
| //These two formats are MDF specific, so we add the mapping here. |
| switch(format) |
| { |
| case CM_SURFACE_FORMAT_R8U: return Format_R8U; |
| case CM_SURFACE_FORMAT_R16U: return Format_R16U; |
| default: |
| return Mos_Specific_FmtOsToMos(format); |
| } |
| } |
| |
| CM_OSAL_SURFACE_FORMAT CmMosFmtToOSFmt(MOS_FORMAT format) |
| { |
| //CM_SURFACE_FORMAT_R8U/R16U are not valid va surface format. |
| //These two formats are MDF specific, so we add the mapping here. |
| switch(format) |
| { |
| case Format_R8U: return CM_SURFACE_FORMAT_R8U; |
| case Format_R16U: return CM_SURFACE_FORMAT_R16U; |
| default: |
| return Mos_Specific_FmtMosToOs(format); |
| } |
| } |
| |
| int32_t ConvertToOperatingSystemAbstractionLayerFormat(void *src, uint32_t numOfFormats) |
| { |
| uint32_t i = 0 ; |
| if (src == nullptr || numOfFormats == 0) |
| { |
| CM_ASSERTMESSAGE("Error: Invalid input arguments."); |
| return CM_INVALID_ARG_VALUE; |
| } |
| |
| for (i = 0; i < numOfFormats; ++i) |
| { |
| *((CM_OSAL_SURFACE_FORMAT*)src + i) = CmMosFmtToOSFmt(*((CM_SURFACE_FORMAT*)src + i)); |
| } |
| return CM_SUCCESS; |
| } |
| |
| struct CM_CREATESURFACE2DUP_PARAM |
| { |
| uint32_t width; // [in] width of 2D texture in pixel |
| uint32_t height; // [in] height of 2D texture in pixel |
| CM_OSAL_SURFACE_FORMAT format; // [in] 2D texture foramt in OS layer. |
| void *sysMem; // [in] Pointer to system memory |
| void *surface2DUPHandle; // [out] pointer of CmSurface2D used in driver |
| int32_t returnValue; // [out] the return value from driver |
| }; |
| typedef CM_CREATESURFACE2DUP_PARAM *PCM_CREATESURFACE2DUP_PARAM; |
| |
| struct CM_GETSURFACE2DINFO_PARAM |
| { |
| uint32_t width; // [in] Surface Width |
| uint32_t height; // [in] Surface Height |
| CM_OSAL_SURFACE_FORMAT format; // [in] Surface Format |
| uint32_t pitch; // [out] Pitch |
| uint32_t physicalSize; // [out] Physical size |
| uint32_t returnValue; // [out] Return value |
| }; |
| typedef CM_GETSURFACE2DINFO_PARAM *PCM_GETSURFACE2DINFO_PARAM; |
| |
| struct CM_CREATE_SURFACE3D_PARAM |
| { |
| uint32_t width; // [in] width of 3D in pixel |
| uint32_t height; // [in] height of 3D in pixel |
| uint32_t depth; // [in] depth of 3D surface in pixel |
| CM_OSAL_SURFACE_FORMAT format; // [in] 2D texture foramt in OS abstraction layer. |
| void *surface3DHandle; // [out] pointer of CmSurface3D used in driver |
| int32_t returnValue; // [out] the return value from driver |
| }; |
| typedef CM_CREATE_SURFACE3D_PARAM *PCM_CREATE_SURFACE3D_PARAM; |
| |
| using CMRT_UMD::CmSurface2DRT; |
| using CMRT_UMD::CmWrapperEx; |
| using CMRT_UMD::CmSurface2DUP; |
| using CMRT_UMD::CmSurface3D; |
| //*----------------------------------------------------------------------------- |
| //| Purpose: CMRT thin layer library supported function execution |
| //| Return: CM_SUCCESS if successful |
| //*----------------------------------------------------------------------------- |
| int32_t CmThinExecute(VADriverContextP vaDriverCtx, |
| void *deviceHandle, |
| uint32_t inputFunctionId, |
| void *inputData, |
| uint32_t inputDataLen) |
| { |
| CmDevice *device = nullptr; |
| CmDeviceRT *deviceRT = nullptr; |
| VADriverContextP hUMDevice = nullptr; |
| void *cmPrivateInputData = nullptr; |
| uint32_t cmPrivateInputDataSize = 0 ; |
| CMRT_UMD::CmSurface2D *pCmSurface2d = nullptr; |
| SurfaceIndex *surfaceIndex = nullptr; |
| CM_FUNCTION_ID cmFunctionID; |
| int32_t hr = CM_SUCCESS; |
| int32_t cmRet = CM_INVALID_PRIVATE_DATA; |
| |
| hUMDevice = vaDriverCtx; |
| cmPrivateInputData = inputData; |
| cmPrivateInputDataSize = inputDataLen; |
| cmFunctionID = (CM_FUNCTION_ID)inputFunctionId; |
| device = (CmDevice *)deviceHandle; |
| deviceRT = static_cast<CmDeviceRT*>(device); |
| |
| switch(cmFunctionID) |
| { |
| case CM_FN_CREATECMDEVICE: |
| PCM_CREATECMDEVICE_PARAM cmDeviceParam; |
| cmDeviceParam = (PCM_CREATECMDEVICE_PARAM)(cmPrivateInputData); |
| //Create Cm Device |
| cmRet = CreateCmDeviceFromVA(vaDriverCtx, device, cmDeviceParam->devCreateOption); |
| if ( cmRet == CM_SUCCESS) |
| { |
| CM_CHK_NULL_RETURN_WITH_MSG(device, VA_STATUS_ERROR_INVALID_CONTEXT, "Null device."); |
| deviceRT = static_cast<CmDeviceRT*>(device); |
| deviceRT->RegisterCallBack(cmDeviceParam->callbackReleaseVaSurf); |
| cmDeviceParam->driverStoreEnabled = deviceRT->GetDriverStoreFlag(); |
| } |
| //Fill the output message |
| cmDeviceParam->deviceHandle = device; |
| cmDeviceParam->returnValue = cmRet; |
| cmDeviceParam->version = CM_VERSION; |
| break; |
| |
| case CM_FN_DESTROYCMDEVICE: |
| PCM_DESTROYCMDEVICE_PARAM cmDevDestroyParam; |
| cmDevDestroyParam = (PCM_DESTROYCMDEVICE_PARAM)(cmPrivateInputData); |
| device = (CmDevice *)(cmDevDestroyParam->deviceHandle); |
| cmRet = DestroyCmDeviceFromVA(vaDriverCtx,device); |
| //Fill the output message |
| cmDevDestroyParam->deviceHandle = nullptr; |
| cmDevDestroyParam->returnValue = cmRet; |
| break; |
| |
| case CM_FN_CMDEVICE_CREATESURFACE2D: |
| PCM_CREATESURFACE2D_PARAM cmCreate2DParam; |
| MOS_RESOURCE mosResource ; |
| MOS_ZeroMemory(&mosResource, sizeof(MOS_RESOURCE)); |
| cmCreate2DParam = (PCM_CREATESURFACE2D_PARAM)(cmPrivateInputData); |
| if ( cmCreate2DParam->isLibvaCreated ) |
| { |
| //LibVA-created Surface2D |
| cmRet = CmFillMosResource(cmCreate2DParam->vaSurfaceID, |
| vaDriverCtx, |
| &mosResource); |
| |
| if( cmRet != CM_SUCCESS) |
| { |
| CM_ASSERTMESSAGE("Error: Failed to fill MOS resource."); |
| cmCreate2DParam->returnValue = cmRet; |
| return cmRet; |
| } |
| |
| cmRet = deviceRT->CreateSurface2D(&mosResource, cmCreate2DParam->isCmCreated, pCmSurface2d); |
| if( cmRet != CM_SUCCESS) |
| { |
| CM_ASSERTMESSAGE("Error: Failed to create surface 2D from MOS resource."); |
| cmCreate2DParam->returnValue = cmRet; |
| return cmRet; |
| } |
| |
| CmSurface2DRT *surface2dRT = static_cast<CmSurface2DRT *>(pCmSurface2d); |
| surface2dRT->SetVaSurfaceID(cmCreate2DParam->vaSurfaceID, cmCreate2DParam->vaDisplay); |
| } |
| else |
| { |
| // CM Created Surface2D |
| cmRet = device->CreateSurface2D( |
| cmCreate2DParam->width, |
| cmCreate2DParam->height, |
| CmOSFmtToMosFmt(cmCreate2DParam->format), |
| pCmSurface2d); |
| } |
| //Create Surface Index |
| if( cmRet == CM_SUCCESS) |
| { |
| cmCreate2DParam->cmSurface2DHandle = pCmSurface2d; |
| } |
| |
| //Fill output message |
| cmCreate2DParam->returnValue = cmRet; |
| break; |
| |
| case CM_FN_CMDEVICE_CREATESURFACE2DUP: |
| CM_CREATESURFACE2DUP_PARAM *cmCreate2DUpParam; |
| CMRT_UMD::CmSurface2DUP *cmSurface2dup; |
| cmCreate2DUpParam = static_cast<CM_CREATESURFACE2DUP_PARAM*>(inputData); |
| cmRet = device->CreateSurface2DUP(cmCreate2DUpParam->width, |
| cmCreate2DUpParam->height, |
| CmOSFmtToMosFmt(cmCreate2DUpParam->format), |
| cmCreate2DUpParam->sysMem, cmSurface2dup); |
| if( cmRet == CM_SUCCESS) |
| { |
| cmCreate2DUpParam->surface2DUPHandle = static_cast<CmSurface2DUP*>(cmSurface2dup); |
| } |
| cmCreate2DUpParam->returnValue = cmRet; |
| break; |
| |
| case CM_FN_CMDEVICE_CREATESURFACE3D: |
| CM_CREATE_SURFACE3D_PARAM *createSurf3dParam; |
| createSurf3dParam = static_cast<CM_CREATE_SURFACE3D_PARAM*>(inputData); |
| CMRT_UMD::CmSurface3D *cmSurface3d; |
| cmRet = device->CreateSurface3D(createSurf3dParam->width, createSurf3dParam->height, |
| createSurf3dParam->depth, |
| CmOSFmtToMosFmt(createSurf3dParam->format), |
| cmSurface3d); |
| if(cmRet == CM_SUCCESS) |
| { |
| createSurf3dParam->surface3DHandle = static_cast<CmSurface3D*>(cmSurface3d); |
| } |
| createSurf3dParam->returnValue = cmRet; |
| break; |
| |
| case CM_FN_CMDEVICE_GETSURFACE2DINFO: |
| CM_GETSURFACE2DINFO_PARAM *cmGet2DinfoParam; |
| cmGet2DinfoParam = static_cast<CM_GETSURFACE2DINFO_PARAM*>(inputData); |
| uint32_t pitch, physicalsize; |
| cmRet = device->GetSurface2DInfo(cmGet2DinfoParam->width, cmGet2DinfoParam->height, |
| CmOSFmtToMosFmt(cmGet2DinfoParam->format), |
| pitch, physicalsize); |
| cmGet2DinfoParam->pitch = pitch; |
| cmGet2DinfoParam->physicalSize = physicalsize; |
| cmGet2DinfoParam->returnValue = cmRet; |
| break; |
| |
| default: |
| hr = CmThinExecuteInternal(device, cmFunctionID, cmPrivateInputData, cmPrivateInputDataSize); |
| if (hr == CM_INVALID_PRIVATE_DATA) |
| { |
| CmWrapperEx *wrapperEx = CmExtensionCreator<CmWrapperEx>::CreateClass(); |
| if (wrapperEx != nullptr) |
| { |
| wrapperEx->Initialize((void *)vaDriverCtx); |
| hr = wrapperEx->Execute(device,cmFunctionID, cmPrivateInputData, cmPrivateInputDataSize); |
| MOS_Delete(wrapperEx); |
| } |
| else |
| { |
| hr = CM_OUT_OF_HOST_MEMORY; |
| } |
| } |
| } |
| |
| return hr; |
| } |