Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/floppy/boot.asm
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2022-09-02 09:18:52 +0200
committerAndreas Baumann <mail@andreasbaumann.cc>2022-09-02 09:18:52 +0200
commit52be99d8c0862ff87db9a4f9ccec1ac4b5f7caed (patch)
tree1a1a1c98090afd8459cc8f045f9de2d4a7cba5ab /floppy/boot.asm
parent15adaba9eaa6a98c8b55bc5c5f73c3a9e0e55e7a (diff)
added a quite unsorted first version of a floppy boot loader
Diffstat (limited to 'floppy/boot.asm')
-rw-r--r--floppy/boot.asm1408
1 files changed, 1408 insertions, 0 deletions
diff --git a/floppy/boot.asm b/floppy/boot.asm
new file mode 100644
index 0000000..6200196
--- /dev/null
+++ b/floppy/boot.asm
@@ -0,0 +1,1408 @@
+; stage 1
+
+; 16-bit real-mode
+[bits 16]
+
+; BIOS always loads us to this location
+[org 0x7c00]
+
+; export a map of boot loader symbols for debugging
+[map symbols boot.map]
+
+stage1:
+
+; no interrupts, initializing segment registers and a real mode stack
+; growing downwards from 0x7c00 where the stage 1 code and data lives
+ cli
+ cld
+ xor ax, ax
+ mov ds, ax
+ mov ss, ax
+ mov sp, 0x7c00
+ mov bp, sp
+ sti
+
+; dl contains the boot drive, primarily because that's the last function
+; called by the BIOS MBR loader, we remember that for loading additional
+; blocks from the boot medium
+ mov [BOOT_DRIVE], dl
+
+; print greeting message
+ mov si, MESSAGE_GREETING
+ call print_string
+
+; size of stage 2 in sectors (3584-512 bytes, size of boot.img minus stage 1)
+NOF_SECTORS_STAGE2 equ 6
+
+; load stage 2 with simple load method to 0x7e00 (directly
+; after the boot sector), we assume stage 2 fits on a track
+ mov ah, 0x02 ; read sectors from drive
+ mov al, NOF_SECTORS_STAGE2 ; read sectors of stage 2
+ mov ch, 0 ; select first cylinder
+ mov dl, [BOOT_DRIVE] ; drive to read from
+ mov dh, 0 ; first head
+ mov cl, 2 ; second sector after boot sector
+ mov bx, 0 ; where to store the data
+ mov es, bx
+ mov bx, 0x7e00 ; 512 bytes after first sector
+ int 0x13
+ jc .read_error
+ jmp .read_ok
+
+.read_error:
+ mov di, ERR_DISK
+ mov ax, 0x01
+ call print_error
+.read_ok:
+ cmp al, NOF_SECTORS_STAGE2 ; correct number of sectors read?
+ jne .short_read_error ; if not, short read
+ jmp .short_read_ok
+.short_read_error:
+ mov di, ERR_DISK
+ mov ax, 0x02
+ call print_error
+.short_read_ok:
+ jmp stage2
+
+; di: pointer to additional message
+; ax: error code in ASCII, never returns
+print_error:
+ mov si, NL
+ call print_string
+ mov si, MESSAGE_ERROR
+ call print_string
+ mov si, di
+ call print_string
+ call print_hex_nice
+ call kill_motor
+ mov si, NL
+ call print_string
+ jmp reboot
+
+; IN si
+print_string:
+ push ax
+ push bx
+.loop:
+ lodsb
+ cmp al, 0
+ je .fini
+ call print_char
+ jmp .loop
+.fini:
+ pop bx
+ pop ax
+ ret
+
+; IN al: character to print
+; MOD ah
+print_char:
+ mov ah, 0x0e
+ int 0x10
+ ret
+
+; IN ah: hex value to print
+print_hex_nice:
+ push si
+ mov si, HEX_PREFIX
+ call print_string
+ call print_hex
+ mov si, SPACE
+ call print_string
+ pop si
+ ret
+
+; IN ah: hex value to print
+print_hex:
+ push bx
+ push si
+ mov si, HEX_TEMPLATE
+ mov bx, ax
+ and bx, 0x00FF
+ shr bx, 4
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE], bl
+ mov bx, ax
+ and bx, 0x000F
+ mov bx, [HEXABET+bx]
+ mov [HEX_TEMPLATE+1], bl
+ call print_string
+ pop si
+ pop bx
+ ret
+
+; IN: eax: value to print in hex
+print_hex_dword:
+ push eax
+ push ebx
+ mov ebx, eax
+ mov eax, ebx
+ shr eax, 24
+ call print_hex
+ mov eax, ebx
+ shr eax, 16
+ call print_hex
+ mov eax, ebx
+ shr eax, 8
+ call print_hex
+ mov eax, ebx
+ call print_hex
+ pop ebx
+ pop eax
+ ret
+
+kill_motor:
+ push dx
+ mov dx, 0x3F2
+ mov al, 0x00
+ out dx, al
+ pop dx
+ ret
+
+reset_drive:
+ push ax
+ push dx
+ mov ax, 0x00
+ mov dl, [BOOT_DRIVE]
+ int 0x13
+ pop dx
+ pop ax
+ ret
+
+HEX_TEMPLATE:
+ db '??', 0
+
+HEX_PREFIX:
+ db '0x', 0
+
+HEXABET:
+ db '0123456789ABCDEF'
+
+MESSAGE_GREETING:
+ db "Archlinux32 i486 floppy..", 13, 10, 0
+
+MESSAGE_ERROR:
+ db "ERR ", 0
+
+SPACE:
+ db " ", 0
+
+NL:
+ db 13, 10, 0
+
+BOOT_DRIVE:
+ db 0
+
+; pad rest of sector with zeroes so we get 512 bytes in the end
+ times 510-($-$$) db 0
+
+; magic number of a boot sector
+ dw 0xaa55
+
+; stage 2
+stage2:
+
+; check A20 gate
+ mov si, MESSAGE_CHECKING_A20
+ call print_string
+ call check_and_enable_A20
+ cmp ax, 1
+ je .A20_enabled
+ mov si, MESSAGE_DISABLED
+ call print_string
+ mov di, ERR_A20
+ mov ax, 0x01
+ call print_error
+
+.A20_enabled:
+ mov si, MESSAGE_ENABLED
+ call print_string
+
+unreal_mode:
+; now switching to bigger data and extra segments (so copying kernels and ramdisks
+; is not overly painful)
+ mov si, MESSAGE_SWITCHING_TO_UNREAL_MODE
+ call print_string
+
+; disable interrupts for now as we do mode switching and segment manipulations
+ cli
+
+; load GDT (global descriptor table)
+ lgdt [gdt_descriptor]
+
+; switch to protected mode
+ mov eax, cr0
+ or eax, 0x1
+ mov cr0, eax
+
+; unconditional far jump into code segment,
+; wipes the instruction prefetch pipeline
+ jmp $+2
+
+; data descriptor in GDT
+ mov ax, CODE_DATA_SEGMENT
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+
+; back to real mode
+ and al, 0xFE
+ mov cr0, eax
+
+; restore segment values - now limits are removed but seg regs still work as normal
+; we keep cs and ss in real mode locations as before, code is small and stack
+; is not expected to get big, besides they stop working above 1 MB as SP and IP
+; are used and not ESP and EIP!
+ xor ax,ax
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+
+; now we are in UNREAL mode, we can also reenable real mode interrupts, as the
+; code segment is still small, so saving IP should work just fine
+ sti
+ mov si, MESSAGE_ENABLED
+ call print_string
+
+; detect disk geometry
+; IN dl: drive
+detect_disk_geometry:
+ xor ax, ax
+ mov es, ax
+ mov di, ax
+ mov ah, 0x08
+ mov dl, [BOOT_DRIVE]
+ int 0x13
+ jc .error
+ jmp .ok
+
+.error:
+; AH contains return code
+ mov si, ERR_DISK
+ call print_error
+
+; all went well, remember and print drive parameters
+.ok:
+ mov si, MESSAGE_DRIVE_PARAMETERS
+ call print_string
+ mov ax, [BOOT_DRIVE]
+ call print_hex_nice
+ xor ax, ax
+ mov [FLOPPY_TYPE], BYTE bl
+ mov al, [FLOPPY_TYPE]
+ call print_hex_nice
+ add dh, 1
+ mov [NOF_HEADS], BYTE dh
+ add cl, 1
+ mov [SECTORS_PER_CYLINDER], BYTE cl
+ mov al, [NOF_HEADS]
+ call print_hex_nice
+ xor ax, ax
+ mov al, [SECTORS_PER_CYLINDER]
+ call print_hex_nice
+ mov si, NL
+ call print_string
+
+ call reset_drive
+
+; read now sectors after stage 2 as long as we can find
+; tar index sectors containing filenames,
+; depending on the filename we have to load things to different places
+; (and move them to memory above 1MB)
+; start to read after stage 1 (512 on sector 1) and stage 2 (starting on sector 2)
+ mov [CURRENT_SECTOR], byte NOF_SECTORS_STAGE2 + 2
+
+read_next_sector:
+
+ mov bx, 0x0900 ; where to store the data (our floppy data read buffer)
+ mov es, bx
+ mov bx, 0x0
+
+; print (C/H/S) info where we are currently reading and in which state
+; and how many data sectors are left in the current tar entry
+ ;~ jmp .no_verbose
+
+; states
+ mov al, 0x0d
+ call print_char
+ mov al, byte [READ_STATE]
+ call print_hex
+ mov al, ' '
+ call print_char
+ mov al, byte [KERNEL_STATE]
+ call print_hex
+ mov al, ' '
+ call print_char
+
+; tar data sectors to read for current file
+ mov dx, word [READ_DATA_SECTORS]
+ mov ax, dx
+ shr ax, 8
+ call print_hex
+ mov ax, dx
+ call print_hex
+ mov al, ' '
+ call print_char
+
+; disk geometry (current position on the floppy)
+ mov al, '('
+ call print_char
+ xor ax, ax
+ mov al, byte [CURRENT_CYLINDER]
+ call print_hex
+ mov al, '/'
+ call print_char
+ xor ax, ax
+ mov al, byte [CURRENT_HEAD]
+ call print_hex
+ mov al, '/'
+ call print_char
+ xor ax, ax
+ mov al, byte [CURRENT_SECTOR]
+ call print_hex
+ mov al, ')'
+ call print_char
+
+.no_verbose:
+ call read_one_sector_from_disk
+
+; states in the main sector read loop
+STATE_READ_METADATA equ 0
+STATE_READ_KERNEL equ 1
+STATE_READ_INITRD equ 2
+STATE_KERNEL_READ_SECTOR_0 equ 1
+STATE_KERNEL_READ_SECTOR_1 equ 2
+STATE_KERNEL_READ_SECTORS_REAL_MODE equ 3
+STATE_KERNEL_READ_SECTORS_PROTECTED_MODE equ 4
+STATE_KERNEL_FINISHED equ 5
+
+; depending on the read state we have to do different stuff now
+ mov ah, byte [READ_STATE]
+ cmp ah, STATE_READ_METADATA
+ je handle_read_metadata
+ cmp ah, STATE_READ_KERNEL
+ je handle_read_kernel
+ cmp ah, STATE_READ_INITRD
+ je handle_read_initrd
+ mov di, ERR_DISK
+ mov ax, 0x03
+ call print_error
+
+; state: STATE_READ_METADATA
+handle_read_metadata:
+ push bx
+ mov esi, 0x09000 + 0x101 ; 5 chars must match 'ustar' for being a tar metadata block
+ mov edi, USTAR_MAGIC
+ mov bx, 5
+ call strncmp
+ pop bx
+ cmp ax, 0
+ je .print_tar_metadata
+ jmp advance_to_next_sector ; read over unknown data or non-tar entries
+
+.print_tar_metadata:
+ mov al, ' '
+ call print_char
+ mov si, 0x09000 + 0x00 ; the filename in the tar
+ call print_string
+ mov al, ' '
+ call print_char
+ mov si, 0x09000 + 0x7c ; the size in ASCII octal
+ call print_string
+ mov si, SPACE
+ call print_string
+ mov si, 0x09000 + 0x7c
+ mov ecx, 11
+
+ ; compute size in decimal and compute number of data sectors we have to read
+ call octal_string_to_int
+ a32 mov [READ_DATA_SIZE], eax
+ call print_hex_dword
+
+ mov ecx, eax ; ecx contains the number of 512 byte sectors
+ shr ecx, 9 ; number of sectors a 512 bytes
+ mov edx, ebx ; compute if we have a remainder
+ and edx, 0x01FF
+ cmp edx, 0 ; no remainder, ending at full sector
+ je .full_sector
+ inc ecx ; one non-full sector more to read
+.full_sector:
+ mov [READ_DATA_SECTORS], ecx
+
+ ; enable to debug metadata in tar
+ ;~ mov si, NL
+ ;~ call print_string
+ ;~ mov ax, 0x0900
+ ;~ mov es, ax
+ ;~ call print_memory
+
+.search_kernel:
+ mov esi, 0x09000 + 0x00
+ mov edi, FILE_BZIMAGE
+ call strcmp
+ cmp ax, 0
+ je .found_kernel
+ jmp .search_initrd
+
+.found_kernel:
+ mov [READ_STATE], byte STATE_READ_KERNEL
+ mov [KERNEL_STATE], byte STATE_KERNEL_READ_SECTOR_0
+ mov [READ_DESTINATION_PTR], dword 0x10000
+ mov al, '!'
+ call print_char
+ mov si, NL
+ call print_string
+ jmp advance_to_next_sector
+
+.search_initrd:
+ mov esi, 0x09000 + 0x00
+ mov edi, FILE_RAMDISK
+ call strcmp
+ cmp ax, 0
+ je .found_initrd
+ jmp .search_eof
+
+.found_initrd:
+ mov [READ_STATE], byte STATE_READ_INITRD
+; qemu initrd start location 7fab000, 133869568 (this is 128MB) too high for us,
+; kernel gives us alignment hints and hints where to load initrd to?
+; let's use 8MB, TODO: does the kernel release the initial ramdisk? I think so. is
+; it relocating it's structures? or do we get fragmented heap and stuff?
+ a32 mov [READ_DESTINATION_PTR], dword 0x00800000
+ a32 mov [INITRD_ADDRESS], dword 0x00800000
+ a32 mov eax, [READ_DATA_SIZE]
+ a32 mov [INITRD_SIZE], eax
+ mov al, '!'
+ call print_char
+ mov si, NL
+ call print_string
+
+ jmp advance_to_next_sector
+
+.search_eof:
+ mov esi, 0x09000 + 0x00
+ mov edi, FILE_EOF
+ call strcmp
+ cmp ax, 0
+ je .found_eof
+ jmp .unknown_file
+
+.found_eof:
+ mov si, NL
+ call print_string
+ mov si, MESSAGE_EOF_REACHED
+ call print_string
+ jmp finished_reading
+
+.unknown_file:
+ mov al, '?'
+ call print_char
+ mov si, NL
+ call print_string
+ jmp advance_to_next_sector
+
+; STATE: STATE_READ_KERNEL
+handle_read_kernel:
+
+ ; move kernel code/data from 0x09000 (our disk read scratch space
+ ; in low memory to 0x10000 (which is the real mode location for the
+ ; kernel code)
+ mov ax, ds
+ mov es, ax
+ mov esi, 0x09000
+ mov edi, dword [READ_DESTINATION_PTR]
+ mov ecx, 512
+ cld
+ a32 rep movsb
+ mov [READ_DESTINATION_PTR], edi
+
+ mov ax, word [READ_DATA_SECTORS]
+ dec ax
+ mov word [READ_DATA_SECTORS], ax
+ cmp ax, 0
+ je .data_end
+ jmp kernel_switch
+.data_end:
+ mov [READ_STATE], byte STATE_READ_METADATA
+ jmp advance_to_next_sector
+
+ ; depending on the kernel substate
+kernel_switch:
+ xor ax, ax
+ mov ah, byte [KERNEL_STATE]
+ cmp ah, STATE_KERNEL_READ_SECTOR_0
+ je handle_data_kernel_sector_0
+ cmp ah, STATE_KERNEL_READ_SECTOR_1
+ je handle_data_kernel_sector_1
+ cmp ah, STATE_KERNEL_READ_SECTORS_REAL_MODE
+ je handle_data_kernel_real_mode
+ cmp ah, STATE_KERNEL_READ_SECTORS_PROTECTED_MODE
+ je handle_data_kernel_protected_mode
+ mov di, ERR_KERN
+ mov ax, 0x01
+ call print_error
+
+; STATE: STATE_KERNEL_READ_SECTOR_0
+handle_data_kernel_sector_0:
+
+; first sector if real mode kernel
+
+ ; enable to debug real mode zero page metadata
+ ;~ mov si, NL
+ ;~ call print_string
+ ;~ mov ax, 0x1000
+ ;~ mov es, ax
+ ;~ call print_memory
+
+ ; get the number of real mode kernel sectors to read
+ ; (or mininmally 4 if 0 is returned), each 512 bytes
+ mov si, NL
+ call print_string
+ a32 mov al, byte [0x10000+0x1f1]
+ cmp al, 0
+ jne .nof_sectors_ok
+ mov al, 4 ; minimally 4 sectors
+
+.nof_sectors_ok:
+ mov si, MESSAGE_KERNEL_NOF_REAL_SECTORS
+ call print_string
+ mov [KERNEL_NOF_REAL_MODE_SECTORS], al
+ call print_hex
+ mov si, NL
+ call print_string
+
+ ; get size of protected mode kernel in 16 bytes
+ a32 mov eax, [0x10000+0x1f4]
+ mov ecx, eax
+ shr ecx, 5 ; 16-pages, shifting by 5 to the right gives
+ ; us the number of sectors a 512 bytes
+ mov ebx, eax ; compute if we have to read one sector more with partial data
+ and ebx, 0x001F ; 32 times 16 bytes per page
+ cmp ebx, 0
+ je .full_sector
+ inc ecx
+.full_sector:
+ mov [KERNEL_NOF_PROTECTED_MODE_SECTORS], word cx
+ mov si, MESSAGE_KERNEL_NOF_PROTECTED_SECTORS
+ call print_string
+ mov bx, [KERNEL_NOF_PROTECTED_MODE_SECTORS]
+ mov ax, bx
+ and ax, 0xFF00
+ shr ax, 8
+ call print_hex
+ mov ax, bx
+ and ax, 0x00FF
+ call print_hex
+ mov si, NL
+ call print_string
+
+ mov [KERNEL_STATE], byte STATE_KERNEL_READ_SECTOR_1
+
+ jmp advance_to_next_sector
+
+; STATE: STATE_KERNEL_READ_SECTOR_1
+handle_data_kernel_sector_1:
+ ; enable to debug real mode zero page metadata
+ ;~ mov si, NL
+ ;~ call print_string
+ ;~ mov ax, 0x1020
+ ;~ mov es, ax
+ ;~ call print_memory
+
+ ; compare header
+ mov esi, 0x10000 + 0x202
+ mov edi, KERNEL_MAGIC
+ mov bx, 4
+ call strncmp
+ cmp ax, 0
+ je .kernel_HdrS_found
+ mov di, ERR_KERN
+ mov ax, 0x02
+ call print_error
+
+.kernel_HdrS_found:
+ ; get protocol version, don't allow anothing below 2.15 (which
+ ; os kernel 5.5 or above)
+ mov al, 0x0d
+ call print_char
+ mov si, NL
+ call print_string
+ mov si, MESSAGE_KERNEL_BOOT_PROTOCOL
+ call print_string
+ a32 mov al, byte [0x10000+0x207]
+ mov byte [KERNEL_BOOT_PROTOCOL_MAJOR], al
+ call print_hex
+ mov al, '.'
+ call print_char
+ a32 mov al, byte [0x10000+0x206]
+ mov byte [KERNEL_BOOT_PROTOCOL_MINOR], al
+ call print_hex
+ mov si, NL
+ call print_string
+ xor ax, ax
+ mov al, byte [KERNEL_BOOT_PROTOCOL_MAJOR]
+ cmp al, 2
+ jl .protocol_error
+ xor ax, ax
+ mov al, byte [KERNEL_BOOT_PROTOCOL_MINOR]
+ cmp al, 15
+ jl .protocol_error
+ jmp .get_kernel_version_offset
+
+.protocol_error:
+ mov di, ERR_KERN
+ mov ax, 0x03
+ call print_error
+
+ ; get offset pointing to human readable kernel message,
+ ; we can print it only after having read the real mode part
+ ; of the kernel (or just before we actually start the kernel)
+.get_kernel_version_offset:
+ a32 mov ax, word [0x10000+0x20e]
+ mov [KERNEL_VERSION_PTR], ax
+ jmp .set_boot_data
+
+.set_boot_data:
+ ; not quite clear what the kernel does with this data, TODO: must read kernel code
+ a32 mov byte [0x10000+0x210], 0xe1 ; Extended bootloader type
+ a32 mov byte [0x10000+0x226], 0x00 ; ext_loader_ver
+ a32 mov byte [0x10000+0x227], 0x01 ; ext_loader_type (id: 0x11)
+ a32 or byte [0x10000+0x211], 0x80 ; set CAN_USE_HEAP
+ a32 mov word [0x10000+0x224], 0xde00 ; head_end_ptr
+
+ ; set up area and copy command line from the boot loader area to
+ ; a area registered with the kernel (0x1e000)
+ a32 mov dword [0x10000+0x228], 0x1e000 ; set cmd_line_ptr
+ xor ax, ax
+ mov es, ax
+ mov esi, KERNEL_CMD_LINE
+ mov edi, 0x1e000
+ mov ecx, KERNEL_CMD_SIZE
+ cld
+ a32 rep movsb
+
+ jmp .change_state
+
+.change_state:
+ ; enable to debug real mode zero page metadata after modifying and setting parameters
+ ;~ mov si, NL
+ ;~ call print_string
+ ;~ mov ax, 0x1020
+ ;~ mov es, ax
+ ;~ call print_memory
+
+ mov [KERNEL_STATE], byte STATE_KERNEL_READ_SECTORS_REAL_MODE
+ ; intentional fallthrough, decrement real mode sectors below
+
+; STATE: STATE_KERNEL_READ_SECTORS_REAL_MODE
+handle_data_kernel_real_mode:
+ ; read at most KERNEL_NOF_REAL_MODE_SECTORS sectors for real
+ ; mode code/data
+ mov al, byte [KERNEL_NOF_REAL_MODE_SECTORS]
+ dec al
+ mov byte [KERNEL_NOF_REAL_MODE_SECTORS], al
+ cmp al, 0
+ je .last_real_mode_sector_read
+ jmp advance_to_next_sector
+
+.last_real_mode_sector_read:
+ ; show kernel version
+ mov al, 0x0d
+ call print_char
+ mov si, NL
+ call print_string
+ mov si, MESSAGE_KERNEL_VERSION
+ call print_string
+ mov esi, [KERNEL_VERSION_PTR]
+ add esi, 0x200
+ push ds
+ mov ax, 0x1000
+ mov ds, ax
+ call print_string
+ pop ds
+ mov si, NL
+ call print_string
+
+ ; change load pointer to protected mode area 0x100000
+ mov [KERNEL_STATE], byte STATE_KERNEL_READ_SECTORS_PROTECTED_MODE
+ mov [READ_DESTINATION_PTR], dword 0x100000
+
+ jmp advance_to_next_sector
+
+; STATE: STATE_KERNEL_READ_SECTORS_PROTECTED_MODE
+handle_data_kernel_protected_mode:
+ ; read at most KERNEL_NOF_PROTECTED_MODE_SECTORS sectors for protected
+ ; mode code/data
+ mov ax, word [KERNEL_NOF_PROTECTED_MODE_SECTORS]
+ dec ax
+ mov word [KERNEL_NOF_PROTECTED_MODE_SECTORS], ax
+ cmp ax, 0
+ je .last_protected_mode_sector_read
+ jmp advance_to_next_sector
+.last_protected_mode_sector_read:
+ mov [READ_STATE], byte STATE_READ_METADATA
+ mov [KERNEL_STATE], byte STATE_KERNEL_FINISHED
+ jmp advance_to_next_sector
+
+; STATE: STATE_READ_INITRD
+handle_read_initrd:
+ ; move ramdisk data from 0x09000 (our disk read scratch space
+ ; in low memory to high memory 0xaaaa0000
+ mov ax, ds
+ mov es, ax
+ mov esi, 0x09000
+
+ mov edi, dword [READ_DESTINATION_PTR]
+ mov ecx, 512
+ cld
+ a32 rep movsb
+ mov [READ_DESTINATION_PTR], edi
+
+ mov ax, word [READ_DATA_SECTORS]
+ dec ax
+ mov word [READ_DATA_SECTORS], ax
+ cmp ax, 0
+ je .data_end
+ jmp .nothing_todo
+.data_end:
+ mov [READ_STATE], byte STATE_READ_METADATA
+ jmp advance_to_next_sector
+
+.nothing_todo:
+
+advance_to_next_sector:
+ add [CURRENT_SECTOR], byte 1 ; next sector
+ mov ch, [SECTORS_PER_CYLINDER]
+ cmp [CURRENT_SECTOR], ch ; after the end of the current track?
+ je .next_head
+ jmp read_next_sector
+
+.next_head:
+ shr bx, 4 ; make it a segment offset..
+ mov ax, es
+ add ax, bx
+ mov es, ax ; ..and add it to ES
+ mov bx, 0x0 ; we also reset bx and update es to avoid hitting the 64k wrap around point
+ mov [CURRENT_SECTOR], byte 1 ; start from first sector again
+ add [CURRENT_HEAD], byte 1 ; advance head
+ mov ch, [NOF_HEADS]
+ cmp [CURRENT_HEAD], ch ; after the number of heads?
+ je .next_track
+ jmp read_next_sector
+
+.next_track:
+ mov [CURRENT_HEAD], byte 0 ; start from head 0 again
+ add [CURRENT_CYLINDER], byte 1 ; advance track
+ ; TODO depends on boot parameters (the floppy media, add a table)
+ cmp [CURRENT_CYLINDER], byte 80
+ jae .next_floppy
+ jmp read_next_sector
+
+.next_floppy:
+ call kill_motor
+ mov si, NL
+ call print_string
+ mov si, MESSAGE_NEXT_FLOPPY
+ call print_string
+ call wait_for_keypress
+ call reset_drive
+ ; TODO: check for floppy disk change (is there a BIOS function for this?)
+ ; TODO: maybe also check some checksum or so of the floppy data and see
+ ; if we indeed have a new floppy
+ mov [CURRENT_SECTOR], byte 0 ; will be incremented at advance_to_next_sector
+ mov [CURRENT_HEAD], byte 0
+ mov [CURRENT_CYLINDER], byte 0
+ jmp advance_to_next_sector
+
+finished_reading:
+ ; make sure the floppy is not spinnig (also in print_error)
+ call kill_motor
+
+start_kernel:
+
+ ; set ramdisk
+ a32 mov eax, [INITRD_ADDRESS] ; ramdisk size in bytes
+ a32 mov [0x10000+0x218], eax
+ mov si, MESSAGE_INITRD_ADDRESS
+ call print_string
+ a32 mov eax, [INITRD_ADDRESS]
+ call print_hex_dword
+ mov si, NL
+ call print_string
+ a32 mov eax, [INITRD_SIZE] ; ramdisk address in bytes
+ a32 mov [0x10000+0x21c], eax
+ mov si, MESSAGE_INITRD_SIZE
+ call print_string
+ a32 mov eax, [INITRD_SIZE]
+ call print_hex_dword
+ mov si, NL
+ call print_string
+
+ ; TODO: check if we actually do have reached the KERNEL_FINISHED state
+ mov si, MESSAGE_BOOTING_KERNEL
+ call print_string
+
+ ; set up segments for running the real mode kernel
+ cli
+ mov ax, 0x1000
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ mov ss, ax
+ mov sp, 0xe000
+
+ ; jump to kernel real mode entry
+ jmp 0x1020:0
+
+; we should not return here, rather the machine will reset, hang or kernel will OUPS,
+; just in case, restore segments and print an error message just in case it happens..
+ mov ax, CODE_DATA_SEGMENT
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ mov ss, ax
+ mov sp, 0x7c00
+
+ mov si, ERR_KERN
+ mov ax, 0x01
+ call print_error
+
+; allow rebooting if something goes sour in the kernel
+reboot:
+ mov si, MESSAGE_REBOOT
+ call print_string
+
+ call wait_for_keypress
+
+; reboot (cannot use jmp here)
+ db 0xea
+ dw 0x0000
+ dw 0xFFFF
+
+; endless loop if reboot fails
+ jmp $
+
+; wait for key to be pressed
+wait_for_keypress:
+ push ax
+ xor ax, ax
+ int 0x16
+ pop ax
+ ret
+
+; GDT global descriptor table
+
+gdt_start:
+
+; mandatory null entry
+gdt_null:
+ dd 0x0
+ dd 0x0
+
+; on big unreal segment, code and data are in the same unprotected segment
+gdt_code_data:
+ dw 0xffff ; limit (bits 0-15)
+ dw 0x0 ; base (bits 0-15)
+ db 0x0 ; base (bits 16-23)
+ db 10010010b ; flags
+ db 11001111b ; flags, limit (bits 16-19)
+ db 0x0 ; base (bit 24-31)
+
+gdt_end:
+
+gdt_descriptor:
+ dw gdt_end - gdt_start - 1 ; size
+ dd gdt_start ; start address of the GDT
+
+; constants representing the segment bases
+CODE_DATA_SEGMENT equ gdt_code_data - gdt_start
+
+check_and_enable_A20:
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_FAST_SPECIAL_PORT:
+
+ mov al, 'F'
+ call print_char
+
+ in al, 0x92
+ or al, 2
+ out 0x92, al
+
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLE_KBD_PORT:
+ mov al, 'K'
+ call print_char
+ mov al, 0xdd
+ out 0x64, al
+
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLE_VIA_BIOS:
+ mov al, 'B'
+ call print_char
+ mov ax, 0x2401
+ int 0x15
+
+ call check_A20_enabled
+ cmp ax, 1
+
+A20_ENABLE_KBD_OUT:
+
+ mov al, 'k'
+ call print_char
+
+ cli ; disable interrupts, we talk directly to the keyboard ports
+
+ call .wait_input
+ mov al,0xAD
+ out 0x64,al ; disable keyboard
+ call .wait_input
+
+ mov al,0xD0
+ out 0x64,al ; tell controller to read output port
+ call .wait_output
+
+ in al,0x60
+ push eax ; get output port data and store it
+ call .wait_input
+
+ mov al,0xD1
+ out 0x64,al ; tell controller to write output port
+ call .wait_input
+
+ pop eax
+ or al,2 ; set bit 1 (enable a20)
+ out 0x60,al ; write out data back to the output port
+
+ call .wait_input
+ mov al,0xAE ; enable keyboard
+ out 0x64,al
+
+ call .wait_input
+
+ sti
+
+ jmp .retest
+
+; wait for input buffer to be clear
+.wait_input:
+ in al,0x64
+ test al,2
+ jnz .wait_input
+ ret
+
+; wait for output buffer to be clear
+.wait_output:
+ in al,0x64
+ test al,1
+ jz .wait_output
+ ret
+
+.retest:
+ call check_A20_enabled
+ cmp ax, 1
+ je A20_ENABLED
+
+A20_ENABLED:
+ ret
+
+; returns 0 if not A20_ENABLED, 1 if A20_ENABLED in AX
+check_A20_enabled:
+ pushf
+ push ds
+ push es
+ push di
+ push si
+
+ cli
+
+ xor ax, ax
+ mov es, ax
+ mov di, 0x0500 ; es:di = 0000:0500
+
+ mov ax, 0xffff
+ mov ds, ax
+ mov si, 0x0510 ; ds:si = ffff:0510
+
+ mov al, byte [es:di] ; preserve values in memory
+ push ax ; on stack
+ mov al, byte [ds:si]
+ push ax
+
+ mov byte [es:di], 0x00 ; now the test: write 0x00 to 0000:0500
+ mov byte [ds:si], 0xFF ; write 0xff to ffff:0510
+
+ cmp byte [es:di], 0xFF ; memory wrap? A20 not enabled
+ je .disabled
+ jmp .enabled
+
+.restore:
+ pop bx ; restore original memory contents
+ mov byte [ds:si], bl
+ pop bx
+ mov byte [es:di], bl
+ jmp .exit
+
+.enabled:
+ mov al, '+'
+ call print_char
+ mov ax, 1 ; not wrapped around
+ jmp .restore
+
+.disabled:
+ mov al, '-'
+ call print_char
+ mov ax, 0 ; wrapped around (last cmp)
+ jmp .restore
+
+.exit:
+ pop si
+ pop di
+ pop es
+ pop ds
+ popf
+
+ ret
+
+; Read one sector from floppy using CHS adressing
+read_one_sector_from_disk:
+
+ mov ah, 0x02 ; read sectors from drive
+ mov al, 1 ; read 1 sector
+ mov ch, BYTE [CURRENT_CYLINDER]
+ mov dh, BYTE [CURRENT_HEAD]
+ mov dl, BYTE [BOOT_DRIVE]
+ mov cl, BYTE [CURRENT_SECTOR]
+ mov BYTE [CURRENT_RETRIES], 0
+
+ int 0x13
+
+ jc .read_error
+
+ cmp al, 1 ; 1 sector read?
+ jne .short_read ; if not, short read
+
+ ret
+
+.read_error:
+ cmp BYTE [CURRENT_RETRIES], 3
+ jl .read_again
+ jmp .print_error
+.read_again:
+ dec BYTE [CURRENT_RETRIES]
+ call reset_drive
+ jmp read_one_sector_from_disk
+
+.print_error:
+ ;~ xor ax, ax
+ ;~ mov dh, 0
+ ;~ mov dl, ah
+ mov di, ERR_DISK
+ call print_error
+
+.short_read:
+ mov di, ERR_DISK
+ call print_string
+
+; IN al: character to print if printable, dot otherwise
+; MOD ah
+print_dump_char:
+ cmp byte al, 0x20 ; space
+ jl .print_dot
+ test byte al, al ; above 0x7f ~
+ js .print_dot
+ call print_char
+ jmp .done
+.print_dot:
+ mov al, 0x2e ; .
+ call print_char
+.done:
+ ret
+
+; print a dump of 512 bytes in memory (a sector) for debugging purposes
+; IN: es base address (as segment) where to start printing
+print_memory:
+ xor bx, bx
+.next_line:
+ xor cx, cx
+ mov ax, es
+ and ax, 0xFF00
+ shr ax, 8
+ call print_hex
+ mov ax, es
+ and ax, 0x00FF
+ call print_hex
+ xor ax, ax
+ mov ah, ':'
+ call print_char
+ mov al, bh
+ call print_hex
+ mov al, bl
+ call print_hex
+ mov si, DUMP_SEP
+ call print_string
+.next_byte:
+ mov al, [es:bx]
+ call print_hex
+ mov si, SPACE
+ call print_string
+ inc bx
+ inc cx
+ cmp cx, 8
+ je .gap
+ jmp .after_gap
+.gap:
+ mov si, SPACE
+ call print_string
+.after_gap:
+ cmp cx, 16
+ jne .next_byte
+.print_chars:
+ xor cx, cx
+ sub bx, 16
+.next_char:
+ mov al, [es:bx]
+ call print_dump_char
+ inc bx
+ inc cx
+ cmp cx, 16
+ jne .next_char
+.newline:
+ mov si, NL
+ call print_string
+ cmp bx, 256
+ je .wait
+ jmp .cont
+.wait:
+ mov si, MESSAGE_PRESS_KEY_TO_CONTINUE
+ call print_string
+ call wait_for_keypress
+.cont:
+ cmp bx, 512
+ jne .next_line
+ ret
+
+DUMP_SEP:
+ db ": ", 0
+
+; compare strings
+; IN: si, di: start of two zero terminated strings
+; OUT: ax <0 si < di, >0 si > di; =0 si = di
+; CLOBBERS: si, di
+strcmp:
+ push dx
+.loop:
+ a32 mov dl, [esi]
+ a32 mov dh, [edi]
+ cmp dh, $0
+ je .done
+ cmp dl, $0
+ je .done
+ cmp dh, dl
+ jne .done
+ inc si
+ inc di
+ jmp .loop
+.done:
+ xor ax, ax
+ mov al, byte dh
+ sub al, byte dl
+.end:
+ pop dx
+ ret
+
+; compare strings up to a maximal number of characters
+; IN: si, di: start of two zero terminated strings, bx: number of chars
+; OUT: ax <0 si < di, >0 si > di; =0 si = di
+; CLOBBERS: si, di, bx
+strncmp:
+ push dx
+.loop:
+ a32 mov dl, [esi]
+ a32 mov dh, [edi]
+ cmp dh, $0
+ je .done
+ cmp dl, $0
+ je .done
+ cmp dh, dl
+ jne .done
+ inc si
+ inc di
+ dec bx
+ jz .done
+ jmp .loop
+.done:
+ xor ax, ax
+ mov al, byte dh
+ sub al, byte dl
+.end:
+ pop dx
+ ret
+
+; convert octal string to integer
+; IN: si, cx: pointer to the string and length of the string
+; OUT: eax: converted integer
+; clobbers: ebx
+octal_string_to_int:
+ xor ebx, ebx
+.next:
+ movzx eax, byte [esi]
+ inc esi
+ sub al, '0' ; assuming ASCII
+ shl ebx, 3 ; octal, multiply by 8
+ add ebx, eax ; add current digit
+ loop .next ; cx--
+ mov eax, ebx
+ ret
+
+MESSAGE_CHECKING_A20:
+ db "Checking A20 address gate.. ", 0
+
+MESSAGE_ENABLED:
+ db " enabled", 13, 10, 0
+
+MESSAGE_DISABLED:
+ db " disabled", 13, 10, 0
+
+MESSAGE_SWITCHING_TO_UNREAL_MODE:
+ db "Switching to unreal mode..", 0
+
+MESSAGE_DRIVE_PARAMETERS:
+ db "Boot parameters ", 0
+
+MESSAGE_EOF_REACHED:
+ db "Reached end of tar file..", 13, 10, 0
+
+MESSAGE_BOOTING_KERNEL:
+ db "Booting kernel..", 13, 10, 0
+
+MESSAGE_REBOOT:
+ db "Press any key for rebooting..", 13, 10, 0
+
+MESSAGE_NEXT_FLOPPY:
+ db "Insert next floppy and press any key to continue..", 13, 10, 0
+
+MESSAGE_PRESS_KEY_TO_CONTINUE:
+ db "Press any key to continue..", 13, 10, 0
+
+MESSAGE_KERNEL_NOF_REAL_SECTORS:
+ db "Number of real-mode kernel sectors: ", 0
+
+MESSAGE_KERNEL_NOF_PROTECTED_SECTORS:
+ db "Number of protected-mode kernel sectors: ", 0
+
+MESSAGE_KERNEL_BOOT_PROTOCOL:
+ db "Linux boot protocol version: ", 0
+
+MESSAGE_KERNEL_VERSION:
+ db "Linux kernel version: ", 0
+
+MESSAGE_INITRD_ADDRESS:
+ db "Ramdisk address: ", 0
+
+MESSAGE_INITRD_SIZE:
+ db "Ramdisk size: ", 0
+
+ERR_A20:
+ db "A20 ", 0
+
+ERR_DISK:
+ db "DISK ", 0
+
+ERR_KERN:
+ db "KERN ", 0
+
+USTAR_MAGIC:
+ db "ustar", 0
+
+KERNEL_MAGIC:
+ db 'HdrS', 0
+
+; data sections used for reading from floppy, default to some sane values
+; get probed and filled in during floppy drive probing
+FLOPPY_TYPE:
+ db 0x00 ; drive type, usually 0x04 after probing for 3 1/4" 1.44MB
+
+SECTORS_PER_CYLINDER:
+ db 0x3F ; detect parameters enters the correct value here (sectors + 1)
+ ; if detection fails, force int13 to read ahead
+NOF_HEADS:
+ db 0x01 ; number of heads + 1
+
+; read route reads and updates those values while reading from floppy
+CURRENT_SECTOR:
+ db 1
+
+CURRENT_CYLINDER:
+ db 0
+
+CURRENT_HEAD:
+ db 0
+
+CURRENT_RETRIES:
+ db 0
+
+FILE_BZIMAGE:
+ db "bzImage", 0
+
+FILE_RAMDISK:
+ db "ramdisk.img", 0
+
+FILE_EOF:
+ db "EOF", 0
+
+READ_STATE:
+ db STATE_READ_METADATA
+
+READ_DATA_SECTORS:
+ dd 0
+
+READ_DESTINATION_PTR:
+ dd 0x10000
+
+READ_DATA_SIZE:
+ dd 0
+
+KERNEL_STATE:
+ db STATE_KERNEL_READ_SECTOR_0
+
+KERNEL_NOF_REAL_MODE_SECTORS:
+ db 0
+
+KERNEL_NOF_PROTECTED_MODE_SECTORS:
+ dw 0
+
+KERNEL_CMD_LINE:
+ db "debug loglevel=7 earlycon=uart8250,io,0x3f8,9600n8 console=tty0 console=ttyS0,9600n8 rdinit=/sbin/init root=/dev/ram0 iommu=off", 0
+
+KERNEL_CMD_SIZE equ $-KERNEL_CMD_LINE
+
+KERNEL_BOOT_PROTOCOL_MAJOR:
+ db 0
+
+KERNEL_BOOT_PROTOCOL_MINOR:
+ db 0
+
+KERNEL_VERSION_PTR:
+ dw 0
+
+INITRD_ADDRESS:
+ dd 0
+
+INITRD_SIZE:
+ dd 0
+
+; make sure we have full sectors
+times (NOF_SECTORS_STAGE2+1)*512-($-$$) db 0