/* #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 /* Size of segment allocated to first32.c in protected mode */ #define FIRST32SIZE (6*1024) /* * 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. * One last note about prefixes: the data32 prefixes on all call _real_to_prot * instructions could be removed if the _real_to_prot function is changed to * deal correctly with 16 bit return addresses. I tried it, but failed. */ /************************************************************************** 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 jump to the routine */ movl %esp,%eax movl %eax,initsp movl $RELOC+FIRST32SIZE,%esp /* change stack */ /* copy the arguments on the stack over */ pushl 12(%eax) /* bootp */ pushl 8(%eax) /* header */ pushl 4(%eax) /* ebinfo */ call first /* fall through */ _exit: /* we reset sp to the location just before entering first instead of relying on the return from first 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 /************************************************************************** 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 call _prot_to_real .code16 movl $1,%ebx movb $0x0e,%ah movb %cl,%al int $0x10 DATA32 call _real_to_prot .code32 popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** E820_MEMSIZE - Get a listing of memory regions **************************************************************************/ .globl meme820 #define SMAP 0x534d4150 meme820: pushl %ebp movl %esp, %ebp pushl %ebx pushl %esi pushl %edi movl 8(%ebp), %edi /* Address to return e820 structures at */ subl $RELOC, %edi movl 12(%ebp), %esi /* Maximum number of e820 structurs to return */ pushl %esi call _prot_to_real .code16 xorl %ebx, %ebx jmpe820: movl $0xe820, %eax movl $SMAP, %edx movl $20, %ecx /* %di was setup earlier */ int $0x15 jc bail820 cmpl $SMAP, %eax jne bail820 good820: /* If this is useable memory, we save it by simply advancing %di by * sizeof(e820rec) */ decl %esi testl %esi,%esi jz bail820 addw $20, %di again820: cmpl $0, %ebx /* check to see if %ebx is set to EOF */ jne jmpe820 bail820: DATA32 call _real_to_prot .code32 popl %eax subl %esi, %eax /* Compute how many structure we read */ /* Restore everything else */ popl %edi popl %esi popl %ebx movl %ebp, %esp popl %ebp ret /************************************************************************** MEMSIZE - Determine size of extended memory **************************************************************************/ .globl memsize memsize: pushl %ebx pushl %esi pushl %edi call _prot_to_real .code16 stc # fix to work around buggy xorw %cx,%cx # BIOSes which dont clear/set xorw %dx,%dx # carry on pass/error of # e801h memory size call # or merely pass cx,dx though # without changing them. movw $0xe801,%ax int $0x15 jc 3f cmpw $0,%cx # Kludge to handle BIOSes jne 1f # which report their extended cmpw $0,%dx # memory in AX/BX rather than je 2f # CX/DX. The spec I have read 1: movw %cx,%ax # seems to indicate AX/BX movw %dx,%bx # are more reasonable anyway... 2: andl $0xffff,%eax andl $0xffff,%ebx shll $6,%ebx addl %ebx,%eax jmp 4f 3: movw $0x8800,%ax int $0x15 andl $0xffff,%eax 4: movl %eax,%esi DATA32 call _real_to_prot .code32 movl %esi,%eax popl %edi popl %esi popl %ebx ret /************************************************************************** BASEMEMSIZE - Get size of the conventional (base) memory **************************************************************************/ .globl basememsize basememsize: call _prot_to_real .code16 int $0x12 movw %ax,%cx DATA32 call _real_to_prot .code32 movw %cx,%ax ret /************************************************************************** XSTARTLINUX - Transfer control to the kernel just loaded in real mode **************************************************************************/ .globl xstartlinux xstartlinux: pushl %ebp movl %esp,%ebp pushl %ebx pushl %esi pushl %edi movl 8(%ebp),%eax /* Convert to segment:offset form */ movl %eax,%ebx andl $0xF,%eax andl $0xFFFFFFF0,%ebx shll $12,%ebx orl %eax,%ebx call _prot_to_real .code16 /* (isac) some kernels expect segment registers to point to start of real-mode * code (see Documentation/i386/boot.txt:RUNNING THE KERNEL) */ movl %ebx,%eax shrl $16,%eax subw $0x20,%ax /* RM start is seg. offset 0x20 before entry point */ movw %ax,%ds movw %ax,%es movw %ax,%fs movw %ax,%gs movl $((RELOC<<12)+(1f-RELOC)),%eax pushl %eax pushl %ebx lret 1: addw $4,%sp /* XXX or is this 10 in case of a 16bit "ret" */ DATA32 call _real_to_prot .code32 popl %edi popl %esi popl %ebx popl %ebp ret /************************************************************************** _REAL_TO_PROT - Go from REAL mode to Protected Mode **************************************************************************/ .globl _real_to_prot _real_to_prot: .code16 cli cs ADDR32 lgdt gdtarg-_start movl %cr0,%eax orl $CR0_PE,%eax movl %eax,%cr0 /* turn on protected mode */ /* flush prefetch queue, and reload %cs:%eip */ DATA32 ljmp $KERN_CODE_SEG,$1f 1: .code32 /* reload other segment registers */ movl $KERN_DATA_SEG,%eax movl %eax,%ds movl %eax,%es movl %eax,%ss addl $RELOC,%esp /* Fix up stack pointer */ xorl %eax,%eax movl %eax,%fs movl %eax,%gs popl %eax /* Fix up return address */ addl $RELOC,%eax pushl %eax ret /************************************************************************** _PROT_TO_REAL - Go from Protected Mode to REAL Mode **************************************************************************/ .globl _prot_to_real _prot_to_real: .code32 popl %eax subl $RELOC,%eax /* Adjust return address */ pushl %eax subl $RELOC,%esp /* Adjust stack pointer */ #ifdef GAS291 ljmp $REAL_CODE_SEG,$1f-RELOC /* jump to a 16 bit segment */ #else ljmp $REAL_CODE_SEG,$1f-_start /* jump to a 16 bit segment */ #endif /* GAS291 */ 1: .code16 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 andl $0!CR0_PE,%eax movl %eax,%cr0 /* make intersegment jmp to flush the processor pipeline * and reload %cs:%eip (to clear upper 16 bits of %eip). */ DATA32 ljmp $(RELOC)>>4,$2f-_start 2: /* we are in real mode now * set up the real mode segment registers : %ds, $ss, %es */ movw %cs,%ax movw %ax,%ds movw %ax,%es movw %ax,%ss sti DATA32 ret /* There is a 32 bit return address on the stack */ .code32 /************************************************************************** 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,(RELOC&0xffff) .byte (RELOC>>16),0x9b,0x00,(RELOC>>24) _rmds: /* 16 bit real mode data segment */ .word 0xffff,(RELOC&0xffff) .byte (RELOC>>16),0x93,0x00,(RELOC>>24) gdtsave: .long 0,0,0 /* previous GDT */ /* Other variables */ initsp: .long 0