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