Fix segmentation fault caused by incorrect referencing of client audio
[wine] / dlls / kernel / dosmem.c
1 /*
2  * DOS memory emulation
3  *
4  * Copyright 1995 Alexandre Julliard
5  * Copyright 1996 Marcus Meissner
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_MMAN_H
31 # include <sys/mman.h>
32 #endif
33
34 #include "windef.h"
35 #include "winbase.h"
36 #include "winreg.h"
37 #include "excpt.h"
38 #include "winternl.h"
39 #include "wine/winbase16.h"
40
41 #include "kernel_private.h"
42 #include "toolhelp.h"
43 #include "miscemu.h"
44 #include "wine/debug.h"
45
46 WINE_DEFAULT_DEBUG_CHANNEL(dosmem);
47 WINE_DECLARE_DEBUG_CHANNEL(selector);
48
49 WORD DOSMEM_0000H;        /* segment at 0:0 */
50 WORD DOSMEM_BiosDataSeg;  /* BIOS data segment at 0x40:0 */
51 WORD DOSMEM_BiosSysSeg;   /* BIOS ROM segment at 0xf000:0 */
52
53 /* DOS memory highest address (including HMA) */
54 #define DOSMEM_SIZE             0x110000
55 #define DOSMEM_64KB             0x10000
56
57 /* use 2 low bits of 'size' for the housekeeping */
58
59 #define DM_BLOCK_DEBUG          0xABE00000
60 #define DM_BLOCK_TERMINAL       0x00000001
61 #define DM_BLOCK_FREE           0x00000002
62 #define DM_BLOCK_MASK           0x001FFFFC
63
64 /*
65 #define __DOSMEM_DEBUG__
66  */
67
68 typedef struct {
69    unsigned     size;
70 } dosmem_entry;
71
72 typedef struct {
73   unsigned      blocks;
74   unsigned      free;
75 } dosmem_info;
76
77 #define NEXT_BLOCK(block) \
78         (dosmem_entry*)(((char*)(block)) + \
79          sizeof(dosmem_entry) + ((block)->size & DM_BLOCK_MASK))
80
81 #define VM_STUB(x) (0x90CF00CD|(x<<8)) /* INT x; IRET; NOP */
82 #define VM_STUB_SEGMENT 0xf000         /* BIOS segment */
83
84 /* when looking at DOS and real mode memory, we activate in three different
85  * modes, depending the situation.
86  * 1/ By default (protected mode), the first MB of memory (actually 0x110000,
87  *    when you also look at the HMA part) is always reserved, whatever you do.
88  *    We allocated some PM selectors to this memory, even if this area is not
89  *    committed at startup
90  * 2/ if a program tries to use the memory through the selectors, we actually
91  *    commit this memory, made of: BIOS segment, but also some system 
92  *    information, usually low in memory that we map for the circumstance also
93  *    in the BIOS segment, so that we keep the low memory protected (for NULL
94  *    pointer deref catching for example). In this case, we'res still in PM
95  *    mode, accessing part of the "physicale" real mode memory.
96  * 3/ if the process enters the real mode, then we commit the full first MB of
97  *    memory (and also initialize the DOS structures in it).
98  */
99
100 /* DOS memory base (linear in process address space) */
101 static char *DOSMEM_dosmem;
102 /* DOS system base (for interrupt vector table and BIOS data area)
103  * ...should in theory (i.e. Windows) be equal to DOSMEM_dosmem (NULL),
104  * but is normally set to 0xf0000 in Wine to allow trapping of NULL pointers,
105  * and only relocated to NULL when absolutely necessary */
106 static char *DOSMEM_sysmem;
107 /* number of bytes protected from _dosmem. 0 when DOS memory is initialized, 
108  * 64k otherwise to trap NULL pointers deref */
109 static DWORD DOSMEM_protect;
110
111 static void DOSMEM_InitMemory(void);
112
113 /***********************************************************************
114  *           DOSMEM_MemoryTop
115  *
116  * Gets the DOS memory top.
117  */
118 static char *DOSMEM_MemoryTop(void)
119 {
120     return DOSMEM_dosmem+0x9FFFC; /* 640K */
121 }
122
123 /***********************************************************************
124  *           DOSMEM_InfoBlock
125  *
126  * Gets the DOS memory info block.
127  */
128 static dosmem_info *DOSMEM_InfoBlock(void)
129 {
130     /* Start of DOS conventional memory */
131     static char *DOSMEM_membase;
132
133     if (!DOSMEM_membase)
134     {
135         DWORD         reserve;
136
137         /*
138          * Reserve either:
139          * - lowest 64k for NULL pointer catching (Win16)
140          * - lowest 1k for interrupt handlers and 
141          *   another 0.5k for BIOS, DOS and intra-application
142          *   areas (DOS)
143          */
144         if (DOSMEM_dosmem != DOSMEM_sysmem)
145             reserve = DOSMEM_64KB;
146         else
147             reserve = 0x600; /* 1.5k */
148
149         /*
150          * Round to paragraph boundary in order to make 
151          * sure the alignment is correct.
152          */
153         reserve = ((reserve + 15) >> 4) << 4;
154
155         /*
156          * Set DOS memory base and initialize conventional memory.
157          */
158         DOSMEM_membase = DOSMEM_dosmem + reserve;
159         DOSMEM_InitMemory();
160     }
161
162     return (dosmem_info*)DOSMEM_membase;
163 }
164
165 /***********************************************************************
166  *           DOSMEM_RootBlock
167  *
168  * Gets the DOS memory root block.
169  */
170 static dosmem_entry *DOSMEM_RootBlock(void)
171 {
172     /* first block has to be paragraph-aligned */
173     return (dosmem_entry*)(((char*)DOSMEM_InfoBlock()) +
174                            ((((sizeof(dosmem_info) + 0xf) & ~0xf) - sizeof(dosmem_entry))));
175 }
176
177 /***********************************************************************
178  *           DOSMEM_FillIsrTable
179  *
180  * Fill the interrupt table with fake BIOS calls to BIOSSEG (0xf000).
181  *
182  * NOTES:
183  * Linux normally only traps INTs performed from or destined to BIOSSEG
184  * for us to handle, if the int_revectored table is empty. Filling the
185  * interrupt table with calls to INT stubs in BIOSSEG allows DOS programs
186  * to hook interrupts, as well as use their familiar retf tricks to call
187  * them, AND let Wine handle any unhooked interrupts transparently.
188  */
189 static void DOSMEM_FillIsrTable(void)
190 {
191     SEGPTR *isr = (SEGPTR*)DOSMEM_sysmem;
192     int x;
193
194     for (x=0; x<256; x++) isr[x]=MAKESEGPTR(VM_STUB_SEGMENT,x*4);
195 }
196
197 static void DOSMEM_MakeIsrStubs(void)
198 {
199     DWORD *stub = (DWORD*)(DOSMEM_dosmem + (VM_STUB_SEGMENT << 4));
200     int x;
201
202     for (x=0; x<256; x++) stub[x]=VM_STUB(x);
203 }
204
205 static BIOSDATA * DOSMEM_BiosData(void)
206 {
207     return (BIOSDATA *)(DOSMEM_sysmem + 0x400);
208 }
209
210 /**********************************************************************
211  *          DOSMEM_GetTicksSinceMidnight
212  *
213  * Return number of clock ticks since midnight.
214  */
215 static DWORD DOSMEM_GetTicksSinceMidnight(void)
216 {
217     SYSTEMTIME time;
218
219     /* This should give us the (approximately) correct
220      * 18.206 clock ticks per second since midnight.
221      */
222
223     GetLocalTime( &time );
224
225     return (((time.wHour * 3600 + time.wMinute * 60 +
226               time.wSecond) * 18206) / 1000) +
227              (time.wMilliseconds * 1000 / 54927);
228 }
229
230 /***********************************************************************
231  *           DOSMEM_FillBiosSegments
232  *
233  * Fill the BIOS data segment with dummy values.
234  */
235 static void DOSMEM_FillBiosSegments(void)
236 {
237     char *pBiosSys = DOSMEM_dosmem + 0xf0000;
238     BYTE *pBiosROMTable = pBiosSys+0xe6f5;
239     BIOSDATA *pBiosData = DOSMEM_BiosData();
240
241       /* Clear all unused values */
242     memset( pBiosData, 0, sizeof(*pBiosData) );
243
244     /* FIXME: should check the number of configured drives and ports */
245     pBiosData->Com1Addr             = 0x3f8;
246     pBiosData->Com2Addr             = 0x2f8;
247     pBiosData->Lpt1Addr             = 0x378;
248     pBiosData->Lpt2Addr             = 0x278;
249     pBiosData->InstalledHardware    = 0x5463;
250     pBiosData->MemSize              = 640;
251     pBiosData->NextKbdCharPtr       = 0x1e;
252     pBiosData->FirstKbdCharPtr      = 0x1e;
253     pBiosData->VideoMode            = 3;
254     pBiosData->VideoColumns         = 80;
255     pBiosData->VideoPageSize        = 80 * 25 * 2;
256     pBiosData->VideoPageStartAddr   = 0xb800;
257     pBiosData->VideoCtrlAddr        = 0x3d4;
258     pBiosData->Ticks                = DOSMEM_GetTicksSinceMidnight();
259     pBiosData->NbHardDisks          = 2;
260     pBiosData->KbdBufferStart       = 0x1e;
261     pBiosData->KbdBufferEnd         = 0x3e;
262     pBiosData->RowsOnScreenMinus1   = 24;
263     pBiosData->BytesPerChar         = 0x10;
264     pBiosData->ModeOptions          = 0x64;
265     pBiosData->FeatureBitsSwitches  = 0xf9;
266     pBiosData->VGASettings          = 0x51;
267     pBiosData->DisplayCombination   = 0x08;
268     pBiosData->DiskDataRate         = 0;
269
270     /* fill ROM configuration table (values from Award) */
271     *(pBiosROMTable+0x0)        = 0x08; /* number of bytes following LO */
272     *(pBiosROMTable+0x1)        = 0x00; /* number of bytes following HI */
273     *(pBiosROMTable+0x2)        = 0xfc; /* model */
274     *(pBiosROMTable+0x3)        = 0x01; /* submodel */
275     *(pBiosROMTable+0x4)        = 0x00; /* BIOS revision */
276     *(pBiosROMTable+0x5)        = 0x74; /* feature byte 1 */
277     *(pBiosROMTable+0x6)        = 0x00; /* feature byte 2 */
278     *(pBiosROMTable+0x7)        = 0x00; /* feature byte 3 */
279     *(pBiosROMTable+0x8)        = 0x00; /* feature byte 4 */
280     *(pBiosROMTable+0x9)        = 0x00; /* feature byte 5 */
281
282     /* BIOS date string */
283     strcpy(pBiosSys+0xfff5, "13/01/99");
284
285     /* BIOS ID */
286     *(pBiosSys+0xfffe) = 0xfc;
287
288     /* Reboot vector (f000:fff0 or ffff:0000) */
289     *(DWORD*)(pBiosSys + 0xfff0) = VM_STUB(0x19);
290 }
291
292 /***********************************************************************
293  *           DOSMEM_InitMemory
294  *
295  * Initialises the DOS memory structures.
296  */
297 static void DOSMEM_InitMemory(void)
298 {
299     dosmem_info*        info_block = DOSMEM_InfoBlock();
300     dosmem_entry*       root_block = DOSMEM_RootBlock();
301     dosmem_entry*       dm;
302
303     root_block->size = DOSMEM_MemoryTop() - (((char*)root_block) + sizeof(dosmem_entry));
304
305     info_block->blocks = 0;
306     info_block->free = root_block->size;
307
308     dm = NEXT_BLOCK(root_block);
309     dm->size = DM_BLOCK_TERMINAL;
310     root_block->size |= DM_BLOCK_FREE
311 #ifdef __DOSMEM_DEBUG__
312                      | DM_BLOCK_DEBUG
313 #endif
314                      ;
315
316     TRACE( "DOS conventional memory initialized, %d bytes free.\n", 
317            DOSMEM_Available() );
318 }
319
320 static void dosmem_bios_init(void)
321 {
322     static      int bios_created;
323
324     if (!bios_created)
325     {
326         DOSMEM_FillBiosSegments();
327         DOSMEM_FillIsrTable();
328         bios_created = TRUE;
329     }
330 }
331
332 /******************************************************************
333  *              dosmem_handler
334  *
335  * Handler to catch access to our 1MB address space reserved for real memory
336  */
337 static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except)
338 {
339     if (except->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
340     {
341         DWORD   addr = except->ExceptionRecord->ExceptionInformation[1];
342         if (addr >= (ULONG_PTR)DOSMEM_sysmem &&
343             addr < (ULONG_PTR)DOSMEM_sysmem + DOSMEM_64KB)
344         {
345             VirtualProtect( DOSMEM_sysmem, DOSMEM_64KB, PAGE_EXECUTE_READWRITE, NULL );
346             dosmem_bios_init();
347             return EXCEPTION_CONTINUE_EXECUTION;
348         }
349         if (addr >= (ULONG_PTR)DOSMEM_dosmem + DOSMEM_protect &&
350             addr < (ULONG_PTR)DOSMEM_dosmem + DOSMEM_SIZE)
351         {
352             VirtualProtect( DOSMEM_dosmem + DOSMEM_protect, DOSMEM_SIZE - DOSMEM_protect,
353                             PAGE_EXECUTE_READWRITE, NULL );
354             dosmem_bios_init();
355             return EXCEPTION_CONTINUE_EXECUTION;
356         }
357     }
358     return EXCEPTION_CONTINUE_SEARCH;
359 }
360
361 /**********************************************************************
362  *              setup_dos_mem
363  *
364  * Setup the first megabyte for DOS memory access
365  */
366 static void setup_dos_mem(void)
367 {
368     int sys_offset = 0;
369     int page_size = getpagesize();
370     void *addr = NULL;
371
372     if (wine_mmap_is_in_reserved_area( NULL, DOSMEM_SIZE ) != 1)
373     {
374         addr = wine_anon_mmap( (void *)page_size, DOSMEM_SIZE-page_size,
375                                PROT_READ | PROT_WRITE | PROT_EXEC, 0 );
376         if (addr == (void *)page_size) addr = NULL; /* we got what we wanted */
377         else munmap( addr, DOSMEM_SIZE - page_size );
378     }
379
380     if (!addr)
381     {
382         /* now reserve from address 0 */
383         wine_anon_mmap( NULL, DOSMEM_SIZE, 0, MAP_FIXED );
384
385         /* inform the memory manager that there is a mapping here, but don't commit yet */
386         VirtualAlloc( NULL, DOSMEM_SIZE, MEM_RESERVE | MEM_SYSTEM, PAGE_NOACCESS );
387         sys_offset = 0xf0000;
388         DOSMEM_protect = DOSMEM_64KB;
389     }
390     else
391     {
392         ERR("Cannot use first megabyte for DOS address space, please report\n" );
393         /* allocate the DOS area somewhere else */
394         addr = VirtualAlloc( NULL, DOSMEM_SIZE, MEM_RESERVE, PAGE_NOACCESS );
395         if (!addr)
396         {
397             ERR( "Cannot allocate DOS memory\n" );
398             ExitProcess(1);
399         }
400     }
401     DOSMEM_dosmem = addr;
402     DOSMEM_sysmem = (char*)addr + sys_offset;
403     RtlAddVectoredExceptionHandler(FALSE, dosmem_handler);
404 }
405
406
407 /***********************************************************************
408  *           DOSMEM_Init
409  *
410  * Create the dos memory segments, and store them into the KERNEL
411  * exported values.
412  */
413 BOOL DOSMEM_Init(void)
414 {
415     setup_dos_mem();
416
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                                             0x100, 0, WINE_LDT_FLAGS_DATA );
421     DOSMEM_BiosSysSeg = GLOBAL_CreateBlock(GMEM_FIXED,DOSMEM_dosmem + 0xf0000,
422                                            DOSMEM_64KB, 0, WINE_LDT_FLAGS_DATA );
423
424     return TRUE;
425 }
426
427 /******************************************************************
428  *              DOSMEM_InitDosMem
429  *
430  * Initialize the first MB of memory to look like a real DOS setup
431  */
432 BOOL DOSMEM_InitDosMem(void)
433 {
434     static int already_mapped;
435
436     if (!already_mapped)
437     {
438         if (DOSMEM_dosmem)
439         {
440             ERR( "Needs access to the first megabyte for DOS mode\n" );
441             ExitProcess(1);
442         }
443         MESSAGE( "Warning: unprotecting memory to allow real-mode calls.\n"
444                  "         NULL pointer accesses will no longer be caught.\n" );
445         VirtualProtect( NULL, DOSMEM_SIZE, PAGE_EXECUTE_READWRITE, NULL );
446         DOSMEM_protect = 0;
447         dosmem_bios_init();
448         /* copy the BIOS and ISR area down */
449         memcpy( DOSMEM_dosmem, DOSMEM_sysmem, 0x400 + 0x100 );
450         DOSMEM_sysmem = DOSMEM_dosmem;
451         SetSelectorBase( DOSMEM_0000H, 0 );
452         SetSelectorBase( DOSMEM_BiosDataSeg, 0x400 );
453         /* we may now need the actual interrupt stubs, and since we've just moved the
454          * interrupt vector table away, we can fill the area with stubs instead... */
455         DOSMEM_MakeIsrStubs();
456         already_mapped = 1;
457     }
458     return TRUE;
459 }
460
461
462 /***********************************************************************
463  *           DOSMEM_Tick
464  *
465  * Increment the BIOS tick counter. Called by timer signal handler.
466  */
467 void DOSMEM_Tick( WORD timer )
468 {
469     BIOSDATA *pBiosData = DOSMEM_BiosData();
470     if (pBiosData) pBiosData->Ticks++;
471 }
472
473 /***********************************************************************
474  *           DOSMEM_GetBlock
475  *
476  * Carve a chunk of the DOS memory block (without selector).
477  */
478 LPVOID DOSMEM_GetBlock(UINT size, UINT16* pseg)
479 {
480    UINT          blocksize;
481    char         *block = NULL;
482    dosmem_info  *info_block = DOSMEM_InfoBlock();
483    dosmem_entry *dm;
484 #ifdef __DOSMEM_DEBUG_
485    dosmem_entry *prev = NULL;
486 #endif
487
488    if( size > info_block->free ) return NULL;
489    dm = DOSMEM_RootBlock();
490
491    while (dm && dm->size != DM_BLOCK_TERMINAL)
492    {
493 #ifdef __DOSMEM_DEBUG__
494        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
495        {
496             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
497             return NULL;
498        }
499        prev = dm;
500 #endif
501        if( dm->size & DM_BLOCK_FREE )
502        {
503            dosmem_entry  *next = NEXT_BLOCK(dm);
504
505            while( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
506            {
507                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
508                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
509                next = NEXT_BLOCK(dm);
510            }
511
512            blocksize = dm->size & DM_BLOCK_MASK;
513            if( blocksize >= size )
514            {
515                block = ((char*)dm) + sizeof(dosmem_entry);
516                if( blocksize - size > 0x20 )
517                {
518                    /* split dm so that the next one stays
519                     * paragraph-aligned (and dm loses free bit) */
520
521                    dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
522                                               sizeof(dosmem_entry));
523                    next = (dosmem_entry*)(((char*)dm) +
524                            sizeof(dosmem_entry) + dm->size);
525                    next->size = (blocksize - (dm->size +
526                            sizeof(dosmem_entry))) | DM_BLOCK_FREE
527 #ifdef __DOSMEM_DEBUG__
528                                                   | DM_BLOCK_DEBUG
529 #endif
530                                                   ;
531                } else dm->size &= DM_BLOCK_MASK;
532
533                info_block->blocks++;
534                info_block->free -= dm->size;
535                if( pseg ) *pseg = (block - DOSMEM_dosmem) >> 4;
536 #ifdef __DOSMEM_DEBUG__
537                dm->size |= DM_BLOCK_DEBUG;
538 #endif
539                break;
540            }
541            dm = next;
542        }
543        else dm = NEXT_BLOCK(dm);
544    }
545    return (LPVOID)block;
546 }
547
548 /***********************************************************************
549  *           DOSMEM_FreeBlock
550  */
551 BOOL DOSMEM_FreeBlock(void* ptr)
552 {
553    dosmem_info  *info_block = DOSMEM_InfoBlock();
554
555    if( ptr >= (void*)(((char*)DOSMEM_RootBlock()) + sizeof(dosmem_entry)) &&
556        ptr < (void*)DOSMEM_MemoryTop() && !((((char*)ptr)
557                   - DOSMEM_dosmem) & 0xf) )
558    {
559        dosmem_entry  *dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
560
561        if( !(dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL))
562 #ifdef __DOSMEM_DEBUG__
563          && ((dm->size & DM_BLOCK_DEBUG) == DM_BLOCK_DEBUG )
564 #endif
565          )
566        {
567              info_block->blocks--;
568              info_block->free += dm->size;
569
570              dm->size |= DM_BLOCK_FREE;
571              return TRUE;
572        }
573    }
574    return FALSE;
575 }
576
577 /***********************************************************************
578  *           DOSMEM_ResizeBlock
579  *
580  * Resize DOS memory block in place. Returns block size or -1 on error.
581  *
582  * If exact is TRUE, returned value is either old or requested block
583  * size. If exact is FALSE, block is expanded even if there is not
584  * enough space for full requested block size.
585  */
586 UINT DOSMEM_ResizeBlock(void *ptr, UINT size, BOOL exact)
587 {
588    char         *block = NULL;
589    dosmem_info  *info_block = DOSMEM_InfoBlock();
590    dosmem_entry *dm;
591    dosmem_entry *next;
592    UINT blocksize;
593    UINT orgsize;
594
595    if( (ptr < (void*)(sizeof(dosmem_entry) + (char*)DOSMEM_RootBlock())) ||
596        (ptr >= (void*)DOSMEM_MemoryTop()) ||
597        (((((char*)ptr) - DOSMEM_dosmem) & 0xf) != 0) )
598      return (UINT)-1;
599
600    dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
601    if( dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL) )
602        return (UINT)-1;
603
604    next = NEXT_BLOCK(dm);
605    orgsize = dm->size & DM_BLOCK_MASK;
606
607    /* collapse free blocks */
608    while( next->size & DM_BLOCK_FREE )
609    {
610        dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
611        next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
612        next = NEXT_BLOCK(dm);
613    }
614
615    blocksize = dm->size & DM_BLOCK_MASK;
616
617    /*
618     * If collapse didn't help we either expand block to maximum
619     * available size (exact == FALSE) or give collapsed blocks
620     * back to free storage (exact == TRUE).
621     */
622    if (blocksize < size)
623        size = exact ? orgsize : blocksize;
624
625    block = ((char*)dm) + sizeof(dosmem_entry);
626    if( blocksize - size > 0x20 )
627    {
628        /*
629         * split dm so that the next one stays
630         * paragraph-aligned (and next gains free bit) 
631         */
632
633        dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
634                    sizeof(dosmem_entry));
635        next = (dosmem_entry*)(((char*)dm) +
636                               sizeof(dosmem_entry) + dm->size);
637        next->size = (blocksize - (dm->size +
638                                   sizeof(dosmem_entry))) | DM_BLOCK_FREE;
639    } 
640    else 
641    {
642        dm->size &= DM_BLOCK_MASK;
643    }
644
645    /*
646     * Adjust available memory if block size changes.
647     */
648    info_block->free += orgsize - dm->size;
649
650    return size;
651 }
652
653 /***********************************************************************
654  *           DOSMEM_Available
655  */
656 UINT DOSMEM_Available(void)
657 {
658    UINT          blocksize, available = 0;
659    dosmem_entry *dm;
660
661    dm = DOSMEM_RootBlock();
662
663    while (dm && dm->size != DM_BLOCK_TERMINAL)
664    {
665 #ifdef __DOSMEM_DEBUG__
666        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
667        {
668             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
669             return NULL;
670        }
671        prev = dm;
672 #endif
673        if( dm->size & DM_BLOCK_FREE )
674        {
675            dosmem_entry  *next = NEXT_BLOCK(dm);
676
677            while( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
678            {
679                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
680                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
681                next = NEXT_BLOCK(dm);
682            }
683
684            blocksize = dm->size & DM_BLOCK_MASK;
685            if ( blocksize > available ) available = blocksize;
686            dm = next;
687        }
688        else dm = NEXT_BLOCK(dm);
689    }
690    return available;
691 }
692
693
694 /***********************************************************************
695  *           DOSMEM_MapLinearToDos
696  *
697  * Linear address to the DOS address space.
698  */
699 UINT DOSMEM_MapLinearToDos(LPVOID ptr)
700 {
701     if (((char*)ptr >= DOSMEM_dosmem) &&
702         ((char*)ptr < DOSMEM_dosmem + DOSMEM_SIZE))
703           return (UINT)ptr - (UINT)DOSMEM_dosmem;
704     return (UINT)ptr;
705 }
706
707
708 /***********************************************************************
709  *           DOSMEM_MapDosToLinear
710  *
711  * DOS linear address to the linear address space.
712  */
713 LPVOID DOSMEM_MapDosToLinear(UINT ptr)
714 {
715     if (ptr < DOSMEM_SIZE) return (LPVOID)(ptr + (UINT)DOSMEM_dosmem);
716     return (LPVOID)ptr;
717 }
718
719
720 /***********************************************************************
721  *           DOSMEM_MapRealToLinear
722  *
723  * Real mode DOS address into a linear pointer
724  */
725 LPVOID DOSMEM_MapRealToLinear(DWORD x)
726 {
727    LPVOID       lin;
728
729    lin = DOSMEM_dosmem + HIWORD(x) * 16 + LOWORD(x);
730    TRACE_(selector)("(0x%08lx) returns %p.\n", x, lin );
731    return lin;
732 }
733
734 /***********************************************************************
735  *           DOSMEM_AllocSelector
736  *
737  * Allocates a protected mode selector for a realmode segment.
738  */
739 WORD DOSMEM_AllocSelector(WORD realsel)
740 {
741         HMODULE16 hModule = GetModuleHandle16("KERNEL");
742         WORD    sel;
743
744         sel=GLOBAL_CreateBlock( GMEM_FIXED, DOSMEM_dosmem+realsel*16, DOSMEM_64KB,
745                                 hModule, WINE_LDT_FLAGS_DATA );
746         TRACE_(selector)("(0x%04x) returns 0x%04x.\n", realsel,sel);
747         return sel;
748 }