dosmem.c 19.9 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1 2 3 4
/*
 * DOS memory emulation
 *
 * Copyright 1995 Alexandre Julliard
Alexandre Julliard's avatar
Alexandre Julliard committed
5
 * Copyright 1996 Marcus Meissner
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
Alexandre Julliard's avatar
Alexandre Julliard committed
20 21
 */

22
#include "config.h"
23
#include "wine/port.h"
24

Alexandre Julliard's avatar
Alexandre Julliard committed
25
#include <signal.h>
26
#include <stdarg.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
27
#include <stdlib.h>
Alexandre Julliard's avatar
Alexandre Julliard committed
28
#include <string.h>
29
#include <sys/types.h>
30 31 32 33
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif

34
#include "windef.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
35
#include "winbase.h"
36 37
#include "excpt.h"
#include "winternl.h"
38
#include "wine/winbase16.h"
39

40
#include "kernel16_private.h"
41
#include "dosexe.h"
42
#include "wine/debug.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
43

44 45
WINE_DEFAULT_DEBUG_CHANNEL(dosmem);
WINE_DECLARE_DEBUG_CHANNEL(selector);
46

47 48 49
WORD DOSMEM_0000H;        /* segment at 0:0 */
WORD DOSMEM_BiosDataSeg;  /* BIOS data segment at 0x40:0 */
WORD DOSMEM_BiosSysSeg;   /* BIOS ROM segment at 0xf000:0 */
Alexandre Julliard's avatar
Alexandre Julliard committed
50

51 52 53 54
/* DOS memory highest address (including HMA) */
#define DOSMEM_SIZE             0x110000
#define DOSMEM_64KB             0x10000

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
/*
 * Memory Control Block (MCB) definition
 * FIXME: implement Allocation Strategy
 */

#define MCB_DUMP(mc) \
    TRACE ("MCB_DUMP base=%p type=%02xh psp=%04xh size=%04xh\n", mc, mc->type, mc->psp , mc->size )

#define MCB_NEXT(mc) \
    (MCB*) ((mc->type==MCB_TYPE_LAST) ? NULL : (char*)(mc) + ((mc->size + 1) << 4) )

/* FIXME: should we check more? */
#define MCB_VALID(mc) \
    ((mc->type==MCB_TYPE_NORMAL) || (mc->type==MCB_TYPE_LAST))


#define MCB_TYPE_NORMAL    0x4d
#define MCB_TYPE_LAST      0x5a

#define MCB_PSP_DOS        0x0060
#define MCB_PSP_FREE       0

#include "pshpack1.h"
typedef struct {
    BYTE type;
    WORD psp;     /* segment of owner psp */
    WORD size;    /* in paragraphs */
    BYTE pad[3];
    BYTE name[8];
} MCB;
#include "poppack.h"

/*
#define __DOSMEM_DEBUG__
 */

#define VM_STUB(x) (0x90CF00CD|(x<<8)) /* INT x; IRET; NOP */
#define VM_STUB_SEGMENT 0xf000         /* BIOS segment */

/* FIXME: this should be moved to the LOL */
static MCB* DOSMEM_root_block;

97 98 99 100 101 102 103 104 105 106
/* when looking at DOS and real mode memory, we activate in three different
 * modes, depending the situation.
 * 1/ By default (protected mode), the first MB of memory (actually 0x110000,
 *    when you also look at the HMA part) is always reserved, whatever you do.
 *    We allocated some PM selectors to this memory, even if this area is not
 *    committed at startup
 * 2/ if a program tries to use the memory through the selectors, we actually
 *    commit this memory, made of: BIOS segment, but also some system 
 *    information, usually low in memory that we map for the circumstance also
 *    in the BIOS segment, so that we keep the low memory protected (for NULL
107 108 109 110 111 112
 *    pointer deref catching for example). In this case, we're still in PM
 *    mode, accessing part of the "physical" real mode memory. In fact, we don't
 *    map all the first meg, we keep 64k uncommitted to still catch NULL 
 *    pointers dereference
 * 3/ if the process enters the real mode, then we (also) commit the full first
 *    MB of memory (and also initialize the DOS structures in it).
113 114 115
 */

/* DOS memory base (linear in process address space) */
116
static char *DOSMEM_dosmem;
117
static char *DOSMEM_sysmem;
118 119 120
/* number of bytes protected from _dosmem. 0 when DOS memory is initialized, 
 * 64k otherwise to trap NULL pointers deref */
