|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | 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 = 0; | 
|  |  | 
|  | /********************************************************************** | 
|  | *          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( CONTEXT86 *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( CONTEXT86 *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( CONTEXT86 *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( CONTEXT86 *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( CONTEXT86 *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( CONTEXT86 *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 (WINEDOS16.203) | 
|  | * | 
|  | * Handler for interrupt 67h EMS routines. | 
|  | */ | 
|  | void WINAPI DOSVM_Int67Handler( CONTEXT86 *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 WINAPI EMS_Ioctl_Handler( CONTEXT86 *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; | 
|  | } | 
|  | } |