Release 980315
[wine] / msdos / dosmem.c
1 /*
2  * DOS memory emulation
3  *
4  * Copyright 1995 Alexandre Julliard
5  * Copyright 1996 Marcus Meissner
6  */
7
8 #include <signal.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include "windows.h"
13 #include "winbase.h"
14 #include "global.h"
15 #include "ldt.h"
16 #include "miscemu.h"
17 #include "module.h"
18 #include "debug.h"
19
20 HANDLE16 DOSMEM_BiosSeg;  /* BIOS data segment at 0x40:0 */
21
22 #pragma pack(1)
23
24 typedef struct
25 {
26     WORD  Com1Addr;                  /* 00: COM1 I/O address */
27     WORD  Com2Addr;                  /* 02: COM2 I/O address */
28     WORD  Com3Addr;                  /* 04: COM3 I/O address */
29     WORD  Com4Addr;                  /* 06: COM4 I/O address */
30     WORD  Lpt1Addr;                  /* 08: LPT1 I/O address */
31     WORD  Lpt2Addr;                  /* 0a: LPT2 I/O address */
32     WORD  Lpt3Addr;                  /* 0c: LPT3 I/O address */
33     WORD  Lpt4Addr;                  /* 0e: LPT4 I/O address */
34     WORD  InstalledHardware;         /* 10: Installed hardware flags */
35     BYTE  POSTstatus;                /* 12: Power-On Self Test status */
36     WORD  MemSize WINE_PACKED;       /* 13: Base memory size in Kb */
37     WORD  unused1 WINE_PACKED;       /* 15: Manufacturing test scratch pad */
38     BYTE  KbdFlags1;                 /* 17: Keyboard flags 1 */
39     BYTE  KbdFlags2;                 /* 18: Keyboard flags 2 */
40     BYTE  unused2;                   /* 19: Keyboard driver workspace */
41     WORD  NextKbdCharPtr;            /* 1a: Next character in kbd buffer */
42     WORD  FirstKbdCharPtr;           /* 1c: First character in kbd buffer */
43     WORD  KbdBuffer[16];             /* 1e: Keyboard buffer */
44     BYTE  DisketteStatus1;           /* 3e: Diskette recalibrate status */
45     BYTE  DisketteStatus2;           /* 3f: Diskette motor status */
46     BYTE  DisketteStatus3;           /* 40: Diskette motor timeout */
47     BYTE  DisketteStatus4;           /* 41: Diskette last operation status */
48     BYTE  DiskStatus[7];             /* 42: Disk status/command bytes */
49     BYTE  VideoMode;                 /* 49: Video mode */
50     WORD  VideoColumns;              /* 4a: Number of columns */
51     WORD  VideoPageSize;             /* 4c: Video page size in bytes */
52     WORD  VideoPageStartAddr;        /* 4e: Video page start address */
53     BYTE  VideoCursorPos[16];        /* 50: Cursor position for 8 pages */
54     WORD  VideoCursorType;           /* 60: Video cursor type */
55     BYTE  VideoCurPage;              /* 62: Video current page */
56     WORD  VideoCtrlAddr WINE_PACKED; /* 63: Video controller address */
57     BYTE  VideoReg1;                 /* 65: Video mode select register */
58     BYTE  VideoReg2;                 /* 66: Video CGA palette register */
59     DWORD ResetEntry WINE_PACKED;    /* 67: Warm reset entry point */
60     BYTE  LastIRQ;                   /* 6b: Last unexpected interrupt */
61     DWORD Ticks;                     /* 6c: Ticks since midnight */
62     BYTE  TicksOverflow;             /* 70: Timer overflow if past midnight */
63     BYTE  CtrlBreakFlag;             /* 71: Ctrl-Break flag */
64     WORD  ResetFlag;                 /* 72: POST Reset flag */
65     BYTE  DiskOpStatus;              /* 74: Last hard-disk operation status */
66     BYTE  NbHardDisks;               /* 75: Number of hard disks */
67     BYTE  DiskCtrlByte;              /* 76: Disk control byte */
68     BYTE  DiskIOPort;                /* 77: Disk I/O port offset */
69     BYTE  LptTimeout[4];             /* 78: Timeouts for parallel ports */
70     BYTE  ComTimeout[4];             /* 7c: Timeouts for serial ports */
71     WORD  KbdBufferStart;            /* 80: Keyboard buffer start */
72     WORD  KbdBufferEnd;              /* 82: Keyboard buffer end */
73 } BIOSDATA;
74
75 #pragma pack(4)
76
77 static BIOSDATA *pBiosData = NULL;
78 static char     *DOSMEM_dosmem;
79 static char     *DOSMEM_top;
80
81        DWORD     DOSMEM_CollateTable;
82
83 /* use 2 low bits of 'size' for the housekeeping */
84
85 #define DM_BLOCK_DEBUG          0xABE00000
86 #define DM_BLOCK_TERMINAL       0x00000001
87 #define DM_BLOCK_FREE           0x00000002
88 #define DM_BLOCK_MASK           0x001FFFFC
89
90 /*
91 #define __DOSMEM_DEBUG__
92  */
93
94 typedef struct {
95    unsigned     size;
96 } dosmem_entry;
97
98 typedef struct {
99   unsigned      blocks;
100   unsigned      free;
101 } dosmem_info;
102
103 static dosmem_entry*    root_block = NULL;
104 static dosmem_info*     info_block = NULL;
105
106 #define NEXT_BLOCK(block) \
107         (dosmem_entry*)(((char*)(block)) + \
108          sizeof(dosmem_entry) + ((block)->size & DM_BLOCK_MASK))
109
110 /***********************************************************************
111  *           DOSMEM_FillBiosSegment
112  *
113  * Fill the BIOS data segment with dummy values.
114  */
115 static void DOSMEM_FillBiosSegment(void)
116 {
117     pBiosData = (BIOSDATA *)GlobalLock16( DOSMEM_BiosSeg );
118
119       /* Clear all unused values */
120     memset( pBiosData, 0, sizeof(*pBiosData) );
121
122     /* FIXME: should check the number of configured drives and ports */
123
124     pBiosData->Com1Addr             = 0x3e8;
125     pBiosData->Com2Addr             = 0x2e8;
126     pBiosData->Lpt1Addr             = 0x378;
127     pBiosData->Lpt2Addr             = 0x278;
128     pBiosData->InstalledHardware    = 0x8443;
129     pBiosData->MemSize              = 640;
130     pBiosData->NextKbdCharPtr       = 0x1e;
131     pBiosData->FirstKbdCharPtr      = 0x1e;
132     pBiosData->VideoMode            = 0;
133     pBiosData->VideoColumns         = 80;
134     pBiosData->VideoPageSize        = 80 * 25 * 2;
135     pBiosData->VideoPageStartAddr   = 0xb800;
136     pBiosData->VideoCtrlAddr        = 0x3d4;
137     pBiosData->Ticks                = INT1A_GetTicksSinceMidnight();
138     pBiosData->NbHardDisks          = 2;
139     pBiosData->KbdBufferStart       = 0x1e;
140     pBiosData->KbdBufferEnd         = 0x3e;
141 }
142
143 /***********************************************************************
144  *           DOSMEM_InitCollateTable
145  *
146  * Initialises the collate table (character sorting, language dependent)
147  */
148 static void DOSMEM_InitCollateTable()
149 {
150         DWORD           x;
151         unsigned char   *tbl;
152         int             i;
153
154         x = GlobalDOSAlloc(258);
155         DOSMEM_CollateTable = MAKELONG(0,(x>>16));
156         tbl = DOSMEM_MapRealToLinear(DOSMEM_CollateTable);
157         *(WORD*)tbl     = 0x100;
158         tbl += 2;
159         for ( i = 0; i < 0x100; i++) *tbl++ = i;
160 }
161
162 /***********************************************************************
163  *           DOSMEM_InitMemory
164  *
165  * Initialises the DOS memory structures.
166  */
167 static void DOSMEM_InitMemory()
168 {
169    /* Low 64Kb are reserved for DOS/BIOS so the useable area starts at
170     * 1000:0000 and ends at 9FFF:FFEF. */
171
172     dosmem_entry*       dm;
173
174     DOSMEM_top = DOSMEM_dosmem+0x9FFFC; /* 640K */
175     info_block = (dosmem_info*)( DOSMEM_dosmem + 0x10000 );
176
177     /* first block has to be paragraph-aligned relative to the DOSMEM_dosmem */
178
179     root_block = (dosmem_entry*)( DOSMEM_dosmem + 0x10000 +
180                  ((((sizeof(dosmem_info) + 0xf) & ~0xf) - sizeof(dosmem_entry))));
181     root_block->size = DOSMEM_top - (((char*)root_block) + sizeof(dosmem_entry));
182
183     info_block->blocks = 0;
184     info_block->free = root_block->size;
185
186     dm = NEXT_BLOCK(root_block);
187     dm->size = DM_BLOCK_TERMINAL;
188     root_block->size |= DM_BLOCK_FREE 
189 #ifdef __DOSMEM_DEBUG__
190                      | DM_BLOCK_DEBUG;
191 #endif
192                      ;
193 }
194
195 /***********************************************************************
196  *           DOSMEM_Init
197  *
198  * Create the dos memory segments, and store them into the KERNEL
199  * exported values.
200  */
201 BOOL32 DOSMEM_Init(void)
202 {
203     /* Allocate 1 MB dosmemory 
204      * - it is mostly wasted but we use can some of it to 
205      *   store internal translation tables, etc...
206      */
207     DOSMEM_dosmem = VirtualAlloc( NULL, 0x100000, MEM_COMMIT,
208                                   PAGE_EXECUTE_READWRITE );
209     if (!DOSMEM_dosmem)
210     {
211         fprintf( stderr, "Could not allocate DOS memory.\n" );
212         return FALSE;
213     }
214     DOSMEM_BiosSeg = GLOBAL_CreateBlock(GMEM_FIXED,DOSMEM_dosmem+0x400,0x100,
215                                         0, FALSE, FALSE, FALSE, NULL );
216     DOSMEM_FillBiosSegment();
217     DOSMEM_InitMemory();
218     DOSMEM_InitCollateTable();
219     return TRUE;
220 }
221
222 void DOSMEM_InitExports(HMODULE16 hKernel)
223 {
224 #define SET_ENTRY_POINT(num,addr) \
225     MODULE_SetEntryPoint( hKernel, (num), GLOBAL_CreateBlock( GMEM_FIXED, \
226                                   DOSMEM_dosmem+(addr), 0x10000, hKernel, \
227                                   FALSE, FALSE, FALSE, NULL ))
228
229     SET_ENTRY_POINT( 183, 0x00000 );  /* KERNEL.183: __0000H */
230     SET_ENTRY_POINT( 174, 0xa0000 );  /* KERNEL.174: __A000H */
231     SET_ENTRY_POINT( 181, 0xb0000 );  /* KERNEL.181: __B000H */
232     SET_ENTRY_POINT( 182, 0xb8000 );  /* KERNEL.182: __B800H */
233     SET_ENTRY_POINT( 195, 0xc0000 );  /* KERNEL.195: __C000H */
234     SET_ENTRY_POINT( 179, 0xd0000 );  /* KERNEL.179: __D000H */
235     SET_ENTRY_POINT( 190, 0xe0000 );  /* KERNEL.190: __E000H */
236     SET_ENTRY_POINT( 173, 0xf0000 );  /* KERNEL.173: __ROMBIOS */
237     SET_ENTRY_POINT( 194, 0xf0000 );  /* KERNEL.194: __F000H */
238     MODULE_SetEntryPoint(hKernel, 193,DOSMEM_BiosSeg); /* KERNEL.193: __0040H */
239
240 #undef SET_ENTRY_POINT
241 }
242
243 /***********************************************************************
244  *           DOSMEM_Tick
245  *
246  * Increment the BIOS tick counter. Called by timer signal handler.
247  */
248 void DOSMEM_Tick(void)
249 {
250     if (pBiosData) pBiosData->Ticks++;
251 }
252
253 /***********************************************************************
254  *           DOSMEM_GetBlock
255  *
256  * Carve a chunk of the DOS memory block (without selector).
257  */
258 LPVOID DOSMEM_GetBlock(UINT32 size, UINT16* pseg)
259 {
260    UINT32        blocksize;
261    char         *block = NULL;
262    dosmem_entry *dm;
263 #ifdef __DOSMEM_DEBUG_
264    dosmem_entry *prev = NULL;
265 #endif
266  
267    if( size > info_block->free ) return NULL;
268    dm = root_block;
269
270    while (dm && dm->size != DM_BLOCK_TERMINAL)
271    {
272 #ifdef __DOSMEM_DEBUG__
273        if( (dm->size & DM_BLOCK_DEBUG) != DM_BLOCK_DEBUG )
274        {
275             fprintf(stderr,"DOSMEM_GetBlock: MCB overrun! [prev = 0x%08x]\n", 4 + (UINT32)prev);
276             return NULL;
277        }
278        prev = dm;
279 #endif
280        if( dm->size & DM_BLOCK_FREE )
281        {
282            dosmem_entry  *next = NEXT_BLOCK(dm);
283
284            while( next->size & DM_BLOCK_FREE ) /* collapse free blocks */
285            {
286                dm->size += sizeof(dosmem_entry) + (next->size & DM_BLOCK_MASK);
287                next->size = (DM_BLOCK_FREE | DM_BLOCK_TERMINAL);
288                next = NEXT_BLOCK(dm);
289            }
290
291            blocksize = dm->size & DM_BLOCK_MASK;
292            if( blocksize >= size )
293            {
294                block = ((char*)dm) + sizeof(dosmem_entry);
295                if( blocksize - size > 0x20 )
296                {
297                    /* split dm so that the next one stays
298                     * paragraph-aligned (and dm loses free bit) */
299
300                    dm->size = (((size + 0xf + sizeof(dosmem_entry)) & ~0xf) -
301                                               sizeof(dosmem_entry));
302                    next = (dosmem_entry*)(((char*)dm) + 
303                            sizeof(dosmem_entry) + dm->size);
304                    next->size = (blocksize - (dm->size + 
305                            sizeof(dosmem_entry))) | DM_BLOCK_FREE 
306 #ifdef __DOSMEM_DEBUG__
307                                                   | DM_BLOCK_DEBUG
308 #endif
309                                                   ;
310                } else dm->size &= DM_BLOCK_MASK;
311
312                info_block->blocks++;
313                info_block->free -= dm->size;
314                if( pseg ) *pseg = (block - DOSMEM_dosmem) >> 4;
315 #ifdef __DOSMEM_DEBUG__
316                dm->size |= DM_BLOCK_DEBUG;
317 #endif
318                break;
319            }
320            dm = next;
321        }
322        else dm = NEXT_BLOCK(dm);
323    }
324    return (LPVOID)block;
325 }
326
327 /***********************************************************************
328  *           DOSMEM_FreeBlock
329  */
330 BOOL32 DOSMEM_FreeBlock(void* ptr)
331 {
332    if( ptr >= (void*)(((char*)root_block) + sizeof(dosmem_entry)) &&
333        ptr < (void*)DOSMEM_top && !((((char*)ptr) - DOSMEM_dosmem) & 0xf) )
334    {
335        dosmem_entry  *dm = (dosmem_entry*)(((char*)ptr) - sizeof(dosmem_entry));
336
337        if( !(dm->size & (DM_BLOCK_FREE | DM_BLOCK_TERMINAL))
338 #ifdef __DOSMEM_DEBUG__
339          && ((dm->size & DM_BLOCK_DEBUG) == DM_BLOCK_DEBUG )
340 #endif
341          )
342        {
343              info_block->blocks--;
344              info_block->free += dm->size;
345
346              dm->size |= DM_BLOCK_FREE;
347              return TRUE;
348        }
349    }
350    return FALSE;
351 }
352
353
354 /***********************************************************************
355  *           DOSMEM_MapLinearToDos
356  *
357  * Linear address to the DOS address space.
358  */
359 UINT32 DOSMEM_MapLinearToDos(LPVOID ptr)
360 {
361     if (((char*)ptr >= DOSMEM_dosmem) &&
362         ((char*)ptr < DOSMEM_dosmem + 0x100000))
363           return (UINT32)ptr - (UINT32)DOSMEM_dosmem;
364     return (UINT32)ptr;
365 }
366
367
368 /***********************************************************************
369  *           DOSMEM_MapDosToLinear
370  *
371  * DOS linear address to the linear address space.
372  */
373 LPVOID DOSMEM_MapDosToLinear(UINT32 ptr)
374 {
375     if (ptr < 0x100000) return (LPVOID)(ptr + (UINT32)DOSMEM_dosmem);
376     return (LPVOID)ptr;
377 }
378
379
380 /***********************************************************************
381  *           DOSMEM_MapRealToLinear
382  *
383  * Real mode DOS address into a linear pointer
384  */
385 LPVOID DOSMEM_MapRealToLinear(DWORD x)
386 {
387    LPVOID       lin;
388
389    lin=DOSMEM_dosmem+(x&0xffff)+(((x&0xffff0000)>>16)*16);
390    TRACE(selector,"(0x%08lx) returns 0x%p.\n",
391                     x,lin );
392    return lin;
393 }
394
395 /***********************************************************************
396  *           DOSMEM_AllocSelector
397  *
398  * Allocates a protected mode selector for a realmode segment.
399  */
400 WORD DOSMEM_AllocSelector(WORD realsel)
401 {
402         HMODULE16 hModule = GetModuleHandle16("KERNEL");
403         WORD    sel;
404
405         sel=GLOBAL_CreateBlock(
406                 GMEM_FIXED,DOSMEM_dosmem+realsel*16,0x10000,
407                 hModule,FALSE,FALSE,FALSE,NULL
408         );
409         TRACE(selector,"(0x%04x) returns 0x%04x.\n",
410                 realsel,sel
411         );
412         return sel;
413 }
414