/* * Interrupt emulation * * Copyright 2002 Jukka Heinonen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include "wine/winbase16.h" #include "kernel16_private.h" #include "dosexe.h" #include "winternl.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(int); WINE_DECLARE_DEBUG_CHANNEL(relay); #define BCD_TO_BIN(x) ((x&15) + (x>>4)*10) #define BIN_TO_BCD(x) ((x%10) + ((x/10)<<4)) static void WINAPI DOSVM_Int11Handler(CONTEXT*); static void WINAPI DOSVM_Int12Handler(CONTEXT*); static void WINAPI DOSVM_Int17Handler(CONTEXT*); static void WINAPI DOSVM_Int19Handler(CONTEXT*); static void WINAPI DOSVM_Int1aHandler(CONTEXT*); static void WINAPI DOSVM_Int20Handler(CONTEXT*); static void WINAPI DOSVM_Int2aHandler(CONTEXT*); static void WINAPI DOSVM_Int41Handler(CONTEXT*); static void WINAPI DOSVM_Int4bHandler(CONTEXT*); static void WINAPI DOSVM_Int5cHandler(CONTEXT*); static void WINAPI DOSVM_DefaultHandler(CONTEXT*); static FARPROC16 DOSVM_Vectors16[256]; static const INTPROC DOSVM_VectorsBuiltin[] = { /* 00 */ 0, 0, 0, 0, /* 04 */ 0, 0, 0, 0, /* 08 */ 0, 0, 0, 0, /* 0C */ 0, 0, 0, 0, /* 10 */ 0, DOSVM_Int11Handler, DOSVM_Int12Handler, 0, /* 14 */ 0, DOSVM_Int15Handler, 0, DOSVM_Int17Handler, /* 18 */ 0, DOSVM_Int19Handler, DOSVM_Int1aHandler, 0, /* 1C */ 0, 0, 0, 0, /* 20 */ DOSVM_Int20Handler, DOSVM_Int21Handler, 0, 0, /* 24 */ 0, DOSVM_Int25Handler, DOSVM_Int26Handler, 0, /* 28 */ 0, 0, DOSVM_Int2aHandler, 0, /* 2C */ 0, 0, 0, DOSVM_Int2fHandler, /* 30 */ 0, DOSVM_Int31Handler, 0, 0, /* 34 */ DOSVM_Int34Handler, DOSVM_Int35Handler, DOSVM_Int36Handler, DOSVM_Int37Handler, /* 38 */ DOSVM_Int38Handler, DOSVM_Int39Handler, DOSVM_Int3aHandler, DOSVM_Int3bHandler, /* 3C */ DOSVM_Int3cHandler, DOSVM_Int3dHandler, DOSVM_Int3eHandler, 0, /* 40 */ 0, DOSVM_Int41Handler, 0, 0, /* 44 */ 0, 0, 0, 0, /* 48 */ 0, 0, 0, DOSVM_Int4bHandler, /* 4C */ 0, 0, 0, 0, /* 50 */ 0, 0, 0, 0, /* 54 */ 0, 0, 0, 0, /* 58 */ 0, 0, 0, 0, /* 5C */ DOSVM_Int5cHandler }; /* * Sizes of real mode and protected mode interrupt stubs. */ #define DOSVM_STUB_PM16 5 /********************************************************************** * DOSVM_DefaultHandler * * Default interrupt handler. This will be used to emulate all * interrupts that don't have their own interrupt handler. */ static void WINAPI DOSVM_DefaultHandler( CONTEXT *context ) { } /********************************************************************** * DOSVM_Exit */ void DOSVM_Exit( WORD retval ) { DWORD count; ReleaseThunkLock( &count ); ExitThread( retval ); } /********************************************************************** * DOSVM_GetBuiltinHandler * * Return Wine interrupt handler procedure for a given interrupt. */ static INTPROC DOSVM_GetBuiltinHandler( BYTE intnum ) { if (intnum < ARRAY_SIZE(DOSVM_VectorsBuiltin)) { INTPROC proc = DOSVM_VectorsBuiltin[intnum]; if (proc) return proc; } WARN("int%x not implemented, returning dummy handler\n", intnum ); return DOSVM_DefaultHandler; } /********************************************************************** * DOSVM_IntProcRelay * * Simple DOSRELAY that interprets its argument as INTPROC and calls it. */ static void DOSVM_IntProcRelay( CONTEXT *context, LPVOID data ) { INTPROC proc = (INTPROC)data; proc(context); } /********************************************************************** * DOSVM_PushFlags * * This routine is used to make default int25 and int26 handlers leave the * original eflags into stack. In order to do this, stack is manipulated * so that it actually contains two copies of eflags, one of which is * popped during return from interrupt handler. */ static void DOSVM_PushFlags( CONTEXT *context, BOOL islong, BOOL isstub ) { if (islong) { DWORD *stack = CTX_SEG_OFF_TO_LIN(context, context->SegSs, context->Esp); context->Esp += -4; /* One item will be added to stack. */ if (isstub) { DWORD ip = stack[0]; DWORD cs = stack[1]; stack += 2; /* Pop ip and cs. */ *(--stack) = context->EFlags; *(--stack) = cs; *(--stack) = ip; } else *(--stack) = context->EFlags; } else { WORD *stack = CTX_SEG_OFF_TO_LIN(context, context->SegSs, context->Esp); ADD_LOWORD( context->Esp, -2 ); /* One item will be added to stack. */ if (isstub) { WORD ip = stack[0]; WORD cs = stack[1]; stack += 2; /* Pop ip and cs. */ *(--stack) = LOWORD(context->EFlags); *(--stack) = cs; *(--stack) = ip; } else *(--stack) = LOWORD(context->EFlags); } } /********************************************************************** * DOSVM_HardwareInterruptPM * * Emulate call to interrupt handler in 16-bit or 32-bit protected mode. * * Pushes interrupt frame to stack and changes instruction * pointer to interrupt handler. */ static void DOSVM_HardwareInterruptPM( CONTEXT *context, BYTE intnum ) { FARPROC16 addr = DOSVM_GetPMHandler16( intnum ); if (SELECTOROF(addr) == int16_sel) { TRACE( "builtin interrupt %02x has been invoked " "(through vector %02x)\n", OFFSETOF(addr)/DOSVM_STUB_PM16, intnum ); if (intnum == 0x25 || intnum == 0x26) DOSVM_PushFlags( context, FALSE, FALSE ); DOSVM_BuildCallFrame( context, DOSVM_IntProcRelay, DOSVM_GetBuiltinHandler( OFFSETOF(addr)/DOSVM_STUB_PM16 ) ); } else { TRACE( "invoking hooked interrupt %02x at %04x:%04x\n", intnum, SELECTOROF(addr), OFFSETOF(addr) ); /* Push the flags and return address on the stack */ PUSH_WORD16( context, LOWORD(context->EFlags) ); PUSH_WORD16( context, context->SegCs ); PUSH_WORD16( context, LOWORD(context->Eip) ); /* Jump to the interrupt handler */ context->SegCs = HIWORD(addr); context->Eip = LOWORD(addr); } } /********************************************************************** * DOSVM_EmulateInterruptPM * * Emulate software interrupt in 16-bit or 32-bit protected mode. * Called from signal handler when intXX opcode is executed. * * Pushes interrupt frame to stack and changes instruction * pointer to interrupt handler. */ BOOL DOSVM_EmulateInterruptPM( CONTEXT *context, BYTE intnum ) { TRACE_(relay)("\1Call DOS int 0x%02x ret=%04x:%08x\n" " eax=%08x ebx=%08x ecx=%08x edx=%08x\n" " esi=%08x edi=%08x ebp=%08x esp=%08x\n" " ds=%04x es=%04x fs=%04x gs=%04x ss=%04x flags=%08x\n", intnum, context->SegCs, context->Eip, context->Eax, context->Ebx, context->Ecx, context->Edx, context->Esi, context->Edi, context->Ebp, context->Esp, context->SegDs, context->SegEs, context->SegFs, context->SegGs, context->SegSs, context->EFlags ); DOSMEM_InitDosMemory(); if (context->SegCs == relay_code_sel) { /* * This must not be called using DOSVM_BuildCallFrame. */ DOSVM_RelayHandler( context ); } else if (context->SegCs == int16_sel) { /* Restore original flags stored into the stack by the caller. */ WORD *stack = CTX_SEG_OFF_TO_LIN(context, context->SegSs, context->Esp); context->EFlags = (DWORD)MAKELONG( stack[2], HIWORD(context->EFlags) ); if (intnum != context->Eip / DOSVM_STUB_PM16) WARN( "interrupt stub has been modified " "(interrupt is %02x, interrupt stub is %02x)\n", intnum, context->Eip/DOSVM_STUB_PM16 ); TRACE( "builtin interrupt %02x has been branched to\n", intnum ); if (intnum == 0x25 || intnum == 0x26) DOSVM_PushFlags( context, FALSE, TRUE ); DOSVM_BuildCallFrame( context, DOSVM_IntProcRelay, DOSVM_GetBuiltinHandler(intnum) ); } else if (ldt_is_system(context->SegCs)) { INTPROC proc; if (intnum >= ARRAY_SIZE(DOSVM_VectorsBuiltin)) return FALSE; if (!(proc = DOSVM_VectorsBuiltin[intnum])) return FALSE; proc( context ); } else { DOSVM_HardwareInterruptPM( context, intnum ); } return TRUE; } /********************************************************************** * DOSVM_GetPMHandler16 * * Return the protected mode interrupt vector for a given interrupt. */ FARPROC16 DOSVM_GetPMHandler16( BYTE intnum ) { TDB *pTask; FARPROC16 proc = 0; pTask = GlobalLock16(GetCurrentTask()); if (pTask) { switch( intnum ) { case 0x00: proc = pTask->int0; break; case 0x02: proc = pTask->int2; break; case 0x04: proc = pTask->int4; break; case 0x06: proc = pTask->int6; break; case 0x07: proc = pTask->int7; break; case 0x3e: proc = pTask->int3e; break; case 0x75: proc = pTask->int75; break; } if( proc ) return proc; } if (!DOSVM_Vectors16[intnum]) { proc = (FARPROC16)MAKESEGPTR( int16_sel, DOSVM_STUB_PM16 * intnum ); DOSVM_Vectors16[intnum] = proc; } return DOSVM_Vectors16[intnum]; } /********************************************************************** * DOSVM_SetPMHandler16 * * Set the protected mode interrupt handler for a given interrupt. */ void DOSVM_SetPMHandler16( BYTE intnum, FARPROC16 handler ) { TDB *pTask; TRACE("Set protected mode interrupt vector %02x <- %04x:%04x\n", intnum, HIWORD(handler), LOWORD(handler) ); pTask = GlobalLock16(GetCurrentTask()); if (!pTask) return; switch( intnum ) { case 0x00: pTask->int0 = handler; break; case 0x02: pTask->int2 = handler; break; case 0x04: pTask->int4 = handler; break; case 0x06: pTask->int6 = handler; break; case 0x07: pTask->int7 = handler; break; case 0x3e: pTask->int3e = handler; break; case 0x75: pTask->int75 = handler; break; default: DOSVM_Vectors16[intnum] = handler; break; } } /********************************************************************** * DOSVM_CallBuiltinHandler * * Execute Wine interrupt handler procedure. */ static void DOSVM_CallBuiltinHandler( CONTEXT *context, BYTE intnum ) { /* * FIXME: Make all builtin interrupt calls go via this routine. * FIXME: Check for PM->RM interrupt reflection. * FIXME: Check for RM->PM interrupt reflection. */ INTPROC proc = DOSVM_GetBuiltinHandler( intnum ); proc( context ); } /********************************************************************** * __wine_call_int_handler (KERNEL.@) */ void __wine_call_int_handler( CONTEXT *context, BYTE intnum ) { DOSMEM_InitDosMemory(); DOSVM_CallBuiltinHandler( context, intnum ); } /********************************************************************** * DOSVM_Int11Handler * * Handler for int 11h (get equipment list). * * * Borrowed from Ralph Brown's interrupt lists: * * bits 15-14: number of parallel devices * bit 13: [Conv] Internal modem * bit 12: reserved * bits 11- 9: number of serial devices * bit 8: reserved * bits 7- 6: number of diskette drives minus one * bits 5- 4: Initial video mode: * 00b = EGA,VGA,PGA * 01b = 40 x 25 color * 10b = 80 x 25 color * 11b = 80 x 25 mono * bit 3: reserved * bit 2: [PS] =1 if pointing device * [non-PS] reserved * bit 1: =1 if math co-processor * bit 0: =1 if diskette available for boot * * * Currently the only of these bits correctly set are: * * bits 15-14 } Added by William Owen Smith, * bits 11-9 } wos@dcs.warwick.ac.uk * bits 7-6 * bit 2 (always set) ( bit 2 = 4 ) * bit 1 } Robert 'Admiral' Coeyman * All *nix systems either have a math processor or * emulate one. */ static void WINAPI DOSVM_Int11Handler( CONTEXT *context ) { int diskdrives = 0; int parallelports = 0; int serialports = 0; int x; if (GetDriveTypeA("A:\\") == DRIVE_REMOVABLE) diskdrives++; if (GetDriveTypeA("B:\\") == DRIVE_REMOVABLE) diskdrives++; if (diskdrives) diskdrives--; for (x=0; x < 9; x++) { HANDLE handle; char file[10]; /* serial port name */ sprintf( file, "\\\\.\\COM%d", x+1 ); handle = CreateFileA( file, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle != INVALID_HANDLE_VALUE) { CloseHandle( handle ); serialports++; } sprintf( file, "\\\\.\\LPT%d", x+1 ); handle = CreateFileA( file, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); if (handle != INVALID_HANDLE_VALUE) { CloseHandle( handle ); parallelports++; } } if (serialports > 7) /* 3 bits -- maximum value = 7 */ serialports = 7; if (parallelports > 3) /* 2 bits -- maximum value = 3 */ parallelports = 3; SET_AX( context, (diskdrives << 6) | (serialports << 9) | (parallelports << 14) | 0x06 ); } /********************************************************************** * DOSVM_Int12Handler * * Handler for int 12h (get memory size). */ static void WINAPI DOSVM_Int12Handler( CONTEXT *context ) { SET_AX( context, 640 ); } /********************************************************************** * DOSVM_Int17Handler * * Handler for int 17h (printer - output character). */ static void WINAPI DOSVM_Int17Handler( CONTEXT *context ) { switch( AH_reg(context) ) { case 0x00:/* Send character*/ FIXME("Send character not supported yet\n"); SET_AH( context, 0x00 );/*Timeout*/ break; case 0x01: /* PRINTER - INITIALIZE */ FIXME("Initialize Printer - Not Supported\n"); SET_AH( context, 0x30 ); /* selected | out of paper */ break; case 0x02: /* PRINTER - GET STATUS */ FIXME("Get Printer Status - Not Supported\n"); break; default: SET_AH( context, 0 ); /* time out */ INT_BARF( context, 0x17 ); } } /********************************************************************** * DOSVM_Int19Handler * * Handler for int 19h (Reboot). */ static void WINAPI DOSVM_Int19Handler( CONTEXT *context ) { TRACE( "Attempted Reboot\n" ); ExitProcess(0); } /********************************************************************** * DOSVM_Int1aHandler * * Handler for int 1ah. */ static void WINAPI DOSVM_Int1aHandler( CONTEXT *context ) { switch(AH_reg(context)) { case 0x00: /* GET SYSTEM TIME */ { BIOSDATA *data = DOSVM_BiosData(); SET_CX( context, HIWORD(data->Ticks) ); SET_DX( context, LOWORD(data->Ticks) ); SET_AL( context, 0 ); /* FIXME: midnight flag is unsupported */ TRACE( "GET SYSTEM TIME - ticks=%d\n", data->Ticks ); } break; case 0x01: /* SET SYSTEM TIME */ FIXME( "SET SYSTEM TIME - not allowed\n" ); break; case 0x02: /* GET REAL-TIME CLOCK TIME */ TRACE( "GET REAL-TIME CLOCK TIME\n" ); { SYSTEMTIME systime; GetLocalTime( &systime ); SET_CH( context, BIN_TO_BCD(systime.wHour) ); SET_CL( context, BIN_TO_BCD(systime.wMinute) ); SET_DH( context, BIN_TO_BCD(systime.wSecond) ); SET_DL( context, 0 ); /* FIXME: assume no daylight saving */ RESET_CFLAG(context); } break; case 0x03: /* SET REAL-TIME CLOCK TIME */ FIXME( "SET REAL-TIME CLOCK TIME - not allowed\n" ); break; case 0x04: /* GET REAL-TIME CLOCK DATE */ TRACE( "GET REAL-TIME CLOCK DATE\n" ); { SYSTEMTIME systime; GetLocalTime( &systime ); SET_CH( context, BIN_TO_BCD(systime.wYear / 100) ); SET_CL( context, BIN_TO_BCD(systime.wYear % 100) ); SET_DH( context, BIN_TO_BCD(systime.wMonth) ); SET_DL( context, BIN_TO_BCD(systime.wDay) ); RESET_CFLAG(context); } break; case 0x05: /* SET REAL-TIME CLOCK DATE */ FIXME( "SET REAL-TIME CLOCK DATE - not allowed\n" ); break; case 0x06: /* SET ALARM */ FIXME( "SET ALARM - unimplemented\n" ); break; case 0x07: /* CANCEL ALARM */ FIXME( "CANCEL ALARM - unimplemented\n" ); break; case 0x08: /* SET RTC ACTIVATED POWER ON MODE */ case 0x09: /* READ RTC ALARM TIME AND STATUS */ case 0x0a: /* READ SYSTEM-TIMER DAY COUNTER */ case 0x0b: /* SET SYSTEM-TIMER DAY COUNTER */ case 0x0c: /* SET RTC DATE/TIME ACTIVATED POWER-ON MODE */ case 0x0d: /* RESET RTC DATE/TIME ACTIVATED POWER-ON MODE */ case 0x0e: /* GET RTC DATE/TIME ALARM AND STATUS */ case 0x0f: /* INITIALIZE REAL-TIME CLOCK */ INT_BARF( context, 0x1a ); break; case 0xb0: if (CX_reg(context) == 0x4d52 && DX_reg(context) == 0x4349 && AL_reg(context) == 0x01) { /* * Microsoft Real-Time Compression Interface (MRCI). * Ignoring this call indicates MRCI is not supported. */ TRACE( "Microsoft Real-Time Compression Interface - not supported\n" ); } else { INT_BARF(context, 0x1a); } break; default: INT_BARF( context, 0x1a ); } } /********************************************************************** * DOSVM_Int20Handler * * Handler for int 20h. */ static void WINAPI DOSVM_Int20Handler( CONTEXT *context ) { DOSVM_Exit( 0 ); } /********************************************************************** * DOSVM_Int2aHandler * * Handler for int 2ah (network). */ static void WINAPI DOSVM_Int2aHandler( CONTEXT *context ) { switch(AH_reg(context)) { case 0x00: /* NETWORK INSTALLATION CHECK */ break; default: INT_BARF( context, 0x2a ); } } /*********************************************************************** * DOSVM_Int41Handler */ static void WINAPI DOSVM_Int41Handler( CONTEXT *context ) { switch ( AX_reg(context) ) { case 0x4f: case 0x50: case 0x150: case 0x51: case 0x52: case 0x152: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: /* Notifies the debugger of a lot of stuff. We simply ignore it for now, but some of the info might actually be useful ... */ break; default: INT_BARF( context, 0x41 ); break; } } /*********************************************************************** * DOSVM_Int4bHandler * */ static void WINAPI DOSVM_Int4bHandler( CONTEXT *context ) { switch(AH_reg(context)) { case 0x81: /* Virtual DMA Spec (IBM SCSI interface) */ if(AL_reg(context) != 0x02) /* if not install check */ { SET_CFLAG(context); SET_AL( context, 0x0f ); /* function is not implemented */ } break; default: INT_BARF(context, 0x4b); } } /*********************************************************************** * DOSVM_Int5cHandler * * Called from NetBIOSCall16. */ static void WINAPI DOSVM_Int5cHandler( CONTEXT *context ) { BYTE* ptr; ptr = MapSL( MAKESEGPTR(context->SegEs,BX_reg(context)) ); FIXME("(%p): command code %02x (ignored)\n",context, *ptr); *(ptr+0x01) = 0xFB; /* NetBIOS emulator not found */ SET_AL( context, 0xFB ); }