%if 0 Multiboot header and loader 2008--2019 by C. Masloch Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. %endif %macro mbootchecksummed 1-2.nolist 1BADB002h dd %2 ; signature dd %1 ; flags dd -((%2+%1) & 0FFFF_FFFFh) ; checksum: dword sum of these three = 0 %endmacro %macro mboot2checksummed 0-1.nolist 0E852_50D6h dd %1 ; signature dd 0 ; platform (i386) dd mboot2_header_end - mboot2_header ; size of header, including header tags dd -((%1 + (mboot2_header_end - mboot2_header))) & 0FFFF_FFFFh ; checksum %endmacro MULTIBOOT_CMDLINE_LENGTH equ lsvclBufferLength MULTIBOOT_BASE equ (1024+64)*1024 ; one paragraph above the HMA MULTIBOOT_END equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0) MULTIBOOT_BSS_END equ MULTIBOOT_END \ + fromdwords(dwords(MULTIBOOT_CMDLINE_LENGTH)) MULTIBOOT_TARGET_SEGMENT equ 200h MULTIBOOT_TARGET equ MULTIBOOT_TARGET_SEGMENT << 4 MULTIBOOT_BPB equ MULTIBOOT_TARGET - 512 - 16 MULTIBOOT_CMDLINE_START equ MULTIBOOT_BPB + lsvCommandLine.start MULTIBOOT_STACK_TOP equ MULTIBOOT_CMDLINE_START %if _MULTIBOOT1 ; Multiboot header ; must be dword aligned and in first 8 KiB! align 4, db 0 mbootheader: mbootchecksummed 00010000h ; flags: provides info where to load image dd MULTIBOOT_BASE + mbootheader ; load address of header dd MULTIBOOT_BASE ; load address dd MULTIBOOT_END ; load area end (0 = load whole file) dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none) dd MULTIBOOT_BASE + mbootentry ; entry point %endif %if _MULTIBOOT2 ; Multiboot2 header ; qword (64-bit) aligned and in first 32 KiB align 8, db 0 mboot2_header: mboot2checksummed mboot2_tag_information_request: .: .type: dw 1 .flags: dw 0 .size: dd .end - . dd 1 ; command line dd 5 ; ROM-BIOS boot device .end: align 8, db 0 mboot2_tag_addresses: .: .type: dw 2 .flags: dw 0 .size: dd .end - . dd MULTIBOOT_BASE + mboot2_header ; load address of header dd MULTIBOOT_BASE ; load address dd MULTIBOOT_END ; load area end (0 = load whole file) dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none) .end: align 8, db 0 mboot2_tag_entrypoint: .: .type: dw 3 .flags: dw 0 .size: dd .end - . dd MULTIBOOT_BASE + mboot2entry ; entry point .end: align 8, db 0 mboot2_tag_end: .: .type: dw 0 .flags: dw 0 .size: dd .end - . .end: mboot2_header_end: %endif %unmacro mbootchecksummed 1-2.nolist 1BADB002h %unmacro mboot2checksummed 0-1.nolist 0E85250D6h %if ($-$$) > 8192 %fatal Multiboot header must be in the first 8 KiB %endif struc MBI mbiFlags: resd 1 resb 8 mbiBootDevice: resd 1 mbiCmdLine: resd 1 mbiModuleCount: resd 1 mbiModuleTable: resd 1 ; (More data follows but none which seems useful.) endstruc [cpu 386] [bits 32] numdef MULTIBOOT_CHECKS, 1 numdef MULTIBOOT_DEBUG, 0 numdef MULTIBOOT_HALT, 0 numdef MULTIBOOT_PROGRESS, 0 %if _MULTIBOOT_CHECKS || _MULTIBOOT_HALT mbootentry.halt: mboot2entry.halt: ; Unfortunately, as the environment is unknown, we can only ; literally halt here, as we don't know how to do I/O. cli @@: hlt jmp short @B %endif %if _MULTIBOOT1 ; INP: eax = 2BADB002h ; ebx-> multiboot info structure ; STT: loaded and executed according to Multiboot header, ie ; loaded at MULTIBOOT_BASE ; eip = MULTIBOOT_BASE+mbootentry ; in PM32 with paging disabled ; CPL 0 ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors ; DI ; ! stack not set up ; ! GDT might not be set up ; ! IDT might not be set up ; A20 on ; ; Note: Although A20 is on, HMA access without an XMM ; to manage it might be undesirable. Multiboot ; also doesn't tell us whether and if so then ; how we can switch A20. mbootentry: %if _MULTIBOOT_HALT jmp .halt %endif %if _MULTIBOOT_CHECKS [bits 16] test ax, 0 ; (test eax if in 32-bit segment) jmp short .halt ; (skipped if in 32-bit segment) [bits 32] cmp eax, 2BADB002h ; signature ? jne short .halt ; no --> smsw eax rol eax, 1 ; 2 = protection, 1 = paging and al, 3 ; mask off others cmp al, 2 ; PE but not PG ? jne short .halt ; no --> mov eax, cs and al, 3 ; CPL 0 ? jnz short .halt ; no --> %endif %if _MULTIBOOT_PROGRESS mov ebp, 0B8000h + 2 * 80 * 20 mov word [ebp], 5000h | '1' inc ebp inc ebp %endif ; prepare this and that or edx, -1 ; locate boot drive in Multiboot info structure test byte [ ebx + mbiFlags ], 2 ; boot device info valid ? jz @F mov eax, [ ebx + mbiBootDevice ]; get the info rol eax, 16 xchg al, ah mov edx, eax ; dl = boot load unit (or 0FFh), ; dh = partition (or 0FFh) ; (edxh = subpartition numbers) @@: %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | '2' inc ebp inc ebp %endif xor eax, eax mov edi, MULTIBOOT_END mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 test byte [ ebx + mbiFlags ], 4 ; command line valid ? jz @F mov esi, [ ebx + mbiCmdLine ] rep movsd mov byte [ edi - 1 ], 0 ; insure it is terminated ; (this may truncate the line) @@: rep stosd %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | '3' inc ebp inc ebp %endif %if _MULTIBOOT2 jmp multiboot_1_2_common %endif %endif %if _MULTIBOOT2 ; INP: eax = 36D7_6289h ; ebx-> multiboot2 info structure ; STT: loaded and executed according to Multiboot2 header, ie ; loaded at MULTIBOOT_BASE ; eip = MULTIBOOT_BASE+mboot2entry ; in PM32 with paging disabled ; CPL 0 ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors ; DI ; ! stack not set up ; ! GDT might not be set up ; ! IDT might not be set up ; A20 on ; ; Note: Although A20 is on, HMA access without an XMM ; to manage it might be undesirable. Multiboot ; also doesn't tell us whether and if so then ; how we can switch A20. mboot2entry: %if _MULTIBOOT_HALT jmp .halt %endif %if _MULTIBOOT_CHECKS [bits 16] test ax, 0 ; (test eax if in 32-bit segment) jmp short .halt ; (skipped if in 32-bit segment) [bits 32] cmp eax, 36D7_6289h ; signature ? jne .halt ; no --> smsw eax rol eax, 1 ; 2 = protection, 1 = paging and al, 3 ; mask off others cmp al, 2 ; PE but not PG ? jne .halt ; no --> mov eax, cs and al, 3 ; CPL 0 ? jnz .halt ; no --> %endif %if _MULTIBOOT_PROGRESS mov ebp, 0B8000h + 2 * 80 * 20 mov word [ebp], 5000h | 'A' inc ebp inc ebp %endif or edx, -1 ; initialise boot partition info xor eax, eax mov edi, MULTIBOOT_END mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 rep stosd ; initialise command line buffer add ebx, 8 ; skip fixed header (size, reserved) .tags_loop: %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'B' inc ebp inc ebp %endif mov eax, dword [ebx] test eax, eax ; end of tags ? jz .tags_end ; yes --> cmp eax, 1 je .cmdline cmp eax, 5 je .partition .tags_next: %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'C' inc ebp inc ebp %endif add ebx, dword [ebx + 4] ; -> after end of tag ; (at padding or next tag) add ebx, 7 and ebx, ~7 ; skip any padding jmp .tags_loop .cmdline: %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'D' inc ebp inc ebp %endif lea esi, [ebx + 8] mov edi, MULTIBOOT_END mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 rep movsd ; copy command line mov byte [ edi - 1 ], 0 ; insure it is terminated ; (this may truncate the line) jmp .tags_next .partition: %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'E' inc ebp inc ebp %endif mov eax, dword [ebx + 8] cmp eax, 100h jae @F mov dl, al ; get ROM-BIOS unit @@: mov eax, dword [ebx + 12] cmp eax, 100h jae @F mov dh, al ; get partition number (0 = first primary) @@: jmp .tags_next .tags_end: xor eax, eax %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'F' inc ebp inc ebp %endif %endif multiboot_1_2_common: mov esp, MULTIBOOT_STACK_TOP ; set a valid stack, at 8 KiB ; (also required by 86M entry later) %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 's' inc ebp inc ebp %endif ; A20 is on. set up some things xor ecx, ecx mov edi, 1024*1024 mov cl, 10h ; (ecx = 10h) rep stosd ; clear this area so A20 tests succeed ; The above writes to 10_0000h..10_003Fh, leaving edi = 10_0040h mov al, 0C0h ; (eax = C0h) mov byte [ edi-40h+eax ], 0EAh mov [ edi-40h+eax+1 ], eax ; write jump for CP/M entry hack here ; The above writes to 10_00C0h..10_00C4h %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 't' inc ebp inc ebp %endif ; relocate image to a good location, 2000h mov esi, MULTIBOOT_BASE ; -> iniload image mov edi, MULTIBOOT_TARGET mov ecx, (payload.actual_end - $$ + 0 + 3) / 4 rep movsd %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'u' inc ebp inc ebp %endif MULTIBOOT_ESI_AFTER equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0 + 3) / 4 * 4 %if MULTIBOOT_ESI_AFTER != MULTIBOOT_END mov esi, MULTIBOOT_END %endif mov edi, MULTIBOOT_CMDLINE_START mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4 rep movsd %if _MULTIBOOT_PROGRESS mov word [ebp], 5000h | 'v' inc ebp inc ebp %endif ; now back to real mode o32 lgdt [ MULTIBOOT_BASE+mbootgdtdesc ] ; set our GDT mov ax, 10h mov ds, ax mov es, ax mov ss, ax mov fs, ax mov gs, ax ; set 64 KiB segment limits o32 push byte 0 o32 popf ; reset all flags jmp 08h:.pm16 ; use 16-bit selector [bits 16] ; now really switch to real mode ; (already executing relocated code) .pm16: mov eax, cr0 dec ax mov cr0, eax ; clear PE bit jmp dword MULTIBOOT_TARGET_SEGMENT:.rm ; reload cs ; Set up registers and the environment for ; the kernel entry point. It doesn't need initialised ; segment registers but we'll initialise them in case ; they still contain selector content. .rm: ; some sources indicate we should set the IDT o32 lidt [cs:mbootidtdesc] ; set IDT to RM IVT xor ax, ax mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ; (cs should be = 200h now, need no far jump) ; cs = 200h, ip = mboot_86m_entry ; ds = es = fs = gs = ss = 0 ; sp = 2000h - 512 - 16 + lsvCommandLine.start ; dl = boot unit (or -1) ; dh = boot partition (0..3 = primary, 4+ = logical, or -1) mov bp, MULTIBOOT_BPB ; -> pseudo BPB mov di, MULTIBOOT_BPB + lsvCommandLine.signature mov ax, lsvclSignature stosw ; store signature mov cx, (- (lsvCommandLine.signature + 2) + 512 + 3) / 4 xor eax, eax rep stosd ; clear ; lsvFirstCluster = 0 (same as for FreeDOS entrypoint) ; bsBPB + bpbHiddenSectors = 0 (invalid or unpartitioned) ; bsBPB + bpbCHSSectors = 0 ; bsBPB + bpbCHSHeads = 0 inc ax mov dword [bp + lsvDataStart], eax ; lsvDataStart = 1 (placeholder) __CPU__ mov word [bp + bsBPB + bpbNumRootDirEnts], ax mov word [bp + bsBPB + bpbSectorsPerFAT], ax ; do not detect as FAT32 mov byte [bp + bsBPB + bpbSectorsPerCluster], al mov word [bp + bsBPB + bpbBytesPerSector], 512 mov word [bp + bsBPB + bpbTotalSectors], 8192 ; 1 C/s * 8 Ki sectors = 8 Ki clusters, detect as FAT16 ; that is, do not load FAT at all (would load a FAT12) mov byte [bp + bsBPB + bpbNew + bpbnBootUnit], dl ; set boot load unit %if _LSVEXTRA %if _MULTIBOOT_DEBUG mov ax, dx call mb_disp_ax_hex %endif xchg dl, dh ; dl = Multiboot spec partition number ; (0..3 primary, 4+ logical, -1 invalid) inc dx ; 0 invalid, 1..4 primary, 5+ logical mov dh, lsvefPartitionNumber mov word [bp + lsvExtra], dx %endif jmp freedos_entry.multiboot_entry %if _MULTIBOOT_DEBUG mb_disp_ax_hex: ; ax xchg al,ah call mb_disp_al_hex ; display former ah xchg al,ah ; and fall trough for al mb_disp_al_hex: ; al push cx mov cl,4 ror al,cl call mb_disp_al_lownibble_hex ; display former high-nibble rol al,cl pop cx ; and fall trough for low-nibble mb_disp_al_lownibble_hex: push ax ; save ax for call return and al,00001111b ; high nibble must be zero add al,'0' ; if number is 0-9, now it's the correct character cmp al,'9' jna .decimalnum ; if we get decimal number with this, ok --> add al,7 ; otherwise, add 7 and we are inside our alphabet .decimalnum: call mb_disp_al pop ax retn mb_disp_al: push ax push bx push bp mov ah, 0Eh mov bx, 7 int 10h pop bp pop bx pop ax retn %endif align 16, db 0 mbootgdt: dw 0,0 db 0,0,0,0 ; selector 8: 16-bit real mode CS ; base = 00002000h, limit 0FFFFh (1 B Granularity), present ; type = 16-bit code execute/read only/conforming, DPL = 0 dw 0FFFFh,MULTIBOOT_TARGET db 0,9Eh,0,0 ; selector 10h: 16-bit real mode DS ; base = 00000000h, limit 0FFFFh (1 B Granularity), present ; type = 16-bit data read/write, DPL = 0 dw 0FFFFh,0 db 0,92h,0,0 endarea mbootgdt mbootgdtdesc: dw mbootgdt_size-1 ; limit dd MULTIBOOT_BASE+mbootgdt ; address mbootidtdesc: dw 400h-1 ; limit dd 0 ; address (86M IVT)