816141abf28199256e42052221608ebc1a9727b6
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dos32krnl / himem.c
1 /*
2 * COPYRIGHT: GPLv2+ - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: himem.c
5 * PURPOSE: DOS XMS Driver
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "ntvdm.h"
14 #include "emulator.h"
15 #include "cpu/bop.h"
16 #include "io.h"
17 #include "hardware/ps2.h"
18
19 #include "dos.h"
20 #include "dos/dem.h"
21 #include "device.h"
22 #include "himem.h"
23
24 #define XMS_DEVICE_NAME "XMSXXXX0"
25
26 /* BOP Identifiers */
27 #define BOP_XMS 0x52
28
29 /* PRIVATE VARIABLES **********************************************************/
30
31 static const BYTE EntryProcedure[] = {
32 0xEB, // jmp short +0x03
33 0x03,
34 0x90, // nop
35 0x90, // nop
36 0x90, // nop
37 LOBYTE(EMULATOR_BOP),
38 HIBYTE(EMULATOR_BOP),
39 BOP_XMS,
40 0xCB // retf
41 };
42
43 static PDOS_DEVICE_NODE Node = NULL;
44 static XMS_HANDLE HandleTable[XMS_MAX_HANDLES];
45 static WORD FreeBlocks = XMS_BLOCKS;
46 static RTL_BITMAP AllocBitmap;
47 static ULONG BitmapBuffer[(XMS_BLOCKS + 31) / 32];
48
49 /*
50 * Flag used by Global Enable/Disable A20 functions, so that they don't
51 * need to re-change the state of A20 if it was already enabled/disabled.
52 */
53 static BOOLEAN IsA20Enabled = FALSE;
54 /*
55 * This flag is set to TRUE or FALSE when A20 line was already disabled or
56 * enabled when XMS driver was loaded.
57 * In case A20 was disabled, we are allowed to modify it. In case A20 was
58 * already enabled, we are not allowed to touch it.
59 */
60 static BOOLEAN CanChangeA20 = TRUE;
61 /*
62 * Count for enabling or disabling the A20 line. The A20 line is enabled
63 * only if the enabling count is greater than or equal to 0.
64 */
65 static LONG A20EnableCount = 0;
66
67 /* HELPERS FOR A20 LINE *******************************************************/
68
69 static BOOLEAN PCAT_A20Control(BYTE Control, PBOOLEAN A20Status)
70 {
71 BYTE ControllerOutput;
72
73 /* Retrieve PS/2 controller output byte */
74 IOWriteB(PS2_CONTROL_PORT, 0xD0);
75 ControllerOutput = IOReadB(PS2_DATA_PORT);
76
77 switch (Control)
78 {
79 case 0: /* Disable A20 line */
80 ControllerOutput &= ~0x02;
81 IOWriteB(PS2_CONTROL_PORT, 0xD1);
82 IOWriteB(PS2_DATA_PORT, ControllerOutput);
83 break;
84
85 case 1: /* Enable A20 line */
86 ControllerOutput |= 0x02;
87 IOWriteB(PS2_CONTROL_PORT, 0xD1);
88 IOWriteB(PS2_DATA_PORT, ControllerOutput);
89 break;
90
91 default: /* Get A20 status */
92 break;
93 }
94
95 if (A20Status)
96 *A20Status = (ControllerOutput & 0x02) != 0;
97
98 /* Return success */
99 return TRUE;
100 }
101
102 static VOID XmsLocalEnableA20(VOID)
103 {
104 /* Enable A20 only if we can do so, otherwise make the caller believe we enabled it */
105 if (!CanChangeA20) goto Quit;
106
107 /* The count is zero so enable A20 */
108 if (A20EnableCount == 0 && !PCAT_A20Control(1, NULL))
109 goto Fail;
110
111 ++A20EnableCount;
112
113 Quit:
114 setAX(0x0001); /* Line successfully enabled */
115 setBL(XMS_STATUS_SUCCESS);
116 return;
117
118 Fail:
119 setAX(0x0000); /* Line failed to be enabled */
120 setBL(XMS_STATUS_A20_ERROR);
121 return;
122 }
123
124 static VOID XmsLocalDisableA20(VOID)
125 {
126 /* Disable A20 only if we can do so, otherwise make the caller believe we disabled it */
127 if (!CanChangeA20) goto Quit;
128
129 /* If the count is already zero, fail */
130 if (A20EnableCount == 0) goto Fail;
131
132 --A20EnableCount;
133
134 /* The count is zero so disable A20 */
135 if (A20EnableCount == 0 && !PCAT_A20Control(0, NULL))
136 goto Fail;
137
138 Quit:
139 setAX(0x0001); /* Line successfully disabled */
140 setBL(XMS_STATUS_SUCCESS);
141 return;
142
143 Fail:
144 setAX(0x0000); /* Line failed to be enabled */
145 setBL(XMS_STATUS_A20_ERROR);
146 return;
147 }
148
149 static VOID XmsGetA20State(VOID)
150 {
151 BOOLEAN A20Status = FALSE;
152
153 /*
154 * NOTE: The XMS specification explicitely says that this check is done
155 * in a hardware-independent manner, by checking whether high memory wraps.
156 * For our purposes we just call the emulator API.
157 */
158
159 /* Get A20 status */
160 if (PCAT_A20Control(2, &A20Status))
161 setBL(XMS_STATUS_SUCCESS);
162 else
163 setBL(XMS_STATUS_A20_ERROR);
164
165 setAX(A20Status);
166 }
167
168 /* PRIVATE FUNCTIONS **********************************************************/
169
170 static inline PXMS_HANDLE GetHandleRecord(WORD Handle)
171 {
172 PXMS_HANDLE Entry;
173 if (Handle == 0 || Handle >= XMS_MAX_HANDLES) return NULL;
174
175 Entry = &HandleTable[Handle - 1];
176 return Entry->Size ? Entry : NULL;
177 }
178
179 static CHAR XmsAlloc(WORD Size, PWORD Handle)
180 {
181 BYTE i;
182 PXMS_HANDLE HandleEntry;
183
184 if (Size > FreeBlocks) return XMS_STATUS_OUT_OF_MEMORY;
185
186 for (i = 0; i < XMS_MAX_HANDLES; i++)
187 {
188 HandleEntry = &HandleTable[i];
189 if (HandleEntry->Handle == 0)
190 {
191 *Handle = i + 1;
192 break;
193 }
194 }
195
196 if (i == XMS_MAX_HANDLES) return XMS_STATUS_OUT_OF_HANDLES;
197
198 HandleEntry->Handle = i + 1;
199 HandleEntry->LockCount = 0;
200 HandleEntry->Size = Size;
201 FreeBlocks -= Size;
202
203 return XMS_STATUS_SUCCESS;
204 }
205
206 static CHAR XmsFree(WORD Handle)
207 {
208 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
209 if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
210 if (HandleEntry->LockCount) return XMS_STATUS_LOCKED;
211
212 HandleEntry->Handle = 0;
213 FreeBlocks += HandleEntry->Size;
214
215 return XMS_STATUS_SUCCESS;
216 }
217
218 static CHAR XmsLock(WORD Handle, PDWORD Address)
219 {
220 DWORD CurrentIndex = 0;
221 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
222
223 if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
224 if (HandleEntry->LockCount == 0xFF) return XMS_STATUS_LOCK_OVERFLOW;
225
226 if (HandleEntry->LockCount)
227 {
228 /* Just increment the lock count */
229 HandleEntry->LockCount++;
230 return XMS_STATUS_SUCCESS;
231 }
232
233 while (CurrentIndex < XMS_BLOCKS)
234 {
235 ULONG RunStart;
236 ULONG RunSize = RtlFindNextForwardRunClear(&AllocBitmap, CurrentIndex, &RunStart);
237 if (RunSize == 0) break;
238
239 if (RunSize >= HandleEntry->Size)
240 {
241 /* Lock it here */
242 HandleEntry->LockCount++;
243 HandleEntry->Address = XMS_ADDRESS + RunStart * XMS_BLOCK_SIZE;
244
245 RtlSetBits(&AllocBitmap, RunStart, RunSize);
246 *Address = HandleEntry->Address;
247 return XMS_STATUS_SUCCESS;
248 }
249
250 /* Keep searching */
251 CurrentIndex = RunStart + RunSize;
252 }
253
254 /* Can't find any suitable range */
255 return XMS_STATUS_CANNOT_LOCK;
256 }
257
258 static CHAR XmsUnlock(WORD Handle)
259 {
260 DWORD BlockNumber;
261 PXMS_HANDLE HandleEntry = GetHandleRecord(Handle);
262
263 if (HandleEntry == NULL) return XMS_STATUS_INVALID_HANDLE;
264 if (!HandleEntry->LockCount) return XMS_STATUS_NOT_LOCKED;
265
266 /* Decrement the lock count and exit early if it's still locked */
267 if (--HandleEntry->LockCount) return XMS_STATUS_SUCCESS;
268
269 BlockNumber = (HandleEntry->Address - XMS_ADDRESS) / XMS_BLOCK_SIZE;
270 RtlClearBits(&AllocBitmap, BlockNumber, HandleEntry->Size);
271
272 return XMS_STATUS_SUCCESS;
273 }
274
275 static VOID WINAPI XmsBopProcedure(LPWORD Stack)
276 {
277 switch (getAH())
278 {
279 /* Get XMS Version */
280 case 0x00:
281 {
282 setAX(0x0300); /* XMS version 3.00 */
283 setBX(0x0301); /* Driver version 3.01 */
284 setDX(0x0001); /* HMA present */
285 break;
286 }
287
288 /* Global Enable A20 */
289 case 0x03:
290 {
291 /* Enable A20 if needed */
292 if (!IsA20Enabled)
293 {
294 XmsLocalEnableA20();
295 if (getAX() != 1)
296 {
297 /* XmsLocalEnableA20 failed and already set AX and BL to their correct values */
298 break;
299 }
300
301 IsA20Enabled = TRUE;
302 }
303
304 setAX(0x0001); /* Line successfully enabled */
305 setBL(XMS_STATUS_SUCCESS);
306 break;
307 }
308
309 /* Global Disable A20 */
310 case 0x04:
311 {
312 /* Disable A20 if needed */
313 if (IsA20Enabled)
314 {
315 XmsLocalDisableA20();
316 if (getAX() != 1)
317 {
318 /* XmsLocalDisableA20 failed and already set AX and BL to their correct values */
319 break;
320 }
321
322 IsA20Enabled = FALSE;
323 }
324
325 setAX(0x0001); /* Line successfully disabled */
326 setBL(XMS_STATUS_SUCCESS);
327 break;
328 }
329
330 /* Local Enable A20 */
331 case 0x05:
332 {
333 /* This call sets AX and BL to their correct values */
334 XmsLocalEnableA20();
335 break;
336 }
337
338 /* Local Disable A20 */
339 case 0x06:
340 {
341 /* This call sets AX and BL to their correct values */
342 XmsLocalDisableA20();
343 break;
344 }
345
346 /* Query A20 State */
347 case 0x07:
348 {
349 /* This call sets AX and BL to their correct values */
350 XmsGetA20State();
351 break;
352 }
353
354 /* Query Free Extended Memory */
355 case 0x08:
356 {
357 setAX(FreeBlocks);
358 setDX(XMS_BLOCKS);
359 setBL(XMS_STATUS_SUCCESS);
360 break;
361 }
362
363 /* Allocate Extended Memory Block */
364 case 0x09:
365 {
366 WORD Handle;
367 CHAR Result = XmsAlloc(getDX(), &Handle);
368
369 if (Result >= 0)
370 {
371 setAX(1);
372 setDX(Handle);
373 }
374 else
375 {
376 setAX(0);
377 setBL(Result);
378 }
379
380 break;
381 }
382
383 /* Free Extended Memory Block */
384 case 0x0A:
385 {
386 CHAR Result = XmsFree(getDX());
387
388 setAX(Result >= 0);
389 setBL(Result);
390
391 break;
392 }
393
394 /* Lock Extended Memory Block */
395 case 0x0C:
396 {
397 DWORD Address;
398 CHAR Result = XmsLock(getDX(), &Address);
399
400 if (Result >= 0)
401 {
402 setAX(1);
403
404 /* Store the LINEAR address in DX:BX */
405 setDX(HIWORD(Address));
406 setBX(LOWORD(Address));
407 }
408 else
409 {
410 setAX(0);
411 setBL(Result);
412 }
413
414 break;
415 }
416
417 /* Unlock Extended Memory Block */
418 case 0x0D:
419 {
420 CHAR Result = XmsUnlock(getDX());
421
422 setAX(Result >= 0);
423 setBL(Result);
424
425 break;
426 }
427
428 default:
429 {
430 DPRINT1("XMS command AH = 0x%02X NOT IMPLEMENTED\n", getAH());
431 setBL(XMS_STATUS_NOT_IMPLEMENTED);
432 }
433 }
434 }
435
436 /* PUBLIC FUNCTIONS ***********************************************************/
437
438 BOOLEAN XmsGetDriverEntry(PDWORD Pointer)
439 {
440 if (Node == NULL) return FALSE;
441 *Pointer = DEVICE_PRIVATE_AREA(Node->Driver);
442 return TRUE;
443 }
444
445 VOID XmsInitialize(VOID)
446 {
447 RtlZeroMemory(HandleTable, sizeof(HandleTable));
448 RtlZeroMemory(BitmapBuffer, sizeof(BitmapBuffer));
449 RtlInitializeBitMap(&AllocBitmap, BitmapBuffer, XMS_BLOCKS);
450
451 Node = DosCreateDeviceEx(DOS_DEVATTR_IOCTL | DOS_DEVATTR_CHARACTER,
452 XMS_DEVICE_NAME,
453 sizeof(EntryProcedure));
454
455 RegisterBop(BOP_XMS, XmsBopProcedure);
456
457 /* Copy the entry routine to the device private area */
458 RtlMoveMemory(FAR_POINTER(DEVICE_PRIVATE_AREA(Node->Driver)),
459 EntryProcedure,
460 sizeof(EntryProcedure));
461 }
462
463 VOID XmsCleanup(VOID)
464 {
465 RegisterBop(BOP_XMS, NULL);
466 DosDeleteDevice(Node);
467 }