static DWORD DOSMEM_protect;
121

122
static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except);
123
static void *vectored_handler;
Alexandre Julliard's avatar
Alexandre Julliard committed
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
/***********************************************************************
 *           DOSMEM_FillIsrTable
 *
 * Fill the interrupt table with fake BIOS calls to BIOSSEG (0xf000).
 *
 * NOTES:
 * Linux normally only traps INTs performed from or destined to BIOSSEG
 * for us to handle, if the int_revectored table is empty. Filling the
 * interrupt table with calls to INT stubs in BIOSSEG allows DOS programs
 * to hook interrupts, as well as use their familiar retf tricks to call
 * them, AND let Wine handle any unhooked interrupts transparently.
 */
static void DOSMEM_FillIsrTable(void)
{
    SEGPTR *isr = (SEGPTR*)DOSMEM_sysmem;
    int x;

    for (x=0; x<256; x++) isr[x]=MAKESEGPTR(VM_STUB_SEGMENT,x*4);
}
144

145
static void DOSMEM_MakeIsrStubs(void)
146
{
147 148
    DWORD *stub = (DWORD*)(DOSMEM_dosmem + (VM_STUB_SEGMENT << 4));
    int x;
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    for (x=0; x<256; x++) stub[x]=VM_STUB(x);
}

BIOSDATA* DOSVM_BiosData(void)
{
    return (BIOSDATA *)(DOSMEM_sysmem + 0x400);
}

/**********************************************************************
 *          DOSMEM_GetTicksSinceMidnight
 *
 * Return number of clock ticks since midnight.
 */
static DWORD DOSMEM_GetTicksSinceMidnight(void)
{
    SYSTEMTIME time;

    /* This should give us the (approximately) correct
     * 18.206 clock ticks per second since midnight.
169
     */
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

    GetLocalTime( &time );

    return (((time.wHour * 3600 + time.wMinute * 60 +
              time.wSecond) * 18206) / 1000) +
             (time.wMilliseconds * 1000 / 54927);
}

/***********************************************************************
 *           DOSMEM_FillBiosSegments
 *
 * Fill the BIOS data segment with dummy values.
 */
static void DOSMEM_FillBiosSegments(void)
{
    BYTE *pBiosSys = (BYTE*)DOSMEM_dosmem + 0xf0000;
    BYTE *pBiosROMTable = pBiosSys+0xe6f5;
    BIOSDATA *pBiosData = DOSVM_BiosData();
    static const char bios_date[] = "13/01/99";

      /* Clear all unused values */
    memset( pBiosData, 0, sizeof(*pBiosData) );

    /* FIXME: should check the number of configured drives and ports */
    pBiosData->Com1Addr             = 0x3f8;
    pBiosData->Com2Addr             = 0x2f8;
    pBiosData->Lpt1Addr             = 0x378;
    pBiosData->Lpt2Addr             = 0x278;
    pBiosData->InstalledHardware    = 0x5463;
    pBiosData->MemSize              = 640;
    pBiosData->NextKbdCharPtr       = 0x1e;
    pBiosData->FirstKbdCharPtr      = 0x1e;
    pBiosData->VideoMode            = 3;
    pBiosData->VideoColumns         = 80;
    pBiosData->VideoPageSize        = 80 * 25 * 2;
    pBiosData->VideoPageStartAddr   = 0xb800;
    pBiosData->VideoCtrlAddr        = 0x3d4;
    pBiosData->Ticks                = DOSMEM_GetTicksSinceMidnight();
    pBiosData->NbHardDisks          = 2;
    pBiosData->KbdBufferStart       = 0x1e;
    pBiosData->KbdBufferEnd         = 0x3e;
    pBiosData->RowsOnScreenMinus1   = 24;
    pBiosData->BytesPerChar         = 0x10;
    pBiosData->ModeOptions          = 0x64;
    pBiosData->FeatureBitsSwitches  = 0xf9;
    pBiosData->VGASettings          = 0x51;
    pBiosData->DisplayCombination   = 0x08;
    pBiosData->DiskDataRate         = 0;

    /* fill ROM configuration table (values from Award) */
    *(pBiosROMTable+0x0)	= 0x08; /* number of bytes following LO */
    *(pBiosROMTable+0x1)	= 0x00; /* number of bytes following HI */
    *(pBiosROMTable+0x2)	= 0xfc; /* model */
    *(pBiosROMTable+0x3)	= 0x01; /* submodel */
    *(pBiosROMTable+0x4)	= 0x00; /* BIOS revision */
    *(pBiosROMTable+0x5)	= 0x74; /* feature byte 1 */
    *(pBiosROMTable+0x6)	= 0x00; /* feature byte 2 */
    *(pBiosROMTable+0x7)	= 0x00; /* feature byte 3 */
    *(pBiosROMTable+0x8)	= 0x00; /* feature byte 4 */
    *(pBiosROMTable+0x9)	= 0x00; /* feature byte 5 */

    /* BIOS date string */
    memcpy(pBiosSys+0xfff5, bios_date, sizeof bios_date);

    /* BIOS ID */
    *(pBiosSys+0xfffe) = 0xfc;

    /* Reboot vector (f000:fff0 or ffff:0000) */
    *(DWORD*)(pBiosSys + 0xfff0) = VM_STUB(0x19);
}

