index : archi486 | |
Archlinux32 i486 tools | gitolite user |
summaryrefslogtreecommitdiff |
author | Andreas Baumann <mail@andreasbaumann.cc> | 2022-09-02 09:18:52 +0200 |
---|---|---|
committer | Andreas Baumann <mail@andreasbaumann.cc> | 2022-09-02 09:18:52 +0200 |
commit | 52be99d8c0862ff87db9a4f9ccec1ac4b5f7caed (patch) | |
tree | 1a1a1c98090afd8459cc8f045f9de2d4a7cba5ab /floppy/boot.asm | |
parent | 15adaba9eaa6a98c8b55bc5c5f73c3a9e0e55e7a (diff) |
-rw-r--r-- | floppy/boot.asm | 1408 |
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 |