Release 970101
[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
21 HANDLE16 DOSMEM_BiosSeg;  /* BIOS data segment at 0x40:0 */
22
23
24 #pragma pack(1)
25
26 typedef struct
27 {
28     WORD  Com1Addr;                  /* 00: COM1 I/O address */
29     WORD  Com2Addr;                  /* 02: COM2 I/O address */
30     WORD  Com3Addr;                  /* 04: COM3 I/O address */
31     WORD  Com4Addr;                  /* 06: COM4 I/O address */
32     WORD  Lpt1Addr;                  /* 08: LPT1 I/O address */
33     WORD  Lpt2Addr;                  /* 0a: LPT2 I/O address */
34     WORD  Lpt3Addr;                  /* 0c: LPT3 I/O address */
35     WORD  Lpt4Addr;                  /* 0e: LPT4 I/O address */
36     WORD  InstalledHardware;         /* 10: Installed hardware flags */
37     BYTE  POSTstatus;                /* 12: Power-On Self Test status */
38     WORD  MemSize WINE_PACKED;       /* 13: Base memory size in Kb */
39     WORD  unused1 WINE_PACKED;       /* 15: Manufacturing test scratch pad */
40     BYTE  KbdFlags1;                 /* 17: Keyboard flags 1 */
41     BYTE  KbdFlags2;                 /* 18: Keyboard flags 2 */
42     BYTE  unused2;                   /* 19: Keyboard driver workspace */
43     WORD  NextKbdCharPtr;            /* 1a: Next character in kbd buffer */
44     WORD  FirstKbdCharPtr;           /* 1c: First character in kbd buffer */
45     WORD  KbdBuffer[16];             /* 1e: Keyboard buffer */
46     BYTE  DisketteStatus1;           /* 3e: Diskette recalibrate status */
47     BYTE  DisketteStatus2;           /* 3f: Diskette motor status */
48     BYTE  DisketteStatus3;           /* 40: Diskette motor timeout */
49     BYTE  DisketteStatus4;           /* 41: Diskette last operation status */
50     BYTE  DiskStatus[7];             /* 42: Disk status/command bytes */
51     BYTE  VideoMode;                 /* 49: Video mode */
52     WORD  VideoColumns;              /* 4a: Number of columns */
53     WORD  VideoPageSize;             /* 4c: Video page size in bytes */
54     WORD  VideoPageStartAddr;        /* 4e: Video page start address */
55     BYTE  VideoCursorPos[16];        /* 50: Cursor position for 8 pages */
56     WORD  VideoCursorType;           /* 60: Video cursor type */
57     BYTE  VideoCurPage;              /* 62: Video current page */
58     WORD  VideoCtrlAddr WINE_PACKED; /* 63: Video controller address */
59     BYTE  VideoReg1;                 /* 65: Video mode select register */
60     BYTE  VideoReg2;                 /* 66: Video CGA palette register */
61     DWORD ResetEntry WINE_PACKED;    /* 67: Warm reset entry point */
62     BYTE  LastIRQ;                   /* 6b: Last unexpected interrupt */
63     DWORD Ticks;                     /* 6c: Ticks since midnight */
64     BYTE  TicksOverflow;             /* 70: Timer overflow if past midnight */
65     BYTE  CtrlBreakFlag;             /* 71: Ctrl-Break flag */
66     WORD  ResetFlag;                 /* 72: POST Reset flag */
67     BYTE  DiskOpStatus;              /* 74: Last hard-disk operation status */
68     BYTE  NbHardDisks;               /* 75: Number of hard disks */
69     BYTE  DiskCtrlByte;              /* 76: Disk control byte */
70     BYTE  DiskIOPort;                /* 77: Disk I/O port offset */
71     BYTE  LptTimeout[4];             /* 78: Timeouts for parallel ports */
72     BYTE  ComTimeout[4];             /* 7c: Timeouts for serial ports */
73     WORD  KbdBufferStart;            /* 80: Keyboard buffer start */
74     WORD  KbdBufferEnd;              /* 82: Keyboard buffer end */
75 } BIOSDATA;
76
77 #pragma pack(4)
78
79
80 static BIOSDATA *pBiosData = NULL;
81 char    *DOSMEM_dosmem;
82 struct dosmem_entry {
83         struct  dosmem_entry    *next;
84         BYTE                    isfree;
85 };
86
87
88 /***********************************************************************
89  *           DOSMEM_InitCollateTable
90  *
91  * Initialises the collate table (character sorting, language dependend)
92  */
93 DWORD DOSMEM_CollateTable;
94
95 static void DOSMEM_InitCollateTable()
96 {
97         DWORD           x;
98         unsigned char   *tbl;
99         int             i;
100
101         x=GlobalDOSAlloc(258);
102         DOSMEM_CollateTable=MAKELONG(0,(x>>16));
103         tbl=DOSMEM_RealMode2Linear(DOSMEM_CollateTable);
104         *(WORD*)tbl     = 0x100;
105         tbl+=2;
106         for (i=0;i<0x100;i++)
107                 *tbl++=i;
108 }
109
110
111 /***********************************************************************
112  *           DOSMEM_Init
113  *
114  * Create the dos memory segments, and store them into the KERNEL
115  * exported values.
116  */
117 BOOL32 DOSMEM_Init(void)
118 {
119     /* Allocate 1 MB dosmemory */
120     /* Yes, allocating 1 MB of memory, which is usually not even used, is a 
121      * waste of memory. But I (MM) don't see any easy method to use 
122      * GlobalDOS{Alloc,Free} within an area of memory, with protected mode
123      * selectors pointing into it, and the possibilty, that the userprogram
124      * calls SetSelectorBase(,physical_address_in_DOSMEM); that includes 
125      * dynamical enlarging (reallocing) the dosmem area.
126      * Yes, one could walk the ldt_copy on every realloc() on DOSMEM, but
127      * this feels more like a hack to me than this current implementation is.
128      * If you find another, better, method, just change it. -Marcus Meissner
129      */
130     DOSMEM_dosmem = VirtualAlloc(NULL,0x1000000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
131     if (!DOSMEM_dosmem)
132     {
133         fprintf( stderr, "Could not allocate DOS memory.\n" );
134         return FALSE;
135     }
136     DOSMEM_BiosSeg = GLOBAL_CreateBlock(GMEM_FIXED,DOSMEM_dosmem+0x400,0x100,
137                                         0, FALSE, FALSE, FALSE, NULL );
138     DOSMEM_FillBiosSegment();
139     DOSMEM_InitMemoryHandling();
140     DOSMEM_InitCollateTable();
141     return TRUE;
142 }
143
144 /***********************************************************************
145  *           DOSMEM_InitMemoryHandling
146  *
147  * Initialises the DOS Memory structures.
148  */
149 void
150 DOSMEM_InitMemoryHandling()
151 {
152     struct      dosmem_entry    *dm;
153
154     dm = (struct dosmem_entry*)(DOSMEM_dosmem+0x10000);
155     dm->isfree  =  1;
156     dm->next    =  (struct dosmem_entry*)(DOSMEM_dosmem+0x9FFF0);
157     dm          =  dm->next;
158     dm->isfree  = 0;
159     dm->next    = NULL;
160 }
161
162 /***********************************************************************
163  *           DOSMEM_Tick
164  *
165  * Increment the BIOS tick counter. Called by timer signal handler.
166  */
167 void DOSMEM_Tick(void)
168 {
169     if (pBiosData) pBiosData->Ticks++;
170 }
171
172
173 /***********************************************************************
174  *           DOSMEM_FillBiosSegment
175  *
176  * Fill the BIOS data segment with dummy values.
177  */
178 void DOSMEM_FillBiosSegment(void)
179 {
180     pBiosData = (BIOSDATA *)GlobalLock16( DOSMEM_BiosSeg );
181
182       /* Clear all unused values */
183     memset( pBiosData, 0, sizeof(*pBiosData) );
184
185     /* FIXME: should check the number of configured drives and ports */
186
187     pBiosData->Com1Addr             = 0x3e8;
188     pBiosData->Com2Addr             = 0x2e8;
189     pBiosData->Lpt1Addr             = 0x378;
190     pBiosData->Lpt2Addr             = 0x278;
191     pBiosData->InstalledHardware    = 0x8443;
192     pBiosData->MemSize              = 640;
193     pBiosData->NextKbdCharPtr       = 0x1e;
194     pBiosData->FirstKbdCharPtr      = 0x1e;
195     pBiosData->VideoMode            = 0;
196     pBiosData->VideoColumns         = 80;
197     pBiosData->VideoPageSize        = 80 * 25 * 2;
198     pBiosData->VideoPageStartAddr   = 0xb800;
199     pBiosData->VideoCtrlAddr        = 0x3d4;
200     pBiosData->Ticks                = INT1A_GetTicksSinceMidnight();
201     pBiosData->NbHardDisks          = 2;
202     pBiosData->KbdBufferStart       = 0x1e;
203     pBiosData->KbdBufferEnd         = 0x3e;
204 }
205
206 /***********************************************************************
207  *           GlobalDOSAlloc     (KERNEL.184)
208  *
209  * Allocates a piece of DOS Memory, in the first 1 MB physical memory.
210  * 
211  * operates on the preallocated DOSMEM_dosmem (1MB). The useable area
212  * starts at 1000:0000 and ends at 9FFF:FFEF
213  * Memory allocation strategy is First Fit. (FIXME: Yes,I know that First Fit
214  * is a rather bad strategy. But since those functions are rather seldom
215  * called, it's easyness fits the purpose well.)
216  * 
217  */
218
219 DWORD GlobalDOSAlloc(DWORD size)
220 {
221         struct  dosmem_entry    *dm,*ndm;
222         DWORD   start,blocksize;
223         WORD    sel;
224         HMODULE16 hModule=GetModuleHandle("KERNEL");
225
226
227         start   = 0;
228         dm      = (struct dosmem_entry*)(DOSMEM_dosmem+0x10000);
229         size    = (size+0xf)&~0xf;
230         while (dm && dm->next) {
231                 blocksize = ((char*)dm->next-(char*)dm)-16;
232                 if ((dm->isfree) && (blocksize>=size)) {
233                         dm->isfree = 0;
234                         start = ((((char*)dm)-DOSMEM_dosmem)+0x10)& ~0xf;
235                         if ((blocksize-size) >= 0x20) {
236                                 /* if enough memory is left for a new block
237                                  * split this area into two blocks
238                                  */
239                                 ndm=(struct dosmem_entry*)((char*)dm+0x10+size);
240                                 ndm->isfree     = 1;
241                                 ndm->next       = dm->next;
242                                 dm->next        = ndm;
243                         }
244                         break;
245                 }
246                 dm=dm->next;
247         }
248         if (!start)
249                 return 0;
250         sel=GLOBAL_CreateBlock(
251                 GMEM_FIXED,DOSMEM_dosmem+start,size,
252                 hModule,FALSE,FALSE,FALSE,NULL
253         );
254         return MAKELONG(sel,start>>4);
255 }
256
257 /***********************************************************************
258  *           GlobalDOSFree      (KERNEL.185)
259  *
260  * Frees allocated dosmemory and corresponding selector.
261  */
262
263 WORD
264 GlobalDOSFree(WORD sel)
265 {
266         DWORD   base;
267         struct  dosmem_entry    *dm;
268
269         base = GetSelectorBase(sel);
270         /* base has already been conversed to a physical address */
271         if (base>=0x100000)
272                 return sel;
273         dm      = (struct dosmem_entry*)(DOSMEM_dosmem+base-0x10);
274         if (dm->isfree) {
275                 fprintf(stderr,"Freeing already freed DOSMEM.\n");
276                 return 0;
277         }
278         dm->isfree = 1;
279
280         /* collapse adjunct free blocks into one */
281         dm = (struct dosmem_entry*)(DOSMEM_dosmem+0x10000);
282         while (dm && dm->next) {
283                 if (dm->isfree && dm->next->isfree)
284                         dm->next = dm->next->next;
285                 dm = dm->next;
286         }
287         GLOBAL_FreeBlock(sel);
288         return 0;
289 }
290
291 /***********************************************************************
292  *           DOSMEM_RealMode2Linear
293  *
294  * Converts a realmode segment:offset address into a linear pointer
295  */
296 LPVOID DOSMEM_RealMode2Linear(DWORD x)
297 {
298         LPVOID  lin;
299
300         lin=DOSMEM_dosmem+(x&0xffff)+(((x&0xffff0000)>>16)*16);
301         dprintf_selector(stddeb,"DOSMEM_RealMode2Linear(0x%08lx) returns 0x%p.\n",
302                 x,lin
303         );
304         return lin;
305 }
306
307 /***********************************************************************
308  *           DOSMEM_AllocSelector
309  *
310  * Allocates a protected mode selector for a realmode segment.
311  */
312 WORD DOSMEM_AllocSelector(WORD realsel)
313 {
314         HMODULE16 hModule=GetModuleHandle("KERNEL");
315         WORD    sel;
316
317         sel=GLOBAL_CreateBlock(
318                 GMEM_FIXED,DOSMEM_dosmem+realsel*16,0x10000,
319                 hModule,FALSE,FALSE,FALSE,NULL
320         );
321         dprintf_selector(stddeb,"DOSMEM_AllocSelector(0x%04x) returns 0x%04x.\n",
322                 realsel,sel
323         );
324         return sel;
325 }