/* * Converted to use Eric Biederman's _real_call routine which frees * the 16 bit code from running in the same 64kB segment as the * 32 bit code's stack. */ /* #defines because ljmp wants a number, probably gas bug */ /* .equ KERN_CODE_SEG,_pmcs-_gdt */ #define KERN_CODE_SEG 0x08 .equ KERN_DATA_SEG,_pmds-_gdt /* .equ REAL_CODE_SEG,_rmcs-_gdt */ #define REAL_CODE_SEG 0x18 .equ REAL_DATA_SEG,_rmds-_gdt .equ CR0_PE,1 #ifdef GAS291 #define DATA32 data32; #define ADDR32 addr32; #define LJMPI(x) ljmp x #else #define DATA32 data32 #define ADDR32 addr32 /* newer GAS295 require #define LJMPI(x) ljmp *x */ #define LJMPI(x) ljmp x #endif #define DO_REAL_CALL pushl $10f; pushl $20f-10f; call _real_call; .section ".text16"; 10: .code16 #define DO_REAL_RETURN ret; 20: .code32; .previous .section ".text" .section ".text16","ax",@progbits .previous .code32 .arch i386 /* * NOTE: if you write a subroutine that is called from C code (gcc/egcs), * then you only have to take care of %ebx, %esi, %edi and %ebp. These * registers must not be altered under any circumstance. All other registers * may be clobbered without any negative side effects. If you don't follow * this rule then you'll run into strange effects that only occur on some * gcc versions (because the register allocator may use different registers). * * All the data32 prefixes for the ljmp instructions are necessary, because * the assembler emits code with a relocation address of 0. This means that * all destinations are initially negative, which the assembler doesn't grok, * because for some reason negative numbers don't fit into 16 bits. The addr32 * prefixes are there for the same reasons, because otherwise the memory * references are only 16 bit wide. Theoretically they are all superfluous. */ /************************************************************************** START - Where all the fun begins.... **************************************************************************/ /* this must be the first thing in the file because we enter from the top */ .global _start _start: /* We have to use our own GDT when running in our segment because the old GDT will have the wrong descriptors for the real code segments */ sgdt gdtsave /* save old GDT */ lgdt gdtarg /* load ours */ /* reload the segment registers */ movl $KERN_DATA_SEG,%eax movl %eax,%ds movl %eax,%es movl %eax,%ss movl %eax,%fs movl %eax,%gs /* flush prefetch queue, and reload %cs:%eip */ ljmp $KERN_CODE_SEG,$1f 1: /* save the stack pointer and call the routine */ movl %esp,%eax movl %eax,initsp movl $RELOC+0x20000,%esp /* change stack */ pushl 12(%eax) /* replicate args on new stack */ pushl 8(%eax) pushl 4(%eax) call menu _exit: /* we reset sp to the location just before entering first instead of relying on the return from menu because exit could have been called from anywhere */ movl initsp,%ebx movl %ebx,%esp lgdt gdtsave /* restore old GDT */ ret .globl exit exit: movl 4(%esp),%eax jmp _exit /************************************************************************** SET_SEG_BASE - Set the base address of a segment register Stolen from Etherboot 5.1. With thanks to Eric Biederman **************************************************************************/ /* .globl set_seg_base */ set_seg_base: /* Low half of the gdt base */ movl 4(%esp), %eax shll $16, %eax /* High half of the gdt base */ movl 4(%esp), %ecx shrl $16, %ecx andl $0xff, %ecx movl 4(%esp), %edx andl $0xff000000, %edx orl %edx, %ecx movl 8(%esp), %edx /* Fixup the code segment */ andl $0x0000ffff, 0(%edx) orl %eax , 0(%edx) andl $0x00ffff00, 4(%edx) orl %ecx , 4(%edx) /* Fixup the data segment */ andl $0x0000ffff, 8(%edx) orl %eax , 8(%edx) andl $0x00ffff00, 12(%edx) orl %ecx , 12(%edx) ret /************************************************************************** _REAL_CALL - Run some code in real mode. Stolen from Etherboot 5.1. With thanks to Eric Biederman **************************************************************************/ /* MAX_REAL_MODE_STACK is carefully tuned to work * with the stack bottom at 0x7c00 while not chancing * overwriting data below 0x500. */ #define MAX_REAL_MODE_STACK 29696 #define RADDR(sym) (((sym) - _end16) + MAX_REAL_MODE_STACK) .balign 4 /* .globl real_mode_stack */ real_mode_stack: .long 0x7c00 /* Put the stack just below the dos load address */ real_stack_top: .long 0 _save_esp: .long 0 /* .globl _real_call */ _real_call: /* Save the original %esp value */ movl %esp, _save_esp /* Save the temporary registers I use */ pushl $0 pushl %ebx pushl %ecx pushl %edx pushl %esi pushl %edi pushl %ebp /* Load up the registers */ movl 32(%esp), %ecx /* The 16bit code len */ movl 36(%esp), %esi /* The 16bit code start */ movl virt_offset, %ebp /* The virtual offset */ /* stack top = phys_to_virt(real_mode_stack - MAX_REAL_MODE_STACK) */ movl real_mode_stack, %ebx /* The stack top */ subl $MAX_REAL_MODE_STACK, %ebx movl %ebx, real_stack_top subl %ebp, %ebx /* Save the real mode stack top */ movl %ebx, 24(%esp) /* Compute where the copied code goes */ leal RADDR(__real_call)(%ebx), %edi subl %ecx, %edi andl $0xfffffffc, %edi /* 4 byte aligned */ /* Remember where the code is executed */ movl %edi, %eax subl %ebx, %eax movw %ax, real_ip /* Copy the user code onto the real mode stack */ rep movsb /* Copy the trampoline onto the stack */ movl $__real_call, %esi movl $_end16 - __real_call, %ecx leal RADDR(__real_call)(%ebx), %edi rep movsb /* Fixup real_gdtarg */ leal _gdt(%ebp), %eax movl %eax, RADDR(real_gdtarg +2)(%ebx) /* Fixup the gdt */ pushl $_rmcs leal 0(%ebx, %ebp), %eax pushl %eax call set_seg_base addl $8, %esp /* Restore the saved registers */ popl %ebp popl %edi popl %esi popl %edx popl %ecx popl %ebx /* And switch stacks */ popl %esp movzwl RADDR(real_ip)(%esp), %eax addl %eax, %esp /* Setup for jump to real mode */ movl real_stack_top, %eax shrl $4, %eax pushw %ax pushw $RADDR(real16) /* Switch stack from %esp 32bit virtual to %sp 16bit physical */ addl virt_offset, %esp subl real_stack_top, %esp /* Jump to 16bit code */ ljmp $REAL_CODE_SEG, $RADDR(code16) /* jump to a 16 bit segment */ _real_call_ret: /* reload segment registers */ movl $KERN_DATA_SEG,%eax movl %eax,%ds movl %eax,%es movl %eax,%ss movl %eax,%fs movl %eax,%gs /* Restore the stack */ movl _save_esp, %esp /* Restore the direction flag */ cld /* Get the real mode stack pointer */ movl real_stack_top, %eax subl virt_offset, %eax pushl %eax movzwl RADDR(real_sp)(%eax), %eax addl 0(%esp), %eax addl $4, %esp /* Return to my caller */ ret $8 .balign 16 __real_call: real_sp: .word 0 real_ip: .word 0 real_gdtarg: .word _gdt_end - _gdt - 1 /* limit */ .long _gdt /* addr */ .code16 code16: /* Load 16bit segment descriptors to force 16bit segment limit */ movw $REAL_DATA_SEG, %ax movw %ax,%ds movw %ax,%ss movw %ax,%es movw %ax,%fs movw %ax,%gs /* clear the PE bit of CR0 */ movl %cr0,%eax andb $0!CR0_PE,%al movl %eax,%cr0 /* make intersegment jmp to flush the processor pipeline * and reload %cs:%eip (to clear upper 16 bits of %eip). */ lret real16: /* we are in real mode now * set up the real mode segment registers : %ds, $ss, %es */ movw %cs, %ax movw %ax, %ss movw %ax, %ds movw %ax, %fs movw %ax, %gs /* Enable interrupts */ sti /* Call the user supplied code */ call *RADDR(real_ip) /* Save the stack pointer */ /* Reload %ds */ movw %cs, %ax movw %ax, %ds movw %sp, RADDR(real_sp) /* Disable interrupts */ cli /* Switch back to protected mode */ cs DATA32 lgdt RADDR(real_gdtarg) movl %cr0, %eax orb $CR0_PE, %al movl %eax, %cr0 /* turn on protected mode */ /* flush prefetch queue, and reload %cs:%eip */ DATA32 ljmp $KERN_CODE_SEG, $_real_call_ret .code32 __end16: .balign 16 _end16: .code32 /************************************************************************** CURRTICKS - Get Time Use direct memory access to BIOS variables, longword 0040:006C (ticks today) and byte 0040:0070 (midnight crossover flag) instead of calling timeofday BIOS interrupt. **************************************************************************/ .globl currticks currticks: pushl %ebp pushl %ebx pushl %esi pushl %edi DO_REAL_CALL DO_REAL_RETURN movl virt_offset,%ebp negl %ebp movl 0x46C(%ebp), %eax movb 0x470(%ebp), %bl cmpb $0, %bl je notmidnite movb $0, 0x470(%ebp) /* clear the flag */ addl $0x1800b0,days /* 0x1800b0 ticks per day */ notmidnite: addl days,%eax popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** console_cls() BIOS call "INT 10H Function 0Fh" to get current video mode Call with %ah = 0x0f Returns %al = (video mode) %bh = (page number) BIOS call "INT 10H Function 00h" to set the video mode (clears screen) Call with %ah = 0x00 %al = (video mode) **************************************************************************/ .globl console_cls console_cls: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL movb $0xf, %ah int $0x10 /* Get Current Video mode */ xorb %ah, %ah int $0x10 /* Set Video mode (clears screen) */ DO_REAL_RETURN popl %edi popl %esi popl %ebx ret /************************************************************************** console_nocursor() BIOS call "INT 10H Function 01h" to set cursor type Call with %ah = 0x01 %ch = cursor starting scanline %cl = cursor ending scanline **************************************************************************/ .globl console_nocursor console_nocursor: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL movw $0x2000, %cx movb $0x1, %ah int $0x10 DO_REAL_RETURN popl %edi popl %esi popl %ebx ret /************************************************************************** console_getxy() BIOS call "INT 10H Function 03h" to get cursor position Call with %ah = 0x03 %bh = page Returns %ch = starting scan line %cl = ending scan line %dh = row (0 is top) %dl = column (0 is left) **************************************************************************/ .globl console_getxy console_getxy: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL xorb %bh, %bh /* set page to 0 */ movb $0x3, %ah int $0x10 /* get cursor position */ DO_REAL_RETURN xor %eax, %eax movb %dl, %ah movb %dh, %al popl %edi popl %esi popl %ebx ret /************************************************************************** console_gotoxy(x,y) BIOS call "INT 10H Function 02h" to set cursor position Call with %ah = 0x02 %bh = page %dh = row (0 is top) %dl = column (0 is left) **************************************************************************/ .globl console_gotoxy console_gotoxy: pushl %ebp movl %esp,%ebp pushl %ebx pushl %esi pushl %edi movb 0x8(%ebp), %dl /* %dl = x */ movb 0xC(%ebp), %dh /* %dh = y */ DO_REAL_CALL xorb %bh, %bh /* set page to 0 */ movb $0x2, %ah int $0x10 /* set cursor position */ DO_REAL_RETURN popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** * console_setattrib(attr) : Sets the character attributes for character at * current cursor position. * * Bitfields for character's display attribute: * Bit(s) Description * 7 foreground blink * 6-4 background color * 3 foreground bright * 2-0 foreground color * * Values for character color: * Normal Bright * 000b black dark gray * 001b blue light blue * 010b green light green * 011b cyan light cyan * 100b red light red * 101b magenta light magenta * 110b brown yellow * 111b light gray white * * BIOS call "INT 10H Function 08h" to read character and attribute data * Call with %ah = 0x08 * %bh = page * Returns %ah = character attribute * %al = character value * BIOS call "INT 10H Function 09h" to write character and attribute data * Call with %ah = 0x09 * %al = character value * %bh = page * %bl = character attribute * %cx = count to display (???, possible side-effects!!) **************************************************************************/ .globl console_setattrib console_setattrib: pushl %ebp movl %esp,%ebp pushl %ebx pushl %esi pushl %edi movl 0x8(%ebp), %ecx xorl %ebx, %ebx DO_REAL_CALL movb $0x8, %ah int $0x10 movb $0x9, %ah movb %cl, %bl movw $1, %cx int $0x10 DO_REAL_RETURN popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** CONSOLE_PUTC - Print a character on console **************************************************************************/ .globl console_putc console_putc: pushl %ebp movl %esp,%ebp pushl %ebx pushl %esi pushl %edi movb 8(%ebp),%cl DO_REAL_CALL movl $1,%ebx movb $0x0e,%ah movb %cl,%al int $0x10 DO_REAL_RETURN popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** CONSOLE_GETC - Get a character from console **************************************************************************/ .globl console_getc console_getc: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL movb $0x0,%ah int $0x16 movb %al,%bl DO_REAL_RETURN xor %eax,%eax movzbl %bl,%eax popl %edi popl %esi popl %ebx ret /************************************************************************** console_getkey() BIOS call "INT 16H Function 00H" to read character from keyboard Call with %ah = 0x10 Return: %ah = keyboard scan code %al = ASCII character **************************************************************************/ .globl console_getkey console_getkey: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL movb $0x0,%ah int $0x16 movw %ax, %bx DO_REAL_RETURN movzwl %bx, %eax popl %edi popl %esi popl %ebx ret /************************************************************************** console_checkkey() if there is a character pending, return it; otherwise return -1 BIOS call "INT 16H Function 01H" to check whether a character is pending Call with %ah = 0x1 Return: If key waiting to be input: %ah = keyboard scan code %al = ASCII character Zero flag = clear else Zero flag = set **************************************************************************/ .globl console_checkkey console_checkkey: pushl %ebx pushl %esi pushl %edi xorl %ebx, %ebx DO_REAL_CALL movb $0x1, %ah int $0x16 jz 1f movw %ax, %bx jmp 2f 1: movl $0xFFFFFFFF, %ebx 2: DO_REAL_RETURN movzwl %bx, %eax popl %edi popl %esi popl %ebx ret /************************************************************************** ISCHAR - Check for keyboard interrupt **************************************************************************/ .globl console_ischar console_ischar: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL xorw %bx,%bx movb $0x1,%ah int $0x16 jz 2f movb %al,%bl 2: DO_REAL_RETURN movzbl %bl,%eax popl %edi popl %esi popl %ebx ret /************************************************************************** GETSHIFT - Get keyboard shift state **************************************************************************/ .globl console_getshift console_getshift: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL movb $2,%ah int $0x16 andb $0xdf,%al movw %ax,%bx DO_REAL_RETURN movzbl %bl,%eax popl %edi popl %esi popl %ebx ret /************************************************************************** INT10 - Call Interrupt 0x10 **************************************************************************/ .globl _int10 _int10: push %ebp mov %esp,%ebp push %ebx push %esi push %edi movw 8(%ebp),%si movw 10(%ebp),%bx movw 12(%ebp),%cx movw 14(%ebp),%dx DO_REAL_CALL movw %si,%ax int $0x10 movw %ax,%si DO_REAL_RETURN movl %esi,%eax andl $0xFFFF,%eax movw %ax,int10ret movw %bx,int10ret+2 shl $16,%ebx orl %ebx,%eax movw %cx,int10ret+4 movw %dx,int10ret+6 pop %edi pop %esi pop %ebx pop %ebp ret .globl int10ret int10ret: .word 0,0,0,0 /************************************************************************** CPU_NAP - Save power by halting the CPU until the next interrupt **************************************************************************/ .globl cpu_nap cpu_nap: pushl %ebx pushl %esi pushl %edi DO_REAL_CALL hlt DO_REAL_RETURN popl %edi popl %esi popl %ebx ret /************************************************************************** SETJMP - Save stack context for non-local goto **************************************************************************/ .globl setjmp setjmp: movl 4(%esp),%ecx movl 0(%esp),%edx movl %edx,0(%ecx) movl %ebx,4(%ecx) movl %esp,8(%ecx) movl %ebp,12(%ecx) movl %esi,16(%ecx) movl %edi,20(%ecx) movl %eax,24(%ecx) movl $0,%eax ret /************************************************************************** LONGJMP - Non-local jump to a saved stack context **************************************************************************/ .globl longjmp longjmp: movl 4(%esp),%edx movl 8(%esp),%eax movl 0(%edx),%ecx movl 4(%edx),%ebx movl 8(%edx),%esp movl 12(%edx),%ebp movl 16(%edx),%esi movl 20(%edx),%edi cmpl $0,%eax jne 1f movl $1,%eax 1: movl %ecx,0(%esp) ret /************************************************************************** GLOBAL DESCRIPTOR TABLE **************************************************************************/ .align 4 _gdt: gdtarg: .word 0x27 /* limit */ .long _gdt /* addr */ .byte 0,0 _pmcs: /* 32 bit protected mode code segment */ .word 0xffff,0 .byte 0,0x9f,0xcf,0 _pmds: /* 32 bit protected mode data segment */ .word 0xffff,0 .byte 0,0x93,0xcf,0 _rmcs: /* 16 bit real mode code segment */ .word 0xffff,(0&0xffff) .byte (0>>16),0x9b,0x00,(0>>24) _rmds: /* 16 bit real mode data segment */ .word 0xffff,(0&0xffff) .byte (0>>16),0x93,0x00,(0>>24) _gdt_end: gdtsave: .long 0,0,0 /* previous GDT */ virt_offset: .long 0 initsp: .long 0 days: .long 0