// SPDX-License-Identifier: GPL-2.0 // // setup.S collects the memory map information from the BIOS, disables APM, // enables A20, and performs the switch from real mode to protected mode // before jumping to the main program entry point. // // The memory map information is stored in the 4KB block of memory immediately // following the setup code. The layout of the information matches the Linux // boot_params struct. A pointer to this block is passed to the main program, // for compatiblity with the Linux 32-bit boot protocol. // // Copyright (C) 2020-2021 Martin Whitaker. // // Derived from memtest86+ setup.S and head.S: // // 1-Jan-96 Modified by Chris Brady for use as a boot/loader for memtest-86. #define __ASSEMBLY__ #include "boot.h" #define BOOT_PARAMS_START (SETUP_SECS * 512) #define BOOT_PARAMS_END (BOOT_PARAMS_START + 4096) .section ".setup", "ax", @progbits .code16 # Emulate the Linux boot header, to allow loading by other boot loaders. # Indicate that the main program code should be loaded in high memory. # bootsect.S will fix up the values if we are booted directly from the BIOS. .globl setup setup: jmp do_setup header: .ascii "HdrS" version: .word 0x020c realmode_swtch: .long 0 start_sys_seg: .word 0x1000 kernel_version: .word 0 type_of_loader: .byte 0 loadflags: .byte 0x1 # LOADED_HIGH setup_move_size: .word 0 .globl code32_start code32_start: .long HIGH_LOAD_ADDR ramdisk_image: .long 0 ramdisk_size: .long 0 bootsect_kludge: .long 0 heap_end_ptr: .word 0 ext_loader_ver: .byte 0 ext_loader_type: .byte 0 cmd_line_ptr: .long 0 initrd_addr_max: .long 0xffffffff kernel_alignment: .long 0 relocatable_kernel: .byte 0 min_alignment: .byte 0 xload_flags: #ifdef __x86_64__ .word 0x9 # XLF_KERNEL_64,XLF_EFI_HANDOVER_64 #else .word 0x4 # XLF_EFI_HANDOVER_32 #endif cmd_line_size: .long 255 hardware_subarch: .long 0 hardware_subarch_data: .quad 0 payload_offset: .long 0 payload_length: .long 0 setup_data: .quad 0 pref_address: .quad HIGH_LOAD_ADDR init_size: .long _init_size handover_offset: .long 0x10 do_setup: # Reload the segment registers, except for the stack. movw %cs, %ax movw %ax, %ds movw %ax, %es # Get the memory map and disable APM. call get_mem_info call disable_apm # Disable interrupts. cli movb $0x80, %al # disable NMI outb %al, $0x70 # Enable A20. # Try to switch using the fast A20 gate. movw $0x92, %dx inb %dx, %al # Skip if it's unimplemented (read returns 0xff). cmpb $0xff, %al jz 0f orb $0x02, %al # set the ALT_A20_GATE bit andb $0xfe, %al # clear the INIT_NOW bit outb %al, %dx 0: # Use the keyboard controller method anyway. call empty_8042 movb $0xd1, %al # send write command outb %al, $0x64 call empty_8042 movb $0xdf, %al # A20 on outb %al, $0x60 call empty_8042 # Set up a minimal GDT and IDT. xorl %eax, %eax movw %cs, %ax shll $4, %eax addl %eax, gdt_descr - setup + 2 lgdt gdt_descr - setup lidt idt_descr - setup # Load a pointer to the boot_params block into ESI. xorl %esi, %esi movw %cs, %si shll $4, %esi addl $BOOT_PARAMS_START, %esi # Fix up the jump address. movl (code32_start - setup), %eax movl %eax, (jump - setup + 2) # Copy code32_start to the boot_params struct. movl %eax, (BOOT_PARAMS_START + 0x214) # Copy cmd_line_ptr and cmd_line_size to the boot_params struct. movl (cmd_line_ptr - setup), %eax movl %eax, (BOOT_PARAMS_START + 0x228) movl (cmd_line_size - setup), %eax movl %eax, (BOOT_PARAMS_START + 0x238) # Switch to protected mode. movl %cr0, %eax orl $1, %eax movl %eax, %cr0 jmp flush flush: # Reload the segment registers and jump to the main test program. movw $KERNEL_DS, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss movw %ax, %fs movw %ax, %gs jump: data32 ljmp $KERNEL_CS, $0 # This subroutine queries the BIOS to determine the system memory map # and stores the results in the boot_params structure that we pass to # the startup code. #define SMAP 0x534d4150 get_mem_info: push %ds push %es # Set DS and ES to point to the start of the boot_params structure. movw %ds, %ax addw $(BOOT_PARAMS_START >> 4), %ax movw %ax, %ds movw %ax, %es # Zero the entire boot_params structure. movw $0x0000, %di movw $0x0400, %cx xorl %eax, %eax cld rep stosl # First try method E820. E820 returns memory classified into a whole # bunch of different types, and allows memory holes and everything. mem_e820: movw $E820_MAP, %di # destination pointer xorl %ebx, %ebx # continuation counter loop_e820: movl $0x0000e820, %eax # e820, upper word zeroed movl $SMAP, %edx # ASCII 'SMAP' movl $20, %ecx # size of the e820 record int $0x15 # make the call jc done_e820 # bail out if it fails cmpl $SMAP, %eax # check the return is 'SMAP' jne done_e820 # bail out if it fails incb (E820_ENTRIES) addw $E820_ENTRY_SIZE, %di movb (E820_ENTRIES), %al # check for table full cmpb $E820_MAP_SIZE, %al je done_e820 cmpl $0, %ebx # any more entries? jne loop_e820 done_e820: cmpb $0, (E820_ENTRIES) jnz get_mem_done # Next try method E801. mem_e801: stc # Fix to work around buggy BIOSs xorw %cx,%cx # which don't clear/set carry on xorw %dx,%dx # pass/error of e801h memory size # call or merely pass cx,dx through # without changing them. movw $0xe801, %ax int $0x15 jc mem_88 cmpw $0x0, %cx # Kludge to handle BIOSes which jne 0f # report their extended memory in cmpw $0x0, %dx # AX/BX rather than CX/DX. The spec jne 0f # I have read seems to indicate that movw %ax, %cx # AX/BX are more reasonable anyway. movw %bx, %dx 0: jmp fake_e820 # Finally try method 88. mem_88: movb $0x88, %ah int $0x15 movw %ax, %cx movw $0, %dx fake_e820: # Write entry for memory below 1MB. movl $0x0, E820_ADDR(%di) movl $0xa0000, E820_SIZE(%di) movl $1, E820_TYPE(%di) incb (E820_ENTRIES) addw $E820_ENTRY_SIZE, %di # Write entry for memory between 1MB and 16MB. andl $0xffff, %ecx # convert to 32-bits jz 0f shll $10, %ecx # convert to bytes movl $0x100000, E820_ADDR(%di) movl %ecx, E820_SIZE(%di) movl $1, E820_TYPE(%di) incb (E820_ENTRIES) addw $E820_ENTRY_SIZE, %di 0: # Write entry for memory above 16MB. andl $0xffff, %edx # convert to 32-bits jz 1f shll $16, %edx # convert to bytes movl $0x1000000, E820_ADDR(%di) movl %edx, E820_SIZE(%di) movl $1, E820_TYPE(%di) incb (E820_ENTRIES) addw $E820_ENTRY_SIZE, %di 1: get_mem_done: pop %es pop %ds ret # This subroutine disables APM if it is present. disable_apm: movw $0x5300, %ax # APM BIOS installation check xorw %bx, %bx int $0x15 jc disable_apm_done # error -> no APM BIOS cmpw $0x504d, %bx # check for "PM" signature jne disable_apm_done # no signature -> no APM BIOS movw $0x5304, %ax # Disconnect first just in case xorw %bx, %bx int $0x15 # ignore return code movw $0x5301, %ax # Real Mode connect xorw %bx, %bx int $0x15 jc disable_apm_done # error movw $0x5308, %ax # Disable APM mov $0xffff, %bx xorw %cx, %cx int $0x15 disable_apm_done: ret # This subroutine checks that the keyboard command queue is empty (after # emptying the output buffers). No timeout is used - if this hangs there # is something wrong with the machine, and we probably couldn't proceed # anyway. empty_8042: call delay inb $0x64, %al # 8042 status port cmpb $0xff, %al # skip if not implemented jz empty_8042_ret testb $1, %al # anything in the output buffer? jz no_output call delay inb $0x60, %al # read it jmp empty_8042 no_output: testb $2, %al # is input buffer full? jnz empty_8042 # yes - loop empty_8042_ret: ret # This subroutine provides a short delay. delay: .word 0x00eb # jmp $+2 ret # A minimal GDT and IDT. .align 4 gdt: .quad 0x0000000000000000 # NULL descriptor .quad 0x0000000000000000 # not used .quad 0x00c09a0000007fff # 128MB 32-bit code at 0x000000 .quad 0x00c0920000007fff # 128MB 32-bit code at 0x000000 gdt_end: .word 0 # for alignment gdt_descr: .word gdt_end - gdt - 1 # gdt limit .long gdt - setup # gdt base - relocated at run time .word 0 # for alignment idt_descr: .word 0 # idt limit=0 .long 0 # idt base=0 # Pad to the declared size. .org (SETUP_SECS*512)