]> 4ch.mooo.com Git - 16.git/blob - src/lib/doslib/dos/dos_ltp.c
added a bunch of things~ and midi stuff~
[16.git] / src / lib / doslib / dos / dos_ltp.c
1 /* dos.c
2  *
3  * Code to detect the surrounding DOS/Windows environment and support routines to work with it
4  * (C) 2009-2012 Jonathan Campbell.
5  * Hackipedia DOS library.
6  *
7  * This code is licensed under the LGPL.
8  * <insert LGPL legal text here>
9  */
10
11 #ifdef TARGET_WINDOWS
12 # include <windows.h>
13 #endif
14
15 #include <stdio.h>
16 #include <conio.h> /* this is where Open Watcom hides the outp() etc. functions */
17 #include <stdlib.h>
18 #include <string.h>
19 #include <stddef.h>
20 #include <unistd.h>
21 #include <malloc.h>
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <dos.h>
25
26 #include <hw/cpu/cpu.h>
27 #include <hw/dos/dos.h>
28 #include <hw/dos/doswin.h>
29 #include <hw/dos/dosntvdm.h>
30
31 /* TODO: Since VCPI/EMM386.EXE can affect 16-bit real mode, why not enable this API for 16-bit real mode too? */
32 /* TODO: Also why not enable this function for 16-bit protected mode under Windows 3.1? */
33 #if TARGET_MSDOS == 32
34 struct dos_linear_to_phys_info dos_ltp_info;
35 unsigned char dos_ltp_info_init=0;
36
37 /* WARNING: Caller must have called probe_dos() and detect_windows() */
38 int dos_ltp_probe() {
39         if (!dos_ltp_info_init) {
40                 memset(&dos_ltp_info,0,sizeof(dos_ltp_info));
41
42                 /* part of our hackery needs to know what CPU we're running under */
43                 if (cpu_basic_level < 0)
44                         cpu_probe();
45
46                 probe_dos();
47
48 #if defined(TARGET_WINDOWS)
49                 /* TODO: Careful analsys of what version and mode Windows we're running under */
50                 /* start with the assumption that we don't know where we are and we can't translate to physical. */
51                 dos_ltp_info.vcpi_xlate = 0; /* TODO: It is said Windows 3.0 has VCPI at the core. Can we detect that? Use it? */
52                 dos_ltp_info.paging = (windows_mode <= WINDOWS_STANDARD ? 0 : 1); /* paging is not used in REAL or STANDARD modes */
53                 dos_ltp_info.dos_remap = dos_ltp_info.paging;
54                 dos_ltp_info.should_lock_pages = 1;
55                 dos_ltp_info.cant_xlate = 1;
56                 dos_ltp_info.using_pae = 0; /* TODO: Windows XP SP2 and later can and do use PAE. How to detect that? */
57                 dos_ltp_info.dma_dos_xlate = 0;
58
59 # if TARGET_MSDOS == 32
60 # else
61                 /* TODO: Use GetWinFlags() and version info */
62 # endif
63 #else
64 /* ================ MS-DOS specific =============== */
65                 /* we need to know if VCPI is present */
66                 probe_vcpi();
67
68                 /* NTS: Microsoft Windows 3.0/3.1 Enhanced mode and Windows 95/98/ME all trap access to the control registers.
69                  *      But then they emulate the instruction in such a way that we get weird nonsense values.
70                  *
71                  *      Windows 95/98/ME: We get CR0 = 2. Why??
72                  *      Windows 3.0/3.1: We get CR0 = 0.
73                  *
74                  *      So basically what Windows is telling us... is that we're 32-bit protected mode code NOT running in
75                  *      protected mode? What? */
76                 if (windows_mode == WINDOWS_ENHANCED) {
77                         /* it's pointless, the VM will trap and return nonsense for control register contents */
78                         dos_ltp_info.cr0 = 0x80000001UL;
79                         dos_ltp_info.cr3 = 0x00000000UL;
80                         dos_ltp_info.cr4 = 0x00000000UL;
81                 }
82                 else if (windows_mode == WINDOWS_NT) {
83                         /* Windows NTVDM will let us read CR0, but CR3 and CR4 come up blank. So what's the point then? */
84                         uint32_t r0=0;
85
86                         __asm {
87                                 xor     eax,eax
88                                 dec     eax
89
90                                 mov     eax,cr0
91                                 mov     r0,eax
92                         }
93                         dos_ltp_info.cr0 = r0 | 0x80000001UL; /* paging and protected mode are ALWAYS enabled, even if NTVDM should lie to us */
94                         dos_ltp_info.cr3 = 0x00000000UL;
95                         dos_ltp_info.cr4 = 0x00000000UL;
96                 }
97                 else {
98                         uint32_t r0=0,r3=0,r4=0;
99                         __asm {
100                                 xor     eax,eax
101                                 dec     eax
102
103                                 mov     eax,cr0
104                                 mov     r0,eax
105
106                                 mov     eax,cr3
107                                 mov     r3,eax
108
109                                 mov     eax,cr4
110                                 mov     r4,eax
111                         }
112                         dos_ltp_info.cr0 = r0;
113                         dos_ltp_info.cr3 = r3;
114                         dos_ltp_info.cr4 = r4;
115                 }
116
117                 dos_ltp_info.vcpi_xlate = vcpi_present?1:0;                     /* if no other methods available, try asking the VCPI server */
118                 dos_ltp_info.paging = (dos_ltp_info.cr0 >> 31)?1:0;             /* if bit 31 of CR0 is set, the extender has paging enabled */
119                 dos_ltp_info.dos_remap = vcpi_present?1:0;                      /* most DOS extenders map 1:1 the lower 1MB, but VCPI can violate that */
120                 dos_ltp_info.should_lock_pages = dos_ltp_info.paging;           /* it's a good assumption if paging is enabled the extender probably pages to disk and may move things around */
121                 dos_ltp_info.cant_xlate = dos_ltp_info.paging;                  /* assume we can't translate addresses yet if paging is enabled */
122                 dos_ltp_info.using_pae = (dos_ltp_info.cr4 & 0x20)?1:0;         /* take note if PAE is enabled */
123                 dos_ltp_info.dma_dos_xlate = dos_ltp_info.paging;               /* assume the extender translates DMA if paging is enabled */
124
125                 if (windows_mode == WINDOWS_ENHANCED || windows_mode == WINDOWS_NT) {
126                         dos_ltp_info.should_lock_pages = 1;             /* Windows is known to page to disk (the swapfile) */
127                         dos_ltp_info.dma_dos_xlate = 1;                 /* Windows virtualizes the DMA controller */
128                         dos_ltp_info.cant_xlate = 1;                    /* Windows provides us no way to determine the physical memory address from linear */
129                         dos_ltp_info.dos_remap = 1;                     /* Windows remaps the DOS memory area. This is how it makes multiple DOS VMs possible */
130                         dos_ltp_info.vcpi_xlate = 0;                    /* Windows does not like VCPI */
131                         dos_ltp_info.paging = 1;                        /* Windows uses paging. Always */
132                 }
133
134                 /* this code is ill prepared for PAE modes for the time being, since PAE makes page table entries 64-bit
135                  * wide instead of 32-bit wide. Then again, 99% of DOS out there probably couldn't handle PAE well either. */
136                 if (dos_ltp_info.using_pae)
137                         dos_ltp_info.cant_xlate = 1;
138
139                 /* if NOT running under Windows, and the CR3 register shows something, then we can translate by directly peeking at the page tables */
140                 if (windows_mode == WINDOWS_NONE && dos_ltp_info.cr3 >= 0x1000)
141                         dos_ltp_info.cant_xlate = 0;
142 #endif
143
144                 dos_ltp_info_init = 1;
145         }
146
147         return 1;
148 }
149 #endif
150
151 #if TARGET_MSDOS == 32
152 /* WARNINGS: Worst case scanario this function cannot translate anything at all.
153  *           It will return 0xFFFFFFFFUL if it cannot determine the address.
154  *           If paging is disabled, the linear address will be returned.
155  *           If the environment requires you to lock pages, then you must do so
156  *           before calling this function. Failure to do so will mean erratic
157  *           behavior when the page you were working on moves out from under you! */
158
159 /* "There is no way in a DPMI environment to determine the physical address corresponding to a given linear address. This is part of the design of DPMI. You must design your application accordingly."
160  *
161  * Fuck you.
162  * I need the damn physical address and you're not gonna stop me! */
163
164 /* NOTES:
165  *
166  *     QEMU + Windows 95 + EMM386.EXE:
167  *
168  *        I don't know if the DOS extender is doing this, or EMM386.EXE is enforcing it, but
169  *        a dump of the first 4MB in the test program reveals our linear address space is
170  *        randomly constructed from 16KB pages taken from all over extended memory. Some of
171  *        them, the pages are arranged BACKWARDS. Yeah, backwards.
172  *
173  *        Anyone behind DOS4/GW and DOS32a care to explain that weirdness?
174  *
175  *        Also revealed, DOS4/GW follows the DPMI spec and refuses to map pages from conventional
176  *        memory. So if DOS memory is not mapped 1:1 and the page tables are held down there we
177  *        literally cannot read them! */
178 /* NOTE: The return value is 64-bit so that in the future, if we ever run under DOS with PAE or
179  *       PSE-36 trickery, we can still report the correct address even if above 4GB. The parameter
180  *       supplied however remains 32-bit, because as a 32-bit DOS program there's no way any
181  *       linear address will ever exceed 4GB. */
182 uint64_t dos_linear_to_phys(uint32_t linear) {
183         uint32_t off,ent;
184         uint64_t ret = DOS_LTP_FAILED;
185         unsigned char lock1=0,lock2=0;
186         uint32_t *l1=NULL,*l2=NULL;
187
188         if (!dos_ltp_info.paging)
189                 return linear;
190         if (linear < 0x100000UL && !dos_ltp_info.dos_remap) /* if below 1MB boundary and DOS is not remapped, then no translation */
191                 return linear;
192
193         /* if VCPI translation is to be used, and lower DOS memory is remapped OR the page requested is >= 1MB (or in adapter ROM/RAM), then call the VCPI server and ask */
194         if (dos_ltp_info.vcpi_xlate && (dos_ltp_info.dos_remap || linear >= 0xC0000UL)) {
195                 ent = dos_linear_to_phys_vcpi(linear>>12);
196                 if (ent != 0xFFFFFFFFUL) return ent;
197                 /* Most likely requests for memory >= 1MB will fail, because VCPI is only required to
198                  * provide the system call for lower 1MB DOS conventional memory */
199         }
200
201 /* if we can't use VCPI and cannot translate, then give up. */
202 /* also, this code does not yet support PAE */
203         if (dos_ltp_info.using_pae || dos_ltp_info.cant_xlate)
204                 return ret;
205
206 /* re-read control reg because EMM386, etc. is free to change it at any time */
207         {
208                 uint32_t r3=0,r4=0;
209                 __asm {
210                         xor     eax,eax
211                         dec     eax
212
213                         mov     eax,cr3
214                         mov     r3,eax
215
216                         mov     eax,cr4
217                         mov     r4,eax
218                 }
219                 dos_ltp_info.cr3 = r3;
220                 dos_ltp_info.cr4 = r4;
221         }
222
223         /* OK then, we have to translate */
224         off = linear & 0xFFFUL;
225         linear >>= 12UL;
226         if (dos_ltp_info.cr3 < 0x1000) /* if the contents of CR3 are not available, then we cannot translate */
227                 return ret;
228
229         /* VCPI possibility: the page table might reside below 1MB in DOS memory, and remain unmapped. */
230         if (dos_ltp_info.dos_remap && dos_ltp_info.vcpi_xlate && dos_ltp_info.cr3 < 0x100000UL) {
231                 lock1 = 0;
232                 if (dos_linear_to_phys_vcpi(dos_ltp_info.cr3>>12) == (dos_ltp_info.cr3&0xFFFFF000UL)) /* if VCPI says it's a 1:1 mapping down there, then it's OK */
233                         l1 = (uint32_t*)(dos_ltp_info.cr3 & 0xFFFFF000UL);
234         }
235         /* DOS4/GW and DOS32A Goodie: the first level of the page table is in conventional memory... and the extender maps DOS 1:1 :) */
236         else if (!dos_ltp_info.dos_remap && dos_ltp_info.cr3 < 0x100000UL) {
237                 lock1 = 0;
238                 l1 = (uint32_t*)(dos_ltp_info.cr3 & 0xFFFFF000UL);
239         }
240         else {
241                 /* well, then we gotta map it */
242                 l1 = (uint32_t*)dpmi_phys_addr_map(dos_ltp_info.cr3 & 0xFFFFF000UL,4096);
243                 if (l1 != NULL) lock1 = 1;
244         }
245
246         if (l1 != NULL) {
247                 /* level 1 lookup */
248                 ent = l1[linear >> 10UL];
249                 if (ent & 1) { /* if the page is actually present... */
250                         /* if the CPU supports PSE (Page Size Extensions) and has them enabled (via CR4) and the page is marked PS=1 */
251                         if ((cpu_cpuid_features.a.raw[2/*EDX*/] & (1 << 3)) && (dos_ltp_info.cr4 & 0x10) && (ent & 0x80)) {
252                                 /* it's a 4MB page, and we stop searching the page hierarchy here */
253                                 ret = ent & 0xFFC00000UL; /* bits 31-22 are the actual page address */
254
255                                 /* but wait: if the CPU supports PSE-36, then we need to readback address bits 35...32,
256                                  * or if an AMD64 processor, address bits 39...32 */
257                                 /* FIXME: So, we can assume if the CPU supports 64-bit long mode, that we can use bits 39...32?
258                                  *        Perhaps this is a more in-depth check that we should be doing in the ltp_probe function? */
259                                 if (cpu_cpuid_features.a.raw[2/*E2X*/] & (1 << 17)) { /* If PSE support exists */
260                                         if (cpu_cpuid_features.a.raw[3/*ECX*/] & (1 << 29)) { /* If CPU supports 64-bit long mode */
261                                                 /* AMD64 compatible, up to 40 bits */
262                                                 ret |= ((uint64_t)((ent >> 13UL) & 0xFFUL)) << 32ULL;
263                                         }
264                                         else { /* else, assume Pentium III compatible, up to 36 bits */
265                                                 ret |= ((uint64_t)((ent >> 13UL) & 0xFUL)) << 32ULL;
266                                         }
267                                 }
268                         }
269                         else {
270                                 /* VCPI possibility: the page table might reside below 1MB in DOS memory, and remain unmapped. */
271                                 if (dos_ltp_info.dos_remap && dos_ltp_info.vcpi_xlate && ent < 0x100000UL) {
272                                         lock2 = 0;
273                                         if (dos_linear_to_phys_vcpi(ent>>12) == (ent&0xFFFFF000UL)) /* if VCPI says it's a 1:1 mapping down there, then it's OK */
274                                                 l2 = (uint32_t*)(ent & 0xFFFFF000UL);
275                                 }
276                                 /* again the second level is usually in DOS memory where we can assume 1:1 mapping */
277                                 else if (!dos_ltp_info.dos_remap && !dos_ltp_info.dos_remap && ent < 0x100000UL) {
278                                         lock2 = 0;
279                                         l2 = (uint32_t*)(ent & 0xFFFFF000UL);
280                                 }
281                                 else {
282                                         /* well, then we gotta map it */
283                                         l2 = (uint32_t*)dpmi_phys_addr_map(ent & 0xFFFFF000UL,4096);
284                                         if (l2 != NULL) lock2 = 1;
285                                 }
286                         }
287                 }
288         }
289
290         if (l2 != NULL) {
291                 /* level 2 lookup */
292                 ent = l2[linear & 0x3FF];
293                 if (ent & 1) { /* if the page is actually present... */
294                         ret = ent & 0xFFFFF000UL;
295                 }
296         }
297
298         if (lock2) dpmi_phys_addr_free((void*)l2);
299         if (lock1) dpmi_phys_addr_free((void*)l1);
300         return ret;
301 }
302 #endif
303