1 2 /* 3 * Copyright (C) Texas Instruments - http://www.ti.com/ 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 * 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 /* ============================================================================= 22 * Texas Instruments OMAP(TM) Platform Software 23 * (c) Copyright Texas Instruments, Incorporated. All Rights Reserved. 24 * 25 * Use of this software is controlled by the terms and conditions found 26 * in the license agreement under which this software has been supplied. 27 * =========================================================================== */ 28 /** 29 * @file OMX_Video_Dec_Thread.c 30 * 31 * This file implements OMX Component for MPEG-4 decoder that 32 * is fully compliant with the Khronos OMX specification 1.0. 33 * 34 * @path $(CSLPATH)\src 35 * 36 * @rev 0.1 37 */ 38 /* -------------------------------------------------------------------------- */ 39 /* ============================================================================= 40 *! 41 *! Revision History 42 *! ============================================================================= 43 *! 44 *! 02-Feb-2006 mf: Revisions appear in reverse chronological order; 45 *! that is, newest first. The date format is dd-Mon-yyyy. 46 * =========================================================================== */ 47 48 /* ------compilation control switches ----------------------------------------*/ 49 /******************************************************************************* 50 * INCLUDE FILES 51 *******************************************************************************/ 52 /* ----- system and platform files -------------------------------------------*/ 53 #ifdef UNDER_CE 54 #include <windows.h> 55 #include <oaf_osal.h> 56 #include <omx_core.h> 57 #else 58 #define _XOPEN_SOURCE 600 59 #include <wchar.h> 60 #include <sys/select.h> 61 #include <signal.h> 62 #include <unistd.h> 63 #include <sys/time.h> 64 #include <sys/types.h> 65 #include <sys/ioctl.h> 66 #include <fcntl.h> 67 #include <errno.h> 68 69 #endif 70 71 #include <dbapi.h> 72 #include <string.h> 73 #include <stdlib.h> 74 #include <stdio.h> 75 76 #include "OMX_VideoDecoder.h" 77 #include "OMX_VideoDec_Utils.h" 78 #include "OMX_VideoDec_Thread.h" 79 #include "OMX_VideoDec_DSP.h" 80 81 82 extern OMX_ERRORTYPE VIDDEC_HandleCommand (OMX_HANDLETYPE pHandle, OMX_U32 nParam1); 83 extern OMX_ERRORTYPE VIDDEC_DisablePort(VIDDEC_COMPONENT_PRIVATE *pComponentPrivate, OMX_U32 nParam1); 84 extern OMX_ERRORTYPE VIDDEC_EnablePort(VIDDEC_COMPONENT_PRIVATE *pComponentPrivate, OMX_U32 nParam1); 85 extern OMX_ERRORTYPE VIDDEC_HandleDataBuf_FromApp( VIDDEC_COMPONENT_PRIVATE *pComponentPrivate); 86 extern OMX_ERRORTYPE VIDDEC_HandleDataBuf_FromDsp( VIDDEC_COMPONENT_PRIVATE *pComponentPrivate ); 87 extern OMX_ERRORTYPE VIDDEC_HandleFreeDataBuf( VIDDEC_COMPONENT_PRIVATE *pComponentPrivate ); 88 extern OMX_ERRORTYPE VIDDEC_HandleFreeOutputBufferFromApp(VIDDEC_COMPONENT_PRIVATE *pComponentPrivate) ; 89 extern OMX_ERRORTYPE VIDDEC_Start_ComponentThread(OMX_HANDLETYPE pHandle); 90 extern OMX_ERRORTYPE VIDDEC_Stop_ComponentThread(OMX_HANDLETYPE pComponent); 91 extern OMX_ERRORTYPE VIDDEC_HandleCommandMarkBuffer(VIDDEC_COMPONENT_PRIVATE *pComponentPrivate, OMX_U32 nParam1, OMX_PTR pCmdData); 92 extern OMX_ERRORTYPE VIDDEC_HandleCommandFlush(VIDDEC_COMPONENT_PRIVATE *pComponentPrivate, OMX_U32 nParam1, OMX_BOOL bPass); 93 extern OMX_ERRORTYPE VIDDEC_Handle_InvalidState (VIDDEC_COMPONENT_PRIVATE* pComponentPrivate); 94 95 /*----------------------------------------------------------------------------*/ 96 /** 97 * OMX_VidDec_Thread() is the open max thread. This method is in charge of 98 * listening to the buffers coming from DSP, application or commands through the pipes 99 **/ 100 /*----------------------------------------------------------------------------*/ 101 102 /** Default timeout used to come out of blocking calls*/ 103 #define VIDD_TIMEOUT (1000) /* milliseconds */ 104 105 void* OMX_VidDec_Thread (void* pThreadData) 106 { 107 int status; 108 #ifdef UNDER_CE 109 struct timeval tv; 110 #else 111 sigset_t set; 112 struct timespec tv; 113 #endif 114 int fdmax; 115 fd_set rfds; 116 OMX_ERRORTYPE eError = OMX_ErrorNone; 117 OMX_COMMANDTYPE eCmd; 118 OMX_U32 nParam1; 119 OMX_PTR pCmdData; 120 VIDDEC_COMPONENT_PRIVATE* pComponentPrivate; 121 LCML_DSP_INTERFACE *pLcmlHandle; 122 OMX_U32 aParam[4]; 123 #ifndef UNDER_CE 124 OMX_BOOL bFlag = OMX_FALSE; 125 #endif 126 /*OMX_U32 timeout = 0;*/ 127 128 pComponentPrivate = (VIDDEC_COMPONENT_PRIVATE*)pThreadData; 129 130 #ifdef __PERF_INSTRUMENTATION__ 131 pComponentPrivate->pPERFcomp = PERF_Create(PERF_FOURS("VD T"), 132 PERF_ModuleComponent | PERF_ModuleVideoDecode); 133 #endif 134 135 pLcmlHandle = (LCML_DSP_INTERFACE *)pComponentPrivate->pLCML; 136 137 /**Looking for highest number of file descriptor for pipes in order to put in select loop */ 138 fdmax = pComponentPrivate->cmdPipe[VIDDEC_PIPE_READ]; 139 140 if (pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 141 fdmax = pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ]; 142 } 143 144 if (pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 145 fdmax = pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ]; 146 } 147 148 if (pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 149 fdmax = pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ]; 150 } 151 152 if (pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 153 fdmax = pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ]; 154 } 155 156 while (1) { 157 FD_ZERO (&rfds); 158 FD_SET(pComponentPrivate->cmdPipe[VIDDEC_PIPE_READ], &rfds); 159 FD_SET(pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ], &rfds); 160 FD_SET(pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ], &rfds); 161 FD_SET(pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ], &rfds); 162 FD_SET(pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ], &rfds); 163 164 #ifdef UNDER_CE 165 tv.tv_sec = 0; 166 tv.tv_usec = VIDD_TIMEOUT * 30; 167 #else 168 tv.tv_sec = 0; 169 tv.tv_nsec = 30000; 170 #endif 171 172 173 #ifdef UNDER_CE 174 status = select (fdmax+1, &rfds, NULL, NULL, NULL); 175 #else 176 sigemptyset (&set); 177 sigaddset (&set, SIGALRM); 178 status = pselect (fdmax+1, &rfds, NULL, NULL, NULL, &set); 179 sigdelset (&set, SIGALRM); 180 #endif 181 182 if (0 == status) { 183 ; 184 } 185 else if (-1 == status) { 186 OMX_TRACE4(pComponentPrivate->dbg, "Error in Select\n"); 187 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 188 pComponentPrivate->pHandle->pApplicationPrivate, 189 OMX_EventError, 190 OMX_ErrorInsufficientResources, 191 OMX_TI_ErrorSevere, 192 "Error from Component Thread in select"); 193 eError = OMX_ErrorInsufficientResources; 194 break; 195 } 196 else { 197 if (FD_ISSET(pComponentPrivate->cmdPipe[VIDDEC_PIPE_READ], &rfds)) { 198 #ifndef UNDER_CE 199 if(!bFlag) { 200 201 bFlag = OMX_TRUE; 202 #endif 203 read(pComponentPrivate->cmdPipe[VIDDEC_PIPE_READ], &eCmd, sizeof(eCmd)); 204 read(pComponentPrivate->cmdDataPipe[VIDDEC_PIPE_READ], &nParam1, sizeof(nParam1)); 205 206 #ifdef __PERF_INSTRUMENTATION__ 207 PERF_ReceivedCommand(pComponentPrivate->pPERFcomp, 208 eCmd, nParam1, PERF_ModuleLLMM); 209 #endif 210 if (eCmd == OMX_CommandStateSet) { 211 if ((OMX_S32)nParam1 < -2) { 212 OMX_ERROR2(pComponentPrivate->dbg, "Incorrect variable value used\n"); 213 } 214 if ((OMX_S32)nParam1 != -1 && (OMX_S32)nParam1 != -2) { 215 eError = VIDDEC_HandleCommand(pComponentPrivate, nParam1); 216 if (eError != OMX_ErrorNone) { 217 /*pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 218 pComponentPrivate->pHandle->pApplicationPrivate, 219 OMX_EventError, 220 eError, 221 0, 222 "Error in HadleCommand function");*/ 223 } 224 } 225 else if ((OMX_S32)nParam1 == -1) { 226 break; 227 } 228 else if ((OMX_S32)nParam1 == -2) { 229 OMX_VidDec_Return(pComponentPrivate); 230 VIDDEC_Handle_InvalidState( pComponentPrivate); 231 break; 232 } 233 } 234 else if (eCmd == OMX_CommandPortDisable) { 235 eError = VIDDEC_DisablePort(pComponentPrivate, nParam1); 236 if (eError != OMX_ErrorNone) { 237 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 238 pComponentPrivate->pHandle->pApplicationPrivate, 239 OMX_EventError, 240 eError, 241 OMX_TI_ErrorSevere, 242 "Error in DisablePort function"); 243 } 244 } 245 else if (eCmd == OMX_CommandPortEnable) { 246 eError = VIDDEC_EnablePort(pComponentPrivate, nParam1); 247 if (eError != OMX_ErrorNone) { 248 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 249 pComponentPrivate->pHandle->pApplicationPrivate, 250 OMX_EventError, 251 eError, 252 OMX_TI_ErrorSevere, 253 "Error in EnablePort function"); 254 } 255 } else if (eCmd == OMX_CommandFlush) { 256 VIDDEC_HandleCommandFlush (pComponentPrivate, nParam1, OMX_TRUE); 257 } 258 else if (eCmd == OMX_CommandMarkBuffer) { 259 read(pComponentPrivate->cmdDataPipe[VIDDEC_PIPE_READ], &pCmdData, sizeof(pCmdData)); 260 pComponentPrivate->arrCmdMarkBufIndex[pComponentPrivate->nInCmdMarkBufIndex].hMarkTargetComponent = ((OMX_MARKTYPE*)(pCmdData))->hMarkTargetComponent; 261 pComponentPrivate->arrCmdMarkBufIndex[pComponentPrivate->nInCmdMarkBufIndex].pMarkData = ((OMX_MARKTYPE*)(pCmdData))->pMarkData; 262 pComponentPrivate->nInCmdMarkBufIndex++; 263 pComponentPrivate->nInCmdMarkBufIndex %= VIDDEC_MAX_QUEUE_SIZE; 264 265 } 266 #ifndef UNDER_CE 267 bFlag = OMX_FALSE; 268 #endif 269 270 #ifndef UNDER_CE 271 } 272 #endif 273 } 274 if(pComponentPrivate->bPipeCleaned){ 275 pComponentPrivate->bPipeCleaned =0; 276 } 277 else{ 278 if (FD_ISSET(pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 279 if(pComponentPrivate->bDynamicConfigurationInProgress){ 280 VIDDEC_WAIT_CODE(); 281 continue; 282 } 283 eError = VIDDEC_HandleDataBuf_FromDsp(pComponentPrivate); 284 if (eError != OMX_ErrorNone) { 285 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while handling filled DSP output buffer\n"); 286 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 287 pComponentPrivate->pHandle->pApplicationPrivate, 288 OMX_EventError, 289 eError, 290 OMX_TI_ErrorSevere, 291 "Error from Component Thread while processing dsp Responses"); 292 } 293 } 294 if ((FD_ISSET(pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ], &rfds))){ 295 OMX_PRSTATE2(pComponentPrivate->dbg, "eExecuteToIdle 0x%x\n",pComponentPrivate->eExecuteToIdle); 296 /* When doing a reconfiguration, don't send input buffers to SN & wait for SN to be ready*/ 297 if(pComponentPrivate->bDynamicConfigurationInProgress == OMX_TRUE || 298 pComponentPrivate->eLCMLState != VidDec_LCML_State_Start){ 299 VIDDEC_WAIT_CODE(); 300 continue; 301 } 302 else{ 303 eError = VIDDEC_HandleDataBuf_FromApp (pComponentPrivate); 304 if (eError != OMX_ErrorNone) { 305 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while handling filled input buffer\n"); 306 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 307 pComponentPrivate->pHandle->pApplicationPrivate, 308 OMX_EventError, 309 eError, 310 OMX_TI_ErrorSevere, 311 "Error from Component Thread while processing input buffer"); 312 } 313 } 314 } 315 if (FD_ISSET(pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 316 if(pComponentPrivate->bDynamicConfigurationInProgress){ 317 VIDDEC_WAIT_CODE(); 318 continue; 319 } 320 eError = VIDDEC_HandleFreeDataBuf(pComponentPrivate); 321 if (eError != OMX_ErrorNone) { 322 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while processing free input buffers\n"); 323 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 324 pComponentPrivate->pHandle->pApplicationPrivate, 325 OMX_EventError, 326 eError, 327 OMX_TI_ErrorSevere, 328 "Error from Component Thread while processing free input buffer"); 329 } 330 } 331 if (FD_ISSET(pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 332 if(pComponentPrivate->bDynamicConfigurationInProgress){ 333 VIDDEC_WAIT_CODE(); 334 continue; 335 } 336 OMX_PRSTATE2(pComponentPrivate->dbg, "eExecuteToIdle 0x%x\n",pComponentPrivate->eExecuteToIdle); 337 eError = VIDDEC_HandleFreeOutputBufferFromApp(pComponentPrivate); 338 if (eError != OMX_ErrorNone) { 339 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while processing free output buffer\n"); 340 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 341 pComponentPrivate->pHandle->pApplicationPrivate, 342 OMX_EventError, 343 eError, 344 OMX_TI_ErrorSevere, 345 "Error from Component Thread while processing free output buffer"); 346 } 347 } 348 } 349 } 350 } 351 352 #ifdef __PERF_INSTRUMENTATION__ 353 PERF_Done(pComponentPrivate->pPERFcomp); 354 #endif 355 356 return (void *)eError; 357 } 358 359 void* OMX_VidDec_Return (void* pThreadData) 360 { 361 int status = 0; 362 struct timeval tv1; 363 #ifdef UNDER_CE 364 struct timeval tv; 365 #else 366 sigset_t set; 367 struct timespec tv; 368 #endif 369 int fdmax = 0; 370 OMX_U32 iLock = 0; 371 fd_set rfds; 372 OMX_ERRORTYPE eError = OMX_ErrorNone; 373 VIDDEC_COMPONENT_PRIVATE* pComponentPrivate = NULL; 374 375 pComponentPrivate = (VIDDEC_COMPONENT_PRIVATE*)pThreadData; 376 gettimeofday(&tv1, NULL); 377 /**Looking for highest number of file descriptor for pipes in order to put in select loop */ 378 fdmax = pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ]; 379 380 if (pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 381 fdmax = pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ]; 382 } 383 384 if (pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 385 fdmax = pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ]; 386 } 387 388 if (pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ] > fdmax) { 389 fdmax = pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ]; 390 } 391 while ((pComponentPrivate->nCountInputBFromApp != 0 && 392 (pComponentPrivate->eLCMLState == VidDec_LCML_State_Start && pComponentPrivate->bDynamicConfigurationInProgress == OMX_FALSE)) || 393 pComponentPrivate->nCountOutputBFromApp != 0 || 394 pComponentPrivate->nCountInputBFromDsp != 0 || pComponentPrivate->nCountOutputBFromDsp != 0) { 395 FD_ZERO (&rfds); 396 FD_SET(pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ], &rfds); 397 FD_SET(pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ], &rfds); 398 FD_SET(pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ], &rfds); 399 FD_SET(pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ], &rfds); 400 401 #ifdef UNDER_CE 402 tv.tv_sec = 0; 403 tv.tv_usec = VIDD_TIMEOUT * 30; 404 #else 405 tv.tv_sec = 0; 406 tv.tv_nsec = 10000; 407 #endif 408 409 410 #ifdef UNDER_CE 411 status = select (fdmax+1, &rfds, NULL, NULL, &tv); 412 #else 413 sigemptyset (&set); 414 sigaddset (&set, SIGALRM); 415 status = pselect (fdmax+1, &rfds, NULL, NULL, &tv, &set); 416 sigdelset (&set, SIGALRM); 417 #endif 418 if (0 == status) { 419 iLock++; 420 if (iLock > 2){ 421 pComponentPrivate->bPipeCleaned = 1; 422 break; 423 } 424 } 425 else if (-1 == status) { 426 OMX_PRINT2(pComponentPrivate->dbg, "Error in Select\n"); 427 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 428 pComponentPrivate->pHandle->pApplicationPrivate, 429 OMX_EventError, 430 OMX_ErrorInsufficientResources, 431 OMX_TI_ErrorSevere, 432 "Error from Component Thread in select"); 433 eError = OMX_ErrorInsufficientResources; 434 break; 435 } 436 else { 437 if (FD_ISSET(pComponentPrivate->filled_outBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 438 eError = VIDDEC_HandleDataBuf_FromDsp(pComponentPrivate); 439 if (eError != OMX_ErrorNone) { 440 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while handling filled DSP output buffer\n"); 441 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 442 pComponentPrivate->pHandle->pApplicationPrivate, 443 OMX_EventError, 444 eError, 445 OMX_TI_ErrorSevere, 446 "Error from Component Thread while processing dsp Responses"); 447 } 448 } 449 if ((FD_ISSET(pComponentPrivate->filled_inpBuf_Q[VIDDEC_PIPE_READ], &rfds))){ 450 OMX_PRSTATE2(pComponentPrivate->dbg, "eExecuteToIdle 0x%x\n",pComponentPrivate->eExecuteToIdle); 451 if(!(pComponentPrivate->bDynamicConfigurationInProgress == OMX_TRUE && pComponentPrivate->bInPortSettingsChanged == OMX_FALSE)){ 452 eError = VIDDEC_HandleDataBuf_FromApp (pComponentPrivate); 453 if (eError != OMX_ErrorNone) { 454 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while handling filled input buffer\n"); 455 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 456 pComponentPrivate->pHandle->pApplicationPrivate, 457 OMX_EventError, 458 eError, 459 OMX_TI_ErrorSevere, 460 "Error from Component Thread while processing input buffer"); 461 } 462 } 463 } 464 if (FD_ISSET(pComponentPrivate->free_inpBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 465 eError = VIDDEC_HandleFreeDataBuf(pComponentPrivate); 466 if (eError != OMX_ErrorNone) { 467 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while processing free input buffers\n"); 468 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 469 pComponentPrivate->pHandle->pApplicationPrivate, 470 OMX_EventError, 471 eError, 472 OMX_TI_ErrorSevere, 473 "Error from Component Thread while processing free input buffer"); 474 } 475 } 476 if (FD_ISSET(pComponentPrivate->free_outBuf_Q[VIDDEC_PIPE_READ], &rfds)) { 477 OMX_PRSTATE2(pComponentPrivate->dbg, "eExecuteToIdle 0x%x\n",pComponentPrivate->eExecuteToIdle); 478 eError = VIDDEC_HandleFreeOutputBufferFromApp(pComponentPrivate); 479 if (eError != OMX_ErrorNone) { 480 OMX_PRBUFFER4(pComponentPrivate->dbg, "Error while processing free output buffer\n"); 481 pComponentPrivate->cbInfo.EventHandler(pComponentPrivate->pHandle, 482 pComponentPrivate->pHandle->pApplicationPrivate, 483 OMX_EventError, 484 eError, 485 OMX_TI_ErrorSevere, 486 "Error from Component Thread while processing free output buffer"); 487 } 488 } 489 } 490 } 491 492 pComponentPrivate->bPipeCleaned = OMX_TRUE; 493 return (void *)eError; 494 } 495 496