3 ; Assembly language support routines for dos.c
4 ; (C) 2011-2012 Jonathan Campbell.
5 ; Hackipedia DOS library.
7 ; This code is licensed under the LGPL.
8 ; <insert LGPL legal text here>
10 extern _dpmi_entered ; BYTE
11 extern _dpmi_entry_point ; DWORD
12 extern _dpmi_private_data_segment ; word
13 extern _dpmi_rm_entry ; qword
14 extern _dpmi_pm_entry ; dword
15 extern _dpmi_pm_cs,_dpmi_pm_ds,_dpmi_pm_es,_dpmi_pm_ss
17 section .text class=CODE
19 ; NTS: If we code 'push ax' and 'popf' for the 16-bit tests in 32-bit protected mode we will screw up the stack pointer and crash
20 ; so we avoid duplicate code by defining 'native' pushf/popf functions and 'result' to ax or eax depending on CPU mode
21 %if TARGET_MSDOS == 32
35 %if TARGET_MSDOS == 16
37 %error You must specify MMODE variable (memory model) for 16-bit real mode code
41 %if TARGET_MSDOS == 16
43 %define retnative retf
44 %define cdecl_param_offset 6 ; RETF addr + PUSH BP
47 %define retnative retf
48 %define cdecl_param_offset 6 ; RETF addr + PUSH BP
51 %define cdecl_param_offset 4 ; RET addr + PUSH BP
56 %define cdecl_param_offset 8 ; RET addr + PUSH EBP
59 %ifndef TARGET_WINDOWS
60 %if TARGET_MSDOS == 16
61 ; cheap coding: put some variables here in the code segment. as real-mode
62 ; code there's nothing to stop us from leaving DS == CS on entry and letting
63 ; DPMI build an alias to our own code segment
65 l_dpmi_rm_entry dd 0 ; also re-used to call entry!
68 ; we also need to record the segments given to us by the DPMI server so
69 ; that we can re-enter protected mode
70 l_dpmi_segs dw 0,0,0,0
73 ; void __cdecl dpmi_enter_core(); /* Watcom's inline assembler is too limiting to carry out the DPMI entry and switch back */
74 global _dpmi_enter_core
84 mov ax,seg _dpmi_entered
87 mov bl,byte [_dpmi_entered]
88 mov byte [cs:l_dpmi_mode],bl ; the protected mode side of the function needs this
91 or al,1 ; indicate 32-bit DPMI connection
93 ; so: AX=0 if 16-bit setup, AX=1 if 32-bit setup. Now for simplicity set DS==CS
94 mov bx,seg _dpmi_private_data_segment ; NTS may be zero if DPMI doesn't need it
96 mov es,[es:_dpmi_private_data_segment] ; NTS: ES = DPMI private data. Do not modify between here and call to DPMI entry
98 mov bx,seg _dpmi_entry_point
100 mov bx,word [_dpmi_entry_point+0]
101 mov word [cs:l_dpmi_rm_entry+0],bx
102 mov bx,word [_dpmi_entry_point+2]
103 mov word [cs:l_dpmi_rm_entry+2],bx
107 call far word [cs:l_dpmi_rm_entry]
109 ; ENTRY FAILED. Set entered flag to zero and return
110 mov ax,seg _dpmi_entered
112 mov byte [_dpmi_entered],0
113 add sp,4 ; discard saved CS+SS
119 ; HERE: Entry succeeded. Get DPMI PM/RM entry points and then switch back to real mode.
120 ; note that because we entered with DS == CS the DPMI server should have CS != DS but both
121 ; refer to the same segment, as aliases. That makes our job simpler as we can use local storage
122 ; privately in the code segment.
127 ; BX:CX real to protected mode entry point
128 mov word [l_dpmi_pm_entry+0],cx
129 mov word [l_dpmi_pm_entry+2],bx
131 ; save the selectors preallocated by DPMI
132 mov word [l_dpmi_segs+0],cs
133 mov word [l_dpmi_segs+2],ds
134 mov word [l_dpmi_segs+4],es
135 mov word [l_dpmi_segs+6],ss
137 ; SI:DI (16-bit) or SI:EDI (32-bit) protected mode to real mode entry point
138 cmp byte [l_dpmi_mode],32
140 ; 32-bit storage, and return
141 mov dword [l_dpmi_rm_entry+0],edi
142 mov word [l_dpmi_rm_entry+4],si
143 pop dx ; restore SS into DX. DX will become SS
144 pop ax ; restore CS into AX. AX will become DS
145 mov cx,ax ; CX will become ES
146 mov si,ax ; SI will become CS
147 mov bx,sp ; BX will become SP
148 mov di,.exit_ok ; DI will become IP, so direct it at the exit point below
149 jmp far dword [l_dpmi_rm_entry]
151 ; 16-bit storage, and return
152 mov word [l_dpmi_rm_entry+0],di
153 mov word [l_dpmi_rm_entry+2],si
154 pop dx ; restore SS into DX. DX will become SS
155 pop ax ; restore CS into AX. AX will become DS
156 mov cx,ax ; CX will become ES
157 mov si,ax ; SI will become CS
158 mov bx,sp ; BX will become SP
159 mov di,.exit_ok ; DI will become IP, so direct it at the exit point below
160 jmp far word [l_dpmi_rm_entry]
161 ; jump back to realmode here
164 ; copy results to host variables
165 mov ax,word [cs:l_dpmi_pm_entry]
166 mov bx,word [cs:l_dpmi_pm_entry+2]
167 mov cx,seg _dpmi_pm_entry
169 mov word [_dpmi_pm_entry+0],ax
170 mov word [_dpmi_pm_entry+2],bx
172 mov ax,word [cs:l_dpmi_rm_entry]
173 mov bx,word [cs:l_dpmi_rm_entry+2]
174 mov cx,word [cs:l_dpmi_rm_entry+4]
175 mov dx,seg _dpmi_rm_entry
177 mov word [_dpmi_rm_entry+0],ax
178 mov word [_dpmi_rm_entry+2],bx
179 mov word [_dpmi_rm_entry+4],cx
181 mov ax,word [cs:l_dpmi_segs+0]
182 mov dx,seg _dpmi_pm_cs
184 mov word [_dpmi_pm_cs],ax
186 mov ax,word [cs:l_dpmi_segs+2]
187 mov dx,seg _dpmi_pm_ds
189 mov word [_dpmi_pm_ds],ax
191 mov ax,word [cs:l_dpmi_segs+4]
192 mov dx,seg _dpmi_pm_es
194 mov word [_dpmi_pm_es],ax
196 mov ax,word [cs:l_dpmi_segs+6]
197 mov dx,seg _dpmi_pm_ss
199 mov word [_dpmi_pm_ss],ax
201 ; now that DPMI is active, we have to hook real-mode INT 21h
202 ; to catch program termination, so we can forward that to the DPMI
203 ; server for proper DPMI cleanup
215 ; We use DPMI entry and thunking back to real mode to let the host
216 ; program remain 16-bit. BUT: there's a problem. if the host program
217 ; exits normally with INT 21h via real mode, the DPMI server never gets
218 ; the message and it remains stuck running in the background. To make
219 ; DPMI exit normally, we have to hook INT 21h and reflect AH=0x4C to
221 %ifndef TARGET_WINDOWS
222 %if TARGET_MSDOS == 16
232 mov bx,word [es:(0x21*4)]
233 mov cx,word [es:(0x21*4)+2]
234 mov word [es:(0x21*4)],dpmi_int21_hook_exit
235 mov word [es:(0x21*4)+2],ax
236 mov word [cs:old_int21h+0],bx
237 mov word [cs:old_int21h+2],cx
239 ; also keep track of this process's PSP segment, so we can readily
240 ; identify WHO is calling INT 21h AH=0x4C and forward to DPMI only
241 ; for our process, not any other process.
244 mov word [cs:this_process_psp],bx
252 ; Our INT 21h hook. We're looking for any INT 21h AH=0x4C call coming from
253 ; this process. If the call comes from any other program in memory, the call
254 ; is forwarded without modification, so that DPMI does not prematurely exit
255 ; when subprocesses started by this program terminate.
257 ; This hack seems silly but apparently most DPMI servers do not monitor real-mode
258 ; INT 21h for the AH=0x4C call. If they never see the termination call from
259 ; protected mode, then they never clean up for this process and in most cases
260 ; (especially Windows) end up leaking selectors and other resources. So to avoid
261 ; memory leaks, we must forward INT 21h AH=0x4C to the protected mode side of
264 ; TODO: This hook should also catch INT 21h AH=31 Terminate and Stay Resident,
265 ; DPMI needs to keep those too!
267 ; FIXME: How will this code catch cases where the calling program calls INT 21h
268 ; from protected mode to terminate? Worst case scenario: DPMI cleans up
269 ; and we never get a chance to remove our INT 21h hook.
270 dpmi_int21_hook_exit:
273 jmp far word [cs:old_int21h]
275 ; this is our process terminating, not some subprocess, right?
276 ; we want to forward termination only for this process, not anyone else.
279 mov ah,0x62 ; get PSP segment
281 cmp bx,word [cs:this_process_psp]
285 jmp far word [cs:old_int21h]
289 ; restore the old vector
297 mov bx,word [cs:old_int21h+0]
298 mov cx,word [cs:old_int21h+2]
299 mov word [es:(0x21*4)],bx
300 mov word [es:(0x21*4)+2],cx
305 ; OK. Switch into protected mode.
306 ; use the segment values given to us by the DPMI server.
309 mov ax,word [cs:l_dpmi_segs+2] ; AX becomes DS (so load DS from DPMI env)
310 mov cx,ax ; CX becomes ES
311 mov dx,ax ; DX becomes SS (doesn't matter)
312 mov bx,sp ; BX becomes SP (doesn't matter)
313 mov si,word [cs:l_dpmi_segs+0] ; SI becomes CS (so load CS from DPMI env)
314 mov di,.catch_exit_pmode ; DI becomes IP
315 jmp far word [cs:l_dpmi_pm_entry]
319 int 21h ; now issue INT 21h AH=0x4C where the DPMI server can see it
324 %if TARGET_MSDOS == 16
325 %ifndef TARGET_WINDOWS
327 ; WARNING: The caller must have ensured we are running on a 386 or higher, and that
328 ; the DPMI entry points were obtained
330 ; __cdecl: right-to-left argument passing (meaning: caller does "push sz", "push lsrc", "push dst"...)
332 l_lin2fm_param_dst: dd 0 ; unsigned char far *dst
333 l_lin2fm_param_lsrc: dd 0 ; uint32_t lsrc
334 l_lin2fm_param_sz: dd 0 ; uint32_t sz
341 ; TODO: Export these so they are visible as C variables
342 ; we need these selectors for copy operation
343 l_lin2fm_src_sel dw 0
344 l_lin2fm_dst_sel dw 0
346 ; dpmi_pm_cs,dpmi_pm_ds,dpmi_pm_es,dpmi_pm_ss
347 ; int __cdecl dpmi_lin2fmemcpy_32(unsigned char far *dst,uint32_t lsrc,uint32_t sz);
348 global _dpmi_lin2fmemcpy_32
349 _dpmi_lin2fmemcpy_32:
353 ; copy params, we need them in protected mode
354 mov eax,dword [bp+cdecl_param_offset+0]
355 mov dword [cs:l_lin2fm_params+0],eax
356 mov eax,dword [bp+cdecl_param_offset+4]
357 mov dword [cs:l_lin2fm_params+4],eax
358 mov eax,dword [bp+cdecl_param_offset+8]
359 mov dword [cs:l_lin2fm_params+8],eax
361 pusha ; save all regs
366 push cs ; realmode re-entry needs this
367 push ss ; realmode re-entry needs this
368 push ds ; realmode re-entry needs this
370 mov ax,seg _dpmi_pm_entry
374 mov word [cs:l_rm_ret],ax
376 mov eax,dword [_dpmi_rm_entry+0]
377 mov dword [cs:l_rm_reentry+0],eax
379 mov ax,word [_dpmi_rm_entry+4]
380 mov word [cs:l_rm_reentry+4],ax
382 mov ax,word [_dpmi_pm_ds]
384 mov dx,word [_dpmi_pm_ss]
386 mov si,word [_dpmi_pm_cs]
388 call far word [_dpmi_pm_entry]
389 ; didn't make it. error return
390 add sp,6 ; do not restore SS+CS+DS, just discard
395 xor ax,ax ; return 0 == no copy made
399 ; we need to allocate two selectors to do the copy operation with
400 cmp word [l_lin2fm_src_sel],0
401 jnz .sel_avail ; if != 0, then skip code
402 ; allocate two descriptors
406 jnc .sel_alloced ; if carry clear, continue
407 jmp .go_to_exit_pm ; else return to RM with retval == 0
409 ; we got two descriptors, store them
410 mov word [l_lin2fm_src_sel],ax
411 add ax,8 ; obviously...
412 mov word [l_lin2fm_dst_sel],ax
413 ; we need to make them data selectors
414 mov ax,0x0009 ; DPMI Set Descriptor Access Rights
415 mov bx,word [l_lin2fm_src_sel]
416 mov cl,0xF0 ; P=1 DPL=3 data expand-up r/o. I know DPMI says it must equal our level, but Windows always runs us Ring-3 so we can assume
417 xor ch,ch ; 16-bit selector (we are 16-bit code!)
419 jc short $ ; FIXME:For now, hang if the request failed
420 mov ax,0x0008 ; DPMI Set Selector Limit
421 mov bx,word [l_lin2fm_src_sel]
424 dec dx ; CX:DX = 0000:FFFF
427 mov ax,0x0009 ; DPMI Set Descriptor Access Rights
428 mov bx,word [l_lin2fm_dst_sel]
429 mov cl,0xF2 ; P=1 DPL=3 data expand-up r/w. I know DPMI says it must equal our level, but Windows always runs us Ring-3 so we can assume
430 xor ch,ch ; 16-bit selector (we are 16-bit code!)
432 jc short $ ; FIXME:For now, hang if the request failed
433 mov ax,0x0008 ; DPMI Set Selector Limit
434 mov bx,word [l_lin2fm_dst_sel]
437 dec dx ; CX:DX = 0000:FFFF
440 ; OK, pull in source address (flat) from param and set the selector base
442 mov bx,word [l_lin2fm_src_sel]
443 mov dx,word [l_lin2fm_param_lsrc+0] ; CX:DX = base
444 mov cx,word [l_lin2fm_param_lsrc+2]
446 ; and the dest address (realmode seg:off) too
447 movzx eax,word [l_lin2fm_param_dst+2]
449 movzx ebx,word [l_lin2fm_param_dst+0]
454 mov bx,word [l_lin2fm_dst_sel]
457 ; alright then, do the memcpy
458 mov cx,word [l_lin2fm_param_sz]
459 mov word [l_rm_ret],cx ; set return value too
463 mov ax,word [l_lin2fm_src_sel]
464 mov bx,word [l_lin2fm_dst_sel]
469 rep movsb ; ES:DI <- DS:SI
473 ; NTS: when dpmi_enter_core() did it's job it made sure DS == CS
474 ; so the DPMI server would make DS an alias of CS in protected mode
475 pop ax ; AX = realmode DS
477 pop dx ; DX = realmode SS
478 pop si ; SI = realmode CS
481 call far dword [l_rm_reentry] ; NTS: We're using the 32-bit DPMI server, the RM entry point is 16:32 format
482 .exit_pm: ; NTS: Don't forget CS+DS+SS was pushed but the PM part popped them as part of returning
488 mov ax,word [cs:l_rm_ret]
491 ; NOTE: This version of the code is written to work with 16-bit DPMI servers,
492 ; and to work within the constraint that we could be run on a 286 where
493 ; 32-bit registers are not available.
494 ; dpmi_pm_cs,dpmi_pm_ds,dpmi_pm_es,dpmi_pm_ss
495 ; int __cdecl dpmi_lin2fmemcpy_16(unsigned char far *dst,uint32_t lsrc,uint32_t sz);
496 global _dpmi_lin2fmemcpy_16
497 _dpmi_lin2fmemcpy_16:
501 ; copy params, we need them in protected mode
502 mov ax,word [bp+cdecl_param_offset+0]
503 mov word [cs:l_lin2fm_params+0],ax
504 mov ax,word [bp+cdecl_param_offset+2]
505 mov word [cs:l_lin2fm_params+2],ax
506 mov ax,word [bp+cdecl_param_offset+4]
507 mov word [cs:l_lin2fm_params+4],ax
508 mov ax,word [bp+cdecl_param_offset+6]
509 mov word [cs:l_lin2fm_params+6],ax
510 mov ax,word [bp+cdecl_param_offset+8]
511 mov word [cs:l_lin2fm_params+8],ax
512 mov ax,word [bp+cdecl_param_offset+10]
513 mov word [cs:l_lin2fm_params+10],ax
515 pusha ; save all regs
520 push cs ; realmode re-entry needs this
521 push ss ; realmode re-entry needs this
522 push ds ; realmode re-entry needs this
524 mov ax,seg _dpmi_pm_entry
528 mov word [cs:l_rm_ret],ax
530 mov eax,dword [_dpmi_rm_entry+0]
531 mov dword [cs:l_rm_reentry+0],eax
533 mov ax,word [_dpmi_rm_entry+4]
534 mov word [cs:l_rm_reentry+4],ax
536 mov ax,word [_dpmi_pm_ds]
538 mov dx,word [_dpmi_pm_ss]
540 mov si,word [_dpmi_pm_cs]
542 call far word [_dpmi_pm_entry]
543 ; didn't make it. error return
544 add sp,6 ; do not restore SS+CS+DS, just discard
549 xor ax,ax ; return 0 == no copy made
553 ; we need to allocate two selectors to do the copy operation with
554 cmp word [l_lin2fm_src_sel],0
555 jnz .sel_avail ; if != 0, then skip code
556 ; allocate two descriptors
560 jnc .sel_alloced ; if carry clear, continue
561 jmp .go_to_exit_pm ; else return to RM with retval == 0
563 ; we got two descriptors, store them
564 mov word [l_lin2fm_src_sel],ax
565 add ax,8 ; obviously...
566 mov word [l_lin2fm_dst_sel],ax
567 ; we need to make them data selectors
568 mov ax,0x0009 ; DPMI Set Descriptor Access Rights
569 mov bx,word [l_lin2fm_src_sel]
570 mov cl,0xF0 ; P=1 DPL=3 data expand-up r/o. I know DPMI says it must equal our level, but Windows always runs us Ring-3 so we can assume
571 xor ch,ch ; 16-bit selector (we are 16-bit code!)
573 jc short $ ; FIXME:For now, hang if the request failed
574 mov ax,0x0008 ; DPMI Set Selector Limit
575 mov bx,word [l_lin2fm_src_sel]
578 dec dx ; CX:DX = 0000:FFFF
581 mov ax,0x0009 ; DPMI Set Descriptor Access Rights
582 mov bx,word [l_lin2fm_dst_sel]
583 mov cl,0xF2 ; P=1 DPL=3 data expand-up r/w. I know DPMI says it must equal our level, but Windows always runs us Ring-3 so we can assume
584 xor ch,ch ; 16-bit selector (we are 16-bit code!)
586 jc short $ ; FIXME:For now, hang if the request failed
587 mov ax,0x0008 ; DPMI Set Selector Limit
588 mov bx,word [l_lin2fm_dst_sel]
591 dec dx ; CX:DX = 0000:FFFF
594 ; OK, pull in source address (flat) from param and set the selector base
596 mov bx,word [l_lin2fm_src_sel]
597 mov dx,word [l_lin2fm_param_lsrc+0] ; CX:DX = base
598 mov cx,word [l_lin2fm_param_lsrc+2]
600 ; and the dest address (realmode seg:off) too
601 mov dx,word [l_lin2fm_param_dst+2] ; DX = (seg << 4) + offset, CX = (seg >> 12) + (carry flag result of computing DX)
605 add dx,word [l_lin2fm_param_dst+0]
606 adc cx,0 ; CX:DX = 32-bit linear address
607 mov bx,word [l_lin2fm_dst_sel]
610 ; alright then, do the memcpy
611 mov cx,word [l_lin2fm_param_sz]
612 mov word [l_rm_ret],cx ; set return value too
616 mov ax,word [l_lin2fm_src_sel]
617 mov bx,word [l_lin2fm_dst_sel]
622 rep movsb ; ES:DI <- DS:SI
626 ; NTS: when dpmi_enter_core() did it's job it made sure DS == CS
627 ; so the DPMI server would make DS an alias of CS in protected mode
628 pop ax ; AX = realmode DS
630 pop dx ; DX = realmode SS
631 pop si ; SI = realmode CS
634 call far word [l_rm_reentry] ; NTS: We're using the 16-bit DPMI server, the RM entry point is 16:16 format
635 .exit_pm: ; NTS: Don't forget CS+DS+SS was pushed but the PM part popped them as part of returning
641 mov ax,word [cs:l_rm_ret]