/***********************************************************************
 *           BiosTick
 *
 * Increment the BIOS tick counter. Called by timer signal handler.
 */
static void CALLBACK BiosTick( LPVOID arg, DWORD low, DWORD high )
{
    BIOSDATA *pBiosData = arg;
    pBiosData->Ticks++;
}

/***********************************************************************
 *           timer_thread
 */
static DWORD CALLBACK timer_thread( void *arg )
{
    LARGE_INTEGER when;
    HANDLE timer;

    if (!(timer = CreateWaitableTimerA( NULL, FALSE, NULL ))) return 0;

    when.u.LowPart = when.u.HighPart = 0;
    SetWaitableTimer( timer, &when, 55 /* actually 54.925 */, BiosTick, arg, FALSE );
    for (;;) SleepEx( INFINITE, TRUE );
}

267 268 269 270 271 272 273 274 275 276 277 278 279
/***********************************************************************
 *           DOSVM_start_bios_timer
 *
 * Start the BIOS ticks timer when the app accesses selector 0x40.
 */
void DOSVM_start_bios_timer(void)
{
    static LONG running;

    if (!InterlockedExchange( &running, 1 ))
        CloseHandle( CreateThread( NULL, 0, timer_thread, DOSVM_BiosData(), 0, NULL ));
}

280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
/***********************************************************************
 *           DOSMEM_Collapse
 *
 * Helper function for internal use only.
 * Attach all following free blocks to this one, even if this one is not free.
 */
static void DOSMEM_Collapse( MCB* mcb )
{
    MCB* next = MCB_NEXT( mcb );

    while (next && next->psp == MCB_PSP_FREE)
    {
        mcb->size = mcb->size + next->size + 1;
        mcb->type = next->type;    /* make sure keeping MCB_TYPE_LAST */
        next = MCB_NEXT( next );
    }
}

/******************************************************************
 *		DOSMEM_InitDosMemory
 */
BOOL DOSMEM_InitDosMemory(void)
{
    static int done;
    static HANDLE hRunOnce;

    if (done) return TRUE;

    /* FIXME: this isn't 100% thread safe, as we won't catch accesses while initializing */
309
    if (hRunOnce == 0)
310
    {
311
	HANDLE hEvent = CreateEventW( NULL, TRUE, FALSE, NULL );
312
        if (InterlockedCompareExchangePointer( &hRunOnce, hEvent, 0 ) == 0)
313
	{
314 315
            BOOL ret;
            DWORD reserve;
316 317

	    /* ok, we're the winning thread */
318 319 320 321
            if (!(ret = VirtualProtect( DOSMEM_dosmem + DOSMEM_protect,
                                        DOSMEM_SIZE - DOSMEM_protect,
                                        PAGE_READWRITE, NULL )))
                ERR("Cannot load access low 1Mb, DOS subsystem unavailable\n");
322
            RemoveVectoredExceptionHandler( vectored_handler );
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352

            /*
             * Reserve either:
             * - lowest 64k for NULL pointer catching (Win16)
             * - lowest 1k for interrupt handlers and
             *   another 0.5k for BIOS, DOS and intra-application
             *   areas (DOS)
             */
            if (DOSMEM_dosmem != DOSMEM_sysmem)
                reserve = 0x10000; /* 64k */
            else
                reserve = 0x600; /* 1.5k */

            /*
             * Set DOS memory base and initialize conventional memory.
             */
            DOSMEM_FillBiosSegments();
            DOSMEM_FillIsrTable();

            /* align root block to paragraph */
            DOSMEM_root_block = (MCB*)(DOSMEM_dosmem + reserve);
            DOSMEM_root_block->type = MCB_TYPE_LAST;
            DOSMEM_root_block->psp = MCB_PSP_FREE;
            DOSMEM_root_block->size = (DOSMEM_dosmem + 0x9fffc  - ((char*)DOSMEM_root_block)) >> 4;

            TRACE("DOS conventional memory initialized, %d bytes free.\n",
                  DOSMEM_Available());

            DOSVM_InitSegments();

353
            SetEvent( hRunOnce );
354 355
            done = 1;
            return ret;
356 357 358
	}
	/* someone beat us here... */
	CloseHandle( hEvent );
359
    }
360 361 362

    /* and wait for the winner to have finished */
    WaitForSingleObject( hRunOnce, INFINITE );
363
    return TRUE;
364 365 366 367 368 369 370 371 372 373 374
}

