Avoid a crash in free_window_handle() on an invalid window handle.
[wine] / dlls / winedos / 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 "dosexe.h"
42 #include "wine/debug.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(dosmem);
45
46 /* DOS memory highest address (including HMA) */
47 #define DOSMEM_SIZE             0x110000
48 #define DOSMEM_64KB             0x10000
49
50 /* see dlls/kernel/dosmem.c for the details */
51 static char *DOSMEM_dosmem;
52 static char *DOSMEM_sysmem;
53
54 /* use 2 low bits of 'size' for the housekeeping */
55 #define DM_BLOCK_DEBUG          0xABE00000
56 #define DM_BLOCK_TERMINAL       0x00000001
57 #define DM_BLOCK_FREE           0x00000002
58 #define DM_BLOCK_MASK           0x001FFFFC
59
60 /*
61 #define __DOSMEM_DEBUG__
62  */
63
64 typedef struct {
65    unsigned     size;
66 } dosmem_entry;
67
68 typedef struct {
69   unsigned      blocks;
70   unsigned      free;
71 } dosmem_info;
72
73 static inline dosmem_entry* next_block(dosmem_entry* block)
74 {
75     return (dosmem_entry*)((char*)block +
76                            sizeof(dosmem_entry) + (block->size & DM_BLOCK_MASK));
77 }
78
79 #define VM_STUB(x) (0x90CF00CD|(x<<8)) /* INT x; IRET; NOP */
80 #define VM_STUB_SEGMENT 0xf000         /* BIOS segment */
81
82 /* FIXME: this should be moved to the LOL, and the whole allocation strategy
83  * should use real MCB
84  */
85 static dosmem_info* DOSMEM_info_block;
86
87 /***********************************************************************
88  *           DOSMEM_MemoryTop
89  *
90  * Gets the DOS memory top.
91  */
92 static char *DOSMEM_MemoryTop(void)
93 {
94     return DOSMEM_dosmem+0x9FFFC; /* 640K */
95 }
96
97 /***********************************************************************
98  *           DOSMEM_RootBlock
99  *
100  * Gets the DOS memory root block.
101  */
102 static dosmem_entry *DOSMEM_RootBlock(void)
103 {
104     /* first block has to be paragraph-aligned */
105     return (dosmem_entry*)(((char*)DOSMEM_info_block) +
106                            ((((sizeof(dosmem_info) + 0xf) & ~0xf) - sizeof(dosmem_entry))));
107 }
108
109 /***********************************************************************
110  *           DOSMEM_FillIsrTable
111  *
112  * Fill the interrupt table with fake BIOS calls to BIOSSEG (0xf000).
113  *
114  * NOTES:
115  * Linux normally only traps INTs performed from or destined to BIOSSEG
116  * for us to handle, if the int_revectored table is empty. Filling the
117  * interrupt table with calls to INT stubs in BIOSSEG allows DOS programs
118  * to hook interrupts, as well as use their familiar retf tricks to call
119  * them, AND let Wine handle any unhooked interrupts transparently.
120  */
121 static void DOSMEM_FillIsrTable(void)
122 {
123     SEGPTR *isr = (SEGPTR*)DOSMEM_sysmem;
124     int x;
125
126     for (x=0; x<256; x++) isr[x]=MAKESEGPTR(VM_STUB_SEGMENT,x*4);
127 }
128
129 static void DOSMEM_MakeIsrStubs(void)
130 {
131     DWORD *stub = (DWORD*)(DOSMEM_dosmem + (VM_STUB_SEGMENT << 4));
132     int x;
133
134     for (x=0; x<256; x++) stub[x]=VM_STUB(x);
135 }
136
137 BIOSDATA* DOSVM_BiosData(void)
138 {
139     return (BIOSDATA *)(DOSMEM_sysmem + 0x400);
140 }
141
142 /**********************************************************************
143  *          DOSMEM_GetTicksSinceMidnight
144  *
145  * Return number of clock ticks since midnight.
146  */
147 static DWORD DOSMEM_GetTicksSinceMidnight(void)
148 {
149     SYSTEMTIME time;
150
151     /* This should give us the (approximately) correct
152      * 18.206 clock ticks per second since midnight.
153      */
154
155     GetLocalTime( &time );
156
157     return (((time.wHour * 3600 + time.wMinute * 60 +
158               time.wSecond) * 18206) / 1000) +
159              (time.wMilliseconds * 1000 / 54927);
160 }
161
162 /***********************************************************************
163  *           DOSMEM_FillBiosSegments
164  *
165  * Fill the BIOS data segment with dummy values.
166  */
167 static void DOSMEM_FillBiosSegments(void)
168 {
169     char *pBiosSys = DOSMEM_dosmem + 0xf0000;
170     BYTE *pBiosROMTable = pBiosSys+0xe6f5;
171     BIOSDATA *pBiosData = DOSVM_BiosData();
172
173       /* Clear all unused values */
174     memset( pBiosData, 0, sizeof(*pBiosData) );
175
176     /* FIXME: should check the number of configured drives and ports */
177     pBiosData->Com1Addr             = 0x3f8;
178     pBiosData->Com2Addr             = 0x2f8;
179     pBiosData->Lpt1Addr             = 0x378;
180     pBiosData->Lpt2Addr             = 0x278;
181     pBiosData->InstalledHardware    = 0x5463;
182     pBiosData->MemSize              = 640;
183     pBiosData->NextKbdCharPtr       = 0x1e;
184     pBiosData->FirstKbdCharPtr      = 0x1e;
185     pBiosData->VideoMode            = 3;
186     pBiosData->VideoColumns         = 80;
187     pBiosData->VideoPageSize        = 80 * 25 * 2;
188     pBiosData->VideoPageStartAddr   = 0xb800;
189     pBiosData->VideoCtrlAddr        = 0x3d4;
190     pBiosData->Ticks                = DOSMEM_GetTicksSinceMidnight();
191     pBiosData->NbHardDisks          = 2;
192     pBiosData->KbdBufferStart       = 0x1e;
193     pBiosData->KbdBufferEnd         = 0x3e;
194     pBiosData->RowsOnScreenMinus1   = 24;
195     pBiosData->BytesPerChar         = 0x10;
196     pBiosData->ModeOptions          = 0x64;
197     pBiosData->FeatureBitsSwitches  = 0xf9;
198     pBiosData->VGASettings          = 0x51;
199     pBiosData->DisplayCombination   = 0x08;
200     pBiosData->DiskDataRate         = 0;
201
202     /* fill ROM configuration table (values from Award) */
203     *(pBiosROMTable+0x0)        = 0x08; /* number of bytes following LO */
204     *(pBiosROMTable+0x1)        = 0x00; /* number of bytes following HI */
205     *(pBiosROMTable+0x2)        = 0xfc; /* model */
206     *(pBiosROMTable+0x3)        = 0x01; /* submodel */
207     *(pBiosROMTable+0x4)        = 0x00; /* BIOS revision */
208     *(pBiosROMTable+0x5)        = 0x74; /* feature byte 1 */
209     *(pBiosROMTable+0x6)        = 0x00; /* feature byte 2 */
210     *(pBiosROMTable+0x7)        = 0x00; /* feature byte 3 */
211     *(pBiosROMTable+0x8)        = 0x00; /* feature byte 4 */
212     *(pBiosROMTable+0x9)        = 0x00; /* feature byte 5 */
213
214     /* BIOS date string */
215     strcpy(pBiosSys+0xfff5, "13/01/99");
216
217     /* BIOS ID */
218     *(pBiosSys+0xfffe) = 0xfc;
219
220     /* Reboot vector (f000:fff0 or ffff:0000) */
221     *(DWORD*)(pBiosSys + 0xfff0) = VM_STUB(0x19);
222 }
223
224 /***********************************************************************
225  *           BiosTick
226  *
227  * Increment the BIOS tick counter. Called by timer signal handler.
228  */
229 void BiosTick( WORD timer )
230 {
231     BIOSDATA *pBiosData = DOSVM_BiosData();
232     if (pBiosData) pBiosData->Ticks++;
233 }
234
235 /***********************************************************************
236  *           DOSMEM_AllocBlock
237  *
238  * Carve a chunk of the DOS memory block (without selector).
239  */
240 LPVOID DOSMEM_AllocBlock(UINT size, UINT16* pseg)
241 {
242    UINT          blocksize;
243    char         *block = NULL;
244    dosmem_info  *info_block = DOSMEM_info_block;
245    dosmem_entry *dm;
246 #ifdef __DOSMEM_DEBUG_
247    dosmem_entry *prev = NULL;
248 #endif
249
250    if( size > info_block->free ) return NULL;
251    dm = DOSMEM_RootBlock();
252
253    while (dm && dm->size != DM_BLOCK_TERMINAL)
254    {
255 #ifdef __DOSMEM_DEBUG__
256        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
257        {
258             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
259             return NULL;
260        }
261        prev = dm;
262 #endif
263        if( dm->size & DM_BLOCK_FREE )
264        {
265            dosmem_entry  *next = next_block(dm);
266
267            while ( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
268            {
269                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
270                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
271                next = next_block(dm);
272            }
273
274            blocksize = dm->size & DM_BLOCK_MASK;
275            if( blocksize >= size )
276            {
277                block = ((char*)dm) + sizeof(dosmem_entry);
278                if( blocksize - size > 0x20 )
279                {
280                    /* split dm so that the next one stays
281                     * paragraph-aligned (and dm loses free bit) */
282
283                    dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
284                                               sizeof(dosmem_entry));
285                    next = (dosmem_entry*)(((char*)dm) +
286                            sizeof(dosmem_entry) + dm->size);
287                    next->size = (blocksize - (dm->size +
288                            sizeof(dosmem_entry))) | DM_BLOCK_FREE
289 #ifdef __DOSMEM_DEBUG__
290                                                   | DM_BLOCK_DEBUG
291 #endif
292                                                   ;
293                } else dm->size &= DM_BLOCK_MASK;
294
295                info_block->blocks++;
296                info_block->free -= dm->size;
297                if( pseg ) *pseg = (block - DOSMEM_dosmem) >> 4;
298 #ifdef __DOSMEM_DEBUG__
299                dm->size |= DM_BLOCK_DEBUG;
300 #endif
301                break;
302            }
303            dm = next;
304        }
305        else dm = next_block(dm);
306    }
307    return (LPVOID)block;
308 }
309
310 /***********************************************************************
311  *           DOSMEM_FreeBlock
312  */
313 BOOL DOSMEM_FreeBlock(void* ptr)
314 {
315    dosmem_info  *info_block = DOSMEM_info_block;
316
317    if( ptr >= (void*)(((char*)DOSMEM_RootBlock()) + sizeof(dosmem_entry)) &&
318        ptr < (void*)DOSMEM_MemoryTop() && !((((char*)ptr)
319                   - DOSMEM_dosmem) & 0xf) )
320    {
321        dosmem_entry  *dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
322
323        if( !(dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL))
324 #ifdef __DOSMEM_DEBUG__
325          && ((dm->size & DM_BLOCK_DEBUG) == DM_BLOCK_DEBUG )
326 #endif
327          )
328        {
329              info_block->blocks--;
330              info_block->free += dm->size;
331
332              dm->size |= DM_BLOCK_FREE;
333              return TRUE;
334        }
335    }
336    return FALSE;
337 }
338
339 /***********************************************************************
340  *           DOSMEM_ResizeBlock
341  *
342  * Resize DOS memory block in place. Returns block size or -1 on error.
343  *
344  * If exact is TRUE, returned value is either old or requested block
345  * size. If exact is FALSE, block is expanded even if there is not
346  * enough space for full requested block size.
347  */
348 UINT DOSMEM_ResizeBlock(void *ptr, UINT size, BOOL exact)
349 {
350    char         *block = NULL;
351    dosmem_info  *info_block = DOSMEM_info_block;
352    dosmem_entry *dm;
353    dosmem_entry *next;
354    UINT blocksize;
355    UINT orgsize;
356
357    if( (ptr < (void*)(sizeof(dosmem_entry) + (char*)DOSMEM_RootBlock())) ||
358        (ptr >= (void*)DOSMEM_MemoryTop()) ||
359        (((((char*)ptr) - DOSMEM_dosmem) & 0xf) != 0) )
360      return (UINT)-1;
361
362    dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
363    if( dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL) )
364        return (UINT)-1;
365
366    next = next_block(dm);
367    orgsize = dm->size & DM_BLOCK_MASK;
368
369    /* collapse free blocks */
370    while ( next->size & DM_BLOCK_FREE )
371    {
372        dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
373        next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
374        next = next_block(dm);
375    }
376
377    blocksize = dm->size & DM_BLOCK_MASK;
378
379    /*
380     * If collapse didn't help we either expand block to maximum
381     * available size (exact == FALSE) or give collapsed blocks
382     * back to free storage (exact == TRUE).
383     */
384    if (blocksize < size)
385        size = exact ? orgsize : blocksize;
386
387    block = ((char*)dm) + sizeof(dosmem_entry);
388    if( blocksize - size > 0x20 )
389    {
390        /*
391         * split dm so that the next one stays
392         * paragraph-aligned (and next gains free bit) 
393         */
394
395        dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
396                    sizeof(dosmem_entry));
397        next = (dosmem_entry*)(((char*)dm) +
398                               sizeof(dosmem_entry) + dm->size);
399        next->size = (blocksize - (dm->size +
400                                   sizeof(dosmem_entry))) | DM_BLOCK_FREE;
401    } 
402    else 
403    {
404        dm->size &= DM_BLOCK_MASK;
405    }
406
407    /*
408     * Adjust available memory if block size changes.
409     */
410    info_block->free += orgsize - dm->size;
411
412    return size;
413 }
414
415 /***********************************************************************
416  *           DOSMEM_Available
417  */
418 UINT DOSMEM_Available(void)
419 {
420    UINT          blocksize, available = 0;
421    dosmem_entry *dm;
422
423    dm = DOSMEM_RootBlock();
424
425    while (dm && dm->size != DM_BLOCK_TERMINAL)
426    {
427 #ifdef __DOSMEM_DEBUG__
428        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
429        {
430             WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
431             return NULL;
432        }
433        prev = dm;
434 #endif
435        if( dm->size & DM_BLOCK_FREE )
436        {
437            dosmem_entry  *next = next_block(dm);
438
439            while ( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
440            {
441                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
442                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
443                next = next_block(dm);
444            }
445
446            blocksize = dm->size & DM_BLOCK_MASK;
447            if ( blocksize > available ) available = blocksize;
448            dm = next;
449        }
450        else dm = next_block(dm);
451    }
452    return available;
453 }
454
455 /***********************************************************************
456  *           DOSMEM_InitMemory
457  *
458  * Initialises the DOS memory structures.
459  */
460 static void DOSMEM_InitMemory(char* addr)
461 {
462     dosmem_entry*       root_block;
463     dosmem_entry*       dm;
464
465     DOSMEM_FillBiosSegments();
466     DOSMEM_FillIsrTable();
467
468     DOSMEM_info_block = (dosmem_info*)addr;
469     root_block = DOSMEM_RootBlock();
470     root_block->size = DOSMEM_MemoryTop() - (((char*)root_block) + sizeof(dosmem_entry));
471
472     DOSMEM_info_block->blocks = 0;
473     DOSMEM_info_block->free = root_block->size;
474
475     dm = next_block(root_block);
476     dm->size = DM_BLOCK_TERMINAL;
477     root_block->size |= DM_BLOCK_FREE
478 #ifdef __DOSMEM_DEBUG__
479         | DM_BLOCK_DEBUG
480 #endif
481         ;
482
483     TRACE("DOS conventional memory initialized, %d bytes free.\n",
484           DOSMEM_Available());
485 }
486
487 /******************************************************************
488  *             DOSMEM_InitDosMemory
489  *
490  * When WineDOS is loaded, initializes the current DOS memory layout.
491  */
492 void DOSMEM_InitDosMemory(void)
493 {
494     HMODULE16           hModule;
495     unsigned short      sel;
496     LDT_ENTRY           entry;
497     DWORD               reserve;
498
499     hModule = GetModuleHandle16("KERNEL");
500     /* KERNEL.194: __F000H */
501     sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(ULONG_PTR)194));
502     wine_ldt_get_entry(sel, &entry);
503     DOSMEM_dosmem = (char*)wine_ldt_get_base(&entry) - 0xF0000;
504     /* KERNEL.183: __0000H */
505     sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)183));
506     wine_ldt_get_entry(sel, &entry);
507     DOSMEM_sysmem = wine_ldt_get_base(&entry);
508
509     /*
510      * Reserve either:
511      * - lowest 64k for NULL pointer catching (Win16)
512      * - lowest 1k for interrupt handlers and
513      *   another 0.5k for BIOS, DOS and intra-application
514      *   areas (DOS)
515      */
516     if (DOSMEM_dosmem != DOSMEM_sysmem)
517         reserve = 0x10000; /* 64k */
518     else
519         reserve = 0x600; /* 1.5k */
520
521     /*
522      * Round to paragraph boundary in order to make
523      * sure the alignment is correct.
524      */
525     reserve = ((reserve + 15) >> 4) << 4;
526
527     /*
528      * Set DOS memory base and initialize conventional memory.
529      */
530     DOSMEM_InitMemory(DOSMEM_dosmem + reserve);
531 }
532
533 /******************************************************************
534  *              DOSMEM_MapDosLayout
535  *
536  * Initialize the first MB of memory to look like a real DOS setup
537  */
538 BOOL DOSMEM_MapDosLayout(void)
539 {
540     static int already_mapped;
541
542     if (!already_mapped)
543     {
544         HMODULE16       hModule;
545         unsigned short  sel;
546         LDT_ENTRY       entry;
547
548         if (DOSMEM_dosmem)
549         {
550             ERR( "Needs access to the first megabyte for DOS mode\n" );
551             ExitProcess(1);
552         }
553         MESSAGE( "Warning: unprotecting memory to allow real-mode calls.\n"
554                  "         NULL pointer accesses will no longer be caught.\n" );
555         VirtualProtect( NULL, DOSMEM_SIZE, PAGE_EXECUTE_READWRITE, NULL );
556         /* copy the BIOS and ISR area down */
557         memcpy( DOSMEM_dosmem, DOSMEM_sysmem, 0x400 + 0x100 );
558         DOSMEM_sysmem = DOSMEM_dosmem;
559         hModule = GetModuleHandle16("KERNEL");
560         /* selector to 0000H */
561         sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)183));
562         wine_ldt_get_entry(sel, &entry);
563         wine_ldt_set_base(&entry, NULL);
564         wine_ldt_set_entry(sel, &entry);
565         /* selector to BiosData */
566         sel = LOWORD(GetProcAddress16(hModule, (LPCSTR)(DWORD_PTR)193));
567         wine_ldt_get_entry(sel, &entry);
568         wine_ldt_set_base(&entry, (const void*)0x400);
569         wine_ldt_set_entry(sel, &entry);
570         /* we may now need the actual interrupt stubs, and since we've just moved the
571          * interrupt vector table away, we can fill the area with stubs instead... */
572         DOSMEM_MakeIsrStubs();
573         already_mapped = 1;
574     }
575     return TRUE;
576 }