memdisk: relocate real-mode code before booting
[profile/ivi/syslinux.git] / memdisk / memdisk16.asm
1 ;; -*- fundamental -*-
2 ;; -----------------------------------------------------------------------
3 ;;
4 ;;   Copyright 1994-2008 H. Peter Anvin - All Rights Reserved
5 ;;   Copyright 2009 Intel Corporation; author: H. Peter Anvin
6 ;;
7 ;;   This program is free software; you can redistribute it and/or modify
8 ;;   it under the terms of the GNU General Public License as published by
9 ;;   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
10 ;;   Boston MA 02111-1307, USA; either version 2 of the License, or
11 ;;   (at your option) any later version; incorporated herein by reference.
12 ;;
13 ;; -----------------------------------------------------------------------
14
15 ;;
16 ;; init16.asm
17 ;;
18 ;; Routine to initialize and to trampoline into 32-bit
19 ;; protected memory.  This code is derived from bcopy32.inc and
20 ;; com32.inc in the main SYSLINUX distribution.
21 ;;
22
23 %include '../version.gen'
24
25 MY_CS           equ 0x0800              ; Segment address to use
26 CS_BASE         equ (MY_CS << 4)        ; Corresponding address
27
28 ; Low memory bounce buffer
29 BOUNCE_SEG      equ (MY_CS+0x1000)
30
31 %define DO_WBINVD 0
32
33                 section .rodata align=16
34                 section .data   align=16
35                 section .bss    align=16
36                 section .stack  align=16 nobits
37 stack           resb 512
38 stack_end       equ $
39
40 ;; -----------------------------------------------------------------------
41 ;;  Kernel image header
42 ;; -----------------------------------------------------------------------
43
44                 section .text           ; Must be first in image
45                 bits 16
46
47 cmdline         times 497 db 0          ; We put the command line here
48 setup_sects     db 0
49 root_flags      dw 0
50 syssize         dw 0
51 swap_dev        dw 0
52 ram_size        dw 0
53 vid_mode        dw 0
54 root_dev        dw 0
55 boot_flag       dw 0xAA55
56
57 _start:         jmp short start
58
59                 db "HdrS"               ; Header signature
60                 dw 0x0203               ; Header version number
61
62 realmode_swtch  dw 0, 0                 ; default_switch, SETUPSEG
63 start_sys_seg   dw 0x1000               ; obsolete
64 version_ptr     dw memdisk_version-0x200        ; version string ptr
65 type_of_loader  db 0                    ; Filled in by boot loader
66 loadflags       db 1                    ; Please load high
67 setup_move_size dw 0                    ; Unused
68 code32_start    dd 0x100000             ; 32-bit start address
69 ramdisk_image   dd 0                    ; Loaded ramdisk image address
70 ramdisk_size    dd 0                    ; Size of loaded ramdisk
71 bootsect_kludge dw 0, 0
72 heap_end_ptr    dw 0
73 pad1            dw 0
74 cmd_line_ptr    dd 0                    ; Command line
75 ramdisk_max     dd 0xffffffff           ; Highest allowed ramdisk address
76
77 ;
78 ; These fields aren't real setup fields, they're poked in by the
79 ; 32-bit code.
80 ;
81 b_esdi          dd 0                    ; ES:DI for boot sector invocation
82 b_edx           dd 0                    ; EDX for boot sector invocation
83 b_sssp          dd 0                    ; SS:SP on boot sector invocation
84 b_csip          dd 0                    ; CS:IP on boot sector invocation
85
86                 section .rodata
87 memdisk_version:
88                 db "MEMDISK ", VERSION_STR, " ", DATE, 0
89
90 ;; -----------------------------------------------------------------------
91 ;;  End kernel image header
92 ;; -----------------------------------------------------------------------
93
94 ;
95 ; Move ourselves down into memory to reduce the risk of conflicts;
96 ; then canonicalize CS to match the other segments.
97 ;
98                 section .text
99                 bits 16
100 start:
101                 mov ax,MY_CS
102                 mov es,ax
103                 movzx cx,byte [setup_sects]
104                 inc cx                  ; Add one for the boot sector
105                 shl cx,7                ; Convert to dwords
106                 xor si,si
107                 xor di,di
108                 mov fs,si               ; fs <- 0
109                 cld
110                 rep movsd
111                 mov ds,ax
112                 mov ss,ax
113                 mov esp,stack_end
114                 jmp MY_CS:.next
115 .next:
116
117 ;
118 ; Copy the command line, if there is one
119 ;
120 copy_cmdline:
121                 xor di,di               ; Bottom of our own segment (= "boot sector")
122                 mov eax,[cmd_line_ptr]
123                 and eax,eax
124                 jz .endcmd              ; No command line
125                 mov si,ax
126                 shr eax,4               ; Convert to segment
127                 and si,0x000F           ; Starting offset only
128                 mov gs,ax
129                 mov cx,496              ; Max number of bytes
130 .copycmd:
131                 gs lodsb
132                 and al,al
133                 jz .endcmd
134                 stosb
135                 loop .copycmd
136 .endcmd:
137                 xor al,al
138                 stosb
139
140 ;
141 ; Now jump to 32-bit code
142 ;
143                 sti
144                 call init32
145 ;
146 ; When init32 returns, we have been set up, the new boot sector loaded,
147 ; and we should go and and run the newly loaded boot sector.
148 ;
149 ; The setup function will have poked values into the setup area.
150 ;
151                 movzx edi,word [cs:b_esdi]
152                 mov es,word [cs:b_esdi+2]
153                 mov edx,[cs:b_edx]
154
155                 cli
156                 xor esi,esi             ; No partition table involved
157                 mov ds,si               ; Make all the segments consistent
158                 mov fs,si
159                 mov gs,si
160                 lss sp,[cs:b_sssp]
161                 movzx esp,sp
162                 call far [cs:b_csip]
163                 int 18h                 ; A far return -> INT 18h
164
165 ;
166 ; We enter protected mode, set up a flat 32-bit environment, run rep movsd
167 ; and then exit.  IMPORTANT: This code assumes cs == MY_CS.
168 ;
169 ; This code is probably excessively anal-retentive in its handling of
170 ; segments, but this stuff is painful enough as it is without having to rely
171 ; on everything happening "as it ought to."
172 ;
173 DummyTSS        equ  0x580              ; Hopefully safe place in low mmoery
174
175                 section .data
176
177         ; desc base, limit, flags
178 %macro  desc 3
179         dd (%2 & 0xffff) | ((%1 & 0xffff) << 16)
180         dd (%1 & 0xff000000) | (%2 & 0xf0000) | ((%3 & 0xf0ff) << 8) | ((%1 & 0x00ff0000) >> 16)
181 %endmacro
182
183                 align 8, db 0
184 call32_gdt:     dw call32_gdt_size-1    ; Null descriptor - contains GDT
185 .adj1:          dd call32_gdt+CS_BASE   ; pointer for LGDT instruction
186                 dw 0
187
188                 ; 0008: Dummy TSS to make Intel VT happy
189                 ; Should never be actually accessed...
190                 desc DummyTSS, 103, 0x8089
191
192                 ; 0010: Code segment, use16, readable, dpl 0, base CS_BASE, 64K
193                 desc CS_BASE, 0xffff, 0x009b
194
195                 ; 0018: Data segment, use16, read/write, dpl 0, base CS_BASE, 64K
196                 desc CS_BASE, 0xffff, 0x0093
197
198                 ; 0020: Code segment, use32, read/write, dpl 0, base 0, 4G
199                 desc 0, 0xfffff, 0xc09b
200
201                 ; 0028: Data segment, use32, read/write, dpl 0, base 0, 4G
202                 desc 0, 0xfffff, 0xc093
203
204 call32_gdt_size:        equ $-call32_gdt
205
206 err_a20:        db 'ERROR: A20 gate not responding!',13,10,0
207
208                 section .bss
209                 alignb 4
210 Return          resd 1                  ; Return value
211 SavedSP         resw 1                  ; Place to save SP
212 A20Tries        resb 1
213
214                 section .data
215                 align 4, db 0
216 Target          dd 0                    ; Target address
217 Target_Seg      dw 20h                  ; Target CS
218
219 A20Type         dw 0                    ; Default = unknown
220
221                 section .text
222                 bits 16
223 ;
224 ; Routines to enable and disable (yuck) A20.  These routines are gathered
225 ; from tips from a couple of sources, including the Linux kernel and
226 ; http://www.x86.org/.  The need for the delay to be as large as given here
227 ; is indicated by Donnie Barnes of RedHat, the problematic system being an
228 ; IBM ThinkPad 760EL.
229 ;
230 ; We typically toggle A20 twice for every 64K transferred.
231 ;
232 %define io_delay        call _io_delay
233 %define IO_DELAY_PORT   80h             ; Invalid port (we hope!)
234 %define disable_wait    32              ; How long to wait for a disable
235
236 %define A20_DUNNO       0               ; A20 type unknown
237 %define A20_NONE        1               ; A20 always on?
238 %define A20_BIOS        2               ; A20 BIOS enable
239 %define A20_KBC         3               ; A20 through KBC
240 %define A20_FAST        4               ; A20 through port 92h
241
242                 align 2, db 0
243 A20List         dw a20_dunno, a20_none, a20_bios, a20_kbc, a20_fast
244 A20DList        dw a20d_dunno, a20d_none, a20d_bios, a20d_kbc, a20d_fast
245 a20_adjust_cnt  equ ($-A20List)/2
246
247 slow_out:       out dx, al              ; Fall through
248
249 _io_delay:      out IO_DELAY_PORT,al
250                 out IO_DELAY_PORT,al
251                 ret
252
253 enable_a20:
254                 pushad
255                 mov byte [A20Tries],255 ; Times to try to make this work
256
257 try_enable_a20:
258
259 ;
260 ; Flush the caches
261 ;
262 %if DO_WBINVD
263                 call try_wbinvd
264 %endif
265
266 ;
267 ; If the A20 type is known, jump straight to type
268 ;
269                 mov bp,[A20Type]
270                 add bp,bp                       ; Convert to word offset
271 .adj4:          jmp word [bp+A20List]
272
273 ;
274 ; First, see if we are on a system with no A20 gate
275 ;
276 a20_dunno:
277 a20_none:
278                 mov byte [A20Type], A20_NONE
279                 call a20_test
280                 jnz a20_done
281
282 ;
283 ; Next, try the BIOS (INT 15h AX=2401h)
284 ;
285 a20_bios:
286                 mov byte [A20Type], A20_BIOS
287                 mov ax,2401h
288                 pushf                           ; Some BIOSes muck with IF
289                 int 15h
290                 popf
291
292                 call a20_test
293                 jnz a20_done
294
295 ;
296 ; Enable the keyboard controller A20 gate
297 ;
298 a20_kbc:
299                 mov dl, 1                       ; Allow early exit
300                 call empty_8042
301                 jnz a20_done                    ; A20 live, no need to use KBC
302
303                 mov byte [A20Type], A20_KBC     ; Starting KBC command sequence
304
305                 mov al,0D1h                     ; Write output port
306                 out 064h, al
307                 call empty_8042_uncond
308
309                 mov al,0DFh                     ; A20 on
310                 out 060h, al
311                 call empty_8042_uncond
312
313                 ; Apparently the UHCI spec assumes that A20 toggle
314                 ; ends with a null command (assumed to be for sychronization?)
315                 ; Put it here to see if it helps anything...
316                 mov al,0FFh                     ; Null command
317                 out 064h, al
318                 call empty_8042_uncond
319
320                 ; Verify that A20 actually is enabled.  Do that by
321                 ; observing a word in low memory and the same word in
322                 ; the HMA until they are no longer coherent.  Note that
323                 ; we don't do the same check in the disable case, because
324                 ; we don't want to *require* A20 masking (SYSLINUX should
325                 ; work fine without it, if the BIOS does.)
326 .kbc_wait:      push cx
327                 xor cx,cx
328 .kbc_wait_loop:
329                 call a20_test
330                 jnz a20_done_pop
331                 loop .kbc_wait_loop
332
333                 pop cx
334 ;
335 ; Running out of options here.  Final attempt: enable the "fast A20 gate"
336 ;
337 a20_fast:
338                 mov byte [A20Type], A20_FAST    ; Haven't used the KBC yet
339                 in al, 092h
340                 or al,02h
341                 and al,~01h                     ; Don't accidentally reset the machine!
342                 out 092h, al
343
344 .fast_wait:     push cx
345                 xor cx,cx
346 .fast_wait_loop:
347                 call a20_test
348                 jnz a20_done_pop
349                 loop .fast_wait_loop
350
351                 pop cx
352
353 ;
354 ; Oh bugger.  A20 is not responding.  Try frobbing it again; eventually give up
355 ; and report failure to the user.
356 ;
357
358                 dec byte [A20Tries]
359                 jnz try_enable_a20
360
361
362                 ; Error message time
363                 mov si,err_a20
364 print_err:
365                 lodsb
366                 and al,al
367                 jz die
368                 mov bx,7
369                 mov ah,0xe
370                 int 10h
371                 jmp print_err
372
373
374 die:
375                 sti
376 .hlt:           hlt
377                 jmp short .hlt
378
379 ;
380 ; A20 unmasked, proceed...
381 ;
382 a20_done_pop:   pop cx
383 a20_done:       popad
384                 ret
385
386 ;
387 ; This routine tests if A20 is enabled (ZF = 0).  This routine
388 ; must not destroy any register contents.
389 ;
390
391 ; This is the INT 1Fh vector, which is standard PCs is used by the
392 ; BIOS when the screen is in graphics mode.  Even if it is, it points to
393 ; data, not code, so it should be safe enough to fiddle with.
394 A20Test         equ (1Fh*4)
395
396 a20_test:
397                 push ds
398                 push es
399                 push cx
400                 push eax
401                 xor ax,ax
402                 mov ds,ax               ; DS == 0
403                 dec ax
404                 mov es,ax               ; ES == 0FFFFh
405                 mov cx,32               ; Loop count
406                 mov eax,[A20Test]
407                 cmp eax,[es:A20Test+10h]
408                 jne .a20_done
409                 push eax
410 .a20_wait:
411                 inc eax
412                 mov [A20Test],eax
413                 io_delay
414                 cmp eax,[es:A20Test+10h]
415                 loopz .a20_wait
416                 pop dword [A20Test]     ; Restore original value
417 .a20_done:
418                 pop eax
419                 pop cx
420                 pop es
421                 pop ds
422                 ret
423
424 disable_a20:
425                 pushad
426 ;
427 ; Flush the caches
428 ;
429 %if DO_WBINVD
430                 call try_wbinvd
431 %endif
432
433                 mov bp,[A20Type]
434                 add bp,bp                       ; Convert to word offset
435 .adj5:          jmp word [bp+A20DList]
436
437 a20d_bios:
438                 mov ax,2400h
439                 pushf                           ; Some BIOSes muck with IF
440                 int 15h
441                 popf
442                 jmp short a20d_snooze
443
444 ;
445 ; Disable the "fast A20 gate"
446 ;
447 a20d_fast:
448                 in al, 092h
449                 and al,~03h
450                 out 092h, al
451                 jmp short a20d_snooze
452
453 ;
454 ; Disable the keyboard controller A20 gate
455 ;
456 a20d_kbc:
457                 call empty_8042_uncond
458
459                 mov al,0D1h
460                 out 064h, al            ; Write output port
461                 call empty_8042_uncond
462
463                 mov al,0DDh             ; A20 off
464                 out 060h, al
465                 call empty_8042_uncond
466
467                 mov al,0FFh             ; Null command/synchronization
468                 out 064h, al
469                 call empty_8042_uncond
470
471                 ; Wait a bit for it to take effect
472 a20d_snooze:
473                 push cx
474                 mov cx, disable_wait
475 .delayloop:     call a20_test
476                 jz .disabled
477                 loop .delayloop
478 .disabled:      pop cx
479 a20d_dunno:
480 a20d_none:
481                 popad
482                 ret
483
484 ;
485 ; Routine to empty the 8042 KBC controller.  If dl != 0
486 ; then we will test A20 in the loop and exit if A20 is
487 ; suddenly enabled.
488 ;
489 empty_8042_uncond:
490                 xor dl,dl
491 empty_8042:
492                 call a20_test
493                 jz .a20_on
494                 and dl,dl
495                 jnz .done
496 .a20_on:        io_delay
497                 in al, 064h             ; Status port
498                 test al,1
499                 jz .no_output
500                 io_delay
501                 in al, 060h             ; Read input
502                 jmp short empty_8042
503 .no_output:
504                 test al,2
505                 jnz empty_8042
506                 io_delay
507 .done:          ret
508
509 ;
510 ; Execute a WBINVD instruction if possible on this CPU
511 ;
512 %if DO_WBINVD
513 try_wbinvd:
514                 wbinvd
515                 ret
516 %endif
517
518                 section .bss
519                 alignb 4
520 PMESP           resd 1                  ; Protected mode %esp
521
522                 section .idt nobits align=4096
523                 alignb 4096
524 pm_idt          resb 4096               ; Protected-mode IDT, followed by interrupt stubs
525
526
527
528
529 pm_entry:       equ 0x100000
530
531                 section .rodata
532                 align 2, db 0
533 call32_rmidt:
534                 dw 0ffffh               ; Limit
535                 dd 0                    ; Address
536
537                 section .data
538                 alignb 2
539 call32_pmidt:
540                 dw 8*256                ; Limit
541                 dd 0                    ; Address (entered later)
542
543                 section .text
544 ;
545 ; This is the main entrypoint in this function
546 ;
547 init32:
548                 mov bx,call32_call_start        ; Where to go in PM
549
550 ;
551 ; Enter protected mode.  BX contains the entry point relative to the
552 ; real-mode CS.
553 ;
554 call32_enter_pm:
555                 mov ax,cs
556                 mov ds,ax
557                 movzx ebp,ax
558                 shl ebp,4               ; EBP <- CS_BASE
559                 movzx ebx,bx
560                 add ebx,ebp             ; entry point += CS_BASE
561                 cli
562                 mov [SavedSP],sp
563                 cld
564                 call enable_a20
565                 lea eax,[ebp+.in_pm]
566                 mov [.pm_jmp+2],eax     ; Patch the PM jump
567                 jmp .sync
568 .sync:
569                 mov byte [call32_gdt+8+5],89h   ; Mark TSS unbusy
570                 o32 lgdt [call32_gdt]   ; Set up GDT
571                 o32 lidt [call32_pmidt] ; Set up IDT
572                 mov eax,cr0
573                 or al,1
574                 mov cr0,eax             ; Enter protected mode
575 .pm_jmp:        jmp 20h:strict dword 0
576
577
578                 bits 32
579 .in_pm:
580                 xor eax,eax             ; Available for future use...
581                 mov fs,eax
582                 mov gs,eax
583                 lldt ax
584
585                 mov al,28h              ; Set up data segments
586                 mov es,eax
587                 mov ds,eax
588                 mov ss,eax
589
590                 mov al,08h
591                 ltr ax
592
593                 mov esp,[ebp+PMESP]     ; Load protmode %esp if available
594                 jmp ebx                 ; Go to where we need to go
595
596 ;
597 ; This is invoked before first dispatch of the 32-bit code, in 32-bit mode
598 ;
599 call32_call_start:
600                 ;
601                 ; Set up a temporary stack in the bounce buffer;
602                 ; start32.S will override this to point us to the real
603                 ; high-memory stack.
604                 ;
605                 mov esp, (BOUNCE_SEG << 4) + 0x10000
606
607                 push dword stack_end            ; RM size
608                 push dword call32_gdt+CS_BASE
609                 push dword call32_handle_interrupt+CS_BASE
610                 push dword CS_BASE              ; Segment base
611                 push dword (BOUNCE_SEG << 4)    ; Bounce buffer address
612                 push dword call32_syscall+CS_BASE ; Syscall entry point
613
614                 call pm_entry-CS_BASE           ; Run the program...
615
616                 ; ... fall through to call32_exit ...
617
618 call32_exit:
619                 mov bx,call32_done      ; Return to command loop
620
621 call32_enter_rm:
622                 ; Careful here... the PM code may have relocated the
623                 ; entire RM code, so we need to figure out exactly
624                 ; where we are executing from.  If the PM code has
625                 ; relocated us, it *will* have adjusted the GDT to
626                 ; match, though.
627                 call .here
628 .here:          pop ebp
629                 sub ebp,.here
630                 mov ecx,ebp
631                 shr ecx,4
632                 mov [ebp+.rm_jmp+3],cx  ; Set segment
633                 jmp .sync
634 .sync:
635                 o32 sidt [ebp+call32_pmidt]
636                 cli
637                 cld
638                 mov [ebp+PMESP],esp     ; Save exit %esp
639                 xor esp,esp             ; Make sure the high bits are zero
640                 jmp 10h:.in_pm16        ; Return to 16-bit mode first
641
642                 bits 16
643 .in_pm16:
644                 mov ax,18h              ; Real-mode-like segment
645                 mov es,ax
646                 mov ds,ax
647                 mov ss,ax
648                 mov fs,ax
649                 mov gs,ax
650
651                 lidt [call32_rmidt]     ; Real-mode IDT (rm needs no GDT)
652                 mov eax,cr0
653                 and al,~1
654                 mov cr0,eax
655 .rm_jmp:        jmp MY_CS:.in_rm
656
657 .in_rm:                                 ; Back in real mode
658                 mov ds,cx
659                 mov es,cx
660                 mov fs,cx
661                 mov gs,cx
662                 mov ss,cx
663                 mov sp,[SavedSP]        ; Restore stack
664                 jmp bx                  ; Go to whereever we need to go...
665
666 call32_done:
667                 call disable_a20
668                 sti
669                 ret
670
671 ;
672 ; 16-bit support code
673 ;
674                 bits 16
675
676 ;
677 ; 16-bit interrupt-handling code
678 ;
679 call32_int_rm:
680                 pushf                           ; Flags on stack
681                 push cs                         ; Return segment
682                 push word .cont                 ; Return address
683                 push dword edx                  ; Segment:offset of IVT entry
684                 retf                            ; Invoke IVT routine
685 .cont:          ; ... on resume ...
686                 mov bx,call32_int_resume
687                 jmp call32_enter_pm             ; Go back to PM
688
689 ;
690 ; 16-bit system call handling code
691 ;
692 call32_sys_rm:
693                 pop gs
694                 pop fs
695                 pop es
696                 pop ds
697                 popad
698                 popfd
699                 retf                            ; Invoke routine
700 .return:
701                 pushfd
702                 pushad
703                 push ds
704                 push es
705                 push fs
706                 push gs
707                 mov bx,call32_sys_resume
708                 jmp call32_enter_pm
709
710 ;
711 ; 32-bit support code
712 ;
713                 bits 32
714
715 ;
716 ; This is invoked on getting an interrupt in protected mode.  At
717 ; this point, we need to context-switch to real mode and invoke
718 ; the interrupt routine.
719 ;
720 ; When this gets invoked, the registers are saved on the stack and
721 ; AL contains the register number.
722 ;
723 call32_handle_interrupt:
724                 movzx eax,al
725                 xor ebx,ebx             ; Actually makes the code smaller
726                 mov edx,[ebx+eax*4]     ; Get the segment:offset of the routine
727                 mov bx,call32_int_rm
728                 jmp call32_enter_rm     ; Go to real mode
729
730 call32_int_resume:
731                 popad
732                 iret
733
734 ;
735 ; Syscall invocation.  We manifest a structure on the real-mode stack,
736 ; containing the call32sys_t structure from <call32.h> as well as
737 ; the following entries (from low to high address):
738 ; - Target offset
739 ; - Target segment
740 ; - Return offset
741 ; - Return segment (== real mode cs)
742 ; - Return flags
743 ;
744 call32_syscall:
745                 pushfd                  ; Save IF among other things...
746                 pushad                  ; We only need to save some, but...
747                 cld
748                 call .here
749 .here:          pop ebp
750                 sub ebp,.here
751
752                 movzx edi,word [ebp+SavedSP]
753                 sub edi,54              ; Allocate 54 bytes
754                 mov [ebp+SavedSP],di
755                 add edi,ebp             ; Create linear address
756
757                 mov esi,[esp+11*4]      ; Source regs
758                 xor ecx,ecx
759                 mov cl,11               ; 44 bytes to copy
760                 rep movsd
761
762                 movzx eax,byte [esp+10*4] ; Interrupt number
763                 ; ecx == 0 here; adding it to the EA makes the
764                 ; encoding smaller
765                 mov eax,[ecx+eax*4]     ; Get IVT entry
766                 stosd                   ; Save in stack frame
767                 mov ax,call32_sys_rm.return     ; Return offset
768                 stosw                           ; Save in stack frame
769                 mov eax,ebp
770                 shr eax,4                       ; Return segment
771                 stosw                           ; Save in stack frame
772                 mov eax,[edi-12]        ; Return flags
773                 and eax,0x200cd7        ; Mask (potentially) unsafe flags
774                 mov [edi-12],eax        ; Primary flags entry
775                 stosw                   ; Return flags
776
777                 mov bx,call32_sys_rm
778                 jmp call32_enter_rm     ; Go to real mode
779
780                 ; On return, the 44-byte return structure is on the
781                 ; real-mode stack.  call32_enter_pm will leave ebp
782                 ; pointing to the real-mode base.
783 call32_sys_resume:
784                 movzx esi,word [ebp+SavedSP]
785                 mov edi,[esp+12*4]      ; Dest regs
786                 add esi,ebp             ; Create linear address
787                 and edi,edi             ; NULL pointer?
788                 jnz .do_copy
789 .no_copy:       mov edi,esi             ; Do a dummy copy-to-self
790 .do_copy:       xor ecx,ecx
791                 mov cl,11               ; 44 bytes
792                 rep movsd               ; Copy register block
793
794                 add word [ebp+SavedSP],44       ; Remove from stack
795
796                 popad
797                 popfd
798                 ret                     ; Return to 32-bit program