/******************************************************************
 *		dosmem_handler
 *
 * Handler to catch access to our 1MB address space reserved for real memory
 */
static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except)
{
    if (except->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
    {
375 376
        char *addr = (char *)except->ExceptionRecord->ExceptionInformation[1];
        if (addr >= DOSMEM_dosmem + DOSMEM_protect && addr < DOSMEM_dosmem + DOSMEM_SIZE)
377
        {
378
            if (DOSMEM_InitDosMemory()) return EXCEPTION_CONTINUE_EXECUTION;
379 380 381 382
        }
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
383

384 385
/***********************************************************************
 *           DOSMEM_Init
386
 *
387 388
 * Create the dos memory segments, and store them into the KERNEL
 * exported values.
389
 */
390
BOOL DOSMEM_Init(void)
391
{
392 393
    void *addr = (void *)1;
    SIZE_T size = DOSMEM_SIZE - 1;
394

395 396
    if (NtAllocateVirtualMemory( GetCurrentProcess(), &addr, 0, &size,
                                 MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS ))
397
    {
398 399
        ERR( "Cannot allocate DOS memory\n" );
        ExitProcess(1);
400 401
    }

402
    if (addr <= (void *)DOSMEM_64KB)
403
    {
404
        DOSMEM_dosmem = 0;
405
        DOSMEM_protect = DOSMEM_64KB;
406
        DOSMEM_sysmem = (char *)0xf0000;  /* store sysmem in high addresses for now */
407 408 409 410
    }
    else
    {
        WARN( "First megabyte not available for DOS address space.\n" );
411
        DOSMEM_dosmem = addr;
412
        DOSMEM_protect = 0;
413
        DOSMEM_sysmem = DOSMEM_dosmem;
414
    }
415

416
    vectored_handler = AddVectoredExceptionHandler(FALSE, dosmem_handler);
417
    DOSMEM_0000H = GLOBAL_CreateBlock( GMEM_FIXED, DOSMEM_sysmem,
418
                                       DOSMEM_64KB, 0, WINE_LDT_FLAGS_DATA );
419
    DOSMEM_BiosDataSeg = GLOBAL_CreateBlock( GMEM_FIXED, DOSMEM_sysmem + 0x400,
420 421 422
                                             0x100, 0, WINE_LDT_FLAGS_DATA );
    DOSMEM_BiosSysSeg = GLOBAL_CreateBlock( GMEM_FIXED, DOSMEM_dosmem + 0xf0000,
                                            DOSMEM_64KB, 0, WINE_LDT_FLAGS_DATA );
423

Alexandre Julliard's avatar
Alexandre Julliard committed
424 425 426
    return TRUE;
}

Alexandre Julliard's avatar
Alexandre Julliard committed
427
/***********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
428
 *           DOSMEM_MapLinearToDos
Alexandre Julliard's avatar
Alexandre Julliard committed
429
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
430
 * Linear address to the DOS address space.
Alexandre Julliard's avatar
Alexandre Julliard committed
431
 */
432
UINT DOSMEM_MapLinearToDos(LPVOID ptr)
Alexandre Julliard's avatar
Alexandre Julliard committed
433
{
434
    if (((char*)ptr >= DOSMEM_dosmem) &&
435
        ((char*)ptr < DOSMEM_dosmem + DOSMEM_SIZE))
436
          return (char *)ptr - DOSMEM_dosmem;
437
    return (UINT)ptr;
Alexandre Julliard's avatar
Alexandre Julliard committed
438 439
}

Alexandre Julliard's avatar
Alexandre Julliard committed
440

Alexandre Julliard's avatar
Alexandre Julliard committed
441
/***********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
442
 *           DOSMEM_MapDosToLinear
Alexandre Julliard's avatar
Alexandre Julliard committed
443
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
444
 * DOS linear address to the linear address space.
Alexandre Julliard's avatar
Alexandre Julliard committed
445
 */
446
LPVOID DOSMEM_MapDosToLinear(UINT ptr)
Alexandre Julliard's avatar
Alexandre Julliard committed
447
{
448
    if (ptr < DOSMEM_SIZE) return DOSMEM_dosmem + ptr;
Alexandre Julliard's avatar
Alexandre Julliard committed
449
    return (LPVOID)ptr;
Alexandre Julliard's avatar
Alexandre Julliard committed
450 451
}

Alexandre Julliard's avatar
Alexandre Julliard committed
452

Alexandre Julliard's avatar
Alexandre Julliard committed
453
/***********************************************************************
Alexandre Julliard's avatar
Alexandre Julliard committed
454
 *           DOSMEM_MapRealToLinear
Alexandre Julliard's avatar
Alexandre Julliard committed
455
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
456
 * Real mode DOS address into a linear pointer
Alexandre Julliard's avatar
Alexandre Julliard committed
457
 */
Alexandre Julliard's avatar
Alexandre Julliard committed
458
LPVOID DOSMEM_MapRealToLinear(DWORD x)
Alexandre Julliard's avatar
Alexandre Julliard committed
459
{
Alexandre Julliard's avatar
Alexandre Julliard committed
460
   LPVOID       lin;
Alexandre Julliard's avatar
Alexandre Julliard committed
461

462
   lin = DOSMEM_dosmem + HIWORD(x) * 16 + LOWORD(x);
463
   TRACE_(selector)("(0x%08x) returns %p.\n", x, lin );
Alexandre Julliard's avatar
Alexandre Julliard committed
464
   return lin;
Alexandre Julliard's avatar
Alexandre Julliard committed
465
}
466 467 468 469 470 471 472 473 474 475

/***********************************************************************
 *           DOSMEM_AllocBlock
 *
 * Carve a chunk of the DOS memory block (without selector).
 */
LPVOID DOSMEM_AllocBlock(UINT size, UINT16* pseg)
{
    MCB *curr;
    MCB *next = NULL;
476
    WORD psp;
477 478 479 480 481 482

    DOSMEM_InitDosMemory();

    curr = DOSMEM_root_block;
    if (!(psp = DOSVM_psp)) psp = MCB_PSP_DOS;

483
    if (pseg) *pseg = 0;
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672

    TRACE( "(%04xh)\n", size );

    /* round up to paragraph */
    size = (size + 15) >> 4;

#ifdef __DOSMEM_DEBUG__
    DOSMEM_Available();     /* checks the whole MCB list */
#endif

    /* loop over all MCB and search the next large enough MCB */
    while (curr)
    {
        if (!MCB_VALID (curr))
        {
            ERR( "MCB List Corrupt\n" );
            MCB_DUMP( curr );
            return NULL;
        }
        if (curr->psp == MCB_PSP_FREE)
        {
            DOSMEM_Collapse( curr );
            /* is it large enough (one paragraph for the MCB)? */
            if (curr->size >= size)
            {
                if (curr->size > size)
                {
                    /* split curr */
                    next = (MCB *) ((char*) curr + ((size+1) << 4));
                    next->psp = MCB_PSP_FREE;
                    next->size = curr->size - (size+1);
                    next->type = curr->type;
                    curr->type = MCB_TYPE_NORMAL;
                    curr->size = size;
                }
                /* curr is the found block */
                curr->psp = psp;
                if( pseg ) *pseg = (((char*)curr) + 16 - DOSMEM_dosmem) >> 4;
                return (LPVOID) ((char*)curr + 16);
            }
        }
        curr = MCB_NEXT(curr);
    }
    return NULL;
}

/***********************************************************************
 *           DOSMEM_FreeBlock
 */
BOOL DOSMEM_FreeBlock(void* ptr)
{
    MCB* mcb = (MCB*) ((char*)ptr - 16);

    TRACE( "(%p)\n", ptr );

#ifdef __DOSMEM_DEBUG__
    DOSMEM_Available();
#endif

    if (!MCB_VALID (mcb))
    {
        ERR( "MCB invalid\n" );
        MCB_DUMP( mcb );
        return FALSE;
    }

    mcb->psp = MCB_PSP_FREE;
    DOSMEM_Collapse( mcb );
    return TRUE;
}

/***********************************************************************
 *           DOSMEM_ResizeBlock
 *
 * Resize DOS memory block in place. Returns block size or -1 on error.
 *
 * If exact is TRUE, returned value is either old or requested block
 * size. If exact is FALSE, block is expanded even if there is not
 * enough space for full requested block size.
 *
 * TODO: return also biggest block size
 */
UINT DOSMEM_ResizeBlock(void *ptr, UINT size, BOOL exact)
{
    MCB* mcb = (MCB*) ((char*)ptr - 16);
    MCB* next;

    TRACE( "(%p,%04xh,%s)\n", ptr, size, exact ? "TRUE" : "FALSE" );

    /* round up to paragraph */
    size = (size + 15) >> 4;

#ifdef __DOSMEM_DEBUG__
    DOSMEM_Available();
#endif

    if (!MCB_VALID (mcb))
    {
        ERR( "MCB invalid\n" );
        MCB_DUMP( mcb );
        return -1;
    }

    /* resize needed? */
    if (mcb->size == size)
        return size << 4;

    /* collapse free blocks */
    DOSMEM_Collapse( mcb );

    /* shrink mcb ? */
    if (mcb->size > size)
    {
        next = (MCB *) ((char*)mcb + ((size+1) << 4));
        next->type = mcb->type;
        next->psp = MCB_PSP_FREE;
        next->size = mcb->size - (size+1);
        mcb->type = MCB_TYPE_NORMAL;
        mcb->size = size;
        return size << 4;
    }

    if (!exact)
    {
        return mcb->size << 4;
    }

    return -1;
}

/***********************************************************************
 *           DOSMEM_Available
 */
UINT DOSMEM_Available(void)
{
    UINT  available = 0;
    UINT  total = 0;
    MCB *curr = DOSMEM_root_block;
    /* loop over all MCB and search the largest free MCB */
    while (curr)
    {
#ifdef __DOSMEM_DEBUG__
        MCB_DUMP( curr );
#endif
        if (!MCB_VALID (curr))
        {
            ERR( "MCB List Corrupt\n" );
            MCB_DUMP( curr );
            return 0;
        }
        if (curr->psp == MCB_PSP_FREE &&
            curr->size > available )
            available = curr->size;

        total += curr->size + 1;
        curr = MCB_NEXT( curr );
    }
    TRACE( " %04xh of %04xh paragraphs available\n", available, total );
    return available << 4;
}

/******************************************************************
 *		DOSMEM_MapDosLayout
 *
 * Initialize the first MB of memory to look like a real DOS setup
 */
BOOL DOSMEM_MapDosLayout(void)
{
    static int already_mapped;

    if (!already_mapped)
    {
        if (DOSMEM_dosmem || !VirtualProtect( NULL, DOSMEM_SIZE, PAGE_EXECUTE_READWRITE, NULL ))
        {
            ERR( "Need full access to the first megabyte for DOS mode\n" );
            ExitProcess(1);
        }
        /* copy the BIOS and ISR area down */
        memcpy( DOSMEM_dosmem, DOSMEM_sysmem, 0x400 + 0x100 );
        DOSMEM_sysmem = DOSMEM_dosmem;
        SetSelectorBase( DOSMEM_0000H, 0 );
        SetSelectorBase( DOSMEM_BiosDataSeg, 0x400 );
        /* we may now need the actual interrupt stubs, and since we've just moved the
         * interrupt vector table away, we can fill the area with stubs instead... */
        DOSMEM_MakeIsrStubs();
        already_mapped = 1;
    }
    return TRUE;
}