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