| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/arch/asm.h> |
| |
| #include "phys32.h" |
| #include "linuxboot-asm.h" |
| |
| // The phys.ld linker script places this first in the image. |
| // The linuxboot.ld linker script arranges that this means it |
| // it is "before" the actual fixed load address of the bzImage |
| // 32-bit code (which includes the start32.S entry point). |
| .section .boot.header, "awx", %progbits |
| |
| // This is the putative boot_params object at the start of the image. |
| // See linuxboot.h for full details about the protocol. In fact, this |
| // just corresponds to the boot_params layout for purposes of locating |
| // the boot_params::setup_header, below. |
| .org 0 |
| .label zero_page |
| |
| // This is not used by the Linux/x86 boot protocol. Historically, |
| // this format could be used directly as a Master Boot Record image. |
| // This ancient protocol consists of simply loading the first sector |
| // into a well-known location in memory and jumping to it in 16-bit |
| // real mode. This is not supported by Linux kernels any more, but a |
| // legacy stub is provided to panic nicely if loaded in the original |
| // x86 PC way by firmware supporting the old 16-bit x86 BIOS ABI. |
| .code16 |
| .function MbrEntry, nosection=nosection |
| // This is the traditional physical load address. Make sure |
| // it's the exact start of the %cs segment. |
| ljmp $0x07c0, $(0f - MbrEntry) |
| 0: |
| |
| // Reset segment registers to be safe and don't trust any stack pointer. |
| mov %cs, %ax |
| mov %ax, %ds |
| mov %ax, %es |
| mov %ax, %ss |
| mov %ax, %ss |
| xor %sp, %sp |
| |
| // Normalize flags that might affect the BIOS or instructions used here. |
| sti |
| cld |
| |
| // cf https://en.wikipedia.org/wiki/BIOS_interrupt_call |
| mov $(MbrEntryMessage - MbrEntry), %si |
| mov $MbrEntryMessageSize, %cx |
| .Lbios_write_char: |
| lodsb // %ax = *%si++ |
| mov $0xe, %ah // Write character in TTY mode. |
| mov $0xd, %bx // Color (light magenta). |
| int $0x10 // https://en.wikipedia.org/wiki/INT_10H |
| loop .Lbios_write_char |
| |
| .Lbios_wait_for_key: |
| xor %ax, %ax |
| int $0x16 |
| |
| .Lbios_reboot: |
| int $0x19 // Ask the BIOS to reboot. Should not return. |
| ljmp $0xf000, $0xfff0 // If it does, jump directly to the BIOS entry point. |
| .end_function |
| |
| .object MbrEntryMessage, nosection=nosection |
| .ascii "Legacy x86 MBR booting is not supported.\r\n" |
| .ascii "Install a boot loader that supports Linux/x86 bzImage format.\r\n" |
| .ascii "Hit any key to reboot...\r\n" |
| MbrEntryMessageSize = . - MbrEntryMessage |
| .end_object |
| |
| // The space before boot_params::hdr is not examined by the boot loader. A |
| // boot loader using the 16-bit entry protocol reads hdr.setup_sects and then |
| // loads this whole file's contents into memory at zero_page before entering |
| // at setup_header::jump, below. |
| .org BOOT_PARAMS_HDR |
| |
| .object setup_header, nosection=nosection |
| // This tells the boot loader how many 512-byte sectors after this first |
| // one to load. LINUXBOOT_SETUP_SIZE is defined at the end of the file. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_SETUP_SECTS |
| .byte ((LINUXBOOT_SETUP_SIZE >> 9) - 1) |
| |
| // This tells the boot loader how many 16-byte units (Intelspeak paragraphs) |
| // to load at the fixed load address for the 32-bit kernel image. The |
| // linuxboot.ld linker script calculates the value of LINUXBOOT_SYSSIZE. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_SYSSIZE |
| .int LINUXBOOT_SYSSIZE |
| |
| // This is a required magic number the boot loader checks for. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_BOOT_FLAG |
| .short LINUXBOOT_BOOT_FLAG |
| |
| // This is offset 512, where the 16-bit entry point is. Since there's a |
| // required header field right after, this must be a two-byte instruction. |
| // So we make it a jump to past the end of the fixed header layout, where |
| // we define the real-mode 16-bit entry path. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_JUMP |
| // This must be a 1-byte displacement to make a 2-byte instruction that |
| // fits before the next field. That makes a maximum jump distance of 127, |
| // to 0x202 + 127 = 0x281. |
| jmp Phys16Entry |
| |
| // This is a required magic number the boot loader checks for. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_HEADER |
| .ascii "HdrS" |
| |
| // This tells the boot loader which precise version of the protocol for |
| // encoding bits in these headers this kernel image is compatible with. |
| // Version 2.06 is not the newest, but none of the Zircon shim code needs |
| // to take advantange of any of the newer features. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_VERSION |
| .short 0x0206 |
| |
| // This indicates "bzImage" format: load the 32-bit code at 1MiB. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_LOADFLAGS |
| .byte LOADFLAGS_LOADED_HIGH |
| |
| // This gives the boot loader license to place the initrd RAMDISK image |
| // anywhere in the 4G address space. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_INITRD_ADDR_MAX |
| .int 0xffffffff |
| |
| // Load addresses must be aligned to 4KiB. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_KERNEL_ALIGNMENT |
| .int 0x1000 |
| |
| // This constrains the maximum size of kernel command line data passed. |
| .org BOOT_PARAMS_HDR + SETUP_HEADER_CMDLINE_SIZE |
| .int 0xffffffff |
| |
| // The rest of setup_header contains fields a boot loader might examine |
| // or modify though they're not meaningful to the version of the protocol |
| // we support if they have zero-initialized values. |
| .org BOOT_PARAMS_HDR + SIZEOF_SETUP_HEADER |
| .end_object |
| |
| // This is close to the farthest the two-byte jump above can reach. |
| // The boot loader enters at offset 512, which is that short jump here. |
| .function Phys16Entry, nosection=nosection |
| // Normalize flags that might affect the BIOS or instructions used here. |
| sti |
| cld |
| |
| // To be safe, only trust %ds to have been set by the boot loader. |
| mov %ds, %ax |
| mov %ax, %es |
| |
| // Copy the setup_header, as modified by the boot loader, into the |
| // boot_params struct to be provided to the 32-bit entry path. |
| mov $(boot_params + BOOT_PARAMS_HDR - zero_page), %di |
| mov $(setup_header - zero_page), %si |
| mov $SIZEOF_SETUP_HEADER, %cx |
| rep movsb |
| |
| // boot_params::e820_table needs to be populated using the legacy BIOS ABI. |
| xor %ebx, %ebx // Cleared for first BIOS call. |
| movb %bl, (boot_params + BOOT_PARAMS_E820_ENTRIES - zero_page) |
| .Le820_loop: |
| // If you've been wondering where the name came from... |
| mov $0xe820, %eax |
| |
| // %ebx is zero for the first iteration and is updated by the BIOS |
| // to new, unspecified, nonzero values with each iteration. |
| |
| // This gives the buffer size: we read only one entry at a time. |
| mov $SIZEOF_E820ENTRY, %ecx |
| |
| // This is a second magic number that must be set exactly. |
| mov $E820_MAGIC, %edx |
| |
| // Use a single temporary buffer so as not to trust the BIOS to handle |
| // an address advancing through the table properly. |
| mov $(gE820Buffer - zero_page), %di |
| |
| // Call the BIOS. The carry flag indicates the call failed. |
| // Otherwise a few registers have results. |
| int $0x15 |
| jc .Le820_end |
| |
| // Ignore the whole table if the BIOS is not following the protocol. |
| cmp $E820_MAGIC, %eax |
| je 0f |
| movb $0, (boot_params + BOOT_PARAMS_E820_ENTRIES - zero_page) |
| jmp .Le820_end |
| 0: |
| |
| // Copy the entry. |
| mov $SIZEOF_E820ENTRY, %cx |
| mov $(gE820Buffer - zero_page), %si |
| mov $(boot_params + BOOT_PARAMS_E820_TABLE - zero_page), %di |
| movb (boot_params + BOOT_PARAMS_E820_ENTRIES - zero_page), %al |
| mov %al, %dl |
| mul %cl |
| add %ax, %di |
| rep movsb |
| |
| // Update the boot_params::e820_entries counter. |
| inc %dl |
| movb %dl, (boot_params + BOOT_PARAMS_E820_ENTRIES - zero_page) |
| |
| // Bail out if the table is full. |
| cmpb $MAX_E820_TABLE_ENTRIES, %dl |
| jle .Le820_end |
| |
| // The BIOS returns zero in %ebx when there are no more entries. |
| test %ebx, %ebx |
| jnz .Le820_loop |
| .Le820_end: |
| |
| // Make sure interrupts are disabled just in case. |
| cli |
| |
| // Materialize the linear address of the code below, which will be the |
| // new PC in the flat 32-bit code segment. |
| mov %ds, %eax |
| shl $4, %eax |
| add $(Protected32Entry - zero_page), %eax |
| |
| // Push the long return address that will be popped below. |
| pushl $PHYS32_CODE32_SEL |
| .cfi_adjust_cfa_offset 4 |
| pushl %eax |
| .cfi_adjust_cfa_offset 4 |
| |
| // Now point %esi at the boot_params being filled out here. |
| // This is the argument register for the 32-bit entry path. |
| mov %ds, %esi |
| shl $4, %esi |
| add $(boot_params - zero_page), %esi |
| |
| // Linux booting protocol requires these all be cleared. It doesn't really |
| // matter to our code, but it makes it consistent with what a boot loader |
| // doing direct 32-bit entry will do. |
| xor %ebx, %ebx |
| xor %ecx, %ecx |
| xor %edx, %edx |
| xor %ebp, %ebp |
| xor %edi, %edi |
| |
| // Build a temporary descriptor pointing at the GDT to load it. |
| // We reuse the gdt32.cc setup that start32.S will load again later. |
| // But we need it loaded first so we can enable 32-bit protected mode. |
| movl $gPhys32Gdt, -4(%esp) |
| movw $(PHYS32_GDT_SIZE - 1), -6(%esp) |
| lgdtl -6(%esp) |
| |
| // Enable protected mode. The Intel manual recommends doing a far jump |
| // into a new %cs segment immediately after setting the PE bit. |
| mov %cr0, %eax |
| orb $X86_CR0_PE, %al |
| mov %eax, %cr0 |
| |
| // Immediately return into the new 32-bit code segment. |
| lretl |
| .end_function |
| |
| // Execution resumes here, now in 32-bit protected mode. |
| // This code still has to be purely position-independent. |
| .code32 |
| .function Protected32Entry, align=16, nosection=nosection |
| // Reload all the segment registers (except %cs). The Intel manual |
| // recommends doing this immediately after jumping into the new %cs segment. |
| mov $PHYS32_DATA32_SEL, %ax // Flat data segment for %ds, %es, and %ss. |
| mov %ax, %ds |
| mov %ax, %es |
| mov %ax, %ss |
| mov %bx, %fs // Null segment selector for %fs and %gs. |
| mov %bx, %gs |
| |
| // Now jump to the linux entry point. |
| mov $Linux32Entry, %eax |
| jmp *%eax |
| .end_function |
| |
| // When the 16-bit entry path is used, it passes this to the 32-bit path after |
| // copying setup_header, as previously modified by the boot loader, into it. |
| // The 16-bit setup code here is the only source of information other than what |
| // the boot loader wrote into setup_header. When the boot loader uses the |
| // 32-bit entry path, it passes its own pointer to a boot_params struct it has |
| // allocated elsewhere and filled with appropriate data, including copying our |
| // setup_header into that struct. In that case this space is not used at all. |
| .balign 16, 0 |
| .object boot_params, nosection=nosection |
| .space SIZEOF_BOOT_PARAMS, 0 |
| .end_object |
| |
| .object gE820Buffer, align=4, nosection=nosection |
| .space SIZEOF_E820ENTRY, 0 |
| .end_object |
| |
| // Pad out to a whole number of 512-byte sectors and record that total size (in |
| // bytes) as the value of the LINUXBOOT_SETUP_SIZE symbol (a SHN_ABS symbol |
| // that's a size, not an address). The setup_header::setup_sects field |
| // initializer is calculated from this value above. The linuxboot.ld linker |
| // script also uses this symbol to compute the LINUXBOOT_SYSSIZE symbol that's |
| // needed for the setup_header::syssize field initializer above. |
| .balign 512, 0 |
| .label LINUXBOOT_SETUP_SIZE, global, notype, . - zero_page |
| |
| // This is the entrypoint for bootloaders using the 32-bit linux boot |
| // protocol. |
| .function Linux32Entry, nosection=nosection |
| // Jump to the start32.S entry point at its fixed address. |
| mov $Phys32Entry, %eax |
| jmp *%eax |
| .end_function |
| |
| # Add the in-kernel config file, which is gzipped. |
| # Some emulators look for this file using extract-ikconfig before booting. |
| .ascii "IKCFG_ST" |
| .incbin IKCONFIG_FILE |
| .ascii "IKCFG_ED" |