4 * Copyright 1995 Alexandre Julliard
5 * Copyright 1996 Marcus Meissner
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.
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.
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
23 #include "wine/port.h"
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_MMAN_H
31 # include <sys/mman.h>
39 #include "wine/winbase16.h"
41 #include "kernel_private.h"
44 #include "wine/debug.h"
46 WINE_DEFAULT_DEBUG_CHANNEL(dosmem);
47 WINE_DECLARE_DEBUG_CHANNEL(selector);
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 */
53 /* DOS memory highest address (including HMA) */
54 #define DOSMEM_SIZE 0x110000
55 #define DOSMEM_64KB 0x10000
57 /* use 2 low bits of 'size' for the housekeeping */
59 #define DM_BLOCK_DEBUG 0xABE00000
60 #define DM_BLOCK_TERMINAL 0x00000001
61 #define DM_BLOCK_FREE 0x00000002
62 #define DM_BLOCK_MASK 0x001FFFFC
65 #define __DOSMEM_DEBUG__
77 #define NEXT_BLOCK(block) \
78 (dosmem_entry*)(((char*)(block)) + \
79 sizeof(dosmem_entry) + ((block)->size & DM_BLOCK_MASK))
81 #define VM_STUB(x) (0x90CF00CD|(x<<8)) /* INT x; IRET; NOP */
82 #define VM_STUB_SEGMENT 0xf000 /* BIOS segment */
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).
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;
111 static void DOSMEM_InitMemory(void);
113 /***********************************************************************
116 * Gets the DOS memory top.
118 static char *DOSMEM_MemoryTop(void)
120 return DOSMEM_dosmem+0x9FFFC; /* 640K */
123 /***********************************************************************
126 * Gets the DOS memory info block.
128 static dosmem_info *DOSMEM_InfoBlock(void)
130 /* Start of DOS conventional memory */
131 static char *DOSMEM_membase;
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
144 if (DOSMEM_dosmem != DOSMEM_sysmem)
145 reserve = DOSMEM_64KB;
147 reserve = 0x600; /* 1.5k */
150 * Round to paragraph boundary in order to make
151 * sure the alignment is correct.
153 reserve = ((reserve + 15) >> 4) << 4;
156 * Set DOS memory base and initialize conventional memory.
158 DOSMEM_membase = DOSMEM_dosmem + reserve;
162 return (dosmem_info*)DOSMEM_membase;
165 /***********************************************************************
168 * Gets the DOS memory root block.
170 static dosmem_entry *DOSMEM_RootBlock(void)
172 /* first block has to be paragraph-aligned */
173 return (dosmem_entry*)(((char*)DOSMEM_InfoBlock()) +
174 ((((sizeof(dosmem_info) + 0xf) & ~0xf) - sizeof(dosmem_entry))));
177 /***********************************************************************
178 * DOSMEM_FillIsrTable
180 * Fill the interrupt table with fake BIOS calls to BIOSSEG (0xf000).
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.
189 static void DOSMEM_FillIsrTable(void)
191 SEGPTR *isr = (SEGPTR*)DOSMEM_sysmem;
194 for (x=0; x<256; x++) isr[x]=MAKESEGPTR(VM_STUB_SEGMENT,x*4);
197 static void DOSMEM_MakeIsrStubs(void)
199 DWORD *stub = (DWORD*)(DOSMEM_dosmem + (VM_STUB_SEGMENT << 4));
202 for (x=0; x<256; x++) stub[x]=VM_STUB(x);
205 static BIOSDATA * DOSMEM_BiosData(void)
207 return (BIOSDATA *)(DOSMEM_sysmem + 0x400);
210 /**********************************************************************
211 * DOSMEM_GetTicksSinceMidnight
213 * Return number of clock ticks since midnight.
215 static DWORD DOSMEM_GetTicksSinceMidnight(void)
219 /* This should give us the (approximately) correct
220 * 18.206 clock ticks per second since midnight.
223 GetLocalTime( &time );
225 return (((time.wHour * 3600 + time.wMinute * 60 +
226 time.wSecond) * 18206) / 1000) +
227 (time.wMilliseconds * 1000 / 54927);
230 /***********************************************************************
231 * DOSMEM_FillBiosSegments
233 * Fill the BIOS data segment with dummy values.
235 static void DOSMEM_FillBiosSegments(void)
237 char *pBiosSys = DOSMEM_dosmem + 0xf0000;
238 BYTE *pBiosROMTable = pBiosSys+0xe6f5;
239 BIOSDATA *pBiosData = DOSMEM_BiosData();
241 /* Clear all unused values */
242 memset( pBiosData, 0, sizeof(*pBiosData) );
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;
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 */
282 /* BIOS date string */
283 strcpy(pBiosSys+0xfff5, "13/01/99");
286 *(pBiosSys+0xfffe) = 0xfc;
288 /* Reboot vector (f000:fff0 or ffff:0000) */
289 *(DWORD*)(pBiosSys + 0xfff0) = VM_STUB(0x19);
292 /***********************************************************************
295 * Initialises the DOS memory structures.
297 static void DOSMEM_InitMemory(void)
299 dosmem_info* info_block = DOSMEM_InfoBlock();
300 dosmem_entry* root_block = DOSMEM_RootBlock();
303 root_block->size = DOSMEM_MemoryTop() - (((char*)root_block) + sizeof(dosmem_entry));
305 info_block->blocks = 0;
306 info_block->free = root_block->size;
308 dm = NEXT_BLOCK(root_block);
309 dm->size = DM_BLOCK_TERMINAL;
310 root_block->size |= DM_BLOCK_FREE
311 #ifdef __DOSMEM_DEBUG__
316 TRACE( "DOS conventional memory initialized, %d bytes free.\n",
317 DOSMEM_Available() );
320 static void dosmem_bios_init(void)
322 static int bios_created;
326 DOSMEM_FillBiosSegments();
327 DOSMEM_FillIsrTable();
332 /******************************************************************
335 * Handler to catch access to our 1MB address space reserved for real memory
337 static LONG WINAPI dosmem_handler(EXCEPTION_POINTERS* except)
339 if (except->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
341 DWORD addr = except->ExceptionRecord->ExceptionInformation[1];
342 if (addr >= (ULONG_PTR)DOSMEM_sysmem &&
343 addr < (ULONG_PTR)DOSMEM_sysmem + DOSMEM_64KB)
345 VirtualProtect( DOSMEM_sysmem, DOSMEM_64KB, PAGE_EXECUTE_READWRITE, NULL );
347 return EXCEPTION_CONTINUE_EXECUTION;
349 if (addr >= (ULONG_PTR)DOSMEM_dosmem + DOSMEM_protect &&
350 addr < (ULONG_PTR)DOSMEM_dosmem + DOSMEM_SIZE)
352 VirtualProtect( DOSMEM_dosmem + DOSMEM_protect, DOSMEM_SIZE - DOSMEM_protect,
353 PAGE_EXECUTE_READWRITE, NULL );
355 return EXCEPTION_CONTINUE_EXECUTION;
358 return EXCEPTION_CONTINUE_SEARCH;
361 /**********************************************************************
364 * Setup the first megabyte for DOS memory access
366 static void setup_dos_mem(void)
369 int page_size = getpagesize();
372 if (wine_mmap_is_in_reserved_area( NULL, DOSMEM_SIZE ) != 1)
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 );
382 /* now reserve from address 0 */
383 wine_anon_mmap( NULL, DOSMEM_SIZE, 0, MAP_FIXED );
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;
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 );
397 ERR( "Cannot allocate DOS memory\n" );
401 DOSMEM_dosmem = addr;
402 DOSMEM_sysmem = (char*)addr + sys_offset;
403 RtlAddVectoredExceptionHandler(FALSE, dosmem_handler);
407 /***********************************************************************
410 * Create the dos memory segments, and store them into the KERNEL
413 BOOL DOSMEM_Init(void)
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 );
427 /******************************************************************
430 * Initialize the first MB of memory to look like a real DOS setup
432 BOOL DOSMEM_InitDosMem(void)
434 static int already_mapped;
440 ERR( "Needs access to the first megabyte for DOS mode\n" );
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 );
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();
462 /***********************************************************************
465 * Increment the BIOS tick counter. Called by timer signal handler.
467 void DOSMEM_Tick( WORD timer )
469 BIOSDATA *pBiosData = DOSMEM_BiosData();
470 if (pBiosData) pBiosData->Ticks++;
473 /***********************************************************************
476 * Carve a chunk of the DOS memory block (without selector).
478 LPVOID DOSMEM_GetBlock(UINT size, UINT16* pseg)
482 dosmem_info *info_block = DOSMEM_InfoBlock();
484 #ifdef __DOSMEM_DEBUG_
485 dosmem_entry *prev = NULL;
488 if( size > info_block->free ) return NULL;
489 dm = DOSMEM_RootBlock();
491 while (dm && dm->size != DM_BLOCK_TERMINAL)
493 #ifdef __DOSMEM_DEBUG__
494 if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
496 WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
501 if( dm->size & DM_BLOCK_FREE )
503 dosmem_entry *next = NEXT_BLOCK(dm);
505 while( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
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);
512 blocksize = dm->size & DM_BLOCK_MASK;
513 if( blocksize >= size )
515 block = ((char*)dm) + sizeof(dosmem_entry);
516 if( blocksize - size > 0x20 )
518 /* split dm so that the next one stays
519 * paragraph-aligned (and dm loses free bit) */
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__
531 } else dm->size &= DM_BLOCK_MASK;
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;
543 else dm = NEXT_BLOCK(dm);
545 return (LPVOID)block;
548 /***********************************************************************
551 BOOL DOSMEM_FreeBlock(void* ptr)
553 dosmem_info *info_block = DOSMEM_InfoBlock();
555 if( ptr >= (void*)(((char*)DOSMEM_RootBlock()) + sizeof(dosmem_entry)) &&
556 ptr < (void*)DOSMEM_MemoryTop() && !((((char*)ptr)
557 - DOSMEM_dosmem) & 0xf) )
559 dosmem_entry *dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
561 if( !(dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL))
562 #ifdef __DOSMEM_DEBUG__
563 && ((dm->size & DM_BLOCK_DEBUG) == DM_BLOCK_DEBUG )
567 info_block->blocks--;
568 info_block->free += dm->size;
570 dm->size |= DM_BLOCK_FREE;
577 /***********************************************************************
580 * Resize DOS memory block in place. Returns block size or -1 on error.
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.
586 UINT DOSMEM_ResizeBlock(void *ptr, UINT size, BOOL exact)
589 dosmem_info *info_block = DOSMEM_InfoBlock();
595 if( (ptr < (void*)(sizeof(dosmem_entry) + (char*)DOSMEM_RootBlock())) ||
596 (ptr >= (void*)DOSMEM_MemoryTop()) ||
597 (((((char*)ptr) - DOSMEM_dosmem) & 0xf) != 0) )
600 dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
601 if( dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL) )
604 next = NEXT_BLOCK(dm);
605 orgsize = dm->size & DM_BLOCK_MASK;
607 /* collapse free blocks */
608 while( next->size & DM_BLOCK_FREE )
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);
615 blocksize = dm->size & DM_BLOCK_MASK;
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).
622 if (blocksize < size)
623 size = exact ? orgsize : blocksize;
625 block = ((char*)dm) + sizeof(dosmem_entry);
626 if( blocksize - size > 0x20 )
629 * split dm so that the next one stays
630 * paragraph-aligned (and next gains free bit)
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;
642 dm->size &= DM_BLOCK_MASK;
646 * Adjust available memory if block size changes.
648 info_block->free += orgsize - dm->size;
653 /***********************************************************************
656 UINT DOSMEM_Available(void)
658 UINT blocksize, available = 0;
661 dm = DOSMEM_RootBlock();
663 while (dm && dm->size != DM_BLOCK_TERMINAL)
665 #ifdef __DOSMEM_DEBUG__
666 if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
668 WARN("MCB overrun! [prev = 0x%08x]\n", 4 + (UINT)prev);
673 if( dm->size & DM_BLOCK_FREE )
675 dosmem_entry *next = NEXT_BLOCK(dm);
677 while( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
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);
684 blocksize = dm->size & DM_BLOCK_MASK;
685 if ( blocksize > available ) available = blocksize;
688 else dm = NEXT_BLOCK(dm);
694 /***********************************************************************
695 * DOSMEM_MapLinearToDos
697 * Linear address to the DOS address space.
699 UINT DOSMEM_MapLinearToDos(LPVOID ptr)
701 if (((char*)ptr >= DOSMEM_dosmem) &&
702 ((char*)ptr < DOSMEM_dosmem + DOSMEM_SIZE))
703 return (UINT)ptr - (UINT)DOSMEM_dosmem;
708 /***********************************************************************
709 * DOSMEM_MapDosToLinear
711 * DOS linear address to the linear address space.
713 LPVOID DOSMEM_MapDosToLinear(UINT ptr)
715 if (ptr < DOSMEM_SIZE) return (LPVOID)(ptr + (UINT)DOSMEM_dosmem);
720 /***********************************************************************
721 * DOSMEM_MapRealToLinear
723 * Real mode DOS address into a linear pointer
725 LPVOID DOSMEM_MapRealToLinear(DWORD x)
729 lin = DOSMEM_dosmem + HIWORD(x) * 16 + LOWORD(x);
730 TRACE_(selector)("(0x%08lx) returns %p.\n", x, lin );
734 /***********************************************************************
735 * DOSMEM_AllocSelector
737 * Allocates a protected mode selector for a realmode segment.
739 WORD DOSMEM_AllocSelector(WORD realsel)
741 HMODULE16 hModule = GetModuleHandle16("KERNEL");
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);