| /* |
| * Linux Boot Option ROM |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, see <http://www.gnu.org/licenses/>. |
| * |
| * Copyright Novell Inc, 2009 |
| * Authors: Alexander Graf <agraf@suse.de> |
| * |
| * Based on code in hw/pc.c. |
| */ |
| |
| #include "optionrom.h" |
| |
| #define BOOT_ROM_PRODUCT "Linux loader" |
| |
| BOOT_ROM_START |
| |
| run_linuxboot: |
| |
| cli |
| cld |
| |
| jmp copy_kernel |
| boot_kernel: |
| |
| read_fw FW_CFG_SETUP_ADDR |
| |
| mov %eax, %ebx |
| shr $4, %ebx |
| |
| /* All segments contain real_addr */ |
| mov %bx, %ds |
| mov %bx, %es |
| mov %bx, %fs |
| mov %bx, %gs |
| mov %bx, %ss |
| |
| /* CX = CS we want to jump to */ |
| add $0x20, %bx |
| mov %bx, %cx |
| |
| /* SP = cmdline_addr-real_addr-16 */ |
| read_fw FW_CFG_CMDLINE_ADDR |
| mov %eax, %ebx |
| read_fw FW_CFG_SETUP_ADDR |
| sub %eax, %ebx |
| sub $16, %ebx |
| mov %ebx, %esp |
| |
| /* Build indirect lret descriptor */ |
| pushw %cx /* CS */ |
| xor %ax, %ax |
| pushw %ax /* IP = 0 */ |
| |
| /* Clear registers */ |
| xor %eax, %eax |
| xor %ebx, %ebx |
| xor %ecx, %ecx |
| xor %edx, %edx |
| xor %edi, %edi |
| xor %ebp, %ebp |
| |
| /* Jump to Linux */ |
| lret |
| |
| |
| copy_kernel: |
| /* Read info block in low memory (0x10000 or 0x90000) */ |
| read_fw FW_CFG_SETUP_ADDR |
| shr $4, %eax |
| mov %eax, %es |
| xor %edi, %edi |
| read_fw_blob_addr32_edi(FW_CFG_SETUP) |
| |
| cmpw $0x203, %es:0x206 // if protocol >= 0x203 |
| jae 1f // have initrd_max |
| movl $0x37ffffff, %es:0x22c // else assume 0x37ffffff |
| 1: |
| |
| /* Check if using kernel-specified initrd address */ |
| read_fw FW_CFG_INITRD_ADDR |
| mov %eax, %edi // (load_kernel wants it in %edi) |
| read_fw FW_CFG_INITRD_SIZE // find end of initrd |
| add %edi, %eax |
| xor %es:0x22c, %eax // if it matches es:0x22c |
| and $-4096, %eax // (apart from padding for page) |
| jz load_kernel // then initrd is not at top |
| // of memory |
| |
| /* pc.c placed the initrd at end of memory. Compute a better |
| * initrd address based on e801 data. |
| */ |
| mov $0xe801, %ax |
| xor %cx, %cx |
| xor %dx, %dx |
| int $0x15 |
| |
| /* Output could be in AX/BX or CX/DX */ |
| or %cx, %cx |
| jnz 1f |
| or %dx, %dx |
| jnz 1f |
| mov %ax, %cx |
| mov %bx, %dx |
| 1: |
| |
| or %dx, %dx |
| jnz 2f |
| addw $1024, %cx /* add 1 MB */ |
| movzwl %cx, %edi |
| shll $10, %edi /* convert to bytes */ |
| jmp 3f |
| |
| 2: |
| addw $16777216 >> 16, %dx /* add 16 MB */ |
| movzwl %dx, %edi |
| shll $16, %edi /* convert to bytes */ |
| |
| 3: |
| read_fw FW_CFG_INITRD_SIZE |
| subl %eax, %edi |
| andl $-4096, %edi /* EDI = start of initrd */ |
| movl %edi, %es:0x218 /* put it in the header */ |
| |
| load_kernel: |
| /* We need to load the kernel into memory we can't access in 16 bit |
| mode, so let's get into 32 bit mode, write the kernel and jump |
| back again. */ |
| |
| /* Reserve space on the stack for our GDT descriptor. */ |
| mov %esp, %ebp |
| sub $16, %esp |
| |
| /* Now create the GDT descriptor */ |
| movw $((3 * 8) - 1), -16(%bp) |
| mov %cs, %eax |
| movzwl %ax, %eax |
| shl $4, %eax |
| addl $gdt, %eax |
| movl %eax, -14(%bp) |
| |
| /* And load the GDT */ |
| data32 lgdt -16(%bp) |
| mov %ebp, %esp |
| |
| /* Get us to protected mode now */ |
| mov $1, %eax |
| mov %eax, %cr0 |
| |
| /* So we can set ES to a 32-bit segment */ |
| mov $0x10, %eax |
| mov %eax, %es |
| |
| /* We're now running in 16-bit CS, but 32-bit ES! */ |
| |
| /* Load kernel and initrd */ |
| read_fw_blob_addr32_edi(FW_CFG_INITRD) |
| read_fw_blob_addr32(FW_CFG_KERNEL) |
| read_fw_blob_addr32(FW_CFG_CMDLINE) |
| |
| /* And now jump into Linux! */ |
| mov $0, %eax |
| mov %eax, %cr0 |
| |
| /* ES = CS */ |
| mov %cs, %ax |
| mov %ax, %es |
| |
| jmp boot_kernel |
| |
| /* Variables */ |
| |
| .align 4, 0 |
| gdt: |
| /* 0x00 */ |
| .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| |
| /* 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) */ |
| .byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00 |
| |
| /* 0x10: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) */ |
| .byte 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00 |
| |
| BOOT_ROM_END |