/* * Int67 (EMS) 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 <assert.h> #include "wine/winbase16.h" #include "dosexe.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(int); /* * EMS page size == 16 kilobytes. */ #define EMS_PAGE_SIZE (16*1024) /* * Linear address of EMS page. */ #define EMS_PAGE_ADDRESS(base,page) (((char*)base) + EMS_PAGE_SIZE * page) /* * Maximum number of pages that can be allocated using EMS. */ #define EMS_MAX_PAGES 1024 /* * Maximum number of EMS handles (allocated blocks). */ #define EMS_MAX_HANDLES 256 /* * Global EMM Import Record. * Applications can get address of this record * and directly access allocated memory if they use * IOCTL interface. * * FIXME: Missing lots of fields, packing is not correct. */ static struct { struct { UCHAR hindex; /* handle number */ BYTE flags; /* bit 0: normal handle rather than system handle */ char name[8]; /* handle name */ WORD pages; /* allocated pages */ void *address; /* physical address*/ } handle[EMS_MAX_HANDLES]; /* Wine specific fields... */ int used_pages; /* Number of allocated pages. */ void *frame_address; /* Address of 64k EMS page frame */ WORD frame_selector; /* Segment of 64k EMS page frame */ struct { UCHAR hindex; /* handle number */ WORD logical_page; /* logical page */ } mapping[4]; struct { UCHAR hindex; /* handle number */ WORD logical_page; /* logical page */ } mapping_save_area[EMS_MAX_HANDLES][4]; } *EMS_record; /********************************************************************** * EMS_init * * Allocates and initialized page frame and EMS global import record. */ static void EMS_init(void) { /* * Start of 64k EMS frame. */ ULONG base = 0xc0000; if(EMS_record) return; EMS_record = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*EMS_record)); EMS_record->frame_address = (void *)base; EMS_record->frame_selector = base >> 4; } /********************************************************************** * EMS_alloc * * Get handle and allocate memory. */ static void EMS_alloc( CONTEXT *context ) { int hindex = 1; /* handle zero is reserved for system */ while(hindex < EMS_MAX_HANDLES && EMS_record->handle[hindex].address) hindex++; if(hindex == EMS_MAX_HANDLES) { SET_AH( context, 0x85 ); /* status: no more handles available */ } else { int pages = BX_reg(context); void *buffer = HeapAlloc( GetProcessHeap(), 0, pages * EMS_PAGE_SIZE ); if(!buffer) { SET_AH( context, 0x88 ); /* status: insufficient pages available */ } else { EMS_record->handle[hindex].address = buffer; EMS_record->handle[hindex].pages = pages; EMS_record->used_pages += pages; SET_DX( context, hindex ); /* handle to allocated memory*/ SET_AH( context, 0 ); /* status: ok */ } } } /********************************************************************** * EMS_access_name * * Get/set handle name. */ static void EMS_access_name( CONTEXT *context ) { char *ptr; int hindex = DX_reg(context); if(hindex < 0 || hindex >= EMS_MAX_HANDLES) { SET_AH( context, 0x83 ); /* invalid handle */ return; } switch (AL_reg(context)) { case 0x00: /* get name */ ptr = PTR_REAL_TO_LIN(context->SegEs, DI_reg(context)); memcpy(ptr, EMS_record->handle[hindex].name, 8); SET_AH( context, 0 ); break; case 0x01: /* set name */ ptr = PTR_REAL_TO_LIN(context->SegDs, SI_reg(context)); memcpy(EMS_record->handle[hindex].name, ptr, 8); SET_AH( context, 0 ); break; default: INT_BARF(context,0x67); break; } } /********************************************************************** * EMS_map * * Map logical page into physical page. */ static BYTE EMS_map( WORD physical_page, WORD new_hindex, WORD new_logical_page ) { int old_hindex; int old_logical_page; void *physical_address; if(physical_page > 3) return 0x8b; /* status: invalid physical page */ old_hindex = EMS_record->mapping[physical_page].hindex; old_logical_page = EMS_record->mapping[physical_page].logical_page; physical_address = EMS_PAGE_ADDRESS(EMS_record->frame_address, physical_page); /* unmap old page */ if(old_hindex) { void *ptr = EMS_PAGE_ADDRESS(EMS_record->handle[old_hindex].address, old_logical_page); memcpy(ptr, physical_address, EMS_PAGE_SIZE); } /* map new page */ if(new_hindex && new_logical_page != 0xffff) { void *ptr = EMS_PAGE_ADDRESS(EMS_record->handle[new_hindex].address, new_logical_page); if(new_hindex >= EMS_MAX_HANDLES || !EMS_record->handle[new_hindex].address) return 0x83; /* status: invalid handle */ if(new_logical_page >= EMS_record->handle[new_hindex].pages) return 0x8a; /* status: invalid logical page */ memcpy(physical_address, ptr, EMS_PAGE_SIZE); EMS_record->mapping[physical_page].hindex = new_hindex; EMS_record->mapping[physical_page].logical_page = new_logical_page; } else { EMS_record->mapping[physical_page].hindex = 0; EMS_record->mapping[physical_page].logical_page = 0; } return 0; /* status: ok */ } /********************************************************************** * EMS_map_multiple * * Map multiple logical pages into physical pages. */ static void EMS_map_multiple( CONTEXT *context ) { WORD *ptr = PTR_REAL_TO_LIN(context->SegDs, SI_reg(context)); BYTE status = 0; int i; for(i=0; i<CX_reg(context) && !status; i++, ptr += 2) switch(AL_reg(context)) { case 0x00: status = EMS_map( ptr[1], DX_reg(context), ptr[0] ); break; case 0x01: status = EMS_map( (ptr[1] - EMS_record->frame_selector) >> 10, DX_reg(context), ptr[0] ); break; default: status = 0x8f; /* status: undefined subfunction */ } SET_AH( context, status ); } /********************************************************************** * EMS_free * * Free memory and release handle. */ static void EMS_free( CONTEXT *context ) { int hindex = DX_reg(context); int i; if(hindex < 0 || hindex >= EMS_MAX_HANDLES) { SET_AH( context, 0x83 ); /* status: invalid handle */ return; } if(!EMS_record->handle[hindex].address) { SET_AH( context, 0 ); /* status: ok */ return; } EMS_record->used_pages -= EMS_record->handle[hindex].pages; /* unmap pages */ for(i=0; i<4; i++) if(EMS_record->mapping[i].hindex == hindex) EMS_record->mapping[i].hindex = 0; /* free block */ HeapFree( GetProcessHeap(), 0, EMS_record->handle[hindex].address ); EMS_record->handle[hindex].address = 0; SET_AH( context, 0 ); /* status: ok */ } /********************************************************************** * EMS_save_context * * Save physical page mappings into handle specific save area. */ static void EMS_save_context( CONTEXT *context ) { WORD h = DX_reg(context); int i; for(i=0; i<4; i++) { EMS_record->mapping_save_area[h][i].hindex = EMS_record->mapping[i].hindex; EMS_record->mapping_save_area[h][i].logical_page = EMS_record->mapping[i].logical_page; } SET_AX( context, 0 ); /* status: ok */ } /********************************************************************** * EMS_restore_context * * Restore physical page mappings from handle specific save area. */ static void EMS_restore_context( CONTEXT *context ) { WORD handle = DX_reg(context); int i; for(i=0; i<4; i++) { int hindex = EMS_record->mapping_save_area[handle][i].hindex; int logical_page = EMS_record->mapping_save_area[handle][i].logical_page; if(EMS_map( i, hindex, logical_page )) { SET_AX( context, 0x8e ); /* status: restore of mapping context failed */ return; } } SET_AX( context, 0 ); /* status: ok */ } /********************************************************************** * DOSVM_Int67Handler * * Handler for interrupt 67h EMS routines. */ void WINAPI DOSVM_Int67Handler( CONTEXT *context ) { switch (AH_reg(context)) { case 0x40: /* EMS - GET MANAGER STATUS */ SET_AH( context, 0 ); /* status: ok */ break; case 0x41: /* EMS - GET PAGE FRAME SEGMENT */ EMS_init(); SET_BX( context, EMS_record->frame_selector ); /* segment of page frame */ SET_AH( context, 0 ); /* status: ok */ break; case 0x42: /* EMS - GET NUMBER OF PAGES */ EMS_init(); /* unallocated 16k pages */ SET_BX( context, EMS_MAX_PAGES - EMS_record->used_pages ); /* total number of 16k pages */ SET_DX( context, EMS_MAX_PAGES ); /* status: ok */ SET_AH( context, 0 ); break; case 0x43: /* EMS - GET HANDLE AND ALLOCATE MEMORY */ EMS_init(); EMS_alloc(context); break; case 0x44: /* EMS - MAP MEMORY */ EMS_init(); SET_AH( context, EMS_map( AL_reg(context), DX_reg(context), BX_reg(context) ) ); break; case 0x45: /* EMS - RELEASE HANDLE AND MEMORY */ EMS_init(); EMS_free(context); break; case 0x46: /* EMS - GET EMM VERSION */ SET_AL( context, 0x40 ); /* version 4.0 */ SET_AH( context, 0 ); /* status: ok */ break; case 0x47: /* EMS - SAVE MAPPING CONTEXT */ EMS_init(); EMS_save_context(context); break; case 0x48: /* EMS - RESTORE MAPPING CONTEXT */ EMS_init(); EMS_restore_context(context); break; case 0x49: /* EMS - reserved - GET I/O PORT ADDRESSES */ case 0x4a: /* EMS - reserved - GET TRANSLATION ARRAY */ INT_BARF(context,0x67); break; case 0x4b: /* EMS - GET NUMBER OF EMM HANDLES */ SET_BX( context, EMS_MAX_HANDLES ); /* EMM handles */ SET_AH( context, 0 ); /* status: ok */ break; case 0x4c: /* EMS - GET PAGES OWNED BY HANDLE */ case 0x4d: /* EMS - GET PAGES FOR ALL HANDLES */ case 0x4e: /* EMS - GET OR SET PAGE MAP */ case 0x4f: /* EMS 4.0 - GET/SET PARTIAL PAGE MAP */ INT_BARF(context,0x67); break; case 0x50: /* EMS 4.0 - MAP/UNMAP MULTIPLE HANDLE PAGES */ EMS_init(); EMS_map_multiple(context); break; case 0x51: /* EMS 4.0 - REALLOCATE PAGES */ case 0x52: /* EMS 4.0 - GET/SET HANDLE ATTRIBUTES */ INT_BARF(context,0x67); break; case 0x53: /* EMS 4.0 - GET/SET HANDLE NAME */ EMS_init(); EMS_access_name(context); break; case 0x54: /* EMS 4.0 - GET HANDLE DIRECTORY */ case 0x55: /* EMS 4.0 - ALTER PAGE MAP AND JUMP */ case 0x56: /* EMS 4.0 - ALTER PAGE MAP AND CALL */ case 0x57: /* EMS 4.0 - MOVE/EXCHANGE MEMORY REGION */ case 0x58: /* EMS 4.0 - GET MAPPABLE PHYSICAL ADDRESS ARRAY */ INT_BARF(context,0x67); break; case 0x59: /* EMS 4.0 - GET EXPANDED MEMORY HARDWARE INFORMATION */ if(AL_reg(context) == 0x01) { EMS_init(); /* unallocated raw pages */ SET_BX( context, EMS_MAX_PAGES - EMS_record->used_pages ); /* total number raw pages */ SET_DX( context, EMS_MAX_PAGES ); /* status: ok */ SET_AH( context, 0 ); } else INT_BARF(context,0x67); break; case 0x5a: /* EMS 4.0 - ALLOCATE STANDARD/RAW PAGES */ case 0x5b: /* EMS 4.0 - ALTERNATE MAP REGISTER SET */ case 0x5c: /* EMS 4.0 - PREPARE EXPANDED MEMORY HARDWARE FOR WARM BOOT */ case 0x5d: /* EMS 4.0 - ENABLE/DISABLE OS FUNCTION SET FUNCTIONS */ INT_BARF(context,0x67); break; case 0xde: /* Virtual Control Program Interface (VCPI) */ if(AL_reg(context) == 0x00) { /* * VCPI INSTALLATION CHECK * (AH_reg() != 0) means VCPI is not present */ TRACE("- VCPI installation check\n"); return; } else INT_BARF(context,0x67); break; default: INT_BARF(context,0x67); } } /********************************************************************** * EMS_Ioctl_Handler * * Handler for interrupt 21h IOCTL routine for device "EMMXXXX0". */ void EMS_Ioctl_Handler( CONTEXT *context ) { assert(AH_reg(context) == 0x44); switch (AL_reg(context)) { case 0x00: /* IOCTL - GET DEVICE INFORMATION */ RESET_CFLAG(context); /* operation was successful */ SET_DX( context, 0x4080 ); /* bit 14 (support ioctl read) and * bit 7 (is_device) */ break; case 0x02: /* EMS - GET MEMORY MANAGER INFORMATION */ /* * This is what is called "Windows Global EMM Import Specification". * Undocumented of course! Supports three requests: * GET API ENTRY POINT * GET EMM IMPORT STRUCTURE ADDRESS * GET MEMORY MANAGER VERSION */ INT_BARF(context,0x21); break; case 0x07: /* IOCTL - GET OUTPUT STATUS */ RESET_CFLAG(context); /* operation was successful */ SET_AL( context, 0xff ); /* device is ready */ break; default: INT_BARF(context,0x21); break; } }