mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-22 11:17:19 +00:00
1105 lines
28 KiB
C
1105 lines
28 KiB
C
/*-
|
|
* Copyright (c) 1997 Helmut Wirth <hfwirth@ping.at>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice immediately at the beginning of the file, witout modification,
|
|
* this list of conditions, and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* XMS memory management
|
|
*
|
|
* To emulate DOS extended memory (EMM) we use an implementation of
|
|
* HIMEM.SYS driver capabilities, according to the XMS 3.0 Spec.
|
|
* The actual memory allocated via XMS calls from DOS is allocated
|
|
* via malloc by the emulator. Maximum memory allocation is configurable.
|
|
*
|
|
* Credits to:
|
|
* The original author of this file, some parts are still here
|
|
* Linux dosemu programmers. I looked into their code.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
#include "doscmd.h"
|
|
#include "xms.h"
|
|
|
|
|
|
/* Extended memory handle management */
|
|
|
|
static XMS_handle xms_hand[NUM_HANDLES];
|
|
int num_free_handle = NUM_HANDLES;
|
|
|
|
/* This is planned to be selectable from .doscmdrc */
|
|
u_long xms_maxsize = DEFAULT_EMM_SIZE;
|
|
static u_long xms_free_mem;
|
|
static u_long xms_used_mem;
|
|
static u_char vec_grabbed;
|
|
|
|
/* Address entry for zero size allocated handles */
|
|
#define XMS_NULL_ALLOC 0xffffffff
|
|
|
|
/* High memory area (HMA) management */
|
|
static u_char HMA_allocated = 0;
|
|
static short HMA_a20 = -1;
|
|
static int HMA_fd_off, HMA_fd_on;
|
|
|
|
/* high memory mapfiles */
|
|
static char memfile[] = "/tmp/doscmd.XXXXXX";
|
|
|
|
/* Upper memory block (UMB) management */
|
|
UMB_block *UMB_freelist = NULL;
|
|
UMB_block *UMB_alloclist = NULL;
|
|
|
|
/* Calls to emulator */
|
|
u_long xms_vector;
|
|
static u_char xms_trampoline[] = {
|
|
0xeb, /* JMP 5 */
|
|
0x03,
|
|
0x90, /* NOP */
|
|
0x90, /* NOP */
|
|
0x90, /* NOP */
|
|
0xf4, /* HLT */
|
|
0xcb, /* RETF */
|
|
};
|
|
|
|
/* Local prototypes */
|
|
static void add_block(UMB_block **listp, UMB_block *blk);
|
|
static UMB_block *create_block(u_long addr, u_long size);
|
|
static void disable_a20(void);
|
|
static void enable_a20(void);
|
|
static int get_free_handle(void);
|
|
static void merge_blocks(void);
|
|
static void xms_entry(regcontext_t *REGS);
|
|
|
|
/* Init the entire module */
|
|
void
|
|
xms_init(void)
|
|
{
|
|
/* Initialize handle table: xms_handle.addr == 0 means free */
|
|
bzero((void *)xms_hand, sizeof(XMS_handle) * NUM_HANDLES);
|
|
xms_free_mem = xms_maxsize;
|
|
xms_used_mem = 0;
|
|
vec_grabbed = 0;
|
|
HMA_allocated = 0;
|
|
/* Initialize UMB blocks */
|
|
/* 0xD0000 to 0xDffff */
|
|
add_block(&UMB_freelist, create_block(0xd0000, 64*1024));
|
|
/*XXX check for EMS emulation, when it is done! */
|
|
/* 0xE0000 to 0xEffff */
|
|
|
|
/* This is used as window for EMS, will be configurable ! */
|
|
/* add_block(&UMB_freelist, create_block(0xe0000, 64*1024)); */
|
|
|
|
merge_blocks();
|
|
|
|
xms_vector = insert_generic_trampoline(
|
|
sizeof(xms_trampoline), xms_trampoline);
|
|
register_callback(xms_vector + 5, xms_entry, "xms");
|
|
}
|
|
|
|
/*
|
|
* UMB management routines: UMBs normally lie between 0xd0000 and
|
|
* 0xefff0 in VM86 memory space and are accessible for all DOS applications.
|
|
* We could enable more space, but with the emulator we do not
|
|
* need many drivers, so I think 2 * 64kB will suffice. If EMS emulation
|
|
* exists, a 64kB segment (0xe0000 - 0xeffff for example) is needed for
|
|
* the EMS mapping, in this case we have 64kB UMB space. This is more than
|
|
* many PCs are able to do.
|
|
* This emulation does only the management for the memory, the memory
|
|
* is present and read/write/executable for VM86 applications.
|
|
*/
|
|
|
|
/* Add a block to a list, maintain ascending start address order */
|
|
|
|
static void
|
|
add_block(UMB_block **listp, UMB_block *blk)
|
|
{
|
|
UMB_block *bp, *obp;
|
|
|
|
/* No blocks there, attach the new block to the head */
|
|
if (*listp == NULL) {
|
|
*listp = blk;
|
|
blk->next = NULL;
|
|
} else {
|
|
/* Insert at the start */
|
|
bp = obp = *listp;
|
|
if (blk->addr < bp->addr) {
|
|
blk->next = *listp;
|
|
*listp = blk;
|
|
return;
|
|
}
|
|
/* Not at the start, insert into the list */
|
|
for (; bp != NULL; bp = bp->next) {
|
|
if (blk->addr > bp->addr) {
|
|
obp = bp;
|
|
continue;
|
|
} else {
|
|
obp->next = blk;
|
|
blk->next = bp;
|
|
return;
|
|
}
|
|
}
|
|
/* Append to the end of the list */
|
|
obp->next = blk;
|
|
blk->next = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Find a block with address addr in the alloc list */
|
|
static UMB_block *
|
|
find_allocated_block(u_long addr)
|
|
{
|
|
UMB_block *bp;
|
|
|
|
if (UMB_alloclist == NULL)
|
|
return NULL;
|
|
|
|
for (bp = UMB_alloclist; bp != NULL; bp = bp->next)
|
|
if (bp->addr == addr)
|
|
return bp;
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove a block blk from a list, the block must exist on the list */
|
|
static void
|
|
remove_block(UMB_block **listp, UMB_block *blk)
|
|
{
|
|
UMB_block *bp;
|
|
|
|
if (*listp == NULL)
|
|
goto faterr;
|
|
|
|
if (*listp == blk) {
|
|
*listp = (*listp)->next;
|
|
return;
|
|
}
|
|
bp = *listp;
|
|
do {
|
|
if (bp->next == blk) {
|
|
bp->next = bp->next->next;
|
|
return;
|
|
}
|
|
bp = bp->next;
|
|
} while(bp != NULL);
|
|
faterr:
|
|
fatal("XMS: UMB remove_block did not find block\n");
|
|
}
|
|
|
|
/* Try to merge neighbouring blocks in the free list */
|
|
static void
|
|
merge_blocks()
|
|
{
|
|
UMB_block *bp;
|
|
u_long endaddr;
|
|
|
|
if (UMB_freelist == NULL)
|
|
return;
|
|
bp = UMB_freelist;
|
|
do {
|
|
endaddr = bp->addr + bp->size;
|
|
if (bp->next != NULL && endaddr == bp->next->addr) {
|
|
/* Merge the current and the next block */
|
|
UMB_block *mergebp = bp->next;
|
|
bp->size += mergebp->size;
|
|
bp->next = mergebp->next;
|
|
free(mergebp);
|
|
} else {
|
|
/* Goto next block */
|
|
bp = bp->next;
|
|
}
|
|
} while (bp != NULL);
|
|
}
|
|
|
|
/* Try to find a free block of size exactly siz */
|
|
static UMB_block *
|
|
find_exact_block(u_long siz)
|
|
{
|
|
UMB_block *bp;
|
|
|
|
if (UMB_freelist == NULL)
|
|
return NULL;
|
|
|
|
for (bp = UMB_freelist; bp != NULL; bp = bp->next)
|
|
if (bp->size == siz)
|
|
return bp;
|
|
return NULL;
|
|
}
|
|
|
|
/* Try to find a block with a size bigger than requested. If there is
|
|
* no such block, return the block with the biggest size. If there is
|
|
* no free block at all, return NULL
|
|
*/
|
|
static UMB_block *
|
|
find_block(u_long siz)
|
|
{
|
|
UMB_block *bp;
|
|
UMB_block *biggest = NULL;
|
|
|
|
if (UMB_freelist == NULL)
|
|
return NULL;
|
|
|
|
for (bp = UMB_freelist; bp != NULL; bp = bp->next) {
|
|
if (bp->size > siz)
|
|
return bp;
|
|
if (biggest == NULL) {
|
|
biggest = bp;
|
|
continue;
|
|
}
|
|
if (biggest->size < bp->size)
|
|
biggest = bp;
|
|
}
|
|
return biggest;
|
|
}
|
|
|
|
/* Create a block structure, memory is allocated. The structure lives
|
|
* until the block is merged into another block, then it is freed */
|
|
static UMB_block *
|
|
create_block(u_long addr, u_long size)
|
|
{
|
|
UMB_block *blk;
|
|
|
|
if ((blk = malloc(sizeof(UMB_block))) == NULL)
|
|
fatal ("XMS: Cannot allocate UMB structure\n");
|
|
blk->addr = addr;
|
|
blk->size = size;
|
|
blk->next = NULL;
|
|
return blk;
|
|
}
|
|
|
|
|
|
/*
|
|
* initHMA(): The first 64kB of memory are mapped from 1MB (0x100000)
|
|
* again to emulate the address wrap around of the 808x. The HMA area
|
|
* is a cheap trick, usable only with 80386 and higher. The 80[345..]86
|
|
* does not have this address wrap around. If more than 1MB is installed
|
|
* the processor can address more than 1MB: load 0xFFFF to the segment
|
|
* register and using the full offset of 0xffff the resulting highest
|
|
* address is (0xffff << 4) + 0xffff = 0x10ffef. Nearly 64kB are accessible
|
|
* from real or VM86 mode. The mmap calls emulate the address wrap by
|
|
* mapping the lowest 64kB the the first 64kB after the 1MB limit.
|
|
* In hardware this is achieved by setting and resetting the a20 bit,
|
|
* an ugly compatibility hack: The hardware simlpy clamps the address 20
|
|
* line of the processor to low and hence the wrap around is forced.
|
|
* This is switchable via the BIOS or via HIMEM.SYS and therefore the
|
|
* first 64kB over 1MB can be enabled or disabled at will by software.
|
|
* DOS uses this trick to load itself high, if the memory is present.
|
|
* We emulate this behaviour by mapping and unmapping the HMA area.
|
|
* (Linux has implemented this feature using shared memory (SHM) calls.)
|
|
*
|
|
* This routine is called from doscmd.c at startup. A20 is disabled after
|
|
* startup.
|
|
*/
|
|
|
|
void initHMA()
|
|
{
|
|
int mfd;
|
|
|
|
/*
|
|
* We need two files, one for the wrap around mapping and one
|
|
* for the HMA contents
|
|
*/
|
|
|
|
mfd = mkstemp(memfile);
|
|
|
|
if (mfd < 0) {
|
|
fprintf(stderr, "memfile: %s\n", strerror(errno));
|
|
fprintf(stderr, "High memory will not be mapped\n");
|
|
|
|
/* We need this for XMS services. If it fails, turn HMA off */
|
|
HMA_a20 = -1;
|
|
return;
|
|
}
|
|
unlink(memfile);
|
|
HMA_fd_off = squirrel_fd(mfd);
|
|
|
|
lseek(HMA_fd_off, 64 * 1024 - 1, 0);
|
|
write(HMA_fd_off, "", 1);
|
|
|
|
mfd = mkstemp(memfile);
|
|
|
|
if (mfd < 0) {
|
|
fprintf(stderr, "memfile: %s\n", strerror(errno));
|
|
fprintf(stderr, "High memory will not be mapped\n");
|
|
|
|
/* We need this for XMS services. If it fails, turn HMA off */
|
|
HMA_a20 = -1;
|
|
return;
|
|
}
|
|
unlink(memfile);
|
|
HMA_fd_on = squirrel_fd(mfd);
|
|
|
|
lseek(HMA_fd_on, 64 * 1024 - 1, 0);
|
|
write(HMA_fd_on, "", 1);
|
|
|
|
if (mmap((caddr_t)0x000000, 0x100000,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_ANON | MAP_FIXED | MAP_SHARED,
|
|
-1, 0) == MAP_FAILED) {
|
|
perror("Error mapping HMA, HMA disabled: ");
|
|
HMA_a20 = -1;
|
|
close(HMA_fd_off);
|
|
close(HMA_fd_on);
|
|
return;
|
|
}
|
|
if (mmap((caddr_t)0x000000, 64 * 1024,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_FILE | MAP_FIXED | MAP_SHARED,
|
|
HMA_fd_off, 0) == MAP_FAILED) {
|
|
perror("Error mapping HMA, HMA disabled: ");
|
|
HMA_a20 = -1;
|
|
close(HMA_fd_off);
|
|
close(HMA_fd_on);
|
|
return;
|
|
}
|
|
if (mmap((caddr_t)0x100000, 64 * 1024,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_FILE | MAP_FIXED | MAP_SHARED,
|
|
HMA_fd_off, 0) == MAP_FAILED) {
|
|
perror("Error mapping HMA, HMA disabled: ");
|
|
HMA_a20 = -1;
|
|
close(HMA_fd_off);
|
|
close(HMA_fd_on);
|
|
return;
|
|
}
|
|
HMA_a20 = 0;
|
|
}
|
|
|
|
|
|
/* Enable the a20 "address line" by unmapping the 64kB over 1MB */
|
|
static void enable_a20()
|
|
{
|
|
if (HMA_a20 < 0)
|
|
return;
|
|
|
|
/* Unmap the wrap around portion (fd = HMA_fd_off) */
|
|
/* XXX Not sure about this: Should I unmap first, then map new or
|
|
* does it suffice to map new "over' the existing mapping ? Both
|
|
* works (define to #if 0 next line and some lines below to try.
|
|
*/
|
|
#if 1
|
|
if (munmap((caddr_t)0x100000, 64 * 1024) < 0) {
|
|
fatal("HMA unmapping error: %s\nCannot recover\n", strerror(errno));
|
|
}
|
|
#endif
|
|
/* Map memory for the HMA with fd = HMA_fd_on */
|
|
if (mmap((caddr_t)0x100000, 64 * 1024,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_FILE | MAP_FIXED | MAP_SHARED,
|
|
HMA_fd_on, 0) == MAP_FAILED) {
|
|
fatal("HMA mapping error: %s\nCannot recover\n", strerror(errno));
|
|
}
|
|
}
|
|
|
|
/* Disable the a20 "address line" by mapping the 64kB over 1MB again */
|
|
static void disable_a20()
|
|
{
|
|
if (HMA_a20 < 0)
|
|
return;
|
|
#if 1
|
|
/* Unmap the HMA (fd = HMA_fd_on) */
|
|
if (munmap((caddr_t)0x100000, 64 * 1024) < 0) {
|
|
fatal("HMA unmapping error: %s\nCannot recover\n", strerror(errno));
|
|
}
|
|
#endif
|
|
/* Remap the wrap around area */
|
|
if (mmap((caddr_t)0x100000, 64 * 1024,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_FILE | MAP_FIXED | MAP_SHARED,
|
|
HMA_fd_off, 0) == MAP_FAILED) {
|
|
fatal("HMA mapping error: %s\nCannot recover\n", strerror(errno));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* This handles calls to int15 function 88: BIOS extended memory
|
|
* request. XMS spec says: "In order to maintain compatibility with existing
|
|
* device drivers, DOS XMS drivers must not hook INT 15h until the first
|
|
* non-Version Number call to the control function is made."
|
|
*/
|
|
|
|
void
|
|
get_raw_extmemory_info(regcontext_t *REGS)
|
|
{
|
|
if (vec_grabbed)
|
|
R_AX = 0x0;
|
|
else
|
|
R_AX = xms_maxsize / 1024;
|
|
return;
|
|
}
|
|
|
|
|
|
/* Handle management routine: Find next free handle */
|
|
|
|
static int
|
|
get_free_handle()
|
|
{
|
|
int i;
|
|
/* Linear search, there are only a few handles */
|
|
for (i = 0; i < NUM_HANDLES; i++) {
|
|
if (xms_hand[i].addr == 0)
|
|
return i + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Installation check */
|
|
int
|
|
int2f_43(regcontext_t *REGS)
|
|
{
|
|
|
|
switch (R_AL) {
|
|
case 0x00: /* installation check */
|
|
R_AL = 0x80;
|
|
break;
|
|
|
|
case 0x10: /* get handler address */
|
|
PUTVEC(R_ES, R_BX, xms_vector);
|
|
break;
|
|
|
|
default:
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* Main call entry point for the XMS handler from DOS */
|
|
static void
|
|
xms_entry(regcontext_t *REGS)
|
|
{
|
|
|
|
if (R_AH != 0)
|
|
vec_grabbed = 1;
|
|
|
|
/* If the HMA feature is disabled these calls are "not managed" */
|
|
if (HMA_a20 < 0) {
|
|
if (R_AH == XMS_ALLOCATE_HIGH_MEMORY || R_AH == XMS_FREE_HIGH_MEMORY ||
|
|
R_AH == XMS_GLOBAL_ENABLE_A20 || R_AH == XMS_GLOBAL_DISABLE_A20 ||
|
|
R_AH == XMS_LOCAL_ENABLE_A20 || R_AH == XMS_LOCAL_DISABLE_A20 ||
|
|
R_AH == XMS_QUERY_A20) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_HMA_NOT_MANAGED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (R_AH) {
|
|
case XMS_GET_VERSION:
|
|
debug(D_XMS, "XMS: Get Version\n");
|
|
R_AX = XMS_VERSION; /* 3.0 */
|
|
R_BX = XMS_REVISION; /* internal revision 0 */
|
|
R_DX = (HMA_a20 < 0) ? 0x0000 : 0x0001;
|
|
break;
|
|
|
|
/*
|
|
* XXX Not exact! Spec says compare size to a HMAMIN parameter and
|
|
* refuse HMA, if space is too small. With MSDOS 5.0 and higher DOS
|
|
* itself uses the HMA (DOS=HIGH), so I think we can safely ignore
|
|
* that.
|
|
*/
|
|
case XMS_ALLOCATE_HIGH_MEMORY:
|
|
debug(D_XMS, "XMS: Allocate HMA\n");
|
|
if (HMA_allocated) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_HMA_ALREADY_USED;
|
|
} else {
|
|
HMA_allocated = 1;
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case XMS_FREE_HIGH_MEMORY:
|
|
debug(D_XMS, "XMS: Free HMA\n");
|
|
if (HMA_allocated) {
|
|
HMA_allocated = 0;
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
} else {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_HMA_NOT_ALLOCATED;
|
|
}
|
|
break;
|
|
|
|
case XMS_GLOBAL_ENABLE_A20:
|
|
debug(D_XMS, "XMS: Global enable A20\n");
|
|
if (HMA_a20 == 0)
|
|
enable_a20();
|
|
HMA_a20 = 1;
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
|
|
case XMS_GLOBAL_DISABLE_A20:
|
|
debug(D_XMS, "XMS: Global disable A20\n");
|
|
if (HMA_a20 != 0)
|
|
disable_a20();
|
|
HMA_a20 = 0;
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
|
|
/*
|
|
* This is an accumulating call. Every call increments HMA_a20.
|
|
* Caller must use LOCAL_DISBALE_A20 once for each previous call
|
|
* to LOCAL_ENABLE_A20.
|
|
*/
|
|
case XMS_LOCAL_ENABLE_A20:
|
|
debug(D_XMS, "XMS: Local enable A20\n");
|
|
HMA_a20++;
|
|
if (HMA_a20 == 1)
|
|
enable_a20();
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
|
|
case XMS_LOCAL_DISABLE_A20:
|
|
debug(D_XMS, "XMS: Local disable A20\n");
|
|
if (HMA_a20 > 0)
|
|
HMA_a20--;
|
|
if (HMA_a20 == 0)
|
|
disable_a20();
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
|
|
case XMS_QUERY_A20:
|
|
/*
|
|
* Disabled because DOS permanently scans this, to avoid endless output.
|
|
*/
|
|
#if 0
|
|
debug(D_XMS, "XMS: Query A20\n"); */
|
|
#endif
|
|
R_AX = (HMA_a20 > 0) ? 0x1 : 0x0;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
|
|
case XMS_QUERY_FREE_EXTENDED_MEMORY:
|
|
/* DOS MEM.EXE chokes, if the HMA is enabled and the reported
|
|
* free space includes the HMA. So we subtract 64kB from the
|
|
* space reported, if the HMA is enabled.
|
|
*/
|
|
if (HMA_a20 < 0)
|
|
R_EAX = R_EDX = xms_free_mem / 1024;
|
|
else
|
|
R_EAX = R_EDX = (xms_free_mem / 1024) - 64;
|
|
|
|
if (xms_free_mem == 0)
|
|
R_BL = XMS_FULL;
|
|
else
|
|
R_BL = XMS_SUCCESS;
|
|
debug(D_XMS, "XMS: Query free EMM: Returned %dkB\n", R_AX);
|
|
break;
|
|
|
|
case XMS_ALLOCATE_EXTENDED_MEMORY:
|
|
{
|
|
size_t req_siz;
|
|
int hindx, hnum;
|
|
void *mem;
|
|
|
|
debug(D_XMS, "XMS: Allocate EMM: ");
|
|
/* Enough handles ? */
|
|
if ((hnum = get_free_handle()) == 0) {
|
|
R_AX = 0x00;
|
|
R_BL = XMS_OUT_OF_HANDLES;
|
|
debug(D_XMS, " Out of handles\n");
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
req_siz = R_DX * 1024;
|
|
|
|
/* Enough memory ? */
|
|
if (req_siz > xms_free_mem) {
|
|
R_AX = 0x00;
|
|
R_BL = XMS_FULL;
|
|
debug(D_XMS, " No memory left\n");
|
|
break;
|
|
}
|
|
|
|
xms_hand[hindx].size = req_siz;
|
|
xms_hand[hindx].num_locks = 0;
|
|
|
|
/* XXX
|
|
* Not sure about that: Is it possible to reserve a handle
|
|
* but with no memory attached ? XMS specs are unclear on
|
|
* that point. Linux implementation does it this way.
|
|
*/
|
|
if (req_siz == 0) {
|
|
/* This handle is reserved, but has size 0 and no address */
|
|
xms_hand[hindx].addr = XMS_NULL_ALLOC;
|
|
} else {
|
|
if ((mem = malloc(req_siz)) == NULL)
|
|
fatal("XMS: Cannot malloc !");
|
|
xms_hand[hindx].addr = (u_long)mem;
|
|
}
|
|
xms_free_mem -= req_siz;
|
|
xms_used_mem += req_siz;
|
|
num_free_handle--;
|
|
R_AX = 0x1;
|
|
R_DX = hnum;
|
|
R_BL = XMS_SUCCESS;
|
|
debug(D_XMS, " Allocated %d kB, handle %d\n",
|
|
req_siz / 1024, hnum);
|
|
break;
|
|
}
|
|
|
|
case XMS_FREE_EXTENDED_MEMORY:
|
|
{
|
|
int hnum, hindx;
|
|
|
|
debug(D_XMS, "XMS: Free EMM: ");
|
|
hnum = R_DX;
|
|
if (hnum > NUM_HANDLES || hnum == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
debug(D_XMS, " Invalid handle\n");
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
|
|
if (xms_hand[hindx].addr == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
debug(D_XMS, " Invalid handle\n");
|
|
|
|
} else if (xms_hand[hindx].num_locks > 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_BLOCK_IS_LOCKED;
|
|
debug(D_XMS, " Is locked\n");
|
|
|
|
} else {
|
|
if (xms_hand[hindx].addr != XMS_NULL_ALLOC) {
|
|
free((void *)xms_hand[hindx].addr);
|
|
xms_free_mem += xms_hand[hindx].size;
|
|
xms_used_mem -= xms_hand[hindx].size;
|
|
}
|
|
xms_hand[hindx].addr = 0;
|
|
xms_hand[hindx].size = 0;
|
|
xms_hand[hindx].num_locks = 0;
|
|
num_free_handle++;
|
|
debug(D_XMS, " Success for handle %d\n", hnum);
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case XMS_MOVE_EXTENDED_MEMORY_BLOCK:
|
|
{
|
|
u_long srcptr, dstptr;
|
|
u_long srcoffs, dstoffs;
|
|
int srcidx, dstidx;
|
|
const struct EMM *eptr;
|
|
int n;
|
|
|
|
debug(D_XMS, "XMS: Move EMM block: ");
|
|
eptr = (struct EMM *)MAKEPTR(R_DS, R_SI);
|
|
|
|
/* Sanity check: Don't allow eptr pointing to emulator data */
|
|
if (((u_long)eptr + sizeof(struct EMM)) >= 0x100000) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_GENERAL_ERROR;
|
|
debug(D_XMS, " Offset to EMM structure wrong\n");
|
|
break;
|
|
}
|
|
|
|
/* Validate handles and offsets */
|
|
|
|
if (eptr->src_handle > NUM_HANDLES) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_SOURCE_HANDLE;
|
|
debug(D_XMS, " Invalid handle\n");
|
|
break;
|
|
}
|
|
if (eptr->dst_handle > NUM_HANDLES) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_DESTINATION_HANDLE;
|
|
debug(D_XMS, " Invalid handle\n");
|
|
break;
|
|
}
|
|
srcidx = eptr->src_handle - 1;
|
|
dstidx = eptr->dst_handle - 1;
|
|
srcoffs = eptr->src_offset;
|
|
dstoffs = eptr->dst_offset;
|
|
n = eptr->nbytes;
|
|
/* Length must be even, see XMS spec */
|
|
if (n & 1) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_LENGTH;
|
|
debug(D_XMS, " Length not even\n");
|
|
break;
|
|
}
|
|
if (eptr->src_handle != 0) {
|
|
srcptr = xms_hand[srcidx].addr;
|
|
if (srcptr == 0 || srcptr == XMS_NULL_ALLOC) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_SOURCE_HANDLE;
|
|
debug(D_XMS, " Invalid source handle\n");
|
|
break;
|
|
}
|
|
if ((srcoffs + n) > xms_hand[srcidx].size) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_SOURCE_OFFSET;
|
|
debug(D_XMS, " Invalid source offset\n");
|
|
break;
|
|
}
|
|
srcptr += srcoffs;
|
|
} else {
|
|
srcptr = VECPTR(srcoffs);
|
|
/* Sanity check: Don't allow srcptr pointing to
|
|
* emulator data above 1M
|
|
*/
|
|
if ((srcptr + n) >= 0x100000) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_GENERAL_ERROR;
|
|
debug(D_XMS, " Source segment invalid\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (eptr->dst_handle != 0) {
|
|
dstptr = xms_hand[dstidx].addr;
|
|
if (dstptr == NULL || dstptr == XMS_NULL_ALLOC) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_DESTINATION_HANDLE;
|
|
debug(D_XMS, " Invalid dest handle\n");
|
|
break;
|
|
}
|
|
if ((dstoffs + n) > xms_hand[dstidx].size) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_DESTINATION_OFFSET;
|
|
debug(D_XMS, " Invalid dest offset\n");
|
|
break;
|
|
}
|
|
dstptr += dstoffs;
|
|
} else {
|
|
dstptr = VECPTR(dstoffs);
|
|
/* Sanity check: Don't allow dstptr pointing to
|
|
* emulator data above 1M
|
|
*/
|
|
if ((dstptr + n) >= 0x100000) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_GENERAL_ERROR;
|
|
debug(D_XMS, " Dest segment invalid\n");
|
|
break;
|
|
}
|
|
}
|
|
memmove((void *)dstptr, (void *)srcptr, n);
|
|
debug(D_XMS, "Moved from %08lx to %08lx, %04x bytes\n",
|
|
srcptr, dstptr, n);
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case XMS_LOCK_EXTENDED_MEMORY_BLOCK:
|
|
{
|
|
int hnum,hindx;
|
|
|
|
debug(D_XMS, "XMS: Lock EMM block\n");
|
|
hnum = R_DX;
|
|
if (hnum > NUM_HANDLES || hnum == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
if (xms_hand[hindx].addr == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
if (xms_hand[hindx].num_locks == 255) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_BLOCK_LOCKCOUNT_OVERFLOW;
|
|
break;
|
|
}
|
|
xms_hand[hindx].num_locks++;
|
|
R_AX = 0x1;
|
|
/*
|
|
* The 32 bit "physical" address is returned here. I hope
|
|
* the solution to simply return the linear address of the
|
|
* malloced area is good enough. Most DOS programs won't
|
|
* need this anyway. It could be important for future DPMI.
|
|
*/
|
|
R_BX = xms_hand[hindx].addr & 0xffff;
|
|
R_DX = (xms_hand[hindx].addr & 0xffff0000) >> 16;
|
|
break;
|
|
}
|
|
|
|
case XMS_UNLOCK_EXTENDED_MEMORY_BLOCK:
|
|
{
|
|
int hnum,hindx;
|
|
|
|
debug(D_XMS, "XMS: Unlock EMM block\n");
|
|
hnum = R_DX;
|
|
if (hnum > NUM_HANDLES || hnum == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
if (xms_hand[hindx].addr == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
if (xms_hand[hindx].num_locks == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_BLOCK_NOT_LOCKED;
|
|
break;
|
|
}
|
|
xms_hand[hindx].num_locks--;
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case XMS_GET_EMB_HANDLE_INFORMATION:
|
|
{
|
|
int hnum,hindx;
|
|
|
|
debug(D_XMS, "XMS: Get handle information: DX=%04x\n", R_DX);
|
|
hnum = R_DX;
|
|
if (hnum > NUM_HANDLES || hnum == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
if (xms_hand[hindx].addr == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
R_AX = 0x1;
|
|
R_BH = xms_hand[hindx].num_locks;
|
|
R_BL = num_free_handle;
|
|
R_DX = xms_hand[hindx].size / 1024;
|
|
break;
|
|
}
|
|
|
|
case XMS_RESIZE_EXTENDED_MEMORY_BLOCK:
|
|
{
|
|
int hnum,hindx;
|
|
size_t req_siz;
|
|
long sizediff;
|
|
void *mem;
|
|
|
|
debug(D_XMS, "XMS: Resize EMM block\n");
|
|
hnum = R_DX;
|
|
req_siz = R_BX * 1024;
|
|
if (hnum > NUM_HANDLES || hnum == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
hindx = hnum - 1;
|
|
if (xms_hand[hindx].addr == 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_HANDLE;
|
|
break;
|
|
}
|
|
if (xms_hand[hindx].num_locks > 0) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_BLOCK_IS_LOCKED;
|
|
break;
|
|
}
|
|
sizediff = req_siz - xms_hand[hindx].size;
|
|
|
|
if (sizediff > 0) {
|
|
if ((sizediff + xms_used_mem) > xms_maxsize) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_FULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (sizediff == 0) { /* Never trust DOS programs */
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
xms_used_mem += sizediff;
|
|
xms_free_mem -= sizediff;
|
|
|
|
if (xms_hand[hindx].addr == XMS_NULL_ALLOC) {
|
|
if ((mem = malloc(req_siz)) == NULL)
|
|
fatal("XMS: Cannot malloc !");
|
|
xms_hand[hindx].addr = (u_long)mem;
|
|
xms_hand[hindx].size = req_siz;
|
|
} else {
|
|
if ((mem = realloc((void *)xms_hand[hindx].addr,req_siz))
|
|
== NULL)
|
|
fatal("XMS: Cannot realloc !");
|
|
xms_hand[hindx].addr = (u_long)mem;
|
|
xms_hand[hindx].size = req_siz;
|
|
}
|
|
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
case XMS_ALLOCATE_UMB:
|
|
{
|
|
u_long req_siz;
|
|
UMB_block *bp;
|
|
|
|
debug(D_XMS, "XMS: Allocate UMB: DX=%04x\n", R_DX);
|
|
req_siz = R_DX * 16;
|
|
/* Some programs try to allocate 0 bytes. XMS spec says
|
|
* nothing about this. So the driver grants the request
|
|
* but it rounds up to the next paragraph size (1) and
|
|
* returns this amount of memory
|
|
*/
|
|
if (req_siz == 0)
|
|
req_siz = 0x10;
|
|
|
|
/* First try to find an exact fit */
|
|
if ((bp = find_exact_block(req_siz)) != NULL) {
|
|
/* Found ! Move block from free list to alloc list */
|
|
remove_block(&UMB_freelist, bp);
|
|
add_block(&UMB_alloclist, bp);
|
|
R_AX = 0x1;
|
|
R_DX = req_siz >> 4;
|
|
R_BX = bp->addr >> 4;
|
|
break;
|
|
}
|
|
/* Try to find a block big enough */
|
|
bp = find_block(req_siz);
|
|
if (bp == NULL) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_NO_UMBS_AVAILABLE;
|
|
R_DX = 0x0;
|
|
|
|
} else if (bp->size < req_siz) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_REQUESTED_UMB_TOO_BIG;
|
|
R_DX = bp->size / 16;
|
|
|
|
} else {
|
|
UMB_block *newbp;
|
|
/* Found a block large enough. Split it into the size
|
|
* we need, rest remains on the free list. New block
|
|
* goes to the alloc list
|
|
*/
|
|
newbp = create_block(bp->addr, req_siz);
|
|
bp->addr += req_siz;
|
|
bp->size -= req_siz;
|
|
add_block(&UMB_alloclist, newbp);
|
|
R_AX = 0x1;
|
|
R_BX = newbp->addr >> 4;
|
|
R_DX = req_siz / 16;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case XMS_DEALLOCATE_UMB:
|
|
{
|
|
u_long req_addr;
|
|
UMB_block *blk;
|
|
|
|
debug(D_XMS, "XMS: Deallocate UMB: DX=%04x\n", R_DX);
|
|
req_addr = R_DX << 4;
|
|
if ((blk = find_allocated_block(req_addr)) == NULL) {
|
|
R_AX = 0x0;
|
|
R_BL = XMS_INVALID_UMB_SEGMENT;
|
|
} else {
|
|
/* Move the block from the alloc list to the free list
|
|
* and try to do garbage collection
|
|
*/
|
|
remove_block(&UMB_alloclist, blk);
|
|
add_block(&UMB_freelist, blk);
|
|
merge_blocks();
|
|
R_AX = 0x1;
|
|
R_BL = XMS_SUCCESS;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
/*
|
|
* If the option DOS=UMB is enabled, DOS grabs the entire UMB
|
|
* at boot time. In any other case this is used to load resident
|
|
* utilities. I don't think this function is neccesary here.
|
|
*/
|
|
case XMS_REALLOCATE_UMB:
|
|
debug(D_XMS, "XMS: Reallocate UMB\n");
|
|
R_AX = 0x0;
|
|
R_BL = XMS_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
/* Some test programs use this call */
|
|
case XMS_QUERY_FREE_EXTENDED_MEMORY_LARGE:
|
|
/* DOS MEM.EXE chokes, if the HMA is enabled and the reported
|
|
* free space includes the HMA. So we subtract 64kB from the
|
|
* space reported, if the HMA is enabled.
|
|
*/
|
|
if (HMA_a20 < 0)
|
|
R_EAX = R_EDX = xms_free_mem / 1024;
|
|
else
|
|
R_EAX = R_EDX = (xms_free_mem / 1024) - 64;
|
|
/* ECX should return the highest address of any memory block
|
|
* We return 1MB + size of extended memory
|
|
*/
|
|
R_ECX = 1024 * 1024 + xms_maxsize -1;
|
|
if (xms_free_mem == 0)
|
|
R_BL = XMS_FULL;
|
|
else
|
|
R_BL = XMS_SUCCESS;
|
|
debug(D_XMS, "XMS: Query free EMM(large): Returned %dkB\n", R_AX);
|
|
break;
|
|
|
|
/* These are the same as the above functions, but they use 32 bit
|
|
* registers (i.e. EDX instead of DX). This is for allocations of
|
|
* more than 64MB. I think this will hardly be used in the emulator
|
|
* It seems to work without them, but the functions are in the XMS 3.0
|
|
* spec. If something breaks because they are not here, I can implement
|
|
* them
|
|
*/
|
|
case XMS_ALLOCATE_EXTENDED_MEMORY_LARGE:
|
|
case XMS_FREE_EXTENDED_MEMORY_LARGE:
|
|
debug(D_XMS, "XMS: %02x function called, not implemented\n", R_AH);
|
|
R_AX = 0x0;
|
|
R_BL = XMS_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
default:
|
|
debug(D_ALWAYS, "XMS: Unimplemented function %02x, \n", R_AH);
|
|
R_AX = 0;
|
|
R_BL = XMS_NOT_IMPLEMENTED;
|
|
break;
|
|
}
|
|
}
|